Skip to content

Commit 060fbec

Browse files
committed
evidence(stage2): the campaign re-recorded against the fixed harness, 11/11
p022-stage2-1 @ 30a4918, dirty:false — 11/11 caught, 0 survived, 0 compile-error, 0 invalid, 0 runner-error, every expected catcher hit, M00 surviving the unmutated tree. Re-recorded because the previous run measured a census whose ledger key took the host's path separator, and because S11 changed the definition's hash. A result that names a tree nobody would want to check out is not evidence, whatever its counts say. The Windows verdict on the same eleven mutations is a CI gate (stage2-windows-mutations) rather than a second recorded file: one campaign has one recorded provenance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CYLNQy6tLXqsV1CbuNqsSb
1 parent 30a4918 commit 060fbec

2 files changed

Lines changed: 136 additions & 1 deletion

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"schema": 1,
3+
"comment": "Recorded mutation-campaign run (scripts/mutate_campaign.py --run). Raw facts only: outcomes, catchers, provenance. Counts are derived by scripts/render_checkpoint_status.py; regenerate this file by re-running the campaign, never by hand.",
4+
"campaign": "p022-stage2-1",
5+
"definition": "docs/evidence/p022-stage2-1.json",
6+
"definition_sha256": "fd96f37ba882a9b94e7bedf4158574816702ee2023936b7ef27f56656740af3b",
7+
"source_commit": "30a491864b4fcdd37c5ecee26431326821162948",
8+
"dirty": false,
9+
"recorded_at": "2026-09-09T15:07:56Z",
10+
"layers": [
11+
"stage2"
12+
],
13+
"command": "every layer the definition declares, for every mutation",
14+
"control": {
15+
"id": "M00",
16+
"outcome": "survived",
17+
"catchers": [],
18+
"elapsed_seconds": 8.4
19+
},
20+
"mutations": [
21+
{
22+
"id": "S01",
23+
"outcome": "caught",
24+
"catchers": [
25+
"stage2::internal-default-not-rust"
26+
],
27+
"elapsed_seconds": 6.4
28+
},
29+
{
30+
"id": "S02",
31+
"outcome": "caught",
32+
"catchers": [
33+
"stage2::wrong-rust-candidate"
34+
],
35+
"elapsed_seconds": 6.4
36+
},
37+
{
38+
"id": "S03",
39+
"outcome": "caught",
40+
"catchers": [
41+
"stage2::platform-leg-lost"
42+
],
43+
"elapsed_seconds": 6.1
44+
},
45+
{
46+
"id": "S04",
47+
"outcome": "caught",
48+
"catchers": [
49+
"stage2::locator-contract-bypassed"
50+
],
51+
"elapsed_seconds": 5.8
52+
},
53+
{
54+
"id": "S05",
55+
"outcome": "caught",
56+
"catchers": [
57+
"stage2::rust-job-falls-back"
58+
],
59+
"elapsed_seconds": 6.1
60+
},
61+
{
62+
"id": "S06",
63+
"outcome": "caught",
64+
"catchers": [
65+
"stage2::public-default-moved"
66+
],
67+
"elapsed_seconds": 6.7
68+
},
69+
{
70+
"id": "S07",
71+
"outcome": "caught",
72+
"catchers": [
73+
"stage2::public-default-moved"
74+
],
75+
"elapsed_seconds": 7.0
76+
},
77+
{
78+
"id": "S08",
79+
"outcome": "caught",
80+
"catchers": [
81+
"stage2::compare-gate-dropped"
82+
],
83+
"elapsed_seconds": 6.2
84+
},
85+
{
86+
"id": "S09",
87+
"outcome": "caught",
88+
"catchers": [
89+
"stage2::stage2-census"
90+
],
91+
"elapsed_seconds": 6.1
92+
},
93+
{
94+
"id": "S10",
95+
"outcome": "caught",
96+
"catchers": [
97+
"stage2::stage2-census"
98+
],
99+
"elapsed_seconds": 6.3
100+
},
101+
{
102+
"id": "S11",
103+
"outcome": "caught",
104+
"catchers": [
105+
"stage2::internal-default-not-rust"
106+
],
107+
"elapsed_seconds": 6.6
108+
}
109+
]
110+
}

docs/generated/p022-stage2-mutations.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,29 @@ Campaign `p022-stage2-1` — #262 Stage 2 — the CI/dogfood ENGINE-SELECTION se
1010

1111
Definition: `docs/evidence/p022-stage2-1.json` (sha256 `fd96f37ba882a9b9…`, 11 mutations). Replay on a clean tree with `python scripts/mutate_campaign.py --campaign docs/evidence/p022-stage2-1.json --run`; the recorded run is raw outcomes and provenance, the counts below are derived from it.
1212

13-
**No recorded run** is committed (expected at `docs/evidence/p022-stage2-1.result.json`): the campaign has a definition but no evidence. Nothing below is a number.
13+
| measure | value |
14+
|--------------------------------------------------|---|
15+
| recorded at commit | `30a491864b4fcdd37c5ecee26431326821162948` |
16+
| layers run (every one, for every mutation) | `stage2` |
17+
| mutations | 11 |
18+
| caught | 11 |
19+
| survived | 0 |
20+
| compile-error (no evidence either way) | 0 |
21+
| invalid-mutation | 0 |
22+
| runner-error | 0 |
23+
| caught without every expected catcher | none |
24+
| honesty control `M00` (unmutated tree must pass) | survived — as required |
25+
26+
| id | rule | mutation | outcome | caught by |
27+
|---|---|---|---|---|
28+
| S01 | dogfood-selects-rust-explicitly | the dog-food job stops asking for an engine — the most likely regression of all, because the job still runs, still analyses, still uploads, and silently goes back to the public Python default | caught | `stage2::internal-default-not-rust` |
29+
| S02 | dogfood-runs-the-production-core | the dog-food candidate becomes own-shadow-engine — the #260 dev adapter is a Rust binary that this repository really builds, so the substitution looks right and measures a thing that is not the product | caught | `stage2::wrong-rust-candidate` |
30+
| S03 | the-claim-covers-both-platforms | the Rust-default dogfood matrix loses its Windows leg — 'our CI runs on Rust' quietly becomes a statement about Linux, which is the half of the product whose launcher mechanics never broke | caught | `stage2::platform-leg-lost` |
31+
| S04 | the-locator-is-owen-rust-core-alone | the dogfood finds its candidate on PATH — 'the binary is right there, why spell out an absolute path', which is precisely how a stale binary stands in for the one under test | caught | `stage2::locator-contract-bypassed` |
32+
| S05 | a-rust-failure-is-visible | the dogfood run swallows its exit code — the classic 'don't let the dogfood job break the build', which turns every Rust-default claim in this PR into decoration | caught | `stage2::rust-job-falls-back` |
33+
| S06 | the-public-default-does-not-move | the PRODUCT default flips to Rust — the covert Stage 3, and the single edit that would make every internal Rust job pass by accident while changing what every user gets | caught | `stage2::public-default-moved` |
34+
| S07 | the-public-default-does-not-move | the ACTION's public engine input defaults to rust — the same cutover through the other public door, and the one a C#-only control would miss | caught | `stage2::public-default-moved` |
35+
| S08 | compare-evidence-is-not-traded-away | a #260 compare gate is disabled — the cheapest way to make a Rust-default dogfood green is to remove the job that would have disagreed with it | caught | `stage2::compare-gate-dropped` |
36+
| S09 | no-unclassified-call-site | a new bare launcher invocation appears in a job nobody classified — the hole the census exists to close, and the one that reopens every time somebody adds a convenient scan step | caught | `stage2::stage2-census` |
37+
| S10 | no-escape-by-relabelling | the dog-food job is reclassified out of the Rust-default population — every other rule is satisfied by shrinking Class D, so without a rule that reads the job's own name this is a green way to stop dogfooding | caught | `stage2::stage2-census` |
38+
| S11 | dogfood-selects-rust-explicitly | the dog-food's engine selection is COMMENTED OUT rather than deleted — the shape a temporary rollback really takes ('just for one run, we'll put it back'). Its twin S01 deletes the line; this one leaves the words `engine: rust` sitting in the file, which is all a control that greps the job's raw text would need to stay green. It exists because S03 survived for exactly that reason on a different key | caught | `stage2::internal-default-not-rust` |

0 commit comments

Comments
 (0)