Nine hours apart. Neither one opened the other. The Together protocol: transport-agnostic signed envelopes carrying utterance-only bodies between paired archives, HMAC-authenticated under an out-of-band pair secret, replayed into local state that rebuilds byte-identical. 126 tests of this rail's own, green in the full Personal Neutral Archive (PNA) suite at its July 2026 build (current whole-suite counts live on the build log) — including an independent adversarial pass that found 8 real defect classes, all fixed. The plain-words version is for everyone else.
A PNA connects to other people through exactly two methods, kept strictly separate by design. The consent gate handles reading: another person, holding an exact-match grant, reads from your archive. Message transport handles sending: it carries a message you composed, an utterance with provenance. Archive contents are never copied into it. Nothing does both.
| Grant rail — the gate (pna-mcp) | Sharing rail — message transport (this spec) | |
|---|---|---|
| What moves | Access: another identity reads your archive through the gate | A message: a consented utterance with provenance. No archive contents are copied |
| Direction | Inbound. Scoped, exact-match, logged, C0-invisible | Outbound. Composed, signed, logged, per message |
| Trust object | A grant in consents/policy.yaml | A contact card in your roster, established by pairing |
| Example | Your caregiver reads your Medications partition | You invite a friend for a Saturday walk |
A walk invitation is not a grant. Nobody's archive opens. Two sovereign archives exchange sentences, and each writes what it agreed to into its own home. The gate needs zero changes for this rail to exist.
An envelope is one utterance in flight: JSON, one file, append-only on both sides.
The utterance-only rule (required): body may contain only what the sender composed for this message (activity, slots, title, note, roster). Never archive paths, never archive contents, never auto-attached items. v0.1 refuses attachments entirely; consented excerpts arrive in v0.2 with per-item consent checks at compose time.
| Rung | Means | Standing |
|---|---|---|
| transport | Envelope is HMAC-authenticated per-pair; confidentiality comes from the transport (a private repo/folder run by an operator the circle trusts) | v0.1 builds this |
| e2e | Payload additionally encrypted to recipient (age/X25519); relay sees routing + ciphertext only | defined; implements when age is present |
Invariant: a transport-sealed envelope must refuse to travel through a transport marked untrusted, declared in the envelope, checked at send, enforced in code. Authenticity in v0.1 is HMAC-SHA256 under the pair secret (stdlib, constant-time compare). Pairwise secrets are sufficient because groups are organizer-fanout stars; per-pair asymmetric keys (Ed25519) arrive with v0.2 introductions, where "who vouched whom" needs real signatures.
Verb set: PROPOSE · ACCEPT · COUNTER · DECLINE · CONFIRM · ADD · WITHDRAW · CANCEL cross the wire; DUPLICATE · DONE are local acts. State machine (per event): proposed → agreed → done | cancelled, with counter loops before agreement and per-participant status invited | accepted | declined | withdrawn. Out-of-order verbs are rejected. Nothing is repaired: a CONFIRM on a declined thread, an ACCEPT after CANCEL, a second CONFIRM — all refused with one log line. Replayed envelopes (same env id) are idempotent no-ops.
The organizer is the serialization point (star topology); groups are organizer-fanout: participants trust the organizer's assertion of the roster, exactly as a dinner-party host is trusted. At ~10-person scale the star topology makes pairwise primitives enough, so no one needs distributed group cryptography (MLS) here.
A slot is a UTC instant plus the proposer's IANA zone name. An offset is never stored, so DST cannot fork the agreement. Every rendering shows both clocks; the recipient's side renders from their zone in their roster identity, so nobody does arithmetic. The agreement is the instant: both archives store the same UTC, and each derives its own local display and its own .ics.
The archive doctrine lands here intact: the envelope thread is canonical; event.yaml is an index. Current state is a pure replay of the signed exchange: delete every event.yaml and pna share rebuild reconstructs them byte-identically (tested). The thread is also the provenance of the agreement itself: who proposed, who countered, who confirmed, when. Records default C1; the gate is not involved. Circle members never read Together/. They hold their own copies.
Same discipline as the builders page: a claim is ENFORCED only when a test refuses the violation today. RECORDED means written down and honored by convention; FUTURE means designed. The build has not started.
| Claim | Standing | Where |
|---|---|---|
| Pairing gates everything: unknown sender quarantined unopened, logged, never rendered | ENFORCED | I3 — test A1 |
| Constant-time HMAC-SHA256 auth under the out-of-band pair secret; one-bit tamper refuses | ENFORCED | §4 — stdlib compare, Session 5's lesson inherited |
| Utterance-only body allowlist: composed fields only, no archive paths/contents, attachments refused outright | ENFORCED | I2 — test A3, zero bytes emitted from a conflict check |
| Organizer authority: CANCEL/CONFIRM/ADD are organizer-only; history preserved on every side | ENFORCED | I4 — test A5 |
| Thread-origin binding: a validly-paired non-organizer cannot plant themselves as organizer on a third party's archive | ENFORCED | I4 — defect #3 fix, bound evt-ids |
| One courier-log line per send/receive/quarantine/reject; unwritable log ⇒ operation refuses | ENFORCED | I6 — test A7 |
| Transport-sealed envelope refuses to travel through a transport marked untrusted | ENFORCED | I7 — test A10 |
| State machine refuses disorder (CONFIRM-on-declined, ACCEPT-after-CANCEL, second-CONFIRM, replay) | ENFORCED | I5 — test A4 |
| Replay rebuilds truth: delete all event.yaml, rebuild reconstructs byte-identical from exchange/ | ENFORCED | test A9 |
| ADD shares the present: a third participant gets current agreed state only, zero negotiation history | ENFORCED | I2 — test A8 |
| Cross-zone agreement is one UTC instant; each side's .ics parses with its own local clock | ENFORCED | test A2 |
| Real age e2e round-trip: the binary is absent here; compose degrades to transport | RECORDED | refusal logic (I7) is age-independent and tested; the encrypt/decrypt path has no live integration test yet |
| A live network transport (Signal / shared repo relay reachable off one machine) | FUTURE | v0.1 uses shared-folder transport; Kurt's lane, post-provisioning |
| Ed25519 introductions and vouching (who-vouched-whom needs real signatures) | FUTURE | v0.2, spec §2/§10 |
| MLS/relay federation, once circles cross operators the participants don't already trust | FUTURE | later, spec §3/§10 |
test_pna_share.py: 38 green, including spec §9 acceptance A1–A10 by name — pairing-gates-everything, cross-zone-one-instant, utterances-only, state-machine-refuses-disorder, organizer-only-CANCEL-keeps-history, DUPLICATE-lineage, one-log-line-plus-fail-closed, ADD-shares-present-not-past, replay-rebuilds-truth, transport-refuses-untrusted-transport. test_pna_share_adversarial.py: 88 green — all 14 defect-tests now pass, the rest confirm defenses. Full combined PNA suite at that build: 400 tests, OK, exit 0 (test_pna 119 · test_pna_mcp 59 · test_pna_mcp_adversarial 96 [2 intended xfails, pre-existing] · test_pna_share 38 · test_pna_share_adversarial 88); no pre-existing suite regressed. ~/PNA untouched — access-log still 2 lines, no Together/ directory; every test ran against tempfile archives.
Mid-fix, the builder hit an account session limit (during defect #3): it had already closed defects 5, 6, and 8, and left defect 7 partially wired. The main thread finished the remaining five defects inline and verified each one atomically. A fresh agent would have hit the same limit and risked another mid-flight failure. The interruption and the finish are both part of the record. Neither was smoothed over. That's the house culture.
An independent adversary, told not to read the builder's tests, attacked from spec §7 (I1–I10) and §9 (A1–A10) and found 8 real defect classes, written as failing assertions of correct behavior. All 8 were fixed and re-verified.
The two test commands above are the verified ground truth: run them and read the exit code. The CLI walkthrough below them follows the rail's verb vocabulary faithfully and is illustrative. No published CLI reference is quoted.
The F4 adversarial pass inherits these targets; good-faith security research is welcome through the main site's Write to us → security door — a safe harbor written into our Terms, §15.
This is the correspondence rail's first lived application; co-purchase (a neighbor-to-neighbor bulk-buying protocol, built the same week) is its second, riding the same utterance-only discipline. Its receipt-share is the rail's first consented excerpt. This spec names the v0.2 attachment path. We have not built it yet (§4). This spec and the co-purchase session's export-rail draft stand side by side ("one rail, two protocols"), and whether they merge into one is Carmen's call, recorded. Neither build waits on it. Working documents, in the repository: pna/SHARING-SPEC-v0.1.md · pna/SHARING-SPEC-v0.1-export-rail.md · pna/PNA-SHARING-BUILD-REPORT.md · pna/PNA-COPURCHASE-BUILD-REPORT.md.
Prototype, built July 9, 2026, on synthetic data only. Two invented archives, paired the way the spec describes; the founder's real archive was never touched (verified: its access log is unchanged, still 2 lines, no Together/ directory).
Not yet: gate (pna-mcp) integration, a live network transport, the real e2e sealing rung, introductions/Ed25519 vouching, MLS federation, mobile surfaces, recurrence rules. Each is specified with a plain status in the spec above.
Open design calls (Carmen's, recorded; work continues while they wait): the Together/ partition name (alternatives Circles/, Compañía/) — joint with co-purchase's own naming sign-off · outbound-consent framing for a future agent-sends-on-your-behalf grant (v0.2, routes through policy.yaml) · first live transport placement, joint with Kurt · ES invitation wording, riding the terms-pack seam · the one-rail-two-protocols merge with co-purchase's export-rail draft.