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>
This commit is contained in:
Meta Vens 2026-09-16 02:03:14 +02:00
commit 8a598ebbed

View file

@ -3,22 +3,27 @@
# Each entry below is one rule that a surviving test claims to pin, together
# with the exact source change that removes the rule. A mutation counts only if
# the named test fails while the mutation is applied. The script applies one
# mutation at a time, runs that test alone, and restores the file from HEAD
# mutation at a time, runs that test alone, and restores the file from disk
# before the next one, so a failed run never leaves the tree mutated.
#
# pwsh -File tools/eureka-cut4-mutations.ps1
#
# Requires CARGO_TARGET_DIR to be set the way the cut ran it, or it will build
# into the repo-local target/.
#
# M9 and M10 arrived with Soul's Cut 4 findings F2 and F4: the two rules that
# survived the store deletion with no test left standing over them. They pin
# refusals in two different files, so every entry names its own file.
$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent $PSScriptRoot
$file = 'epiphany-core/src/pipeline_documents.rs'
$path = Join-Path $repo $file
$documents = 'epiphany-core/src/pipeline_documents.rs'
$spine = 'epiphany-core/src/runtime_spine.rs'
$mutations = @(
@{
Id = 'M1'
File = $documents
Rule = 'A document decodes back only from an envelope of its own type id.'
Test = 'pipeline_documents::tests::every_pipeline_kind_round_trips_through_named_slot_zero'
Old = '$(if envelope.r#type == <$document as DatabaseEntry>::TYPE {'
@ -26,6 +31,7 @@ $mutations = @(
},
@{
Id = 'M2'
File = $documents
Rule = 'Text bounds are measured in UTF-8 bytes, not characters.'
Test = 'pipeline_documents::tests::bounds_refuse_in_utf8_bytes'
Old = ' move |field: &str, value: &str| bound(field, limit, value.len())'
@ -33,6 +39,7 @@ $mutations = @(
},
@{
Id = 'M3'
File = $documents
Rule = 'An OrgRepo carries exactly one slash.'
Test = 'pipeline_documents::tests::repo_fields_must_be_org_slash_repo'
Old = " if !org.is_empty() && !repo.is_empty() && !repo.contains('/') && value.len() <= 200 =>"
@ -40,6 +47,7 @@ $mutations = @(
},
@{
Id = 'M4'
File = $documents
Rule = 'A write envelope''s key is recomputed from the value and a mismatch refuses.'
Test = 'pipeline_documents::tests::keys_are_derived_and_mismatch_refuses'
Old = ' if envelope.key != expected {'
@ -47,6 +55,7 @@ $mutations = @(
},
@{
Id = 'M5'
File = $documents
Rule = 'A finding label carries no dot, so a composed key segments one way.'
Test = 'pipeline_documents::tests::composed_keys_cannot_collide'
Old = ' label_text("finding.label", &value.label.0)?;'
@ -54,6 +63,7 @@ $mutations = @(
},
@{
Id = 'M6'
File = $documents
Rule = 'A parent local must carry its own kind''s marker character.'
Test = 'pipeline_documents::tests::parent_ids_are_parsed_strictly'
Old = ' let digits = suffix.strip_prefix(marker).ok_or_else(invalid)?;'
@ -61,6 +71,7 @@ $mutations = @(
},
@{
Id = 'M7'
File = $documents
Rule = 'A PipelineRef''s id is parsed as a full id of its declared kind.'
Test = 'pipeline_documents::tests::resolution_subject_is_a_full_id_of_its_kind'
Old = ' pipeline_id(&format!("{at}.id"), &self.id.0, self.kind).map(|_| ())'
@ -68,20 +79,58 @@ $mutations = @(
},
@{
Id = 'M8'
File = $documents
Rule = 'Published schemas are the Rust derivation, byte for byte.'
Test = 'pipeline_documents::tests::pipeline_published_schemas_match_derivation'
Old = ' Short = 200, |field, value| within(200)(field, value);'
New = ' Short = 201, |field, value| within(200)(field, value);'
},
# Soul F2. M1 removes the positive match; this removes the refusal that
# catches everything the positive match misses, leaving a decode that says
# "malformed payload" about an envelope that was never a document at all.
@{
Id = 'M9'
File = $documents
Rule = 'An envelope whose type id belongs to another kind is refused as ForeignStore.'
Test = 'pipeline_documents::tests::decode_refuses_an_envelope_of_a_foreign_type'
Old = 'Err(PipelineRefusal::ForeignStore { r#type: envelope.r#type.clone() })'
New = 'Err(format_error("payload", &envelope.r#type))'
},
# Soul F4. The spine cache refuses a pipeline store by registering its own
# types and nothing else, so the mutation that removes the rule is the one
# that registers the pipeline types into it. The registration function is
# `cfg(test)`, which is exactly the build this mutation compiles under.
@{
Id = 'M10'
File = $spine
Rule = 'The runtime spine cache refuses an epiphany.pipeline.* envelope.'
Test = 'runtime_spine::tests::runtime_spine_cache_refuses_a_pipeline_store'
Old = @'
fn runtime_spine_schema_cache() -> Result<CultCache> {
let mut cache = CultCache::new();
'@
New = @'
fn runtime_spine_schema_cache() -> Result<CultCache> {
let mut cache = CultCache::new();
crate::pipeline_documents::register_pipeline_document_types(&mut cache)?;
'@
}
)
$results = @()
foreach ($mutation in $mutations) {
$path = Join-Path $repo $mutation.File
$original = [IO.File]::ReadAllText($path)
if (-not $original.Contains($mutation.Old)) {
throw "$($mutation.Id): anchor not found in $file. The mutation is stale."
# The anchors here are written with plain newlines and the working tree is
# CRLF, so a multi-line anchor matches nothing unless it is converted to the
# line endings the file actually has. Convert towards the file.
$eol = if ($original.Contains("`r`n")) { "`r`n" } else { "`n" }
$old = ($mutation.Old -replace "`r`n", "`n") -replace "`n", $eol
$new = ($mutation.New -replace "`r`n", "`n") -replace "`n", $eol
if (-not $original.Contains($old)) {
throw "$($mutation.Id): anchor not found in $($mutation.File). The mutation is stale."
}
$mutated = $original.Replace($mutation.Old, $mutation.New)
$mutated = $original.Replace($old, $new)
if ($mutated -eq $original) {
throw "$($mutation.Id): replacement changed nothing."
}