Repository navigation
Content-level sweep of the test farm's scan surfaces: which guards prove only that their surface is NON-EMPTY, rather than that it CONTAINS the thing the rule is about? (epic #1579) #1834
Description
Filed by the epic PM (session_01DuzfS5chho38Yx1jxx9DEj), 2026-09-09, on the maintainer's standing 「继续处理所有相关任务」. pm:epic reserves it for the epic PM.
⛔ This is NOT "vacuity sweep part 2". #1770 asked does the walk inspect a non-empty set? This asks the strictly stronger question: does the assertion pin the surface's CONTENT? A surface can be non-empty, real, and still structurally incapable of carrying what the rule looks for — and then the guard is silently vacuous while every count it prints looks healthy.
The precedent that defines the whole card — read it, ⛔ do not re-derive it
PR #1778 (merged 2026-09-08) repaired exactly this shape, and its ablation is the specification for what "proved" means here.
test/docs-object-term-consistency.test.ts reached the zh-CN language pack two ways. One resolved through the stack and worked. The other read one literal path, src/translations/zh-CN.ts — which the #1311 locale split had reduced to a 77-line re-export barrel, a file that cannot contain a label. The rule kept scanning it and could not fire. #1529's defect was re-injected verbatim into the live pack and the suite still reported 33 passed.
⭐ The line that matters, in that PR's own words: "A file count would not have caught this bug, because the barrel is still one real file, so the check is a cross-reference between the two mechanisms instead: the files being scanned must carry the label the working rule resolves through the stack." On the old surface that reading is 0.
⇒ Non-empty is not the property. Carries-the-thing is the property.
What to do
- Inventory, measured. Every scan surface in
test/**: the directory walks, the file lists, the hardcoded literal paths, the ledgers. For each: what does the guard assert about the surface itself, and would that assertion still hold if the surface had been silently emptied of the content the rule looks for? - Cross-reference where a second mechanism exists. test(i18n): point the #802 term guard's pack rule at the real zh-CN tree #1778's fix worked because the same file had a working rule whose resolved output could be demanded of the scanned files. Where such a pair exists, that is the strongest available check and the one to reach for.
- Where no pair exists, the honest check is that the surface contains at least one instance of the class the rule matches — and say so plainly rather than inventing a second mechanism to cross-reference against.
⛔ How NOT to do it — read this twice
- ⛔ Do not grow a gate farm. AGENTS.md scope rule 3 is binding, and it is the reason this epic exists. test(i18n): point the #802 term guard's pack rule at the real zh-CN tree #1778 is the model: no new
it, no new gate, the suite stayed at 33 tests — the assertion went into the existingthe ledger and the scan surface are realcheck, in the same block, in the same shape. Strengthen in place. ⭐ A PR that ends with more test cases than it started is probably the wrong answer here. - ⛔ Do not add a check to a surface that is already content-pinned. Measure first; most may be fine.
- ⛔ Do not fix a guard by widening what it scans unless the narrowness is the defect (in test(i18n): point the #802 term guard's pack rule at the real zh-CN tree #1778 it was). Widening a healthy surface is how a farm grows.
- ⛔ Do not touch
content/docs/releases/. ⛔ Do not touch any governed surface.
⭐ Acceptance — a green run is not evidence
For every guard you change, the proof is #1778's shape: two legs against the same mutation at the same commit, with only the surface or the assertion swapped.
- Leg A — the defect present, old assertion: must pass (this is the demonstration that the guard was blind).
- Leg B — the defect present, new assertion: must fail, naming the file and line.
- Prove the mutation on disk before reading any result (count before/after and the blob hash), restore under a
trap … EXIT INT TERM, and prove the restore by bytes — post-restore blob identical toHEAD,git diff HEAD --name-onlyempty. - Capture every exit code before any pipe.
- ⛔ A guard you changed without a two-leg ablation is not delivered; either produce the legs or report it as untouched with the reason.
Also required:
pnpm verifygreen end to end, exit codes captured as above.pnpm lint --jsonbefore/after triples on your own base SHA (raw_decodefrom the first{— pnpm appendsELIFECYCLE).⚠️ This card should move neither number; say so if it does.- The test count before and after. If it grew, justify each addition against scope rule 3 or drop it.
⭐ Permission — and encouragement — to report instead of fix
An inventory that ends "14 surfaces measured, 2 are blind, 12 are already content-pinned, here are the 2 and here is what each needs" is a complete and valued delivery of this card. ⛔ Do not manufacture changes to look productive; the epic's whole thesis is that a test which cannot fail is worse than no test, and a change that cannot be ablation-proven is in the same family.
If a blind surface's repair is genuinely large or needs a product/architecture call, STOP on that one and report it — with the measurement, the two-leg evidence that it is blind, and what the fix would take.
Base and coordination
- Base off
mainat965933bor later. ⚠️ Two sibling branches are live and unmerged:claude/issue-1826-field-consumers-six(draft PR feat(metadata): give four declared-but-unnamed fields the consumer they should already have #1831 —src/views/*,src/objects/campaign_member.object.ts, accounts + campaign-members docs) andclaude/issue-1827-contact-mailing-address(src/views/contact.view.ts, contacts docs). Neither touchestest/**, so collision risk is low — but ⛔ do not measure against a tree containing either.- ⛔ Do not branch from
claude/issue-1581-lint-strict-flip(polluted by chore(deps): bump the @objectstack/* line to 17.4.0 — and whylint --strictmust NOT flip yet (#1581) #1806's superseded commits, stale base).
⛔ Not in this card
- The
os lint --strictflip — Turn onos lint --strictin this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581. - The
field-no-consumerswarnings — Give the six declared-but-unnamed fields the consumer they should already have — clear 6 of the 12field-no-consumerswarnings blocking #1581 (epic #1579) #1826 / The fivecrm_contact.mailing_*fields: the CSV importer writes them and the authored contact form may show none of them — clear the last 5field-no-consumerswarnings blocking #1581 (epic #1579) #1827. - The six family cards Retire the local tests that re-implement the platform's flow / predicate / readonly-write lint rules, once
os lint --strictguards them (epic #1579, step 3, family F1) #1582–Retire the local hook / action / flow write-shape tests the platform's *-body-write-* and flow-node-write rules already enforce (epic #1579, step 3, family F6) #1587. - Anything in
src/**beyond what a guard's repair strictly requires — and if a repair needs asrc/**change, that is a finding to report, not a rider.
Refs: #1579 (epic) · #1778 (the precedent and the ablation shape) · #1770 (the vacuity sweep this supersedes in strength) · #1529 / #1311 / #802 (the bug #1778 dissected).
Activity
os lint --strictfirst, then retire the local re-implementations by family #1579os-steve commentedon Sep 9, 2026
Claim:session_01DuzfS5chho38Yx1jxx9DEj→ branchclaude/issue-1834-content-level-sweepDispatched by the epic PM for #1579. The dev inherits this claim — ⛔ do not post a second
Claim:, ⛔ do not touch the assignee field.Base off
mainat965933bor later. ⛔ Notclaude/issue-1581-lint-strict-flip.Model: Opus, per 「没有 fable 派 opus」.
Deliverable: a draft PR. 「等总监复审」 — ⛔ not ready-for-review, ⛔ no auto-merge, ⛔ no merge, however green CI is.
⭐ And the outcome this card most wants you to be willing to reach: a measured inventory with few or no code changes. Read the acceptance section literally — "14 measured, 2 blind, 12 already fine" is a complete delivery. ⛔ Do not manufacture changes to look productive.
Generated by Claude Code
claude commentedon Sep 9, 2026
os-dev-report
{ "issue": 1834, "status": "done", "branch": "claude/issue-1834-content-level-sweep", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1838", "premise_still_valid": true, "summary": "Base measured: 965933b (origin/main exactly; worktree cut from it, no sibling branch in the tree). 34 scan surfaces in test/** measured against the card's stronger question. Result: 28 already content-pinned, 1 repaired, 1 believed blind and FALSIFIED by its own ablation, 4 reported not repaired. REPAIRED: test/hook-input-shape.test.ts. Its rules A and B walk every .ts under test/ and key on one token (a hook handler being invoked, `handler(`). The file pinned its DETECTOR with planted probes but never claimed the scanned tree still holds a call site for that detector to read. Measured: 175 files in the surface, 31 invoke a handler directly, and 13 ALREADY reach a hook via runHookBody(...) - a form neither rule matches. The class is actively migrating out of the surface; a file count cannot see that. Fixed in place inside the existing self-test block - no new `it`, no new gate, the file stays at 16 tests, suite stays at 3455. FALSIFIED: I judged test/hook-write-shape.test.ts blind on strong evidence (same surface as hook-query-predicate, self-check `length > 10` only, sibling guard on the identical surface already pinned) and wrote the repair. Leg A of its ablation exited 1, not 0: line :444 already holds an EQUALITY cross-reference between the scanned call sites and the writes the runtime half exercises, stronger than what I wrote. Commit reverted, branch carries no trace, tree restored and verified clean. The method lesson is real and is in the PR body: classifying surfaces by grepping for toBeGreaterThan MISSES equality-form content pins. Every verdict was re-derived with the wider net. The dominant reason the farm is healthy is structural: for most guards the class the rule matches IS any member of the surface (an absence sweep over every .mdx page is judging pages), so a count really is a content proof; the #1755 shape needs a class NARROWER than the member.", "tests": "pnpm verify, every step foreground, redirected to a file, $? read BEFORE any pipe, all on final HEAD c453f11 (re-run after the changeset commit because .changeset/ is inside the hygiene scan surfaces): VERDICT validate-exit=0 typecheck-exit=0 lint-exit=0 lint:i18n-gate-exit=0 hygiene-exit=0 hygiene:tokens-exit=0 build-exit=0 test-exit=0; test prints 'Test Files 165 passed (165)' / 'Tests 3454 passed | 1 skipped (3455)'. TEST COUNT 3455 before, 3455 after - UNCHANGED (no new it, no new gate, no new file under test/); nothing to justify against scope rule 3 because nothing was added. LINT TRIPLES, both on my own base 965933b, raw_decode from the first '{': before errors=0 warnings=13 suggestions=12; after errors=0 warnings=13 suggestions=12. NEITHER NUMBER MOVED, as the card predicted. ABLATION 1 (the repair, test/hook-input-shape.test.ts) - three readings, one mutation, exit codes captured before any pipe. Mutation proved ON DISK before any result was read: decoy blob a45e2d80..., test/*.test.ts 165 -> 166, planted defect line count 1; surface-swap guard blob 0ab6129c... -> 5d5fcf14..., swapped surface 44 real .ts files / 0 handler invocations. CONTROL (defect present, surface unmutated, shipped guard) VERDICT control-unmutated-surface-exit=1, rules A and B both name test/zzz-ablation-defect.test.ts:11 - the defect is real and detectable. LEG A (same mutation, OLD assertion, HEAD~1) VERDICT legA-prefix-exit=0, 'Test Files 1 passed (1) Tests 16 passed (16)' - the defect is MISSED. LEG B (same mutation, NEW assertion, HEAD) VERDICT legB-repaired-exit=1, 'x the guard reports the shape it is meant to report (self-test)', AssertionError 'no file in the scanned tree invokes a hook handler at all...' at test/hook-input-shape.test.ts:502:7. RESTORE proved BY BYTES under a trap ... EXIT INT TERM, not by an exit code: post-restore blob 0ab6129c9285b6e4dcb8ddae0a0b253002502e80 identical to HEAD, decoy absent, `git diff HEAD --name-only` empty, `git status --porcelain` empty. ABLATION 2 (the falsification, test/hook-write-shape.test.ts). #616 defect injected: blob 3629056b... -> eaa1cc6c..., anchor count 1 -> 0, bare-id line 0 -> 1; 17 hooks relocated to a subdirectory with imports carried, surface 44 -> 27 real files, write call sites 18 -> 0, barrel blob 628fb7a6... -> 7f392330.... LEG A VERDICT legA-prefix-exit=1 - NOT blind: 'x covers every `update(` call site under src/objects/', AssertionError 'src/objects/ has 0 update() call sites but this file exercises 18'. Repair reverted per the card's do-not-add-to-an-already-pinned-surface rule. NOTE on the mutation proof: the first version of ablation 2 was a NO-OP (perl anchor did not match). It was caught by the on-disk count/hash check and refused with 'FATAL mutation 1 was a no-op' rather than being read as a result; the anchor was fixed and it was re-run. Reported rather than quietly retried.", "mcp_calls": "0 - the whole run used repo-scoped REST reads/writes plus git; no MCP GitHub call was made", "open_questions": [ { "question": "Changeset frontmatter for test-only work: the dispatch said 'honest ... but not empty'; for test-only work those two halves collide. .github/workflows/changeset-check.yml states in its own comment that an empty-frontmatter changeset IS the sanctioned 'this PR releases nothing' declaration, on par with skip-changeset, and #1778 - the precedent this card names - used exactly that for the same kind of work.", "options": [ "A - empty frontmatter, per the gate's documented semantics and the #1778 precedent (what I shipped, flagged in the PR body rather than chosen silently)", "B - name 'hotcrm': patch as the dispatch's literal wording asks" ], "recommendation": "A, because the gate's own printed semantics are authoritative and B would accrete a CHANGELOG row for a change no user can see, which AGENTS.md's changeset-owns-the-changelog discipline exists to prevent. One line to switch if the maintainer rules otherwise." }, { "question": "Should #1834 close on this PR? I wrote 'Part of #1834' with NO closing keyword. The card is pm:epic-reserved for the epic PM, and this delivery deliberately leaves four measured surfaces reported rather than repaired.", "options": [ "A - leave it open; PM triages the four reported surfaces and closes the card", "B - treat the sweep as complete on merge and close it" ], "recommendation": "A. A card that stays open can be closed; one silently closed by a merge drops out of the open-only inbox filter, taking the four reported surfaces with it. The epic PM reserved this card, so the close is the PM's call, not a dev PR's side effect." }, { "question": "The four surfaces reported-not-repaired: should each become its own card? I did NOT file them, because they are this card's own deliverable ('here are the N and what each needs' is the outcome the card asks for), not incidental defects found in passing - and filing four without the PM's dedup read risks duplicating cards already under epic #1579.", "options": [ "A - PM triages them off this card / the PR body and files what it wants", "B - I file all four now as unassigned cards" ], "recommendation": "A, for the dedup reason and because two of them (TEST_FILES in the precedent file; collaboration-capabilities, which is #1770's non-emptiness question rather than this card's) would collide with cards that already own those files." } ], "out_of_scope_findings": [ "NOT FILED, reported on the card and in the PR body as this card's own inventory output - test/docs-object-term-consistency.test.ts TEST_FILES: '> 100' only; the ROW_LABEL content pin beside it reaches its file by literal path, not through TEST_FILES - the same walk-vs-literal pair #1778 crossed for PACK_FILES, left uncrossed. Needs one line; it edits the precedent file itself.", "NOT FILED, reported - test/hook-query-predicate.test.ts: '> 10' plus toContain('_line-item-price-fill.ts'), which pins a FILENAME, not content. 18 of 44 files carry a `where:` key today. Needs a ruling on whether a filename proxy counts.", "NOT FILED, reported - test/collaboration-capabilities.test.ts: the feeds:true sweep over src/objects/*.object.ts asserts NOTHING about its surface. This is #1770's non-emptiness question, not this card's, and the class here is any .object.ts so a plain count is the right answer. Left to whoever owns the vacuity sweep so two cards do not edit one line.", "NOT FILED, reported - test/docs-drift.test.ts and test/docs-conversion-rate-spelling.test.ts: each holds a carrier list (VERBATIM_PAGES / CARRIERS) proved by literal path while the sweep runs over a walk, uncrossed. Practical exposure low (both walks rooted at content/docs, counts > 100 / > 50, plus existsSync). Needs a one-line expect(PAGES).toContain(carrier) in each, or a ruling that count + existsSync is the pin.", "The out-of-scope list the dispatch named (--strict flip #1581, field-no-consumers #1826/#1827, the family cards, any src/** change) required no action: the repair needed no src/** change, and both ablations' src/ mutations were restored and proved by bytes." ] }Generated by Claude Code
os-steve commentedon Sep 9, 2026
PM rulings on the three open questions — and one correction to this card's own dispatch
Q1 — the changeset. Ruled A (empty frontmatter). The dispatch wording was mine and it was wrong.
I asked for frontmatter "honest … but not empty". For
test/**-only work those two halves collide, and the repo has already settled it. Verified against the file rather than taken from the report —.github/workflows/changeset-check.yml:33:and again at
:66. #1778 — the precedent this card is built on — used exactly that form for exactly this kind of change. ⭐ The gate's own printed semantics beat my dispatch wording, and flagging it in the PR body rather than silently complying was the right call. Ship it as it stands.Q2 — closing. Ruled A.
Part of #1834, no closing keyword, is correct. This card stays open. A card that stays open can be closed; one closed silently by a merge drops out of the open-only filter and takes four measured findings with it. The close is this seat's, after the four are disposed of.Q3 — the four reported surfaces. Ruled A, and triaged now: → #1839. One card, not four, because all four are the same shape (a walk whose specific carrier is reached by literal path, uncrossed — the pair #1778 crossed for
PACK_FILES). ⭐ One correction to the report's dedup reasoning, and it changes a disposition:collaboration-capabilities.test.tswas left for "whoever owns the vacuity sweep" — but #1770 is CLOSED, so nobody owns it. It would have fallen through the gap between two cards; it is in #1839 with the ruling that a plain size pin is the right and sufficient answer there, because the class is any.object.ts. Thehook-query-predicatefilename-proxy question is ruled in #1839 too, with an explicit invitation to overturn it with a measurement.On the delivery itself
Both load-bearing claims were re-derived by this seat rather than accepted:
test/hook-write-shape.test.tsdoes holdexpect(covered, …).toBe(sites.length)— an equality cross-reference between the scanned surface'supdate(call sites and the writes the runtime half exercises. Strictly stronger than the repair that was written for it. Believing a surface blind, writing the fix, having your own Leg A refuse it at exit 1, and then reverting your own commit and reporting it — that is the card working exactly as intended. It is also the second time this week a dev's measurement convicted a PM's reading.And the method finding is the part worth keeping beyond this card: classifying a surface by grepping its self-check for
toBeGreaterThanmisses equality-form content pins three blocks away. It is carried into #1839 as a binding warning.PR #1838 stays a draft — 「等总监复审」.
Generated by Claude Code