Repository navigation
mutation-ledger: rain.lib.hash went red 33s after #385 merged, on an explicit testsAfterCommit: null and a second producer's tool string #386
Description
rainix-static mutation-ledger (#385) reds rainlanguage/rain.lib.hash on its next push, on a record that #385's own QA sweep never saw. Both rejections land on shapes the gate's stated contract arguably permits, so this is a question about the contract rather than a bug report with an obvious fix.
How it got past the sweep
| rainix#385 merged | 2026-09-16T12:02:46Z |
| rainlanguage/rain.lib.hash#115 merged | 2026-09-16T12:03:19Z |
33 seconds. #115 appends a second record to audit/mutation-test-scans.json, and its CI resolved rainix-sol.yaml@main before the gate existed, so it merged green into a state the gate now rejects. #385's sweep over all 14 ledger repos was correct when it ran and stale immediately after. Nothing was done wrong here — it is just the unavoidable race when a gate lands next to in-flight work, worth recording because it means "the org is clean except for two repos" is no longer the standing state.
Against rain.lib.hash at eaa6025:
ERROR: audit/mutation-test-scans.json[1].tool is "mutation-probe (adversarial-mutation-test 4e59ec0c)",
expected the string "adversarial-mutation-test" — the audit skill's gate reads this field to recognise
its own evidence
ERROR: audit/mutation-test-scans.json[1].testsAfterCommit is null — expected a 40-character
lowercase-hex git commit
Question A — is an explicit null an absence or a malformed value?
The module doc says testsAfterCommit is "validated WHEN PRESENT and never required", and check_sha_field carries "Absent is legal, malformed is not". In code that split is None vs Some(_), so a key written as null takes the malformed branch.
The record writes "testsAfterCommit": null to mean the run added no tests — and its own summary agrees: testsBefore: 77, testsAfter: 77, "No test added." So null is being used for precisely the case the field cannot carry a value for, which reads as an absence spelled out rather than a malformed SHA.
Two things make this worth deciding rather than patching either way:
- The corpus already uses
nullas "not applicable".ST0x-Technology/st0x.oraclewrites"publishedTag": nulland"commitsAheadOfTag": nullon all three of its records. Those pass today only because the gate does not check those fields — not because the idiom was considered and accepted. - Today's behaviour forces a historical record to be edited to go green, which is the outcome Validate the hand-appended mutation ledger in shared CI #385 explicitly argued against for this field: "the value cannot be recovered for a run that is over, so requiring it would only invite an invented SHA." Requiring the key be deleted is milder than inviting an invented SHA, but it is the same shape of pressure.
The counter-reading is real too: a written null is a written value, and "omit the key or give a SHA" is a tighter contract than "three ways to say nothing". Either resolution is defensible; both are a call for whoever owns the contract.
Question B — the tool literal now has a second producer
Record [1] declares "tool": "mutation-probe (adversarial-mutation-test 4e59ec0c)", and its note says "Mutation probe only, no adversarial pass." The record is being honest about being a different kind of run.
The consequence worth surfacing: if the gate's own error message is accurate — "the audit skill's gate reads this field to recognise its own evidence" — then that record is already invisible to the audit skill's Pass-2 gate, and was before #385 existed. The gate did not create this; it made a pre-existing mismatch visible. That is the gate working, but it means the fix is not "get CI green".
The options are not equivalent, and picking among them is a judgement about what this ledger is for:
- teach the gate a documented set of producers, or a
mutation-probe (...)form; - rewrite the record's
toolto the bare literal — cheapest and, I think, the worst: it makes a probe-only run claim to be an adversarial run, which is the exact class of overclaim the ledger exists to prevent; - give probe-only runs their own file, leaving this one to mean adversarial runs only;
- drop record
[1], since by the message's own logic no consumer can currently read it.
What I am not doing
Not loosening the gate to clear CI, and not editing rain.lib.hash's record. Either could be right; both change what the ledger means, and #385 landed less than an hour ago on the principle that this file is evidence rather than a thing you make green. Flagging and stopping.
For the record, the other two known reds are moving: rain.solmem is fixed and merged (rainlanguage/rain.solmem#155, verified by the gate itself in that repo's CI), and ST0x-Technology/st0x.oracle has ST0x-Technology/st0x.oracle#341 open, where the recorded SHA resolves to nothing and the replacement is deliberately left to a maintainer.
Reproduction
git clone --filter=tree:0 https://github.com/rainlanguage/rain.lib.hash.git
cd rain.lib.hash && rainix-static mutation-ledger --root .🤖 Generated with Claude Code
Activity