Fix Signal send handling and wrap inbound messages
ober
8d98325c42c88855a39c21f18392885137cc38c3
--- a/Makefile +++ b/Makefile @@ -52,6 +52,7 @@ run-tui: binary tui-shim ./$(BIN) tui $(ARGS) test: binary + $(JEXEC) tests/test-send-result.ss ./$(BIN) --help >/dev/null && echo "smoke ok" install: binary new file mode 100644 --- /dev/null +++ b/docs/full-port.md @@ -0,0 +1,1399 @@ +# Full Native Signal Port Plan + +Status: planning document +Date: 2026-06-09 +Repository: `jerboa-signal` + +## 1. Objective + +Build a native Signal backend for `jerboa-signal` that removes the hard dependency on the `signal-cli` Java process while continuing to use upstream Signal cryptography as provided by `libsignal`. + +The intended end state is: + +- `jerboa-signal` owns the client/service layer that talks to Signal servers. +- `jerboa-signal` stores account, session, message, contact, group, and sync state in `jsqlite`. +- Upstream `libsignal` remains the cryptographic and protocol primitive implementation. +- A small Rust crate exposes the needed `libsignal` operations through a stable C ABI. +- Jerboa links that Rust crate into the final binary. +- The existing `signal-cli` backend remains available during migration as a reference backend and fallback until native parity is proven. + +This is not a plan to rewrite Signal cryptography in Jerboa. Rewriting `libsignal` would be a much larger and riskier project, and it would create unnecessary security and compatibility risk. The correct split is: + +- Rust/libsignal: crypto, Signal protocol primitives, group crypto primitives, zero-knowledge group support, key/session machinery. +- Jerboa: application orchestration, storage, network transport, UI integration, retries, tracing, migration, business logic. + +## 2. Why This Work Exists + +The current app uses `signal-cli` as a subprocess or JSON-RPC service. That gives us a working path quickly, but it also means we inherit behavior and packaging issues outside our control. + +Recent concrete issues: + +- The native Homebrew `signal-cli` build failed during captcha submission because a GraalVM native image could not resolve an internal Kotlin reflection class. +- Sends could appear successful while not actually delivering unless we inspect per-recipient result payloads carefully. +- Rate limit, captcha, and challenge behavior is difficult to present correctly when the real service state is hidden behind another CLI. +- `signal-cli` releases can stop working when Signal changes server behavior. +- The Java process adds deployment complexity for an otherwise Jerboa-centered app. + +The replacement should preserve the parts that are trustworthy and hard to reproduce, especially upstream cryptography, while moving the service/client behavior into code we can debug, trace, and ship as part of this project. + +## 3. Source And License Reality + +Relevant upstream projects: + +- `libsignal`: <https://github.com/signalapp/libsignal> +- `Signal-Android`: <https://github.com/signalapp/Signal-Android> +- `signal-cli`: <https://github.com/AsamK/signal-cli> + +Important facts: + +- `libsignal` is open source and is the official Signal client library. Its core is Rust with bindings for Java, Swift, TypeScript, and other clients. +- `Signal-Android` is open source and contains current client behavior, service models, and API usage patterns. +- `signal-cli` is open source and implements an unofficial Java client using Signal libraries and service code patterns. +- `libsignal` is AGPL-3.0-only at the time this plan was written. +- `signal-cli` is GPL-3.0-or-later at the time this plan was written. +- Copying code from `signal-cli` or `Signal-Android` has license and derivative-work implications. +- Linking `libsignal` into a final binary also has license implications. + +This document is technical, not legal advice. Before distributing binaries, confirm the project's license posture and source-distribution obligations. + +The preferred implementation style is: + +- Use `libsignal` as a normal upstream dependency. +- Study `signal-cli` and `Signal-Android` for behavior. +- Avoid copying large bodies of Java/Kotlin code unless we intentionally accept the license consequences. +- Keep all third-party licenses and exact source revisions documented in the release artifact. + +## 4. Current Architecture + +Current rough flow: + +```text +TUI / command code + -> Jerboa signal modules + -> signal-cli subprocess or JSON-RPC daemon + -> signal-cli Java service layer + -> libsignal / Signal service APIs + -> Signal servers +``` + +Important current local modules: + +- `signal/rpc.ss`: JSON-RPC client calls into `signal-cli`. +- `signal/rpc-actor.ss`: actor and timeout behavior around RPC calls. +- `signal/cmd-send.ss`: command-line send path. +- `signal/tui/main.ss`: interactive TUI path. +- `signal/send-result.ss`: send result validation and classification. +- `signal/trace.ss`: low-level tracing and command selection. + +Current behavior to preserve: + +- Existing TUI interactions. +- Existing history behavior. +- Existing command-line send behavior. +- Send tracing for debugging. +- Per-conversation local rate limit handling. +- Captcha challenge presentation and retry. +- Clear classification of transport failure, rate limit failure, captcha challenge, and per-recipient failure. + +## 5. Target Architecture + +Target rough flow: + +```text +TUI / command code + -> signal/engine.ss + -> native Signal backend + -> service orchestration in Jerboa + -> jsqlite persistent store + -> Rust signal-native static library + -> upstream libsignal + -> HTTP/WebSocket transport + -> Signal servers +``` + +The engine boundary is important. The UI and command modules should call an abstract backend so we can keep both implementations during migration: + +```text +signal/engine.ss + signal-cli-backend + native-backend +``` + +High-level proposed module layout: + +```text +signal/ + engine.ss ; backend protocol and common result types + backend/ + signal-cli.ss ; adapter around existing rpc/cmd behavior + native.ss ; adapter around native service implementation + native/ + account.ss ; local account/device state + provision.ss ; linked-device provisioning + service.ss ; Signal service HTTP/WebSocket calls + receive.ss ; receive queue loop and envelope handling + send.ss ; message construction, encryption, submission + attachments.ss ; upload/download/verification + groups.ss ; groups v2 and sender key orchestration + contacts.ss ; contacts, profiles, identity names + store.ss ; jsqlite schema and transactions + migrations.ss ; DB migrations + ffi.ss ; Jerboa bindings to Rust C ABI + events.ss ; normalized app events emitted upward + errors.ss ; native error taxonomy +native/ + signal-native/ + Cargo.toml + src/lib.rs ; C ABI exported functions + src/error.rs + src/buffer.rs + src/store.rs ; optional Rust-side store abstractions if needed + include/signal_native.h +``` + +The final binary should link the Rust static library and expose no Java runtime requirement. + +## 6. Backend Interface + +Create a narrow interface that represents what the application needs, not what `signal-cli` happens to expose. + +Initial interface shape: + +```scheme +(backend-name backend) ; symbol/string +(backend-version backend) ; structured version metadata +(backend-ready? backend) ; account/device/store usable? + +(backend-list-accounts backend) +(backend-current-account backend) +(backend-link-device backend options) ; provisioning flow +(backend-submit-captcha backend challenge) + +(backend-send-message backend request) ; returns normalized send result +(backend-receive-once backend options) ; fetch/decrypt one batch +(backend-start-receive-loop backend sink opts) +(backend-stop-receive-loop backend) + +(backend-list-contacts backend) +(backend-list-groups backend) +(backend-get-conversation backend conversation-id) +(backend-mark-read backend conversation-id timestamps) + +(backend-upload-attachment backend attachment) +(backend-download-attachment backend pointer) +``` + +The existing UI should depend on normalized app-level objects: + +- Conversation ID. +- Recipient address. +- Message body. +- Attachments. +- Timestamp. +- Send status. +- Failure reason. +- Captcha challenge. +- Rate limit wait. +- Delivery/read receipts. +- Decrypted incoming message event. + +The UI should not know whether the backend is `signal-cli` or native. + +## 7. Native Rust FFI Strategy + +Create a Rust crate at `native/signal-native` that depends on a pinned upstream `libsignal` revision or crate version. + +Build output: + +- `staticlib` for final binary linking. +- Optional `cdylib` during development for easier local testing. +- C header generated manually or via `cbindgen`. + +Rules: + +- Export only `extern "C"` functions. +- Do not expose Rust structs across the ABI. +- Use opaque handles for long-lived state. +- Use explicit allocation and free functions for buffers returned to Jerboa. +- Return structured status values, not panics. +- Catch panics at the FFI boundary. +- Never log keys, plaintext, session blobs, sealed sender certificates, access keys, or attachment plaintext. +- Pin the upstream version at the start of every phase and include it in test output. + +Preferred result convention: + +```c +typedef struct { + int32_t code; + uint8_t *data; + size_t data_len; + uint8_t *error_json; + size_t error_json_len; +} SignalNativeResult; +``` + +Every FFI call returns either: + +- `code == 0` and `data` contains a serialized result. +- `code != 0` and `error_json` contains a structured error. + +Provide one free function: + +```c +void signal_native_result_free(SignalNativeResult result); +``` + +The serialized format should initially be JSON for ease of debugging. Once stable, switch hot paths to protobuf or a compact binary encoding only if profiling proves it matters. + +Minimum metadata functions: + +```c +SignalNativeResult signal_native_version(void); +SignalNativeResult signal_native_build_info(void); +SignalNativeResult signal_native_supported_features(void); +``` + +Candidate crypto/session functions: + +```c +SignalNativeResult signal_native_generate_identity_key_pair(void); +SignalNativeResult signal_native_generate_registration_id(void); +SignalNativeResult signal_native_generate_pre_keys(uint32_t start, uint32_t count); +SignalNativeResult signal_native_generate_signed_pre_key( + const uint8_t *identity_key_pair, size_t identity_key_pair_len, + uint32_t key_id, + uint64_t timestamp_ms); + +SignalNativeResult signal_native_process_prekey_bundle( + const uint8_t *local_store_json, size_t local_store_json_len, + const uint8_t *remote_bundle_json, size_t remote_bundle_json_len); + +SignalNativeResult signal_native_encrypt_message( + const uint8_t *session_json, size_t session_json_len, + const uint8_t *plaintext, size_t plaintext_len); + +SignalNativeResult signal_native_decrypt_message( + const uint8_t *session_json, size_t session_json_len, + const uint8_t *ciphertext, size_t ciphertext_len); + +SignalNativeResult signal_native_sealed_sender_encrypt( + const uint8_t *request_json, size_t request_json_len); + +SignalNativeResult signal_native_sealed_sender_decrypt( + const uint8_t *request_json, size_t request_json_len); +``` + +Candidate group/sender-key functions: + +```c +SignalNativeResult signal_native_create_sender_key_distribution_message( + const uint8_t *request_json, size_t request_json_len); + +SignalNativeResult signal_native_process_sender_key_distribution_message( + const uint8_t *request_json, size_t request_json_len); + +SignalNativeResult signal_native_group_encrypt( + const uint8_t *request_json, size_t request_json_len); + +SignalNativeResult signal_native_group_decrypt( + const uint8_t *request_json, size_t request_json_len); +``` + +These names are placeholders. The implementation must map them to actual current `libsignal` APIs during the FFI spike. + +## 8. Storage Plan With jsqlite + +Use `~/mine/jsqlite` as the SQLite replacement. Do not use C SQLite unless the user explicitly changes this decision. + +Storage goals: + +- Durable account/device state. +- Atomic session updates. +- Never reuse prekeys incorrectly. +- Keep enough raw event data for debugging without leaking secrets. +- Support schema migrations. +- Support import/migration from current `signal-cli` data only if feasible and explicitly chosen. + +Suggested database areas: + +### Metadata + +Tables: + +- `schema_migrations` +- `store_metadata` +- `backend_metadata` + +Data: + +- Schema version. +- Native backend version. +- Upstream `libsignal` version. +- Account identifier. +- Device ID. +- Created/updated timestamps. + +### Account And Device + +Tables: + +- `accounts` +- `devices` +- `account_credentials` +- `service_tokens` + +Data: + +- ACI/PNI/service identifiers. +- Local phone number if available. +- Device ID. +- Device name. +- Registration state. +- Service auth material. +- Last successful sync. +- Server capability flags. + +### Identity And Sessions + +Tables: + +- `identity_keys` +- `recipient_identities` +- `sessions` +- `prekeys` +- `signed_prekeys` +- `pq_prekeys` +- `kyber_prekeys` +- `sender_keys` + +Data: + +- Local identity key pair. +- Remote identity keys and trust decisions. +- Per-recipient/per-device sessions. +- One-time prekeys and upload state. +- Signed prekeys and replacement schedule. +- Post-quantum prekey material if required by current `libsignal`. +- Sender key records for group messaging. + +Session writes must be transactional. A decrypt/encrypt operation that mutates session state must commit exactly once with the associated message state. + +### Recipients, Contacts, Profiles + +Tables: + +- `recipients` +- `contacts` +- `profiles` +- `profile_keys` +- `recipient_devices` +- `recipient_capabilities` + +Data: + +- UUID/ACI/PNI mappings. +- Phone numbers when available. +- Display names. +- Profile keys. +- Avatar pointers. +- Device lists. +- Capability flags. +- Blocked/muted/archived local state if needed. + +### Conversations And Messages + +Tables: + +- `conversations` +- `messages` +- `message_revisions` +- `message_attachments` +- `message_receipts` +- `message_failures` +- `outbox` +- `pending_retries` + +Data: + +- Direct and group conversation identity. +- Message body, timestamps, sender, recipient. +- Delivery/read receipts. +- Send attempts and per-recipient results. +- Captcha/rate-limit failure records. +- Retry scheduling. +- Decrypted event linkage. + +Message plaintext storage policy must be decided explicitly. If plaintext is stored, document file permissions, backup behavior, and passphrase/encryption strategy. + +### Groups + +Tables: + +- `groups` +- `group_members` +- `group_roles` +- `group_revisions` +- `group_send_endorsements` +- `group_pending_members` +- `group_banned_members` + +Data: + +- Groups v2 identifiers. +- Master keys or derived group secrets, if required. +- Revision numbers. +- Membership. +- Roles. +- Pending invite state. +- Sender key state. +- Access control metadata. + +### Attachments + +Tables: + +- `attachments` +- `attachment_uploads` +- `attachment_downloads` +- `attachment_cache` + +Data: + +- CDN IDs. +- Attachment pointers. +- Encryption keys. +- Digest/checksum. +- MIME type. +- Local file path. +- Upload/download status. +- Expiration/cleanup policy. + +Attachment plaintext should not be logged. Temporary files should be cleaned after failure. + +## 9. Network And Service Layer + +The native backend must implement the Signal service layer currently hidden by `signal-cli`. + +Major service areas: + +- Linked-device provisioning. +- Authenticated HTTP requests. +- WebSocket receive queue. +- Message send endpoint. +- Attachment upload endpoint. +- Attachment download endpoint. +- Profile fetch/update. +- Contact discovery or recipient resolution. +- Group state fetch/update. +- Captcha/proof challenge submission. +- Sync messages. +- Receipts. +- Prekey upload/refill. + +Implementation approach: + +1. Inventory current API behavior from `signal-cli` and `Signal-Android`. +2. Define typed Jerboa request/response records. +3. Generate or port protobuf message definitions where required. +4. Implement one HTTP/WebSocket transport module with tracing hooks. +5. Keep redaction centralized. +6. Store enough request metadata to debug failures without storing secrets. + +Do not scatter endpoint URLs, auth headers, retry behavior, or redaction logic across feature modules. + +## 10. Receive Flow + +Target receive flow: + +```text +connect receive websocket + -> fetch encrypted envelopes + -> persist raw envelope metadata + -> decrypt envelope with libsignal + -> classify message type + -> apply session mutations atomically + -> apply message/contact/group mutations atomically + -> acknowledge delivery to service + -> emit normalized app event + -> update history/TUI +``` + +Required message classes: + +- Plain direct messages. +- Sealed sender direct messages. +- Sync messages from linked devices. +- Delivery receipts. +- Read receipts. +- Typing messages. +- Contact/profile updates. +- Attachment messages. +- Group messages. +- Group state updates. +- Sender key distribution messages. +- Session repair or retry messages. + +Initial MVP should support only what is needed for direct text messages, but the event model should not block the later classes. + +Receive exit criteria: + +- Can connect as a linked device. +- Can receive a direct text message from a known contact. +- Can decrypt the message. +- Can store it in `jsqlite`. +- Can show it in the existing history/TUI. +- Can acknowledge it so it does not loop forever. +- Can preserve and inspect a redacted trace when decryption fails. + +## 11. Send Flow + +Target direct send flow: + +```text +user sends message + -> create outbox row + -> resolve recipient and devices + -> ensure sessions exist for each device + -> build content payload + -> encrypt per recipient/device with libsignal + -> submit service request + -> parse per-recipient/per-device result + -> persist final status or retry state + -> emit normalized send result +``` + +The send result must preserve: + +- Global request failure. +- Captcha challenge. +- Server rate limit with wait duration. +- Per-recipient success/failure. +- Per-device success/failure. +- Stale device list failures. +- Identity key change failures. +- Network timeout. +- Retryable vs permanent failure. + +This project already learned that "the command returned success" is not enough. The native backend must make success mean "the service accepted the message for the intended recipient/device set" or clearly label the narrower result. + +Direct send exit criteria: + +- Can send a direct text message to one recipient. +- Can send to a recipient with multiple devices. +- Can correctly handle missing/stale sessions. +- Can report identity change separately from rate limit. +- Can report captcha challenge without losing the composed message. +- Can retry after challenge submission. +- Can verify delivery using a second client during manual test. + +## 12. Captcha, Proofs, And Rate Limits + +Captcha and proof challenges are service-level controls. They cannot be treated as local throttles. + +Required behavior: + +- Preserve the original failed send request. +- Preserve the challenge token or challenge metadata. +- Display the exact challenge URL/token required by the service. +- Submit the solved challenge through the same account/backend context. +- Retry only when the service confirms challenge acceptance. +- Keep the user-visible distinction between: + - local throttle, + - server rate limit, + - captcha required, + - challenge accepted but message not yet sent, + - retry succeeded, + - retry failed. + +Important operational caveat: + +- If a challenge is solved in a browser on a different network path than the client uses to submit/send, Signal may still reject or rate-limit the send. The implementation cannot guarantee success if the remote server and browser IP/reputation differ. + +Do not automatically spam retries after a challenge. A bad retry loop can worsen server-side reputation. + +## 13. Provisioning And Account Bootstrap + +The native backend should initially support linked-device mode, matching the current practical usage. + +Provisioning tasks: + +- Generate local identity/device material. +- Request provisioning URI or QR payload. +- Present provisioning URI/QR in CLI/TUI. +- Accept provisioning response from Signal service. +- Store account/device credentials in `jsqlite`. +- Fetch initial contact/device/profile/group state. +- Upload prekeys as needed. +- Start receive queue. + +Exit criteria: + +- A fresh native store can link as a secondary device. +- The official Signal client shows the linked device. +- Native backend can receive a sync message. +- Restarting the app preserves device state. +- Unlinking/revocation is detected and presented clearly. + +Primary account registration is out of scope for the first port unless explicitly added. Linked-device support is enough to replace the current app usage and avoids more account-abuse-sensitive registration flows. + +## 14. Attachments + +Attachment support should come after direct text send/receive is reliable. + +Required behavior: + +- Upload attachment plaintext only after encrypting locally. +- Store local pointer, digest, key, content type, and size. +- Submit attachment pointer in message content. +- Download encrypted attachment from CDN. +- Verify digest/checksum. +- Decrypt locally. +- Store or stream plaintext according to local policy. +- Clean temporary files on failure. + +Exit criteria: + +- Send one small image/file to a direct recipient. +- Receive one small image/file from a direct recipient. +- Reject corrupted downloads. +- Show useful failure when upload succeeds but message send fails. +- Avoid logging plaintext paths when privacy mode requires redaction. + +## 15. Groups V2 + +Groups are the largest feature area after basic direct messaging. + +Required behavior: + +- Parse and store group identifiers and revisions. +- Fetch group state. +- Maintain member list and roles. +- Process group update messages. +- Maintain sender keys. +- Send group messages. +- Handle membership changes. +- Handle profile/name/avatar metadata. +- Respect access control and role rules. + +Likely upstream areas involved: + +- `libsignal` group/sender-key APIs. +- `libsignal` zkgroup APIs. +- Signal service group endpoints. +- Signal-Android group update behavior. +- `signal-cli` group send/fetch implementation. + +Group exit criteria: + +- Can receive group text message. +- Can send group text message. +- Can handle a member added after local state exists. +- Can handle a member removed. +- Can handle group revision changes. +- Can recover from sender key missing/stale state. + +Groups should not be attempted until direct messaging and session persistence are boring. + +## 16. Sync Messages + +As a linked device, native backend must handle sync messages from the primary device and other linked devices. + +Required sync categories: + +- Sent messages from another linked device. +- Read receipts. +- Contact updates. +- Profile updates. +- Block list updates. +- Sticker/attachment metadata if relevant. +- Group state updates. +- Configuration changes. + +Initial MVP can store unknown sync messages in a redacted raw event log and ignore them, but direct sent-message sync should be handled early so local history does not diverge badly. + +Exit criteria: + +- Sending from the official client appears in native history. +- Reading in native client can emit or store read state consistently. +- Unknown sync messages do not crash receive loop. + +## 17. Error Taxonomy + +Define one native error model and map every backend to it. + +Suggested categories: + +- `ok` +- `network-timeout` +- `network-unreachable` +- `service-unavailable` +- `unauthorized` +- `device-unlinked` +- `captcha-required` +- `rate-limited` +- `identity-changed` +- `stale-device-list` +- `session-missing` +- `session-corrupt` +- `decrypt-failed` +- `attachment-upload-failed` +- `attachment-download-failed` +- `storage-failed` +- `ffi-failed` +- `protocol-unsupported` +- `unknown` + +Every error should include: + +- Stable category. +- Human-readable message. +- Retryability. +- Optional wait duration. +- Optional challenge payload. +- Optional recipient/device scope. +- Redacted raw service code. +- Trace ID. + +The UI should never parse raw upstream JSON directly. + +## 18. Tracing And Debugging + +Tracing is mandatory for this project because Signal service behavior changes and some failures are server-driven. + +Trace requirements: + +- Global trace ID per send/receive operation. +- Redacted HTTP method/path/status. +- Redacted WebSocket envelope metadata. +- Backend version and upstream `libsignal` version. +- Recipient count and device count, but not plaintext addresses unless debug mode explicitly allows it. +- Send result classification. +- Captcha/rate-limit state transitions. +- Storage transaction boundaries for send/receive. +- FFI call name, duration, and status, but not inputs containing secrets or plaintext. + +Recommended trace files: + +```text +trace/ + yyyy-mm-dd/ + send-<trace-id>.jsonl + receive-<trace-id>.jsonl +``` + +Redaction should be centralized in one module. Do not rely on each caller remembering to redact. + +## 19. Testing Strategy + +Testing must be layered because the riskiest behavior spans FFI, storage, network, and external service state. + +### Unit Tests + +Rust: + +- Buffer allocation/free. +- Error conversion. +- Panic handling at FFI boundary. +- JSON/protobuf input validation. +- `libsignal` wrapper functions with local test vectors. + +Jerboa: + +- Backend interface conformance. +- Send result normalization. +- Error mapping. +- Store migrations. +- Store transaction behavior. +- Redaction. +- Retry scheduling. + +### Golden Fixtures + +Keep redacted fixtures for: + +- Successful direct send. +- Captcha-required send. +- Rate-limited send. +- Per-recipient partial failure. +- Successful direct receive. +- Decrypt failure. +- Attachment pointer message. +- Group message. +- Unknown future message type. + +Fixtures should be generated from: + +- Existing `signal-cli` traces. +- Native backend traces. +- Synthetic unit-level payloads. + +### Integration Tests + +Use at least two test Signal accounts/devices: + +- Account A official app plus native linked device. +- Account B official app. +- Optional Account C for group tests. + +Direct messaging matrix: + +- B sends to A native. +- A native sends to B. +- A official sends to B and native sync sees it. +- B has multiple devices. +- Device list changes between session setup and send. +- Identity key change is detected. + +Attachment matrix: + +- Small text file. +- Small image. +- Larger file near service limit. +- Corrupted download. +- Failed upload retry. + +Group matrix: + +- Receive group message. +- Send group message. +- Add member. +- Remove member. +- Rename group. +- Change avatar. +- Sender key refresh. + +### Manual Tests + +Some flows cannot be reliably automated without risking service reputation: + +- Captcha challenge. +- Long server rate-limit waits. +- Account unlink/relink. +- Suspicious-network behavior. + +Document exact manual commands and expected results for these flows. + +## 20. Security Requirements + +Security rules: + +- Let upstream `libsignal` handle cryptographic primitives. +- Never implement custom crypto except glue required by upstream APIs. +- Zeroize Rust buffers containing secrets when practical. +- Do not log plaintext, private keys, session records, access keys, auth tokens, or attachment keys. +- Mark secret-containing structs clearly. +- Keep DB file permissions restrictive. +- Keep temporary attachment files restrictive. +- Avoid crash dumps containing secret memory. +- Do not expose secret material through formatted exceptions. +- Do not send secret payloads through generic debug printers. + +FFI-specific rules: + +- Validate all pointers and lengths. +- Bound input sizes. +- Catch panics. +- Free all returned buffers exactly once. +- Provide leak tests. +- Prefer immutable byte slices at the boundary. +- Do not hold Jerboa-owned pointers after a call returns. + +Storage-specific rules: + +- Key/session updates must be atomic with message processing. +- Never reuse one-time prekeys after commit. +- Treat failed transaction rollback carefully around decrypt/send operations. +- Include migrations in tests. + +## 21. Build And Packaging + +Build requirements: + +- Build Rust crate before Jerboa final link. +- Pin Rust toolchain or use a checked minimum supported Rust version. +- Pin `libsignal` dependency exactly. +- Include upstream version in binary metadata. +- Include all license files in release artifacts. +- Support clean rebuild from source. + +Proposed Makefile targets: + +```make +native-build +native-test +native-clean +native-header +native-check +test-native-backend +``` + +Proposed generated artifacts: + +```text +native/signal-native/target/ +native/signal-native/include/signal_native.h +build/libsignal_native.a +build/signal-native-build-info.json +``` + +Do not commit Rust `target/` output. + +## 22. Version Tracking + +Every release should record: + +- `jerboa-signal` git commit. +- `jsqlite` git commit. +- `libsignal` version and git commit. +- Signal service behavior reference: + - `Signal-Android` commit inspected. + - `signal-cli` commit or release inspected. +- Protobuf/schema generation timestamp. +- Test account matrix last passed date. + +Weekly upstream watch list: + +- `libsignal` releases. +- `Signal-Android` service/protobuf changes. +- `signal-cli` release notes and bug fixes. +- Server-side breakage reports from issue trackers. + +Version bump checklist: + +1. Read upstream release notes. +2. Diff public `libsignal` APIs used by `signal-native`. +3. Diff service model/protobuf changes in `Signal-Android`. +4. Diff `signal-cli` send/receive/provisioning behavior. +5. Update pinned dependency. +6. Regenerate protobuf bindings if needed. +7. Run Rust unit tests. +8. Run Jerboa unit tests. +9. Run direct send/receive integration tests. +10. Run attachment tests if affected. +11. Run group tests if affected. +12. Update build metadata. + +## 23. Phased Roadmap + +The estimates below assume one experienced engineer with access to test accounts and enough time to chase upstream behavior. A less experienced model or engineer should treat these as lower bounds. + +### Phase 0: Inventory And Guardrails + +Estimate: 1 to 2 weeks. + +Tasks: + +- Pin the current known-good `signal-cli` release used as a behavioral reference. +- Pin the current `libsignal` release. +- Clone or vendor references for `Signal-Android`, `signal-cli`, and `libsignal`.