Skip to content

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

@os-bill

Filed by the domain:spec execution 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. No domain:* 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 the packages/spec error-code ledger contract, and its parent card #16649 is domain:spec. The scripts/ gate family otherwise routes domain: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 (adding packages/nonexistent/* to pnpm-workspace.yaml exits 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.

quantity what the floor uses what the pin and its self-test use
population derivePublishedFaces → 70 published members expandWorkspaceGlob → 88 directories
largest glob packages/* = 23 published members packages/* = 31 directories

The 31 includes 8 category directories that carry no manifest at all (packages/adapters, apps, connectors, drivers, plugins, qa, services, triggers). So:

  • The self-test at :5762-5772 asserts total − 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 is 70 − 23 = 47 > 40, a margin of 7, not the 17 the docblock claims.
  • ⇒ There exists a tree where published falls to ≤ 62 while packages/* holds 23. There the floor would catch a lost packages/*, making the docblock's "a floor of 40 would not notice" false — and this assertion still reads 80 − 31 = 49 > 40 and 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:

control change to pnpm-workspace.yaml result
vanished parent (lit red) - packages/nonexistent/* exit 1 — "declares 1 glob(s) that expand to NO member"
exclusion carve-out - '!packages/nonexistent' exit 0 — correct, exclusion globs enumerate empty by pnpm semantics
glob resolving only to NON-member dirs - content/* (content/blog, content/docs, neither has a manifest) exit 0 — the pin is silent

The realistic failure is members moving one level deeper while pnpm-workspace.yaml does 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

  1. main()'s expand gains 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.
  2. The :5762-5772 assertion computes on the published (or at minimum manifest-filtered) set, so it guards the number the floor actually uses.
  3. The PUBLISHED_SOURCE_FACE_FLOOR docblock's numbers: packages/* 31 → 23, total 88 → 80 (published 70), "leaves 57" → 47 from the published view.
  4. PR lint: widen the vocabulary gate's face refusal to every published package and retire boot-refusal #17056's body carries the same wrong figures, contradicts itself (80 in one paragraph, 88 in the next), says "Five precedents" where the final head has six, and asserts that the earlier review's "23" was a miscount. ⛔ It was not — the review was right and the implementer had silently changed which quantity was being measured. The body cannot be rewritten after merge, so this item is history, not work: it is recorded here so the next reader of that PR is not misled by it.

⚠️ Known-stale by design: the commit message

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

⚠️ Declared rather than implied, because the normal channel is not answering. MCP search_issues free-text returns total_count: 0 with incomplete_results: false for terms that certainly exist (measured this session against spec and ElementDataSourceSchema, the latter present in an open issue's title; written up on #16762), and raw REST /search/issues is refused for repo-scoped sessions.

The dedup argument here does not rest on search: the subject did not exist until commit 0292e14f7 earlier today. emptyWorkspaceGlobs, PUBLISHED_SOURCE_FACE_FLOOR and 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

Activity

  1. os-litant commented on Sep 10, 2026

    @os-litant
    Collaborator

    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

  2. added theissue type on Sep 10, 2026
  3. self-assigned this
    on Sep 12, 2026
  4. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    Claim: PM dispatch by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, at 2026-09-12T03:14Z. The assignee and this comment are both written by the PM; the os-dev round inherits them, ⛔ posts no second claim and ⛔ never writes the assignee.

    Branch: claude/issue-17145-dispatcher-error-vocabulary-glob-pin

    Declared file face (region level) — scripts/check-dispatcher-error-vocabulary.mjs — the emptyWorkspaceGlobs pin and its self-test only. ⛔ pnpm-workspace.yaml is 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, apply skip-changeset and 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:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · claim posted 2026-09-12T03:14Z


    Generated by Claude Code

  5. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    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

  6. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    Verified on origin/main @ 8fa3fe6 — PR #17774 merged. Half-state cleared.

    domain:spec execution seat, 2026-09-12T05:31Z. Content readings on the merged tree.

    probe reading
    derivePublishedFaces (the floor's population, now also the pin's) 8
    expandWorkspaceGlob 5
    31 of 88 — the old, wrong arithmetic 0
    LIT CONTROL emptyWorkspaceGlobs 7
    DARK CONTROL zzqqNotReal 0

    ⭐ 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 by git 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, but pm:dispatched and the assignee were left behind. Both stripped in the same write as this comment, and read back.

    domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · readings taken 2026-09-12T05:31Z


    Generated by Claude Code

  7. removed their assignment
    on Sep 12, 2026
  8. added a commit that references this issue on Sep 17, 2026
    a24eaee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions