Skip to content

fix(spec-drift): close nine defects an adversarial review found in the freshness gate, two of them P1 - #239

Merged
jeremylongshore merged 1 commit into
mainfrom
fix/review-findings-freshness-gate
Jul 23, 2026
Merged

fix(spec-drift): close nine defects an adversarial review found in the freshness gate, two of them P1#239
jeremylongshore merged 1 commit into
mainfrom
fix/review-findings-freshness-gate

Conversation

@jeremylongshore

Copy link
Copy Markdown
Owner

What

Closes nine defects an adversarial review found in the freshness gate #237 shipped — two P1 — plus a provenance gap the review surfaced in its own evidence. Every finding reproduced first, fixed, then locked by a test that was mutation-verified by reverting the fix.

Why this review happened

Per the repo's own posture note, no AI reviewer runs here — Gemini is sunset, Greptile is dark. #237 merged green with a mutation-tested lock and exactly one pair of eyes. That was the real gap, and it cost two P1s.

The two P1s are the same bug this gate exists to end, re-entering by a door the lock didn't watch

F1 — the registry can name a checker in the wrong MODE

--surface is a top-level parser arg, so the wrong mode accepts and silently ignores it:

$ python3 scripts/extract-agent-definition-projection.py --check --surface totally-bogus
extract-agent-definition-projection --check: OK — committed projection is fresh.   exit=0

Swap --check-fresh--check in any semantic_coverage.checker and the driver prints an authoritative all-green board — including plugin-marketplaces, which has real outstanding findings:

  CLEAN  plugin-marketplaces  enforced     ← this surface has 3 real findings
projection-freshness: CLEAN (exit 0) over 5 field-level surface(s).

check-surface-registry.py returned exit 0 on that registry. So did the test suite. Both validated only that checker[0] is an existing file — never what the argv means.

That lands here specifically because the registry is the file humans are told to edit ("flipping a surface to failing is a one-line registry edit"). The next person who flips a surface, sees red, and "fixes" the flag gets a green board and no gate objects.

Fixed in two layers — the registry gate pins each checker's freshness-mode flag and rejects any other mode flag (--write included, which would have the gate mutate the baseline it guards), and the extractors now reject --surface outside --check-fresh instead of ignoring it.

Chose an explicit in-script allowlist over letting the registry declare its own mode, because a config that attests to its own correctness is the thing being defended against.

F2 — plugin-manifest asserted a silently truncated closed enum

No closedness guard at all:

input old output
One of `command` (Claude Code v2.1.200+), `http`, or `mcp_tool`. ['command']
One of `command`, `http`, or {/* min-version: … */}`mcp_tool`. ['command', 'http']

Same clause-truncation that broke permissionMode — but the three siblings behave oppositely: agent-definition and hook-config decline (residue guard); only this one produced confident nonsense.

A short enum is worse than an absence: the projection is the field-diff baseline, so the next report reads as a legitimate value removal and sends a human to reconcile a phantom. Not hypothetical — the captured plugins-reference page already carries MDX comments in table cells; it just hasn't landed on a One of row yet.

Root cause fixed, not papered over: a period followed by a digit is a version number, not a sentence end. One shared rule (captured_source.clause_end) makes all three cases correct:

One of `string`, `number`, `boolean`, `directory`, or `file`.   -> enum ['string','number','boolean','directory','file']
One of `command` (Claude Code v2.1.200+), `http`, or `mcp_tool`. -> candidates ['command','http','mcp_tool']
One of `command`, `http`, or {/* min-version… */}`mcp_tool`.     -> enum ['command','http','mcp_tool']

The other seven

