Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions artifacts/release-v0.77/RQ-77-PROSEBLIND.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ artifacts:
req-type: process
priority: must
issue: "#1319"
# R14 (#1484): the `verified-by:` opens REFUTED beside a claiming status,
# so the ISSUE's fate must be declared rather than inferred from prose.
# `closes` is the truthful value: #1319 is CLOSED because the refutation
# this artifact records had ALREADY SHIPPED in v0.69. Not `outlives`.
issue-scope: closes
landed: "#1424"
verified-by: >-
REFUTED, AND THE REFUTATION ALREADY SHIPPED IN v0.69 — which this lane did not know when it
Expand Down
43 changes: 39 additions & 4 deletions artifacts/release-v0.82/RQ-82-CLOSEARM.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ artifacts:
- id: RQ-82-CLOSEARM
type: system-req
release: v0.82
status: proposed
status: implemented
title: "Satisfying one gate ARMED another: a claiming status is both R3's remedy and the closer's permission, and nothing reads the conjunction"
description: >
v0.81 nearly closed an external reporter's live defect by machine, and neither gate
Expand Down Expand Up @@ -39,8 +39,13 @@ artifacts:
artifacts no longer carry `outlives`, or the authorised set no longer contains an
issue whose artifact disclaims progress, the instance is gone — re-derive the shape
before building on it. Then
`python3 -c "import sys; sys.path.insert(0,'scripts'); import status_evidence_check as s; print(sorted(s.INCOMPLETE))"`
— if an INCOMPLETE verdict beside a claiming status is ALREADY red, this premise is dead.
`python3 -c "import sys; sys.path.insert(0,'scripts'); import verdict_prose_check as v; print(sorted(v.INCOMPLETE))"`
— CORRECTED at the lane's start: `INCOMPLETE`/`COMPLETE` live in `verdict_prose_check`,
NOT in `status_evidence_check` (which owns `CLOSES_WITHOUT_CLAIMING`); the wrong module
raises AttributeError, which is the GOOD failure. Then derive the population with the
GATE'S OWN classifier and never a hand-written regex: `leading_verdict(vb) in INCOMPLETE
and status in CLAIMING` over `artifacts/**/*.yaml`. If that shape is ALREADY red, this
premise is dead.
tags:
- process
- gate
Expand All @@ -53,5 +58,35 @@ artifacts:
req-type: process
priority: must
issue: "#1484"
landed: "R14 landed in PR #1486 — the rule, seven unit tests, both potency mutants, and the day-one red (RQ-77-PROSEBLIND gains `issue-scope: closes`)."
verified-by: >
DELIVERED, AND THE RULE JUDGES ITS OWN ARTIFACT. R14 reds an artifact whose
`verified-by` opens with an INCOMPLETE verdict beside a CLAIMING status with no
`issue-scope:` declared. This artifact is claiming, so its own verdict had to be a
COMPLETE one — and it is, truthfully: the rule, seven tests, paired potency mutants
in BOTH directions and the single day-one red are all in the tree.

PROVEN POTENT WITH PAIRED MUTANTS, because the rule has two failure directions and
each is invisible to the other's tests. Forcing the condition False kills the two
red-first tests; dropping `not _iscope14` so it over-fires kills the two green-path
tests; as shipped 7/7 pass and the live gate is rc=0. On real data R14 reds
RQ-77-PROSEBLIND, and reds BOTH v0.81 artifacts when their `outlives` is stripped —
the near-miss an operator caught by hand, reproduced by a gate.

THE DESIGN WAS CONSTRAINED BY THE DEFECT IT FIXES. Demanding `outlives` would have
reddened a legitimate artifact: R11 forbids a non-delivery `disposition:` beside a
claiming status and R3 forbids a non-claiming status with done-when evidence, so
claiming + prose-only refutation + no disposition is the FORCED configuration. R14
therefore demands a DECLARATION in either direction, and R3 and R11 are untouched.

THE VOCABULARY IS IMPORTED UNDER AN IDENTITY ASSERT: `INCOMPLETE` and
`leading_verdict` come from `verdict_prose_check`, checked with `assertIs` and not
`assertEqual`, because an equal-but-separate set passes today and drifts tomorrow.
Asserted: zero `INCOMPLETE = {` literals in the gate.

AND IT WAS NEARLY BORN WITH THE DEFECT ITS SIBLING LANE SCOPES: written as
`str(fields.get("issue-scope", ""))` a bare key would read as the truthy string
"None", so a declaration of NOTHING would have SATISFIED R14. It is written `or ""`
before `str()`, and a test pins exactly that case.
verification-track: manual
done-when: "manual:2026-12-31 an artifact that claims its outcome holds while its own `verified-by` opens with an INCOMPLETE verdict and declares no `issue-scope: outlives` is RED, proven red-first against v0.81's own two artifacts with the `outlives` field removed, and proven NOT to red either of them as shipped; R3's rule unchanged; and the live population measured before the rule lands so a day-one red count is known rather than discovered"
done-when: "manual:2026-12-31 an artifact that claims its outcome holds while its own `verified-by` opens with an INCOMPLETE verdict and declares NO `issue-scope:` AT ALL is RED — EITHER value satisfies it, because R11 forbids a non-delivery `disposition:` beside a claiming status and R3 forbids a non-claiming status with done-when evidence, so claiming + prose-only refutation + no disposition is the FORCED configuration and demanding `outlives` specifically would red RQ-77-PROSEBLIND, whose issue IS resolved; proven red-first against v0.81's own two artifacts with the `outlives` field removed, proven NOT to red either of them as shipped, proven not to fire on a COMPLETE verdict or a non-claiming status, proven that a VALUELESS `issue-scope:` key still reds, and the verdict vocabulary IMPORTED from `verdict_prose_check` with an identity assert so no mirror can drift; R3's and R11's rules unchanged; and the live population measured before the rule lands so the day-one red count is known rather than discovered"
29 changes: 29 additions & 0 deletions artifacts/release-v0.82/RQ-82-VALUELESSKEY.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,35 @@ artifacts:
truth or membership. Derive that set and fix the class, or state which sites are
deliberately tolerant and why.

POPULATION DERIVED AT THE LANE'S START, with `ast` and not a regex — a
`git grep` for the literal `str(fields.get(` misses `str(art.get(`,
`str(a.get(`, `str(fl.get(` and `str((a[4] or {}).get(`, so it UNDERCOUNTS the
very class this artifact scopes. Walking every `scripts/**/*.py` for a
`str(<expr>.get(...))` call whose argument is a bare `.get` (an `or ""` guard
makes the argument a BoolOp, so already-fixed sites are excluded by
construction, and that exclusion is controlled):

32 total sites of the shape
14 of them read a YAML ARTIFACT FIELD — the live defect class, because an
author's bare key is what makes the value None
13 read GitHub API JSON or config, where the None comes from the API
2 are test fixtures and cannot surprise
3 other (two reach an artifact field through a tuple)
11 of the 32 are inside `status_evidence_check.py` itself

TWO SITES ARE NAMED BECAUSE THEY SIT IN R12's OWN PATH:
`str(fields.get("issue-scope", ""))` and `str(fields.get("issue", ""))`. A bare
`issue-scope:` key there reads as the INVENTED string "none" and then fails R12's
vocabulary check with a value no author wrote — directly beneath R11's own comment
admitting "Round 1 fixed the same trap in `loop_conformance_check` and left R11's
twin — the shape this programme has had to correct repeatedly."

WHAT THIS LANE MUST NOT DO: patch 32 sites. The classes differ in whether None is
reachable at all, so the deliverable is a DERIVATION plus a tripwire over the
artifact-field class, not a sweep. v0.82's own R14 was written with `or ""` BEFORE
`str()` and is therefore NOT in the population — verified by re-running the census
after it landed.

REFUTING COMMAND: `git grep -c -F 'str(fields.get(' -- scripts/` and
`python3 -c "print(repr(str(None).strip()))"`. If the idiom is gone from the gate
surface, or if a shared accessor now normalises it, the premise is dead. Then
Expand Down
2 changes: 1 addition & 1 deletion scripts/repro/diagnostic_issue_census_345.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# make this wrong" named exactly this outcome. What ships instead is this
# script plus the convention recorded in CLAUDE.md. There is nothing here for
# CI to fail on, which is why it is `manual` and not `wired`.
"""RQ-74-STALEMSG (#345): what does an issue number in a diagnostic MEAN?
r"""RQ-74-STALEMSG (#345): what does an issue number in a diagnostic MEAN?

WHY THIS WAS ASKED. v0.73 cost real time twice to one diagnostic:

Expand Down
52 changes: 52 additions & 0 deletions scripts/status_evidence_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,17 @@ class that produced the splice disappears at the source (#1059's

import yaml

# R14 (#1484, RQ-82-CLOSEARM) reads the LEADING VERDICT of `verified-by:`. The
# classifier and its vocabulary live in `verdict_prose_check` and are IMPORTED,
# never mirrored: a second copy of COMPLETE/INCOMPLETE is how the model drifts
# from the shipped table (#667's lesson, one tier down). A FAILING import must
# be LOUD — a gate that cannot classify a verdict must not read as "no finding".
sys.path.insert(0, str(Path(__file__).resolve().parent))
from verdict_prose_check import ( # noqa: E402
INCOMPLETE as VERDICT_INCOMPLETE,
leading_verdict as _leading_verdict,
)

REPO_ROOT = Path(__file__).resolve().parent.parent

# Statuses that CLAIM the artifact's stated outcome holds. Everything else
Expand Down Expand Up @@ -1788,6 +1799,47 @@ def check(root: Path, release_glob: str, subjects: list[str],
f"`proposed`, #1250); flip to a claiming status, or "
f"set `disposition: partial|refuted|deferred`"
)
# R14 (#1484, RQ-82-CLOSEARM). SATISFYING ONE GATE CAN ARM ANOTHER.
# `status: implemented` is simultaneously R3's ONLY remedy for "the
# status under-reports shipped work" AND `authorised_close_set`'s
# permission to retire the issue. v0.81's JESSDIVERGE3 and WIDEFILE4
# each delivered a REFUTATION, said so in their own `verified-by`, and
# were set claiming for R3's sake — which silently placed an externally
# reported live blocker in the authorised set. An operator caught it by
# running the closure gate before tagging; no gate did.
#
# WHY THIS DOES NOT DEMAND `outlives`, AND WHY IT CANNOT DEMAND A
# `disposition:`. R11 reds a non-delivery `disposition:` beside a
# claiming status, and R3 reds a non-claiming status whose done-when
# evidence exists. So for a lane that did real work and refuted its own
# premise, claiming + prose-only refutation + NO disposition is the
# FORCED configuration — there is no legal third option. Demanding
# `outlives` would therefore red a legitimate artifact whose issue IS
# resolved (RQ-77-PROSEBLIND: `verified-by` opens REFUTED, and #1319 is
# closed because the refutation shipped in v0.69).
#
# What this demands instead is a DECLARATION, in either direction: say
# `closes` or say `outlives`, in the field the closure gate already
# reads. The prose already states the outcome; this makes the ISSUE's
# fate structural rather than inferable only by reading English.
# R3 and R11 are untouched.
if version >= DISPOSITION_SINCE:
# `or ""` BEFORE `str()` — `str(None)` is the four-character string
# "None" and a bare key would read as a verdict. (#1458.)
_vb = str(fields.get("verified-by") or "")
_lead = _leading_verdict(_vb) if _vb.strip() else None
_iscope14 = str(fields.get("issue-scope") or "").strip().lower()
if _lead in VERDICT_INCOMPLETE and status in CLAIMING and not _iscope14:
failures.append(
f"R14 {art_id}: `verified-by:` opens `{_lead}` while status "
f"`{status}` claims the outcome holds, and no `issue-scope:` "
f"says what that means for the ISSUE. A claiming status is "
f"both R3's only remedy and the closure gate's permission, "
f"so this shape silently authorises retiring an issue the "
f"artifact itself says is unresolved. Declare "
f"`issue-scope: outlives` (the issue survives this release) "
f"or `issue-scope: closes` (the verdict resolves it)."
)
# R12 (#1250, RQ-71-ISSUESCOPE): an artifact may declare that its
# ISSUE outlives its own delivery. Separate key from `disposition:`
# by design — see ISSUE_SCOPES above — so this never trips R11.
Expand Down
2 changes: 1 addition & 1 deletion scripts/test_oracle_run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Unit tests for scripts/oracle_run.py.
r"""Unit tests for scripts/oracle_run.py.

RQ-77-STDERR (#1419). This driver runs **206 oracles** in CI and had NO tests at
all, and was not wired into `ci.yml`. The specific defect it shipped with: an
Expand Down
78 changes: 78 additions & 0 deletions scripts/test_status_evidence_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,84 @@ def test_r4_plan_and_chore_subjects_are_not_matched(self):



class R14ClosearmPoseVerdict1484(unittest.TestCase):
"""RQ-82-CLOSEARM (#1484). A claiming status is simultaneously R3's ONLY
remedy for "the status under-reports shipped work" AND the closure gate's
permission to retire the issue, and nothing read their conjunction. v0.81's
JESSDIVERGE3 and WIDEFILE4 each delivered a REFUTATION, said so in their own
`verified-by`, and were set claiming for R3's sake -- which silently placed
an externally reported LIVE blocker in the authorised close set. An operator
caught it by running the closure gate before tagging; no gate did.

WHY R14 DEMANDS A DECLARATION RATHER THAN `outlives`. R11 reds a
non-delivery `disposition:` beside a claiming status, and R3 reds a
non-claiming status whose done-when evidence exists, so for a lane that did
real work and refuted its own premise, claiming + prose-only refutation +
NO disposition is the FORCED configuration. Demanding `outlives` would red
RQ-77-PROSEBLIND, whose `verified-by` opens REFUTED and whose issue (#1319)
IS closed because the refutation shipped in v0.69. So R14 demands that the
ISSUE's fate be STATED -- either value -- in the field the closure gate
already reads."""

MANUAL = "manual: the stated outcome holds"
REFUTED = "REFUTED. The premise did not survive its own measurement."
DELIVERED = "DELIVERED. Both directions, measured at the cut."

def _fx(self, fields):
fx = Fixture()
fx.release("release-v0.82/RQ-82-SUBJECT.yaml",
[art("RQ-82-SUBJECT", fields.pop("status"), fields)])
return fx

def test_incomplete_verdict_beside_claiming_without_a_scope_is_red(self):
fx = self._fx({"status": "implemented", "done-when": self.MANUAL,
"verified-by": self.REFUTED})
self.assertTrue(has(fx.run([]), "R14 RQ-82-SUBJECT"), fails(fx.run([])))

def test_outlives_satisfies_it(self):
fx = self._fx({"status": "implemented", "done-when": self.MANUAL,
"verified-by": self.REFUTED, "issue-scope": "outlives"})
self.assertEqual(fails(fx.run([])), [])

def test_closes_satisfies_it_too(self):
# The PROSEBLIND shape: the verdict refutes the premise AND resolves the
# issue. Demanding `outlives` here would be false.
fx = self._fx({"status": "implemented", "done-when": self.MANUAL,
"verified-by": self.REFUTED, "issue-scope": "closes"})
self.assertEqual(fails(fx.run([])), [])

def test_a_COMPLETE_verdict_is_untouched(self):
# CONTROL: the rule must fire on the INCOMPLETE set only, or it would
# demand a scope declaration from every delivered artifact in the tree.
fx = self._fx({"status": "implemented", "done-when": self.MANUAL,
"verified-by": self.DELIVERED})
self.assertEqual(fails(fx.run([])), [])

def test_a_non_claiming_status_is_untouched(self):
# CONTROL: R3/R11 own that case. R14 must not double-report it.
fx = self._fx({"status": "proposed", "done-when": self.MANUAL,
"verified-by": self.REFUTED})
self.assertFalse(has(fx.run([]), "R14 "), fails(fx.run([])))

def test_a_VALUELESS_issue_scope_key_declares_nothing_and_still_reds(self):
# #1458's shape: a bare `issue-scope:` parses to None, and `str(None)`
# is the four-character string "None" -- truthy. Read that way a
# valueless key would SATISFY R14 while declaring nothing at all.
fx = self._fx({"status": "implemented", "done-when": self.MANUAL,
"verified-by": self.REFUTED, "issue-scope": None})
self.assertTrue(has(fx.run([]), "R14 RQ-82-SUBJECT"), fails(fx.run([])))

def test_the_vocabulary_is_IMPORTED_not_mirrored(self):
# DERIVE WHAT YOU CHECK AGAINST. If R14 ever grows its own copy of the
# verdict sets, the two drift silently -- the #667 lesson one tier down.
# Identity, not equality: an equal-but-separate set would still drift.
import verdict_prose_check as vpc
self.assertIs(sec.VERDICT_INCOMPLETE, vpc.INCOMPLETE,
"R14 must READ verdict_prose_check's set, not copy it")
self.assertIs(sec._leading_verdict, vpc.leading_verdict,
"R14 must READ verdict_prose_check's classifier")


class ClosesWithoutClaimingIsScopedToRefuted(unittest.TestCase):
"""v0.81 round-1 cold review, finding 9.

Expand Down
Loading