[FIX] PENDING-164 (c)+(d): prior-art search, one implementation, two surfaces
Steward-authorized in session. prior-art.py searches commit messages across every owned repo with NO count window and reports the register's mention count beside it; the executor runs it as a CLI (d) and governance-mcp.py exposes it as prior_art (c). One implementation on purpose — a value computed twice on two sides of a boundary is how the parties end up with different answers. Verified on the case that motivated the item: 'LFS' returns 20 commits including 0677e8a and95760ff, both past repo_activity's 100-commit floor, one of them in dotfiles which is not in REPOS. The positive control forced the enumeration to be COMPUTED from remote ownership rather than copied from REPOS, or95760ffwould have been unreachable and the control would have failed. Had this existed this morning, one command before filing PENDING-163 would have returned 0677e8a and 400c054. ⚠ Its first run returned zero and the control caught it: sh() discarded stdout on non-zero exit, and find over $HOME exits 1 from 154 unreadable Library dirs while printing all 37 repos. Third false-zero of the day, first one caught before being believed — the difference is that the jurist pre-specified what it must return. AMENDMENT 1's census: mechanical half runs (661 candidates, narrowed to 270), interpretive half does not. Identifying WHICH mechanism a commit decided about is interpretation, not extraction. Limit declared rather than a column manufactured. The backlog is NOT censused and no number here is one. Extending the read-only guarantee to delegates found a pre-existing hole: bare .replace flagged str.replace() (why it had never been extended), and wake-digest, a delegate since before today, was never covered. Its only real mutation is emit_brief(), its hook role, unreachable from any tool. Now a declared exemption per delegate, so a new mutating function fails until named. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvZAKSf9aqratbqHbU9LK5
This commit is contained in:
co-authored by
Claude Opus 5
parent
46aa3d15a2
commit
b28cc03737
+119
-1
@@ -63,6 +63,7 @@ def _load(mod_name, filename):
|
||||
|
||||
|
||||
wd = _load("wake_digest", "wake-digest.py") # the single definition of "open item"
|
||||
pa = _load("prior_art", "prior-art.py") # PENDING-164 (c): commit history, no window
|
||||
|
||||
# ---- the enumerated domain: keys, never paths -------------------------------
|
||||
FILES = {
|
||||
@@ -428,6 +429,53 @@ def t_pair(args):
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
# ── PENDING-164 (c) ───────────────────────────────────────────────────────────────
|
||||
# repo_activity caps at 100 commits. At chamber-library's rate that floor sat five weeks
|
||||
# short of 0677e8a — the commit retiring LFS — so when the jurist recommended adopting
|
||||
# LFS on 2026-08-26, NO instrument available to it could have reached the refutation.
|
||||
# 'Search prior art before proposing' was not a rule the jurist could follow.
|
||||
#
|
||||
# This removes the asymmetry rather than papering over it: one implementation
|
||||
# (prior-art.py), two consumers — the executor's CLI and this tool — so the answer cannot
|
||||
# differ by surface. It returns the substrate, not testimony about it.
|
||||
def t_prior_art(args):
|
||||
"""Commit-message history for a named mechanism, across every owned repo, unbounded."""
|
||||
term = (args.get("term") or "").strip()
|
||||
if not term:
|
||||
return "ERROR: term is required (the mechanism's name, e.g. 'LFS', 'submodule')."
|
||||
if len(term) < 2:
|
||||
return "ERROR: term too short to be discriminating."
|
||||
ok, notes, _ = pa.controls()
|
||||
hits = pa.search_commits(term)
|
||||
reg = pa.register_mentions(term)
|
||||
out = []
|
||||
if not ok:
|
||||
out.append("⚠ INSTRUMENT NOT VERIFIED — result unestablished. "
|
||||
+ "; ".join(notes))
|
||||
out.append("")
|
||||
out.append(f"PRIOR ART: {term!r}")
|
||||
out.append(f" commits mentioning it : {len(hits)} (all branches, NO count window)")
|
||||
out.append(f" register mentions : {reg} (PENDING, PENDING-archive, REVIEWED)")
|
||||
out.append("")
|
||||
for repo, sha, date, subj in sorted(hits, key=lambda h: h[2])[:60]:
|
||||
out.append(f" {date} {repo:32} {sha} {subj[:72]}")
|
||||
if len(hits) > 60:
|
||||
out.append(f" … {len(hits) - 60} further commit(s) not listed.")
|
||||
out.append("")
|
||||
if hits and reg == 0:
|
||||
out.append(" \u26a0 FINDING — PENDING-164's condition exactly: this mechanism has a")
|
||||
out.append(" history in the repos and NO trace in the authorization record. Whatever")
|
||||
out.append(" was decided about it was decided in a commit message. Read those")
|
||||
out.append(" commits before proposing anything about it.")
|
||||
elif hits:
|
||||
out.append(" Both surfaces carry it. A ruling can post-date the commit that")
|
||||
out.append(" motivated it, or precede the one that undid it — read both.")
|
||||
else:
|
||||
out.append(" No prior art. \u26a0 Weak absence: it means no COMMIT MESSAGE names this")
|
||||
out.append(" term, not that nothing was decided about it.")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
TOOLS = [
|
||||
("governance_state", t_state,
|
||||
"Current governance state, computed live: every open authorization item with its "
|
||||
@@ -474,6 +522,16 @@ TOOLS = [
|
||||
"Full governance-drift-check output: claims in CLAUDE.md the substrate contradicts. "
|
||||
"Detection only — correcting doctrine requires steward authorization.",
|
||||
{"type": "object", "properties": {}}),
|
||||
("prior_art", t_prior_art,
|
||||
"Commit-message history for a named mechanism across every owned repo, with NO "
|
||||
"commit-count window, plus whether the authorization register mentions it. Use this "
|
||||
"BEFORE proposing or ruling on any named mechanism — repo_activity caps at 100 "
|
||||
"commits and that floor has already hidden a decision this system had made. Commits "
|
||||
"with zero register mentions is the finding, not the noise.",
|
||||
{"type": "object", "properties": {
|
||||
"term": {"type": "string",
|
||||
"description": "The mechanism's name, e.g. 'LFS', 'submodule', 'worktree'."}},
|
||||
"required": ["term"]}),
|
||||
("repo_activity", t_repo,
|
||||
"Branch, uncommitted-file status and recent commits for one of the active repos.",
|
||||
{"type": "object", "properties": {
|
||||
@@ -550,8 +608,16 @@ def write_calls(src):
|
||||
duly found all of them, in its own definition. That is the day's recurring shape:
|
||||
an instrument whose domain includes itself. The AST sees calls, not characters."""
|
||||
import ast
|
||||
MUTATORS = {"remove", "unlink", "rename", "replace", "rmdir", "mkdir", "makedirs",
|
||||
MUTATORS = {"remove", "unlink", "rename", "rmdir", "mkdir", "makedirs",
|
||||
"chmod", "truncate", "write", "writelines", "write_text", "write_bytes"}
|
||||
# `replace` is NOT in MUTATORS as a bare attribute: str.replace() is ubiquitous and
|
||||
# flagging it made this check unusable on any file that manipulates text — which is
|
||||
# why it had never been extended past this file. os.replace/Path.replace ARE caught,
|
||||
# by qualified name below. Narrowed 2026-08-26 when extending the guarantee to
|
||||
# delegates surfaced three false positives in wake-digest.py (lines 142, 150, 888,
|
||||
# every one a string replace) alongside one real write.
|
||||
QUALIFIED = {("os", "replace"), ("shutil", "move"), ("shutil", "rmtree"),
|
||||
("shutil", "copy"), ("shutil", "copy2"), ("shutil", "copytree")}
|
||||
out = []
|
||||
for n in ast.walk(ast.parse(src)):
|
||||
if not isinstance(n, ast.Call):
|
||||
@@ -565,6 +631,9 @@ def write_calls(src):
|
||||
out.append(f"open(mode={mode!r}) at line {n.lineno}")
|
||||
elif isinstance(f, ast.Attribute) and f.attr in MUTATORS:
|
||||
out.append(f"{f.attr}() at line {n.lineno}")
|
||||
elif (isinstance(f, ast.Attribute) and isinstance(f.value, ast.Name)
|
||||
and (f.value.id, f.attr) in QUALIFIED):
|
||||
out.append(f"{f.value.id}.{f.attr}() at line {n.lineno}")
|
||||
return out
|
||||
|
||||
|
||||
@@ -693,6 +762,19 @@ def selftest():
|
||||
"2026-08-05; this control goes red if a header is ever hidden again]",
|
||||
"MATCHES OUTSIDE ANY ITEM" not in t_search({"query": "the"}))
|
||||
|
||||
print("\nprior_art — PENDING-164 (c): the window that hid 0677e8a:")
|
||||
chk("prior_art returns the commit repo_activity's 100-window could not reach "
|
||||
"[0677e8a, 2026-06-05, five weeks past the floor]",
|
||||
"0677e8a" in t_prior_art({"term": "LFS"}))
|
||||
chk("prior_art reaches dotfiles, which is NOT in REPOS [95760ff]",
|
||||
"95760ff" in t_prior_art({"term": "LFS"}))
|
||||
chk("prior_art reports the register count alongside the commits "
|
||||
"[the asymmetry IS the finding]",
|
||||
"register mentions" in t_prior_art({"term": "LFS"}))
|
||||
chk("prior_art on a nonsense term reports no prior art, and calls the absence weak "
|
||||
"[negative control]",
|
||||
"No prior art" in t_prior_art({"term": "zzqqxx-not-a-real-term-9971"}))
|
||||
chk("prior_art refuses an empty term", t_prior_art({}).startswith("ERROR"))
|
||||
chk("repo_activity refuses an unlisted repo",
|
||||
t_repo({"repo": "/etc"}).startswith("ERROR: unknown repo"))
|
||||
chk("repo_activity ACCEPTS a listed repo [positive control]",
|
||||
@@ -740,6 +822,42 @@ def selftest():
|
||||
src = open(__file__, encoding="utf-8").read()
|
||||
chk("no filesystem-mutating call in this file",
|
||||
write_calls(src) == [])
|
||||
# ⚠ The guarantee is scoped to the file the AST reads. Adding prior_art (PENDING-164
|
||||
# (c)) put a DELEGATION outside that scope: t_prior_art calls into prior-art.py, whose
|
||||
# code this check never saw. A structural guarantee with a hole where it delegates is
|
||||
# the shape of every other defect in this thread — so the delegate is checked too, and
|
||||
# any future delegate must be added here or the guarantee silently narrows.
|
||||
# Each delegate must have NO mutating call, except in functions DECLARED here as
|
||||
# unreachable from this server. Declared, never inferred — the same shape as the hook
|
||||
# allowlist: a new mutating function in a delegate fails until someone names it and
|
||||
# says why. wake-digest.py is also a SessionStart hook, and emit_brief() is its hook
|
||||
# role; no tool in this file calls it.
|
||||
_delegates = {"prior-art.py": set(), "wake-digest.py": {"emit_brief"}}
|
||||
for _fn, _exempt in sorted(_delegates.items()):
|
||||
_dsrc = open(os.path.join(SCRIPTS, _fn), encoding="utf-8").read()
|
||||
_tree = __import__("ast").parse(_dsrc)
|
||||
_spans = {f.name: (f.lineno, f.end_lineno) for f in __import__("ast").walk(_tree)
|
||||
if isinstance(f, __import__("ast").FunctionDef)}
|
||||
_bad = []
|
||||
for _call in write_calls(_dsrc):
|
||||
_ln = int(_call.rsplit(" ", 1)[-1])
|
||||
_in = [n for n, (a, b) in _spans.items() if a <= _ln <= (b or a)]
|
||||
if not any(n in _exempt for n in _in):
|
||||
_bad.append(f"{_call} in {_in or ['<module level>']}")
|
||||
chk(f"DELEGATE {_fn}: no mutating call outside its declared exemptions {sorted(_exempt) or '(none)'} "
|
||||
"[the read-only guarantee must not stop at this file's edge]",
|
||||
_bad == [], )
|
||||
if _bad:
|
||||
for _b in _bad:
|
||||
print(f" {_b}")
|
||||
chk("the delegate check DOES flag an undeclared mutation [positive control — an "
|
||||
"exemption list that never refuses is not a check]",
|
||||
write_calls("import os\nos.remove('x')\n") != [])
|
||||
chk("str.replace() is NOT flagged as a mutation [negative control — it was, and that "
|
||||
"false positive is why this guarantee had never been extended]",
|
||||
write_calls("s = 'a'.replace('a','b')\n") == [])
|
||||
chk("os.replace() IS still flagged [positive control for the narrowing above]",
|
||||
write_calls("import os\nos.replace('a','b')\n") != [])
|
||||
chk("the checker DOES flag writes when present [positive control — a text search "
|
||||
"here would match its own token list, which is how the first version of this "
|
||||
"check failed]",
|
||||
|
||||
Reference in New Issue
Block a user