From e12ee638406aa3e87ab04efa78a405d383e8d3d0 Mon Sep 17 00:00:00 2001 From: Libby Date: Tue, 10 Nov 2026 18:19:26 +0000 Subject: [PATCH] Fork desk digests: door-form legal_retraction golden (#75) --- .../cultnet/governance/fork-card-vectors.json | 29 +++++++++++++++---- contracts/cultnet/governance/fork-card.md | 2 +- .../test/governance-vectors.test.ts | 2 ++ scripts/gen-governance-vectors.py | 7 +++-- .../GovernanceVectorTests.cs | 2 ++ 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/contracts/cultnet/governance/fork-card-vectors.json b/contracts/cultnet/governance/fork-card-vectors.json index d65162dd..259f468c 100644 --- a/contracts/cultnet/governance/fork-card-vectors.json +++ b/contracts/cultnet/governance/fork-card-vectors.json @@ -364,7 +364,7 @@ { "name": "legal_retraction", "status": "main_7ea11b4_must_not_move", - "why": "a legal retraction: choice and note null, retracts the full stored key (Huginn's pinned test names Q1.1)", + "why": "a legal retraction as Huginn main pins it: retracts is `Q1.1`, a string no door produces (the key grammar is .n); kept for the frozen-form test", "input": { "fork": "eureka-state:question:Q1", "options_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -395,6 +395,23 @@ "canonical": "{\"fork\":\"eureka-state:question:Q1\",\"options_hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"answered_at\":\"2026-11-10T12:00:00Z\",\"choice\":\"A\",\"note\":null,\"retracts\":\"eureka-state:desk_answer:Q1.n1\",\"illegal_retraction\":true}", "sha256": "69074cc60829ef5edb12358746f7549ad08da97653d59359c945cea102f53565" }, + { + "name": "legal_retraction_door_form", + "status": "main_7ea11b4_must_not_move", + "why": "a legal retraction as the door produces it: the wire's n1 converted to the full stored key Q1.n1 before the digest; the golden that stands for the real form (digest unaffected by .n vs .1)", + "input": { + "fork": "eureka-state:question:Q1", + "options_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "answered_at": "2026-11-10T12:00:00Z", + "choice": null, + "note": null, + "retracts": "eureka-state:desk_answer:Q1.n1", + "illegal_retraction": null, + "retracted": null + }, + "canonical": "{\"fork\":\"eureka-state:question:Q1\",\"options_hash\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"answered_at\":\"2026-11-10T12:00:00Z\",\"choice\":null,\"note\":null,\"retracts\":\"eureka-state:desk_answer:Q1.n1\"}", + "sha256": "84576840247830b9b75754b460dc4ea52883ca1e410f9f011521e0f69b99eee1" + }, { "name": "illegal_ii_on_main", "status": "main_7ea11b4_must_not_move", @@ -414,7 +431,7 @@ }, { "name": "illegal_i_retraction_with_an_answer_retracted_true", - "status": "pending_eureka_merge_cut-desk-digest-retracted.r2", + "status": "pending_eureka_merge_huginn_30_cut-desk-digest-retracted.r2", "why": "(i) answer present, retracted true, target Q1.n1", "input": { "fork": "eureka-state:question:Q1", @@ -431,7 +448,7 @@ }, { "name": "illegal_iii_plain_answer_with_a_target_retracted_false", - "status": "pending_eureka_merge_cut-desk-digest-retracted.r2", + "status": "pending_eureka_merge_huginn_30_cut-desk-digest-retracted.r2", "why": "(iii) the same bytes as (i) with retracted false: another body, so request_id_conflict", "input": { "fork": "eureka-state:question:Q1", @@ -448,8 +465,8 @@ }, { "name": "illegal_i_retraction_with_an_answer_and_no_target_retracted_true", - "status": "computed_here_by_the_same_rule_not_in_the_order_doc_pending_eureka_merge", - "why": "(i) with the target null (the order doc: retracts set or null); verified with sha256sum, but Eureka has not published this golden", + "status": "pending_eureka_merge_huginn_30_cut-desk-digest-retracted.r2", + "why": "(i) with the target null (the order doc: retracts set or null); Eureka re-derived it with sha256sum (order doc addendum); the fix branch's pinned test does not yet carry it", "input": { "fork": "eureka-state:question:Q1", "options_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -465,7 +482,7 @@ }, { "name": "illegal_ii_no_answer_no_target_retracted_true", - "status": "pending_eureka_merge_cut-desk-digest-retracted.r2", + "status": "pending_eureka_merge_huginn_30_cut-desk-digest-retracted.r2", "why": "(ii) no answer, no target, retracted true", "input": { "fork": "eureka-state:question:Q1", diff --git a/contracts/cultnet/governance/fork-card.md b/contracts/cultnet/governance/fork-card.md index ffbc66f8..fd2c3d55 100644 --- a/contracts/cultnet/governance/fork-card.md +++ b/contracts/cultnet/governance/fork-card.md @@ -67,7 +67,7 @@ The stamped `authority` field of a stored answer is an **attribution**: the role **The mind's record and this document.** Huginn keeps the answer as its own record kind (`epiphany.pipeline.desk_answer.v2`, key `:desk_answer:.n`, the outcome as one externally tagged value, the fork as `fork`). The second door projects it to this document and nothing else crosses: `answer_id` is the key's `n` part, `fork_id` is the stored `fork`, the tagged outcome flattens into `outcome`, `of`, `retracts` and `code`, and the six refusal codes are spelt as in the schema (snake_case; the record's enum spells them in the mind's own PascalCase). On the wire an answer is named by its `n` alone, always inside the fork whose `fork_id` the document carries; a runtime that holds the full record key converts at the door, in one place. `body_sha256` and any other digest Huginn keeps to decide "same body" stay in the record: Huginn alone decides a replay or a `request_id_conflict` and Bifrost never compares bodies, so no digest is a field of this document. A digest is a valid way to decide it only if it covers `fork_id`, `options_hash`, `answer.choice`, `answer.note`, `retracted`, `retracts` and `answered_at` (every field of the request but `request_id`), is computed by Huginn and never read from a client. -**Huginn's digest, pinned in the vectors (`bodyDigests`; informative, not a wire document).** SHA-256 of one compact JSON object as `serde_json::to_string` writes it (UTF-8, no spaces), lowercase hex, keys in this order, `null` for a missing value, strings verbatim: `fork`, `options_hash`, `answered_at`, `choice`, `note`, `retracts` (the **full stored key** `:desk_answer: