Commit graph Epiphany/tools
Author SHA1 Message Date
Meta Vens
5cda0886bd Open one door onto the reference grammar
Ruling 1 leaves the schemas and the key grammar with the leaf, and the read
side in Huginn must refuse a `PipelineRef` whose kind and id disagree rather
than answer the nothing a lookup finds. `Bounded` is `pub(crate)`, so nothing
outside could ask.

`PipelineRef::validate_ref` delegates to that impl at the field name `ref`, so
the grammar keeps one owner and the wrappers and `pipeline_id` stay where they
are. A method, as `prepare` and `decode` are, because it concerns one public
type; `validate_ref` rather than a bare `validate`, because an inherent
`validate` would shadow `Bounded`'s for every in-crate holder of a
`PipelineRef`.

The test holds every sample document's own key to be a reference of its own
kind, and refuses a kind that is not the id's and a trailing dot at `ref.id`.
It also pins the boundary rather than assuming it: a resolution id missing the
per-subject sequence its writer always composes is still well formed here. The
leaf reads an id kind-deep, and the local's per-kind shape is admission's rule.

Entries R1 and R2 in `tools/eureka-cut8-epiphany-mutations.psd1`: the door that
answers `Ok(())` to everything, and the door that reads the kind segment alone.

`cargo test -p epiphany-pipeline --lib` 34 passed, 0 warnings; `cargo check -p
epiphany-core --lib --tests` clean; the four Epiphany suites green-and-killed
through the harness. No schema and no lock moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 22:31:26 +02:00
Meta Vens
d5a36c2a29 Pin the two sequences the all-ones fixtures hid
Soul's pass over the Cut 6d suite found two rules nothing tested. Every
nested fixture sat at `sequence: 1` at both levels, where a resolution's own
sequence and the one its subject already ends in are indistinguishable, so a
mutant that copies the subject's survived. And no test resolved a
stewardship, the other sequenced kind, so nothing held its own sequence into
the resolution key the way M27 holds a nested resolution's.

Two tests. `every_level_of_a_nesting_keeps_its_own_sequence` builds a chain
that differs at every level -- `question.Q1.n9`, then `.n10`, `.n0`, `.n12`
-- and reads it back level by level, each level's subject recovered whole and
each level's own sequence last. `a_resolution_of_a_stewardship_reads_back`
resolves both assignments of one repo, pins the two keys apart, and nests a
withdrawal over each. Entries S6 and S11 carry the mutants they kill.

The nesting test's doc comment records the spelling limit while it is in
hand: the leaf emits `n<N>` as `u32` writes it, so `n01` is never emitted,
but a hand-built id ending `n01` or `n1x` is a legal label and no business of
a grammar that reads keys. Nothing canonicalises it either -- it names a
document the leaf would never key, which admission's A7 reads as a missing
referent.

Suites: 6d seven entries killed, M0 green; 6b, 6c and cut8-epiphany
unchanged and green. 33 tests, no warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 19:20:24 +02:00
Meta Vens
b82c76dfd6 Add the Cut 6d mutation suite
Five entries, each restoring the permissiveness the shape it pins
removed: M23 and M24 the one-per-subject and one-per-repo keys, M25 and
M26 the sequence anywhere but last, M27 a nested subject stripped of its
own sequence on the way into the key. Each dies on the test named beside
it, M0 green.

The file records what has no entry and why: the `n` marker is a fixture
pin, the `u32` type has no runtime mutation, and date-invariance is
covered by M24's test asserting a stewardship local is exactly two parts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 18:37:12 +02:00
Meta Vens
9699751f00 Key a resolution and a stewardship by sequence
A subject's resolutions and a repo's assignments on a mind each carried
one key, so the second record of either overwrote the first and a
withdrawn resolution could not be kept under its subject. Both keys now
end in a writer-set `sequence: u32`, `n<N>`, last so the history of a
subject or a repo is one key prefix and R2 keeps that prefix exact:
`question.Q1.n` cannot match `question.Q10.n1`, and a withdrawal keys one
nesting out under the resolution it withdraws.

`assigned_on` stops being a key input and stays a field, validated where
every field is, so the same assignment recorded on two days is one
document and a repo transferred away and back on one day does not
collide. A hand-off is unchanged in shape and key: a transfer back is the
same document with the instances swapped, and no field names a return.

Nesting arithmetic moves with the shape: each nesting prepends
`resolution.` and appends `.n<s>`, so a chain composes `14 * (d - 1) + L`
bytes against `LOCAL_MAX`, and both fixtures key four deep. The leaf
refuses no sequence value -- not `0`, not a gap -- as it refuses no
`revision` value; which sequence a document may claim and which record is
in force are admission's, and are not in this crate. `pipeline_id` gains
no arm and no key is parsed back.

Two schema files regenerate from the derivation, gaining the property,
the required entry and the new doc comments. No epoch moves: no store is
written at this epoch and no reader is pinned to the file.

Cut 6b's M2 is re-anchored on the new parts line, as N7 and N8 re-anchored
Cut 6's M12 and M13.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 18:26:46 +02:00
Meta Vens
b4b17fc895 Serialise the pipeline envelope and its refusals
The leaf owns the document and refusal types, so it owns their
serialisation. Huginn's admission batch cannot derive Serialize while
PipelineDocument does not, and mirrors PipelineRefusal through a
serde(remote) definition kept in step by hand; neither is the wire's
business.

PipelineDocument is adjacently tagged on the kind name: a document on a
wire is { kind, value }, and kind is exactly PipelineKind::name(), so a
reader dispatches on the kind segment it already knows from the key and
needs no second registry mapping variant spellings to kinds. Adjacent
rather than internal because a value is a named map of its own.
PipelineRefusal stays externally tagged, serde's default, which is what
keeps every variant's field and value on the wire under their own names.

No kind, field, type id or epoch moves, and the published per-kind
schemas are untouched: the envelope is the wire's shape, not a document's.

every_document_and_refusal_serialises_and_reads_back round-trips every
sample document and every refusal variant through serde_json and
rmp_serde, and holds each derived schema's variant list to the thirteen
kinds by name and to the four refusals. Mutations D1 (the tag rename
removed) and D2 (a refusal's field skipped) are both killed by it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 17:26:30 +02:00
Meta Vens
b3bd4a82e4 Repair a missing file from its sidecar and stop on a nested one
Soul's XF: a sidecar whose file was gone hit the `if`/`else` empty-array
collapse of Windows PowerShell 5.1, so `Find-FirstDifference` threw a raw
`ArgumentNullException`, the sidecar stayed, and every later run in that
repo died the same way before M0. A missing file is now its own case,
decided before any comparison: it is recreated from the sidecar, whose
bytes are the only copy, hash-verified, and the run says so.
`Find-FirstDifference` refuses a null array with the harness's own words.

Soul's XG: a sidecar of a sidecar was repaired in enumeration order, so
the inner sidecar's body became the file and the outer one then crashed
on its missing target. No run leaves that shape and the two cannot be
ordered, so the repair pass now stops before touching anything, naming
both files.

Soul's F2: the `.eureka-mutation-overwritten` copy was written before the
restore was attempted, so a locked target (XC) left a copy claiming an
overwrite that never happened. The copy is still written first, because
once the restore lands it is the only home of the pre-repair bytes; a
restore that fails without opening the file removes it again, and one
that changed the file before failing keeps it and says so.

Every repair step now sits inside one `try`/`catch` per sidecar, so the
"could not repair" message is the error that escapes, with the cause
appended, never a bare .NET exception.

Reproduced against a scratch copy with Soul's soul-cut8e-harness-attack:
XF recreates the file and removes the sidecar; XG stops with both files
named and the target untouched; XC leaves no overwritten copy. XA, XB,
XD, XE, XH, XI unchanged. An empty existing file beside a sidecar (XK)
repairs without the null path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:51:14 +02:00
Meta Vens
a65c6420f6 Cut 8, Epiphany half: the leaf's write path goes live for the organ
`epiphany-pipeline` was the organ's type library with its prepare,
decode, register and validate path gated `cfg(test)` "until the organ
prepares writes (Cut 8)". This is Cut 8. The four are `pub` and
un-gated: `register_pipeline_document_types` registers the thirteen
kinds and nothing else, `PipelineDocument::prepare` and `::decode` are
the one door to the crate-private wrappers, and
`validate_pipeline_write_envelope` returns `PipelineRefusal` directly,
which drops the `downcast_ref` in the key test. `anyhow` and `rmp-serde`
move from dev to normal dependencies; both were already in the lock, so
the lock is unchanged. The test stand-in `ForeignDocument` stays
`cfg(test)` and is registered by the tests' `schema_cache`, no longer by
the live registrar.

`PIPELINE_SCHEMA_EPOCH` returns as one `pub const` beside the schemas it
names, with the README's evolution rule as its doc comment: additive
keeps it, a widened `PipelineKind` is additive, a breaking bump refuses
the old store. `every_kind_is_at_the_epochs_version` reads the version
off the constant and holds every type id to it, and counts the live
registrar's types against `PipelineKind::ALL`.

Verification: `cargo check -p epiphany-pipeline --lib --tests` and
`cargo test -p epiphany-pipeline --lib` (27 passed, 0 warnings);
`cargo check -p epiphany-core --lib --tests` untouched; `cfg(test)` in
lib.rs is now only `derived_schema`, `ForeignDocument` and `mod tests`;
`schemas/cultnet/` and `Cargo.lock` unchanged. Mutations
`tools/eureka-cut8-epiphany-mutations.psd1`: M0 green, E1 (epoch `.v2`),
E2 (one kind fewer registered), E3 (the stand-in registered again under
`cfg(test)`) all killed by the new test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:21:34 +02:00
Meta Vens
3f7d58d191 Harness: repair every sidecar in the repo, and never overwrite silently
Soul's extra attacks on the mutation harness found the crash repair too
narrow and too eager. A sidecar left beside a file outside this run's
targets was ignored (XA), so a dead run's mutant survived in the tree; a
hand edit made after a crash was overwritten by the sidecar without a
word (XB); a locked target threw a bare IOException with no path (XC);
and a sidecar equal to its file was reported as a repair (XD).

At startup the harness now walks the repo root (`target/`,
`node_modules/` and `.git/` pruned) for every `*.eureka-mutation-original`
and repairs each. A file equal to its sidecar is left alone and the run
says "sidecar matched; nothing to restore". A file that differs has its
current bytes written to `<file>.eureka-mutation-overwritten` first, with
the path and SHA-256 printed. A write that fails prints the path and
"could not repair, sidecar kept" before rethrowing.

`-Repo` names the root the entries, targets and commands are relative to,
defaulting to this script's parent, so a suite in another repo runs
through this one harness.

Reproduced against a scratch copy with a fake runner: XA repairs b.rs
and removes its sidecar; XB keeps the hand-edited bytes in the
overwritten copy (same hash) and restores the original; XC stops with
the path and the sidecar kept; XD removes the sidecar and restores
nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:16:11 +02:00
Meta Vens
fcfbda3f94 Pin the three Cut 6c rules Soul's mutations survived
Soul's own suite found three validator rules no test measured: a fix's
commit was unpinned once `by` was `Some`, the validator's own bound on a
supersession was unpinned (only the schema's `maxItems` was), and the
reason on `Recorded` and `Withdrawn` had no bound test. Each is now a
forgery in the test that owns the arm: `Fixed { commit: "dirty-worktree",
by: Some(ruling) }` refused at `resolution.outcome.commit`; nine
supersessors refused as `FieldBound` at `resolution.outcome.by`, eight
accepted; a 1,001-byte reason refused at `resolution.outcome.reason` on
both outcomes.

Soul's S2, S14 and S10 join the Cut 6c suite under their own ids. Rerun:
M0 green (26 passed); M16-M22, S2, S14, S10 killed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:14:41 +02:00
Meta Vens
dddf9ede53 Cut 6c mutation suite, M16-M22
Seven entries through the shared harness: the Superseded list checked by
length only (M16), the outcome's whole match body replaced by Ok (M17),
the fix's commit unvalidated (M18), the mutation label widened to Short
(M19), its commit widened to Short (M20), the sample estimate's line
counts transposed under a two-line anchor that carries the opener (M21),
and a claim's mutations bound widened to sixteen (M22). Each is killed by
the Cut 6c test that pins it; the key suite is blind to all seven.

The first run left M19 and M20 with no verdict: the sample and the test
spelled the label and the commit with the narrow constructors, so the
widened tree did not compile instead of admitting the forgery. Both now
go through From<&str>, which every text type has, and the forgeries
reach validation and kill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 15:41:41 +02:00
Meta Vens
7cd1a38bc8 Pin the reader against a trimmed trailing dot on the root and the local
Soul's X1 and X11 survived the second fix batch: a reader that trimmed a
trailing dot from the root or from the local before its dotted_text check
passed every test, because the only fixtures with a trailing dot went
through the writer, which never composes one. Four malformed ids now
read through pipeline_id in keys_read_back_as_ids_of_their_kind and are
refused: a trailing dot on the root, a trailing dot, a leading dot and an
empty part on the local. X1 and X11 join the Cut 6b suite as entries,
each killed by that test.

The resolution arm's comment named "the 64-byte bound in local"; the
bound is LOCAL_MAX and the comment now says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 15:28:23 +02:00
Meta Vens
9d57a46079 Harness: restore inside every finally, sidecar the original, time out
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>
2026-09-16 15:24:15 +02:00
Meta Vens
f00062dbb1 Cut 6b: pin dotted roots, the reader's root kinds, and name the bound
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>
2026-09-16 14:59:27 +02:00
Meta Vens
9370aa0f60 Harness: restore from bytes, count overlapping anchors
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>
2026-09-16 14:50:43 +02:00
Meta Vens
d03a32dfc1 One mutation harness for every Eureka cut
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>
2026-09-16 14:19:38 +02:00
Meta Vens
95ee551aa0 Cut 6b: mutation suite for the key grammar
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>
2026-09-16 13:45:39 +02:00
Meta Vens
b6f6e8023c Read an instance key back as an instance id
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>
2026-09-16 12:16:50 +02:00
Meta Vens
eb55efe9c9 Make the repo key escape injective
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>
2026-09-16 12:14:46 +02:00
Meta Vens
08c1c9d907 Carry the mutation suite across the package boundary
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>
2026-09-16 11:46:30 +02:00
Meta Vens
617c397d88 Close Soul's Cut 5 findings: pin one handle, and every write
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>
2026-09-16 11:21:16 +02:00
Meta Vens
4c091f57db Commit the Cut 5 mutation suite
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>
2026-09-16 10:44:55 +02:00
Meta Vens
06100d8e30 Make the mutation suite runnable and its anchors exact
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>
2026-09-16 02:31:18 +02:00
Meta Vens
8a598ebbed Pin the two restored rules with mutations, and match anchors to the file
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>
2026-09-16 02:03:14 +02:00
Meta Vens
a9f06c2ae5 Commit the Cut 4 mutation suite
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>
2026-09-16 01:24:28 +02:00
Meta Vens
387afe4941 Remove parallel executable control planes 2026-08-23 01:51:24 +02:00
Meta Vens
1d5a1f1793 Delete unowned executable surfaces 2026-08-23 01:32:47 +02:00
Meta Vens
a6d1cd46b2 Found Starfire resident runtime 2026-08-05 23:18:06 +01:00
Meta Vens
2eb1410ad6 Replace Codex MCP with native RMCP runtime 2026-07-21 19:27:28 +01:00
Meta Vens
60bbf07c12 Delete the local Bifrost ledger oracle 2026-07-15 20:04:59 +01:00
Meta Vens
56f54055fa Separate declared topology from observed presence 2026-07-15 19:43:56 +01:00
Meta Vens
d023a919d8 Delete the Bifrost readiness oracle 2026-07-15 18:37:21 +01:00
Meta Vens
98dd338994 Replace false SCM authority with durable task ownership 2026-07-15 11:47:54 +01:00
Meta Vens
ef26c0753a Separate character context from scheduler physiology 2026-07-13 17:31:08 +01:00
Meta Vens
4e1b80c98a Derive Persona bubble projection status 2026-07-13 17:22:59 +01:00
Meta Vens
8e3e48d027 Separate provider readiness from live transport 2026-07-13 17:16:36 +01:00
Meta Vens
d055938e29 Delete fixture-aggregated MVP gate 2026-07-12 22:59:53 +01:00
Meta Vens
e57509e804 Delete counterfeit readiness approval 2026-07-12 22:56:48 +01:00
Meta Vens
4441256033 Delete synthetic Bifrost accounting closure 2026-07-12 22:46:12 +01:00
Meta Vens
b03ab6265f Make service command identity own execution intent 2026-07-12 20:37:37 +01:00
Meta Vens
2a797370f6 Bind operator run receipts to result evidence 2026-07-12 20:33:30 +01:00
Meta Vens
c9faff92f2 Cut remaining Bifrost receipt counterfeits 2026-07-12 20:13:49 +01:00
Meta Vens
16b4a2fc8b Split public proof requests from Bifrost receipts 2026-07-12 20:08:49 +01:00
Meta Vens
b861c58254 Split Eve requests from provider receipts 2026-07-12 19:55:42 +01:00
Meta Vens
e1140d39d6 Split daemon tool requests from provider receipts 2026-07-12 19:51:35 +01:00
Meta Vens
a130258e40 Split Bifrost request from provider receipts 2026-07-12 19:45:56 +01:00
Meta Vens
bf7d2ba52b Remove false Gjallar ownership from Epiphany 2026-07-12 17:14:39 +01:00
Meta Vens
68c47b7ffa Move heartbeat service lifecycle under Idunn 2026-07-12 12:49:22 +01:00
Meta Vens
c0b542f7ba Collapse native coordinator store boundary 2026-07-12 07:58:19 +01:00
Meta Vens
706b43c5f8 Clean doctrine and operator projection surfaces 2026-07-11 18:41:07 +01:00
Meta Vens
791d7765a0 Expose Persona bridge proof wrapper 2026-06-22 17:38:55 +01:00