ActivationCrossedReceipt — Design/Audit Contract
Status: draft — design/audit
Truth class: descriptive
Canonical: no
Last reviewed: 2026-07-03
Source basis: read against origin/main @ 0f4fa895 (re-verify before relying on exact line numbers or hashes — they drift)
Related: #2293 (this contract's issue), #1748 (Institutional Process Substrate), #2141 (vertical institutional spine), #2041 (human/AT pass — open/parked), #2291 (process-evidence surface), #2292 (human/AT packet), ADR-0026 (receipt & provenance proof envelope)
This is the design contract #2293 requires before any implementation. It scopes a candidate
ActivationCrossedReceiptas the next narrow process-transition receipt rung under #1748 / #2141 — the receipt that would witness that an already-recorded decision crossed the activation boundary (the spine's "boundary between deciding and doing") before any later mutation/evidence work.This document adds no runtime code and asserts no implementation.
ActivationCrossedReceiptdoes not exist anywhere in the runtime today. This contract audits current state honestly, proposes a candidate contract subject to implementation proof, and names the blockers that must be resolved by a narrow decision rung before an implementation PR can begin. Receipts record facts and grant no authority.
1. Purpose
The process-transition receipt lane under #1748 / #2141 has landed four classes that make institutional process legible as replayable, hash-anchored evidence:
ProcessSessionOpenedReceipt(anchor);DeliberationEntryRecordedReceipt;DecisionRecordedReceipt;ProcessGateResultReceipt.
The framing spine (ops/ideas/framing/institutional-process-substrate.md) orders the substrate as:
preview → deliberation → decision → activation → mutation plan → action cards → receipts → evidence
The four landed classes cover preview → decision. The next narrow, VM-executable rung is activation — the explicit boundary a recorded decision crosses on its way toward action, before any mutation is planned or applied. This document is the design contract for the receipt that would witness the fact of that crossing.
It is deliberately not a general workflow engine, not mutation planning, and not evidence-packet production. It is one receipt rung, and — as the audit below shows — it needs a decision rung of its own before implementation, exactly as DecisionRecordedReceipt needed the Q4 decision (decision-recorded-q4-decision.md) before #2280–#2282.
2. Status basis
Verified live at authoring time (origin/main @ 0f4fa895):
- #1749 — read-model dogfood slice for the Institutional Process Substrate — landed (merged).
- #2291 — fixture-only process-evidence member-shell surface (
?mode=demo&set=process-evidence) rendering the four existing receipt classes — landed (merged,b28fbeb2). - #2292 — human/AT validation packet extended to the process-evidence surface — landed (merged,
0f4fa895); the packet was extended, not executed. - #2041 — real screen-reader / low-vision / switch / AT-compat human pass — open / parked for a broader human-testing phase; not attempted here.
- #2289 — organizer-steward evidence surface scope — closed / completed (by #2290 design + #2291 impl).
No production, pilot, organizer-ready, member-ready, live-federation, NYCN-activation, or Phase-2 claim is made or implied by this document.
3. Current repo audit
Classification of every process-transition / activation / mutation / evidence term, read against origin/main @ 0f4fa895:
| Term | State | Where |
|---|---|---|
ProcessSessionOpenedReceipt |
runtime (Rust) | icn/crates/icn-governance/src/proof.rs; wired in apps/governance/{manager,http/handlers,receipt_backend}; *_runtime_slice.rs test |
DeliberationEntryRecordedReceipt |
runtime (Rust) | proof.rs + app wiring + runtime slice |
DecisionRecordedReceipt |
runtime (Rust) | proof.rs + app wiring + runtime slice |
ProcessGateResultReceipt |
runtime (Rust) | proof.rs; wired in apps/governance. Note: icn-baseline-lock/src/receipt_emit.rs emits BaselineProcessGateResultReceipt — a baseline-lock test stand-in with a separate type/domain tag, not production governance emission of this class. |
ActivationCrossedReceipt |
docs/framing only | framing + dogfood MVP + decision-recorded-receipt.md / -q4-decision.md (name candidate). No Rust struct, tag, manager method, backend class constant, route, or test. |
ActivationRequest (gate object) |
docs/framing only | framing + dogfood MVP. No Rust gate object, no "activation authority", no "second-screen confirm" primitive. |
| "activation crossed" / "activation boundary" (phrases) | framing only / pre-contract audit found no seam | concept expressed in framing as "the boundary between deciding and doing". Before this contract, the repo audit found no activation boundary runtime or documented seam outside those framing concepts; this design document — and the registry/index references generated from it — now introduces the design term for #2293. (The no-hit finding is stated as of the pre-contract audit, outside this document, so it stays true after merge.) |
MutationPlanRecordedReceipt |
docs/framing only | framing/dogfood/STATE. No Rust. |
MutationAppliedReceipt |
docs/framing only | framing notes it exists "only in concept" via existing action-item/governance receipt families; no dedicated class. |
EvidencePacketProducedReceipt |
docs/framing only | framing/dogfood/STATE. No Rust; #2289 used a repo-safe fixture export summary, not a runtime producer. |
Honest bottom line: the four landed classes are the only ProcessTransitionReceipt runtime types. Everything from activation onward — including this contract's subject — is a named candidate with no runtime seam. This class is entirely seam-discovery work.
3.1 The pattern the four landed classes share (what a fifth class would mirror)
#[derive(Clone, Debug, Serialize, Deserialize)]; hand-writtenPartialEq/Eqanchored only torecord_hash.- A
DOMAIN_TAGconst followingicn:gov:<class_snake_case>:v1, hashed first, required to be disjoint from every other tag. - Anchor is always the
(domain_id, session_id)pair —session_idis meaningful only withdomain_id; a receipt requires the session to have been opened first (ProcessSessionOpenedReceiptprecondition). - A caller-opaque per-item id (
entry_id/decision_id). recorded_by/opened_by/author: a DID string, actor evidence that grants zero authority ("recorder, not decider").recorded_at: u64(Unix seconds), hashed intorecord_hashbut excluded from duplicate identity — a retry never restamps.body_hash: Hash(Deliberation/Decision only): a caller-supplied 32-byte fingerprint; the body is never stored.record_hash: Hash: canonical blake3 overDOMAIN_TAG+ length-prefixed variable-length strings + explicit-u8enum ordinals +recorded_at.to_le_bytes()+ any fixed-sizebody_hash(raw, no length prefix).- Uniqueness/idempotence via the
put_opaque_if_absentbackend primitive (§9); duplicate identity is the stable fields only (domain_id, session_id, item_id+ author/body_hash for conflict detection). - No
target_refon any class (Q1 deferred); no cross-receipt reference on any class (no receipt names another receipt'srecord_hash/id).
4. Problem statement
A human decision can be recorded today (DecisionRecordedReceipt), gate results can be recorded (ProcessGateResultReceipt), and the process-evidence surface (#2291) can render that evidence read-only. But there is not yet a narrow, receipt-backed activation boundary: no evidence object that says a recorded decision crossed from review-only into "ready to drive action," with the required gates observed as passed.
Framing (institutional-process-substrate.md) states the boundary directly:
"a decision is not a mutation. A decision authorizes a mutation; a mutation plan describes one; an activation request crosses the boundary; only then does runtime mutate."
"
ActivationRequest… declares: a decision has been recorded, the authority to act is established, and the institution is now ready to cross from review-only into mutation. … It is the gate. It can be refused (process gate result fails, accessibility review pending, privacy review pending, charter rule unmet, settlement window not yet open)."
The dogfood MVP (institutional-process-substrate-mvp.md, Step 5) walks activation as read-model only: "activation does not occur in this slice. ActivationRequest is sketched, not issued." It pairs a future ActivationCrossedReceipt (name candidate) with the existing envelope, gated behind gate-results all pass.
This is not production activation, NYCN activation, service deployment, pilot readiness, or mutation application. For this dogfood slice, "activation" is a local/dev/fixture institutional fact: a recorded decision was accepted as ready to drive a later action-planning step, with its required gates observed as passed.
5. Activation boundary definition (for this slice)
For this dogfood slice, activation means: an already-recorded decision (witnessed by a
DecisionRecordedReceipt) is accepted as ready to drive a later action/mutation-planning step, inside a local/dev/fixture process path, conditioned on the requiredProcessGateResultReceipts observingpass. It is the gate, not the mutation.Activation in this slice is not production activation, not service deployment, not NYCN launch, not mutation planning, and not mutation application. Crossing the boundary produces a receipt of the crossing and nothing else; it mutates no domain state and grants no authority.
The gate can be refused — a design-level requirement, not an implementation claim: if a required gate result is Fail or absent, the boundary is not crossed and no ActivationCrossedReceipt is emitted (fail-closed, mirroring the session-precondition and conflict discipline of the landed classes).
6. Proposed receipt contract (candidate — subject to implementation proof)
Candidate tag: icn:gov:activation_crossed:v1 (must hash-separate from all existing tags, and must never converge with icn:gov:decision_recorded:v1, icn:gov:process_gate_result:v1, or the proposal/vote icn:gov:decision:v1/v2/v3 lineage).
Candidate fields (naming follows the landed classes; anything marked OPEN is a blocker deferred to §11's decision rung, not an asserted field):
| Field | Type | Notes |
|---|---|---|
domain_id |
String |
anchor half; hashed length-prefixed |
session_id |
String |
anchor half; session must be opened first (precondition) |
activation_id |
String |
caller-opaque per-activation id (mirrors decision_id/entry_id); the key2 uniqueness half |
decision_ref |
OPEN | how this activation names the decision it crosses — see blocker B1. A cross-receipt reference (by decision_id and/or a decision record_hash) would be the first inter-receipt link in this lane; no existing pattern supports it. |
gate_basis |
OPEN | how the "gates observed as passed" precondition is witnessed in the receipt — see blocker B2 (a fingerprint of the required gate-result set? a count? nothing, relying on external query?). |
crossed_by |
String (DID) |
actor evidence — the recorder of the crossing, not an authority to act ("recorder, not crosser"); grants zero authority |
crossed_at / effective_at |
u64 |
OPEN — caller-supplied recorded_at (current receipt pattern) vs decision-carried effective time — see blocker B3 |
body_hash |
Hash (optional) |
if an ActivationRequest payload is fingerprinted, a caller-supplied 32-byte hash; the request body is never stored |
record_hash |
Hash |
canonical blake3 per §3.1 hashing discipline; the sole PartialEq/Eq anchor |
Canonical hashing (candidate): DOMAIN_TAG first → length-prefixed domain_id, session_id, activation_id, crossed_by (and any resolved decision_ref string) → crossed_at.to_le_bytes() → any fixed-size body_hash/gate_basis raw (no length prefix). Exact layout is fixed by the implementation PR and pinned by a golden vector (§12).
Deliberately absent (must never appear in v1):
- no mutation content, mutation plan, action-card payload, or applied-effect;
- no proposal/vote/tally/quorum/mandate/outcome semantics;
- no authority grant, capability, or token;
- no new
ProcessGateKindsemantics embedded in the receipt (the receipt witnesses that gates passed; it does not evaluate gates); - no
target_ref(Q1 stays deferred; binding is the session anchor only); - no stored deliberation/decision/request body (fingerprints only).
Session precondition & duplicate semantics: identical to the landed classes — the (domain_id, session_id) session must be opened first (fail-closed otherwise); at most one activation per (domain_id, session_id, activation_id); a same-identity retry returns the original receipt un-restamped; a different crossed_by/body_hash/decision_ref for the same identity is a fail-closed conflict.
7. ADR-0026 envelope usage
ActivationCrossedReceipt should sit where the other four sit: ADR-0026 Layer 2, alongside GovernanceDecisionReceipt, as a self-contained record carrying its own canonical blake3 record_hash.
Honest layering caveat the implementation PR must respect: ADR-0026's written Layer-2 model (ArtifactReceipt wrapping a signed, merkle-rooted Layer-1 GovernanceProof) predates the process-transition classes. Those classes reuse the Layer-2 slot but use a lighter model — a self-hashed blake3 record_hash, no signature, no merkle root. This contract does not claim the activation receipt inherits the signed-proof envelope; it inherits the process-transition discipline (self-contained record hash, opaque-store persistence). Any future signature/merkle upgrade is out of scope here and would be an ADR-0026 revision, not a receipt rung.
8. Links and provenance
How the receipt would link back (design-level; the cross-receipt link is blocker B1):
- process/session — via the
(domain_id, session_id)anchor (existing pattern; no new seam). - decision — via
decision_ref(OPEN, B1): naming theDecisionRecordedReceiptbeing activated. This is the lane's first inter-receipt reference; the DecisionRecorded contract deferred even linking a decision to its deliberation entries, so this posture is a genuine open question, not a copy of an existing pattern. - gate result(s) — via
gate_basis(OPEN, B2): witnessing that the requiredProcessGateResultReceipts werepass. Candidate approaches: a fingerprint over the required gate-resultrecord_hashes; a plain "all required gates passed" boolean with the gate set named out-of-band; or no in-receipt basis (the surface joins gate receipts by session at read time). - proof/envelope metadata — the receipt's own
record_hashis the provenance pointer; persistence and retrieval go through the same opaque receipt store as the other four (§9).
9. Idempotence and replay
Design requirements (mechanism already exists for the landed classes; this class would reuse it):
- Emission goes through the backend primitive
put_opaque_if_absent(GovernanceReceiptBackend; production impl inicn-gateway'sReceiptStore, atomic within one sled transaction).Nonereturned ⇒ this write won;Some(existing)⇒ hydrate and return the original persisted receipt — never re-stamp. - The uniqueness marker is keyed on
(class, key1, key2)wherekey1is an injective netstring-style composite of(domain_id, session_id)andkey2isactivation_id. Injectivity must be tested (("ab","c")vs("a","bc")must not alias; two domains sharing asession_idmust never mix). - Same-identity retry ⇒ idempotent return of the original; different stable-identity fields for the same key ⇒ fail-closed conflict (e.g.
activation_crossed_conflict, mirroringdecision_recorded_conflict). - Concurrent duplicate crossings must serialize to exactly one winner; losers observe the winner.
- Timestamp doctrine (blocker B3, but the invariant holds regardless): a timestamp may live in
record_hashonly because the receipt is idempotent on stable, non-timestamp identity — so two nodes replaying the same logical crossing converge on the original receipt (original timestamp, original hash) rather than minting divergent wall-clock hashes. Local wall-clock must not be an input to any cross-node-deterministic identity. Whethercrossed_atis a caller-suppliedrecorded_ator a decision-carriedeffective_at(the #2288 doctrine, which currently lives only in the membership actor lane, not in any receipt) is the open choice.
10. Privacy boundary
- No private deliberation, decision, or activation-request body text in the receipt — fingerprints (
body_hash) only, exactly as Deliberation/Decision store body_hash and never the body. - Only hashes, opaque ids, DIDs, and repo-safe metadata are carried.
- Any private content behind an
ActivationRequeststays fixture-safe or redacted; the receipt proves a crossing occurred, not that all audiences may read the underlying request. - A future evidence/export summary of activation must be a repo-safe fixture summary (the #2289 pattern:
record_hash/body_hashproof pointers with redaction reasons, never private text).
11. Member-shell / evidence-surface decision
Recommendation: defer rendering. Member-shell rendering is out of scope for the first implementation PR (and entirely out of scope for this docs-only PR — web/member-shell/ is not touched here). The #2291 surface is fixture-only and read-only; wiring a real ActivationCrossedReceipt into it should follow the receipt landing, not precede it.
The contract must nonetheless specify, at minimum, how a future member-shell evidence story would explain the boundary: a plain-language "activation crossed / not yet crossed" state for a session, showing which required gates were observed as passed and the decision it activated, sourced entirely from record_hash/body_hash proof pointers (no private text), inside the existing fixture/dry-run/live labeling. A later, separately-scoped PR may add a fixture-only surface extension if it can do so safely, exactly as #2291 did for the first four classes.
12. Validation plan (for the future implementation PR)
The implementation PR must include both test tiers the landed classes use:
proof.rsunit tests: a golden vector pinning the v1record_hashof a fixed sample; a determinism test (same inputs ⇒ same hash); a per-field test (every field change ⇒ different hash); and a tag-disjointness test assertingicn:gov:activation_crossed:v1never collides with — and carries a comment that it must never converge with —decision_recorded,process_gate_result, and the proposal/voteicn:gov:decision:vNlineage.- Runtime-slice integration test (mirror
decision_recorded_receipt_runtime_slice.rs): emission + field round-trip + non-zerorecord_hash+ retrieval; same-identity retry returns original, never restamped; different-crossed_byand different-body_hash/decision_refconflicts fail closed; unopened-session fails closed and creates nothing; empty/whitespace ids rejected pre-persistence; missing receipt store / backend failure fail closed; concurrent duplicates serialize to one winner; composite key injective (no aliasing); two domains sharing asession_idnever mix. - Gate-precondition test: if a required gate result is
Failor absent, the boundary is not crossed and no receipt is emitted (fail-closed). - Privacy grep: no private body text in any serialized receipt or fixture.
- No-overclaim grep: no "activation implemented / complete / production / pilot / organizer-ready / member-ready / live federation / NYCN" claims introduced by the change.
- Docs/fixture check if any fixture is added later (doc-control + generated-index convergence).
13. Deferred work (explicitly out of scope of this contract and its future implementation)
MutationPlanRecordedReceipt— the receipt that would describe a planned mutation after activation.MutationAppliedReceipt— the receipt that would witness a mutation actually applied.EvidencePacketProducedReceipt— a runtime evidence-packet producer (the #2289 export was a repo-safe fixture summary, not this).- The actual #2041 human/AT pass (screen-reader / low-vision / switch / AT-compat) — parked for a real human-testing phase.
- Production / pilot / NYCN activation / live federation / Phase-2 work.
- entity-auth enforcement (#2081), trusted token issuance (#2080), UnknownLegacy repair (#2274), service hosting, K3s/DNS/Forgejo.
14. Implementation sequencing
Implementation cannot begin from this contract alone. Three blockers below have no existing seam and must be resolved by a narrow decision rung (a sibling decision doc, mirroring decision-recorded-q4-decision.md) before an implementation PR:
- B1 — decision→activation reference posture. Does
ActivationCrossedReceiptname the decision it activates, and if so bydecision_id, by decisionrecord_hash, or both? This would be the lane's first inter-receipt link. - B2 — gate-basis representation. How does the receipt witness "required gates observed as passed"? (fingerprint of the gate-result set / boolean+external join / nothing.) And: is a new
ActivationRequestgate object and/or a newProcessGateKindvariant required?ProcessGateKindis a closed 6-variant enum (PrivacyReview,AccessibilityReview,RepoSafetyReview,ScopeConfirmation,NoMutationCheck,SecondReviewerSignoff); adding an activation gate is an ADR-controlled taxonomy change, not a free append. - B3 — activation timestamp source. Caller-supplied
crossed_at(current receipt pattern) vs decision-carriedeffective_at(the #2288 doctrine, currently membership-actor-only).
Recommendation (Option C, matching the DecisionRecorded lane cadence): land this design contract; then a narrow decision doc resolving B1/B2/B3; only then a contract-conformant implementation PR. The implementation PR must keep #1748 / #2141 / #2041 open unless separately reviewed, and must leave #2293 open for maintainer disposition rather than auto-closing it by side effect.
15. Non-goals
Restated from #2293 — this contract and its future implementation are:
- not #2041 completion; not human/AT execution;
- not production / pilot / organizer-ready / member-ready readiness;
- not live federation; not NYCN activation; not Phase-2 completion;
- not #2081 / #2080 / #2274; not entity-auth enforcement; not trusted token issuance; not UnknownLegacy repair; not service hosting; not K3s/DNS/Forgejo;
- not a general workflow engine; not chat/comment/moderation/social feed;
- not proposal/vote/quorum/mandate semantics;
- not mutation planning or mutation application;
- not
EvidencePacketProducedReceipt— unless a decision rung proves the sequence must be split differently.
16. Related
Refs #2293. Refs #1748. Refs #2141. Refs #2041. Refs #2291. Refs #2292.