Soul lost a run to CRLF and it still exited 0. The cause: this file's own line
endings travel with the heredoc, so the remote bash fails on `set -euo
pipefail\r` before `set -e` is in force, limps past the failure and exits
clean. A verification runner that can report success without running anything
is worse than no runner.
Two guards, because one was not enough to notice this for a day. The remote
now strips carriage returns before bash sees them, and it prints a verdict
sentinel the local side requires; a missing sentinel is treated as failure
rather than as silence. A .gitattributes keeps the file LF on disk so the
first guard is rarely needed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same commit mutated three times at -j4 gave three different survivor sets,
while every -j1 run reproduced the same one. Likely the shared cargo target
directory under concurrent builds, which is this file's existing scar wearing
a new hat.
A score survives concurrency roughly intact, because a genuinely killed mutant
stays killed. A survivor list does not, and survivors are what drive rulings.
Stryker's concurrency flag is in the same position, so figures taken at four
in earlier campaigns are provisional; the rulings they informed rest on the
hand-confirmed mutations beside them.
Second entry: three fixtures in two days passed by coincidence rather than by
rule. Values whose bit pattern happened to match the assertion, hashes that
happened to sort the right way, a file wrapped in a directory so a different
path ran. Breaking the rule and watching the test fail is what separates these
from coverage, and it is already in the brief.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three Soul passes over one file and a selection batch found the same thing
three ways. Every artifact-digest test wrapped its file in a directory so a
different code path ran, while production passes a bare file immediately
before launch. A row-tiebreak test passed with the rule reverted and swapped,
because of which real hash happened to sort first. An xattr test pinned
presence while the privilege lives in the value.
None of these looked like gaps. They looked like coverage, which is why they
survived. Write the fixture at the shape production uses, and derive keys from
ids discovered at runtime rather than hoping their order cooperates.
Second scar, same campaign: a counter was an honour system, so it became a
port, and the port is an honour system one level up. Soul restored the bug
around it with the suite green. Where a rule says all access goes through one
place, the enforcement belongs in the build, not in a habit. If the fix is
another thing to remember, it is the same defect one level up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three scars from one Idunn batch.
A test pinned a cost regression with a ratio whose clamp always fired, so it
was really a fixed 200 ms threshold, on a host that now runs three jobs at
once. Clean code had already measured 198.9 ms. The reported 33x separation
was an artifact of one pair straddling the clamp; the real figure was 1.63x.
Count the calls instead, and treat a reported ratio as a claim to check rather
than a result to accept.
Two fixtures in the same batch would have lied, and mutating caught both. A
setuid fixture chowned after chmod, which silently clears the bit. An
observe_frozen test tampered where a different check also fires, so it stayed
green with the check it pinned deleted. A third term could not be pinned
honestly at all, and Hands deleted the fixture rather than keep one that
implied otherwise.
Last, Self ruled a mount carry nosuid without checking that Docker offers it.
It does not, and the stronger mitigation was already in place. A ruling naming
a flag is a factual claim about a tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul reported four containment checks where one sufficed, each surviving
deletion on its own, and Self ruled three deleted. With all three gone a
symlink whose whole target is ".." at the root escapes containment. The method
was sound; the conclusion did not follow from it. A redundancy finding has to
be probed at the combination that will actually be deleted.
A Hands hand-trace caught it, because the brief says a deletion judged unsafe
comes back with evidence rather than being quietly kept. That rule stays, and
it applies to deletions Self ordered.
Second scar, same batch: Soul's HTTPS rig had evaporated by the time Hands
was told to commit it. Probes stay throwaway, but once Self rules one becomes
a committed test it is no longer scratch, and the handover has to happen while
it still exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A QUIC test suite passed every scenario on Windows against an unrelated
msquic.dll picked up from PATH, a tenth the size of the pinned build. The
same missing copy failed loudly on Linux. Windows' search order makes silent
substitution the default failure mode.
Two rules follow. A verification step that loads a pinned native library
reports what it actually loaded, by size or hash, rather than only that the
run passed. And a defect found on one platform gets checked on the other
before it is closed, because both earlier fixes for this lived inside
if(NOT WIN32) and nobody looked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two Hands were dispatched into one working tree. The second switched the
branch out from under the first's running build, then found an uncommitted
edit it had not made and stopped rather than forcing the checkout. Nothing was
lost because of that refusal, not because of anything Self did.
The rule was already written and Self ignored it, so the fix is not a louder
warning to Hands. A brief that names a repo path without naming a worktree is
a defect in the brief.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two scars from one Hands report. The map it read was five days old because
Self commits maps to whatever branch it is standing on; SKILL.md now says
main, and says every brief names the map's branch.
The same agent refused a mid-task instruction from Self that it could not
distinguish from an injection. The instruction was genuine. The fix is not to
make the agent more trusting: scope expansion should arrive as a new dispatch,
not appended after a tool result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Mind Steward pass falsified SKILL.md's claim that the stopgap runs at most
two jobs at once. The operator raised it to three this morning and the script
carries that; SKILL.md did not. An agent steering by SKILL.md would have
under-parallelized verification and quietly contradicted an operator ruling.
The changelog's entry for the same day is worse: it records caps of 6 CPUs and
16 GiB, which match neither the script nor SKILL.md. I cannot tell whether
that was ever live. The correction says so rather than inventing a history.
Both surfaces now name ygg-verify.sh as the owner of its own numbers. Prose
that restates a running tool's configuration goes stale in silence, which is
how three surfaces disagreed inside one day.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator: "throw in a third slot, the other cores are mostly sitting idle".
Verification now takes 12 of Yggdrasil's 16 CPUs at most, and the live
services keep four.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling 2026-09-22: better nothing than a harness that punishes
refactoring. Behavioural tests plus the ecosystem's mutation tools on a cut's
diff; Soul's hand mutants stay one-off probes. The stopgap image carries
cargo-mutants instead of pwsh and is tagged by its Dockerfile's hash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A worktree's .git points at a host path the container cannot see, so
Ghostlight's build.rs failed asking git for the commit. Editing the running
script in place killed a job with 'unexpected EOF': bash reads scripts from disk
as it runs, so new versions go in by rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A job that found both slots busy blocked on slot 1 alone while slot 2 freed,
and ssh sessions whose remote end was gone hung on Starfire for over an hour
with no keepalive to notice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hands and Soul measure suites with Stryker.NET, cargo-mutants, StrykerJS or
mutmut scoped to the cut's diff, and triage every survivor by name. The
hand-written anchor harness is demoted to code no tool reaches. Evidence in
the changelog: Aetheria's 23 harnesses stranded on refactors and one anchor
silently re-targeted; a Stryker spike killed everything they defended and
found the splash-handedness bug they missed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul found 36 Idunn tests fail without it, so Hands' numbers only reproduced
after a hand-made machine-id inside the container.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the three defects Soul's H14 pass found and this pass fixed:
the startup-repair marker check for H1's kept sidecar, the unique
overwritten-copy naming, and the case-sensitive content/hash/no-op
comparisons. Points at the fixing commits.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every hash comparison in the script (sidecar verification, mutant
verification, restore verification: Assert-TargetUnedited, Restore-
Targets, the startup repair loop, and M0) used PowerShell's plain -eq/
-ne, which is case-insensitive. Hashes are hex strings where case never
carries meaning here (Get-Hash and Get-BytesHash always produce a fixed
case each), so this was latent, not exploitable -- but it left every
"is this file still what I think it is" check one string-comparison
operator away from silently accepting a corrupted file whose hex
happened to differ only in case. Switched all eight sites to -ceq/-cne.
Hash case is chosen to matter, both for defence in depth and because
these functions are the harness's only proof that a restore or a
mutant write landed the intended bytes.
The no-op guard that rejects a mutation whose old and new text come out
identical also used -eq: `if ($old -eq $new)`. Under case-insensitive
comparison a mutant that differs from the source only in case, such as
`"query"` to `"Query"`, compared equal and was refused as a no-op even
though it is a real source change. Switched to -ceq.
Anchor matching (Measure-Sites, and the splice-site IndexOf that
follows it) already used [System.StringComparison]::Ordinal, which is
case-sensitive; checked and left unchanged.
Demonstrated in a scratch repo (demo-case) with a single-mutation suite
whose only edit is `"query"` -> `"Query"` against `fn f() -> query { 1
}`, and a fake test command that fails unless the unmutated lowercase
text is present (i.e. is killed by the mutation).
Before: "CaseDemo: replacement changes nothing in t.txt.", exit 1,
mutation never ran.
After: the mutation runs, the fake test fails against the mutated text,
verdict "killed", M0 green, exit 0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
H1 (Assert-TargetUnedited, :~166-220) keeps a target's sidecar rather
than removing it when it catches an edit the harness did not make, so
the M0 original survives beside the lost edit. Startup repair (the
sidecar loop after "Repair a previous run that died mid-mutation") could
not tell that kept sidecar apart from a crash's: on the next run it
restored the M0 original over whatever an operator had since hand-
cleaned into the target, moved the operator's clean file to
`.eureka-mutation-overwritten` (clobbering H1's own copy there before
the F2 fix), printed "a previous run died mid-mutation", removed the
sidecar, and finished green. The hand-cleaned edit was gone one run
later with no non-zero exit to notice it by.
Get-H1MarkerPath names a marker, `<target>.eureka-h1-stopped`, that
Assert-TargetUnedited now writes beside the sidecar whenever it keeps
one. Startup repair now scans for these markers before touching any
sidecar; if any exist it refuses the whole pass, restores nothing,
prints which files to reconcile and where their marker is, and exits
non-zero. An operator deletes the marker (and the sidecar, once done
with it) by hand to let repair run again.
Demonstrated in a scratch repo (demo-f1): H1 fired (a fake test command
appended stray bytes to the target mid-run), leaving a sidecar, a
marker, and an overwritten copy. The target was then hand-cleaned to
"alpha-HANDS-EDIT" and the harness rerun.
Before (original script, ignoring the marker): exit 0, target silently
reverted to the M0 original ("alpha"), the hand-cleaned edit gone.
After (fixed script): exit 1, "Startup repair refuses to run: 1 file(s)
were stopped by H1 in a previous run, not crashed...", target still
"alpha-HANDS-EDIT", sidecar/marker/overwritten all untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assert-TargetUnedited (tools/eureka-mutations.ps1:172-173 before this
change) and the startup sidecar repair (:342) both wrote an unconditional
"$path.eureka-mutation-overwritten" whenever they preserved bytes that
were about to be overwritten. A second EDIT LOST or a second dead-mid-
mutation repair against the same target silently destroyed the first
preserved copy -- and so did an operator's own file already sitting at
that name.
Get-UniqueOverwrittenPath now picks the first unused name in the family
(".eureka-mutation-overwritten", then ".eureka-mutation-overwritten.1",
".2", ...) before either site writes. Every preserved copy now survives
beside every other.
Demonstrated in a scratch repo (demo-f2): a file was planted at
t.txt.eureka-mutation-overwritten holding "PRECIOUS-PRE-EXISTING-COPY",
then an H1 edit-lost event was triggered against the same target.
Before: the planted file's content was replaced with the newly-lost
bytes; only one copy survived.
After: the planted file is untouched (still "PRECIOUS-PRE-EXISTING-COPY")
and the new loss lands at t.txt.eureka-mutation-overwritten.1; both
copies survive.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restore-Targets never checked what a target's bytes actually were before
restoring over them, so a build step or stray edit landing mid-run or
between entries was overwritten without a trace (Soul's finding on the
Huginn memory-organ Cut 10 fourth fix batch, 2026-09-22; that same batch
had lost a Hands edit this way). Restore-Targets and the entry loop's
pre-write step now hash the target first: a match against the M0 original
or this entry's own mutant restores as before, anything else is saved to
<target>.eureka-mutation-overwritten, warned about loudly, and fails the
run with a non-zero exit and no further entries.
Demonstrated in the scratchpad with a throwaway repo whose test command
appends a line to the target mid-run: pre-fix the run went green and the
target came back pristine; post-fix it failed loudly and kept the
appended line. Recorded in references/changelog.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A Ghostlight Hands agent amended and force-pushed a kernel commit while
Self pushed map commits to the same branch. The briefs and the scars
list now forbid it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two worktrees sharing one cargo target get one artifact hash, and a plain
cargo test in the older checkout ran the other's mutant binary. Found by
Soul on the Huginn Cut 10 second fix batch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GPT-Image-2.5 Flare, operator-run, 2026-09-17. The last piece of the chain the
repository could not know on its own.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The second crop carries no wordmark, for surfaces that set their own type: the
site can letter it in live Ubuntu Light small caps rather than in pixels, which
stays selectable, scales without the lattice fighting the text, and follows the
brand if the brand moves. The lettered crop stays for surfaces that cannot set
type at all.
Both were committed under a filename that recorded which run produced them
rather than what they are.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The render goes through the repixelizer onto a real lattice, then the operator
crops it to the specimen case, the character and the verdict panel, and letters
it in Ubuntu Light small caps. Less busy, and it survives being shown with big
chunky pixels.
The prompt file now carries the whole chain: the exact rebuild command, why the
repixelizer's own command line is the wrong way to run it, and the editable
original beside the render. An image without its provenance is not a
deliverable.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Epiphany's avatar fights JSON heresy across a landscape of ownership
monoliths. Eureka's enemy is a green suite over a broken machine, so the
character has pried up a floor panel stencilled GREEN and is looking at the
mechanism underneath, pleased with herself, beside a specimen case of pinned
mutants with one still moving.
The prompt lands in the repository first and the render joins it, because an
image without its prompt has lost its provenance.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The README opened with the org chart when the evidence is the argument. It now
opens with the thing that goes wrong when one agent plans, implements, tests
and reports on its own work, and puts the numbers in the first screen.
Four cases from one day show what the falsifier actually catches, including a
rule everyone agreed could not be tested and a mutation history that turned out
not to exist.
One correction: the status section described the state client in the present
tense. It is not written, and the skill is not wired to it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The mutation harness lived in Epiphany because that is the repo that needed it
first, and Huginn reached across for it. It takes the repository as a
parameter, so one copy serves every campaign and a fix to it fixes all of
them. A repo that cannot run it keeps its own runner and owes the same
contract by name.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One line took three passes to pin. The suite proved the wait was not one
constant, then a scenario proved it was not any constant, and a clamp walked
through both. Capping a wait so shutdown gets noticed is the most ordinary way
that line will ever be broken, and it is a function of the argument rather
than a replacement for it.
So when a rule says a value is derived from an input, one mutant has to be a
function of that input, and the probe values have to sit where such a function
would show. Constants are only ever the first case.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Twice in one day a rule was honestly recorded as impossible to pin, and twice
the claim was wrong: one fell to a probe built from the crate's own public
surface, the other to a client opening a closed port in fifteen lines. An
unreachability claim is a hypothesis and gets falsified like any promise, so
it is written as not yet reached and the next pass opens by reaching it.
The same pass found why a rule kept being pinned by constants. The scenario
held its callers after the wait whose duration was the rule, so every mutant
of it landed behind the observation, and a hard-coded two seconds walked
through the whole matrix on both targets.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two ways a green sweep lied on the same afternoon. A bridge's whole mutation
history lived in scratchpads that no longer exist, so the rules it defended
were defended by nothing anyone could rerun; a probe that is the only killer
now gets committed by the next Hands pass. And a loosening that could not fail
was quietly swapped for an easier target, leaving an instance check that
survived comparing only lengths and only first bytes, because no fixture pair
had ever shared a length. An unfalsifiable loosening is a finding about the
fixtures, and the fixture is what gets fixed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Re-cutting came from questions answerable on day one and asked on day three,
not from maps that lacked detail. A model table now precedes every cut map,
one row per persistent kind, and no cut is mapped while a cell is empty. The
operator's forks are gathered once rather than discovered across five cuts.
In the other direction, the per-file anchors are required only where the code
exists and they say what not to touch. Transcribing the body of code nobody
has written yet is a map at full scale, and the only spec complete enough
there is the code. Cutting faster means less prose per cut, not more code.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Most later Soul passes found rules the suite could not see rather than
defects, because Hands proved each rule with a revert and Soul with a
loosening. Hands now does both. Reports carry findings, promises and
numbers and nothing else, and a second pass on a cut is scoped to the fix
batch. Operator-approved after a cost review of one day's campaigns.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three lessons from Ghostlight L1.
1. Three agents ended their turns while their own detached cargo job was
still running, even though the briefs already said to wait for long jobs.
The stall stopped only once the briefs named the mechanism: block inside
a tool call.
2. A test built on a random draw passed by luck, failing 3 of 40 looped runs.
3. An operator's question about why a mechanism works exposed a defect
that had been present all along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Default" inherits the root session's model, so once the root moved to
Fable every Hands and steward ran on Fable against the skill's own
allocation. The operator noticed before Self did. Self names the tier on
every dispatch from now on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three passes in one day ended their turn on "waiting" with the rule already
in their brief. The rule named the outcome and not the mechanism, and an
agent that has started a detached job reads "wait" as "stop and be woken".
Both briefs now name the loop.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>