Stability and change policy
Stability and change policy
What a third-party client can rely on, how much of it is verified by machine rather than by prose, and how changes to each layer are rolled out.
Coverage matrix
The repository publishes cross-repository wire fixtures under
sync-core/src/test/resources/interop/.
They pin exact bytes that consumers must decode, but they do not cover everything this
documentation describes.
| Area | Machine-checked by the published fixtures | Where |
|---|---|---|
| Payload AEAD: keyset parsing, both modes, associated data, Tink prefix | yes | tink-vectors.json |
| Blob streaming AEAD: keys, segments, associated data, truncation rejection | yes | streaming-vectors.json |
| Linking payload encoding | no | prose in linking.md |
| HTTP endpoints, status codes, headers, precondition semantics | no | prose in http-api.md |
| WebSocket frame schema and delivery behavior | no | prose in websocket.md |
| Capability tag grammar, limits, authority | no | prose in capabilities.md |
| Module document schemas | no | not documented at this revision |
The published fixtures are crypto vectors only. Module-level fixtures published from this repository are a planned later phase.
This repository already consumes module fixtures published by
octi-web and
octi-desktop: per-module tests decode their
committed document vectors through the production decoders, pinned by
fixture-lock.json. That protects this client against those producers’
drift, but it gives a third-party client nothing to verify against.
Anywhere the matrix says “no”, the contract is prose plus the reference implementations. Report a mismatch instead of assuming either side is authoritative.
Consuming the published fixtures
The chain has to start from something you pinned yourself. Fetching a manifest and hashing it proves nothing: whatever commit you fetched, its manifest hashes to whatever it hashes to. The anchor is a digest you recorded out of band.
-
Pin a full 40-character commit SHA of this repository and the expected SHA-256 of that commit’s
manifest.json, together, in your own lockfile. This is whatfixture-lock.jsondoes for the sources this repository consumes:{ "schemaVersion": 2, "sources": { "d4rken-org/octi": { "ref": "<40 hex characters>", "manifest_sha256": "<64 hex characters>" } } } - Fetch
sync-core/src/test/resources/interop/at that SHA. - Verify the fetched
manifest.jsonagainst your pinned digest. Abort on mismatch. - Verify every file listed in
manifest.filesagainst the SHA-256 recorded there. - Decode and decrypt each vector under its committed keyset, comparing the resulting plaintext to the recorded plaintext.
- Reject any
schemaVersionyou do not recognize, in the manifest and in each fixture file. A newer schema may add fields whose absence you would silently misread.
Skipping step 1 removes the only anchor the rest of the chain rests on.
Ciphertext bytes change on every regeneration
Both AES-GCM-SIV and the streaming AEAD use a random nonce per encryption. Regenerating the fixtures under the very same keyset produces different bytes every time.
Verification therefore decrypts and compares plaintext. Never byte-compare a fixture’s ciphertext against your own encryption of the same plaintext; that comparison fails even when both sides are completely correct.
For producers, a regeneration always shows up as a diff, so regenerating without a wire-format reason is pure noise. The keyset is preserved across regenerations by design; rotating it takes a deliberate delete-then-regenerate, because rotation breaks every consumer that pinned the old keyset.
Change policy by layer
HTTP and server API
Changes are additive, or they ship under a new endpoint or a new API version.
Device capability tags cannot help here. They describe peers, not the server, and a client learns
a peer’s tags by calling the very API in question. The server’s own feature level comes from the
API itself, for example the storageApiVersion field in
GET /v1/account/storage, and from the status codes a client
must handle anyway.
Servers are deployed independently of clients. The server you talk to today may not be the one you talked to yesterday, so degrade rather than fail when an endpoint or field is missing.
Module document format
There is exactly one stored document per (owner device, module id), and every peer on the account reads that same document. A producer physically cannot emit one format to one peer and a different format to another. Per-peer dual-write does not exist as an option.
Migrations therefore ship as one shared backward-compatible representation: the document carries the old and the new shape together, so old readers keep reading the fields they know while new readers prefer the new ones.
Peer capability tags do not decide who receives which shape. They decide when it is safe to stop writing the old shape: once every peer that matters advertises support for the new one, the compatibility fields are dead weight and the old shape can go. Until then, both are written for everyone.
Encryption mode
Account-wide and fixed at account creation. It cannot vary per peer and cannot be renegotiated later. A peer that cannot handle the account’s mode is permanently incompatible with that account. Capability tags exist to surface that condition, not to work around it. See encryption.md and capabilities.md.
What is not promised
No compatibility promise attaches to behavior this documentation does not describe. Undocumented fields, undocumented endpoints, incidental orderings, timing and the exact wording of error bodies may change without notice and without a fixture failing.
If your client depends on something not documented here, that dependency is your risk to carry. Ask for it to be specified instead.
Further reading
- Interop fixtures README: the fixture file formats, field by field.
.claude/rules/interop-fixtures.md: the producer-side workflow, regeneration rules, the cross-repository CI gate, and the staged rollout sequence for a deliberate wire-format break..claude/rules/device-capabilities.md: the capability contract across all four implementations.