You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] dispatch-gates never routes a .d.mts change set to check:declaration-mirrors — its corpus is discovered by extension, no path literal, so a drifted mirror only reds in CI #15553
Filed by the PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, from a measured CI red on PR #15532 (#15487). Filed finding, domain:devx; priority stated with its reason (one rework cycle already paid) so triage can overrule.
What happened
PR #15532 edited scripts/js-comment-mask.d.mts (a hand-written declaration mirror) and ran the derived family for its change set — 32 commands, all green. CI then went red on check:declaration-mirrors (scripts/check-declaration-mirrors.mjs, lint.yml step "Hand-written declaration mirrors", run 33914061581):
FAIL: a hand-written declaration disagrees with the module it mirrors.
- scripts/js-comment-mask.d.mts:106 declares `makeRegexRecogniser` with 1 required parameter(s), but scripts/js-comment-mask.mjs implements 0.
The gate is right (Function.length vs a required declared parameter). The derivation could not have named it.
The gap, measured
check-declaration-mirrors.mjs declares its population as DISCOVERED: "every scripts/**/*.d.mts is checked … Discovered, never listed" (:47, :136), walking by extension with no path literal. dispatch-gates.mjs derives families from declared path literals, so a change set consisting of a .d.mts (or the .mjs it mirrors) never routes to this gate. Measured on main 85a245988+:
So the exact class of change this gate exists for — a .d.mts moving out of step with its module — is the class the derivation never sends to it; the red arrives in CI, a cycle late, on every card that touches a mirror. #15532 is the specimen; #15534 / #15398 (both queued, both edit this same .d.mts) would pay it next.
What a fix owes (direction, not prescription)
Declare the gate's population so a .d.mts (and its sibling .mjs) change set derives it — a ROOT_DIR_WATCH_HINTS-shaped declaration naming scripts/** restricted by extension if the hint vocabulary can spell an extension (check hintCovers and check:watch-hint-literal's roster; if the vocabulary is directory-only, the scripts/** hint routes every scripts/ card to a ~1 s gate, which is the false-positive cost to measure and state). ⛔ Not a hand list of mirrors (the corpus is discovered on purpose); ⛔ no self-test edited to pass.
Related: #15501 (the same shape on check:merge-driver, PR #15538 — the fix pattern), #15414 / #15509 (same shape on the self-test gates), #14918.
Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-04T22:45Z — dispatching to an os-dev subagent on branch claude/issue-15553-dmts-derives-declaration-mirrors. pm:queue → pm:dispatched, assignee = the shared account (identity is this session ID). Serial condition met: PR #15570 landed 22:36Z, so scripts/pm/dispatch-gates.mjs is free (the only live PR on it). Scope adds the docblock correction deferred from PR #15580 (the SELF_TEST_VERDICT docblock still says the floor probe cannot read this file). Live devs after this claim: 5/5.
{
"issue": 15553,
"status": "done",
"branch": "claude/issue-15553-dmts-derives-declaration-mirrors",
"pr": "https://github.com/objectstack-ai/objectstack/pull/15601",
"premise_still_valid": true,
"summary": "scripts/check-declaration-mirrors.mjs now declares ROOT_DIR_WATCH_HINTS = ['scripts/**/*.d.mts', 'scripts/**/*.mjs'] beside SCRIPTS_DIR - both sides of a mirror, because mirrorFiles() walks the declarations and checkPair() imports the modules to read Function.length off, so either side moving reds the gate. The corpus stays DISCOVERED: no hand list of pairs. Six cases were added to the existing 'check-declaration-mirrors self-test' battery (floor 23 -> 29) pinning non-empty, multi-segment, not-the-bare-subtree, every discovered .d.mts admitted, its sibling .mjs admitted, and ONE literal array of quoted strings read back from this file's own comment-masked source, statement-scoped. scripts/pm/dispatch-gates.mjs got the ruled docblock paragraph only (no logic, no self-test case): PR #15580 landed as e39619d6f while this card was in flight, so the paragraph is rewritten to the LANDED state and re-measured on the merge base rather than to a queued one. DEVIATION FROM THE DISPATCH, both benign: (a) the branch was merged with origin/main mid-run after dispatch-gates reported a STALE TREE, so the after-numbers and the 1445-case count are main's, not 1415's tree; (b) the PM's premise for item 2 was FALSE on the branch point ca46f8f12 and true only after that merge - on ca46f8f12 the raw anchor landed in a DOCBLOCK (not, as the old paragraph said, a template literal) at :3023 and the injected comment terminator made the copy a SyntaxError; measured both ways before writing. The bare-root-worklist row for this gate still records REFUSE-UNSPELLABLE on a ground the vocabulary no longer holds; it does not red (contradicted fires only on a REACHABLE row) and re-deciding a verdict on that shrink-only map belongs to a card owning that file, so it is filed as #15602 rather than ridden along.",
"tests": "All exit codes captured with a redirect BEFORE any pipe; verdicts quoted from the gates' own lines. Tree: ab5c01379 (merge base 088f761e5). [1] node scripts/check-declaration-mirrors.mjs --self-test -> exit 0, 'All 29 self-test cases passed.' [2] node scripts/check-declaration-mirrors.mjs -> exit 0, 'OK: 4 hand-written declaration(s) agree with their modules on name, kind and required arity.' - diff against main's output: byte-identical. [3] Whole derived family for this change set (node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack -> 27 commands), every one run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-15553, acquired at 0s, no poll-and-retry: ALL 27 EXIT 0; wrapper line 'VERDICT command-exit 0 - held the lock 578s (9m38s) - waited 0s'. Named verdicts: check:watch-hint-literal exit 0 '57 declaration(s) across 4 rostered name(s) ... every one an array of quoted literals inside its own statement'; check:declared-population-live exit 0 '213 of 258 famil(ies) declare a path population, and every one of them reaches this tree's 7565 tracked file(s)'; check:pm-dispatch-gates exit 0 'dispatch-gates self-test: 1445 cases pass.' (NO case edited - the diff on that file is comment-only, verified with git diff; 1445 not 1415 because origin/main moved, see summary); bare-root-worklist --self-test exit 0 '54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) - none stale, none missing, none contradicted'; check:nul-bytes exit 0 'scanned 7558 text file(s) ... no raw ASCII control bytes'. NOT MEASURED, by the tool's own account (a $RUNNER_TEMP / matrix value that exists only in CI): check-cross-package-test-inputs.mjs --union-into ...; check-shard-attestation.mjs --emit ...; check-test-completeness.mjs .... pnpm lint is NOT in the derived family. [4] BEFORE/AFTER derivation, both legs on ONE tree (ab5c01379) so the delta is attributable: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- scripts/js-comment-mask.d.mts scripts/js-comment-mask.mjs' derives 31 commands / 0 lines naming declaration-mirrors with the declaration EMPTIED, and 33 commands / 2 lines ('node scripts/check-declaration-mirrors.mjs' and '... --self-test') as landed. The same zero was measured first on the untouched branch point ca46f8f12: 31 commands, none of them this gate. [5] ABLATIONS - three legs, each trap-guarded ('trap restore EXIT INT TERM') with REPO_ROOT-absolute paths, restore spelled 'git checkout HEAD -- ABSPATH' (never bare), and every leg PROVED on disk in both directions: the mutation by anchor grep counts (literal-form remaining 0, injected form 1) AND a changed git hash-object, the restore by hash-object equal to the HEAD blob a017fc90cf851e4b76f661503457708414f244eb plus 'git diff HEAD' empty. No build/dist leg applies - these are .mjs run directly by node, nothing is compiled, so the source on disk IS what ran. (a) declaration emptied -> exit 1, '4/29 self-test case(s) failed', naming exactly the four dependent cases. (b) declaration COMPUTED (HINT_EXTS.map(...), runtime value unchanged) -> exactly the literal-array case reds, and extractWatchHints on the mutated source returns [\"scripts\"] - both real hints silently gone, leaving only a bare word hintCovers refuses: the silent-drop mechanism, demonstrated. (c) declaration emptied, derivation re-run -> the 31/0 leg in [4]. [6] LINT, narrowed and declared rather than skipped: npx eslint --no-inline-config --format json over the 2 changed files -> exit 0, 0 errors / 0 warnings. The three pieces the narrowing owes: the population came from eslint's own config resolution (not my guess), the file count 2 is read from the --format json output, and eslint.config.mjs:328 records that no config here enables type-aware linting ('no parserOptions.project, no typed @typescript-eslint rules'), so this diff cannot move the verdict on any file it does not touch. Whole-repo pnpm lint is CI's run and was not named by the derivation. [7] Gate cost, for the PR body's trade: 'node scripts/check-declaration-mirrors.mjs --self-test && node scripts/check-declaration-mirrors.mjs' = 0.194s / 0.162s / 0.178s over three runs (SHARED BOX, so read the magnitude); plain invocation alone ~0.09s. Reach: of the 36 open PRs, 10 newly derive this gate - 1 via the .d.mts hint (#15593, a real mirror edit) and 9 via the .mjs hint (#15590 #15580 #15579 #15485 #15460 #15434 #15427 #15392 #15331). Precision on the tracked corpus: scripts/**/*.d.mts reaches 4 files and the gate reads all 4 (100%); scripts/**/*.mjs reaches 214 and the gate reads 4 (1.9%).",
"mcp_calls": "0 - every GitHub read and write went through REST with $GH_TOKEN (issue read, open-PR list + per-PR files, PR create, label add + read-back, issue create + body read-back, this comment). The /search/issues endpoint answered 403 for this seat, so the duplicate check for the finding used the repo-scoped REST issue LIST (367 open issues) plus a local regex over the titles - declared here as the channel actually used.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #15602: bare-root-worklist still records `scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts` as REFUSE-UNSPELLABLE because 'what cannot be spelled here is the EXTENSION filter' - the vocabulary now spells it (hintCovers('scripts/**/*.d.mts','scripts/js-comment-mask.d.mts') -> true), and the row's numbers are stale in both terms (4 mirrors today not 2, 331 tracked files under scripts/ not 261). Nothing reds - `contradicted` fires only on a REACHABLE row and neither hint covers an arbitrary file at the top of the root - so the self-test stays green over a recorded reason the tree has withdrawn. Re-pointing it to DECLARED-NARROWER needs a SPELLINGS entry pinned LIVE/PRECISE/COMPLETE and SET-EQUAL, on a shrink-only map, in a file this card does not own."
]
}
PR #15601 merged 2026-09-05T00:02Z (enqueued 23:31Z, one merge-group run). Probed on origin/main after a re-fetch (HEAD ef45c2f2d): scripts/check-declaration-mirrors.mjs declares ROOT_DIR_WATCH_HINTS = ['scripts/**/*.d.mts', 'scripts/**/*.mjs']; scripts/pm/dispatch-gates.mjs's probe docblock now reads the landed anchor. Measured on main: node scripts/check-declaration-mirrors.mjs --self-test → exit 0, All 29 self-test cases passed.; the derivation for scripts/js-comment-mask.d.mts scripts/js-comment-mask.mjs now names this gate twice (plain + --self-test; the card measured 0).
Stripping pm:dispatched + assignee; Fixes #15553 closed this card — the third derivation-gap card of this shift to land (#15501, #15509, #15553; #15604 is next in the class).
Activity
revert(devx): leave dispatch-gates.mjs to PR #15570; its docblock cor…
claude commentedon Sep 4, 2026
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, 2026-09-04T22:45Z — dispatching to anos-devsubagent on branchclaude/issue-15553-dmts-derives-declaration-mirrors.pm:queue→pm:dispatched, assignee = the shared account (identity is this session ID). Serial condition met: PR #15570 landed 22:36Z, soscripts/pm/dispatch-gates.mjsis free (the only live PR on it). Scope adds the docblock correction deferred from PR #15580 (theSELF_TEST_VERDICTdocblock still says the floor probe cannot read this file). Live devs after this claim: 5/5.Generated by Claude Code
fix(devx): check-declaration-mirrors declares the .d.mts/.mjs populat…
docs(devx): dispatch-gates' probe note reads the landed anchor, not t…
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 15553, "status": "done", "branch": "claude/issue-15553-dmts-derives-declaration-mirrors", "pr": "https://github.com/objectstack-ai/objectstack/pull/15601", "premise_still_valid": true, "summary": "scripts/check-declaration-mirrors.mjs now declares ROOT_DIR_WATCH_HINTS = ['scripts/**/*.d.mts', 'scripts/**/*.mjs'] beside SCRIPTS_DIR - both sides of a mirror, because mirrorFiles() walks the declarations and checkPair() imports the modules to read Function.length off, so either side moving reds the gate. The corpus stays DISCOVERED: no hand list of pairs. Six cases were added to the existing 'check-declaration-mirrors self-test' battery (floor 23 -> 29) pinning non-empty, multi-segment, not-the-bare-subtree, every discovered .d.mts admitted, its sibling .mjs admitted, and ONE literal array of quoted strings read back from this file's own comment-masked source, statement-scoped. scripts/pm/dispatch-gates.mjs got the ruled docblock paragraph only (no logic, no self-test case): PR #15580 landed as e39619d6f while this card was in flight, so the paragraph is rewritten to the LANDED state and re-measured on the merge base rather than to a queued one. DEVIATION FROM THE DISPATCH, both benign: (a) the branch was merged with origin/main mid-run after dispatch-gates reported a STALE TREE, so the after-numbers and the 1445-case count are main's, not 1415's tree; (b) the PM's premise for item 2 was FALSE on the branch point ca46f8f12 and true only after that merge - on ca46f8f12 the raw anchor landed in a DOCBLOCK (not, as the old paragraph said, a template literal) at :3023 and the injected comment terminator made the copy a SyntaxError; measured both ways before writing. The bare-root-worklist row for this gate still records REFUSE-UNSPELLABLE on a ground the vocabulary no longer holds; it does not red (contradicted fires only on a REACHABLE row) and re-deciding a verdict on that shrink-only map belongs to a card owning that file, so it is filed as #15602 rather than ridden along.", "tests": "All exit codes captured with a redirect BEFORE any pipe; verdicts quoted from the gates' own lines. Tree: ab5c01379 (merge base 088f761e5). [1] node scripts/check-declaration-mirrors.mjs --self-test -> exit 0, 'All 29 self-test cases passed.' [2] node scripts/check-declaration-mirrors.mjs -> exit 0, 'OK: 4 hand-written declaration(s) agree with their modules on name, kind and required arity.' - diff against main's output: byte-identical. [3] Whole derived family for this change set (node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack -> 27 commands), every one run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-15553, acquired at 0s, no poll-and-retry: ALL 27 EXIT 0; wrapper line 'VERDICT command-exit 0 - held the lock 578s (9m38s) - waited 0s'. Named verdicts: check:watch-hint-literal exit 0 '57 declaration(s) across 4 rostered name(s) ... every one an array of quoted literals inside its own statement'; check:declared-population-live exit 0 '213 of 258 famil(ies) declare a path population, and every one of them reaches this tree's 7565 tracked file(s)'; check:pm-dispatch-gates exit 0 'dispatch-gates self-test: 1445 cases pass.' (NO case edited - the diff on that file is comment-only, verified with git diff; 1445 not 1415 because origin/main moved, see summary); bare-root-worklist --self-test exit 0 '54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) - none stale, none missing, none contradicted'; check:nul-bytes exit 0 'scanned 7558 text file(s) ... no raw ASCII control bytes'. NOT MEASURED, by the tool's own account (a $RUNNER_TEMP / matrix value that exists only in CI): check-cross-package-test-inputs.mjs --union-into ...; check-shard-attestation.mjs --emit ...; check-test-completeness.mjs .... pnpm lint is NOT in the derived family. [4] BEFORE/AFTER derivation, both legs on ONE tree (ab5c01379) so the delta is attributable: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -- scripts/js-comment-mask.d.mts scripts/js-comment-mask.mjs' derives 31 commands / 0 lines naming declaration-mirrors with the declaration EMPTIED, and 33 commands / 2 lines ('node scripts/check-declaration-mirrors.mjs' and '... --self-test') as landed. The same zero was measured first on the untouched branch point ca46f8f12: 31 commands, none of them this gate. [5] ABLATIONS - three legs, each trap-guarded ('trap restore EXIT INT TERM') with REPO_ROOT-absolute paths, restore spelled 'git checkout HEAD -- ABSPATH' (never bare), and every leg PROVED on disk in both directions: the mutation by anchor grep counts (literal-form remaining 0, injected form 1) AND a changed git hash-object, the restore by hash-object equal to the HEAD blob a017fc90cf851e4b76f661503457708414f244eb plus 'git diff HEAD' empty. No build/dist leg applies - these are .mjs run directly by node, nothing is compiled, so the source on disk IS what ran. (a) declaration emptied -> exit 1, '4/29 self-test case(s) failed', naming exactly the four dependent cases. (b) declaration COMPUTED (HINT_EXTS.map(...), runtime value unchanged) -> exactly the literal-array case reds, and extractWatchHints on the mutated source returns [\"scripts\"] - both real hints silently gone, leaving only a bare word hintCovers refuses: the silent-drop mechanism, demonstrated. (c) declaration emptied, derivation re-run -> the 31/0 leg in [4]. [6] LINT, narrowed and declared rather than skipped: npx eslint --no-inline-config --format json over the 2 changed files -> exit 0, 0 errors / 0 warnings. The three pieces the narrowing owes: the population came from eslint's own config resolution (not my guess), the file count 2 is read from the --format json output, and eslint.config.mjs:328 records that no config here enables type-aware linting ('no parserOptions.project, no typed @typescript-eslint rules'), so this diff cannot move the verdict on any file it does not touch. Whole-repo pnpm lint is CI's run and was not named by the derivation. [7] Gate cost, for the PR body's trade: 'node scripts/check-declaration-mirrors.mjs --self-test && node scripts/check-declaration-mirrors.mjs' = 0.194s / 0.162s / 0.178s over three runs (SHARED BOX, so read the magnitude); plain invocation alone ~0.09s. Reach: of the 36 open PRs, 10 newly derive this gate - 1 via the .d.mts hint (#15593, a real mirror edit) and 9 via the .mjs hint (#15590 #15580 #15579 #15485 #15460 #15434 #15427 #15392 #15331). Precision on the tracked corpus: scripts/**/*.d.mts reaches 4 files and the gate reads all 4 (100%); scripts/**/*.mjs reaches 214 and the gate reads 4 (1.9%).", "mcp_calls": "0 - every GitHub read and write went through REST with $GH_TOKEN (issue read, open-PR list + per-PR files, PR create, label add + read-back, issue create + body read-back, this comment). The /search/issues endpoint answered 403 for this seat, so the duplicate check for the finding used the repo-scoped REST issue LIST (367 open issues) plus a local regex over the titles - declared here as the channel actually used.", "open_questions": [], "out_of_scope_findings": [ "filed as #15602: bare-root-worklist still records `scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts` as REFUSE-UNSPELLABLE because 'what cannot be spelled here is the EXTENSION filter' - the vocabulary now spells it (hintCovers('scripts/**/*.d.mts','scripts/js-comment-mask.d.mts') -> true), and the row's numbers are stale in both terms (4 mirrors today not 2, 331 tracked files under scripts/ not 261). Nothing reds - `contradicted` fires only on a REACHABLE row and neither hint covers an arbitrary file at the top of the root - so the self-test stays green over a recorded reason the tree has withdrawn. Re-pointing it to DECLARED-NARROWER needs a SPELLINGS entry pinned LIVE/PRECISE/COMPLETE and SET-EQUAL, on a shrink-only map, in a file this card does not own." ] }Generated by Claude Code
claude commentedon Sep 5, 2026
LANDED — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.PR #15601 merged 2026-09-05T00:02Z (enqueued 23:31Z, one merge-group run). Probed on
origin/mainafter a re-fetch (HEADef45c2f2d):scripts/check-declaration-mirrors.mjsdeclaresROOT_DIR_WATCH_HINTS = ['scripts/**/*.d.mts', 'scripts/**/*.mjs'];scripts/pm/dispatch-gates.mjs's probe docblock now reads the landed anchor. Measured on main:node scripts/check-declaration-mirrors.mjs --self-test→ exit 0,All 29 self-test cases passed.; the derivation forscripts/js-comment-mask.d.mts scripts/js-comment-mask.mjsnow names this gate twice (plain +--self-test; the card measured 0).Stripping
pm:dispatched+ assignee;Fixes #15553closed this card — the third derivation-gap card of this shift to land (#15501, #15509, #15553; #15604 is next in the class).Generated by Claude Code
fix(devx): anchor the self-test floor probe on the real definition, n…
fix(devx): check-declaration-mirrors declares the .d.mts/.mjs populat…