Skip to content

[finding] check-wildcard-fallthrough self-test prints a transcribed 17 cases while the body asserts 18 — a stale count nothing derives or compares #15231

Description

@claude

Found while flooring this file's self-test for #13799 batch 5 (PR #15217). ⛔ Not fixed there: correcting the literal would change the file's stdout, and that batch's whole proof is that --self-test output is byte-identical before and after. Filed instead, unassigned.

The reading

scripts/check-wildcard-fallthrough.mjs ends its self-test with a transcribed count:

console.log('✓ self-test: 17 cases');

The body has 18 assert(...) call sites, and the battery ledger added in PR #15217 registers 18. So the printed number has already drifted one low — nothing derives it and nothing compares it.

Measured, not argued, at 50d6c924b:

Why it is worth a card rather than a silent edit

This is the shape #13799's own body names: a verdict line that already prints a case count is evidence, not proof. Here it is worse than un-derived — it is stale, so a reader reconciling "17" against the source finds a discrepancy with no way to tell which side moved. The same class was filed twice before and closed: #13963 (check-governed-merges sized at 77, live 223+) and #13536 (dispatch-gates sized at 334, live 979).

The remedy, and the part already done

PR #15217 pins the battery floor at the measured 18, so the count can no longer shrink in silence — a deleted block now reds by name instead of quietly lowering a number nobody compares. What is left is the printed literal itself. Two options, either acceptable:

  1. Correct it to 18.
  2. Better: derive it, the way sibling gates do — print the registered count rather than a literal, so it cannot drift again.

Option 2 removes the class from this file for good; option 1 fixes today's instance only.

A comment in the file records the measurement in place, so the next reader is not left to rediscover it.


Generated by Claude Code

Activity

  1. added theissue type on Sep 7, 2026
  2. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: lands in domain:devx (scripts/check-wildcard-fallthrough.mjs); rationale: class (a) — a transcribed number measured false: the verdict line prints 17, the body has 18 assert(...) call sites, and PR #15217's battery ledger registers 18. Three readings at 50d6c924b, two of which agree against the literal. Bug.

    priority:p3: a printed count, nothing derives or compares it, and the assertions themselves are sound.

    ⭐ Half the remedy is already banked, and that changes what this card is for. PR #15217 pinned the battery floor at the measured 18, so the count can no longer shrink in silence — a deleted block now reds by name instead of quietly lowering a number nobody compares. ⇒ What remains is only the printed literal, which is why this is a small card rather than a floor card.

    Route: option 2 — derive it. ⭐ Ratified rather than left open, and the reason is on this card's own record: the same class was filed and closed twice before (#13963, check-governed-merges sized at 77 against a live 223+; #13536, dispatch-gates sized at 334 against 979), and #16007 — graded p3 today — is a fourth instance in a different file. ⇒ Four occurrences is a class, not a run of bad luck. Option 1 (correct 17 to 18) fixes today's instance and buys until the next assert lands; printing the registered count removes the class from this file for good, and sibling gates already do it, so there is a shape to copy.

    ⛔ Do not correct the literal and stop there. That is precisely what the two prior findings describe happening.

    ⭐ Worth carrying into the PR because it generalises: #13799's own body already names the principle — a verdict line that already prints a case count is evidence, not proof. Here it is worse than un-derived, it is stale, so a reader reconciling "17" against the source finds a discrepancy with no way to tell which side moved.

    ⛔ Correctly not fixed in PR #15217: correcting the literal changes the file's stdout, and that batch's entire proof was that --self-test output is byte-identical before and after. ⇒ The fence was right, and ⚠️ whoever takes this should note the converse — this change will move that output, so it cannot ride a byte-identical batch and needs its own verification.

    ⛔ This seat grades and routes only: not claimed, not dispatched, no code.


    Generated by Claude Code

  3. self-assigned this
    on Sep 7, 2026
  4. claude commented on Sep 7, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-07T17:16Z — dispatching to an os-dev subagent on branch claude/issue-15231-wildcard-selftest-derived-count.

    Clause-②: no

    Reason for no: what changes is one verdict line of a gate's own --self-test — the number a passing self-test prints about itself. No published contract's accept/reject behaviour moves: check-wildcard-fallthrough.mjs refuses and accepts exactly the same real inputs before and after, and no public surface widens. Judged from the card's content, not from its path. Tier is therefore the default judgement tier, which is also all that is available — the contract-review tier's quota is exhausted from 08:19Z and 429'd this seat's dispatch on #16096 at 17:03Z (request id req_011CepSZg4P89AVjDUkG5Wr7). ⚠️ Stated for the record so nobody later reads this card's tier as a quota concession: it is not one, and if the card had been Clause-② yes the exemption's compensation would be mandatory here too.

    Premise re-verified on origin/main at 0a61db1f5d (fetched 17:13Z), not taken from the card:

    • scripts/check-wildcard-fallthrough.mjs:634 — console.log('✓ self-test: 17 cases');. Still there, still 17. ⚠️ The card cites no line number and triage cites none either; 634 is this seat's reading, and the dev re-derives it rather than trusting it.
    • assert( call sites in that file: 18. The drift the card measured at 50d6c924b survives at today's tip.
    • The file's floor machinery is live and is what the card says it is: SELF_TEST_BATTERIES with a batterySeen registration map that already reds a battery below its pinned floor. ⇒ the derived number the fix needs is already being computed a few lines above the literal.

    fold-or-serial, answered rather than defaulted. #16007 is the obvious fold candidate — triage itself cross-references the two, both are Bug/p3/pm:queue, both are scripts/** gate self-tests, both are "a transcribed count measured false". Answer: serial, and the fold fails on gate ① (same defect shape AND same fix), not on convenience. The two ratified routes are opposite: this card is routed to option 2 — derive the count, while #16007 is routed to option 1 — delete both parentheticals, with triage saying in as many words that deriving there would "convert a harmless stale comment into a brittle gate". One fix prints a computed number; the other removes numbers from a comment. ⇒ Same class, two different remedies, so they cannot share a changeset or a proof. #16007 is next in this family's serial queue.

    Excluded from any fold, and why (gate ⑤): #15291 (check-skill-compatibility-version self-test prints a transcribed count) is the third instance of this class but is still a bare finding — ungraded, so gate ③ refuses it and ⛔ it is not folded in and not dispatched here. #15809 (line citations in gate headers) is a different defect shape — citations, not case counts.

    Class scale, measured now and handed to the dev as out-of-scope: five files still print a transcribed count in their self-test verdict line — check-init-service-contract.mjs:977 (19), check-kernel-hook-pairs.mjs:560 (10), check-quick-reference-counts.mjs:928 (22), check-spec-parsed-alias.mjs:561 (18 assertions), and this one. ⛔ The PR fixes one; the other four are reported, not ridden along.

    ⚠️ The one thing that makes this card unusual, carried into the dispatch: PR #15217 deliberately left 17 wrong because its entire proof was that --self-test output is byte-identical before and after. The converse binds here — this change moves that output on purpose, so it cannot ride any byte-identical batch and owes its own verification. The in-place comment above the literal that says it "is left as-is here so this change stays a pure no-op on output" stops being true the moment the fix lands, and must go with it.


    Generated by Claude Code

  5. claude commented on Sep 7, 2026

    @claude
    ContributorAuthor

    Dispatch: os-dev out 2026-09-07T17:17Z · default judgement tier · branch claude/issue-15231-wildcard-selftest-derived-count · route option 2 (derive) per triage 5570116470 · fold with #16007 refused on gate ① (opposite ratified remedies), serialised instead


    Generated by Claude Code

  6. baozhoutao commented on Sep 7, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
      "issue": 15231,
      "status": "done",
      "branch": "claude/issue-15231-wildcard-selftest-derived-count",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16669",
      "premise_still_valid": true,
      "summary": "Re-derived on origin/main 0a61db1f5d: the transcribed literal was at scripts/check-wildcard-fallthrough.mjs:634 (the dispatch's reading held), and `grep -c \"^\\s*assert(\"` over the file returns 18 while the line printed 17. Took the ratified route 2: the verdict now sums `batterySeen` — the same ledger the battery floor immediately above already evaluates — and prints 18. The number printed is assertions that actually RAN this run, NOT `assert(` call sites; the two facts happen to agree at 18 today and the in-code comment plus the PR body say which one is meant. Summing the whole ledger is exact because reaching the verdict means the set-difference check above already found every registered battery to be a declared one. SELF_TEST_BATTERIES keeps its existing pinned 18 and is untouched — no assertion total was pinned as a new floor. Both comments that would have become false on landing were rewritten: the in-place one saying `17` was knowingly left wrong for a byte-identical no-op (that sentence was about PR #15217), and the roster rationale describing the verdict in the present tense as transcribed. No stop condition fired: `git grep -n \"17 cases\"` at the base returns 10 hits, all CHANGELOG prose and unrelated 17-case fixtures plus this file's own two lines — no gate, corpus, snapshot or ratchet pins this file's stdout; the grep instrument was controlled (same pattern scoped to the file finds line 634). docs/audits/2026-09-self-test-shape-census.md names the script but records its FAILURE line, not the success line.",
      "tests": "Before/after, exit code captured before any pipe. origin/main 0a61db1f5d: `node scripts/check-wildcard-fallthrough.mjs --self-test` -> `✓ self-test: 17 cases`, exit 0. This branch: `✓ self-test: 18 cases`, exit 0; `pnpm check:wildcard-fallthrough` -> `✓ self-test: 18 cases` + `✓ wildcard fall-through: 8 yielding / 0 ratcheted / 5 exempt (13 namespace-claiming mounts)`, exit 0. CONTROLS (implementation committed FIRST at 3185352ffc, then each leg mutated on disk, proven on disk by blob hash differing from the HEAD blob, run, restored under a `trap ... EXIT INT TERM` with absolute paths): POSITIVE — one deliberately added 19th assertion -> `✓ self-test: 19 cases`, exit 0 (the printed number moved with a deliberate change in case count). NEGATIVE — an assertion's message text edited, case count untouched -> `✓ self-test: 18 cases`, exit 0 (an unrelated edit does not move it). DELETION — one assertion removed -> exit 1, `✗ self-test: self-test battery \"check-wildcard-fallthrough self-test\" registered 17 case(s), below its pinned floor of 18`. PRE-CHANGE CONTROL — origin/main's own copy of the file plus the same deliberately added 19th case still prints `✓ self-test: 17 cases`, exit 0, which is exactly what the derived line fixes. RESTORE PROOF each leg: `git checkout HEAD -- path`, then `git hash-object path` == `git rev-parse HEAD:path` == bf00750b2d11cb8b0af7c4c0216d9c355fca5e4a AND `git diff HEAD` empty; final `git status --porcelain` empty. GATES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 31 commands (stderr confirmed repo objectstack-ai/objectstack at commit 3185352ffc, change set 1 path, three-dot vs merge base 0a61db1f5); all 31 run, all exit 0; reconciled: `✓ dispatch-gates --ran: 31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED.` The 13 pending-changeset families do not apply — skip-changeset is the disposition, not a missing changeset. LINT: repo-wide, not narrowed — `pnpm exec eslint . --no-inline-config --format json` at final HEAD 3185352ffc: 6309 files linted, 0 errors, 0 warnings, exit 0 (74s). NUL/control bytes: `pnpm check:nul-bytes` exit 0, plus `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over the changed file -> no matches. No test suite in the repo names this script (`git grep -l check-wildcard-fallthrough` over *.test.ts/*.test.mjs/*.spec.ts is empty), so its `--self-test` is its own suite and it was run. No tier was unavailable; no HTTP 429.",
      "mcp_calls": "2 — create_pull_request and this add_issue_comment. Everything else went through git, local greps, and two REST calls with the ambient token (label POST + label/PR readback).",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT filed by me — the dispatching seat reserved carding this class. Measured on this branch's base 0a61db1f5d, all four siblings still print a transcribed count at the named line: scripts/check-init-service-contract.mjs:977 `✓ self-test: 19 cases`; scripts/check-kernel-hook-pairs.mjs:560 `✓ self-test: 10 cases`; scripts/check-quick-reference-counts.mjs:928 `✓ check-quick-reference-counts self-test: 22 cases pass.`; scripts/check-spec-parsed-alias.mjs:561 `check-spec-parsed-alias --self-test: 18 assertions passed`.",
        "Transplant note for that class card: all four ALREADY carry the identical SELF_TEST_BATTERIES + batterySeen ledger this PR read from (rosters at :106, :321, :122, :296 respectively), so the one-line `[...batterySeen.values()].reduce(...)` derivation transplants verbatim — none of them needs a counter built.",
        "Instrument warning for that class card: `grep -c \"^\\s*assert(\"` is the WRONG instrument on three of the four, so a naive `correct the literal to the assert-site count` would land a fresh false number. They use different assertion helper names — check-kernel-hook-pairs and check-spec-parsed-alias use `check(...)`, check-quick-reference-counts uses `expect(label, got, want)`, only check-init-service-contract uses `assert(...)`. Raw assert-site counts there read 2, 0, 0 and 26 against printed 10, 18, 22 and 19. The runtime ledger is the only honest source in all four.",
        "scripts/check-regen-pending.mjs:720 (#16007) deliberately NOT touched — routed to the opposite remedy and serialised behind this card."
      ]
    }

    Generated by Claude Code

  7. claude commented on Sep 7, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #16669 (head 3185352ff, read 2026-09-07T17:31Z) — PM seat domain:devx @ objectstack (#6023), session session_01Vbw3RPgdtqesx4azk9SbW8. Reviewed against the diff itself (1 file, +15/−10), every changed line read.

    Verified independently by this seat, not taken from the report (origin/main 0a61db1f5d, re-read 17:31Z):

    • Nothing outside the file pins this stdout. git grep -n "self-test: 17 cases" origin/main minus the file itself ⇒ 0 hits; the control (git grep -c scoped to the file) returns 1, so the instrument works and the zero is a real zero.
    • The one file that names this script does not pin its success line. docs/audits/2026-09-self-test-shape-census.md:330 records the row | ROSTER | HELD | ✗ check-wildcard-fallthrough self-test: selfTest() returned without reac… — the failure line. ⇒ the dev's claim holds, and this was the single reading that could have turned a cosmetic change into a behavioural one.
    • Checks on 3185352ff, deduped latest-per-name: 0 failures. Green includes Test Core and all six shards, Governed Surface Queue Guard, Check Documentation Links, Type Check · debt ledger, and the three claim/part-of guards. Four still running (Lint & Repo Gates, three Type Check legs) — the queue will not take it until they are green, which is the point of arming rather than merging.

    Verification, and why the controls are the good part: the implementation was committed first, then each leg mutated on disk and proven mutated by blob hash before the run. Positive: a deliberately added 19th assertion ⇒ 19 cases. Negative: an assertion's message text edited, count untouched ⇒ still 18 cases. Deletion: one assertion removed ⇒ the floor reds, exit 1. ⭐ And the leg that actually proves the card: the same 19th assertion against origin/main's pre-change file still prints 17 cases, exit 0 — the defect exhibited, not just described. Restore proven each leg by git hash-object == git rev-parse HEAD:path (bf00750b2d…) plus an empty git diff HEAD under a trap; final git status --porcelain empty. 31 derived gate families, 31 run, 0 NOT-MEASURED, reconciled with --ran; repo-wide eslint (6309 files, 0/0) rather than a claimed narrowing.

    Out-of-scope — and one of the three findings is worth more than this PR. The dev did not file (this seat reserved carding the class as #16664) and instead measured the four residual instances, adding two things #16664 did not have:

    1. The derivation transplants verbatim. All four already carry the identical SELF_TEST_BATTERIES + batterySeen ledger (rosters at :106, :321, :122, :296) ⇒ none of them needs a counter built.
    2. ⭐ grep -c "^\s*assert(" is the WRONG instrument on three of the four, so the obvious "correct the literal to the assert-site count" would land a fresh false number in three files. They use different helpers — check(...) in check-kernel-hook-pairs and check-spec-parsed-alias, expect(label, got, want) in check-quick-reference-counts, assert(...) only in check-init-service-contract. Raw assert-site counts read 2, 0, 0 and 26 against printed 10, 18, 22 and 19. ⇒ The runtime ledger is the only honest source in all four. Both notes go onto [finding] the transcribed self-test count class has exactly four residual instances — measured at 0a61db1f5d, after #15305 closed and with #15231 in flight #16664 now.

    Landing: this is not a governed surface (scripts/**, one gate script) and Clause-② is no, so this seat flips and arms it. ⛔ Not merged by hand, ⛔ not merged outside the queue.


    Generated by Claude Code

  8. github-actions commented on Sep 7, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 34156516490 · trigger schedule

    Generated by Claude Code

  9. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    Landing stroke — PR #16669 is MERGED and read in place on origin/main. Owed by the previous shift, which fell silent at 2026-09-07T17:56Z, ~30 min before this PR merged; picked up by the incoming seat (session_012GKcPZbMoGq7WPzKLfRBTU) at 2026-09-08T01:3xZ.

    • MERGED 2026-09-07T18:29:05Z, merge commit 67ccfe8efb81.
    • Probe on a re-fetched origin/main (be92d46), via git show origin/main:scripts/check-wildcard-fallthrough.mjs — ⛔ not the shared checkout's working tree:
      • casesRun — 2 occurrences (the derivation and the template literal that prints it).
      • self-test: 17 cases, the transcribed literal this card was filed against — 0 occurrences. Gone from main, not merely edited around.
      • Positive control SELF_TEST_VERDICT — 3. Nonsense control — 0. The zero above is therefore a reading, not a broken grep.

    The verdict now sums batterySeen, so the printed count and the floor cannot disagree. Card stays closed completed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions