fix(849): round 3 — replace every unknown state, and prove the clauses that claim to

Two more cold reviews — cross-family (Codex/GPT-5.6) and a cold Claude reviewer that ran
the mutants itself — converged on two separate things: a remaining class of paths that
still left an unknown state standing, and, more importantly, that several clauses this
branch claimed as fixes SURVIVED mutation of the exact text they name.

## Behaviour

1. The reconciliation witness matches the CURRENT row's `id`, not merely a row with the
   sentinel's description. Description alone is satisfied by an OLDER identical sentinel —
   which is what a fixed point produces — so a read carrying only the earlier row cleared
   the sentinel while the verdict buried under the current one ended up below the fresh
   mark. Falls back to the description where the server omits `id`.
2. The two OBSERVED-mutation arms mark a head that carries a row this run declined, instead
   of only abstaining. They are still right not to post their CLASSIFICATION — computed
   against a base or head the PR may no longer have — but a declined row must not stay
   authoritative for the whole window until a successor finishes, and for a PR's FIRST push
   no successor is queued at all. Scoped to `pre_state` being non-empty, so the common path
   stays quiet.
3. `replace_unknown_state` RETURNS a status. Its first version ended the failure arm with a
   successful `echo`, so it reported 0 after both POSTs failed and the fence caller's
   `exit 0` reported an abstention that had not happened.
4. An `id` difference counts only when BOTH reads supplied one. A response that omits `id`
   beside one that includes it otherwise reads as a replacement, and this guard's reaction
   is to abstain — over a row the classification had already declined.
5. Every element and every consumed field of the combined response is type-checked before
   extraction, and a schema failure routes to the replacement. `.statuses` being an array
   was checked; its ELEMENTS were not, so one scalar made `select(.context == $c)`
   hard-error and `set -e` took the step down before any path could mark the head.
6. The path-predicate failure replaces rather than merely exiting, for the same reason.
7. `$UNVERIFIED_DESC` says "Status write", not "Exemption write". It is now written on paths
   that grant no exemption at all, and it is the operator-facing text of a required check.
8. The no-op-repair skip keeps the human `::error::`. Skipping the WRITE is right — the head
   already carries the strongest marker — but that message is the only place a reviewer is
   told their verdict was buried. `raced_why` is a sentence now, not the token `human`.

## Proof

The cold reviewer measured three of the six round-2 claims surviving mutation of their own
clause, one against the verbatim predecessor from the previous commit. Nine proofs added:
the no-mark downgrade's SCOPE (not just the description it writes), the page-2 refusals, the
untrusted-fence write, the row-`id` comparison, the repair floor, the no-op skip, both `$own`
exclusions, the write-result return, and the both-ids-present rule.

Two of those needed the test double to grow: the combined-status stub emitted no `id` at
all, so the `ex_id` clause had never once run with a non-empty value; and POSTs always
succeeded, so both write helpers' failure arms were unreachable.

The `$own` exclusions and the no-op skip are OUTCOME-redundant — mutating either alone leaves
the post sequence unchanged, which is how duplicate guards hide each other. Their proofs
assert the LOG, because what the exclusions alone decide is whether the job reports a race
against its own row. One clause is left deliberately unproven and named as such in the record
and the guard inventory rather than counted: the path-predicate failure branch has no fixture
that can reach it.

## Also

Round 2 left two comment paragraphs duplicated verbatim and a block header narrower than its
block; both fixed. Stale prose corrected in the workflow ("dies WITHOUT posting", "post-write
verification never runs for it", "this block only runs after a `success`"), `docs/ci-cd.md`
("the fence never re-counts", "the history is read twice" — it is three now),
`ci.exemption-provenance` and `docs/guard-inventory.md`.

refs #849
Decisions-Edit: yes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T79beF1Ufid3dXju4yqkF
This commit is contained in:
2026-08-30 05:13:28 +02:00
co-authored by Claude Opus 5
parent 957a328f33
commit 168fe21088
7 changed files with 761 additions and 85 deletions
+610 -31
View File
@@ -956,6 +956,12 @@ url = [a for a in args if a.startswith("http")][-1]
out = pathlib.Path(os.environ["STUB_DIR"])
if "-X" in args and args[args.index("-X") + 1] == "POST":
if os.environ.get("STUB_POST_FAILS") == "1":
# EVERY POST FAILS, retries included. `gh` is `curl -sf`, so an HTTP error is exit 22 with
# empty stdout. Without this the write helpers' failure arms are unreachable — and one of
# them reported SUCCESS after both attempts failed, which is what let a caller exit 0
# believing the head had been marked.
sys.exit(22)
payload = args[args.index("-d") + 1]
(out / "posted.json").write_text(payload)
(out / "posted_url.txt").write_text(url)
@@ -1406,6 +1412,28 @@ if "/statuses/" in url:
n_logical = int(logical.read_text()) if logical.exists() else 1
pages = json.loads(snapshot.read_text()) if snapshot.exists() else [[]]
if hist_page > 1 and mode == "reconcile-page2-error" and n_logical == 1:
# PAGE 2 FAILS ON THE FIRST LOGICAL READ — the reconciliation walk — while page 1, which
# carries the seeded sentinel, is served. That separates the two halves of the trust
# condition: `ph_ok` is `no` and `witness` is 1, so a mutant that drops only the completeness
# operand still clears, and a mutant that drops only the witness operand does not. Against a
# fixture where BOTH are false, `if false` disarms two guards at once and isolates neither.
sys.exit(22)
if hist_page == 1 and mode == "postwrite-page1-error":
# FAILS THE POST-WRITE WALK ONLY (ersatztv#849 round 2). `premark-page1-error` fails the
# FIRST logical read, which is the mark; the repair floor needs a run that got its mark, made
# its write, and THEN could not read the history back. Both attempts of the second logical
# read fail, so the retry cannot rescue it.
# THE THRESHOLD IS DERIVED FROM WHERE THE REQUESTS FALL, not picked: `page_statuses` asks
# for page 1 exactly once per successful walk, so the mark's walk is request 0 and the
# post-write walk is requests 1 and 2 (the second being its retry). `>= 1` therefore lets the
# mark be established and fails the post-write read outright, which is the arrangement the
# repair floor needs and the one `premark-page1-error` cannot produce.
ctr = out / "p1_attempts_pw.txt"
seen = int(ctr.read_text()) if ctr.exists() else 0
ctr.write_text(str(seen + 1))
if seen >= 1:
sys.exit(22)
if hist_page == 1 and mode == "premark-page1-error":
# PAGE 1 ITSELF FAILS, for the whole first logical read (both attempts), so the walk returns
# nothing at all and the mark must be abandoned. Counted in its own file because the logical
@@ -1543,6 +1571,52 @@ if "/status" in url:
]
print(json.dumps({"state": "pending", "total_count": len(mid_rows), "statuses": mid_rows}))
sys.exit(0)
if mode == "malformed-row-beside-verdict":
# A SCALAR IN `.statuses` BESIDE A REAL ROW. `.statuses` is an array and `total_count` agrees,
# so the response passes the shape gate; it is the ELEMENTS that cannot be read. An untyped
# `select(.context == $c)` hard-errors on the scalar, jq exits 5, and under `set -e` the
# assignment takes the whole step down — before any path that could mark the head, while the
# `success` below stays authoritative.
rows = [7, {"context": "review-verdict/h10", "status": "success",
"creator": {"login": "mallory"},
"description": "Review-verdict: MERGEABLE @ a9e3e23 (base: main)"}]
print(json.dumps({"state": "success", "total_count": len(rows), "statuses": rows}))
sys.exit(0)
if mode == "id-appears-on-second-read":
# THE SAME ROW, reported once WITHOUT `id` and once WITH it. Nothing else about it moves. An
# id comparison that does not require both sides to be present reads this as a replacement
# and makes the run abstain — over a row it had already declined to inherit.
ctr = out / "status_reads.txt"
n = int(ctr.read_text()) if ctr.exists() else 0
ctr.write_text(str(n + 1))
row = {"context": "review-verdict/h10", "status": "success",
"creator": {"login": "mallory"},
"description": "Review-verdict: MERGEABLE @ a9e3e23 (base: main)"}
if n > 0:
row = dict(row, id=77)
rows = [{"context": "ci/decoy", "status": "pending", "creator": None,
"description": "unrelated"}, row]
print(json.dumps({"state": "pending", "total_count": len(rows), "statuses": rows}))
sys.exit(0)
if mode == "sentinel-replaced-mid-run":
# THE SAME SENTINEL TEXT AT TWO DIFFERENT IDS (ersatztv#849 round 2). Both reads return an
# unverified sentinel whose description is byte-identical — which is what a fixed point IS —
# so only the row id distinguishes "the row I snapshotted" from "a row another run wrote
# while I classified". Ids are carried here and nowhere else in this stub because this is the
# only fixture whose outcome turns on them.
ctr = out / "status_reads.txt"
n = int(ctr.read_text()) if ctr.exists() else 0
ctr.write_text(str(n + 1))
rows = [
{"context": "ci/decoy", "status": "pending", "creator": None, "description": "unrelated"},
{"id": 100 if n == 0 else 200, "context": "review-verdict/h10", "status": "pending",
"creator": None,
# FROM THE SHIPPED BODY, never a copy: the sentinel is a fixed point, so a stub carrying
# its own spelling would keep passing after the workflow reworded its own.
"description": os.environ["STUB_UNVERIFIED_DESC"]},
]
print(json.dumps({"state": "pending", "total_count": len(rows), "statuses": rows}))
sys.exit(0)
if mode.startswith("sentinel-appears-on-read:"):
# A repair sentinel written by ANOTHER, overlapping run between this job's first read and its
# last-moment re-read (ersatztv#706 round 3). Creator is null: the sentinel is machine-written.
@@ -1635,6 +1709,7 @@ def _run_classify(
timeline_terminator: str = "null",
status_empty_shape: str = "null",
mutate: tuple[str, str] | None = None,
post_fails: bool = False,
):
"""Execute the workflow's classify `run:` block with a stubbed enumeration script.
@@ -1674,6 +1749,8 @@ def _run_classify(
env["STUB_HISTORY_MODE"] = history_mode
env["STUB_HISTORY_CREATOR"] = history_creator
env["STUB_HISTORY_EXTRA"] = json.dumps(history_extra) if history_extra else ""
env["STUB_UNVERIFIED_DESC"] = UNVERIFIED_DESC
env["STUB_POST_FAILS"] = "1" if post_fails else ""
env["STUB_MIDRUN_CREATOR"] = midrun_creator
env["STUB_PRE_ROW"] = pre_row
env["STUB_MIDRUN_ROW"] = midrun_row
@@ -1755,25 +1832,35 @@ def _run_classify(
DOCS_ONLY = 'printf "docs/a.md\\ndocs/b.md\\n"\n'
def _assert_withheld(posted, r, why):
def _assert_withheld(tmp_path, r, why, expect_rc):
"""The gate refused to grant the exemption — which since ersatztv#849 means the head is MARKED,
not left alone.
Every one of these tests asserted `posted is None`. That was right while "withhold" meant
"write nothing", and it became a fail-open assertion when it stopped meaning that: declining to
write protects a REAL verdict on the head and leaves a FORGED one, and this job's own red status
is not a required check, so branch protection still sees whatever was already there.
Every one of these tests asserted `posted is None`. That was right while "withhold" meant "write
nothing", and it became a fail-open assertion when it stopped meaning that: declining to write
protects a REAL verdict on the head and leaves a FORGED one, and this job's own red status is not
a required check, so branch protection still sees whatever was already there.
Asserted positively state AND description because `pending` alone does not distinguish the
sticky sentinel from the generic awaiting-verdict text a later run happily re-derives.
THE WHOLE POST SEQUENCE, not the last write. Inspecting only the final status would accept a job
that posted `success` and then repaired it and the green interval IS part of the threat model
here, since branch protection and an already-scheduled auto-merge can both observe it. Exactly
one POST, and it is the sentinel.
THE EXIT CODE IS PART OF THE CONTRACT and differs by path, so each caller states its own rather
than inheriting a default: the two read refusals were already non-zero exits before this change
and stay red, while the fence branch abstains cleanly. A single default would let one path's
regression hide behind the other's expectation.
"""
assert posted is not None, (
f"{why}: nothing was posted, so whatever {'{CONTEXT}'} this head already carries is standing "
f"unread and unre-derived.\n{r.stdout[-900:]}"
seq = _posted_sequence(tmp_path)
assert len(seq) == 1, (
f"{why}: expected exactly one status write — the sentinel — got {seq}. Nothing posted at all "
f"means whatever this head already carries is standing unread; more than one means a green "
f"was published and taken back.\n{r.stdout[-900:]}"
)
assert posted["state"] == "pending" and posted["description"] == UNVERIFIED_DESC, (
f"{why}: expected the unverified-write sentinel, got {posted}\n{r.stdout[-900:]}"
assert seq[0]["state"] == "pending" and seq[0]["description"] == UNVERIFIED_DESC, (
f"{why}: expected the unverified-write sentinel, got {seq[0]}\n{r.stdout[-900:]}"
)
assert r.returncode == expect_rc, f"{why}: expected exit {expect_rc}, got {r.returncode}\n{r.stdout[-900:]}"
def test_a_FAILING_enumeration_withholds_the_exemption_even_when_stdout_looks_docs_only(tmp_path):
@@ -3249,7 +3336,9 @@ def test_the_head_arm_DEFERS_to_the_trust_guard_when_the_second_count_fails(tmp_
timeline_mode="trusted-then-unreadable",
push_mode="stable:3",
)
_assert_withheld(posted, r, "test_the_head_arm_DEFERS_to_the_trust_guard_when_the_second_count_fails")
_assert_withheld(
tmp_path, r, "test_the_head_arm_DEFERS_to_the_trust_guard_when_the_second_count_fails", expect_rc=0
)
assert "Could not establish a trusted retarget/push count" in r.stdout, (
f"the untrusted re-count was not reported as such. Log:\n{r.stdout[-900:]}"
)
@@ -3387,7 +3476,7 @@ def test_an_EMPTY_FIRST_page_is_untrusted_WHICHEVER_empty_shape_it_is(tmp_path,
timeline_mode="empty-first-page",
timeline_terminator=terminator,
)
_assert_withheld(posted, r, "test_an_EMPTY_FIRST_page_is_untrusted_WHICHEVER_empty_shape_it_is")
_assert_withheld(tmp_path, r, "test_an_EMPTY_FIRST_page_is_untrusted_WHICHEVER_empty_shape_it_is", expect_rc=0)
def test_a_timeline_row_with_NO_READABLE_TYPE_is_not_counted_as_nothing(tmp_path):
@@ -3399,7 +3488,7 @@ def test_a_timeline_row_with_NO_READABLE_TYPE_is_not_counted_as_nothing(tmp_path
IT extracts, which the walk gating the write did not have.
"""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), timeline_mode="untyped-rows")
_assert_withheld(posted, r, "test_a_timeline_row_with_NO_READABLE_TYPE_is_not_counted_as_nothing")
_assert_withheld(tmp_path, r, "test_a_timeline_row_with_NO_READABLE_TYPE_is_not_counted_as_nothing", expect_rc=0)
def test_the_head_fence_reports_the_OBSERVED_counts_in_its_notice(tmp_path):
@@ -3456,7 +3545,7 @@ def test_an_UNTRUSTED_retarget_count_withholds_the_EXEMPTION(tmp_path, mode):
"""A `success` that cannot be shown to describe the PR's current base must not be written. An
absent required check blocks the merge, which is the safe direction."""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), timeline_mode=mode)
_assert_withheld(posted, r, "test_an_UNTRUSTED_retarget_count_withholds_the_EXEMPTION")
_assert_withheld(tmp_path, r, "test_an_UNTRUSTED_retarget_count_withholds_the_EXEMPTION", expect_rc=0)
@pytest.mark.parametrize("mode", ["unreadable", "transport-error"])
@@ -3665,7 +3754,7 @@ def test_an_UNTRUSTED_count_withholds_the_exemption_BY_THAT_BRANCH(tmp_path):
"""Round 2 test-gap: the existing untrusted-count test asserted only "posted nothing", which a
crash also produces. Assert the discriminator and a clean exit."""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), timeline_mode="unreadable")
_assert_withheld(posted, r, "test_an_UNTRUSTED_count_withholds_the_exemption_BY_THAT_BRANCH")
_assert_withheld(tmp_path, r, "test_an_UNTRUSTED_count_withholds_the_exemption_BY_THAT_BRANCH", expect_rc=0)
assert r.returncode == 0, f"the job died rather than declining cleanly: {r.stderr[-800:]}"
# The wording covers BOTH axes since ersatztv#803 — one walk certifies one trust flag, so an
# unreadable page abandons the push count and the retarget count together.
@@ -4349,7 +4438,7 @@ def test_a_terminator_on_PAGE_ONE_does_not_certify_a_zero_retarget_count(tmp_pat
"""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), timeline_mode="empty-first-page")
assert r.returncode == 0, r.stderr
_assert_withheld(posted, r, "test_a_terminator_on_PAGE_ONE_does_not_certify_a_zero_retarget_count")
_assert_withheld(tmp_path, r, "test_a_terminator_on_PAGE_ONE_does_not_certify_a_zero_retarget_count", expect_rc=0)
assert "trusted=no" in r.stdout, (
f"the exemption was withheld, but not because the count was untrusted:\n{r.stdout[-800:]}"
)
@@ -4373,7 +4462,9 @@ def test_a_SECOND_PAGE_of_statuses_refuses_to_conclude_that_no_verdict_exists(tm
the list is longer than one page and the verdict may be beyond it.
"""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), status_mode="twopage")
_assert_withheld(posted, r, "test_a_SECOND_PAGE_of_statuses_refuses_to_conclude_that_no_verdict_exists")
_assert_withheld(
tmp_path, r, "test_a_SECOND_PAGE_of_statuses_refuses_to_conclude_that_no_verdict_exists", expect_rc=1
)
assert "page 2" in (r.stdout + r.stderr).lower(), (
f"nothing was posted, but not because of the page-2 completeness probe:\n{r.stdout[-800:]}"
)
@@ -4426,7 +4517,7 @@ def test_an_UNREADABLE_page_2_refuses_to_conclude_that_no_verdict_exists(tmp_pat
that cannot be read justifies nothing.
"""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), status_mode=mode)
_assert_withheld(posted, r, "test_an_UNREADABLE_page_2_refuses_to_conclude_that_no_verdict_exists")
_assert_withheld(tmp_path, r, "test_an_UNREADABLE_page_2_refuses_to_conclude_that_no_verdict_exists", expect_rc=1)
assert "page 2" in (r.stdout + r.stderr).lower(), (
f"nothing was posted, but not because of the page-2 probe:\n{r.stdout[-800:]}"
)
@@ -4951,6 +5042,17 @@ def test_an_UNREADABLE_history_page_2_also_repairs_rather_than_leaving_green(tmp
# text verbatim.
# * `test_MUTATION_a_GENERIC_pending_...` restores the shape #742 attempted and WITHDREW, not
# `main` — which had no downgrade at all.
# * `test_MUTATION_restoring_the_SUCCESS_only_no_mark_downgrade_...` restores the predicate this
# branch itself shipped one commit earlier, which is where a cold review found it surviving the
# whole suite: the nearest existing proof mutated the DESCRIPTION the downgrade writes, not its
# SCOPE, and its fixture ran a succeeding enumeration, so `state=success` there and the
# `success`-only predecessor fired identically.
#
# TWO GUARDS HERE ARE OUTCOME-REDUNDANT AND WOULD OTHERWISE HIDE EACH OTHER: the `$own` exclusions
# and the no-op-repair skip both suppress the same duplicate POST, so mutating either alone leaves
# the post sequence unchanged. What the exclusions alone decide is the REPORT — without them a run
# counts its own row and tells a reviewer their verdict was overwritten when nothing raced it — so
# their proofs assert the LOG. That is the honest discriminator, not a weaker one.
# * the `if false` mutants disarm clauses that have NO predecessor, because the blocks they gate
# are new. Disarming the condition isolates the DECISION from the round-trip beside it, which
# deleting the block would not; they are counterfactual mutants and are sound as such.
@@ -5238,25 +5340,30 @@ def test_MUTATION_reconciling_on_an_UNREADABLE_history_re_exempts_a_head(tmp_pat
pending. Treating it as "nothing buried" is the fail-open, and it is a tempting simplification
because the happy path looks identical.
The mutant disarms the trust test. BE EXACT ABOUT WHAT THE FIXTURE SHOWS, because an earlier
version of this docstring claimed it "makes the reconciliation walk fail while a verdict IS
present in the history" — `premark-page1-error` serves ordinary rows and no verdict, so that was
a description of a different fixture. What is shown here is narrower and sufficient: on a walk
that could not be read, the shipped code carries the sentinel forward and the mutant clears it
and grants the exemption. That the cleared sentinel can be sitting on a real verdict is shown by
THE MUTATION ISOLATES THE COMPLETENESS OPERAND, which needs a fixture where the OTHER operand
is satisfied. `reconcile-page2-error` serves page 1 carrying the seeded sentinel, so the
witness is 1 and fails page 2 of the reconciliation walk, so `ph_ok` is `no`. Dropping the
completeness operand therefore clears the sentinel over a list the job knows it did not finish
reading, which is where a buried verdict would be. An earlier version used a fixture with BOTH
operands false and mutated the whole condition to `if false`, which disarms two guards at once
and isolates neither.
That the cleared sentinel can be sitting on a real verdict is shown by
`test_the_reconciliation_UPGRADES_to_the_repair_sentinel_when_a_verdict_is_BURIED`, which is the
test that supplies one.
"""
seed = [_sentinel_row()]
posted, r = _run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=UNVERIFIED_DESC,
history_mode="premark-page1-error",
history_mode="reconcile-page2-error",
history_extra=seed,
)
assert posted is not None and posted["description"] == UNVERIFIED_DESC, (
f"an unreadable reconciliation did not carry the sentinel forward: {posted}\n{r.stdout[-900:]}"
f"an incomplete reconciliation did not carry the sentinel forward: {posted}\n{r.stdout[-900:]}"
)
mutant, rm = _run_classify(
@@ -5265,10 +5372,11 @@ def test_MUTATION_reconciling_on_an_UNREADABLE_history_re_exempts_a_head(tmp_pat
status_mode="existing:pending",
status_creator=None,
status_desc=UNVERIFIED_DESC,
history_mode="premark-page1-error",
history_mode="reconcile-page2-error",
history_extra=seed,
mutate=(
'\n if [ "$ph_ok" != yes ] || [ "$witness" -eq 0 ]; then\n',
"\n if false; then\n",
'[ "$ph_ok" != yes ] || [ "$witness" -eq 0 ]; then',
'[ "$witness" -eq 0 ]; then',
),
)
assert mutant is not None and mutant["state"] == "success", (
@@ -5498,6 +5606,477 @@ def test_MUTATION_declining_to_replace_an_unreadable_combined_read_leaves_the_fo
)
def test_MUTATION_a_page_2_refusal_that_only_EXITS_leaves_the_head_unmarked(tmp_path):
"""The page-2 completeness probe refuses AND replaces (ersatztv#849 round 2).
It was excluded from the replacement on the reasoning that the probe fires when NO row for this
context was on page 1, so there is no green of any provenance to leave standing. That is
self-contradictory: the only reason page 2 is read is that the row MAY be beyond page 1, which
the probe's own message says.
`twopage` is a head whose status list runs past one page with no `h10` on either the shape that
makes "no verdict exists" unestablishable.
"""
posted, r = _run_classify(tmp_path / "fixed", _emitting("docs/a.md"), status_mode="twopage")
assert posted is not None and posted["description"] == UNVERIFIED_DESC, (
f"a page-2 refusal left the head unmarked: {posted}\n{r.stdout[-900:]}"
)
mutant, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="twopage",
mutate=(
'replace_unknown_and_die "${CONTEXT} was not on page 1 of the statuses for ${SHA:0:7},'
" but page 2 carries ${more_len} more row(s) — the list is longer than one page, so a"
' verdict of ANY provenance may be sitting beyond it where this job cannot read it." ;;',
"exit 1 ;;",
),
)
assert mutant is None, (
"the mutant still posted, so this fixture does not reach the replacement through the page-2 "
f"refusal and the assertion above proves nothing about it: {mutant}\n{rm.stdout[-900:]}"
)
def test_MUTATION_an_untrusted_fence_that_only_ABSTAINS_leaves_the_declined_row_current(tmp_path):
"""The untrusted-fence branch writes rather than abstains (ersatztv#849 round 2).
It is reached only AFTER the classification declined to inherit whatever `h10` the head carries
that is why it is re-deriving so posting nothing leaves the declined row current, and no
retarget or push need have occurred, so no successor run is guaranteed. Its message used to say
the context "stays absent", which is true only of a head that had none.
"""
posted, r = _run_classify(tmp_path / "fixed", _emitting("docs/a.md"), timeline_mode="unreadable")
assert posted is not None and posted["description"] == UNVERIFIED_DESC, (
f"an untrusted fence left the head unmarked: {posted}\n{r.stdout[-900:]}"
)
mutant, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
timeline_mode="unreadable",
mutate=(
'replace_unknown_state "Could not establish a trusted retarget/push count for PR #${PR}'
" (before=${retargets_before_ok}, after=${rt_ok}), so an exemption 'success' cannot be"
" shown to have been computed against the PR's current base, nor at a single head. NOTE a"
" later run only helps if the cause was transient — a PR whose timeline exceeds the page"
' cap will fail this way on every run, and needs a human verdict."',
":",
),
)
assert mutant is None, (
f"the mutant still posted, so the replacement does not come from that branch: {mutant}\n{rm.stdout[-900:]}"
)
def test_MUTATION_dropping_the_ROW_ID_from_the_mid_run_comparison_overwrites_another_runs_sentinel(
tmp_path,
):
"""Two sentinels are byte-identical by design, so only the row id can tell them apart.
This run reconciles a pre-existing sentinel away which is the case the guard must NOT fire on,
and the reason it cannot simply abstain whenever a sentinel is present and then classifies
docs-only. Between the two reads another run replaces that sentinel with its own. The
description is unchanged, so the state/creator/description triple sees nothing; the id moved.
Without the id clause the run posts its exemption over a sentinel another run had just written,
which is a marker that something on this head is unchecked being replaced by a status a later run
re-derives.
"""
# THE SEEDED SENTINEL CARRIES THE ID THE COMBINED READ REPORTS AT THE FIRST READ (100), because
# the reconciliation witness now matches that id rather than the description. A seed with an
# unrelated id would make this run carry the sentinel forward instead of reconciling it, and the
# guard under test would never be reached.
seed = [
{"id": 3900, "context": "ci/other", "status": "success", "creator": None, "description": "unrelated"},
_sentinel_row(100),
]
posted, r = _run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="sentinel-replaced-mid-run",
history_extra=seed,
)
assert posted is None, (
f"a sentinel written by another run mid-classification was overwritten: {posted}\n{r.stdout[-900:]}"
)
assert "written on" in (r.stdout + r.stderr), f"abstained, but silently:\n{r.stdout[-900:]}"
mutant, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="sentinel-replaced-mid-run",
history_extra=seed,
mutate=(
'if [ -n "$ex_id" ] && [ -n "$pre_id" ] && [ "$ex_id" != "$pre_id" ]; then',
"if false; then",
),
)
assert mutant is not None and mutant["state"] == "success", (
"the mutant did not overwrite the sentinel, so the id clause is not what stops it and the "
f"assertion above proves nothing about it: {mutant}\n{rm.stdout[-900:]}"
)
def test_MUTATION_removing_the_repair_FLOOR_downgrades_the_repair_sentinel(tmp_path):
"""The repair may never write a description weaker than the one this run decided.
Widening the post-write gate to every write means the block now also runs after a carry-forward
write of `$REPAIR_DESC`. One transient post-write read then rewrote that head with the strictly
weaker, machine-clearable sentinel reversing the ordering the classification chain states, and
depending on a later reconciliation to put it back.
"""
posted, r = _run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
history_mode="postwrite-page1-error",
)
seq = _posted_sequence(tmp_path / "fixed")
assert seq and seq[-1]["description"] == REPAIR_DESC, (
f"the repair sentinel was downgraded on an unreadable post-write read: {seq}\n{r.stdout[-900:]}"
)
_run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
history_mode="postwrite-page1-error",
mutate=(' if [ "$desc" = "$REPAIR_DESC" ]; then repair_desc="$REPAIR_DESC"; fi', " :"),
)
mseq = _posted_sequence(tmp_path / "mutant")
assert mseq and mseq[-1]["description"] == UNVERIFIED_DESC, (
"the mutant did not downgrade, so the floor is not what preserves the repair sentinel and "
f"the assertion above proves nothing about it: {mseq}"
)
def test_an_OBSERVED_retarget_MARKS_a_row_this_run_declined(tmp_path):
"""Abstaining is a handoff only when there is nothing to hand off (ersatztv#849 round 3).
The arm is right not to post its CLASSIFICATION computed against a base the PR may no longer
target but when the head already carries a row this run DECLINED to inherit, posting nothing
leaves that row authoritative for the whole window until the successor finishes. And in the case
the head-arm's own message names, a PR's FIRST push, no successor is queued at all.
Here `mallory` is off `$H10_REVIEWERS`, so the existing `success` is declined rather than
inherited, and `moves:0,1` retargets the PR mid-classification.
"""
posted, r = _run_classify(
tmp_path,
_emitting("docs/a.md"),
status_mode="existing:success",
status_creator="mallory",
timeline_mode="moves:0,1",
)
assert posted is not None and posted["description"] == UNVERIFIED_DESC, (
"a declined `success` was left authoritative while this run abstained on an observed "
f"retarget: {posted}\n{r.stdout[-900:]}"
)
assert "was retargeted while this job was classifying" in (r.stdout + r.stderr), (
f"marked, but not by the retarget arm:\n{r.stdout[-900:]}"
)
def test_positive_control_an_OBSERVED_retarget_on_an_UNMARKED_head_still_posts_nothing(tmp_path):
"""The scoping, asserted rather than assumed.
On a head that carries nothing there is nothing to leave standing, so the arm must stay silent
a write there would be noise on the commonest path in this job, and it is also what
`test_a_RETARGET_DURING_the_run_posts_NOTHING` above depends on.
"""
posted, r = _run_classify(tmp_path, _emitting("docs/a.md"), timeline_mode="moves:0,1")
assert posted is None, f"an empty head was marked on an observed retarget: {posted}"
def test_MUTATION_not_marking_the_declined_row_leaves_it_authoritative(tmp_path):
"""Disarming the scope test, which is the whole decision this helper makes."""
_run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="existing:success",
status_creator="mallory",
timeline_mode="moves:0,1",
mutate=('if [ -z "$pre_state" ]; then return 0; fi', "return 0"),
)
seq = _posted_sequence(tmp_path / "mutant")
assert seq == [], (
"the mutant still marked the head, so the scope test is not what produces the write and the "
f"test above proves nothing about it: {seq}"
)
def test_a_MALFORMED_combined_ROW_does_not_kill_the_read_before_anything_can_mark_the_head(tmp_path):
"""`.statuses` being an array was checked; its ELEMENTS were not.
A scalar beside a real row makes an untyped `select(.context == $c)` hard-error, jq exits 5, and
under `set -euo pipefail` the unguarded assignment takes the step down before any of the paths
that replace an unknown state, and with an off-list `success` still authoritative on the head.
The job goes red, but its own status is not a required check.
The type-safe filter drops the unreadable element and judges what is left, so the real row is
still found, still declined (`mallory` is off the allow-list), and still re-derived.
"""
posted, r = _run_classify(tmp_path / "fixed", _emitting("docs/a.md"), status_mode="malformed-row-beside-verdict")
assert posted is not None, f"a malformed neighbour row stopped the job writing anything: {r.stdout[-900:]}"
assert posted["state"] == "success" and posted["description"].startswith("Exempt:"), (
f"expected the off-list row to be re-derived into the docs-only exemption; got {posted}"
)
mutant, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="malformed-row-beside-verdict",
mutate=(
'\'[(.statuses // [])[] | select(type == "object") | select(.context? == $c)] | first // {}\') || row=""',
"'[(.statuses // [])[] | select(.context == $c)] | first // {}')",
),
)
assert mutant is None and rm.returncode != 0, (
"the mutant did not die on the malformed row, so the type test is not what keeps this read "
f"alive: {mutant} rc={rm.returncode}"
)
def test_a_generic_PENDING_with_no_mark_also_becomes_the_sentinel(tmp_path):
"""The no-mark downgrade covers every re-derivable write, not only the exemption.
The damaging PR is one that IS exemptible and got the generic `pending` only from a transient
enumeration failure. Its description carries no marker, the post-write check does not run without
a mark, so a verdict landing in the write window is buried and the NEXT run re-derives that
`pending` into the exemption with the human row below its own mark.
The fixture is that PR: the enumerator exits 1 (generic `pending`) AND the status history cannot
be read (no mark).
"""
posted, r = _run_classify(
tmp_path,
"#!/usr/bin/env bash\n" + DOCS_ONLY + "exit 1\n",
history_mode="premark-page1-error",
)
assert posted is not None and posted["description"] == UNVERIFIED_DESC, (
"a generic `pending` nothing could verify was posted with its re-derivable description "
f"intact: {posted}\n{r.stdout[-900:]}"
)
def test_MUTATION_restoring_the_SUCCESS_only_no_mark_downgrade_leaves_a_re_derivable_pending(tmp_path):
"""The exact predecessor from this branch's own previous commit, restored.
This is the mutation the round-2 suite did not have: the nearest proof mutated the DESCRIPTION
the downgrade writes, not its SCOPE, and its fixture ran a succeeding enumeration so
`state=success` there and the `success`-only predicate fired identically. Nothing reached the
downgrade with `state=pending`, and the predecessor survived the whole suite.
"""
posted, r = _run_classify(
tmp_path / "mutant",
"#!/usr/bin/env bash\n" + DOCS_ONLY + "exit 1\n",
history_mode="premark-page1-error",
mutate=(
'[ "$max_id_before" -lt 0 ] && [ "$desc" != "$REPAIR_DESC" ]; then',
'[ "$state" = "success" ] && [ "$max_id_before" -lt 0 ]; then',
),
)
assert posted is not None and posted["description"] != UNVERIFIED_DESC, (
"the `success`-only predecessor still wrote the sentinel, so this fixture does not reach the "
f"downgrade with state=pending and the test above proves nothing about its scope: {posted}"
f"\n{r.stdout[-900:]}"
)
def test_MUTATION_dropping_the_no_op_repair_SKIP_re_posts_what_is_already_there(tmp_path):
"""The skip, isolated: a repair that would write what this run already wrote is not a repair.
The fixture reaches it the ordinary way a carry-forward `$REPAIR_DESC` write whose post-write
walk then fails, so the floor pins `repair_desc` to the description just POSTed.
"""
_run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
history_mode="postwrite-page1-error",
)
assert len(_posted_sequence(tmp_path / "fixed")) == 1, (
f"the shipped code re-posted: {_posted_sequence(tmp_path / 'fixed')}"
)
_run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
history_mode="postwrite-page1-error",
mutate=('if [ "$raced" -gt 0 ] && [ "$repair_desc" = "$desc" ]; then', "if false; then"),
)
mseq = _posted_sequence(tmp_path / "mutant")
assert len(mseq) == 2 and mseq[0] == mseq[1], (
"the mutant did not duplicate the row, so the skip is not what suppresses it and the "
f"assertion above proves nothing about it: {mseq}"
)
def test_MUTATION_dropping_the_OWN_row_exclusion_reports_a_race_that_did_not_happen(tmp_path):
"""`--arg own "$desc"` keeps this job from counting the row it has just written.
The two exclusions are OUTCOME-redundant with the no-op skip above drop one and the other
still suppresses the duplicate POST which is exactly the shape where two guards hide each
other. What the exclusion alone decides is the REPORT, and after the skip learned to keep the
human `::error::` that report is a false alarm: a run whose own carry-forward row is counted
tells a reviewer their verdict was overwritten when nothing raced it.
So this asserts the log, not the post sequence, and that is the honest discriminator rather than
a weaker one. The head carries `$REPAIR_DESC`, the history is otherwise empty (mark 0), and this
run's own POST lands above the mark.
"""
_, r = _run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
)
# `::error::A human` and not the bare phrase "was overwritten": the classification's own REASON
# string for a carry-forward run also contains that phrase, so matching it would report the
# shipped code as failing on text it is supposed to print.
assert "::error::A human" not in (r.stdout + r.stderr), (
f"the shipped code reported a race against its own row:\n{r.stdout[-900:]}"
)
_, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=REPAIR_DESC,
mutate=(
'or ((.creator == null) and ((.description // "") == $rd)\n'
' and ((.description // "") != $own))',
'or ((.creator == null) and ((.description // "") == $rd))',
),
)
assert "::error::A human" in (rm.stdout + rm.stderr), (
"the mutant did not report a false race, so the `$own` exclusion on the repair-sentinel arm "
f"is not what prevents it:\n{rm.stdout[-900:]}"
)
def test_MUTATION_dropping_the_OWN_exclusion_on_the_UNVERIFIED_arm_reports_a_phantom_other_run(tmp_path):
"""The twin, on the arm that counts the reconcilable sentinel.
A run carrying the unverified sentinel forward POSTs it, then its own row sits above the mark. If
the arm does not exclude it, the job reports that ANOTHER run recorded an unverified write on this
head a second run that does not exist.
"""
_, r = _run_classify(
tmp_path / "fixed",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=UNVERIFIED_DESC,
)
assert "another run recorded an unverified write" not in (r.stdout + r.stderr), (
f"the shipped code reported a phantom second run:\n{r.stdout[-900:]}"
)
_, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="existing:pending",
status_creator=None,
status_desc=UNVERIFIED_DESC,
mutate=(
'select((.creator == null) and ((.description // "") == $ud)\n'
' and ((.description // "") != $own))] | length\')',
'select((.creator == null) and ((.description // "") == $ud))] | length\')',
),
)
assert "another run recorded an unverified write" in (rm.stdout + rm.stderr), (
"the mutant did not report a phantom run, so the `$own` exclusion on the unverified arm is "
f"not what prevents it:\n{rm.stdout[-900:]}"
)
def test_a_FAILED_sentinel_write_on_the_fence_path_FAILS_the_job(tmp_path):
"""The write helper reports whether it wrote, and the fence caller acts on it.
Its first version ended the failure arm with a successful `echo`, so it returned 0 after BOTH
POST attempts failed and the caller's `exit 0` beside it reported an abstention that had not
happened while whatever the head carried stayed authoritative.
"""
posted, r = _run_classify(tmp_path / "fixed", _emitting("docs/a.md"), timeline_mode="unreadable", post_fails=True)
assert posted is None, "the stub was supposed to reject every POST"
assert r.returncode != 0, (
"the job reported a clean abstention while the head was left unmarked and its POSTs had all "
f"failed:\n{r.stdout[-900:]}"
)
assert "COULD NOT WRITE THE UNVERIFIED SENTINEL" in (r.stdout + r.stderr), (
f"the job went red, but not because the sentinel write failed:\n{r.stdout[-900:]}"
)
def test_MUTATION_ignoring_the_write_result_reports_a_clean_abstention(tmp_path):
"""The predecessor: `replace_unknown_state` followed by an unconditional `exit 0`."""
_, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
timeline_mode="unreadable",
post_fails=True,
# MUTATES THE CALLER'S REACTION, not the `if` itself: dropping the `if` keyword leaves a
# dangling `then`/`fi` and the step dies on a syntax error, which is a red for the wrong
# reason. Turning the failure exit into a clean one is exactly the predecessor's OUTCOME and
# isolates the decision.
mutate=(
" # THE SENTINEL WRITE FAILED, so nothing marked this head and whatever it carries is still\n"
" # authoritative. Exiting 0 here would report an abstention that did not happen.\n"
" exit 1",
" exit 0",
),
)
assert rm.returncode == 0, (
"the mutant still failed the job, so the caller is not what turns a failed write into a red "
f"run and the test above proves nothing about it: rc={rm.returncode}\n{rm.stdout[-900:]}"
)
def test_an_ID_that_appears_on_only_ONE_read_is_not_a_replacement(tmp_path):
"""One response omitting `id` beside one that includes it is not evidence of a mid-run write.
The row, its state, its creator and its description are all unchanged; only the SERVER's
reporting differs. Treating that as a replacement makes the run abstain leaving current a row
the classification had already declined to inherit, which is the direction that costs something.
"""
posted, r = _run_classify(tmp_path / "fixed", _emitting("docs/a.md"), status_mode="id-appears-on-second-read")
assert posted is not None, f"an asymmetric id report was read as a mid-run replacement:\n{r.stdout[-900:]}"
assert posted["state"] == "success" and posted["description"].startswith("Exempt:"), (
f"expected the off-list row to be re-derived into the docs-only exemption; got {posted}"
)
def test_MUTATION_comparing_ids_WITHOUT_requiring_both_makes_the_run_abstain(tmp_path):
"""Disarming the presence guards alone, leaving the inequality."""
mutant, rm = _run_classify(
tmp_path / "mutant",
_emitting("docs/a.md"),
status_mode="id-appears-on-second-read",
mutate=(
'if [ -n "$ex_id" ] && [ -n "$pre_id" ] && [ "$ex_id" != "$pre_id" ]; then',
'if [ "$ex_id" != "$pre_id" ]; then',
),
)
assert mutant is None, (
"the mutant did not abstain, so the both-present requirement is not what keeps an asymmetric "
f"id report from reading as a replacement: {mutant}\n{rm.stdout[-900:]}"
)
# --- Workflow token scope (ersatztv#748) -------------------------------------------------------
#
# Both assertions guard a property whose violation is SILENT and, for the first, unrecoverable.