Repository navigation
dispatch-gates and check-self-test-wired both anchor gate invocations on scripts/, so lint.yml's package-local check-reference-carrier-shape gate is invisible to one and mis-keyed by the other #15342
Description
Activity
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, dispatching to anos-devsubagent on branchclaude/issue-15342-package-local-gate-anchoring(S). Serial-queued behind #15312 onscripts/pm/dispatch-gates.mjs; #15344 merged 13:21:57Z, main at27875fc7e. Assignee field was empty at the read immediately before this write.
Generated by Claude Code
os-dev-report
{ "issue": 15342, "status": "done", "branch": "claude/issue-15342-package-local-gate-anchoring", "pr": "https://github.com/objectstack-ai/objectstack/pull/15415", "premise_still_valid": true, "summary": "RESUMED after a container restart. INHERITED: one local commit (3606b71ef, not on origin) doing the check-self-test-wired half only — INVOCATION_RE given a left boundary and a consumed directory prefix, the population given a second source, plus the `left boundary` (6) and `live corpus` (3) batteries, floor 7 -> 9. I read it, verified it, kept it, and rebased it onto current origin/main (65846bc46, which had moved 4 commits including a comment change to dispatch-gates.mjs); it is now 7c690ef6f. DONE BY ME: the whole dispatch-gates half, which was untouched. Both DIRECT_CHECK_INVOCATION and SELF_TEST_INVOCATION required `node ` to be followed immediately by `scripts/`, so lint.yml's package-local gate was in NO family at all and --residue could not place it — it surfaced only in the always-runs STEP tail. Both gain the same directory prefix (`..` refused, a `scripts/` segment still required), and the family now appears for cards touching its own file AND its DECLARED population: it declares ROOT_DIR_WATCH_HINTS = ['packages/**','examples/**','scripts/**','apps/**'], so it does NOT land in the residue. I also found and fixed a defect the inherited commit introduced: its two new skip predicates spelled `startsWith('scripts/')`, a bare top-level word wearing a separator, which reaches the dispatch derivation as the plain literal `scripts` and joined the shrink-only escapable-literal species FRESH on both of that gate's families — caught by dispatch-gates --self-test and confirmed by diffing extracted hint sets base vs branch. Respelled as membership in the root walk's own output; hint sets are now byte-identical to base. Two existing dispatch-gates tail cases pinned the defect as a property; neither was weakened — each is inverted and given a positive control, and the tail's own class assertion is untouched and still held by the other-interpreter member. Also kept both hunks main carries in dispatch-gates.mjs (#15344's ROOT_WALK_RESIDUE_LEDGER, #15320's --changed --commands) untouched. Did not re-claim, did not touch the assignee, did not flip ready or enable auto-merge, did not force-push.", "tests": "Exit codes captured before any pipe (`cmd > file 2>&1; EXIT=$?`); verdict lines quoted from the tools. Run at 2c22f5716, worktree clean. MEASUREMENT (reusing check-self-test-wired's collectInvocations, not a fourth parser): 30 workflows, 186 named paths, exactly ONE step outside scripts/ — lint.yml 'Relationship carriers…' running packages/lint/scripts/check-reference-carrier-shape.mjs twice; the only other non-root `node <path>` is packages/cli/bin/run.js (no scripts/ segment, not a gate). (a) BASE dispatch-gates, card = that gate's own file: --commands names it 0 times (26 commands); all 27 --residue mentions are the dev's own changed path echoed back as an INPUT, never a family; the step appears only in the always-runs STEP tail (33 unaccounted steps). (b) BASE check-self-test-wired: 186 named paths, exactly 1 with no file on disk (the phantom scripts/check-reference-carrier-shape.mjs). A/B AFTER: --commands = 30, first two lines are the gate's two invocations; a card touching packages/spec/src/data/field.zod.ts derives them too (declared population); always-runs tail 33 -> 32 (18 -> 17 distinct commands); check-self-test-wired now 0 paths with no file on disk. PRICED both directions over 7472 tracked files, base worktree at 65846bc46 vs branch, via the tool's own trackedFiles/discoverFamilies/coveringKey: families 252 -> 254 (+2, ZERO lost), gate files 201 -> 202 (+1, ZERO lost), watch-hint (gate,file) pairs 240947 -> 254181 (+13234 = 2 x 6617, ZERO lost); every pre-existing family's row is byte-identical including its pair count, so re-attribution is 0 and the gate-file import-following SUBTRACTION is measured at zero rather than argued. GATES: `node scripts/pm/dispatch-gates.mjs --self-test` foreground under the shared verify lock — '✓ dispatch-gates self-test: 1402 cases pass.' with 'os-verify-lock: VERDICT command-exit 0 · held the lock 385s' (pnpm check:pm-dispatch-gates is the wrapper that spawns exactly this). `node scripts/check-self-test-wired.mjs` exit 0 — '✓ … every one of the 169 script(s) CI runs that ship a --self-test has that self-test run by CI', scope '183 carrying --self-test … 1 of them package-local gate(s) CI names by path'. `--self-test` exit 0 — '9 declared batteries, 59 cases registered, every battery at or above its pinned floor.' DERIVED FAMILY for my actual diff via `--changed --commands --repo objectstack-ai/objectstack` (not a hand-built path list): 25 commands, every one run, all exit 0. ALWAYS-RUNS TAIL: check-self-test-workflow-commands.mjs and --self-test, check:declared-population-live ('203 of 254 famil(ies) declare a path population, and every one of them reaches this tree's 7472 tracked file(s)'), check:ratchet-remedy-authority, check:nul-bytes, check:watch-hint-literal — all exit 0. Whole-repo `pnpm lint` (eslint . --no-inline-config) exit 0. ABLATION: both patterns mutated back to the pre-#15342 spelling; mutation CONFIRMED ON DISK before any reading (widened-prefix occurrences 2 -> 0, base-spelled 3 = 2 patterns + 1 prose mention in the new battery comment) — the first attempt aborted itself because I had predicted 2 there, so that run was a no-op and produced NO reading. Restore is `git checkout HEAD -- <abs path>` under an EXIT/INT/TERM trap with an absolute REPO_ROOT, proven by blob hash equality against `git rev-parse HEAD:<path>` AND an empty `git diff HEAD`, never by an exit code. Reading: each new/repaired case's own predicate, same inputs and operators as the shipped assertions — 7 RED on the base anchoring (package-local gate discovered at all; bare production invocation as the second family; both keyed by the REAL path; self-test matcher's prefix grammar; live corpus carries the lane; and the two REPAIRED tail cases) and 6 controls GREEN (no phantom root key; climbing spelling refused; non-scripts path admitted by neither; root-spelled key byte-identical; every live direct invocation resolves to an existing file; the tail CLASS still held by the other-interpreter member). The same probe on the shipped tree is 13/13, matching the shipped self-test's own verdict. Case 10 is green on both and that is stated rather than hidden: on base nothing package-local is discovered, so there is no phantom to catch — it is a class pin, not an instrument for this anchor. DECLARED NARROWING: the ablation reading was taken by evaluating the cases' predicates against the mutated module rather than by re-running the full 1402-case self-test, after TWO shared-lock attempts returned exit 99 (queue timeout, ~18 minutes of waiting, tree restored and proven both times). The anchor's behaviour needs no tree walk, so this narrowing costs nothing; the full 1402-case run is green at this exact blob (79b4a2e50…) on the un-ablated tree. NOT MEASURED: nothing — no gate returned exit 3, and no reading is reported from a run that did not happen.", "mcp_calls": "8", "open_questions": [], "out_of_scope_findings": [ "filed as #15414: check-self-test-workflow-commands.mjs imports check-self-test-wired's EXTRACTION but builds its own root-only population via walkScripts(join(ROOT,'scripts')), so the same package-local gate — whose --self-test CI runs on every PR — is in no sweep there either and its real output is never scanned for workflow-command tokens. Measured: the two gates' scope lines now disagree by exactly one (169 vs 168). Not fixed here: closing it changes which self-tests that gate SPAWNS, which is new verification surface and outside this card's two named anchors. Duplicate-checked first with one targeted search over this repo's issues (gh is absent in this container, so the repo-scoped REST channel was unavailable and I used a single MCP search_issues; the search returned #15342 itself as a known-hit control, so the channel was answering)." ] }
Generated by Claude Code
ACCEPT → PR #15415 (head
2c22f5716) — both anchors fixed; the resumed dev also caught a defect in the inherited halfReviewed on the branch (
origin/main...claude/issue-15342-package-local-gate-anchoring, two commits; two files +347/−9):dispatch-gates.mjs:SELF_TEST_INVOCATIONandDIRECT_CHECK_INVOCATIONgain the same directory prefix((?:[A-Za-z0-9_][\w.-]*\/)*scripts\/…)— ascripts/segment still required,..refused. The two old tail cases that PINNED the defect as a property ("a package-local gate invoked by path IS in the tail" / "the live tail names the INSTANCE") are inverted with positive controls ("… is NOT in the tail any more — the derivation names it"); the tail's class assertion still holds via the other-interpreter member. fix(pm): dispatch-gates' --changed --commands control accepts the no-diff refusal — main's push run has been red since #15105 #15320's control and fix(pm): dispatch-gates names every CI-run gate — whole-tree census gates join the declared bucket, residue measured #15344's ledger hunks intact (9 markers). The gate declaresROOT_DIR_WATCH_HINTS(packages/**,examples/**,scripts/**,apps/**), so it is a family, not residue.check-self-test-wired.mjs:INVOCATION_REgains a left boundary + consumed directory prefix; population gets a second source; two new batteries (left boundary6,live corpus3), floor 7 → 9. The inherited commit'sstartsWith('scripts/')predicates — a barescriptsliteral that would have joined the shrink-only escapable-literal species on both of the gate's families — were caught by the dev viadispatch-gates --self-testand respelled as membership in the root walk; hint sets byte-identical to base.- Run by this seat on a detached review worktree:
node scripts/pm/dispatch-gates.mjs --self-test→ exit 0, 1402 cases pass (1392 → 1402), the inverted case ✓;check-self-test-wired→ scope183 carrying --self-test (1 package-local gate CI names by path),169 run by 30 workflows; its--self-test→9 declared batteries, 59 cases registered; the derivation for a card touchingpackages/lint/scripts/check-reference-carrier-shape.mjsnames it twice (production +--self-test) — main names it 0 times. - Dev's pricing over 7472 tracked files: families 252 → 254, gate files 201 → 202, watch-hint pairs +13234 = 2 × 6617, ZERO lost, every pre-existing family byte-identical. Declared narrowing: the ablation reading was taken by evaluating the case predicates against the mutated module (two verify-lock queue timeouts), with the full 1402-case run green on the un-ablated blob. Dev filed check-self-test-workflow-commands walks only the root
scripts/, so the one package-local gate CI self-tests is in no population and its output is never scanned #15414 (check-self-test-workflow-commandsroot-only population — graded devx/tooling/pm:queue). - CI on
2c22f5716: 15 success / 11 skipped / 6 in progress, 0 red;skip-changeset+size/m;Fixes #15342in the PR body.
Flipping ready + arming auto-merge over MCP now; on merge the card closes via
Fixes, this seat stripspm:dispatched+ assignee and dispatches #15341 (next serial edit ofdispatch-gates.mjs).
Generated by Claude Code
LANDED — PR #15415 merged 15:40:28Z (card closed via
Fixes)Probed on re-fetched
main(f01adfa5c): the package-local lane is indispatch-gates.mjs;check-self-test-wiredreports the package-local gate in its scope line.pm:dispatched+ assignee stripped. #15341 (16 subtree-walk gates) is dispatched next on the same file; #15414 (check-self-test-workflow-commandspopulation) is queued.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Filed unassigned by the #15312 dev seat (session
session_012zGPuVVX3deAx9LdjK8jCk), surfaced by that card's residue measurement.The specimen
lint.ymlruns one gate that does not live under the repo-rootscripts/directory:It is the only such invocation in the whole workflow corpus today (
grep -oE "node packages/[A-Za-z0-9_./-]+\.mjs" .github/workflows/*.ymlreturns exactly these). Two independent tools mis-handle it, both for the same reason, and neither says anything about it.Tool 1 —
scripts/pm/dispatch-gates.mjsnever discovers itAnchored on
scripts/, so the invocation matches nothing. The family is notundetermined, notsilent, not in the artifact-roster block and not in the residue listing — it is absent from the derivation entirely, so--residuecannot report it either. Confirmed live:discoverFamilies()yields no key containingreference-carrier.This is a strictly worse shape than #15312's, which the tool at least listed as
undeterminedwhere a seat reading--residuecould see it. Here there is nothing to read and decline.Tool 2 —
scripts/check-self-test-wired.mjskeys it to a path that does not existThe same anchor, matching as a SUBSTRING, so
collectInvocationsrecords the gate underscripts/check-reference-carrier-shape.mjs— a path with no file behind it. That key then flows intoauditPopulationandauditLedger, whose whole job is deciding whether a script CI runs has its self-test run. A gate keyed to a nonexistent path is audited as a different script than the one CI executes, and the failure is silent in both directions: the real file is never audited, and the phantom key can never be reconciled against a carrier.Why it matters beyond one gate
The repo has a documented package-local gate lane (
packages/lint/scripts/), so this is not a one-off typo — it is a shape both derivations refuse to see. Today it costs one gate. The cost is a gate CI runs that no seat can be told about, which is precisely the defect #15312 was filed on, arriving through a different door.Remedy sketch (not a decision — the shape is a judgement for the owning lane)
Widen both anchors from
scripts/to any tracked path ending in a gate-shaped.mjs, or introduce an explicit second root for the package-local lane. Both tools already resolvecheck:*npm scripts to files, so the widening has to keep the direct-invocation and script-resolution answers from disagreeing. Whichever shape is chosen, it wants a self-test case pinningpackages/lint/scripts/check-reference-carrier-shape.mjsby name, since it is the tree's only live specimen and a widening measured on zero specimens is not measured.Generated by Claude Code