From b693f9f3b3fca1f79182bf6cc4d7510d793328b1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 18:19:25 +0200 Subject: [PATCH] Map Cut 6d: resolution history and stewardship by sequence Two key shapes gain a writer-set sequence so a subject's resolutions and a repo's stewardships keep their history under one prefix, with the in-force record derived as the latest not withdrawn. A hand-off stays a transfer with no lease field. Two schema files regenerate, no epoch moves, and the Huginn follow-up commit is named for when the pin moves. Co-Authored-By: Claude Fable 5.1 --- notes/eureka-pipeline-state-cut.md | 590 ++++++++++++++++++++++++++++- 1 file changed, 582 insertions(+), 8 deletions(-) diff --git a/notes/eureka-pipeline-state-cut.md b/notes/eureka-pipeline-state-cut.md index 47c08c70..b83eaaf8 100644 --- a/notes/eureka-pipeline-state-cut.md +++ b/notes/eureka-pipeline-state-cut.md @@ -45,14 +45,23 @@ schema-ownership phase inside Epiphany, not because the campaign stops. `b6f6e802`, `80db5db6`, `3ee78e05`, second fix batch at `9b68d83c`, `4cccb45c`, `4a654351`. `epiphany-pipeline` is a leaf library Huginn can depend on without `epiphany-core`. -- **Cut 6b landed** at `602ffd9f` (deletes), `1bddd2ac` (grammar), `95ee551a` - (mutation suite); fix batch in Hands. The second fix batch's Soul pass had - found the same invariant open one level up for the third time, so the key - grammar was redesigned rather than patched a fourth time. Soul's first pass - on the grammar found no collision in 212,450 adversarial keys and four - unpinned checks. -- **Cut 6c landed** at `4d6af409`, `13570e84`, `dddf9ede`, after the third - 6b fix batch at `9d57a460`, `7cd1a38b`. Soul in flight on both. +- **Cut 6b landed and closed** at `602ffd9f` (deletes), `1bddd2ac` + (grammar), `95ee551a` (mutation suite), with three fix batches at + `4b85dd2d`, `d03a32df`, `9370aa0f`, `f00062db`, `9d57a460`, `7cd1a38b`. + The second fix batch's Soul pass on Cut 6 had found the same invariant + open one level up for the third time, so the key grammar was redesigned + rather than patched a fourth time; Soul found no collision in 212,450 + adversarial keys. The shared mutation harness came out of its fix + batches. +- **Cut 6c landed and closed** at `4d6af409`, `13570e84`, `dddf9ede`, fix + batch `fcfbda3f`, `3f7d58d1`, harness repairs `b3bd4a82`, `aef0e1bf`. +- **Cut 6d in Hands** against `b4b17fc`: the key sequences Q17, Q19 and Q20 + ruled, in the leaf. Its Huginn follow-up commit lands after Soul closes it + and the pin moves. **Must land before Cut 9.** +- **Cut 8 landed**: the Epiphany half at `a65c6420` (Soul-closed) with the + Cut 10 prerequisite at `b4b17fc`; the Huginn half on `eureka/memory-organ` + at `946758f`, `a4c5b79`, `0bd7133`, `ca30d3e`, fix batch `1cfa81d`, + `acd32f3`, `30daff8`. Soul in flight on the fix batch. - **Cut 7 landed and closed** on Huginn `eureka/memory-organ` at `1320fc4`, `f63c0f2`, `e20c786`, fix `4094e68`; Eve `main` `e777e4c`, fix `167a2d3`; EveConformance `main` `048ea2f`. Started in parallel on the operator's @@ -2045,6 +2054,571 @@ moves to this commit. cost of one shared `CARGO_TARGET_DIR` across sessions, already accepted (correction 22); not a dependency, and not Hands' explanation either. +## Cut 6d. Resolution history and stewardship by sequence + +Imagination, 2026-09-16, revised the same day for Q19 A and Q20 A. +Implements Q17 B, Q18 A as corrected, Q19 A and Q20 A: **a hand-off is a +transfer of stewardship, symmetric and final as a record; a lease is two +transfers and nothing more** (no lease, expiry, return or intent field +anywhere); **a withdrawal cannot be withdrawn**; and **stewardship is keyed by +a per-(instance, repo) sequence, not by date**, exactly as resolutions are +keyed by a per-subject sequence. + +- **Repo/branch:** Epiphany `codex/eureka-pipeline-state`. Anchors are + `file:line` against `epiphany-pipeline/src/lib.rs` at **`b4b17fc`** (2,099 + lines, 28 tests), byte-identical at HEAD `542dc184` (`git diff --stat + b4b17fc HEAD -- epiphany-pipeline/ schemas/cultnet/ tools/` empty, probed + twice). HEAD moved three times during this pass, the map only; re-anchor by + content as every cut since 6b has. +- **Depends on:** nothing unlanded. **Blocks:** Cut 9 (in-force derivation), + Cut 12 (hand-off), and a Cut 8 follow-up commit in Huginn that this cut + implies (below). Lands before any of them. +- **Cost:** two kinds' keys move (`resolution`, `stewardship`); two fields are + added (`resolution.sequence`, `stewardship.sequence`); two schema files + regenerate; no epoch moves; no kind widens. Keys are free to move today: no mind exists on Yggdrasil. + Huginn pins `a65c6420` (`crates/huginn-mind/Cargo.toml:15`) and its fixtures + spell both old shapes, so the pin move is a named follow-up, not a surprise. + +### What the cut does + +**Q17 B.** A resolution is the record of how a subject was closed and by +what. Its key gains a per-subject sequence as the **last** local part: +`:resolution:..n`. Withdrawn +resolutions stay under their subject as records; the subject can be resolved +again at `n`; the history is the key prefix +`:resolution:..n`. + +**Q18 A + Q20 A.** A stewardship is keyed by repo and a per-(instance, repo) +sequence: `:stewardship:.n`. A repo transferred +away and later transferred back to the same mind is two records under one +prefix, `:stewardship:.n`, and the in-force one is +the latest not withdrawn. `assigned_on` stays a field, validated as a `Date` +by the derived `Bounded` impl, and no longer enters the key. + +Both are key-shape changes in the leaf plus one field each. Everything semantic +(what "in force" means, what `AlreadyResolved` refuses, how a transfer derives +its writes) is admission's and is specified here for the Huginn follow-up, not +built here. The leaf gains no cross-field rule (authority map, Cut 6b/6c). + +### Decisions, with the reasons + +#### D1. The sequence is a field the writer sets; admission checks it + +This decision is made once and applied to both kinds: `resolution.sequence` +is per subject, `stewardship.sequence` is per `(instance, repo)`. The leaf +holds no image (`pipeline_key`'s inputs are "the document's own +fields. No clock, store, registry or counter", map Cut 6b authority map), so +the only way the sequence reaches the key is a field. Admission cannot set it +either: `admit_prepared` takes envelopes "prepared by anyone" and the receipt +digests the exact payload bytes (`receipt.rs`, map Cut 8 A9/A11), so a field +rewritten by admission would make Cut 12's import and exact replay +impossible. Precedent already in the leaf: `revision: u32` on `target` and +`cut_spec` is writer-set and keyed `r`, and Huginn's `revision_rule` +(`admission.rs:605-622` at `1cfa81d`) checks it against the image. The +sequence follows the same pattern exactly. + +- **Fields:** `sequence: u32` on `PipelineResolution` after `subject`, and + `sequence: u32` on `PipelineStewardship` after `repo`. +- **Bound:** none in the leaf beyond `u32` and `LOCAL_MAX`; `n4294967295` is + an 11-byte label. The leaf refuses neither `0` nor a gap, as it refuses + neither `revision: 0` nor `revision: 7` without a predecessor; those are + admission's (`revision_rule` refuses 0). Stated in both fields' doc comments + and pinned as a stated limit in the new tests. +- **Admission's rule (follow-up), the same for both:** `sequence == + latest + 1`, where `latest` is the greatest `sequence` among the records of + the same scope (resolutions of that subject; stewardships of that + `(mind, repo)`) in image ∪ batch other than this document; and no record of + that scope is in force other than this document. Refusals below. +- **Derived writes** (a ruling's `Answered` resolution; on a transfer, the + source mind's `Withdrawn` resolution of its stewardship and the receiving + mind's new stewardship) get their `sequence` from `derive()` the same way, + since `derive` already reads `docs` (`admission.rs:417-458` at `30daff8`). + +#### D2. Spelling: `n`, the last part + +Every numeric key part the leaf has carries a one-letter marker (`r2`, `h1`, +`s2`, `:724,:731,:739,:748`), and `parent_cut` reads the marker to tell a +spec's tail from a report's. The sequence takes `n` ("the nth resolution"). +Nothing parses it back: admission reads `sequence` from the document, never +from the key, and the reader `pipeline_id` stays one shape with no `match +kind` (R1, R2 hold; `:579-599` unchanged). The marker buys legibility and the +house convention for one byte per nesting; the alternative, bare digits, was +considered and rejected because a key a human reads in a Studio row should +say what its tail is. + +The sequence is the **last** part so that the subject's resolutions, and only +they, share the prefix `:resolution:..n`. Under R2 no part +carries a dot, so `question.Q1.n` cannot match `question.Q10.n1`, and the +withdrawal of `Q1`'s first resolution keys under `resolution.question.Q1.n1.n1`, +outside the prefix. The same spelling and position serve stewardship: the +history of a repo on a mind is the prefix `:stewardship:.n`, and `key_segment` escapes `.` to `_d`, so an escaped repo can +never end in `.n`. Both prefix properties are pinned by leaf tests (below) and +are what Cut 9's history queries ride on. + +#### D3. Nesting arithmetic changes + +A depth-one local is `..n`. Each nesting prepends +`resolution.` (11 bytes) and appends `.n` (3 bytes for one digit), so a +chain `d` deep composes `14 * (d - 1) + L1` bytes, `L1` the depth-one local. +At `LOCAL_MAX = 64`: `ruling.A.n1` (11 bytes) and `question.Q1.n1` (14 bytes) +both key **four** deep and refuse at five (53 and 56 fit; 67 and 70 do not). +The bound stays the local's alone (`local`, `:673-682`); no guard is added +(Q11 A). The depth test derives its expectation from `LOCAL_MAX` and the two +literal widths, so the numbers are not restated. + +A stewardship local is `.n`, so the escaped repo has +`LOCAL_MAX - 3` bytes for a one-digit sequence (61), not 64. It does not +nest. The resolution of a stewardship is `:resolution:stewardship..n.n`, two sequence parts, the inner one the stewardship's; a +withdrawal of that is one more `resolution.` and `.n1`, and Q19 A stops the +chain there. + +#### D4. What "withdrawn" means, and what `AlreadyResolved` becomes + +Documents are immutable and status is derived (Cut 9: "status is derived at +read time, never stored"), so the only way a resolution stops counting is +another document naming it: **a resolution of the resolution with outcome +`Withdrawn`**, the matrix row Cut 8 first specified ("resolution: `Withdrawn` +only: withdrawing a resolution reopens its subject", map `:2376`) and Soul +found unimplementable under the outcome-invariant key. The sequence makes it +implementable. Huginn's landed fix batch already restored that row +(`acd32f3`, `admission.rs:673` at `30daff8`: `(K::Resolution, O::Withdrawn { +.. }) => true`) and its doc comment says the sequence "is mapped separately"; +this is that map. + +In-force, one derivation for every kind, recursive and well-founded: + +> `in_force(kind, id)` holds when no resolution `R` with `R.subject == (kind, +> id)` exists in image ∪ batch such that `in_force(Resolution, key(R))`. + +`key(R)` is strictly longer than `id`, so the recursion descends a bounded +chain (D3) and terminates. Today's `in_force` (`admission.rs:255-267`) is the +non-recursive special case that counts a withdrawn resolution as if it still +stood; that is the exact defect Soul named. + +The in-force resolution of a subject is then "the latest not withdrawn". The +two readings of that phrase ("the latest, if not withdrawn" and "the latest +among the not-withdrawn") coincide **only if a withdrawal cannot itself be +withdrawn**: otherwise `n1` withdrawn, `n2` admitted, then `n1`'s withdrawal +withdrawn leaves two resolutions standing. **Q19, ruled A:** the chain is capped at admission (a resolution +of a resolution is admissible only when that resolution's own subject is not +a resolution), so reinstating a withdrawn closure is done by resolving the +subject again at `n`, which is what "keep a log, attached to the +subject" asks for. Under A the two readings coincide and "the latest not +withdrawn" is exact. + +`AlreadyResolved { subject }` moves from A10 to A8: + +- **A8, resolution row:** the subject has an in-force resolution other than + this document → `AlreadyResolved { subject }`. "Other than this document" + is content identity (the same `subject`, `sequence`, `outcome`, `rationale`, + `resolved_on`), so an exact replay passes A8 and reaches A9's + `AlreadyAdmitted`, as the ruling row's `own` closure already arranges + (`admission.rs:496-501`). A wrong sequence → `ResolutionOutOfSequence { + subject, expected, actual }` (new `MindRefusal` variant, Huginn's enum, not + the leaf's). +- **A10:** the `Resolution` arm of `refuse_collisions` (`admission.rs:727-729` + at `30daff8`) is deleted. A resolution key is no longer one-per-subject, so a collision on + it means what it means for every other kind: that identity is taken. + `IdentityCollision` names it. The map's A10 sentence "a subject resolves at + most once because its resolution key is outcome-invariant" is retired. +- **The ruling row** keeps `AlreadyResolved` (a ruling answering a question + whose resolution is in force); the derived `Answered` resolution's sequence + is `latest + 1`, so a question withdrawn-and-reopened can be answered again. + +#### D5. Stewardship: keyed by sequence, in force = the latest not withdrawn + +- **Key:** `local(&key_field, &[&key_segment(&value.repo.0), &sequence])` + with `let sequence = format!("n{}", value.sequence);`, after the existing + `org_repo_text("stewardship.repo", …)?`. `assigned_on` does not enter the + key, so the arm validates nothing about it; the field is validated where + every field is, by the `Bounded` impl `value_types!` emits (`:248-253`), + and `validate_pipeline_write_envelope` runs that before the key check. + Q20's first draft put the date in the key and a `validate` call in the arm; + both are gone, and no test may assert a date-shaped refusal from + `pipeline_key`. +- **Date-invariance:** two stewardships of one repo with one sequence and + different `assigned_on` key once, exactly as two resolutions of one subject + with one sequence and different outcomes key once. Pinned in the new test. +- **Escaped repo bound:** the local is `.n`, so the escaped + repo has 61 bytes for a one-digit sequence. `a_composed_local_is_bounded_whole`'s + stewardship boundary moves with it (below). +- **In force (follow-up), mirroring the resolution row exactly:** `sequence + == latest(mind, repo) + 1` → else `StewardshipOutOfSequence { repo, + expected, actual }`; and no in-force stewardship of `(mind, repo)` other + than this document → else `AlreadyStewarded { repo }`. Both are new + `MindRefusal` variants; the first is the sequence rule the coordinator + named, the second is the in-force half `AlreadyResolved` plays for + resolutions, and it is the one Cut 12's transfer-back depends on (a repo + cannot be assigned twice while one assignment stands). Today the row is + the key collision (`:2362`), which a sequenced key no longer provides. + With both, at most one stewardship of a repo is in force on a mind, so + "the latest not withdrawn" is "the one not withdrawn" and `stewardship_of` + (`admission.rs:271-284` at `30daff8`) keeps returning `find_map` of the + in-force one with no tie-break. +- **Same-day limit, retired.** With the date out of the key, a repo + transferred away and back to the same mind on one day is `n1` withdrawn and + `n2` standing; nothing collides. The hand-off key itself still carries + `handed_on` (`:721`), so two hand-offs in one direction for one repo on one + day still share a key; that is the pre-existing hand-off shape, which this + cut does not touch, recorded under Findings. + +#### D6. A hand-off is a transfer; the derivations are symmetric already + +Operator, 2026-09-16: "We might start with, say, Odin as steward over a whole +swarm of infra tools, and spin off a new steward only when the workload +justifies it. Odin wouldn't be getting it back in that case." + +`derive` at `admission.rs:433-452` (`30daff8`) reads only `from_instance == +mind` (withdraw the in-force stewardship) and `to_instance == mind` (assign a +new one, `assigned_on: handed_on`). A transfer back is a hand-off with the +instances swapped; the source side withdraws the transferee's stewardship +(a resolution at that stewardship's next sequence), the receiving side +derives a new stewardship at `latest(mind, repo) + 1`, which no longer +collides with the withdrawn original. **No field names a return, and none is +added.** `hand_off` is unchanged in shape and key; `PipelineHandOff` +(`:374-377`) does not move and `hand_off.v1.schema.json` does not regenerate. + +What changes for Cut 8's per-kind rules: the hand-off row's two derivations +each set a `sequence` (`derive` computes both from `docs`), and the derived +stewardship's key is `:stewardship:.n`. What changes for Cut 12's +spec: the sentence "with the superseding `stewardship` on the source side" +(map `:2707-2709`) is wrong at HEAD and was before this cut — Cut 8 derives a +`Withdrawn` resolution on the source side, not a supersession — and one test +is added, `a_repo_transferred_back_is_stewarded_again` (transfer A→B, then +B→A; A's in-force stewardship is `n2`; `n1` is still readable by key; the +prefix `A:stewardship:.n` yields both). Cut 12 models no return +specially. + +### Deletes first + +| Path | Lines | What dies | +|---|---:|---| +| `epiphany-pipeline/src/lib.rs` | 0 | Nothing. The cut adds one field and two key parts; the resolution and stewardship arms are already one exit each through `local`, and the cut changes what they compose, not how. | +| Huginn `crates/huginn-mind/src/admission.rs:727-729` at `30daff8` (follow-up, not this commit) | 3 | `refuse_collisions`'s `Resolution` arm. Goes before any new rule is added in that commit. (The matrix's Q17 A paragraph already died at `acd32f3`.) | + +An honest zero in the leaf, not an omission. + +### Keeps and moves + +- **Keeps:** thirteen kinds, `PipelineKind`, every type id, the epoch + constant (`:667`), `pipeline_id` (`:579-599`) untouched, `local`, + `key_segment`, `ROOT_LOCAL`, `LOCAL_MAX`, `parent_local`, `parent_cut`, + `PipelineRef`, `ResolutionOutcome` and its `Bounded` impl, the hand-off arm + (`:714-723`) and key, `PipelineHandOff` and `PipelineStewardship.assigned_on` + as fields, the outcome-invariance assertion (`:1102-1109`: the sequence is + the same across outcomes, so the assertion stays true as written), every + `value_types!` and `pipeline_kinds!` entry, + `validate_pipeline_write_envelope`, all twenty-eight test names. +- **Moves:** the resolution sample key (`:941`), the stewardship sample key + (`:951`), and every literal resolution or stewardship key in the tests + (listed per line below). The nested-depth arithmetic in + `a_resolution_of_a_resolution_reads_back` and the stewardship boundary in + `a_composed_local_is_bounded_whole` are key-shape assertions and move with + the shape; both are named below with the exact new expression. +- **Re-anchor, not move:** `tools/eureka-cut6b-mutations.psd1` M2 pins the + exact `let parts = …` line of the resolution arm; that line changes, so M2's + `Old` and `New` are re-anchored (below). Precedent: N7/N8 re-anchored Cut + 6's M12/M13 in the same file. + +### Adds + +| Add | Owner | Live consumer | Protected invariant | Why an existing owner cannot serve | +|---|---|---|---|---| +| `PipelineResolution.sequence: u32` | the leaf (shape and key) | `pipeline_key`'s resolution arm now; `huginn-mind`'s resolution row and `derive` in the follow-up | A subject's resolutions are distinct records with a total order the key carries, so a withdrawn one is kept and the next one is nameable (Q17 B) | The key must come from fields (D1); no existing field distinguishes two resolutions of one subject. | +| `n` as the last part of a resolution local | `pipeline_key` | the same | History is one key prefix per subject (D2) | The composer has no other path to a key. | +| `PipelineStewardship.sequence: u32` | the leaf | `pipeline_key`'s stewardship arm now; `huginn-mind`'s stewardship row and `derive` in the follow-up | A repo's stewardships on a mind are distinct records with a total order the key carries, so a transferred-away repo can come back as a new record (Q18 A, Q20 A) | No existing field distinguishes two assignments of one repo to one mind; the date was rejected (Q20) because same-day records would collide and a date orders nothing the sequence does not. | +| `n` as the last part of a stewardship local | `pipeline_key` | the same | History is one key prefix per `(instance, repo)` | The composer has no other path to a key. | +| Three tests and `tools/eureka-cut6d-mutations.psd1` (M23-M27) | the leaf's suite | the shared harness | Each rule above has a runtime mutation | — | + +No kind, module, dependency, target, format or epoch. No type outside +`value_types!`. + +### Per-file changes, `epiphany-pipeline/src/lib.rs` at `b4b17fc` + +| Line | Change | +|---|---| +| `:16-20` | Module doc gains one sentence: "A resolution's local ends in its per-subject sequence, `n`, and a stewardship's in its per-repo sequence, so a subject's resolutions and a repo's assignments each share a prefix." | +| `:363` | `pub struct PipelineResolution { subject: PipelineRef, sequence: u32, outcome: ResolutionOutcome, rationale: Para, resolved_on: Date }`, with a doc comment: the record of how a subject was closed and by what; `sequence` is per subject and set by the writer, `1` for the first; whether it is the previous plus one, and whether an earlier one still stands, are admission's. | +| `:369-371` | `pub struct PipelineStewardship { instance: Slug, repo: OrgRepo, sequence: u32, assigned_on: Date, note: Line }`; doc: "an assignment of a repo to a mind; `sequence` is per `(instance, repo)`, set by the writer, `1` for the first, so a repo transferred away and back is two records under one prefix. `assigned_on` is a field, not a key part." | +| `:695-701` | Rewrite the arithmetic comment: each nesting prepends `resolution.` and appends `.n`, `14 * (n - 1) + L1` bytes against `LOCAL_MAX`. | +| `:702-707` | Resolution arm: `let sequence = format!("n{}", value.sequence);` before `let parts = …`; the parts line becomes `let parts = std::iter::once(value.subject.kind.name()).chain(subject_local.split('.')).chain(std::iter::once(sequence.as_str())).collect::>();`. The tuple line `(field, subject_root, local(&key_field, &parts)?)` is **byte-identical** (M3 anchors it). | +| `:710-713` | Stewardship arm: `org_repo_text("stewardship.repo", &value.repo.0)?; let sequence = format!("n{}", value.sequence);` then `local(&key_field, &[&key_segment(&value.repo.0), &sequence])`. No validation of `assigned_on` here. | +| `:937-941` | Resolution sample gains `sequence: 1`; expected key `format!("{CAMPAIGN}:resolution:question.Q1.n1")`. | +| `:948-951` | Stewardship sample gains `sequence: 1`; expected key `format!("{INSTANCE}:stewardship:GameCult_-Epiphany.n1")`. | +| `:1251` | `ruling.R8` → `ruling.R8.n1`. | +| `:1270` | `question.Q1` → `question.Q1.n1`. `:1272` (`{CAMPAIGN}:resolution:R8` validates as well-formed grammar) unchanged; `:1274-1284` refused list unchanged. | +| `:1296` | `campaign.self` → `campaign.self.n1`. | +| `:1413`, `:1428`, `:1432` | Stewardship keys gain `.n1`. `:1436-1441` (no org) and `:1443-1452` (60-byte name still refused, now at 73 bytes) unchanged. | +| `:1530` | The nested probe's subject id: `question.Q1` → `question.Q1.n1` (a real resolution key; the key composed is `resolution.question.Q1.n1.n1`). | +| `:1616-1620` | `campaign.self` → `campaign.self.n1` in both the key and the read-back tuple. | +| `:1824-1831` | Doc comment: the new arithmetic and depths (four for both fixtures at 64). | +| `:1840` | Expected `resolution.question.Q1.n1.n1`. | +| `:1841-1843` | Recovery: `let (subject_kind, rest) = local.split_once('.')`; `let (subject_local, _sequence) = rest.rsplit_once('.')`; assert `format!("{root}:{subject_kind}:{subject_local}") == inner`. | +| `:1846` | `let nesting = "resolution.".len() + ".n1".len();` The loop and its two assertions are otherwise unchanged; `deepest` is still derived from `LOCAL_MAX`. | +| `:1886-1912` | The stewardship boundary: `let stewardship_rest = "GameCult_-".len() + ".n1".len();` and `stewarded(64 - stewardship_rest)`, `stewarded(65 - stewardship_rest)`. Comment says the local is `.n`. The hand-off half is untouched. | +| tests, after `:1938` | Three new tests, under Verification. No sample is added, so every `samples().remove(N)` index is unchanged. | + +`tools/eureka-cut6b-mutations.psd1:38-39` (M2): `Old` becomes the new parts +line above; `New` becomes `let parts = subject_local.split('.').chain(std::iter::once(sequence.as_str())).collect::>();` +(the kind dropped, the sequence kept, so the mutant still compiles and still +keys `question.Q1` and `ruling.Q1` to one key, which +`a_resolution_names_its_subjects_kind` kills as before). No other 6b, 6c, 8e +or D entry anchors a line this cut touches (read: M1, M3-M6, S2, S4, S5, N3, +N6-N10, X1, X11 anchor `local`, `key_segment`, `pipeline_id` and the campaign +arm; 6c anchors `ResolutionOutcome`'s impl, `MutationRecord`, `VerdictClaim` +and the estimate literal). + +`schemas/cultnet/`: **exactly two files regenerate.** +`epiphany.pipeline.resolution.v1.schema.json`: `properties` gains +`"sequence": { "format": "uint32", "minimum": 0, "type": "integer" }` (the +shape `revision` already has in `epiphany.pipeline.target.v1.schema.json:101-105`) +and `required` (`:219-224`) gains `"sequence"` after `"subject"`. +`epiphany.pipeline.stewardship.v1.schema.json`: the same property, and +`required` (`:37-42`) gains `"sequence"` after `"repo"`. Derived, never +hand-written: `pipeline_published_schemas_match_derivation` writes the +derivation to a per-run temp directory and names it; copy from there. +`epiphany.pipeline.hand_off.v1.schema.json` is **byte-identical**: no field +moves on `PipelineHandOff`, and keys are not in schemas. The other ten and +`index.json` (no hashes) are byte-identical. **No epoch moves.** Honesty +about "additive": the epoch constant's doc comment (`:660-666`) says additive +means "a new named field with a serde default", and neither `sequence` has +one — a default of `0` would key a document to `n0`, which admission refuses, +so the default would be a lie. +This lands without a bump on the same ground Cut 6c's four required fields +did: no store is written at this epoch and no reader is pinned to the file +(Huginn pins the crate rev, not the schema). Recorded under Findings as the +rule the campaign will owe an epoch to once a mind exists. + +`git diff --stat b4b17fc -- schemas/cultnet/` names exactly the two files. + +### Authority map + +- **Owner:** `pipeline_key` (`:687`), through `local`, for both keys. Unchanged + owner; changed inputs. +- **Inputs:** the document's own fields, now including `resolution.sequence` + and `stewardship.sequence`. `stewardship.assigned_on` is **not** a key + input. No clock, store, registry or counter. A sequence is a field the + writer sets, never derived here. +- **Outputs:** one `String` or a `PipelineRefusal` naming the field + (`resolution.key`, `stewardship.key`, `stewardship.repo`). +- **Derived state:** the key, never stored. The history of a subject is a + key prefix, not a list anything maintains. In-force status, the sequence + rule, the withdrawal chain and the single-in-force-stewardship rule are + admission's, computed at rule time, never stored (Cut 8/9), and are **not + in this crate**. +- **Forbidden writers:** the leaf may not check either `sequence` against + anything (not `> 0`, not a gap), may not refuse a nested resolution, may + not decide which stewardship is in force, may not put `assigned_on` (or any + date) into the stewardship key, and may not parse a sequence back out of a + key. `pipeline_id` gains no arm. Nothing may put a sequence anywhere but + last. Admission (Huginn) may not set or rewrite `sequence` on a submitted + document; it computes it only for the writes it derives. +- **Shared paths:** the four grammar paths of 6b, unchanged: + `pipeline_key`, `pipeline_id`, `PipelineRef::validate`, + `validate_pipeline_write_envelope`. Huginn's `admit` and `admit_prepared` + converge before A1 and see the new keys through A3 with no new code. +- **Deletion line:** none in the leaf. In the follow-up: `refuse_collisions`'s + `Resolution` arm and the matrix's Q17 A default go before any new rule is + added. + +### Verification + +**Builds.** `CARGO_TARGET_DIR=C:\Users\Meta\.cargo-target-codex`, path-list +baseline before and after. `cargo check -p epiphany-pipeline --lib --tests`; +`cargo test -p epiphany-pipeline --lib` (28 existing + 3 = **31 tests**, 0 +warnings); `cargo check -p epiphany-core --lib --tests` (untouched; `cargo +tree --workspace -i epiphany-pipeline -e normal,build,dev` lists the leaf +alone, map correction 41, so no recompile is legitimate). Host = target = +workstation; the leaf has no platform code. + +Baseline probed this pass in a detached worktree at `b4b17fc` under the +scratchpad: 28 passed, 0 failed, 15 s wall with warm rlibs; worktree removed. + +**Tests.** Existing twenty-eight unchanged in name; assertions unchanged +except the key-shape moves named per line above. + +| Test | Rule it pins | +|---|---| +| `a_subject_keeps_every_resolution_it_had` | Q17 B in the key: two resolutions of `question:Q1` with `sequence` 1 and 2 key to `…question.Q1.n1` and `…n2`, `assert_ne`; each reads back through `pipeline_id` with the local's last part `n`; the same document with `sequence` 1 under `Answered` and `Withdrawn` keys once (outcome-invariance holds per sequence). Stated limits: `sequence: 0` composes `n0` and `u32::MAX` composes `n4294967295` (21-byte local under `ruling.A`); the leaf refuses neither, admission does. | +| `a_subjects_resolutions_share_a_prefix_no_other_key_has` | D2: over the keys of `Q1` n1..n3, `Q10` n1, `ruling:Q1` n1, the campaign-root resolution n1, and the withdrawal of `Q1.n1` (`resolution.question.Q1.n1.n1`), `starts_with("{CAMPAIGN}:resolution:question.Q1.n")` selects exactly the three. Pinned as a string property, which is what a prefix query is. | +| `a_repo_keeps_every_stewardship_it_had` | Q18 A + Q20 A in the key: one repo with `sequence` 1 and 2 keys to `…GameCult_-Epiphany.n1` and `…n2`, `assert_ne`; each reads back through `pipeline_id` with the last part `n`; the same document with `sequence` 1 on `2026-09-15` and on `2026-09-16` keys **once** (date-invariance: `assigned_on` is a field, not a key part); over the keys of `GameCult/Epiphany` n1..n2, `GameCult/Epiphany_thing` n1 and `GameCult/Huginn` n1, `starts_with("{INSTANCE}:stewardship:GameCult_-Epiphany.n")` selects exactly the two. Stated limit: `sequence: 0` composes `n0`; the leaf refuses nothing on the value. | + +**Negative greps over `epiphany-pipeline/src`:** `rg -n "sequence"` shows the +two fields, the two arms, docs and tests and **no** `if`, `match`, `==`, `>` +or `+ 1` on either outside `tests`; `rg -n "assigned_on" -- src/lib.rs` +shows the field, the samples, the derived stewardship nowhere (the leaf +derives nothing) and **no** occurrence inside `pipeline_key`; `rg -n "match kind|declared_kind|KeyParts"` empty; +`rg -n "fn pipeline_id" -A 20` shows no change (`git diff b4b17fc -- +epiphany-pipeline/src/lib.rs` has no hunk in `:572-599`); `git diff --stat +b4b17fc -- schemas/cultnet/` names exactly two files; `rg -n '"sequence"' +schemas/cultnet/epiphany.pipeline.{resolution,stewardship}.v1.schema.json` +matches twice in each (property and required) and `rg -n '"sequence"' +schemas/cultnet/epiphany.pipeline.hand_off.v1.schema.json` is empty; +`rg -n "lease|expiry|returns_to|intent"` empty. + +**Suites that must still kill every entry:** `eureka-cut6b-mutations.psd1` +(seventeen entries, M2 re-anchored), `eureka-cut6c-mutations.psd1` (M16-M22), +`eureka-cut8-epiphany-mutations.psd1` (E1-E3, D1-D2), `eureka-cut5-`, `eureka-cut6-` +if still run per the harness header, and the new `eureka-cut6d-mutations.psd1`. +All through `tools/eureka-mutations.ps1` with M0 green. + +#### Mutations, `tools/eureka-cut6d-mutations.psd1` + +Each restores the old permissiveness of the shape it pins. Anchors are +content Hands lands, matched exactly once. + +| # | Rule | Mutation, exactly | Killed by | +|---|---|---|---| +| M23 | A subject's resolutions are distinct records | Resolution arm: `.chain(std::iter::once(sequence.as_str()))` → removed (the old one-per-subject key) | `a_subject_keeps_every_resolution_it_had` (n1 and n2 key equal); collaterally every moved sample key | +| M24 | A repo's stewardships on a mind are distinct records | Stewardship arm: `&[&key_segment(&value.repo.0), &sequence]` → `&[&key_segment(&value.repo.0)]` (the old one-per-repo key) | `a_repo_keeps_every_stewardship_it_had` (n1 and n2 key equal); collaterally every moved stewardship key | +| M25 | The stewardship sequence is the last part | Stewardship arm: `&[&key_segment(&value.repo.0), &sequence]` → `&[&sequence, &key_segment(&value.repo.0)]` | the same test's prefix assertion (`n1.GameCult_-Epiphany` leaves the prefix) | +| M26 | The sequence is the last part | Resolution arm: sequence chained **before** the subject local (`once(kind).chain(once(sequence)).chain(local parts)`) | `a_subjects_resolutions_share_a_prefix_no_other_key_has` (`question.n1.Q1` leaves the prefix) | +| M27 | The nested subject is recovered whole, sequence stripped | In the resolution arm, `subject_local.split('.')` → `subject_local.rsplit_once('.').map_or(subject_local, \|(head, _)\| head).split('.')` (drop the subject's own sequence when nesting) | `a_resolution_of_a_resolution_reads_back` (the recovered subject is `question.Q1`, not the inner key) and `every_key_has_exactly_three_segments`'s nested probe | + +Stated limits: the `n` marker is pinned only by the sample-key equalities in +`keys_are_derived_and_mismatch_refuses` and the two prefix literals, a +fixture pin like 6c's M21; a marker change is a spelling change and no +mutation restores a permissiveness by it. The `u32` type has no runtime +mutation. Date-invariance has no mutation of its own: putting `assigned_on` +into the key is not a permissiveness the old code had, and M24 already pins +that the second part is the sequence and nothing else (a mutant that appends +the date as a third part dies on M24's test's `assert_eq` of the local's +part count, which Hands asserts as `2`). + +**Operator checks before landing:** none. Q19 and Q20 are ruled. + +### The Cut 8 follow-up commit this cut implies + +Huginn `eureka/memory-organ`, one commit after **`30daff8`** (the fix batch +landed as `acd32f3`, which restored the `resolution → Withdrawn` matrix row +and flipped the nested assertions, and `30daff8`, entries H21-H39; anchors +below are `file:line` at `30daff8`). Pinned rev moves from `a65c6420` to this +cut's landing commit, which also carries `b4b17fc`'s derives: the +`#[serde(remote)]` mirror of `PipelineRefusal` in `refusal.rs` can come out +then, but that is Cut 10's stated work and not this commit's. + +| File | Change | +|---|---| +| `crates/huginn-mind/Cargo.toml:15` | `rev` → the 6d landing commit. | +| `Cargo.lock` | the `epiphany-pipeline` source line. | +| `src/refusal.rs` | `ResolutionOutOfSequence { subject: String, expected: u32, actual: u32 }`, `StewardshipOutOfSequence { repo: String, expected: u32, actual: u32 }`, `AlreadyStewarded { repo: String }`. `AlreadyResolved { subject }` kept. The two `OutOfSequence` variants are the sequence rule for each kind; `AlreadyResolved`/`AlreadyStewarded` are the in-force half. | +| `src/admission.rs:23-26` | Module doc: "in force" is recursive: no resolution names the document that is itself in force. | +| `:248-267` | `resolutions()` yields `(key, &PipelineResolution)`; `in_force_unless` becomes the recursion of D4, with `own` taking the whole `&PipelineResolution` (content identity) rather than the outcome. Add `latest_resolution(subject) -> u32` and `latest_stewardship(mind, repo) -> u32` over image ∪ batch (0 when none). | +| `:271-284` | `stewardship_of` unchanged in shape; its result is now unique by the `AlreadyStewarded` rule. | +| `:417-458` | `derive`: the ruling's `Answered` resolution and the source side's `Withdrawn` resolution each get `sequence: latest_resolution(subject) + 1`; the receiving side's stewardship gets `sequence: latest_stewardship(mind, repo) + 1` with `assigned_on: handed_on` as before. Keys follow from the leaf, no other code. | +| `:591`, new `resolution_rule` steps | Before the matrix: `sequence` rule (`ResolutionOutOfSequence`), then in-force (`AlreadyResolved`). After the matrix, Q19 A: a subject of kind `Resolution` whose own subject is a `Resolution` → `IncompatibleResolution`. | +| `:598` | Stewardship row, mirroring the resolution row: `sequence == latest_stewardship + 1` else `StewardshipOutOfSequence`; then no in-force stewardship of `(mind, repo)` other than this document, else `AlreadyStewarded`. | +| `:654-680` | `matrix`: already restored at `acd32f3`; unchanged. | +| `:724-733` | `refuse_collisions`: delete the `Resolution` arm (`:727-729`); one refusal, `IdentityCollision`. | +| `src/fixtures.rs:100-107`, `:263-270` | `stewardship(instance, repo)` sets `sequence: 1`; add `stewardship_n(instance, repo, sequence)`; `resolution(subject, outcome)` sets `sequence: 1`; add `resolution_n(subject, sequence, outcome)`. | +| Tests moving | `two_minds_in_one_state_root_stay_separate:800`, `the_resolution_matrix_is_admissions:1024,:1042`, `a_ruling_answering_a_question…:1132,:1135`, `a_hand_off_derives_this_minds_side_only:1427,:1448`, `id_of:1474-1477` (appends `.n1`): every `…:stewardship:GameCult_-Epiphany` gains `.n1` and every `…:resolution:question.Q1` gains `.n1`. Fixtures in `:52-116` of the landed batch (H21-H39's tests) that spell either literal move the same way; grep `stewardship:GameCult\|resolution\", \"question.Q1\"` after `30daff8` and re-anchor. | +| `subject_resolves_at_most_once:977` | Renamed `a_subject_with_a_resolution_in_force_refuses_another`: second resolution of `Q1` at `sequence: 2` → `AlreadyResolved`; at `sequence: 1` → `ResolutionOutOfSequence { expected: 2, actual: 1 }`; the answering ruling → `AlreadyResolved` (unchanged). | +| new `a_withdrawn_resolution_reopens_its_subject_and_stays_readable` | `Q1` resolved n1; withdrawal of `…question.Q1.n1` (`Withdrawn`) lands; `Q1` resolved n2 lands; `get` of n1 still returns it; a withdrawal of the withdrawal → `IncompatibleResolution` (Q19 A); a third resolution at n3 while n2 stands → `AlreadyResolved`; a second answering ruling after the withdrawal derives `…question.Q1.n2`. | +| new `a_repo_is_stewarded_once_at_a_time_and_again_after_a_transfer` | second stewardship of `REPO` at `sequence: 2` while n1 stands → `AlreadyStewarded`; at `sequence: 1` → `StewardshipOutOfSequence { expected: 2, actual: 1 }`; after a hand-off away, a hand-off back (swapped instances) lands and derives `:stewardship:GameCult_-Epiphany.n2` with `assigned_on` the second `handed_on`; `envelope` of n1 still present; the source mind's derived withdrawal is `…:resolution:stewardship.GameCult_-Epiphany.n1.n1`. | +| `tools/eureka-cut8-mutations.psd1` | H14's expectation stands (`IdentityCollision` on a ruling). Any H21-H39 entry anchoring `stewardship_of`, `derive` or `refuse_collisions` lines re-anchored. New: H40 resolution sequence rule → `true`; H41 in-force non-recursive (a withdrawn resolution still counts) → the reopen test; H42 stewardship sequence rule → `true` → the stewardship test's `expected: 2, actual: 1` case; H43 `AlreadyStewarded` dropped → the same test; H44 Q19 cap dropped → the reopen test's withdrawal-of-withdrawal; H45 `derive` uses `sequence: 1` for every derived write → the reopen test's second ruling and the transfer test's `n2`. | +| `README.md`, `AGENTS.md` | unchanged; Cut 12's map section corrected by Self ("superseding" → "withdrawal"). | + +Estimate: +about 260 lines in Huginn (rules 50, refusals 9, fixtures 16, +tests 150, entries 48), −about 3 (the A10 arm). One build, `cargo test -p +huginn-mind --lib`, plus the cut-8 suite through the harness with `-Repo`. + +### Subtraction estimate + +Epiphany, this cut: source outside tests +about 12 (two fields, two +`format!("n{}")` lines, two chained parts, four doc lines), −0; tests +about +100 (three tests) and about 25 lines of moved literals; two schema files ++5/−0 each; one 6b entry re-anchored. Kinds, dependencies, targets, formats, +epoch, `index.json`: zero. Liability retired: the one key that could not +name the second closure of a subject, and the one key that could not name +the second stewardship of a repo — both of which Soul found as medium +defects against Cut 8, and both of which would have cost a stored-document +re-key the moment Yggdrasil's mind held a resolution. Also retired before it +ever shipped: the date-keyed stewardship's same-day collision. + +Huginn, the follow-up: +260/−3 as above. Net across both: about +380, all +tests and rules for a capability the operator asked for by name. + +### Build budget + +Epiphany: `epiphany-pipeline` lib + tests only; `epiphany-core` check, no +rebuild expected. Debug, workstation host = target, no features, no +codegen. Footprint delta: within the noise of a warm leaf rebuild (the probe +this pass rebuilt the leaf's test binary in 15 s); expected +0 to +40 paths. +Huginn follow-up: `huginn-mind` lib + tests; the leaf rlib rebuilds at the +new rev (+about 30 paths). Retention: the shared target dir is the operator's; +nothing deleted. + +### Operator questions + +None open. Two were raised by this spec and ruled the same day: + +- **Q19. Can a withdrawal be withdrawn? Ruled A, 2026-09-16.** A resolution + is admissible with `Withdrawn` only when its subject's own subject is not a + resolution (chain depth two at admission). To reinstate a withdrawn + closure, resolve the subject again at `n`. History stays append-only, + "the latest not withdrawn" has one reading, and the in-force recursion + never re-raises an earlier record over a later one. The rejected B (any + depth; in-force is "the latest among the not-withdrawn") could express a + standing record that is not in force. H44's mutant is exactly B. +- **Q20. Stewardship keyed by date or by sequence? Ruled A (sequence), + 2026-09-16.** `:stewardship:.n`, writer-set + `sequence: u32` checked by admission as previous plus one, exactly as + resolutions; `assigned_on` stays a field. The first draft's date key was + rejected for the same-day collision and because a date orders nothing the + sequence does not. + +The Q18 correction (transfer, not lease) raised nothing new: the derivations +were already symmetric in the source. + +### Findings not assignable to this cut + +- **"Additive" has two definitions in the repo.** The epoch constant's doc + (`lib.rs:660-666`) says a new field is additive with a serde default; the + campaign's practice (6c's four required fields, this cut's `sequence`) is + "additive while no store is written at the epoch". Both are right today and + will disagree the day Yggdrasil holds a mind. Owner: the map's ruling 8 + text and the constant's doc; not this cut's. +- **Cut 12's spec says "the superseding `stewardship` on the source side"** + (map `:2707-2709`); Cut 8 landed a `Withdrawn` resolution there + (`admission.rs:437-442`). Stale before this cut; Self's to correct. +- **Cut 8's map text at A10** ("a subject resolves at most once because its + resolution key is outcome-invariant", `:2344`) and the stewardship row + ("the key collides, A10", `:2362`) both describe the shape this cut retires. + Self's, at the follow-up's landing. +- **Cut 9's `open_items_and_rulings_in_force_follow_resolutions`** needs the + recursive derivation and a reopened-subject fixture, or it will pin the + non-recursive defect as correct. Cut 9's spec should say so before Hands + reads it. +- **Same-day granularity survives only in the hand-off key** (`:721`, + `:hand_off:..`): two hand-offs in one direction + for one repo on one day share a key. The stewardship half of this limit is + retired by Q20 A. If the Odin-swarm workflow ever transfers a repo twice + in a day in one direction, the hand-off key wants the same treatment; not + asked, not this cut's, recorded. +- **`Date` derives `Ord` on its string** (`:212`), correct for `YYYY-MM-DD` + only because `Bounded::validate` enforces the shape. Nothing orders dates + after Q20; the sequence orders. + +### Pinned HEADs + +- Epiphany `codex/eureka-pipeline-state` at `542dc184` (map only since + `87d3420c`: `993600fd`, `76167f0a`, `542dc184`); `epiphany-pipeline/src/lib.rs`, + `schemas/cultnet/` and `tools/` byte-identical to `b4b17fc`. Tree clean. +- Huginn `eureka/memory-organ` at `30daff8`, tree clean. The fix batch + landed as `acd32f3` (matrix row `resolution → Withdrawn` restored, nested + assertions flipped) and `30daff8` (nineteen rules pinned, entries + H21-H39). Follow-up anchors above are against `30daff8`. Pin in + `crates/huginn-mind/Cargo.toml:15` is still `a65c6420`. +- CultLib untouched and unread this pass (Soul is in that tree). +- Probe artifacts: a detached worktree `scratchpad/cut6d-wt` at `b4b17fc` ran + `cargo test -p epiphany-pipeline --lib` once (28 passed) under the shared + target dir and was removed; Huginn's sources at `1cfa81d` were copied to + `scratchpad/huginn-*-1cfa81d.rs` by `git show` and read there while the + batch was in flight, then the landed `30daff8` was grepped for every site + the follow-up anchors. + ## Cut 7. Retire Huginn's TypeScript body - **Repo/branch:** Huginn `main` at `91b7fcf`, on a new branch