R-315: wire-contract gate checks mirrored roots field-by-field and prints each root's check kind
The escrow/retained and escrow-ACK wires are now compared path-by-path against the receiver's named mirror type, so the measured mutation (agent renames superseded_at, the old name still a local-API map key) convicts. Decoy pair in test_gate_decoys.py, seen failing with the mirror removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -47,6 +47,9 @@ gate must publish its holes.
|
||||
This makes the gate conservative: it under-reports and does not over-report.
|
||||
2. IT PROVES REACHABILITY OF A NAME, NOT THAT ANYTHING ACTS ON THE VALUE. A tag mentioned once in
|
||||
a struct nobody consults passes. It answers "can this be decoded at all", not "is it used".
|
||||
R-315: a root listed in MIRRORS gets a FIELD-BY-FIELD check against the receiver's named mirror
|
||||
type instead (a renamed receiver tag convicts even when the old name occurs elsewhere); every
|
||||
run prints which check each root got. The other roots are still name-checked only.
|
||||
3. ROOTS ARE DECLARED, NOT DISCOVERED. Only the wires in ROOTS are covered. The hub's
|
||||
desired-state is served as raw stored JSON (`host.DesiredJSON`) with no typed emitter to walk,
|
||||
and the agent's local API has no single root type — NEITHER IS COVERED.
|
||||
@@ -89,6 +92,19 @@ ROOTS = [
|
||||
"hub", "internal/api", "RetainedEscrowResponse", "agent"),
|
||||
]
|
||||
|
||||
# R-315: a root whose receiver decodes it into ONE NAMED MIRROR TYPE gets the stronger check —
|
||||
# field-by-field: every emitted dotted path must be a json path of the mirror type (generic names
|
||||
# included, since a path comparison is exact). The name-reachability check below could not see a
|
||||
# renamed receiver tag when the old name occurred anywhere else in the receiving repo (measured: the
|
||||
# agent's `superseded_at` renamed still passed, because the local API used the same string as a map
|
||||
# key). Roots NOT listed here keep the weaker name check, and the run prints which check each root
|
||||
# got — a green on a name-checked root is not decodability. Keyed by root label:
|
||||
# (receiver package dir, receiver type).
|
||||
MIRRORS = {
|
||||
"hub -> agent (GET /hosts/<id>/escrow/retained)": ("internal/hub", "RetainedEscrowResponse"),
|
||||
"hub -> controller (report ACK, `escrow` object)": ("internal/report", "EscrowStatus"),
|
||||
}
|
||||
|
||||
# Tag names whose literal string carries no information in a repo-wide search. NOT CHECKED.
|
||||
# Listed rather than silently skipped: each one is a hole.
|
||||
GENERIC = {
|
||||
@@ -574,6 +590,7 @@ def run(root_override=None, quiet=False):
|
||||
# one pass per receiving repo, not one subprocess per tag
|
||||
rtokens = {k: receiver_tokens(v) for k, v in repos.items()}
|
||||
convictions = []
|
||||
kinds = [] # R-315: (root label, which kind of check it got) — printed on every run
|
||||
checked = skipped = 0
|
||||
|
||||
for label, emitter, pkgdir, rootname, receiver in ROOTS:
|
||||
@@ -588,6 +605,31 @@ def run(root_override=None, quiet=False):
|
||||
seen_tags.setdefault(tag, dotted)
|
||||
opaque_roots = [p for (lbl, p) in OPAQUE_BELOW if lbl == label]
|
||||
missing = []
|
||||
if label in MIRRORS:
|
||||
mdir, mtype = MIRRORS[label]
|
||||
rby_dir, rby_name = indexes[receiver]
|
||||
if (mdir, mtype) not in rby_dir:
|
||||
die("wire-contract gate INCONCLUSIVE: declared mirror %s.%s not found in %s/%s\n"
|
||||
" A mirror that cannot be resolved is not a pass — fix MIRRORS or the type."
|
||||
% (receiver, mtype, receiver, mdir))
|
||||
mirror_paths = {d for _, d in walk(rby_dir, rby_name, mdir, mtype)}
|
||||
emitted = sorted({d for _, d in tags})
|
||||
n_root = 0
|
||||
for dotted in emitted:
|
||||
if (label, dotted) in ALLOWLIST or any(
|
||||
dotted.startswith(o + ".") and dotted.count(".") > o.count(".") + 1
|
||||
for o in opaque_roots):
|
||||
skipped += 1
|
||||
continue
|
||||
checked += 1
|
||||
n_root += 1
|
||||
if dotted not in mirror_paths:
|
||||
missing.append((dotted.split(".")[-1], dotted))
|
||||
kinds.append((label, "FIELD-BY-FIELD against %s %s.%s (%d path(s))" % (receiver, mdir, mtype, n_root)))
|
||||
if missing:
|
||||
convictions.append((label, receiver, missing))
|
||||
continue
|
||||
kinds.append((label, "name-reachability only (a tag found ANYWHERE in %s passes)" % receiver))
|
||||
for tag, dotted in sorted(seen_tags.items()):
|
||||
if tag in GENERIC:
|
||||
skipped += 1
|
||||
@@ -609,6 +651,8 @@ def run(root_override=None, quiet=False):
|
||||
if not quiet:
|
||||
print("wire-contract gate — %d tag(s) checked across %d declared wire(s); "
|
||||
"%d skipped (generic / opaque / allowlisted)" % (checked, len(ROOTS), skipped))
|
||||
for label, kind in kinds:
|
||||
print(" %-52s %s" % (label, kind))
|
||||
if convictions:
|
||||
if not quiet:
|
||||
for label, receiver, missing in convictions:
|
||||
|
||||
Reference in New Issue
Block a user