Skip to content

fix(pm): the clause-② path limb and SUSPECT_TIER_GLOBS except spec test files - #19948

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-19936-clause2-path-limb-excludes-tests
Sep 24, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/issue-19936-clause2-path-limb-excludes-tests

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #19936
Clause-②: no

The clause-② enqueue gate's path limb read every file under packages/spec/src, while the contract-review rule owes an at-tier review for non-test files there only. For a test-only spec PR from an off-tier seat, the two rules gave opposite answers, so the PR could never enqueue. The ruling on the card (comment 5805897677, letter A) settles it toward the review rule: the path limb and SUSPECT_TIER_GLOBS except test files, using the repo's own test-file predicate. contract-review.md is unchanged.

What changed

scripts/pm/dispatch-gates.mjs

  • SUSPECT_TIER_GLOBS: the packages/spec/src/** entry gains except: isTestPath and an exceptWhy reason. The glob and its why are byte-identical, so check-widening-tells.mjs, which builds its contract surface from g.glob and g.why, sees no change.
  • deriveTier: a suspect glob's except is applied per path, before the suspicion is recorded. A mixed diff keeps the suspicion on its contract files. MANDATORY_TIER_GLOBS has no exception and its semantics are unchanged.
  • The predicate is imported, not respelled: isTestPath from scripts/check-undeclared-dep-imports.mjs. The docblock above the table says why this one was chosen (see the table below).
  • Docs: a new docblock section on SUSPECT_TIER_GLOBS, one sentence in the clause-② header paragraph, and one on deriveTier.
  • Self-test: 20 new cases, listed under Verification. One existing census row moved from dispatch-gates.mjs:708 to :713, because the import shifted that file's own inherited-population declaration by five lines. That census names each declaration by file and line on purpose.

.claude/skills/pm-dispatch/references/landing-operations.md line 10

The line is rewritten in place and no line is added.

-- 路径肢 = diff 触及契约面 `packages/spec/src/**`,含 error-code-ledger 与 `*.zod.ts` 契约 schema。
+- 路径肢 = diff 触及契约面 `packages/spec/src/**` 非测试,含 error-code-ledger、`*.zod.ts` 契约 schema。

The line goes from 111 to 119 bytes.

非测试 is the exact word contract-review.md line 9 uses. To stay under the 120-byte line cap, 与 became 、, with no change in meaning. The file is still 101 lines, at its ceiling of 101.

Which test-file predicate, and why (measured)

The ruling says to take "the repo's own test-file predicate, not a new spelling". The repo has several. Each was run over five shape probes and over the 1541 tracked files under packages/spec/src on this branch:

predicate shape probes excepted tracked files excepted *.zod.ts excepted
check-undeclared-dep-imports.mjs isTestPath (exported) ⭐ 5 of 5 499 0
check-position-name-fold-loaders.mjs isTestFile (exported) 5 of 5 499 0
check-examples-live-imports.mjs (not exported) 5 of 5 499 0
check-dispatcher-error-vocabulary.mjs isTestFile (not exported; also bare fixtures/ anywhere) 5 of 5 499 0
check-runner-env-posture.mjs isTestFile (also .bench., .pin., bare fixtures/) 5 of 5 500 0
check-adr-0087-registration.mjs TEST_FILE_RE (filename only) 2 of 5 500 0
this file's own isTestFilePath (filename only, deliberately) 2 of 5 499 0
tenant-audit-census.mjs / isystem-census.mjs isTestPath (treats qa/ as a test dir) 4 of 5 (misses test/) 501 1 (qa/testing.zod.ts)

The five shape probes cover the ruling's four shapes: stack.test.ts, type-alias-convention.pin.test.ts, a helper under __tests__/, a fixture under __tests__/, and a fixture under test/.

isTestPath was chosen because its gate asks the ruling's exact question: which files under a package's src/ are published source, and which are its tests. It is exported and it covers all four shapes. Its only test directories are test/, tests/, __tests__/ and __fixtures__/, none of which is a contract domain under packages/spec/src. A predicate that also treats qa/ as a test directory would drop a real contract: packages/spec/src/qa/testing.zod.ts is a real contract schema, and it stays a SUSPECT. That case is pinned.

The lit case (ruling item 3)

node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack packages/spec/src/type-alias-convention.pin.test.ts (the one file PR #19932 changes):

  • base 43460b95: prints the SUSPECT block, and its path row reads - packages/spec/src/type-alias-convention.pin.test.ts ⇢ 'packages/spec/src/**' — the contract surface (error-code ledger, *.zod.ts contract schemas) — the normal landing zone of a clause-② card.
  • head ef4d57c0: no line contains SUSPECT. It prints Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s) … and the standing clause-② note (Clause ② is NOT reachable from paths: … This line is a FLOOR, never a clearance.).

Same command on packages/spec/src/ui/view.zod.ts at head ef4d57c0: the SUSPECT block still prints, and its path row reads - packages/spec/src/ui/view.zod.ts ⇢ 'packages/spec/src/**' — the contract surface …. The block's header line names the contract-review tier's model id, so it is not quoted here.

Verification (at HEAD ef4d57c0)

The gates came from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths (36 commands). Five more came from the dispatch-time list. Each exit code was captured before any pipe. The --ran result: ✓ dispatch-gates --ran: 36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED.

gate exit verdict line
pnpm check:pm-dispatch-gates 0 ✓ dispatch-gates self-test: 1925 cases pass. (831 s, run detached as its header prescribes)
pnpm check:pm-skill-ratchet 0 landing-operations.md is 101 lines (ceiling 101; headroom 0)
pnpm check:pm-skill-id-lint 0 34 file(s) clean
node scripts/check-scripts-symbol-anchors.mjs 0 3668 anchors across 281 scripts resolve
node scripts/check-self-test-wired.mjs 0 every one of the 229 script(s) CI runs that ship a --self-test has that self-test run by CI
node scripts/pm/check-governed-queue-guard.mjs --self-test 0 279 cases pass
node scripts/check-skills-token-ratchet.mjs 0 54 authored bundle file(s) within their ceilings
pnpm check:pm-widening-tells (not derived; it imports this table) 0 525 cases pass
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 The first run exited 3 (PREREQUISITE NOT MET: formula and lint were not built). It exited 0 after turbo run build of those two packages under the verify lock.
pnpm check:nul-bytes 0 scanned 9353 text file(s) … no raw ASCII control bytes
the other 30 derived or listed commands 0 green

The 20 new self-test cases:

  • The lit case raises no suspicion. Its rendering has no SUSPECT line and still carries the clause-② note.
  • A *.zod.ts path is still a suspect.
  • Five shape cases are each excepted: a plain test, a pin test, a __tests__/ helper, a fixture under __tests__/, and a fixture under test/.
  • A mixed diff keeps its contract file.
  • qa/testing.zod.ts stays a suspect while qa/testing.test.ts is excepted.
  • A directory surface that contains the contract surface is still a suspect.
  • A test-named file under a mandated root keeps its mandate.
  • The exception is the imported function, and its home is a gate script, so a card editing it derives this gate by gate-script identity.
  • Every excepting entry carries a reason.
  • Live: none of the 203 tracked *.zod.ts files is excepted, and the exception really drops tracked files.
  • End to end on the CLI: --tier on the lit case prints no SUSPECT line, and --tier on view.zod.ts does.

Ablation, from committed state:

  • Mutation: deriveTier's guard if (hintCovers(g.glob, p) && !(typeof g.except === 'function' && g.except(p))) { became if (hintCovers(g.glob, p)) {. It was applied through scripts/ablation-replace.mjs in WRAP mode, and the file hashes show it landed: anchor 1 → 0, replacement 0 → 1, blob caf66526bc7d → f2f8a50e2b5e.
  • Predicted before the run: 11 red. Observed: ✗ dispatch-gates self-test: 11 of 1925 case(s) failed., the predicted 11 exactly. They are the two lit-case cases, the five shape cases, the mixed diff, the qa/testing.test.ts case, the live non-vacuity case, and the CLI lit case.
  • The zod, qa/testing.zod.ts, ancestor, mandate, provenance and live-zod cases stayed green, as they should with no exception applied.
  • Restore: blob after restore caf66526bc7d equals HEAD, git diff HEAD is empty, and git status --porcelain is empty.

eslint, narrowed to the one changed .mjs file:

  • Population: eslint --print-config shows 2 rules (no-restricted-imports, comment-swallow/no-code-inside-block-comment), both per-file, and no parserOptions.project.
  • Count: --format json reports 1 file, 0 errors, 0 warnings.
  • Invariance: no type-aware linting is enabled, so this diff cannot move any untouched file's verdict. The .md file is not linted.

NOT MEASURED: pnpm check:published-readme-exports, exit 3. It needs a full pnpm build of the workspace, which was not run. It is an artifact-roster family printed for every card. This diff touches no package, no package README and no type surface.

Acceptance notes

  • check-widening-tells.mjs, the mechanical half of the declaration limb, imports SUSPECT_TIER_GLOBS and hintCovers from this file. It already excludes tests with its own filename-only predicate (isContractSourceFile). A __tests__/ helper under packages/spec/src would still be read there for tells. There are none on the tree today. That is outside this card's surface and was not changed.
  • A derivation gap that predates this PR: dispatch-gates.mjs --commands scripts/pm/dispatch-gates.mjs does not name pnpm check:pm-widening-tells, although that gate imports from this file. The same holds for its other two imports (scripts/regen-artifacts.mjs, scripts/pm/check-half-states.mjs). The documented remedy is a module constant in the importing gate, the SURFACE_MODULE shape in check-dispatch-gates.mjs. The gate was run here anyway. Carrier: none.
  • packages/spec/src/benchmark.bench.ts stays a SUSPECT. Neither the chosen predicate nor the ruling's four shapes name bench files.
  • The ruling's __tests__/** shape would also except a *.zod.ts placed under a test directory. The package's files[] entry for src zod files would still ship such a file. None exists today, and the new live pin reds the day one does.

维护者速读(草稿)

改了什么:入队闸门的「路径肢」(landing-operations.md 第 10 行)和 dispatch-gates.mjs 的嫌疑表 SUSPECT_TIER_GLOBS,现在都把 packages/spec/src/** 下的测试文件排除在外。排除用的是仓里现成的测试文件判据(check-undeclared-dep-imports.mjs 的 isTestPath),直接 import,没有另写一份。--tier 对只改 spec 测试的 diff 不再打印 SUSPECT 行;改 *.zod.ts 的照旧打印。contract-review.md 未动。

为什么改:按裁决 A(评论 5805897677,维护者「同意」)。两条规则原先对「只改 spec 测试文件的 PR」互相矛盾:一条要求先有达档复核记录,另一条又说不许为它另起复核。结果出自不达档席位的这类 PR 永远进不了队列,眼下卡住的是 PR #19932。测试文件不随包发布,改它不改变任何对外契约。

风险与代价(含回滚):这道门放宽了,但只放宽到「测试文件」。另有两处自测直接对着真实文件树兜底:仓里 203 个 *.zod.ts 一个都不许被排除;qa/testing.zod.ts 这种名字像测试、实为契约的文件必须照旧打嫌疑行。消融实测:去掉排除,恰好预期的 11 条自测变红。强制档表(MANDATORY)没有任何排除。回滚:revert 本 PR 即可,无数据迁移。

席位意见:

你要做的:无需操作。本 PR 属 Tier S(.claude/** + scripts/pm/**),由 skills 席位完成达档复核后入队;PR #19932 在本 PR 落地后入队。


Generated by Claude Code

…st files

The enqueue gate's path limb read every file under packages/spec/src, while
the contract-review rule owes an at-tier record for non-test files only, so a
test-only spec PR from an off-tier seat could never enqueue. Per the ruling
(letter A), SUSPECT_TIER_GLOBS' contract-surface entry now carries an
`except` predicate — isTestPath, imported from check-undeclared-dep-imports.mjs,
never respelled — that deriveTier applies per path before recording a
suspicion; landing-operations.md's path limb reads 非测试 in place.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
The lit case (a test-only spec diff) raises no suspicion and --tier prints no
SUSPECT line; a *.zod.ts diff still does; each of the four ruled test shapes
is excepted; a mixed diff keeps its contract file; qa/testing.zod.ts stays a
suspect; the exception is the imported predicate and never drops a tracked
*.zod.ts on this tree.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
The isTestPath import above it shifted dispatch-gates.mjs's own
inherited-population declaration from line 708 to 713; the census names each
declaration by file:line on purpose, so the row moves with it.

Claude-Session: https://claude.ai/code/session_01A22sUB3mUWs6M36VgfijBq
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ef4d57c09b8207b88f443b62cec923f066a65e5c

① Derived judgments

  • scripts/pm/dispatch-gates.mjs (+107/−5): SUSPECT_TIER_GLOBS' one entry (packages/spec/src/**) gains except: isTestPath + exceptWhy; deriveTier applies except per path before recording a suspicion (hintCovers(g.glob, p) && !(typeof g.except === 'function' && g.except(p))), so a mixed diff keeps every non-excepted path's suspicion and a mandate is never excepted; MANDATORY_TIER_GLOBS untouched. The predicate is imported from scripts/check-undeclared-dep-imports.mjs :412 (exported), not respelled — the ruling's 「the repo's own test-file predicate, ⛔ not a new spelling」 holds; the docblock records why this predicate over the repo's others (5/5 ruling shapes, 0 tracked *.zod.ts excepted, keeps qa/testing.zod.ts). Reproduced on the head in a review worktree: --tier on packages/spec/src/type-alias-convention.pin.test.ts prints 0 SUSPECT lines; packages/spec/src/ui/view.zod.ts and packages/spec/src/qa/testing.zod.ts still print the SUSPECT row; a __tests__/ path prints none. Self-test: 20 new cases pin the lit case, the zod case, the five shapes, the mixed diff, the adversarial qa/testing.zod.ts, the imported-predicate identity, a live no-*.zod.ts-excepted guard and a non-vacuity guard, plus two real-CLI pins; the marker-census row moved 708 → 713 with the import — correct.
  • .claude/skills/pm-dispatch/references/landing-operations.md :10 rewritten in place: 「路径肢 = diff 触及契约面 packages/spec/src/** 非测试,含 error-code-ledger、*.zod.ts 契约 schema。」 — 119 bytes (cap 120), 101 / 101 lines, the word 非测试 is contract-review.md :9's own; no line added, no rule lost (与 → 、 only). contract-review.md unchanged, as ruled.
  • Accept set: the clause-② SUSPICION narrows by exactly the imported predicate; the enqueue gate's declaration limb and the governed register are untouched; check-widening-tells.mjs (a second importer of SUSPECT_TIER_GLOBS) builds from glob and why only — 525 cases still pass. Public surface: none (.claude/**, scripts/pm/** ship in no package) ⇒ skip-changeset is the right form.
  • Closing keywords: Fixes #19936 on line 1 only; no other keyword beside a card number (whole body scanned). Governed: Tier S (check-governed-merges --pr 19948: 1 of 2 paths under .claude/**; dispatch-gates.mjs not on the register; 114 lines).

② Semver level

  • none — no published package touched; skip-changeset label present (timeline 03:22:48Z).

③ Boundary flags

  • open_questions: none (no predicate gap measured). Deviations 1–5 read: the fifth predicate candidate is the measured best and is what the ruling asks for; assumption 6 partly refuted (check-widening-tells.mjs is a second carrier whose verdicts do not move) — recorded, no change owed; the :10 wording fits the cap with the reference's own word.
  • NOT MEASURED, accepted: pnpm check:published-readme-exports (needs a full workspace build; the diff touches no package, README or type surface).
  • Out-of-scope findings (carrier-less, noted on the seat post, ⛔ not this PR's): dispatch-gates' derivation omits check:pm-widening-tells for an edit to a module that gate imports; isContractSourceFile in check-widening-tells.mjs is filename-only (dormant, zero instances).

Implemented-by: claude/issue-19936-clause2-path-limb-excludes-tests
Reviewed-by: session_01A22sUB3mUWs6M36VgfijBq

VERDICT: PASS


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 24, 2026 03:42
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 2dc5889 Sep 24, 2026
37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-19936-clause2-path-limb-excludes-tests branch September 24, 2026 04:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants