Files
archy/docs/peer-request-correlation.md

12 KiB

EventId-correlated peer invitations — inactive source candidate

This document supersedes the older replay document only for the proposed paired implementation. R2's legacy suppression writer and its regression source remain unchanged. This is not a protocol/security approval, migration authorization or runtime qualification.

Implemented source contract

  • Protected wire types are peer-request-event-v1 and peer-decision-event-v1. They deliberately do not reuse legacy discriminants: an older reader must not act on a new request while ignoring correlation fields. Legacy parsing remains available for explicit refusal; legacy sender APIs refuse with C01_MIGRATION_REQUIRED.
  • A protected request carries {request:{from_did,version,name,message}}. A decision carries the entire original signed request Event as original_request and a tagged decision (accept with invite_code, reject/cancel with optional reason). The reference is that verified original EventId, not the decision's ID, arrival timestamp, or local UUID. These exact schema bytes are a review proposal, not coordinator-approved wire bytes.
  • Original proof verification checks signature/EventId, encrypted-DM kind, exactly one recipient, signer/requester, local endpoint, actual decrypted protected request, and decision actor/direction. Reject/accept run recipient-to-requester; cancel runs requester-to-recipient. No custom cryptography; proof and correlation metadata remain inside existing encrypted envelopes.
  • operation_id is a stable UUID provided by the client before RPC; new_request explicitly binds intent. Persist the full immutable command and exact signed event in the same atomic JSON document as the visible row BEFORE publish. Repeating an operation with changed recipient/payload/intent refuses. Retrying an identical still-live operation re-fsyncs and returns the same event bytes, not fresh encryption. A terminalized command returns an explicit terminal error rather than publishing again.
  • C1 keeps distinct generations as separate UUID rows. Grouping by peer must not collapse protocol identity. Delayed A decisions locate only A, never the current row for the same peer B.
  • Local terminal state and encrypted signed decision outbox commit together. Notification opt-out persists and excludes background delivery. Replies retry the exact signed envelope. Incoming approval claims the exact generation durably before restrict_discovery_invite / accept_invite / mesh effects. A partial/uncertain acceptance is visibly marked recovery-required; it is NOT automatically reexecuted, because the existing membership routine can replace nodes and append invite records. Cross-store acceptance reconciliation is an explicit remaining engineering exit.
  • Early cancellation admits only a verified original request proof in the existing signed admission horizon. Its generation consumes a reserved slot without creating a visible pending row; subsequent arrival is suppressed. Known terminal updates do not require a new slot. Protected generations are not pruned or evicted in this candidate, preventing cancellation revival from speculative GC.

Explicit activation and migration boundary

require_activation() unconditionally returns C01_POLICY_PENDING; there is no environment/file switch, test bypass, or hidden production default. Connect/poll/approve/reject/cancel/retry/publication entrypoints are gated. Discovery and existing mesh/federation connectivity are not disabled. List-pending exposes migration/policy and acceptance-recovery status; mutation RPC errors are explicit. Frontend and shared dispatch/session/RBAC/CSRF sources are untouched.

This is not a claim that an unmodified old remote UI can render a new refusal. M1 requires coordinated upgraded clients/peers, a durable migration/rollback boundary, and visible frontend rendering before activation. Current frontend lacks stable operation-ID submission and the new status renderer. New discriminants prevent legacy action, but alone do not implement capability negotiation or remote error presentation. No ambiguous legacy decision is sent as a compatibility fallback.

Current adopted policy and incomplete implementation

The coordinator's C01-D4-COORDINATOR-ADOPTION.md now adopts the exact recommendations in c01-d4-security-disposition-v1/DISPOSITION.md only with mandatory D4-C1 through D4-C5. Older statements that numeric quotas, L1/L2 or retry policy await selection are superseded. This recovery does not implement those requirements merely by documenting them, and cannot be activated.

The source still reuses the old wall-clock admission check and ledger count ceilings. That is not the adopted clock/fence authority or the required independent known/unknown/byte/resource-reservation accounting. There is no protected GC, authenticated nonrenewable command deadline, qualified clock/rollback recovery, encrypted bounded snapshot/control store, aggregate pre-parse receive adapter, launcher downgrade fence, fair globally paced retry scheduler, or final-effect L2 revalidation. Existing claim_replies has no original-request cutoff; approval retry still uses receipt age. None of those behaviors is approved by the fail-closed activation gate.

Exact adopted parameters are bound by the coordinator adoption and disposition rather than duplicated as incomplete runtime defaults here. The original request horizon is L2 inclusive [c,c+2592000], future allowance zero; retention is through H+300. Same logical validated time and atomic nondecreasing watermark, non-ratcheting monotonic trajectory, final-effect expiry and unresolved-effect retention are mandatory. Unknown early cancel is independent of known generations. Resource-holding terminal outboxes retain their allocated slots. Commands cannot become new operations after GC. Transport limits are aggregate across relays and must precede allocation. Serialized snapshot/control ceilings and launcher-enforced downgrade refusal require concrete storage/transport/platform integration, not just constant declarations.

Migration consumer recovery

Recovery consumes lane44's fixed consumer contract. MIGRATION_REQUIRED and POLICY_PENDING now use the existing Not supported sanitizer prefix, ASCII text under its 200-byte truncation limit, and no known frontend transport-retry trigger words. This corrects synchronous error preservation without changing dispatch, auth/CSRF or frontend files. A Rust test targets the actual sanitizer, but is UNRUN. The offline RED/GREEN source-contract check is not Rust execution, mounted UI evidence, durable background refusal or actual old-peer interoperability.

Lane44's remaining application-error retry classifier, map pre-ack animation, operation-ID submission, status projection/readback and refresh ownership still require the frontend owner. Do not parse poll skipped as public migration messages or infer remote approval from relay delivery. Existing notification defaults and unrelated connectivity remain unchanged.

Evidence and test transition

Rust regression sources cover signed endpoint/proof rejection, delayed accept/reject/cancel A versus B, prepared-before-publish reload and exact retry, conflicting commands, early cancellation, first terminal wins, uncertainty after acceptance claim, capacity, no-notify and legacy/policy refusal. They have NOT been compiled or executed. Store reload / simulated lost completion are not process-death, power-loss or fsync fault-injection tests.

The two historical RPC transport tests and the historical legacy replay RPC test asserted legacy emission/automatic acceptance that M1 now prohibits. Their original bytes are retained in the packet baseline. Current sources assert visible refusal/no mutation; signed positive store/wire tests cover the new primitives. Positive managed-relay send/ack/timeout, peer-invite DID/trust integration and real RPC replay/crash tests must be ported and run after selected policy/activation admission. The unrelated metrics test and all R2 tests are unchanged. This transition does not inherit or replace the old transport qualification.

Independent exact protocol/storage/security review, native isolated tests, actual crash/reordering/mixed-version migration, packaged/multi-node/operator qualification remain required. Do not integrate this inactive candidate as finished C01.

Lane55 bounded D4 schema/caller successor (not activated)

The command operation key is now a canonical UUIDv7, not an arbitrary UUID. Its embedded Unix milliseconds, floored to integer seconds, are the immutable origin c. No retry parameter or current receipt time can replace that origin. H=c+2592000 and H+300 use checked arithmetic. The authenticated local connect RPC signs the request with created_at=c; encrypted RequestCommandBinding contains the exact operation key, explicit-new intent and c alongside the immutable request payload. Recipient remains the signed envelope recipient. No private command metadata is added to public Nostr tags. The original signed request EventId, never this UUID or a timestamp, remains decision identity.

Absent commands require new_request=true; identical retry resubmits that same immutable command, including its original true intent. Changed fields conflict. The key's original horizon is checked before looking up a binding, so absence following future safe GC cannot renew the same key. Changing the timestamp bits is a different operation key and needs explicit new intent. All command bindings are currently retained indefinitely; this patch introduces no GC. Client UUIDv7 creation/retry UI and migration negotiation remain unimplemented; UUIDv4 input refuses rather than being silently converted. This encoding needs independent exact-schema review, not renewed numeric-policy selection.

Known accounting uses authenticated remote keys (recipient for outbound, requester for inbound), 4096/256 combined known ceilings, 5 inbound plus 5 outbound live rows, and 1024 resource slots including every full terminal Generation. Unknown authenticated cancels use a separate 512/16 compact tuple pool with exact first terminal decision identity and a 2048-byte bound, never the known pool. Actual compact serialization is measured before correlation commits; pending slots reserve a maximum terminal event plus metadata. Raw event objects remain objects. Terminal state never lends its resource slot. Safe compaction/release is not implemented: even reconciled generations retain their proof/command resources and can cause earlier visible refusal. This conservative limitation must be resolved before activation, not called full D4-C1 completion.

Initial, manual-retry and background approvals share exact-row/original-horizon, nonempty-DID, removed-peer and existing-member eligibility. Explicit removal now holds the correlation decision guard. Incoming acceptance checks the removed DID and original horizon again immediately before calling accept_invite. Publication checks the original horizon after connecting. These are caller-boundary checks, not an atomic external-effect journal: accept_invite's internal asynchronous mutation boundary, every other membership/removal writer, partial effects and reconciliation remain open.

Clock use is still provisional wall time; sibling57 qualified time/fence integration is required before activation or GC. The actual protected encrypted store and bounded pre-parse transport remain sibling56/58 integration work. Legacy R2 persistence/suppression is unchanged. The accounting wrapper does not convert that plaintext pretty-JSON writer into a protected store. Exact maximum signed fixtures, pre-sign complete prospective reservation, frozen clocks, process-death/GC races and independent review are open. Rust test sources are UNRUN under this source-only admission. No full C01, build or runtime pass.