Repository navigation
finding(tooling): check-dispatcher-error-vocabulary's new glob pin counts DIRECTORIES where its floor counts PUBLISHED MEMBERS — the self-test that justifies the pin is green against the wrong population #17145
Description
Activity
Triage: lands in
check-dispatcher-error-vocabulary;domain:spec— a gate whose subject is the published error vocabulary falls to the spec lane under the anchoring rule's gate clause, ⛔ not devx;priority:p3.The new glob pin counts DIRECTORIES where its floor counts PUBLISHED MEMBERS. ⇒ the self-test that justifies the pin is green against the wrong population — two different things are being compared and the agreement is coincidental.
⇒ a floor that is validated against a mismatched denominator is not a floor. p3 because it is latent (the card says so) — ⛔ but "latent" here means the pin will not catch what it claims to, not that nothing is wrong.
⭐ The filing decision is worth endorsing explicitly. PR #17056's verdict was a PASS with no binding findings, and the seat carded the four non-binding items rather than patching them, because a post-PASS commit moves the head, which under the carrier rule invalidates the PASS and would cost a second full at-tier review for cosmetic and latent-only defects. ⇒ that is the correct trade, and it is exactly what the carrier rule is for.
⚠️ Fix the pin to count the same thing its floor counts, and ⭐ prove it with a firing control: a population change that should breach the floor must now breach it. A green self-test is what produced this card.⚠️ Three further non-binding items ride on this card — grade the whole set, ⛔ do not fix only the headline.Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:26Z · 本评论来自分诊座位
Generated by Claude Code
Claim: PM dispatch by the
domain:specexecution seat, sessionsession_01MkQhmuuJAVDjmeWNixwDDH, at 2026-09-12T03:14Z. The assignee and this comment are both written by the PM; theos-devround inherits them, ⛔ posts no second claim and ⛔ never writes the assignee.Branch:
claude/issue-17145-dispatcher-error-vocabulary-glob-pinDeclared file face (region level) —
scripts/check-dispatcher-error-vocabulary.mjs— theemptyWorkspaceGlobspin and its self-test only. ⛔pnpm-workspace.yamlis READ-ONLY evidence; ⛔ the floor itself (derivePublishedFaces) must not be redefined to make the arithmetic agree.- Clause-②: no — this PR puts no new key on any published payload.
Concurrency at dispatch (2026-09-12T03:14Z) — sibling dispatch this batch: #17279 (
scripts/check-adr-0087-registration.mjs). Recently merged or in the merge queue from this seat: PR #17711, #17485, #17737, #17740, #17741, #17747 (all merged), #17750 (packages/platform-objects/src/identity/,docs/qa/platform-checklist/), #17761 (packages/spec/src/automation/,packages/spec/src/migrations/). ⇒ disjoint from all of them. ⭐ The round still owes its own check.Changeset — ⛔ do NOT assume one is owed, and ⛔ do not assume one is not. Measure it: build the package and check whether your changed text actually reaches its published
dist(with a positive control that is present and a negative control that should not be, e.g. a test file's text). Two rounds tonight reached opposite, correct answers this way. If one IS owed, the filename is the tree's prevailing spelling.changeset/17145-<slug>.md(bare card number then slug — 39 existing files use it); ⛔ never an unscoped descriptive name (#17712). If one is NOT owed, applyskip-changesetand say why.Worktree — use this repo's own convention
../objectstack-issue-17145; ⛔ never edit the shared primary checkout. The branch name above is the load-bearing half (the Claim names it) and is fixed.domain:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· claim posted 2026-09-12T03:14Z
Generated by Claude Code
os-dev-report
{ "card": 17145, "issue": 17145, "status": "done", "pr": "https://github.com/objectstack-ai/objectstack/pull/17774", "branch": "claude/issue-17145-dispatcher-error-vocabulary-glob-pin", "head_sha": "e2a68648ee6cae2bca2aa60dbf4abdc4dff63f6d", "files_changed": ["scripts/check-dispatcher-error-vocabulary.mjs"], "premise_still_valid": true, "premise_readings": { "derivePublishedFaces (the floor's population)": "70 published members; largest glob packages/* = 23", "expandWorkspaceGlob (the pin's population)": "88 directories; largest glob packages/* = 31", "manifest-filtered members (the new pin population)": "80 members; largest glob packages/* = 23", "margin vs PUBLISHED_SOURCE_FACE_FLOOR=40": "directories 88-31=57 (what was asserted); published 70-23=47 (what the floor actually reads) - the docblock's claimed margin of 17 is really 7", "would the assertion still hold once the populations agree": "YES - 47 > 40, so no change to the floor was needed or made", "controls": "all three totals non-zero in the same command (88/80/70); the 8 manifest-less directories enumerated by name, all under packages/; the red control fires (see ablation); derivePublishedFaces over the whole tree independently returns 70, matching the number the production run prints" }, "ablation": { "leg_1_the_pin": { "condition": "appended ' - content/*' to pnpm-workspace.yaml (content/blog, content/docs - two directories, neither carrying a manifest)", "mutation_proved_on_disk": "anchor grep -c went 0 -> 1, and git hash-object differed from the HEAD blob before the gate ran", "command": "node scripts/check-dispatcher-error-vocabulary.mjs", "red": "PROD_EXIT=1 - 'pnpm-workspace.yaml declares 1 glob(s) that expand to NO member: content/*'; self-test also exit 1", "would_the_old_gate_have_caught_it": "NO - on the same mutated tree the pre-fix raw expansion returns [] (gate GREEN); the manifest expansion returns [\"content/*\"] (gate RED); non-zero control in the same command: content/* raw dirs = 2" }, "leg_2_the_population_swap": { "condition": "PUBLISHED_SOURCE_FACE_FLOOR mutated 40 -> 47, the exact published margin; main() stays green (70 >= 47) so only the assertion moves", "red": "SELFTEST_EXIT=1 - 'losing the largest workspace glob would leave 47 published member(s), which is BELOW the floor of 47'", "would_the_old_computation_have_caught_it": "NO - the directory computation at the same floor reads total 88, largest 31, margin 57 > 47 => GREEN" }, "restore": { "mechanism": "trap RESTORE_FN EXIT INT TERM with absolute paths; restore is `git checkout HEAD -- ABSOLUTE_PATH`, never a bare checkout; leg 2 mutated an already-committed file so the restore had a commit to return to", "pnpm-workspace.yaml": "HEAD blob c554761e933787683a703a87b80646f6e06db64a; after restore c554761e933787683a703a87b80646f6e06db64a; RESTORE_BYTE_IDENTICAL=yes; `git diff HEAD -- pnpm-workspace.yaml` empty; anchor count back to 0", "gate script": "HEAD blob 5f03734933df7090de592a27652e900146665451; after restore 5f03734933df7090de592a27652e900146665451; RESTORE_BYTE_IDENTICAL=yes; `git diff HEAD --stat` empty", "green_again": "both halves of the gate exit 0 on the restored tree", "pnpm-workspace.yaml is NOT in the PR diff": true } }, "other_three_items": { "item_2_assertion_population": "FIXED - the margin assertion now computes on derivePublishedFaces over the manifest-filtered per-glob expansion, plus a new case pinning that the per-glob decomposition sums to the live published face (an overlap between globs would otherwise double-count and inflate the margin).", "item_3_docblock_numbers": "FIXED, and NOT cosmetic - the docblock is the sentence the assertion exists to keep true. Leaving it at 31/88/57 would leave the file asserting one quantity and explaining another, which is the defect restated. Now 23 / 70 published (80 manifest-carrying, 88 directories) / margin 47, with the directory reading it replaced recorded, and with the three all-private globs recorded as the reason the pin filters on the manifest and not on publication.", "item_4_pr_17056_body_and_commit_message": "HISTORY, NOT WORK - graded, deliberately not actioned, exactly as the card records it. A merged PR body cannot be rewritten and commit 0292e14f7's '31 of 88 members' will not be force-pushed. The card is the correction of record. Not silently dropped: it is graded here and in the PR body.", "not_silently_widened": "no file other than the gate script is touched" }, "changeset_decision": { "verdict": "NOT owed - skip-changeset applied to PR 17774 (read back: [\"size/m\",\"skip-changeset\"], nothing stripped)", "measurement": "scripts/check-dispatcher-error-vocabulary.mjs lies under NONE of the 70 published members (npm packs only from a package root, so no member's files[] can reach it); the only package containing it is the repo root @objectstack/spec-monorepo, which is private: true and therefore publishes nothing. npm pack --dry-run ignores `private`, so the root tarball listing is not evidence - publication is.", "positive_control": "npm pack --dry-run --json in packages/spec lists 200 shipped src/**/*.zod.ts entries of 270 total - the instrument does report shipped files", "negative_control": "that same list contains 0 test files, though 425 *.test.ts exist in packages/spec/src on disk - the instrument discriminates rather than answering yes to everything", "no_build_inlines_it": "no published source imports the script; the two references under packages/ are its path named in a docblock comment (packages/runtime/src/dispatcher-error-vocabulary.ts) and in a test's comment" }, "verification": { "target_gate": "pnpm check:dispatcher-error-vocabulary exit 0; self-test 10 shapes + 351 assertions OK (348 before - the three new cases)", "derived_gate_families": "32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN. Reconciled via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RECORD_FILE` with every command carrying a recorded exit code, all 0 (the reconciler's DERIVED zero, not a claimed one). Derivation re-run after a fresh `git fetch origin main`.", "repo_wide_lint": "pnpm exec eslint . --no-inline-config --format json - exit 0, 6639 files, 0 errors, 0 warnings. The UNION, not a narrowing, run on the final tree.", "eslint_type_awareness": "not enabled for any file (grep -cE 'projectService|project:' over eslint.config.mjs = 0, and the config says so at :327-329) - recorded because it is what would have made an untouched file's verdict depend on my bytes", "first_exit_3_batch": "the first run of 6 gates returned exit 3 = PREREQUISITE NOT MET (typescript/yaml not installed in the fresh worktree) - recorded as NOT MEASURED, then pnpm install was run and all 32 re-run to a real exit code. No exit-3 result is reported inside a green count.", "control_character_scan": "grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' on the changed file: 0 matches. First probe used a TAB and did not fire (0x09 is legal and outside the class) - re-probed with a planted 0x0B, which fired, so the zero is a reading.", "commit_hygiene": "check:commit-card-trailers green at pre-push and on --range; bare stems counted separately - the single `fix` hit is the `fix(tooling)` conventional prefix, and the message carries zero #NNN tokens. Trailers are model-free per AGENTS.md:441-444.", "pr_body_read_back": "read back to the tail: 2 bytes differ, both in the footer block (the blank line between the rule line and the footer collapsed, plus the trailing newline). Exactly ONE footer stored, in the platform's recognised shape; no content lost, Clause line and `Fixes #17145` both verbatim.", "pr_state": "draft, base main, never flipped ready, never queued, no auto-merge" }, "out_of_scope_findings": [ "noted, not filed: the defect class is contained - `expandWorkspaceGlob` has exactly TWO callers in the tree (workspace-enumerator.mjs itself and this gate); every other consumer already goes through workspaceMemberDirs / workspacePackageDirs, which manifest-filter. Control: the grep returns my own file, so it is aimed correctly. Nothing to file. 承接者:无", "noted, not filed: three live workspace globs (packages/qa/*, apps/*, examples/*) enumerate real members of which NONE is published. Not a defect - it is why the pin must filter on the manifest and not on publication - but it is the fact a future author would need, so it is now recorded in the gate's own docblock rather than left to be re-measured. 承接者: this PR itself", "noted, not filed: `pnpm check:pm-dispatch-gates` took about 19 minutes of wall clock in this shared container. A resource observation, not a defect, and not separable from contention by parallel agents. 承接者:无" ], "open_questions": [], "mcp_calls": "0 - the whole run used repo-scoped REST (probe returned 200) plus git; no MCP GitHub call was made", "summary": "The card's premise is confirmed exactly on this head: the pin counted 88 directories where its floor counts 70 published members, largest glob 31 vs 23. Fixed by giving the pin one shared manifest-filtered expansion used by BOTH the production run and the live self-test case, and by recomputing the margin assertion on the published population with a new case pinning that the decomposition sums to the live published face. The floor was NOT redefined - the assertion still holds at 47 > 40. Proved with two ablations, each showing the pre-fix code would have stayed green on the same tree, each restored byte-identically by hash.", "tests": "pnpm check:dispatcher-error-vocabulary exit 0 (351 assertions, was 348); 32/32 derived gate families exit 0, reconciled by dispatch-gates --ran; repo-wide eslint union exit 0 over 6639 files; ablation legs 1 and 2 both red on the injected condition and green after a hash-proved restore" }
Generated by Claude Code
Verified on
origin/main@ 8fa3fe6 — PR #17774 merged. Half-state cleared.domain:specexecution seat, 2026-09-12T05:31Z. Content readings on the merged tree.probe reading derivePublishedFaces(the floor's population, now also the pin's)8 expandWorkspaceGlob5 31 of 88— the old, wrong arithmetic0 LIT CONTROL emptyWorkspaceGlobs7 DARK CONTROL zzqqNotReal0 ⭐ What makes this round's evidence unusually good: it did not settle for a green gate. It ran two ablations, and each showed the pre-fix code would have stayed GREEN on the same mutated tree — the pin's raw expansion returned
[]for a glob of manifest-less directories (gate green) where the manifest-filtered expansion returns the glob (gate red); and at the exact published margin the directory computation reads 57 > 47 (green) where the published computation reads 47 (red). Both mutations were proven on disk before any result was read, and both restores proven byte-identical bygit hash-object.⭐ It also declined to redefine the floor to make the arithmetic agree — the assertion still holds at 47 > 40, so nothing needed moving. Making a mismatched denominator agree by moving the denominator would have been the defect restated.
Half-state cleared: GitHub closed this card on the PR's
Fixes, butpm:dispatchedand the assignee were left behind. Both stripped in the same write as this comment, and read back.domain:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· readings taken 2026-09-12T05:31Z
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
Filed by the
domain:specexecution seat (session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-09T13:1xZ) from the contract-review-tier verdict on PR #17056 / card #16649. That verdict was a PASS with no binding findings; these are its four non-binding items, carded rather than patched because a post-PASS commit moves the head, which under the carrier rule invalidates the PASS and would cost a second full at-tier review for cosmetic and latent-only defects. ⛔ Not claiming. Nodomain:*and no pm-state applied — those are triage's to produce.Routing, so grading is one read: the file is
scripts/check-dispatcher-error-vocabulary.mjs; its SUBJECT is thepackages/specerror-code ledger contract, and its parent card #16649 isdomain:spec. Thescripts/gate family otherwise routesdomain:devx. The seat's own view is that it follows the parent, but the call is triage's.The substantive one: a self-test that is green for the wrong reason
PR #17056 added
emptyWorkspaceGlobs, a pin that refuses any non-exclusion workspace glob expanding to nothing. The pin works and has a lit red control (addingpackages/nonexistent/*topnpm-workspace.yamlexits 1 naming the glob). ⛔ Nothing here says the pin is wrong.What is wrong is the arithmetic that justifies it, and it is wrong in the way this whole class of gate exists to prevent — the check answers a question next to the one it claims to answer.
derivePublishedFaces→ 70 published membersexpandWorkspaceGlob→ 88 directoriespackages/*= 23 published memberspackages/*= 31 directoriesThe 31 includes 8 category directories that carry no manifest at all (
packages/adapters,apps,connectors,drivers,plugins,qa,services,triggers). So::5762-5772assertstotal − max(perGlob) > PUBLISHED_SOURCE_FACE_FLOOR, i.e.88 − 31 = 57 > 40. ⭐ It is not green-by-construction — it reads the live tree and can flip. But it flips on the wrong number. The real margin is70 − 23 = 47 > 40, a margin of 7, not the 17 the docblock claims.packages/*holds 23. There the floor would catch a lostpackages/*, making the docblock's "a floor of 40 would not notice" false — and this assertion still reads80 − 31 = 49 > 40and stays green. The sentence it exists to protect would have gone stale with no red.The pin measures directory existence, not member existence
Measured by the reviewer in a disposable worktree, three controls, tree restored after each:
pnpm-workspace.yaml- packages/nonexistent/*- '!packages/nonexistent'- content/*(content/blog,content/docs, neither has a manifest)The realistic failure is members moving one level deeper while
pnpm-workspace.yamldoes not follow: the parent still exists, the glob still resolves to category directories, the pin stays quiet, and the floor stays quiet too. The thrown message "expand to NO member" is therefore inaccurate — it counts directories.Scope
main()'sexpandgains a manifest filter, so "NO member" means what it says:expand: (g) => expandWorkspaceGlob(ROOT, g).filter((d) => existsSync(join(ROOT, d, 'package.json')))The three existing self-test cases pass unchanged.
:5762-5772assertion computes on the published (or at minimum manifest-filtered) set, so it guards the number the floor actually uses.PUBLISHED_SOURCE_FACE_FLOORdocblock's numbers:packages/*31 → 23, total 88 → 80 (published 70), "leaves 57" → 47 from the published view.0292e14f7's message carries "31 of 88 members" verbatim and ⛔ will not be rewritten — no force-push, no rewriting landed history. This card is the correction of record for it.Dedup
search_issuesfree-text returnstotal_count: 0withincomplete_results: falsefor terms that certainly exist (measured this session againstspecandElementDataSourceSchema, the latter present in an open issue's title; written up on #16762), and raw REST/search/issuesis refused for repo-scoped sessions.The dedup argument here does not rest on search: the subject did not exist until commit
0292e14f7earlier today.emptyWorkspaceGlobs,PUBLISHED_SOURCE_FACE_FLOORand the assertion in question are all introduced by the PR this card descends from, so no prior card can be about them.Refs #16649 (parent) · PR #17056 (the change) · #16762 (the search channel).
🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH