Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ These are NOT auto-loaded. Read them when the task touches the relevant area.
`tests/CLAUDE.md` (auto-loaded when working in `tests/`)
- Full REST + WebSocket API: `docs/api-reference.md`
- Self-update system internals: `docs/upgrade-system.md`
- Signed KV compatibility preparation and gate holds: `docs/legacy-compat.md`
- Trust model, connectivity, enhanced announcements: `docs/trust-and-connectivity.md`
- x0x-symphony integration: `docs/symphony-integration.md`
- CI/CD workflows: `docs/cicd.md`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# ADR 0063: Signed KV legacy gossip compatibility adoption boundary

- **Status:** Proposed (draft; not accepted)
- **Date:** 2026-09-06
- **Decision owner:** David Irvine
- **Reviewers:** Pending
- **Supersedes:** None
- **Related:** x0x #517, #515; saorsa-gossip #46, [#48](https://github.com/saorsa-labs/saorsa-gossip/pull/48), [ADR-013](https://github.com/saorsa-labs/saorsa-gossip/blob/5307e59270b2eead28e948b2206cf8cc04f149d5/docs/adr/ADR-013-explicit-legacy-gossip-egress.md)

## Context

Gossip #48 merged at `5307e59270b2eead28e948b2206cf8cc04f149d5`
(branch tip `4340773c38b688a674a313ff99ba22445083fffc`). This satisfies
library gate G0 only. Its disabled compatibility facility requires receive
connection provenance, guarded egress and a paired x0x V3 inner envelope.
Stock x0x V2 signs `topic || payload` without the topic length: moving the
boundary between `T` and `T/state-sync` can preserve the signed bytes.
A library merge does not prove x0x receive/apply or mixed-version acceptance.

## Decision drivers

- Authenticate the topic boundary and retain the original author's signature.
- Admit only explicitly audited Signed KV topics, authors and receiver profiles.
- Keep connection provenance through every queue hop and fail closed on reconnect.
- Separate preparation, implementation, acceptance and human enablement.

## Considered options

1. Reinterpret V2 as V3: rejected; the V2 topic boundary is ambiguous.
2. Switch every publisher to V3 now: rejected; stock receivers cannot consume it.
3. Add explicit V3 pairing APIs, then adopt them through audited Signed KV
registration after the transport prerequisites: selected.

## Decision

Reserve inner byte `0x03` for a signed V3 envelope. Its wire fields are:

```text
0x03 || author[32] || key_len:u16be || key || sig_len:u16be || signature
|| topic_len:u16be || topic || payload[remainder]
```

The ML-DSA-65 preimage is exactly:

```text
x0x-msg-v3 || author[32] || topic_len:u16be || topic || payload[remainder]
```

The topic length counts UTF-8 bytes. Payload is terminal; extensions require a
new version with a length-prefixed payload. V3 limits the full envelope to
1 MiB, public keys to exactly 1952 bytes and signatures to exactly 3309 bytes.
Author identity must derive from the embedded public key. Strict
V3 decoding rejects V2, unknown versions, relabeled signatures, malformed
fields and invalid signatures. There is no V2 fallback for this facility.
Unsigned V1 has no version tag. Generic dispatch accepts V1 only for leading
`0x00`/`0x01`; `0x04`–`0xff` are unsupported versions. Its encoder limits
topics to 511 UTF-8 bytes. This rejects historical longer unsigned topics.
A stock decoder can interpret `0x03` as a V1 length and rely on accidental
UTF-8 failure to reject key bytes; the paired build explicitly closes that path.

The preparation adds `PubSubManager::publish_signed_kv_v3` and
`decode_signed_kv_v3`. Publication requires signing and a network topic;
verification does not establish store authorization. Existing generic
publishers and KvStoreSync callers remain on V2 until the adoption audit.
Generic reception dispatches V3 to the strict verifier. V2 remains explicitly
V2 on ordinary traffic. No automatic version negotiation or legacy grant is
inferred from a received envelope.

The future compatibility registration covers only exact concrete topics of
`AccessPolicy::Signed` stores: the delta/full-state topic `T` and its
`T/state-sync` topic. No namespace wildcard; no registration of Allowlisted,
Encrypted, AppendOnly, SelfKeyed, presence, membership, DM or group traffic.
Preserve Signed writer/owner authorization, owner-announce checks and the
state-request receive/apply checks. Known author rosters and positive verifier
revisions must reflect the audited paired build.

The required receiver profile is
`x0x/0.30.1+signed-kv-inner-v3;saorsa-gossip-pubsub/0.5.66`.
This names a required patched profile, not an existing accepted release.
Its pubsub `0.5.66` component does not identify code containing #48; upstream
profile revision and a fresh audit remain necessary before grants can be used.
Stock v0.30.1 is ineligible. Authors must re-sign V2 data; relays forward the
inner bytes verbatim. Fresh audit, verifier revisions and grants are required.

`enabled = false` remains the default. Only explicit local operator policy may
issue finite, session-bound, exact-topic grants with issuer/reason and
monotonically increasing revisions. No grant survives a reconnect or restart.
Missing/corrupt/rolled-back floor journals, expired/revoked grants, stale
sessions and `RejectV1` must fail closed. No reset-floor or auto-enable surface.
Leaf pass-through refusal and existing timeout/admission controls stay intact.

Inbound `AuthenticatedSession` must be constructed from the generation stamped
by ant-quic on the actual reader connection, including pre-auth buffer entries,
and carried through every queue. A lookup of the current peer connection after
dequeue cannot establish provenance. Guarded egress must pin the selected
connection, revalidate after waits and stream allocation, and never retry
selected legacy bytes on a replacement connection. G2 cannot close without G1.

## Consequences

The V3 primitives can be reviewed before transport publication, but do not
activate KvStoreSync or legacy compatibility. Older application receivers
need a paired patch and audit. Authentic stock-binary gates remain required;
where stock cannot consume V3, record the failure/hold without substituting a
patched binary or weakening the historical predicates. Registry gossip
`0.5.75` predates #48 despite the same workspace version. See the
[dependency strategy and gate ledger](../legacy-compat.md).

## Validation

Local pairing tests cover independent canonical preimages (including UTF-8),
production publication verified by gossip's crypto API, both-direction
`T`/`T/state-sync` boundary rewrites, V2 relabeling, malformed/truncated input,
author mismatch, terminal-payload tampering, size/shape bounds, default V2
publication, and signing/local-topic refusal. Crypto API verification and
canonical preimage fixtures are not execution of #48's `SignedKvTopic` verifier;
that integration is still blocked on G4.
Run the required ordered Rust gates on the exact pushed tree.

G0 is MET. G1–G8 are OPEN: published ant-quic provenance; x0x provenance and
guarded egress; policy/registration and receive/apply audit; complete dependency
bump; real-daemon efficiency/fail-closed gates; both authentic mixed-version
phases with original deadlines; unchanged ten-run convergence; David's explicit
enablement decision. Local tests close none of these acceptance gates.

x0x #515 remains draft. This ADR authorizes no merge, undraft, tag, daemon
operation, deploy or product work on #530, #531 or #274.

## Notes for AI-assisted work

AI-assisted draft. Only human review may mark this ADR Accepted.
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ This directory contains architecture decision records for x0x.
- [ADR 0059: Invite Authentication and Seating Provenance](./0059-invite-authentication-and-seating-provenance.md) (proposed 2026-09-02) — InviteV4 signs the whole invite minus signatures with inline id-bound inviter/owner keys; Home-join mode pins the admission owner; every invite-derived seat records local, bootstrap-stripped `invite_lineage` with deduplicated authenticated fork evidence (observability only, no eviction); amends ADR 0016 §7; the stale-base residual and old-admin-key caveat are deferred to #472
- [ADR 0060: The Owner's Home Is Elected, Not Per-Install](./0060-one-home-per-owner.md) (proposed 2026-09-05) — the unit of Home is the OWNER, not the install (#449): auto-provisioning becomes optimistic and subject to election on the Tier-1 `("home")` register, publisher and resolver share one `!withdrawn` predicate, and `GET /home` reports `local`/`adoption_pending`/`elsewhere` instead of a bare 404. Amends ADR 0038 (unit of Home) ONLY — no Tier-1 surface change (ADR 0041 stays at four kinds) and no change to ADR 0039 Home eligibility. How a losing device JOINS the winner's Home, retirement of the duplicate, and any device-vs-rider eligibility rule are explicitly deferred: review of PR #507 showed the first implementation broke signed-record and protocol compatibility and had no trustworthy cross-device device/rider signal. #449 stays open.

- [ADR 0063: Signed KV legacy gossip compatibility adoption boundary](./0063-signed-kv-legacy-gossip-compatibility-adoption-boundary.md) — draft; V3 pairing preparation only, G0 met and G1–G8 open; disabled pending audited adoption

## Errata (Accepted ADRs are immutable; corrections recorded here)

Documentation-audit corrections, 2026-07-19. The ADR files themselves are
Expand Down
92 changes: 92 additions & 0 deletions docs/legacy-compat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Signed KV legacy compatibility: preparation and holds

Status: **disabled; preparation only** (`enabled = false`), 2026-09-06. There is no operator
activation endpoint or configuration switch in this patch. See
[proposed ADR-0063](adr/0063-signed-kv-legacy-gossip-compatibility-adoption-boundary.md).

## What is available

`PubSubManager::publish_signed_kv_v3(topic, payload)` signs and publishes inner
version `0x03`, returning the original envelope. It requires a signing context
and refuses `local:` topics. `decode_signed_kv_v3(bytes)` strictly authenticates
that format; `decode_auto` dispatches `0x03` to it without fallback. These APIs
do not register topics or grants. KvStoreSync and ordinary publishers are not
switched to them in this preparation patch. Routing Signed KV through these
APIs is part of the pending G3 receive/apply and registration audit.

The preimage is `x0x-msg-v3 || author[32] || topic_len:u16be || topic || payload`.
The length is in bytes; payload consumes the remainder. Relabeling V2 cannot
produce V3. Ordinary V2 traffic remains V2; it is not eligible for the new
facility. V3 enforces the gossip verifier's 1 MiB envelope ceiling, 1952-byte
public key and 3309-byte signature. Unsigned V1 topics are limited to 511 UTF-8
bytes: high bytes `0x02` and `0x03` dispatch to signed formats; `0x04`–`0xff`
are rejected as unsupported versions, never retried as V1. This deliberately
rejects historical long V1 topics to reserve unambiguous version dispatch.

## Publication audit and bump strategy

Gossip [#48](https://github.com/saorsa-labs/saorsa-gossip/pull/48) merged at
`5307e59270b2eead28e948b2206cf8cc04f149d5`, branch tip
`4340773c38b688a674a313ff99ba22445083fffc`. G0 is MET only.

The workspace at that merge declares **0.5.75**. The registry's existing
[pubsub 0.5.75](https://crates.io/crates/saorsa-gossip-pubsub/0.5.75) and
[transport 0.5.75](https://crates.io/crates/saorsa-gossip-transport/0.5.75)
were published on August 27. Downloaded archive metadata identifies
`6e17f02047017f07f8aa1d82052cdeb2409dfbf1` with `dirty: true`; their source lacks
`AuthenticatedSession`, `handle_authenticated_message` and the V3 verifier.
**Do not bump to registry 0.5.75 and claim #48 adoption.**

Published [ant-quic 0.27.48](https://crates.io/crates/ant-quic/0.27.48) identifies
`79ec79158455c8084bc994ecfb338372ad60a89a` and lacks `recv_with_generation` and
`current_connection_generation`. A tag/version alone does not satisfy G1.
Current Cargo.toml requirements (`gossip 0.5.74`, `ant-quic 0.27.47`) and the
lockfile are retained. The existing lockfile already resolves registry gossip
`0.5.75` and ant-quic `0.27.48`; neither contains the required facility. **Gossip release bump HOLD; ant-quic G1 bump HOLD.**

Selected strategy: **wait for a verified 0.5.76 or later publication containing
#48**; this draft retains the current dependencies. No draft git pin is active.

For a separate reproducible integration branch before publication, pin **all
11** direct gossip crates to the same merge revision using this pattern:

```toml
saorsa-gossip-pubsub = { git = "https://github.com/saorsa-labs/saorsa-gossip", rev = "5307e59270b2eead28e948b2206cf8cc04f149d5" }
```

Apply it to coordinator, crdt-sync, groups, identity, membership, presence,
pubsub, rendezvous, runtime, transport and types. Commit the resulting lockfile;
inspect `cargo tree` for duplicate registry/git type universes. Do not mix git
pubsub with registry transport/types. A path experiment must instead use a
separate clean checkout at that exact SHA and coherent `[patch.crates-io]`
entries for the whole family, with the checkout SHA recorded; machine-local
paths are not a publishable dependency strategy. Neither strategy is activated
by this PR or counted as G4. Once a new registry release actually contains #48,
verify archive source/checksums, pin the coherent family and rerun exact-tree
gates. Do not invent the future version.

## Acceptance ledger

| Gate | State | Remaining evidence |
| --- | --- | --- |
| G0 | MET | Library merge #48 at `5307e592` only |
| G1 | OPEN / HOLD | Published ant-quic reader/pre-auth generation stamping, live-generation and pinned-send contract with reconnect/reuse tests |
| G2 | OPEN / HOLD on G1 | End-to-end receive tokens into `handle_authenticated_message` and guarded egress; no post-dequeue lookup |
| G3 | OPEN | Signed-only exact-topic registration, V3 publication routing, receive/apply audit, owner/state-request checks, floor policy, grants, audit/counters; default false |
| G4 | OPEN / HOLD | Published #48-containing gossip and G1 ant-quic; complete bumped tree green |
| G5 | OPEN | Real-daemon fail-closed, spoof/expiry/queue tests and measured verification/relay overhead |
| G6 | OPEN | Both authentic v0.30.1 mixed-version phases, original predicates/deadlines, both directions |
| G7 | OPEN | Unchanged ten-run convergence on exact candidate binaries; hashes and resolved dependencies |
| G8 | OPEN | David's explicit enablement decision |

Local tests compare canonical preimage bytes and verify production signatures
with the resolved gossip identity crypto API, including matching author IDs.
They do **not** run #48's `SignedKvTopic` verifier: it is absent from the
resolved registry pubsub crate. That actual pubsub integration test, with
exact-topic and roster admission, remains a G4 exit requirement.

The H1 profile requires a patched `+signed-kv-inner-v3` receiver. It does not
prove stock v0.30.1 compatibility. Keep that distinction in G6 evidence;
never replace the authentic binary with a patched one and call the gate green.
No timeout padding, weakened predicate or library-fixture substitution.
#515 stays DRAFT. No tag, deploy, live-daemon action, or #530/#531/#274 work.
Loading
Loading