Skip to content

id-377 {377.6} — efficacy-trial gaps register (merged)

{377.6} efficacy trials — merged gaps register

Section titled “{377.6} efficacy trials — merged gaps register”

Synthesis of two independent efficacy trials run against real parked backlog, both independently verified by a second agent. Corpus: canonical @ ast-dataflow-efficacy-trials, head 8b397be1, working tree clean. Read-only — no canonical edits in either trial or in this synthesis.

trialtaskshapelog
Aid-292CV-literal rename-residual sweep (bid→procurement)trial-a-id292-bid-literals.md
Bid-317vocabulary/reference retirement sweep (content_items)trial-b-id317-content-items.md

Naming correction (resolves an S510 retro item). The S510 continuation prompt’s bl-292 / bl-317 are ordna tasks id-292 and id-317 — a loose legacy bl- prefix, not a separate identifier space. The retro’s “no discoverable definition” item is closed by this note. Same applies to bl-495 = the live-breakage slice promoted out of id-317 (shipped as PR #124).


Ready with caveats — as a precision instrument, not as an inventory instrument.

ast-dataflow is cleared for the codebase-cleanup effort where the question is “is this specific thing still there / still reachable?” and is not cleared where the question is “how many sites are there?”. Four caveats, each measured, each binding until the named gap lands:

  1. Never quote a column-writes count. 18.5 % pooled false positives over 65 hand-checked sites; 44 % of returned rows come back for any column name. Open every site, or wait for G8.
  2. Never read a string-literal-uses zero as absence. It means “no call-site-argument literal” — comparisons, property values and array elements are dropped silently. Measured 0/24 on id-317’s W4 (G1).
  3. Never read any zero as corpus-covered. Membership is transitive, unpredictable and unstable — a fixed query silently changed meaning across an unrelated refactor (G12).
  4. schema-coverage --evidence is the one surface cleared for verdict-grade use — run it with the sidecar or not at all. It is also the only surface that handles the G8 counter correctly.

What the tool did drive end-to-end, unaided: id-317’s W5 MCP tool-name sweep (36 sites, zero noise), the named-symbol half of W2 (references 5/5, 11/11, 8/8 with comments and string labels correctly excluded and no bleed into three same-named local declarations), correct-zero confirmations of retirement (id-292 class 5 and class 8), the loud unknown_table on a retired table, and the cross-language schema verdict (183/807 columns re-verdicted, 6 moved out of unwired into live).


Part 1 — Tool gaps (owner: {377.2}/{377.4}/{377.5} tool-work)

Section titled “Part 1 — Tool gaps (owner: {377.2}/{377.4}/{377.5} tool-work)”

Ranked by impact × effort. Impact asks one question: does the gap produce a WRONG verdict, or merely a slow/awkward one? Every wrong-verdict gap outranks every ergonomics gap. “Two-directional” marks a gap independently hit by both trials from different task shapes — the strongest signal in the register.

rankidimpactefforttwo-dirone-line
1G8wrong verdict — measuredlow–med✅ (A-verifier + B, B-verifier)column-writes fabricates column attribution
2G1wrong verdict — silent zerosmedstring-literal-uses drops the dominant literal shapes
3G2+G3makes every other gap undetectablelowzeros and unknown tables/columns carry no caveat
4G12 (⊃G6)wrong verdict — retroactivelylow (disclose) / high (policy)corpus membership is transitive, unpredictable, unstable
5G11coverage hole — largest measuredlowno identifier-text query exists
6G9blocks provable closuremedreferences has no member-level resolution
7G4wrong error kind on the house idiomlowenum-uses blind to as-const arrays
8G10ergonomicslowreferences truncation has no narrowing path
9G5ergonomicslowfixture-uses is whole-token only
10G7throughputlow~5–12 s cold load per literal query

Rank 1 — G8: column-writes fabricates column attribution

Section titled “Rank 1 — G8: column-writes fabricates column attribution”

The register’s headline. Not an ergonomics wart: it is a correctness defect in the query that prior guidance told readers to trust, and it is what refuted Trial A’s original “column-reads/column-writes are the stars, zero over-reports” scorecard line.

Measured, twice, independently, on disjoint column sets:

measurementsitesfalserate
Trial B (B17) — record_lifecycle.lifecycle_type, notifications.entity_type, notifications.read_at17423.5 %
Trial B verifier — source_documents.publication_status, q_a_pairs.answer_standard, form_instances.workflow_state48816.7 %
pooled — 6 columns / 5 tables, every site opened651218.5 %

Sharpest repro — the column argument carries no information at all. column-writes --table source_documents --column zzz_not_real returns 10 hits for a column that does not exist, each stamped columnPath: "zzz_not_real". Ten of the twelve real publication_status rows are in that same set, so only 2 of 12 are column-attributable. Noise floors: source_documents 10, q_a_pairs 7, form_instances 4, record_lifecycle 4, notifications 3 — 21 of 48 rows (44 %) come back for any column name whatsoever.

Two mechanism findings the fix must absorb:

  • confidence does not discriminate. All 65 pooled rows are indirect and isTyped: false; the path that would emit exact (inspectWriteArg, column-writes.ts:106-173) requires a typed client and detectIsTyped returns false on every .from() chain probed. In this repo every row is indirect, so the label separates nothing. Repo-wide only 115 of 807 columns have exactWrites>0 against 616 with indirectWrites>0.
  • The one-hop chase is too narrow. resolveOneHopObjectLiteral follows only const x = { … }. .insert(arr.map(…)), loop-built identifiers and satisfies/as-wrapped literals all fall through to “cannot rule out”, which is why fixture builders dominate the false set.
  • Smoking gun: lib/notifications.ts:96 is returned for both notifications.entity_type (true) and notifications.read_at (false) — the same site attributed to two different columns.

The FP rate is not uniform — it tracks how many of a table’s columns a typical payload omits, so wide optional-column tables (facets) are worst and NOT-NULL-heavy tables look clean. That is exactly backwards from what a retirement wave needs.

Proposed fix. Give writes the wildcard honesty reads already have: when the queried column is not provably a key of the payload, emit columnPath: "*" + confidence: "wildcard" (or attributed: false with an explicit “writes an unattributed column of <table> message), and stop the count reading as a per-column answer. A --include-indirect flag can preserve today’s behaviour for callers who want the superset. A reference implementation already exists in this codebase on the Python side: an attributable declare_row payload key is exact, an unattributable resolved site emits a table-scoped * smoke row plus a loud caveat. The fix is DR-102’s declaration-vs-proof discipline applied to column-writes’ presentation layer.

Contamination audit — does any already-shipped conclusion rest on this?

Section titled “Contamination audit — does any already-shipped conclusion rest on this?”

Asked of every consumer of column-writes output found across the docs-site and the skill surface.

artefactrests on column-writes?verdict
{377.4} evidence merge (schema-coverage --evidence, PR #150)consumes the same counter, does not promote itCLEAN — no re-derivation. Independently re-verified: 585 rows (plain) / 522 (evidence) have indirect-only write evidence and 0 are verdicted wired/write-only — all stay undecidable. All 6 unwired→live flips rest on confidence: exact sqlglot rows (pair_resolver.py:181, flow.py:2295/2307), confirmed at source.
DR-102 (declaration ≠ write proof)noHOLDS. Re-verified directly: 0 of 807 rows carry verdict wired with all five evidence counters zero.
initiative-12 census recipe (Discipline ¶)no — the recipe is ast-dataflow-py schema-usesschema-coverage --evidenceUNAFFECTED.
id-292 / id-317 task Notesyes, quotedALREADY CORRECTED. Both Notes were written post-G8 and carry the caveat plus hand-verified site lists.
id-69 RESEARCH.md §3.3 + PRODUCT.md BI-2/L206yes — and the reasoning is invalidCONTAMINATED but MOOT — no re-derivation required. See below.
canonical .claude/skills/ast-dataflow/SKILL.md:178-189yes — “every TS file that writes a Supabase column”WRONG AS WRITTEN — live guidance. Needs a caveat now (proposal C1).
canonical .claude/skills/write-tech-spec/SKILL.md:83-86yes — “the correct scope for migration-safety analysis”WRONG AS WRITTEN — live guidance. Needs a caveat now (proposal C1).

id-69, in detail. Two distinct problems, both real, neither actionable:

  1. RESEARCH.md §3.3 concluded an “identical, exhaustive writer set (8 sites)” from running column-writes --table content_item_workspaces on --column workspace_id and --column content_item_id and getting the same answer. Under G8, identical-across-columns is the fingerprint of the defect, not corroboration of exhaustiveness — a table-level result is invariant in the column argument by construction. The conclusion probably survives on its merits (content_item_workspaces is a narrow junction table, and G8’s FP rate tracks omitted optional columns, so the floor there is near zero) — but the evidence offered for it proves nothing.
  2. PRODUCT.md BI-2 (L200-206) makes the negative case an acceptance criterion: “No writer sets source_documents.workspace_id on the canonical path (verifiable by ast-dataflow column-writes --table source_documents --column workspace_id showing no canonical-path writer).” That check is not executable as written: source_documents has a measured 10-row noise floor for any column name, so the query can never show zero. The criterion would have to be discharged by opening all ten.

Neither needs re-derivation: id-69 is archived (closed S371) and both subject tables (content_items, content_item_workspaces) were eliminated by id-131. The finding is recorded so that (a) nobody resurrects BI-2 as a runnable gate, and (b) the “identical across two columns ⇒ exhaustive” inference is retired as a reasoning pattern.

Rank 2 — G1: string-literal-uses drops the dominant literal shapes

Section titled “Rank 2 — G1: string-literal-uses drops the dominant literal shapes”

Two-directional, and the only gap that produced silent zeros over load-bearing production code in both trials. The query classifies five call-site contexts (viMock / jsxProp / sqlTag / envKey / argument) and drops comparisons, object-property values, array elements, type-position literals, return statements and const initializers. Intentional by design (queries/string-literal-uses.ts:118-120, “call-site context search, not raw text search”) — and exactly wrong for the CV / rename / retirement sweeps that are the cleanup effort’s whole shape.

Measured: 6 of id-292’s 8 classes under-report, three to zero, including lib/freshness.ts:103 (=== 'bid_discovered', load-bearing) and lib/layer-inference.ts:94 (=== 'bid_library', the load-bearing inference rule). id-317: 0 of 24 W4 entityType: 'content_item' property-value sites, 0 of 7 W3(a) ['content-items'] array elements, 0 of 6 pipelineName: 'mcp_create_content_item' telemetry values.

Fix: add comparison, caseClause, propertyValue, arrayElement, typeLiteral, initializer kinds (or an --all-contexts flag defaulting on), keeping today’s set as a filter. Trial B raises the priority: property-value is the single most common persisted-enum shape in this repo. For comparison, carry the LHS expression text (args.type, lifecycle_type) — that is what makes load-bearing-vs-stale triage mechanical rather than manual (O2).

Both trials independently reached the same secondary conclusion, which is the reason to fix rather than abandon this query: with G1’s kinds, it would beat rg. Its silence on 221 comment-only files is exactly right, its 10 correct suppressions of ESLint RuleTester code: fixture strings are noise rg cannot filter, and its correct-zero on from_bid (survivors are comments) is the proof.

Merged: one fix, one reference implementation, and together they are what make every other gap in this register undetectable by the user.

  • G2 — zero results carry no caveat. Neither the kind filter (G1) nor corpus membership (G12) is stated in any response, so “no sites exist”, “no sites in the shapes I search” and “your target isn’t in my corpus” are structurally identical payloads.
  • G3column-reads/column-writes on an unknown column or an unknown table return silent [], while schema-coverage — same binary, same generated types — reports unknown_table loudly with a hint. Trial A hit this live on the dropped workspaces.type, where “0 readers” and “the column was deleted” were indistinguishable. Refinement the fix must handle: for an unknown column on a real table the two queries diverge — column-reads returns honestly-labelled columnPath: "*" / confidence: "wildcard" rows, while column-writes returns rows falsely stamped with the bogus column name. “Silent []” understates column-writes: it fabricates (this is the same defect as G8, reached from the other side).

Fix (O3): treat schema-coverage’s response envelope as the house style and retrofit it to every query — caveats block (kinds searched, corpus summary, excluded surfaces, merged sidecars), loud unknown_table/unknown_column, and a summary histogram. No new analysis is required; the good implementation already ships. Lowest effort, highest leverage in the register.

Rank 4 — G12 (widening of G6): corpus membership is unstable

Section titled “Rank 4 — G12 (widening of G6): corpus membership is unstable”

The boundary is not “scripts are excluded”. tsconfig.exclude lists scripts, yet 51 of 82 scripts/*.ts are in corpus via transitive import from __tests__/scripts/*; scripts/eval-classification.ts is IN, scripts/quality-gate.ts is OUT, with no way to tell from the CLI.

Worse, and this is the finding that promotes G6 to G12: membership is not stable across unrelated refactors. Proven by a detached worktree at 74a56c7d (PR #124’s parent) running that commit’s own binary: string-literal-uses --value content_items returned 27 hits / 11 files exactly, of which 17 came from scripts/quality-gate.ts. Today the same query returns 7. PR #124 rewrote that file and its test, dropping it out of the import graph — so a fixed query silently changed meaning because of a refactor that had nothing to do with it. (Ruled out as a methodology artefact: string-literal-uses.ts has two commits and the later one only swaps in truncateSpatial; classifyLiteralKind and the five kinds are untouched.)

Fix: short term, a CLI corpus-info (MCP-only today) plus a per-response corpusSummary (file count, top-level dirs, and whether the --file/--symbol target is a member) — fold into the G2/G3 envelope work. Long term, the pin-or-declare-the-corpus decision, which is already open inside {377.4}; the trials supply the evidence that it is a correctness question, not a tidiness one.

Rank 5 — G11: no identifier-text query exists

Section titled “Rank 5 — G11: no identifier-text query exists”

The largest pure coverage hole measured. The dominant residual shape in a vocabulary retirement — content_items: response keys, content_item_count, content_item_id — is 24 files on the content_items needle alone and 306 TS files across the three needles, and is addressable by neither string-literal-uses (not a literal) nor references (no single declaration; the same key is an independent member of ~10 unrelated interfaces).

Fix: identifier-uses --name <ident> over Identifier / PropertyAssignment / PropertySignature nodes — cheap, no type resolution. Route through {377.2} first (see the corpus-gate note below): rg already answers this, and an LSP “find references” partly does, so it is precisely a build-vs-integrate question rather than an obvious build.

  • G9references has no member-level resolution: file.ts:propName for an interface/type property errors out_of_corpus, the wrong kind (same mis-kind as G4 — “unsupported granularity” reported as “not in corpus”). This is what blocked provable closure on both of id-317’s atomic-set problems (the W2 contentItemId write-back+sweep pair; W3(a) queryKeys.contentItems) — the exact case where a rename must be provably complete. Fix: support File.ts:Interface.member addressing; distinguish unsupported_granularity from out_of_corpus.
  • G4enum-uses supports enums and as-const objects but not as-const arrays, which is the actual KH CV convention. Live FINAL_AWARD_FORM_TYPES (lib/validation/schemas.ts:1140) and VALID_PUBLICATION_STATUSES (lib/governance/publication-transitions.ts:31) both error out_of_corpus. 3 of 3 probes failed in Trial A. Cheap.
  • G10references on a hub object returns 200 rows with truncated: true and totalEstimated: 285; the count is honest, but there is no --offset/--limit and no hint, so the 85 unseen rows are unreachable and closure is unprovable. Fix: pagination + a hint pointing at member-scoped addressing (G9).
  • G5fixture-uses --needle is whole-token exact-match; bid_library inside "scripts/import_bid_library.py" is invisible with no flag to widen. Fix: --substring, or substring-by-default with a matched field showing the containing token.
  • G7 — ~0.5–1.4 s for references but 5–12 s per string-literal-uses (cold project load dominates); Trial A spent ~2.5 min on redundant loads across 20 queries. Fix: accept multiple --value args per invocation to amortise one load across a literal set. Explicitly cheaper than registering the MCP server early, which DR-100’s rider defers to extraction.

Opportunities (not gaps — no wrong answer, but real leverage)

Section titled “Opportunities (not gaps — no wrong answer, but real leverage)”
idwhattrigger
O1No query connects a const to its literal value (LAYER_BID_DETAIL'bid_detail'). references tracks the symbol, string-literal-uses the string, nothing joins them. Fix: carry literalValue on references rows for string-const declarationsTrial A R7
O2Load-bearing-vs-stale classification stays human judgment. A comparison kind carrying the LHS expression text gets most of the wayTrial A R11
O3schema-coverage is the reference implementation for output ergonomics. Retrofitting its envelope closes G2 and G3 with existing codeTrial B B5/B7
O4unwired is under-qualified — 6 of 85 plain-run unwired verdicts (7 %) are live Python-written columns. The caveat names the Python pipeline but the verdict token reads as “dead”. Fix: unwired-in-ts, or a per-row externalSurfacesUnmeasured: true, whenever no sidecar was mergedTrial B B8
O5The absence-guard shape (expect(tables.has('content_items')).toBe(false)) is a first-class artefact of any retirement task and is currently invisible. Once G1 lands, a --kind comparison,arrayElement sweep becomes a mechanical way to find absence guards — worth a documented recipe in the skillTrial B B3/B16

Hard-case corpus check — and why it could not adjudicate 11 of 12 gaps

Section titled “Hard-case corpus check — and why it could not adjudicate 11 of 12 gaps”

Every proposal above was checked against the 25-hard-case acceptance corpus (research/declarative-surface-inventory.md). Result, stated plainly because it is itself a finding:

proposalnewly coversdoes NOT cover
G8 (wildcard-honest write attribution)the TS analogues of hard cases 7 (payload built in a loop into a dict), 8 (** spread inside a value), 15 (comprehension/.map() payload to .insert/.upsert), 24 (splatted parameter tuple built earlier) — all four are “the payload is not a resolvable literal”, which is precisely the class resolveOneHopObjectLiteral falls through on16 (on_conflict= column strings) and 17 (chain-modifier-only mentions) stay unaddressed — they are a detector question, not an attribution one
G1, G2, G3, G4, G5, G7, G9, G10, G11, G12none of the 25

The gate is mis-scoped, not the proposals. The 25 hard cases are a Python declarative-write acceptance corpus, built for {377.4} against scripts/cocoindex_pipeline/**; ten of the twelve gaps are TypeScript-surface detector and output-contract gaps it was never built to adjudicate. G12 is a particularly clear illustration: the corpus cannot speak to corpus instability at all, because ast-dataflow-py scans a directory and never touches tsconfig reachability, so the failure mode does not exist on that side.

Applying “reject proposals that cover nothing in the corpus” literally would reject eleven of twelve proposals, ten of which are backed by direct measurement on real backlog (18.5 % FP over 65 opened sites; 0/24 detection; a proven retroactive meaning-change). Nothing is rejected on corpus grounds. The trials’ own measurements are the stronger gate, and the honest conclusion is the missing artefact:

Proposal T1 — build the TS-side acceptance corpus. There is no TypeScript equivalent of declarative-surface-inventory.md, which is why the TS queries shipped with these gaps undetected. The two trials already produced the seed: 65 hand-checked column-writes sites with per-site verdicts, the 345-file content_items decomposition (7 visible / 17 literal-wrong-position / 24 identifier / 221 comment-only / 8 out-of-corpus / 68 non-TS), and 8 id-292 literal classes with per-class ground truth. Signed off 2026-07-29 and folded into {377.7} as its first deliverable — not a separate record, because the corpus gap is what let the other six gaps ship undetected, so it gates the detector work rather than sitting beside it.

gapfolds intowhy
G8{377.4}It is the write-attribution layer DR-102 already governs; the Python side implements the correct discipline and the TS presentation layer does not
G12 (⊃G6){377.4}Already open there as the corpus pin/declare decision — the trials add the instability proof that makes it a correctness item
G11{377.2}rg and LSP find-references already answer identifier search; “do NOT replicate what exists” applies directly — integrate/wrap/build is the gate
G1 (comparison kind vs Postgres LS / category-5 tooling){377.2} then buildWorth one integrate-check before building six kinds; the rest of G1 has no external substitute
G2, G3, G10, G7{377.5}Output-contract and CLI-ergonomics work that must land before extraction — you do not ship a package whose queries disagree on their error contracts. Pairs with the /mcp-builder audit already in {377.5}
G4, G5, G9, O1, O2, O5{377.7}Minted 2026-07-29 on owner sign-off as the TS detector-coverage wave — no existing subtask covers TS detector granularity. T1 is folded in as its first deliverable, not a separate record: the missing TS acceptance corpus is why these six shipped undetected, so it gates the detector work
O4{377.4}unwired qualification is an evidence-semantics question, same family as DR-102

Part 2 — Cleanup findings (owner: the codebase-cleanup effort / the named task)

Section titled “Part 2 — Cleanup findings (owner: the codebase-cleanup effort / the named task)”

Deliberately separated from Part 1: different owners. Nothing here is a tool change; nothing in Part 1 is a codebase change. All of the following is already written into the relevant task file’s Notes except where marked NEW.

id-292 (bid→procurement literal residue) — stays parked, re-scoped

Section titled “id-292 (bid→procurement literal residue) — stays parked, re-scoped”
  • Class 2 grew a coupled surface. lifecycle_type moved to the record_lifecycle facet (id-131). The id-131 freshness RPC (20260702130000_id131_freshness_rpcs.sql:95) value-compares 'bid_discovered' in SQL, so the “backfill UPDATE + TS literals atomically” plan must also patch the compute_freshness-family RPC bodies. Real TS write surface is 2 sites (lib/mcp/tools/content.ts:1226, e2e/fixtures/test-data-fixture.ts:305), not the 5 column-writes reported — the other three write governance columns only (G8). Pipeline reads it at l_records.py:1194.
  • Class 7 is cheaper than written, and narrower. The workspaces.type TEXT column is dropped (discriminator is application_type_id), so “the DB still emits the alias” no longer holds. The surviving alias is one MCP tool (lib/mcp/tools/workspaces.ts:36,76) — retirement is an MCP-deprecation decision, not a DB data migration.
  • Stale-comment cleanup candidate (NEW, standalone). app/api/workspaces/route.ts:140 carries a comment describing a legacy-alias mapping that does not exist in the code below itWorkspaceCreateBodySchema.type is z.enum(getValidTypeValues()) and lib/workspace-types.ts:23-30 returns six keys, none of them 'bid', so the route rejects the alias at validation. The comment documents intent that was never implemented or was later removed. Delete it independently of any class-7 decision — it actively misleads.
  • KEEP-list entry is moot. VALID_BID_STATUSES / TERMINAL_BID_STATUSES have zero repo-wide hits and types/bid.ts no longer exists. The other two KEEP entries (bid_response reorient tag; 'bid' form_types CV) are live and stay protected.
  • Stale details-field refs (NEW): app/api/items/route.ts no longer exists and lib/queue/handlers/batch-reclassify.ts no longer carries the literal — both are still named in id-292’s Details field.
  • Net: 5 live classes (1, 2, 3, 4, 6) + premise-changed 7; class 5 resolved, class 8 gone. Priority unchanged.

id-317 (content_items retirement) — stays parked, two waves decayed

Section titled “id-317 (content_items retirement) — stays parked, two waves decayed”
  • W0 RESOLVED — bl-495 shipped as PR #124 (c6d28d2a + a0178af4). Zero live .from('content_items') repo-wide; survivors are 10 ESLint RuleTester fixture strings plus comments.
  • W3(a) DECAYED — and carries a live invalidation hazard. Five code consumers, not four: the plan misses components/review/PublicationReviewQueue.tsx:174. Two stale line refs (use-library-bulk-actions 88→104, publication-review-action-bar 114→117). Hazard: ['content-items'] is built raw, outside the key factory, at __tests__/lib/content-browsing/use-content-bulk-runner.test.ts:34,112 — an invalidation that silently misses is exactly the failure mode W3(a) exists to prevent.
  • W4 DECAYED — badly under-inventoried. The plan named 3 entity_type='content_item' sites; the actual count is 24 property-value sites across 11 files (cron freshness-transitions ×9, review-cadence ×4, quality-score ×2, classification-quality, source-documents/[id]/send-to-review ×2, content-owners/bulk-assign, review/assignments, governance/review, plus review.ts:403, governance.ts:1306, content.ts:1871). All three plan-named sites have moved. Backfill still not needed (S483 finding unchanged).
  • Three production files were mis-filed as prose (NEW, carried from the Trial B verification pass). They ship live content_items strings at runtime: lib/mcp/tools/review.ts:219 (MCP tool descriptionarguably W5 public wire copy), lib/mcp/tools/content.ts:477,484,491 (Zod .describe() argument help), lib/content/content-suggestions.ts:202 (sb() telemetry label 'content_items.forSuggestions'). By the plan’s own W5 standard — which counts pipelineName: 'mcp_create_content_item' as a residual — these are residuals.
  • 4th KEEP-FOREVER guard, TS-side: __tests__/scripts/propagation/payload-contract.test.ts:80-92 asserts content_items is excluded from PAYLOAD_CONTRACT. Add it to the list — it is invisible to a literal-driven sweep (array element, G1), so such a sweep would neither see nor protect it.
  • W1 holds (221 comment-only files incl. tests, 116 outside __tests__); W2, W3(b), W5 hold. W5 naming remains owned by id-71’s verdict process.

Tool-documentation cleanup (NEW — canonical repo, not docs-site)

Section titled “Tool-documentation cleanup (NEW — canonical repo, not docs-site)”

Proposal C1 — approved 2026-07-29 and dispatched separately. Out of this task’s edit scope (docs-site only), so recorded here rather than applied; the caveats land in the canonical repo.

Two live guidance surfaces state the G8 defect as fact and should carry a caveat until G8 lands:

  • .claude/skills/ast-dataflow/SKILL.md:178-189“column-writes — every TS file that writes a Supabase column”. It is not: it is every TS file that writes some column of that table, presented under the queried column’s name.
  • .claude/skills/write-tech-spec/SKILL.md:83-86 — directs spec authors to run column-writes on any column a spec touches, calling it “the correct scope for migration-safety analysis”. That is the exact use that produced id-69’s invalid §3.3 inference.

Minimal fix pending G8: one line in each — “counts are table-level; open every site before acting (id-377 {377.6} G8).”


Both trials ran the same protocol — tool answer, then independent rg/Read ground truth, then verdict — and both were audited by an independent second agent who re-derived every headline number from source rather than from the log’s own output. That audit refuted claims in both logs (Trial A’s “zero over-reports” on column-writes, which produced G8; Trial B’s quality-gate.ts corpus history, its totalEstimated claim, and four decomposition bucket counts). The corrected text is what this register consumes. Gap numbering is settled: G1–G7 Trial A, G8 Trial A’s verifier, G9–G12 Trial B.

Strengthened rather than doubted by the audits, and therefore safe to build on: the 27→7 decay is proven (detached worktree at 74a56c7d reproduces 27 hits / 11 files exactly), the 183/807 evidence flips reproduce exactly, DR-102 holds, both spot-checked unwired→live flips confirmed at source, and all four W-plan re-scores confirmed.