Desk answers, Huginn half 1: desk_answer kind and the Mind counting door #29

Merged
eureka merged 11 commits from eureka-substrate/desk-answer-record into main 2026-11-10 17:51:54 +00:00
Owner

Cut eureka-substrate:cut_spec:cut-desk-answer-record.r1: Huginn half 1 of the fork desk's answers. Rulings: desk-answer-kind-tagged-outcome, desk-retraction-is-revision, desk-deciders-outside-mind, desk-second-peer-checked-door. Binding spec: /opt/commons/handoff/fork-desk-answer-binding-20261110.md.

options_hash is filled from CultLib PR #72 (no longer stubbed). It was stubbed in the first commit and filled in 5785c0c after Heimdall's verdict and Libby's go, at the bytes of PR #72 head 7aec4961 (the spec named c2668509; the branch has moved, A-G are unchanged): NFC every string, JCS through a struct declared in key order, SHA-256, lowercase hex. One owner: huginn_mind::options_hash. Libby's 15 vectors (A-G of the binding file plus eight) run as tests (vectors/fork-card-options-hash.json); E equals D, and a second test shows E differs from D without NFC.

What lands:

  • desk_answer kind (epiphany.pipeline.desk_answer.v2, epoch stays v2), tagged DeskOutcome (Counted, Revision, Retraction, Refused), key <campaign>:desk_answer:<question label>.n<seq>; published schema, index row, golden line, huginn schema desk_answer.
  • Mind::answer_desk(stamp, request, now): rules 3-8, R1, R2, R4; refusals 4-7 and R1 are stored; unknown fork, shape fault, moved campaign are refused and not stored. counted_answer derives the counted answer on every read.
  • Door::Desk: admit, import and derivation refuse a desk_answer (DeskAnswerOutsideDesk); matrix is unchanged, so no resolution closes an answer.
  • Not in this cut: R3 and the Ruling rests_on field (follow-up desk-operator-cites-answer), the daemon door, Bifrost, deploy. V10b/ruled is dropped; only fork_closed is producible.

Deviations: unknown fork or shape fault stores nothing (spec's own deviation from 1.3b). depends hashed as stored prose lines. Wire request/response schemas regenerate because PipelineDocument rides Admit. New dependency unicode-normalization (plus tinyvec, tinyvec_macros) in huginn-mind only. selection_schema_matches_cultlib needs network (cargo fetch offline) and was not run here; the clippy -D warnings run fails on three lines in files this cut does not touch (move_tests.rs, import.rs).

Verification: cargo test -p eureka-pipeline -p huginn-mind green (75 + 307); cargo check -p huginn-daemon -p eureka-state -p mind-body --all-targets green; hand mutants per rule probed, all killed except one equivalent (the post-derivation Door check, since no derivation emits a desk_answer).

Fix batch (attempt 2, head 368217a, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h2)

Contract: CultLib PR #72 head 2dea6c56.

  • The decider list is an input (DeskStamp.deciders); membership alone decides rule 5. authority is an attribution (Operator, Steward, Member), stored on every answer including refused ones.
  • answer_desk and DeskStamp are crate-private. Receipt faculty is Operator only for the operator account (others SelfFaculty, placeholder, needs a ruling).
  • counted_answer and R1 consider options_hash. received_at and claim_in_future are stored; answered_at must be UTC.
  • Notes are Words: 1..=4000 bytes, not blank. Decomposed-character tests for context, option text and depends. Closed-fork retraction test.
  • request_id (lowercase D-format UUID) on every desk_answer; key (answerer, request_id); same body replays the stored outcome and writes nothing, another body is request_id_conflict (stored: no). A replayed stale_fork carries no card from Huginn.
  • Tests: cargo test -p eureka-pipeline -p huginn-mind 75 + 321 green; check of daemon, eureka-state, mind-body green. 25 hand mutants, all killed (listed in the report).
  • Not done: depends rendering gap (follow-up note), Admit schema still lists desk_answer, vector file is a byte slice of PR #72's array, not a whole file. Not merged, not deployed.

Fix batch 3 (attempt 3, head 3a16772, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h3)

Heimdall's rulings (follow-up chat-s5422-5422).

  • Faculty gains Member and Steward: attribution, not authority. Admission reads no faculty; a test admits ordinary and desk_answer batches under every variant and gets Hands' outcome. Desk receipts now name Operator, Steward or Member per the stamp; the SelfFaculty placeholder is gone. Published request/response schemas and the faculty row alias carry the new values.
  • body_sha256 stays: Huginn computes it over the canonical body (answered_at and the retraction target included), never from the client. It is the only "same body" comparison in the code. The digest form is frozen with a golden-hex test.
  • Pinned: the request_id lookup follows the shape check; claim_in_future is decided at sub-second precision.
  • 4 hand mutants, all killed. cargo test -p eureka-pipeline -p huginn-mind 75 + 325 green; check of daemon, eureka-state, mind-body green.
  • Notes, not done: Admit schema still lists desk_answer; depends rendering gap. Not merged, not deployed.

Fix batch 4 (attempt 4, head e70a0fe, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h4)

Libby's read (divergences 3, 4, 5) and Heimdall's rulings (follow-up chat-s5449-5450).

  • The typed request is as wide as the wire body. DeskBody::IllegalRetraction (a retraction carrying an answer, lacking retracts, or a plain answer carrying retracts) is stored Refused bad_retraction after steps 4-6, binds its request_id and replays unchanged. choice is the person's text (1..=200 bytes): one that is no option label, grammar or not, is stored Refused choice_invalid (the record keeps no choice then). Unknown fork or malformed body stays unstored and binds nothing. The body digest carries a flag for the illegal form only, so existing digests are unchanged.
  • claim_in_future is decided against the whole-second received_at written to the record, not the clock reading.
  • Pinned: every Faculty serde name is in the faculty row alias values.
  • 6 hand mutants, all killed. cargo test -p eureka-pipeline -p huginn-mind 75 + 331 green; check of daemon, eureka-state, mind-body green; schema tests green; no schema or golden changed.
  • Not done: the lookup-before-probe-validate Low note (the pinned order is lookup after the shape check; I could not read the note); wire naming is cut-desk-wire.r3. Not merged, not deployed.

🤖 Generated with Claude Code

Cut `eureka-substrate:cut_spec:cut-desk-answer-record.r1`: Huginn half 1 of the fork desk's answers. Rulings: desk-answer-kind-tagged-outcome, desk-retraction-is-revision, desk-deciders-outside-mind, desk-second-peer-checked-door. Binding spec: /opt/commons/handoff/fork-desk-answer-binding-20261110.md. **options_hash is filled from CultLib PR #72 (no longer stubbed).** It was stubbed in the first commit and filled in `5785c0c` after Heimdall's verdict and Libby's go, at the bytes of PR #72 head 7aec4961 (the spec named c2668509; the branch has moved, A-G are unchanged): NFC every string, JCS through a struct declared in key order, SHA-256, lowercase hex. One owner: `huginn_mind::options_hash`. Libby's 15 vectors (A-G of the binding file plus eight) run as tests (`vectors/fork-card-options-hash.json`); E equals D, and a second test shows E differs from D without NFC. What lands: - `desk_answer` kind (`epiphany.pipeline.desk_answer.v2`, epoch stays v2), tagged `DeskOutcome` (Counted, Revision, Retraction, Refused), key `<campaign>:desk_answer:<question label>.n<seq>`; published schema, index row, golden line, `huginn schema desk_answer`. - `Mind::answer_desk(stamp, request, now)`: rules 3-8, R1, R2, R4; refusals 4-7 and R1 are stored; unknown fork, shape fault, moved campaign are refused and not stored. `counted_answer` derives the counted answer on every read. - `Door::Desk`: `admit`, import and derivation refuse a `desk_answer` (`DeskAnswerOutsideDesk`); matrix is unchanged, so no resolution closes an answer. - Not in this cut: R3 and the Ruling `rests_on` field (follow-up desk-operator-cites-answer), the daemon door, Bifrost, deploy. V10b/`ruled` is dropped; only `fork_closed` is producible. Deviations: unknown fork or shape fault stores nothing (spec's own deviation from 1.3b). `depends` hashed as stored prose lines. Wire request/response schemas regenerate because `PipelineDocument` rides `Admit`. New dependency `unicode-normalization` (plus `tinyvec`, `tinyvec_macros`) in huginn-mind only. `selection_schema_matches_cultlib` needs network (cargo fetch offline) and was not run here; the clippy `-D warnings` run fails on three lines in files this cut does not touch (`move_tests.rs`, `import.rs`). Verification: `cargo test -p eureka-pipeline -p huginn-mind` green (75 + 307); `cargo check -p huginn-daemon -p eureka-state -p mind-body --all-targets` green; hand mutants per rule probed, all killed except one equivalent (the post-derivation Door check, since no derivation emits a desk_answer). ## Fix batch (attempt 2, head 368217a, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h2) Contract: CultLib PR #72 head 2dea6c56. - The decider list is an input (`DeskStamp.deciders`); membership alone decides rule 5. `authority` is an attribution (Operator, Steward, Member), stored on every answer including refused ones. - `answer_desk` and `DeskStamp` are crate-private. Receipt faculty is Operator only for the operator account (others `SelfFaculty`, placeholder, needs a ruling). - `counted_answer` and R1 consider `options_hash`. `received_at` and `claim_in_future` are stored; `answered_at` must be UTC. - Notes are `Words`: 1..=4000 bytes, not blank. Decomposed-character tests for context, option text and depends. Closed-fork retraction test. - `request_id` (lowercase D-format UUID) on every `desk_answer`; key (answerer, request_id); same body replays the stored outcome and writes nothing, another body is `request_id_conflict` (stored: no). A replayed `stale_fork` carries no card from Huginn. - Tests: `cargo test -p eureka-pipeline -p huginn-mind` 75 + 321 green; check of daemon, eureka-state, mind-body green. 25 hand mutants, all killed (listed in the report). - Not done: depends rendering gap (follow-up note), Admit schema still lists `desk_answer`, vector file is a byte slice of PR #72's array, not a whole file. Not merged, not deployed. ## Fix batch 3 (attempt 3, head 3a16772, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h3) Heimdall's rulings (follow-up chat-s5422-5422). - `Faculty` gains `Member` and `Steward`: attribution, not authority. Admission reads no faculty; a test admits ordinary and desk_answer batches under every variant and gets Hands' outcome. Desk receipts now name Operator, Steward or Member per the stamp; the SelfFaculty placeholder is gone. Published request/response schemas and the faculty row alias carry the new values. - `body_sha256` stays: Huginn computes it over the canonical body (answered_at and the retraction target included), never from the client. It is the only "same body" comparison in the code. The digest form is frozen with a golden-hex test. - Pinned: the request_id lookup follows the shape check; claim_in_future is decided at sub-second precision. - 4 hand mutants, all killed. `cargo test -p eureka-pipeline -p huginn-mind` 75 + 325 green; check of daemon, eureka-state, mind-body green. - Notes, not done: Admit schema still lists desk_answer; depends rendering gap. Not merged, not deployed. ## Fix batch 4 (attempt 4, head e70a0fe, cut_report eureka-substrate:cut_report:cut-desk-answer-record.h4) Libby's read (divergences 3, 4, 5) and Heimdall's rulings (follow-up chat-s5449-5450). - The typed request is as wide as the wire body. `DeskBody::IllegalRetraction` (a retraction carrying an answer, lacking `retracts`, or a plain answer carrying `retracts`) is stored `Refused bad_retraction` after steps 4-6, binds its request_id and replays unchanged. `choice` is the person's text (1..=200 bytes): one that is no option label, grammar or not, is stored `Refused choice_invalid` (the record keeps no choice then). Unknown fork or malformed body stays unstored and binds nothing. The body digest carries a flag for the illegal form only, so existing digests are unchanged. - `claim_in_future` is decided against the whole-second `received_at` written to the record, not the clock reading. - Pinned: every `Faculty` serde name is in the faculty row alias values. - 6 hand mutants, all killed. `cargo test -p eureka-pipeline -p huginn-mind` 75 + 331 green; check of daemon, eureka-state, mind-body green; schema tests green; no schema or golden changed. - Not done: the lookup-before-probe-validate Low note (the pinned order is lookup after the shape check; I could not read the note); wire naming is cut-desk-wire.r3. Not merged, not deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
One kind, key <campaign>:desk_answer:<question label>.n<sequence>, one tagged
outcome (Counted, Revision, Retraction, Refused). Published schema, index row,
golden line and the PipelineKind enum in sibling schemas regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rules 3-8, R1, R2, R4 of the binding spec; counted_answer derives which answer
counts; Door::Desk is the only door that may write a desk_answer, so Admit,
import and derivation refuse it. options_hash sits behind one seam, failing
closed until the bytes are filled; tests use a named fixture hash.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NFC every string, JCS (struct fields in key order through serde_json), SHA-256,
lowercase hex; the seam stays the single owner. Libby's 15 vectors, A-G included,
run as tests; the test fixture hash is gone. Adds unicode-normalization.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DeskStamp carries answerer, attribution (Operator/Steward/Member per CultLib PR #72) and the decider
list; membership decides rule 5, the attribution never does. answer_desk and DeskStamp are crate-private.
The receipt names the operator only for the operator's account. counted_answer and R1 consider
options_hash. A stored answer keeps received_at, claim_in_future, request_id and a body digest;
the same (answerer, request_id) and body replays the stored outcome and writes nothing, another
body is request_id_conflict. Notes are Words (1..=4000 bytes, not blank); answered_at must be UTC.
The vector file is re-sliced byte for byte from PR #72 head 2dea6c56.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each test was failed by a hand mutant of the rule it pins.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
DeskBody::IllegalRetraction stores Refused bad_retraction after steps 4-6 (digest carries the form, so it never replays as a plain answer); a choice is any 1-200 byte text, stored Refused choice_invalid when no option label, grammar or not. claim_in_future compares against the whole-second received_at written to the record.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A bound (answerer, request_id) replays its stored outcome without re-running
probe validation; a malformed id binds nothing and still meets the probe.
Comment on the choice bound now says a Short (200 bytes).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PipelineFacts::DeskAnswer gains counts, derived through counted_answer over the
read's Docs::at(image, as_of); the stored outcome name stays the write-time record.
DeskOutcome::Refused carries the retraction target as sent; the record's choice is a
Short kept as sent (a choice_invalid choice is no label), and an illegal retraction
form keeps its choice and note. body_sha256 form unchanged. Schemas regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
eureka merged commit 7ea11b4419 into main 2026-11-10 17:51:54 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Huginn!29
No description provided.