# Defect Fix
F3/F9 driver exited CLEAN (exit 0) over 0 field-level surface(s) — contradicting its own docstring INOPERABLE on empty; declared semantic_coverage_floor so shrinking coverage is a reviewed edit, not a quietly smaller green board
F4 bare next()/json.loads on a malformed capture → exit 1 → driver read DRIFT, so the watcher would open a reconciliation issue for a parser breakage INOPERABLE; the 0/1/2 distinction preserved
F5 a 30-entry enum gaining one member rendered two sides byte-identical for all 400 visible chars — reader saw a differing byte count. Most likely real drift on claude-hooks, an enforced surface scalar lists diff element-wise → LIST_CHANGED: events.enum: +["WorktreeRemove"]
F6 True == 1, 1 == 1.0 — a bool becoming an int diffed to nothing type changes are always findings
F7 registry gate died with TypeError: unhashable type instead of naming the problem reported, not crashed
F8 re.DOTALL on the MDX stripper — inert today, and an unbalanced {/* opening a 16-line block exists in the captured mcp-config doc line-bounded, reason recorded

Plus a gap the review surfaced in its own evidence

Nothing verified that vendor-meta.json describes the bytes on disk. --check proves the projection derives from the files and never reads sha256 at all — so a hand re-vendor recording a wrong hash passed every gate while the provenance quietly described a file that wasn't there. Three reference docs were re-vendored by hand during this work, each editing sha256 and bytes in a text editor. That's the concrete shape of the risk.

New scripts/check-vendor-meta-integrity.py: every declared file exists, sha256 + bytes match, and no undeclared file sits in a capture dir (an input with no provenance is one vendor-meta edit from being parsed as authority). Wired into ci.yml + the scripts-tests job.

Demonstrated — corrupting a recorded sha fails the new gate while the gate people believe covers it still says OK:

vendor-meta integrity: sha256 MISMATCH — records deadbeef…, file is 504eb9c3…
extract-agent-definition-projection --check: OK — committed projection is fresh.

Verification

Check Result
all five extractors --check + --self-test OK — and no projection changed: the parser fixes are provably behaviour-preserving on today's pages
pytest scripts/tests/ -q 226 passed (33 new)
check-surface-registry / check-vendor-meta-integrity OK — 5 captures, 23 files
projection-freshness exit 0
detector-health / sak-dashboard --check OK
audit-harness verify OK after init

Mutation-tested by reverting each fix in turn — every one turns the suite red:

F1a mode not validated        4 failed
F1b --surface silently ignored 2 failed
F2  no closedness guard        2 failed
F3  empty map = clean          1 failed
F5  whole-value list render    1 failed
F6  no type guard              4 failed
F8  DOTALL restored            2 failed

The sweep also caught a real stale expectation in #237's own suite (test_each_diff_class_is_detected expected CHANGED_VALUE where element-wise diffing now correctly emits LIST_CHANGED). Updated the expectation, not the behaviour.

Risk

  • The two parser changes are the only behaviour changes to shipped output paths, and both are verified no-ops on today's pages (no projection byte changed). They alter output only on inputs that previously produced a wrong or silent answer.
  • semantic_coverage_floor is new registry state; check-surface-registry.py requires it, so a registry missing it fails loudly rather than silently disabling the floor.
  • Rollback is per-commit: every fix is independent and none share a file with another's core edit except captured_source.py (F5+F6, both in _walk).

Follow-up

  • The reviewer's suggested lessons entry — "a config-driven gate that validates the checker's EXISTENCE has not validated its MODE" — generalises past this repo and is worth landing in the estate lessons file.
  • hook-config's _enum_from_description still renders an added value as a vanishing enum (it declines rather than recording candidates). Deliberately not changed here: its clause selection falls back to the whole description, so open-candidates there produced 14 keys of pure noise when measured. Fixing it needs its clause selection anchored first.

Refs

Refs #237

…e freshness gate, two of them P1

#237 shipped green with a mutation-tested lock and still carried two P1s. An
adversarial review of the merged commit found nine defects; all nine reproduce,
all nine are fixed here, each with a regression lock that was mutation-tested by
reverting the fix.

The review happened because no AI reviewer runs on these repos — Gemini is sunset
and Greptile is dark — so #237 had exactly one pair of eyes. That was the actual
gap.

THE TWO P1s ARE THE SAME BUG THIS GATE EXISTS TO END, RE-ENTERING BY A DOOR THE
LOCK DID NOT WATCH

F1 — the registry can name a checker in the WRONG MODE. `--surface` is a
top-level arg, so `--check --surface X` is accepted, silently ignores the surface,
and exits 0 with "OK". Swap `--check-fresh` for `--check` in any
`semantic_coverage.checker` and the driver prints an authoritative all-green board
— including `plugin-marketplaces`, which has real outstanding findings. Verified:
check-surface-registry.py returned exit 0 on that registry, and so did the test
suite, because both validated only that `checker[0]` is an existing FILE.

That matters here specifically because the registry is the file humans are told to
edit ("flipping a surface to `failing` is a one-line registry edit"). The next
person who flips a surface, sees red, and "fixes" the flag gets a green board and
no gate objects.

Fixed in two layers: the registry gate now pins each checker's freshness-mode flag
(and rejects any other mode flag, `--write` included — that would have the gate
MUTATE the baseline it guards), and the extractors now REJECT `--surface` outside
`--check-fresh` rather than ignoring it. Chose an explicit in-script allowlist over
letting the registry self-declare its own mode, because a config that attests to
its own correctness is the thing being defended against.

F2 — `plugin-manifest` asserted a silently TRUNCATED closed enum. Its
`_enum_from_description` had no closedness guard at all:

    "One of `command` (Claude Code v2.1.200+), `http`, or `mcp_tool`."  -> ['command']
    "One of `command`, `http`, or {/* min-version: … */}`mcp_tool`."    -> ['command','http']

This is the same clause-truncation that broke permissionMode, but the three
siblings behave OPPOSITELY under it: agent-definition and hook-config decline
(residue guard), plugin-manifest asserted confident nonsense. A short enum is
worse than an absence — the projection is the field-diff baseline, so the next
report reads as a legitimate value REMOVAL and sends a human to reconcile a
phantom. Not hypothetical: the captured plugins-reference page already carries MDX
comments in table cells; it just has not landed on a `One of` row yet.

Fixed, and the root cause fixed properly rather than papered over: a period
followed by a digit is a VERSION NUMBER, not a sentence end. That one shared rule
(`captured_source.clause_end`) turns all three cases correct — the closed set stays
closed, and both truncating cases now recover all three values.

THE OTHER SEVEN

F3/F9  the driver exited "CLEAN (exit 0) over 0 field-level surface(s)" when the
       map was empty — flatly contradicting its own docstring. Now INOPERABLE,
       plus a declared `semantic_coverage_floor` so SHRINKING coverage is a
       reviewed edit rather than a quietly smaller green board.
F4     bare next()/json.loads raise on a malformed capture -> python exits 1 ->
       the driver read DRIFT, so the watcher would open a RECONCILIATION issue for
       a PARSER breakage. Now INOPERABLE, preserving the 0/1/2 distinction.
F5     a 30-entry enum gaining one member rendered two sides byte-identical for all
       400 visible characters — the reader saw a differing byte COUNT. That is the
       single most likely real drift on `claude-hooks`, an ENFORCED surface, i.e.
       unreadable exactly when it mattered. Scalar lists now diff element-wise.
F6     `True == 1` and `1 == 1.0`, so a bool becoming an int diffed to nothing.
       Type changes are now always findings.
F7     the registry gate died with `TypeError: unhashable type` on a list-valued
       `enforcement` instead of naming the problem.
F8     `re.DOTALL` on the MDX stripper is inert today and loaded tomorrow — an
       unbalanced `{/*` opening a 16-line block exists in the captured mcp-config
       doc. Now line-bounded, with the reason recorded.

PLUS A GAP THE REVIEW SURFACED IN ITS OWN EVIDENCE

Nothing verified that a vendor-meta.json describes the bytes on disk. `--check`
proves the projection derives from the FILES and never reads sha256 at all — so a
hand re-vendor recording a wrong hash passed every gate while the provenance
quietly described a file that was not there. Three reference docs were re-vendored
BY HAND during this work, each editing sha256 and bytes in a text editor, so that
is the concrete shape of the risk.

New scripts/check-vendor-meta-integrity.py verifies every declared file exists,
its sha256 and byte count match, and no UNDECLARED file sits in a capture dir (an
input with no provenance is one vendor-meta edit from being parsed as authority).
Wired into ci.yml + the scripts-tests job. Demonstrated: corrupting a recorded sha
fails the new gate while `--check` still prints "OK".

VERIFICATION

- All five extractors --check + --self-test OK, and NO projection changed — the
  parser fixes are provably behaviour-preserving on today's pages.
- pytest scripts/tests/ -q: 226 passed (33 new).
- Mutation-tested by reverting each fix in turn; every one turns the suite red:
  F1a 4 failed · F1b 2 · F2 2 · F3 1 · F5 1 · F6 4 · F8 2.
- The mutation sweep also caught a REAL stale expectation in the #237 suite
  (test_each_diff_class_is_detected expected CHANGED_VALUE where element-wise
  diffing now correctly emits LIST_CHANGED). Updated the expectation, not the
  behaviour.
- check-surface-registry OK; check-vendor-meta-integrity OK (5 captures, 23 files);
  projection-freshness exit 0; detector-health + sak-dashboard --check OK;
  audit-harness verify OK.

Refs #237
@jeremylongshore
jeremylongshore merged commit 421cd24 into main Jul 23, 2026
18 checks passed
@jeremylongshore
jeremylongshore deleted the fix/review-findings-freshness-gate branch July 23, 2026 02:46
jeremylongshore added a commit that referenced this pull request Jul 23, 2026
Brings in #239 (nine review defects) and #240 (CodeQL scoped to authored code),
the latter being what clears this branch's only remaining red check — a
high-severity js/incomplete-sanitization alert inside
specs/_vendor/mcp-spec-docs/snapshot.html, a captured third-party page this
branch carries as evidence and must not edit.

Verified after merging: no conflicts; projection-freshness exit 0 with all four
enforced surfaces CLEAN; pytest scripts/tests/ passes; the derived dashboards and
vendor-meta provenance all check OK.

# Conflicts:
#	specs/detector-health.json
jeremylongshore added a commit that referenced this pull request Jul 23, 2026
…field-level surface (#241)

Closes the loop opened in #237: all five field-level surfaces are now ENFORCED
freshness gates, and all five are clean.

    CLEAN  agentskills-spec     enforced
    CLEAN  claude-hooks         enforced
    CLEAN  plugins-reference    enforced
    CLEAN  sub-agents           enforced
    CLEAN  plugin-marketplaces  enforced      <- this change
    projection-freshness: CLEAN (exit 0) over 5 field-level surface(s)

WHAT WAS BLOCKING IT

Not a kernel fold-in — that framing was wrong and was corrected earlier. What
actually blocked it was capture skew: main carried a 2026-06-30 plugin-marketplaces
snapshot while the promotion PR (#236) carried 2026-07-23, and the findings were
measured against the newer one. Reconciling against the older bytes would have
turned an ENFORCED gate red the moment #236 merged — a DAILY red on the watcher,
which is precisely how a lane stops being read. #236 has now merged, so the
reconciliation lands against the bytes main actually holds.

THE FOUR FINDINGS, VERIFIED BEFORE THE RE-VENDOR, NOT ASSUMED

1. catalog.renames — object; former-name to current-name migration map; v2.1.193+
2. plugin_entry.relevance — object; org-allowlisted suggestion signals; v2.1.152+
3. relevance joins the documented marketplace_specific_fields list
4. catalog.reserved_names gains `first-party-plugins` and `healthcare`

Each has its own table row AND its own doc section on the captured page, and is
absent from the frozen one.

(4) IS THE MATERIAL ONE AND IS NOT BOOKKEEPING

The page now states Claude Code re-checks reserved names on EVERY marketplace
load, not only on add — so a marketplace already registered under a newly-reserved
name STOPS LOADING and reports an untrusted source. Before v2.1.205 it kept
loading. That is a live behavioural change to how a marketplace can fail.

Estate exposure was checked, not assumed: no marketplace.json under the projects
tree uses either new name. The IS marketplace is `claude-code-plugins-plus`, which
is not reserved — but it IS a suffix-extension of the reserved
`claude-code-plugins`, and the same paragraph also blocks names that "impersonate
official marketplaces" by prose judgement. Recorded as a watch item, not a break.

This finding is the clearest argument yet for the whole track: it is a real,
dated, operational risk to our own marketplace that surfaced only because a gate
could finally see current bytes.

NO KERNEL EDIT IMPLIED

upstream-base/marketplace-catalog.v1.json is a required-set FLOOR: it models
required:[name, owner, plugins] plus the constraints its negative corpus tests,
enumerates no optional plugin-entry fields at all, and leaves additionalProperties
permissive. Both new fields are optional, so they already validate; adding them
would make the schema LOOK current while remaining a floor.

PINNED EXPECTATIONS MOVED, WHICH IS THE MECHANISM WORKING

The extractor's self-test failed loudly on four hard-coded expectations
(8->9 top-level fields, 14->16 reserved names, 20->21 entry fields with
standard-metadata 12->13, and `renames`/`relevance` joining the
doc-fields-not-in-kernel divergences). Those sets exist precisely so a re-capture
that shifts a finding cannot pass silently and a human must reconcile. Updated
them as that human, with the reasoning recorded in vendor-meta and the registry
note rather than as bare number edits.

A TEST RETIRED HONESTLY

test_undispositioned_contracts_still_report_their_known_drift asserted
marketplace-catalog still reported DRIFT, and was written to fail if that went
green WITHOUT the registry flipping to `failing`. The registry flipped, so the
premise retired. Chose to fold the contract into
test_reconciled_contracts_are_clean and document the retirement in place, rather
than delete the test to make a suite pass. The anti-vacuity property it guarded is
unchanged: test_check_fresh_fires_on_a_perturbed_capture still proves the check
fires on real captured bytes per contract, and
test_the_real_coverage_map_runs_and_agrees_with_the_registry still requires every
`failing` surface to actually be clean.

VERIFICATION

- All five extractors --check + --self-test OK, including the reconciled kernel
  cross-check whose agreements AND divergences now match their updated sets exactly.
- projection-freshness exit 0, 5/5 enforced CLEAN.
- pytest scripts/tests/ -q: 226 passed.
- check-surface-registry OK; check-vendor-meta-integrity OK (5 captures, 23 files,
  every sha256 + byte count matching); detector-health and sak-dashboard --check OK;
  audit-harness verify OK after init.

Refs #236, #237, #239

Co-authored-by: jeremylongshore <jeremylongshore@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant