gate: check 7 (register citations) + content WARNings on the memory index
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
Check 7 catches "cites a register item and calls it open when it is not" -- the R-168 class, four files, one self-contradicting. Trigger is an openness CLAIM, not any citation: policing every mention would fire on ~30 legitimate provenance citations and the gate would be switched off. Deliberate deviation from the task's literal wording, to keep it alive. Two bugs found by the check's own red-proofs, both of which would have shipped: - the state marker is not self-closing (**SHIPPED - text**), so the first parser read R-168 itself as OPEN -- a gate that cannot convict its founding case is decoration; - the CLOSED exemption was line-wide, so "shipped" in a title pardoned "OPEN R-25b". Check 6 gains WARN-only content classes on MEMORY.md. Link targets are stripped first: the earlier scan reported three expired statements, all three false (dates in filenames), while missing the one real expired claim, whose deadline was written ~08-02 with no ISO date. 39 -> 60 assertions. All four runners green.
This commit is contained in:
@@ -1,3 +1,36 @@
|
||||
## instructions_gate.py — check 7 (register citations) and content WARNings on the index (2026-08-06)
|
||||
|
||||
**Check 7: a citation that calls a register item OPEN must be right.** Four instruction files said CI
|
||||
was still owed, citing R-168, four days after it closed; one contradicted itself. A trim is a VOLUME
|
||||
operation and does not validate content — this class, at least, is mechanical.
|
||||
|
||||
Three design decisions, each earned during the build:
|
||||
|
||||
- **The trigger is an OPENNESS CLAIM, not any citation.** Requiring every mention of a non-open item
|
||||
to carry "CLOSED" would fire on ~30 legitimate provenance citations (`(R-161)`, `R-117 spike §6.3`)
|
||||
that cite an item as the source of a fact. A gate that noisy gets switched off — R-29's own lesson.
|
||||
**This is a deliberate deviation from the task's literal wording**, taken to keep the gate alive.
|
||||
- **The state marker is not self-closing.** Real rows read `**SHIPPED — and the alarm is
|
||||
DEMONSTRATED**`. The first parser required `**SHIPPED**` and therefore read **R-168 — the row this
|
||||
check exists for — as OPEN**. A gate that cannot convict its own founding case is decoration.
|
||||
- **The exemption is scoped to the citation's CLAUSE.** A line-wide test pardoned `OPEN R-25b`
|
||||
because "shipped" appeared earlier in the same line, in a title and a filename. Found only because
|
||||
the red-proof failed to go red.
|
||||
|
||||
**Check 6 gains content WARNings on `MEMORY.md`** — version literals, host addresses, expired
|
||||
statements, and stale-open citations — all WARN, never FAIL. Claude writes that file between
|
||||
sessions, so a hard failure would refuse a human's push over a line no human typed; the warning is
|
||||
read by the same model that will next edit the file, which makes the loop self-correcting.
|
||||
|
||||
**The expired-statement class carries the sharpest lesson.** A scan on 2026-08-06 reported three
|
||||
expired statements in the index and **every one was false** — each matched the ISO date inside a
|
||||
markdown link TARGET, i.e. a filename — while **missing the one real expired claim** in the same
|
||||
file, whose deadline was written `~08-02` and contained no ISO date at all. Link targets are now
|
||||
stripped before any content judgement, and the deadline pattern matches the bare `MM-DD` form.
|
||||
|
||||
First run over the real index: 32 version literals, 4 host addresses, 0 expired, 0 stale citations.
|
||||
Suite 39 -> 60 assertions. Red-proofs against real files both ways, quoted in the ledger.
|
||||
|
||||
## install_workspace.py + rules_report.py — the workspace survives the machine (2026-08-06, R-229)
|
||||
|
||||
The workspace-root `CLAUDE.md` and the `InstructionsLoaded` hook shape every session and existed on
|
||||
|
||||
@@ -40,7 +40,26 @@ CHECKS (each names the file and the reason; a missing input is a FAILURE, never
|
||||
a directory that is not a git repo, so the copy is the only version-controlled record of it;
|
||||
nothing but this check enforces that they agree.
|
||||
6. The auto-memory index (<workspace>/.claude-memory/MEMORY.md) is within its line and byte
|
||||
limits, and every top-level topic file is referenced by it.
|
||||
limits, every top-level topic file is referenced by it, and its CONTENT is warned about
|
||||
(version literals, host addresses, expired statements, stale-open register citations).
|
||||
7. Every R-nnn cited in a CLAUDE.md or a .claude/rules/*.md that is CALLED OPEN really is open.
|
||||
|
||||
WHY 7 EXISTS (2026-08-06). Four instruction files asserted that CI was still owed, citing R-168,
|
||||
four days after it closed — and one of the four contradicted itself, its release section already
|
||||
saying R-168 mails the failure. A trim is a VOLUME operation: it does not validate content, and the
|
||||
sentence survived because it read as settled. This class, at least, is mechanical.
|
||||
|
||||
- THE TRIGGER IS AN OPENNESS CLAIM, NOT ANY CITATION. Requiring every mention of a non-open item
|
||||
to carry "CLOSED" would fire on ~30 legitimate provenance citations — "(R-161)", "R-117 spike
|
||||
§6.3" — which cite an item as the SOURCE OF A FACT, not as outstanding work. A gate that noisy
|
||||
is switched off, which is the fate R-29 documented.
|
||||
- THE EXEMPTION IS SCOPED TO THE CITATION'S CLAUSE. A line-wide test pardoned a stale claim
|
||||
because the words "shipped" appeared elsewhere on the same line, in a title and a filename.
|
||||
- A CITATION OF AN ITEM IN NEITHER REGISTER FAILS. A reference to nothing cannot be checked by
|
||||
anyone, which is worse than a stale one.
|
||||
- THE STATE MARKER IS NOT SELF-CLOSING. Real rows read `**SHIPPED — and the alarm is
|
||||
DEMONSTRATED**`. An earlier parser required `**SHIPPED**` and therefore read R-168 — the row
|
||||
this check exists for — as open. A gate that cannot convict its own founding case is decoration.
|
||||
|
||||
WHY 6 EXISTS, AND WHY ITS THREE OUTCOMES DIFFER (2026-08-06).
|
||||
|
||||
@@ -276,6 +295,52 @@ def check_memory(workspace_root, failures, warnings, tally):
|
||||
% (index, nbytes, MAX_MEMORY_BYTES)
|
||||
)
|
||||
|
||||
# --- content classes, as WARNINGS (see memory_content_warnings for why not failures) ---
|
||||
cw = memory_content_warnings(text, today_tuple())
|
||||
tally.append(
|
||||
" memory index content : %d version literal(s), %d host address(es), %d expired "
|
||||
"statement(s) [WARN only]"
|
||||
% (len(cw["versions"]), len(cw["addresses"]), len(cw["expired"]))
|
||||
)
|
||||
# Fourth class, beyond the three the CLAUDE.md files are failed on: a stale-open register
|
||||
# citation. Added because TWO of the three false statements found in this file on 2026-08-06
|
||||
# were exactly this ("R-193 decision open", "OPEN R-25b" — both closed), and both were found by
|
||||
# hand. Check 7 polices the hand-written files for it; nothing policed this one.
|
||||
reg = register_state(workspace_root)
|
||||
if reg:
|
||||
stale_cites = []
|
||||
for lineno, raw in enumerate(text.split("\n"), 1):
|
||||
for item in sorted(set(CITE_RE.findall(raw))):
|
||||
cl = clause_around(raw, item)
|
||||
if not (OPENNESS_RE.search(cl) and not SAYS_CLOSED_RE.search(cl)):
|
||||
continue
|
||||
if reg.get(item, "missing") != "open":
|
||||
stale_cites.append((lineno, item, raw.strip()[:90]))
|
||||
if stale_cites:
|
||||
tally.append(" memory stale citations : %d [WARN only]" % len(stale_cites))
|
||||
for lineno, item, ctx in stale_cites[:6]:
|
||||
warnings.append(
|
||||
"%s:%d: calls %s open, but the register does not.\n %s"
|
||||
% (index, lineno, item, ctx)
|
||||
)
|
||||
|
||||
for kind, label, why in (
|
||||
("expired", "EXPIRED statement", "it is read as current fact"),
|
||||
("versions", "version literal", "the fleet is not uniform, so it is stale within a day"),
|
||||
("addresses", "host address", "operations/nodes.md is the single home for these"),
|
||||
):
|
||||
for lineno, hit, ctx in cw[kind][:6]:
|
||||
warnings.append(
|
||||
"%s:%d: %s %r — %s. Not a failure: Claude writes this file between sessions, so "
|
||||
"this warning is aimed at the model that will next edit it, not at whoever is "
|
||||
"pushing.\n %s" % (index, lineno, label, hit, why, ctx)
|
||||
)
|
||||
if len(cw[kind]) > 6:
|
||||
warnings.append(
|
||||
"%s: ... and %d more %s(s) — full list from the tally counts above."
|
||||
% (index, len(cw[kind]) - 6, label)
|
||||
)
|
||||
|
||||
referenced = memory_referenced(text)
|
||||
on_disk = set(
|
||||
n for n in os.listdir(store)
|
||||
@@ -307,6 +372,181 @@ def check_memory(workspace_root, failures, warnings, tally):
|
||||
)
|
||||
|
||||
|
||||
# A date inside a markdown link TARGET is a filename, not a claim: `[x](vacation-...-2026-07-20.md)`.
|
||||
# Matching those produced THREE false "expired statement" findings on 2026-08-06 — every one of the
|
||||
# three reported that day — while missing the one real expired claim in the same file, whose deadline
|
||||
# was written `~08-02` and carried no ISO date at all. A scan that reports the wrong three and misses
|
||||
# the right one is worse than no scan.
|
||||
LINK_TARGET_RE = re.compile(r"\]\([^)]*\)")
|
||||
# Deadline language, including the bare MM-DD form the real one used.
|
||||
DEADLINE_RE = re.compile(
|
||||
r"\b(till|until|by)\s+~?\s*(\d{4}-\d{2}-\d{2}|\d{2}-\d{2})\b", re.I
|
||||
)
|
||||
LOOPBACK = ("127.0.0.1", "0.0.0.0", "255.255.255.255")
|
||||
IPV4_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
|
||||
|
||||
|
||||
def memory_content_warnings(text, today):
|
||||
"""The three content classes the CLAUDE.md files are FAILED on, applied to the index as WARNINGS.
|
||||
|
||||
WARN and not FAIL, deliberately: Claude writes this file between sessions, so a hard failure
|
||||
would refuse a human's push over a line no human typed. The warning IS the mechanism — it is read
|
||||
by the same model that writes the file, which makes the loop self-correcting rather than a chore
|
||||
for a person.
|
||||
"""
|
||||
out = {"versions": [], "addresses": [], "expired": []}
|
||||
for i, raw in enumerate(text.split("\n"), 1):
|
||||
# strip link targets before ANY content judgement — a filename is not an assertion
|
||||
line = LINK_TARGET_RE.sub("]()", raw)
|
||||
|
||||
for m in VERSION_RE.finditer(line):
|
||||
out["versions"].append((i, m.group(), raw.strip()[:90]))
|
||||
for m in IPV4_RE.finditer(line):
|
||||
if m.group() not in LOOPBACK:
|
||||
out["addresses"].append((i, m.group(), raw.strip()[:90]))
|
||||
for m in DEADLINE_RE.finditer(line):
|
||||
token = m.group(2)
|
||||
if len(token) == 5: # MM-DD — assume the current year, which is how it was written
|
||||
when = (today[0], int(token[:2]), int(token[3:]))
|
||||
else:
|
||||
when = tuple(int(x) for x in token.split("-"))
|
||||
if when < today:
|
||||
out["expired"].append((i, m.group(0), raw.strip()[:90]))
|
||||
return out
|
||||
|
||||
|
||||
def register_state(workspace_root):
|
||||
"""Map R-nnn -> 'open' | 'closed' | 'historical' | 'missing'.
|
||||
|
||||
OPEN-ITEMS.md is the authority on what is OPEN and says so in its own header. An item is open
|
||||
when it OWNS A ROW there whose State cell is not a closed marker. An item with no row of its own
|
||||
is not open work — it may still be mentioned inside another row, and it is usually in ROADMAP.md,
|
||||
which keeps the full history. Anything in neither file is a reference to nothing.
|
||||
"""
|
||||
base = os.path.join(workspace_root, "felhom.eu", "documentation", "backlog")
|
||||
reg = os.path.join(base, "OPEN-ITEMS.md")
|
||||
hist = os.path.join(base, "ROADMAP.md")
|
||||
if not os.path.exists(reg):
|
||||
return None
|
||||
|
||||
with io_open(reg) as fh:
|
||||
reg_text = fh.read()
|
||||
hist_text = ""
|
||||
if os.path.exists(hist):
|
||||
with io_open(hist) as fh:
|
||||
hist_text = fh.read()
|
||||
|
||||
state = {}
|
||||
for line in reg_text.split("\n"):
|
||||
m = re.match(r"\|\s*\*\*(R-\d+[a-z]?)\*\*\s*\|", line)
|
||||
if not m:
|
||||
continue
|
||||
cells = [c.strip() for c in line.rstrip().strip("|").split("|")]
|
||||
# The State column is index 2 in BOTH table shapes here (`ID|What|State` and
|
||||
# `ID|What|State|Blocked on|Next action|Owner`). Scan from there rather than taking the
|
||||
# last cell: a row whose prose contains a `|` splits into extra cells and would otherwise
|
||||
# be read from the wrong one.
|
||||
#
|
||||
# THE MARKER IS NOT SELF-CLOSING. Real rows read `**SHIPPED — and the alarm is
|
||||
# DEMONSTRATED**`, not `**SHIPPED**`. An earlier version of this parser required the
|
||||
# closing `**` immediately after the word and therefore read R-168 — the row this whole
|
||||
# check exists for — as OPEN. A gate that cannot convict its own founding case is decoration.
|
||||
state[m.group(1)] = "open"
|
||||
for cell in cells[2:]:
|
||||
if not cell.startswith("**"):
|
||||
continue
|
||||
head = cell[: cell.find("**", 2) + 2] if cell.find("**", 2) > 0 else cell
|
||||
if re.search(r"\b(CLOSED|SHIPPED|DONE|COMPLETE)\b", head) and not re.search(
|
||||
r"\bopen\b", head, re.I
|
||||
):
|
||||
state[m.group(1)] = "closed"
|
||||
break
|
||||
|
||||
mentioned = set(re.findall(r"\bR-\d+[a-z]?\b", reg_text)) | set(
|
||||
re.findall(r"\bR-\d+[a-z]?\b", hist_text)
|
||||
)
|
||||
for item in mentioned:
|
||||
state.setdefault(item, "historical")
|
||||
return state
|
||||
|
||||
|
||||
# Language that CLAIMS an item is still outstanding. The check fires on these, not on every
|
||||
# citation — see check 7's rationale in the module docstring.
|
||||
OPENNESS_RE = re.compile(
|
||||
r"\b(still owed|is owed|are owed|still open|stays open|remains open|still pending|"
|
||||
r"pending\b|still needed|not yet|awaiting|awaits|outstanding|unresolved|to be done|"
|
||||
r"OPEN\s+R-\d)", re.I
|
||||
)
|
||||
SAYS_CLOSED_RE = re.compile(r"\b(CLOSED|DONE|SHIPPED|RESOLVED|COMPLETE)\b", re.I)
|
||||
CITE_RE = re.compile(r"\bR-\d+[a-z]?\b")
|
||||
# Clause boundaries. The exemption is scoped to the clause holding the citation, NOT the whole line:
|
||||
# `- [Customer RESET shipped](…) — …; OPEN R-25b` contains "shipped" twice, in a title and a
|
||||
# filename, and a line-wide exemption pardoned the stale claim at the end of it. Found by this
|
||||
# check's own red-proof, which failed to go red.
|
||||
CLAUSE_SPLIT_RE = re.compile(r"[;.]\s+|\s+—\s+|\s+\|\s+")
|
||||
|
||||
|
||||
def clause_around(line, item):
|
||||
"""The clause of `line` containing `item` — what "the same sentence" means for the exemption."""
|
||||
for part in CLAUSE_SPLIT_RE.split(line):
|
||||
if re.search(r"\b%s\b" % re.escape(item), part):
|
||||
return part
|
||||
return line
|
||||
|
||||
|
||||
def check_citations(root, workspace_root, failures, warnings, tally):
|
||||
"""Check 7 — a citation that calls a register item OPEN must be right.
|
||||
|
||||
Scope: <root>/CLAUDE.md and <root>/.claude/rules/*.md. Effective text only, so a citation
|
||||
parked in an HTML comment for history is not policed.
|
||||
"""
|
||||
state = register_state(workspace_root)
|
||||
if state is None:
|
||||
tally.append(" register citations : n/a (no OPEN-ITEMS.md — not this workspace)")
|
||||
return
|
||||
|
||||
targets = []
|
||||
claude_md = os.path.join(root, "CLAUDE.md")
|
||||
if os.path.exists(claude_md):
|
||||
targets.append(claude_md)
|
||||
rules_dir = os.path.join(root, ".claude", "rules")
|
||||
if os.path.isdir(rules_dir):
|
||||
targets += [
|
||||
os.path.join(rules_dir, n) for n in sorted(os.listdir(rules_dir)) if n.endswith(".md")
|
||||
]
|
||||
|
||||
cited = 0
|
||||
for path in targets:
|
||||
with io_open(path) as fh:
|
||||
eff = effective(fh.read())
|
||||
for lineno, line in enumerate(eff.split("\n"), 1):
|
||||
items = CITE_RE.findall(line)
|
||||
if not items:
|
||||
continue
|
||||
cited += len(items)
|
||||
for item in sorted(set(items)):
|
||||
cl = clause_around(line, item)
|
||||
claims_open = bool(OPENNESS_RE.search(cl)) and not SAYS_CLOSED_RE.search(cl)
|
||||
st = state.get(item, "missing")
|
||||
if st == "missing":
|
||||
failures.append(
|
||||
"%s:%d: cites %s, which appears in NEITHER OPEN-ITEMS.md nor ROADMAP.md. "
|
||||
"A reference to nothing is worse than a stale one — it cannot be checked "
|
||||
"by anyone.\n %s" % (path, lineno, item, line.strip()[:110])
|
||||
)
|
||||
elif claims_open and st != "open":
|
||||
failures.append(
|
||||
"%s:%d: claims %s is still open, but the register says it is %s. Four "
|
||||
"instruction files asserted CI was still owed (R-168) four days after it "
|
||||
"closed, and one contradicted itself about it — a trim is a volume "
|
||||
"operation and does not validate content. Either drop the claim or say "
|
||||
"CLOSED in the same sentence.\n %s"
|
||||
% (path, lineno, item, "not an open row" if st == "historical" else st,
|
||||
line.strip()[:110])
|
||||
)
|
||||
tally.append(" register citations : %d cited, %d register items known" % (cited, len(state)))
|
||||
|
||||
|
||||
def io_open(path):
|
||||
return open(path, "r", encoding="utf-8")
|
||||
|
||||
@@ -338,6 +578,7 @@ def main(argv):
|
||||
check_rules(root, failures, tally)
|
||||
check_workspace_copy(os.path.dirname(root), failures, tally)
|
||||
check_memory(os.path.dirname(root), failures, warnings, tally)
|
||||
check_citations(root, os.path.dirname(root), failures, warnings, tally)
|
||||
|
||||
for line in tally:
|
||||
print(line)
|
||||
|
||||
@@ -295,6 +295,165 @@ def test_memory_store_without_index_fails():
|
||||
check("message says unreachable knowledge", "unreachable knowledge" in out)
|
||||
|
||||
|
||||
# --- check 7: register citations ----------------------------------------------------------------
|
||||
#
|
||||
# The founding case is "CI is still owed (R-168)" shipped four days after R-168 closed, in four
|
||||
# files, one of which contradicted itself. The trigger is an OPENNESS CLAIM, not any citation:
|
||||
# requiring every mention of a non-open item to carry "CLOSED" would fire on ~30 legitimate
|
||||
# provenance citations like "(R-161)" or "R-117 spike §6.3", and a gate that noisy gets switched
|
||||
# off — which is the fate R-29 documented.
|
||||
|
||||
|
||||
def make_register(tmp, rows):
|
||||
"""rows: list of (item, what, state-cell). Mirrors the real 6-column table shape."""
|
||||
d = os.path.join(tmp, "felhom.eu", "documentation", "backlog")
|
||||
os.makedirs(d, exist_ok=True)
|
||||
body = ["| ID | What | State | Blocked on | Next action | Owner |", "|---|---|---|---|---|---|"]
|
||||
for item, what, st in rows:
|
||||
body.append("| **%s** | %s | %s | — | — | operator |" % (item, what, st))
|
||||
with open(os.path.join(d, "OPEN-ITEMS.md"), "w", encoding="utf-8") as fh:
|
||||
fh.write("\n".join(body) + "\n")
|
||||
with open(os.path.join(d, "ROADMAP.md"), "w", encoding="utf-8") as fh:
|
||||
fh.write("| R-900 | a historical item, closed long ago and pruned from OPEN-ITEMS |\n")
|
||||
|
||||
|
||||
REG_ROWS = [
|
||||
("R-168", "~~CI: no runner exists~~", "**SHIPPED — and the alarm is DEMONSTRATED** (2026-08-02)"),
|
||||
("R-161", "volume-persistence gate", "**REDUCED SCOPE — open** (operator ruling)"),
|
||||
]
|
||||
|
||||
|
||||
def test_citation_claiming_open_about_a_closed_item_fails():
|
||||
"""The founding case, verbatim."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(tmp, "# Repo\n\nBoth facts are why CI is still owed (`OPEN-ITEMS.md` R-168).\n")
|
||||
rc, out = run_gate(root)
|
||||
check("stale-open citation exits non-zero", rc != 0, "rc=%d" % rc)
|
||||
check("message names the item", "R-168" in out)
|
||||
check("message says the register disagrees", "register says it is closed" in out)
|
||||
|
||||
|
||||
def test_citation_that_says_closed_passes():
|
||||
"""Explicitly required by the spec: a correct citation must survive."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(
|
||||
tmp, "# Repo\n\na bypass is noticed even though it is not blocked (R-168, CLOSED 2026-08-02).\n"
|
||||
)
|
||||
rc, out = run_gate(root)
|
||||
check("citation saying CLOSED exits 0", rc == 0, "rc=%d" % rc)
|
||||
|
||||
|
||||
def test_bare_provenance_citation_of_a_closed_item_passes():
|
||||
"""Load-bearing negative: citing a closed item as the SOURCE OF A FACT is not a claim it is open."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(tmp, "# Repo\n\nThe canonical runner shape (R-168) is what survives.\n")
|
||||
rc, out = run_gate(root)
|
||||
check("provenance citation of a closed item exits 0", rc == 0, "rc=%d" % rc)
|
||||
|
||||
|
||||
def test_openness_claim_about_an_actually_open_item_passes():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(tmp, "# Repo\n\n**R-161 stays open at reduced scope** — enforcement is by convention.\n")
|
||||
rc, out = run_gate(root)
|
||||
check("openness claim about an OPEN item exits 0", rc == 0, "rc=%d" % rc)
|
||||
|
||||
|
||||
def test_closed_word_elsewhere_on_the_line_does_not_pardon_a_stale_claim():
|
||||
"""Regression. The exemption is scoped to the citation's CLAUSE, not the whole line.
|
||||
|
||||
`- [Customer RESET shipped](customer-reset-shipped-2026-07-17.md) — …; OPEN R-25b` contains
|
||||
"shipped" twice — once in a title, once in a filename — and a line-wide exemption pardoned the
|
||||
stale claim sitting at the end of it. Found by this check's own red-proof failing to go red,
|
||||
which is the only reason it was found at all."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(
|
||||
tmp,
|
||||
"# Repo\n\n- [Customer RESET shipped](customer-reset-shipped-2026-07-17.md) — "
|
||||
"teardown first; OPEN R-168\n",
|
||||
)
|
||||
rc, out = run_gate(root)
|
||||
check("a distant 'shipped' does not pardon the claim", rc != 0, "rc=%d" % rc)
|
||||
check("the stale item is still named", "R-168" in out)
|
||||
|
||||
|
||||
def test_citation_of_an_item_in_no_register_fails():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(tmp, "# Repo\n\nSee R-4242 for the rationale.\n")
|
||||
rc, out = run_gate(root)
|
||||
check("citation of a nonexistent item exits non-zero", rc != 0, "rc=%d" % rc)
|
||||
check("message says it is in neither register", "NEITHER" in out)
|
||||
|
||||
|
||||
def test_citation_in_an_html_comment_is_not_policed():
|
||||
"""Comments are stripped before injection, so a historical citation parked there costs nothing."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(tmp, "# Repo\n\n<!-- history: CI was still owed (R-168) at the time -->\n")
|
||||
rc, out = run_gate(root)
|
||||
check("citation inside a comment is not policed", rc == 0, "rc=%d" % rc)
|
||||
|
||||
|
||||
def test_citation_check_covers_rule_files_too():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_register(tmp, REG_ROWS)
|
||||
root = make_repo(
|
||||
tmp, "# Repo\n",
|
||||
rules={"g.md": '---\npaths: ["**/*.go"]\n---\n\nCI is still owed (R-168).\n'},
|
||||
)
|
||||
rc, out = run_gate(root)
|
||||
check("a rule file's stale citation also fails", rc != 0, "rc=%d" % rc)
|
||||
check("message names the rule file", "g.md" in out)
|
||||
|
||||
|
||||
# --- check 6: content WARNings on the memory index ------------------------------------------------
|
||||
|
||||
|
||||
def test_memory_expired_statement_warns_but_does_not_fail():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_memory(tmp, "# Index\n- [a](a.md) — demo boxes REMOTE till ~08-02\n", topics={"a.md": "# A\n"})
|
||||
root = make_repo(tmp, "# Repo\n")
|
||||
rc, out = run_gate(root)
|
||||
check("expired statement in the index does NOT fail", rc == 0, "rc=%d" % rc)
|
||||
check("expired statement WARNs", "EXPIRED statement" in out)
|
||||
|
||||
|
||||
def test_memory_date_inside_a_link_target_is_not_an_expired_statement():
|
||||
"""THE load-bearing negative. On 2026-08-06 a scan matched the ISO date in the link TARGET and
|
||||
reported three expired statements, every one of them false, while missing the one real expired
|
||||
claim in the same file — whose deadline was written `~08-02` with no ISO date at all."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_memory(tmp, "# Index\n- [Customer RESET](customer-reset-shipped-2026-07-17.md) — teardown first\n",
|
||||
topics={"customer-reset-shipped-2026-07-17.md": "# X\n"})
|
||||
root = make_repo(tmp, "# Repo\n")
|
||||
rc, out = run_gate(root)
|
||||
check("a dated FILENAME is not an expired statement", rc == 0 and "EXPIRED" not in out, "rc=%d" % rc)
|
||||
|
||||
|
||||
def test_memory_future_deadline_does_not_warn():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_memory(tmp, "# Index\n- [a](a.md) — rig stays up until 2026-12-31\n", topics={"a.md": "# A\n"})
|
||||
root = make_repo(tmp, "# Repo\n")
|
||||
rc, out = run_gate(root)
|
||||
check("a future deadline does not warn", "EXPIRED" not in out)
|
||||
|
||||
|
||||
def test_memory_version_literal_and_address_warn_but_do_not_fail():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
make_memory(tmp, "# Index\n- [a](a.md) — ctrl 0.162.0 on 192.168.0.180\n", topics={"a.md": "# A\n"})
|
||||
root = make_repo(tmp, "# Repo\n")
|
||||
rc, out = run_gate(root)
|
||||
check("version literal in the index does NOT fail", rc == 0, "rc=%d" % rc)
|
||||
check("version literal WARNs", "version literal" in out)
|
||||
check("host address WARNs", "host address" in out)
|
||||
check("loopback is not reported", "127.0.0.1" not in out)
|
||||
|
||||
|
||||
def main():
|
||||
print("test_instructions_gate")
|
||||
for fn in sorted(
|
||||
|
||||
Reference in New Issue
Block a user