Soul held the deletion line and the no-consumer claim, and found the fixture's
owner recorded in a third repo that now disagrees, three more Huginn-naming
fields, and AGENTS describing unbuilt cuts in the present tense. Two errors
were the map's own: a follow-up number used twice, and a negative grep that
ripgrep cannot parse.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's second attack on the mutation harness found three ways a target
stayed mutated. A multi-target entry wrote its targets outside the try,
so a write that threw on the second target skipped the finally and left
the first mutated. M0's write-through had no finally at all. And a hard
kill mid-entry, which is what a tool timeout is, runs no finally, so the
target stayed mutated with nothing recording what it had been.
Every write now sits inside a try whose finally restores every target the
step touched, M0's included, each proven by hash; a target that cannot be
restored is printed with RESTORE FAILED and its original SHA-256 before
the run throws. Before any target is written its original bytes go to a
sidecar beside it, deleted only after a verified restore, and a run that
finds a sidecar at startup restores from it, verifies, removes it and
says a previous run died mid-mutation. Commands run through a Process
with a -TimeoutSeconds limit (1800 by default) and are killed with their
tree by the harness inside its own finally, so a hung cargo ends inside
the entry's restore rather than under the operator's tool.
Reproduced against a fake repo: A1 (M0 target write-locked) and A2 (the
second of two targets locked, in both hashtable orders) leave every
target byte-identical with no sidecar; A3 (tree killed mid-entry) leaves
the mutant and the sidecar, and the rerun reports the repair and kills
the entry; a 3-second timeout against a 12-second runner reports TIMED
OUT (no verdict) and restores.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The operator reached npm signup today, so yesterday's blocked subnet is no
longer a Body fact. The ruling stands on a different reason: registry
publication is a later pass. Recorded in the operator's words beside the
earlier statement so the map does not carry a claim its author withdrew.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every grammar pin held under non-revert mutations. What survived is the
harness under failure: writes outside the restoring finally, no guard on
the control's own write-through, nothing that survives a killed process,
and one reader fixture for trailing dots. A third batch takes those and
Cut 6c follows in the same tree.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Huginn's TypeScript body is gone and an empty Rust workspace stands in its
place; Eve's fixture says it is Eve's. The spec's file and line arithmetic
was off and is corrected in the landing note rather than the table, and the
conformance fixture's third Huginn-naming field is flagged for Soul.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The operator's ruling carries a Body fact the maps had not recorded: npmjs
is unreachable from this network, so no GameCult package can be published
there. Cut 7 needed no publish. The QUIC map's Q5 assumed one; that is
raised there separately.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Operator correction: the CultLib QUIC cut may run without Huginn, but the
StreamPixels migration must run with it, because a portfolio piece should
demonstrate the full pipeline on typed state. The campaign no longer pauses
after Cut 6c; Cuts 7-16 follow, alongside QUIC where the repos allow it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The harness now restores from the original bytes on every path and checks
its own round trip before it writes; dotted roots and the reader's root-kind
check have tests and mutations; the local bound is one named constant. The
Cut 6 entry notes name the entries that actually pin their rules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's second pass on the key grammar found four gaps, none in the
grammar itself: nothing pinned them.
No test keyed or read back a root with a dot in it, so both the
composer's root check and the reader's could have been a label check
(Soul's N9 and N10 survived). `dotted_roots_key_and_read_back` keys a
campaign `game.cult` and an instance `ygg.drasil`, reads each back by
`pipeline_id` recovering the root exactly, keys a target under the
dotted campaign and reads it back, and keys a resolution of the dotted
campaign as `game.cult:resolution:campaign.self`.
The reader's kind check was unpinned for the root kinds: excusing the
Instance kind from it survived (N6). `keys_read_back_as_ids_of_their_kind`
is extended, the first change to its body since Cut 6: an instance key
read as an instance succeeds, an instance key read as a campaign is
refused, and a campaign key read as an instance is refused.
The depth test restated the composer's bound as a literal, so moving
the bound and the test's 64s together survived (N4). `LOCAL_MAX` now
names the bound; `local` and the depth test both read it, and that
mutation is no longer a single-site edit. The Cut 6b entries that
anchored the literal, M4 and S5, anchor the name.
Cut 6's M5, M12 and M13 were deleted in d03a32df with notes pointing at
Cut 6b's M6 and S2, which pin neither the tail part nor the escape.
Soul's N3, N7 and N8 are those rules re-anchored: N3 skips the label
check on the last part of a local, N7 makes `key_segment` the identity,
N8 escapes `/` to a bare `_` with `_` unescaped. N6, N9 and N10 join
them in tools/eureka-cut6b-mutations.psd1, and the Cut 6 notes for M5,
M12, M13 and M15 now name the entries that pin them.
Every suite runs green through the harness: Cut 6b sixteen entries
killed (nine prior verdicts unchanged, N3, N6, N7, N8, N9, N10 killed),
Cut 6 ten killed, Cut 5 seven killed, M0 green in each. 21 tests in
epiphany-pipeline, no warnings. schemas/cultnet is unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's attack on tools/eureka-mutations.ps1 found two faults. M0 proved
the I/O path by writing the target and comparing hashes, so a broken
write path corrupted the target in the act of checking it, and the
harness could not put it back because it had only ever read text. And
"exactly once" counted non-overlapping matches, so an anchor that
overlaps itself, `}\n}\n` in `}\n}\n}\n`, counted as one site and the
splice at the first would have changed a region the entry does not
describe.
The harness now reads every target as bytes and keeps those bytes as
the restore source. M0 decodes, re-encodes through the one path every
write uses, and compares the result to the original bytes before
anything is written; a difference names the target and the first
differing offset, writes nothing, and runs no entry. Only then is the
target rewritten through the write path and hashed, with the original
bytes written back before the throw if the write lands other bytes.
After every entry the original bytes are written back and the file is
hashed against them; the offset reverse edit is gone, since the bytes
it reconstructed were in hand all along. Anchor sites are counted by an
IndexOf loop advancing one character per hit, so an overlapping anchor
is two and refused.
The usage comment records that -Test is split on whitespace with no
quoting, so a path with a space cannot be expressed.
Soul's attack script, re-targeted at the new Write-Text line: the
Set-Content variant, the BOM-encoder variant and the BOM-bearing second
target all leave every target byte-identical, the first two stopping at
M0 with no entry run; the overlapping anchor throws "matches 2 times".
The Cut 6b suite runs green through the fixed harness: M0 green, nine
entries killed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The four unpinned checks are pinned and the mutation harness is one script
with a built-in control. Soul then attacked the harness itself and found
the control could rewrite a target it cannot restore, the anchor guard
counted non-overlapping matches, dotted roots and the reader's root-kind
check were unpinned, and Self's own instruction to delete rather than
re-anchor five Cut 6 entries had cost the suite coverage it still claimed.
All of it is in a second fix batch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Cut 5, 6 and 6b suites each carried the same scaffold around their
entries, and Cut 6c would have made a fourth. tools/eureka-mutations.ps1
is now the single harness: it takes an entries file, the target files
and the cargo command; reads and writes byte-exact UTF-8; requires every
anchor to match exactly once; applies each edit by offset and reverses
it by the same offset, verified by SHA-256; keeps a compile-must-fail
entry shape; and runs M0 before any entry, rewriting every target
through its own I/O path and running every command bare, stopping with
"harness broken" if anything moves or fails.
The three suites are now entries only, as .psd1 data files. Cut 6's M5,
M12, M13, M14 and M15 matched nothing after Cut 6b rewrote the grammar;
each is deleted with a note naming the Cut 6b entry that pins the rule
now. Cut 6b's file carries Soul's S2, S4 and S5, the mutations the
previous commit's tests kill.
All three suites run green through the harness: every entry killed,
M0 green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The nesting depth was described as "five" when the bound is the 64-byte
local: a chain n deep over a depth-one local of L bytes composes
11 * (n - 1) + L bytes, so `ruling.A` keys six deep and `question.Q1`
five. The arm comment and the test say the arithmetic, and the test
computes the deepest chain from it for both subjects and asserts the
first refusal.
The escape's `_` half was pinned only through an OrgRepo; a receiver
`a_db` and a receiver `a.b` now key differently, the former through
`a__db`. The composer's root check was pinned by `a:b` alone; a trailing
dot, a leading dot, an empty part and a 65-byte root are each refused
by `pipeline_key` without `validate()`. The whole-local bound was
unpinned on both sides; a 64-byte local keys and a 65-byte one refuses,
for a plain label and for an escaped repo.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut 6b landed in three commits and Soul's whole-key probe found no collision
across 212,450 adversarial keys. Three checks were unpinned (the underscore
half of the escape, the composer's root check, the 64-byte boundary) and are
in a fix batch with Soul's own surviving mutants as their killers. Two map
claims were wrong and are corrected: nesting depth follows from the subject's
local rather than being five, and a validated date passes raw because it is
already a label.
The Cut 6c spec is folded in as reissued against the landed grammar, with the
supersession fixture only the new grammar makes nameable and the mutation
entries pointed at the shared harness the fix batch is factoring out.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six mutations, each restoring one of the old permissivenesses the grammar
made impossible, and M0, a no-op control that rewrites the file through
the harness's own I/O path and requires the suite to stay green before
any verdict is trusted. All file I/O is byte-exact UTF-8 without BOM,
since the file carries an `é` literal that the 5.1 default round-trip
corrupts into a fake kill. Every anchor must match exactly once, multi-line
anchors are converted to the file's own line endings, restoration is the
reverse edit checked against the original's hash, and a mutation whose
tree does not build is an error rather than a survival.
Result on this tree: M0 green; M1-M6 each killed by its named test.
This commit builds; it changes no Rust source.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every key is now `<root>:<kind>:<local>`, three segments with no kind
excused. The root is a `Slug`, the kind is its literal name, and the
local is `Label`s joined by `.`, so no part carries the separator and a
reader recovers the parts by splitting. Roots take the constant local
`self`; a resolution is keyed inside its subject's root with the subject's
kind and local as its own local, so a resolution of a resolution is an
ordinary id bounded by the 64-byte local and needs no guard.
- `local` is the one composer: every part through `label_text`, the join
bounded whole, once. `pipeline_key` has thirteen arms and one exit.
- `pipeline_id` is the grammar read backwards, with no per-kind branch.
- `repo_segment` becomes `key_segment`: the same injective escape, now for
any `Slug` or `OrgRepo` entering a local. `hand_off.to_instance`, the
only raw `Slug` that reached a local, goes through it.
- `parent_cut` indexes the split parts instead of splitting from the right.
Every key moves, and nothing outside this file reads one. No schema file,
kind, type, field, dependency or epoch changes.
Tests: six new (`every_key_has_exactly_three_segments`,
`roots_of_different_kinds_do_not_share_a_key`,
`a_resolution_names_its_subjects_kind`,
`a_resolution_of_a_resolution_reads_back`,
`a_composed_local_is_bounded_whole`,
`no_local_part_carries_the_separator`); thirteen expected keys move in
the samples; `composed_keys_cannot_collide`'s composer probe moves into the
separator test and its dotted receiver now escapes.
Spec deviations, in the report: `local` takes `&[&str]` rather than a
const array because the resolution and finding arms carry a parent's
parts; the whole-local bound refuses `InvalidFormat` to match the spec's
own test and the reader's refusal of the same string; `key_segment` is a
pure escape and each caller validates its source type by its own field.
This commit builds and its tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three fix passes each made one key composition site injective and left the
site above it open. Soul's third pass found campaign and instance sharing a
root namespace, a resolution key discarding its subject's kind, and nested
resolutions composing an unreadable key. That is a missing owner, not three
bugs, so the grammar is redesigned as Cut 6b: root:kind:local, three segments
always, no local part a slug, roots keyed with the constant self. The
exceptions are deleted, not guarded.
The Ghostlight shapes become Cut 6c and land after 6b, once Imagination
reissues them against the settled grammar. The campaign pauses after 6c; the
QUIC realtime plane in CultLib goes first because StreamPixels is the
portfolio piece, and Huginn resumes after it ships.
Also recorded: the second Cut 6 fix batch and the findings it closed; the
mutation-harness audit, which found the corrupting round-trip in no committed
suite but no no-op control in any suite either; corrections 19-24 (the dot
escape, the unstated resolution key, the std::process grep, the ruled
target-dir deviation, the second byte-identical claim, the README's foreign
consumer promise); and Q10-Q12 with recommendations.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The key space had four shapes because three of thirteen kinds returned a
key the composer never saw, and the reader undid them with a per-kind
match and a kind-guessing helper. Every confirmed defect lived in one of
those exceptions. This commit removes them before anything replaces them,
so no commit holds both the exceptions and the new composer:
- `declared_kind`, the inference that hardcoded `Campaign` for a bare slug
- the `match kind` block in `pipeline_id` (root arm, resolution arm and
its recursion)
- `KeyParts`, its constructors, `key`, and the "only the head may carry
the separator" doctrine comment
- the three early `return`s in `pipeline_key` (instance, campaign,
resolution)
This commit does not build: `pipeline_key` references `KeyParts` and the
tests reference the deleted shapes. The next commit lands the grammar.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul G3: the comment asserted only the half that improved -- a passing
run leaves nothing -- and was silent about the failing run, which is the
only case that creates a directory at all and now leaks one permanently,
one per failing run where the shared directory was overwritten however
often it failed. The trade is still right; the comment now names its
cost and says who cleans up.
Comment only. No behaviour, so no mutation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul G2: F3's defect survived for `Resolution`, unpinned in both
directions. A resolution's key is `resolution:<subject id>`, and the
reader demanded `<campaign>:resolution:<local>`. The two are disjoint, so
`pipeline_id` refused every id a resolution actually has and accepted one
no resolution can ever carry: a document that can be written but never
referenced, and a reference that names nothing. `PipelineRef` takes any
kind, so both are reachable -- a question's `raised_in`, a follow-up's
`source`, a resolution's own `subject`.
The reader now accepts what `pipeline_key` derives: a `resolution:`
prefix, then the subject's id parsed as an id of the kind that id names
itself as. `declared_kind` reads that kind from the id's own kind
segment, or calls a bare slug a root. A key and an id are the same
string, and now no kind is excused from that.
`keys_read_back_as_ids_of_their_kind` loses its resolution special case:
the loop covers every kind with no exception, which is the pin the last
batch's doc-comment curiosity should have been.
Mutations, each run against the committed suite and reversed after:
- Soul's survivor, `| PipelineKind::Resolution` on the root case: now
fails `a_resolution_is_named_by_the_key_it_has` and
`keys_read_back_as_ids_of_their_kind`. It compiles with an unreachable
pattern warning, because the resolution case is an arm of the same
match.
- the resolution case falls through to the campaign tail (the landed
behaviour): the same two tests fail.
- a generous reader that takes the prefix when it is there and falls
through when it is not: the negative direction fails on its own,
`eureka-state:resolution:R8` still being accepted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul G1: the hand-off key was not injective. Its local was
`{to_instance}.{repo}.{date}`, and `.` was admitted on both sides of the
first boundary: `to_instance` is a `Slug`, `repo_segment` passed a dot
straight through, and `OrgRepo` imposes no byte class at all. So
`thought-cage.GameCult` + `Epiphany/thing` and `thought-cage` +
`GameCult.Epiphany/thing` both validated and both keyed to
`yggdrasil:hand_off:thought-cage.GameCult.Epiphany_-thing.2026-09-15`.
One of the two documents was lost. This is the invariant S1 named and the
sentence `label_text` uses to keep dots out of labels.
Neither side could be forbidden: a dotted repo name is ordinary and a
dotted instance slug is what `Slug` is for. So the composition is fixed
rather than the one key. Every non-root, non-resolution key now composes
through `KeyParts::key`: the root is validated, the head may carry the
separator, and every tail part is a `Label`. A kind's tail length is
fixed by the arm that builds it, so a reader recovers the parts by
splitting from the right once per tail part. `repo_segment` escapes `.`
as `_d` alongside `__` and `_-`, which keeps the repo a legal tail part
and keeps the escape injective.
Stewardship and hand-off stop composing their own keys and lose their
early returns: their root is an instance rather than a campaign, and that
was the whole difference. The finding key's separate `label_text` call
goes with them -- the tail rule is that check.
`composed_keys_cannot_collide` now pins the rule on the composer every
kind goes through, so it covers whatever kind is added next rather than
the two kinds it happened to list, plus Soul's hand-off pair.
Mutations, each run against the committed suite and reversed after:
- drop the `.` escape from `repo_segment`: `composed_keys_cannot_collide`
fails on the hand-off pair.
- let a tail part be `dotted_text` instead of `label_text`: the same test
fails on the composer rule, `a.b.c` composing two ways.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The escape correction is the one that mattered: the map specified a key segment
that could not tell two legal repos apart, and the test that was supposed to
pin it asserted the property in a comment while checking one half of the pair.
The map now carries the injective encoding and the reason the obvious doubling
trick does not work either.
Two record corrections against Hands' own report. The byte-identical claim for
epiphany-state.exe is withdrawn as false rather than unverified, and no hash
replaces it, because that artifact's hash depends on the build root. The
omission in 7ab838f1's message is half what Self first relayed; the other half
belongs to a commit that does describe it.
Correction 14 is the second ledger drift this campaign, both times after a
correction raised one side of an estimate and left the other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator ruled on 2026-09-16 that widening a `PipelineKind` enum is
additive. The README's additive rule named only a new field with a serde
default, which is true and stays, but did not cover the case.
The clause says why in the README's own voice: these readers are ours and each
ships with the variants it knows, so a reader that meets a kind it has never
heard of refuses it on the kind, not on the epoch. That is also the answer to
the tension with the wire note two sentences earlier -- the contracts do cross
out of this repo, and the rule holds because the readers on the other side are
still ours. If that stops being true, the rule needs revisiting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul F4. The derivation test wrote its stale copies into one shared temp
directory and cleared it first with `remove_dir_all(...).ok()`. The clear was
best-effort and unverified: a held handle refuses the delete on Windows, `.ok()`
swallows the refusal, and a previous run's file survives in a directory whose
path the failure message invites the reader to copy from. The mutation suite
runs M8 and M11 against this test and never cleans up after them, so the
leftovers it would hide are mutated schemas no committed type produces.
The directory is now per-run, named by process id and nanosecond, so there is
no sharing to defend and the clear and its `.ok()` are gone rather than
hardened. It is still created only when something is stale, so a passing run
leaves nothing behind.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul F3. `pipeline_key` made the instance a root and `pipeline_id` was left
behind: it special-cased `campaign` alone and demanded three `:` segments of
everything else, so `PipelineRef { kind: Instance, id: "yggdrasil" }` was
refused as `InvalidFormat` and no resolution could ever take an instance as its
subject. The reader now knows both roots.
Nothing caught it because the key tests assert strings and never read one back.
`keys_read_back_as_ids_of_their_kind` walks every sample, keys it, and parses
the key back as an id of its kind; a resolution is the one kind whose key is
not an id of itself, so its subject is what reads back.
That test also carries Soul F2, which was Soul's surviving mutation: the
instance root's slug was validated nowhere the key could see, so an instance
named `thought cage` keyed to `thought cage` -- a store key holding a byte no
`Label` may carry. Stewardship and hand-off validate every slug they key by;
the instance now does too, and M14 is Soul's survivor turned into an entry.
M15 puts the shipped reader back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul F1. `/` -> `_` is not injective: `GameCult_Epiphany/thing` and
`GameCult/Epiphany_thing` both key to
`yggdrasil:stewardship:GameCult_Epiphany_thing`, so an instance stewarding both
silently loses one document. Keys are identity, not convenience.
`_` is now escaped too, and both codes are two bytes starting with `_`: `_`
becomes `__` and `/` becomes `_-`. Every other byte passes through and is never
`_`, so a reader going left to right takes each `_` with the byte after it and
never has a choice to make. The encoding is reversible, therefore injective.
That is the smallest change that buys the property: nothing else moves, and the
segment stays inside the `Label` byte class.
The test claimed the property and checked one half of the colliding pair, which
asserts nothing about collision. It now keys both halves and asserts they
differ. M12 is re-anchored on the new spelling, and M13 is new: it puts the
shipped `/` -> `_` back, which the pair kills.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every rule the moved code carries keeps a test that fails under its own
mutation, so the suite moves with the code rather than being left pointing at a
file that no longer exists. M1-M9 are Cut 4's entries re-anchored into
`epiphany-pipeline/src/lib.rs`; each entry now names its own package as well as
its own file, because M10 still mutates the spine in epiphany-core. All twelve
were run and all twelve were killed by their own test.
M10's mutation changed shape. It used to register the pipeline documents into
the spine's schema cache, and epiphany-core no longer has them; it now declares
its own type carrying `epiphany.pipeline.campaign.v1` inline, exactly as the
test it attacks now does.
Two entries are new. M12 keeps the repo escaping honest: drop the `/`-to-`_`
substitution and `stewardship_key_escapes_the_repo_slash` fails. M11 is the
cut's "change a value type and leave its schema alone" mutation, expressed as a
new enum variant rather than a new field: a field cannot be added without
breaking every struct literal that builds the value, which fails the whole
target and would prove nothing about the derivation test in particular. M11
changes exactly one derived schema, and the test names exactly that one.
`tools/eureka-cut4-mutations.ps1` is deleted rather than left to throw. Every
one of its ten entries is above, and its first act was to read a file this
branch moved, so it could not run again. It still runs at `a9f06c2a`, where its
receipt is.
The derivation test now empties its derived-schema directory before writing to
it. It did not, and this pass nearly published a schema from an earlier
mutation run's leftovers: the directory is shared, `Short` had `maxLength: 201`
in it from Cut 4's M8, and a copy of everything in there landed in the working
tree before the diff caught it. The gate that exists to stop a hand-written
schema should not be the thing that hands one over.
The stale-anchor guard is unchanged and still hard: an anchor that matches
anything but exactly once throws before a single test runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Main Families entry still said pipeline state is "stored per repo at
`.epiphany/pipeline/pipeline.cc`", which Cut 4 deleted, and the wire note still
sent these contracts to the voidbot projection. Both describe a machine that no
longer exists.
Pipeline state now lives in an instance's mind, owned by the Huginn memory
organ, and the contracts cross to that organ and its `eureka-state` client. The
three kinds this branch added are named, since the list is the entry's point.
The `[value]` payload sentence, the derivation sentence and the Q2 evolution
sentence stand; the derivation sentence now says which package runs the check.
Documentation only: no build, no test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ruling 1 keeps schema ownership in Epiphany; ruling 17 puts admission in
Huginn. Huginn must not take on epiphany-core -- about 41,000 lines and 295
lock packages -- to use about 1,000 lines of document types. So the documents
leave as a leaf library, and epiphany-core keeps no pipeline surface at all:
its consumption is campaign two.
`epiphany-core/src/pipeline_documents.rs` moves whole to
`epiphany-pipeline/src/lib.rs`, with its nine tests. Only three things changed
in the move: the module doc now describes a package rather than a cut map
section; `anyhow` joins `rmp-serde` behind `cfg(test)`, because the write path
has been test-only since 186dbc62 and the live library's errors are all
`PipelineRefusal`; and the decode refusal, which was pinned against
epiphany-core's Mind commit receipt, is pinned against a local `ForeignDocument`
carrying that receipt's type id, since the receipt belongs to the harness.
The package declares four dependencies. chrono parses a Date, cultcache-rs
supplies DatabaseEntry, schemars derives the published schemas (Q5), and serde
is the document format. anyhow, rmp-serde and serde_json are dev-dependencies:
every use of them is `cfg(test)`. `autobins = false`, as a leaf library must.
The workspace lock grows by exactly one entry, the member itself; no new
third-party package.
Deleted rather than re-exported: epiphany-core's `mod pipeline_documents;` and
`pub use pipeline_documents::*;`, and `schemars` from its manifest, whose only
user was this module. No shim, no duplicate type.
`runtime_spine_cache_refuses_a_pipeline_store` stays in epiphany-core, because
the rule is the spine registry's: it registers its own types and nothing else.
It now builds its foreign envelope from a `cfg(test)` stand-in carrying
`epiphany.pipeline.campaign.v1` rather than from the moved documents, so the
rule keeps its test without the package keeping a dependency.
D2's three new kinds arrive with the move, each with a live consumer in the
organ: `instance` is a mind's identity document, `stewardship` records the
repos an instance is assigned, and `hand_off` records a reassignment in both
minds. All three hang off an instance rather than a campaign, so they compose
their own keys instead of borrowing the `<campaign>:<kind>:<local>` tail. A
repo inside a key is escaped to `Org_Repo`, because `/` is not a Label byte.
Thirteen schemas now derive. The five that embed `PipelineKind` gained three
enum variants, which is additive, so Q2 keeps `epiphany.pipeline.epoch.v1`.
epiphany-pipeline: 12 tests, 0 warnings. epiphany-core: 163 tests -- 172 minus
the nine that moved -- 0 warnings. epiphany-state.exe is byte-identical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The regression Soul found is pinned at its real layer: a backing store carries
only its path, so no assertion about files or receipts can see a same-path
re-derivation, and the test now counts resolutions instead. A batch of two
writes with the second refused pins that every write is validated.
The map's own claim of one receipt construction site was wrong in two places,
including an authority-map line. There are two: the owner's, and a test that
plants one on purpose to pin the replay rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul mutated the collapsed commit owner twice and the suite survived both.
Neither rule was pinned; both are now, each by a test that fails under its own
mutation, and both mutations join the suite so the gap cannot reopen.
S7, a regression from base. The owner resolves the backing store once and
reuses it for the cache load, the CAS and the conflict re-read, and its doc
comment says no path in the function can target a second store. Re-deriving the
CAS handle from the same path left the whole suite green. The old profile test
had caught it only because its store was deliberately not the commit path; the
replacement asserted the temp dir holds one store, which a same-path
re-derivation satisfies exactly. A handle carries nothing but its path today,
so no assertion about files or contents can see the second one. The `.redb` arm
makes the difference real -- redb permits one writable handle per path -- and
the map plans redb for the organ's minds.
So the probe is the resolution itself: `runtime_spine_backing_store` records
every path it hands out, under `cfg(test)` only, and
`mind_commit_reads_and_writes_one_store` asserts a commit resolves its store
exactly once. The log is per-path and compared as a delta, so tests running in
parallel cannot see each other. Mutation MS1 re-derives from the same path and
the count reads 2.
S8, pre-existing. Narrowing validation to the first write also survived,
because no test committed a batch carrying more than one distinct write. The
existing mutation neuters validation for every write, which any single-write
batch catches; nothing covered the loop's reach. Added
`mind_commit_validates_every_write_in_the_batch`: two distinct writes, the
second refused, and the batch is refused whole with nothing written -- the
valid first write included. Mutation MS2 takes the first write only and the
test gets a commit where it expected a refusal.
M2's rule text is corrected to match what it actually pins, now that MS2 owns
the stronger half.
S10, cosmetic. The renamed test's fixture still said "mind-profile-test" after
the profile it named was deleted. The pinned receipt digest does not depend on
it and is unchanged.
Builds clean: 172 tests, 0 warnings. Seven mutations, each killed by its own
test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul confirmed the collapse changed no Mind behaviour, reading the owner diff
where the pinned digest is blind. It found that the replacement test pins only
"one file in this directory", so re-deriving the backing store at the CAS
survives, and that validating only the first write of a batch survives too.
Neither is a live bug today; the first is a regression from base and matters
once redb arrives, and the second is pre-existing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TypedCommitStore, its const, its validator and the parameter are gone, with
nothing of the profile shape kept. The Mind receipt digest was proven unmoved
by running the pinning test at the base commit in a detached worktree, not by
a value captured from the new code.
Three corrections stand: ruling 20's uniqueness-ordering half had no named
test until this cut added one; the Deletes table's line anchors were stale by
26 lines, so later cuts should re-anchor before trusting a line number here;
and the subtraction estimate missed the test rewrite the Keeps list implied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five rules, five tests, five mutations, each killed by its own test. Three come
from the cut spec (drop the epoch check from the opener, make the validator a
no-op, make the owner read one store and write another) and two are Soul's
finding S6, which is the reason this collapse is allowed to touch the Mind path
at all: ruling 12 put write validation after identity-uniqueness and after the
store opens, and before the replay check.
The order rules cannot be expressed as one string replacement, because moving a
statement is a deletion and an insertion. So an entry here carries a list of
edits rather than Cut 4's single Old/New pair, and every edit is still asserted
to match exactly once and to change something. A stale anchor throws instead of
reporting a verdict it did not earn. Files are restored in a finally block, and
every file an entry touches is restored, not just the last one.
M4 and M5 are the two halves of the order. M4 hoists validation above the
uniqueness checks and the duplicate-batch test catches the wrong refusal. M5
moves validation below the replay answer, and the planted-receipt test gets a
stored receipt handed back for a batch the validator refuses, which is exactly
the state S6 says must be impossible.
Builds: no code change. The script is not wired into any build or test target;
it is run by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 2 generalised the one commit owner into a profile so a pipeline profile
could share it. Ruling 20 sends admission to Huginn, so the second profile left
the repo and the abstraction had exactly one implementation. A
one-implementation abstraction is a named STOP condition, so it goes. Cut 2's
generalisation did not pay off; this says so rather than keeping the shape to
avoid admitting it.
Deleted: `struct TypedCommitStore`, `const MIND_COMMIT_STORE`, the
`store: &TypedCommitStore` parameter, `fn validate_mind_writes`, and the
`&MIND_COMMIT_STORE` argument at each of the five Mind wrappers. The three
choices the profile carried are re-inlined at their one use: the backing store,
the runtime spine opener, and the write validator, plus the literal
"epiphany-mind" as the receipt's store id. `RuntimeSpineBackingStore` lost its
last use in this file and left the import.
Behaviour is unchanged, including the pinned receipt digest
mind-commit-sha256:415283...281e. The owner's order is unchanged and stays
fail-closed: identities unique, store open (which refuses a foreign epoch),
writes validated, and only then may a stored receipt answer a replay.
The profile's two tests could not survive the thing they tested, so their rules
are re-expressed against the concrete owner. The two profile-shaped tests are
replaced by three that reach the same rules through the Mind path:
validation-before-replay now plants exactly the receipt the batch would produce
and asserts the refusal wins; uniqueness-before-validation pins which refusal a
duplicated invalid batch reports; and one-store asserts a commit touches only
the store it was given. Two surviving tests lost "profile" from their names,
which had become a lie.
Builds clean: 171 tests, 0 warnings.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul proved the two dead-code waivers were hiding genuinely dead items, so the
pipeline write path is now cfg(test) and says what it is; decode and the cache
imports came with it. The mutation suite documents the interpreter this host
has and asserts each anchor matches exactly once.
Cut 6 inherits four gated items rather than two live ones, and must refresh a
stale line citation. The refusal variants still have no reachable non-test
raiser, which is Cut 8's to fix when the organ exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's F10 and F11 on the Cut 4 fix batch.
F10: the header documented `pwsh -File`, but this host has no pwsh — only
Windows PowerShell 5.1. Document the invocation that actually runs, and refuse
anything older or non-Windows up front with a message naming the supported
call, rather than failing later on a cmdlet or path difference.
F11: the anchor guard only proved a match existed, while `.Replace` mutates
every match. A second site would have changed more than the entry describes,
and the named test could then fail for a reason the entry does not name — which
reads as "killed". Assert exactly one site instead. Every current anchor already
matches once, so this guards against growth, not a live defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's F9 on the Cut 4 fix batch: `prepare` and
`validate_pipeline_write_envelope` still wore
`cfg_attr(not(test), expect(dead_code))`, and Soul proved both dead in a
non-test build. `prepare` was structurally unreachable — it needs a CultCache
with the pipeline types registered, and the only registrar has been `cfg(test)`
since this cut. A waiver that describes a caller Cut 8 has not written yet is
the file claiming a live path it does not have.
Both become `#[cfg(test)]`, which is what they honestly are, and the waivers go.
Two things are test-only with them: `decode`, whose only caller was the
validator, and the `CultCache`/`CultCacheEnvelope` imports, which looked live
only because dead code mentioned them. The live path keeps the type ids.
Cut 6 now moves four `cfg(test)` items rather than two live ones; the spec's
Keeps list still holds, since what moves is the same code under a different
gate. The refusal picture is unchanged: `ForeignStore` and `InvalidIdentity`
were already raised only from these two, and the enum stays `pub`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul found the subtraction had taken a live rule's only test with it: the epoch
guard survived a `if false` mutation, and nothing could construct an identity
envelope to reach it. Rather than test state nothing can build, the epoch
branch, ForeignEpoch, the identity, provenance and faculty types and the epoch
constant are deleted; Cut 8 writes them in the organ where a test can reach
them.
Two live rules are pinned instead: decode refuses a foreign-typed envelope with
ForeignStore specifically, and the runtime spine cache refuses a pipeline
store. Four further corrections stand against the sections above, including the
Keeps list and the test count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
M9 and M10 are the mutation half of the previous commit: a rule with no mutation
is a claim, not a pinned rule.
M9 replaces the `ForeignStore` refusal in `decode` with a payload format error.
The envelope is still refused, so a test asserting only "this fails" would pass
either way; the test asserts the refusal itself, and dies. M10 registers the
pipeline document types into `runtime_spine_schema_cache`, which is exactly the
mechanism the spine refuses a pipeline store with — the registry, not the epoch
check. With the types registered the store opens and the test dies. Both aim at
the rule rather than at an arbitrary neighbouring line, which is what makes the
kill mean something.
The suite now spans two files, so every entry names its own, and the first run
caught a rake worth keeping: the anchors here are written with plain newlines
and the working tree is CRLF, so a multi-line anchor matched nothing. M1 to M9
are single-line and never spanned a newline, which is why the flaw surfaced only
when M10 arrived. Normalizing towards LF was backwards; the anchor is now
converted to whatever endings the file has. The stale-anchor throw did its job:
it refused to report a pass it had not earned.
Ten mutations, ten killed, each by its own named test alone. Sources are
restored byte-identical to HEAD afterwards.
Builds: no code change; the script is run by hand and is wired into no target.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's Cut 4 pass found one rule no test could reach and two with no test left
standing over them. F1 is the sharp one: `validate_pipeline_write_envelope`
raised `ForeignEpoch` for an identity envelope, but `EpiphanyPipelineIdentity`
had no constructor anywhere in the repo, so no test could build one to reach the
branch. Replacing the guard with `if false {` left all 168 tests passing. Code
that cannot be constructed cannot be tested, and testing it would have meant
writing the constructor the organ is going to own anyway.
So it goes, rather than growing a test to justify it. Deleted: the epoch branch
and the `ForeignEpoch` variant, `EpiphanyPipelineIdentity`,
`EpiphanyPipelineProvenance`, `PipelineProvenance`, `Faculty`, and
`PIPELINE_SCHEMA_EPOCH`, whose only reader was the deleted branch. D2 already
assigns admission, identity and the epoch refusal to `huginn-mind`; Cut 8 writes
them there against a store its tests can construct. Carrying about fifteen
unreachable lines across two repos costs more than re-adding them where they
work. Every one of the four types was checked for a live producer first: none
has one, in code, test, schema file or `index.json`.
What stays is what is reachable: `validate_pipeline_write_envelope` keeps its
bounds, formats and key recomputation, and `PipelineRefusal` keeps four variants,
each with a raiser.
Two tests for the two rules that survived Cut 4 untested. F2:
`decode_refuses_an_envelope_of_a_foreign_type` pins the `ForeignStore` refusal
against a commit receipt — a type a pipeline store may legitimately hold, and
still not a document. The existing round-trip test pinned only the positive
match. F4: `runtime_spine_cache_refuses_a_pipeline_store` restores coverage that
died with `pipeline_store.rs`, against the surviving mechanism in
`runtime_spine.rs`. It asserts first that `validate_runtime_store_epoch` passes
a pipeline store, because every neighbouring test is epoch-based and the epoch
checks cannot see this: the registry is the only thing refusing.
`register_pipeline_document_types` is now `#[cfg(test)]` instead of a live path
wearing `expect(dead_code)`. Its only callers are tests, and saying so is
honest; the organ registers its own types in Cut 8.
Builds: `cargo test -p epiphany-core --lib`, 170 passed, 0 failed, 0 warnings.
`cargo check -p epiphany-release-bundle --bin epiphany-state` clean. Mutations
for both new rules land with the next commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo-owned store layer is gone: 1,302 lines removed, one store format, one
lease and two git preconditions retired. Eight tests moved with the code rather
than waiting for Cut 6, so no rule sits unpinned between cuts.
Four corrections stand against the sections above: the local PipelineRefusal
keeps five variants because decode and validate_pipeline_write_envelope still
raise two of them; the deleted module held 20 tests, so the count is 168; Cut
6's estimate grows to about -1,060 now that the tests travel with the module;
and the schemas README still describes the old store path, which Cut 6 owns.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's standing finding S9: the previous pass claimed 27 mutations with no
artifact on disk, so the claim could not be checked. This is the reproducible
form. Each entry names one rule a surviving test claims to pin and the exact
source change that removes it, as a literal string replacement in
`epiphany-core/src/pipeline_documents.rs`. The script applies one mutation,
runs that test alone, restores the file, and fails if any mutation survives.
Eight rules, one per test carried into this cut: type-matched decode, UTF-8
byte bounds, the single-slash `OrgRepo`, key recomputation at validation, the
dot-free finding label, the parent marker character, a `PipelineRef` id parsed
as a full id of its kind, and the byte-for-byte schema derivation.
Builds: no code change. The script is not wired into any build or test target;
it is run by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 4. Rulings 14 and 15 moved the state off the repo: an instance owns its
mind and a service owns that state. Every layer that existed only to make a
repo-resident, git-committed, multi-clone store safe is now dead weight.
`epiphany-core/src/pipeline_store.rs` goes whole, 1,251 lines: the commit
profile, the epoch/foreign-type opener, the committed-blob checks, the
main-work-tree resolution, the read handle, the writer lease and its branch
binding, the holder record, and the 20 tests that pinned those rules. With it
go `PipelineWriterHolder` and its wrapper, `validate_pipeline_writes`, the two
`lib.rs` lines, the `.gitattributes` binary marker and the `.gitignore` lock
line. No Epiphany code can now open a pipeline store, take a lease, or read
git for pipeline purposes.
`pipeline_documents.rs` gains a local `PipelineRefusal` in place of the import.
D2 splits the landed thirteen variants: the six repo-store refusals
(`NotRepoRoot`, `NoMainWorkTree`, `StoreNotMarkedBinary`, `LockNotIgnored`,
`WrongBranch`, `WriterLeaseHeld`) die here, and so do `MissingIdentity` and
`Unavailable`, which lost their last raiser with the opener and the lease.
`ForeignEpoch` and `ForeignStore` stay because `decode` and
`validate_pipeline_write_envelope` still raise them; D2 hands them to the organ
when admission moves.
The seven document tests the cut map names, plus the schema derivation test,
move into `pipeline_documents.rs` rather than waiting for Cut 6, so no rule
sits unpinned in between. They lose the git scaffolding that has nothing left
to set up, and the key test now validates one envelope directly instead of
going through the deleted batch validator. Sample payloads that named the
deleted file were rewritten so the negative greps are honest.
This cut removes an owner and installs no replacement; the organ becomes the
owner in Cut 8.
Builds clean with no warnings: `cargo check -p epiphany-core --lib --tests`
and `cargo check -p epiphany-release-bundle --bin epiphany-state`.
`cargo test -p epiphany-core --lib` passes 168/168, down from 180: 20 tests
died with the file and 8 came back with the documents.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling (2026-09-16, "all recommendations ok"): instance identity is
declared attribution rather than signed authentication on a single-operator
LAN; the workstation reaches the organ over WireGuard, because RUDP is UDP;
TypedCommitStore collapses now that its second profile lives in another repo;
and the organ shares voidbot's Qdrant with the coupling declared as an Idunn
dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Imagination rewrote the means for instance-owned minds: Cuts 4-16 cover
subtraction, the epiphany-pipeline leaf library, Huginn's retirement and its
new huginn-mind and huginn-daemon bodies, Qdrant and Ollama, hand-off and
import, the eureka-state MCP client, deployment, skill wiring and a proof.
Cuts 1, 2 and 3a and the dead rulings sit under a history fence.
pipeline_store.rs dies whole (1,251 lines), and Epiphany's net change across
the campaign is negative. Admission moves to Huginn rather than dragging
epiphany-core into the daemon; Epiphany keeps the documents and schemas
through the leaf library, so ruling 1 holds.
Self bound Soul's S-labels to the new layers and corrected five places where
the target had drifted from the Body: the Ollama endpoint, the scope of
Huginn's authority vacancy, the surviving refusals, the thirteen document
kinds, and import not being a merge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator rulings (2026-09-16), superseding the repo-owned store:
- an instance owns its mind, and stewardship over repos is an assignment;
- a service on Yggdrasil owns the state, so minds leave version control and the
per-clone lease, git preconditions and divergence go with them;
- the organ depends on Qdrant directly, not on voidbot, and embeds through
Ollama on Nightwing;
- Huginn becomes that organ, and generic .cc inspection belongs to CultCache
Studio.
The organ speaks CultNet with eureka-state as a thin stdio MCP client; an
unreachable organ refuses loudly rather than spooling. Cut 3a's documents,
keys, validation and refusals survive; its store, lease and git layers do not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first attempt at these two tests still failed no mutation, because in both
cases another check answered first. A test that only passes is not evidence; it
has to be the thing that fails when its own rule is removed.
The marker rule. `cut-3a.h1` and `cut-3a.s1` cite the wrong kind's marker, but
`h1` and `s1` are not digits, so the digits rule refuses them whether or not the
marker is required. `cut-3a.1` is the case that isolates it: a well-formed
number with no marker at all, which only the marker rule refuses.
The enclosing-repo case. A separate git dir in its own temp directory is refused
even without the `.git` check, because the parent is not a work tree and the
toplevel verification fails anyway. The case the check actually exists for is a
clone whose git dir sits inside *another* repo's working tree: there the common
dir's parent is a real, valid work tree, so without the check `main_work_tree`
resolves to the enclosing repo and the store lands in a tree that does not own
it. The test now builds exactly that, and asserts the enclosing repo's store is
never created.
Builds clean with no warnings; `cargo test -p epiphany-core --lib` passes
180/180.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mutation pass found two rules that no test failed for, both because an
earlier check answered first.
The marker rule. Every bad parent in `parent_ids_are_parsed_strictly` also
violated the digits rule, so removing the `<marker>` requirement altogether
(Soul's S3) still refused every case and killed nothing. Two cases now cite the
right kind with a well-formed number and the wrong kind's marker, which only the
marker rule refuses.
The odd-layout refusal. `a_bare_clone_has_no_main_work_tree` asserted
`NotRepoRoot`, which a bare clone hits before `main_work_tree` is ever reached,
so `NoMainWorkTree` was unpinned and dropping the `.git` check failed nothing.
The test is renamed to `a_bare_clone_is_not_a_repo_root` for what it actually
pins, and `a_separate_git_dir_has_no_main_work_tree` covers ruling 10's odd
layout: a work-tree top level whose git dir is not a sibling `.git`, refused on
both the write and the read path.
Builds clean with no warnings; `cargo test -p epiphany-core --lib` passes
180/180.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>