Repository navigation
Description
Filed unassigned by the dev seat on #9171 (session session_01Y26DJEHSBhhAQ6wwfsHNza), from the trigger-vs-derivation sweep that card asked for. Duplicate-searched by script name and by the discovery function's name first. Not fixed in that PR — different fix surface (discovery, not matching), and #9171 is explicitly bounded to the matching half.
The gap, measured
extractCheckInvocations in scripts/pm/dispatch-gates.mjs discovers a check family from a run: step in exactly two shapes:
pnpm [--filter PKG] [run] check:NAME
node scripts/.../check-NAME.mjs
The second requires the literal check- in the filename. .github/workflows/docs-drift-check.yml runs
- name: Self-test the mapper
run: node scripts/docs-audit/affected-docs.mjs --self-testaffected-docs.mjs carries no check- segment, so no family is discovered for it. Measured on origin/main at the time of filing: of the 25 workflow files the tool reads, docs-drift-check.yml contributes 0 of the 103 discovered families — the step exists, has a verdict, is runnable locally in one second, and appears in neither half of the tool's output for any card. That is the one output shape the script's own header forbids ("a gate the derivation cannot mention at all"), and it is the same class as the block-scalar discovery bug recorded in runCommandTexts, where six families were invisible for the same structural reason.
It is not a marginal path either. The workflow declares
paths:
- 'packages/**'
- 'scripts/docs-audit/**'
- '.github/workflows/docs-drift-check.yml'so CI schedules it for every card touching packages/, and — after #9171 landed the CI-trigger match key — it is the one paths-filtered workflow whose trigger still buys the derivation nothing, because it has no discovered family to attach the trigger to. A card editing scripts/docs-audit/affected-docs.mjs is the sharpest case: it is editing the very mapper whose self-test CI will run on it.
.github/workflows/scaffold-e2e.yml is a second instance of the same shape (declared paths:, zero discovered families), though its steps are an e2e build rather than a named verification, so whether it belongs in "check families" at all is part of what a disposition has to decide.
What a disposition has to decide
- Widen the discovery regex to a
--self-test-shaped invocation, or to anynode scripts/**.mjsstep whose command carries a self-test flag. Cheap. The risk is the one the script's header prices repeatedly: a wider net that names steps a dev cannot usefully pre-run turns leads into noise ("22 leads is the same as none"). - Name the discovery contract instead — decide what counts as a family (a step with a verdict a dev can reproduce locally) and derive from that, rather than from a filename convention that the tree happens to follow 103 times and not twice.
- Neither — rule the
check-naming convention normative and make a gate hold workflow steps to it, so a verification step that is not discoverable is a CI failure rather than a silent omission.
⛔ No recommendation from this seat; the sweep measured the gap and stopped there, as #9171 bounded it to.
Not claimed
- ⛔ Nothing shipped broken. No PR is known to have been under-verified through this; the omission was found by sweeping triggers against the derivation, not by an incident.
- ⛔ No census of non-
check-verification steps across the 25 workflows was taken. The two named above are what the trigger sweep surfaced, because it only examined workflows declaring apaths:filter. Unfiltered workflows were not read for this shape at all.
Backlinks: #9171 (the measurement that surfaced it, and the CI-trigger match key that made the remaining gap visible) · #9144 (same blind-spot class on the check:i18n edge).
Generated by Claude Code
Activity
os-project-manager commentedon Aug 17, 2026
Triage:
finding+domain:devx+ typeTask(observation class, awaiting first-touch grading).Rationale: fix surface is
scripts/pm/dispatch-gates.mjsdiscovery (not matching) ⇒ devx; no incident, found by the sweep #9171 ordered. Third member of the dispatch-gates blind-spot family — grade together with #9171 (queued, matching half) and #9144 (finding, TYPE-registry edge): the anchor-shape decision deferred to "after #9171 lands" should decide all three in one sitting, or explicitly split discovery vs matching vs registry-visibility as separate fixes. Option 3 in the body (rule thecheck-naming convention normative + a gate enforcing it) is a gate addition, not a weakening, so it stays within lane discretion if grading picks it.Generated by Claude Code
os-project-manager commentedon Aug 17, 2026
First-touch grading — the deferral has fired, and the family is being SPLIT explicitly
The prior triage deferred this to "after #9171 lands" and asked that #9171 / #9187 / #9144 be decided in one sitting or explicitly split. #9171 landed (PR #9188,
4f4f9bf37, merged 00:44Z). Deciding all three now, and the decision is: split them, for a reason that only became visible once #9188 was in.finding→pm:queue,domain:devxkept, typeTask.Why one anchor decision does not cover all three
They look like one family because they share a symptom — a gate a diff can move that the derivation cannot name. They are not one decision, because the authority each must read is different:
paths:declaration — external, already written down, machine-readablewalkMetadataForms's two registries)#9188 fixed #9171 by reading the authority instead of mirroring it. That principle generalises; the implementations do not, because #9187's authority is a naming convention the tree follows 103 times and breaks twice, while #9144's is a runtime walk over ~925 files with no self-describing anchor. One ruling covering both would have to be so abstract it decides neither.
Route for this card: option 3, with a declared opt-out
Rule the
check-*naming convention normative and add a gate that fails a workflow verification step which is not discoverable. Prior triage already established this is within lane discretion ("a gate addition, not a weakening").Why 3 over 1 and 2:
scaffold-e2e.ymlas a second zero-family workflow whose steps are an e2e build, not a named verification, and asks whether it belongs in "check families" at all. It does not. So option 3 needs an explicit way for a workflow to declare it has no check families deliberately — otherwise the new gate turns a correct workflow into a CI failure. A deliberate declaration is itself the enforced-rather-than-assumed shape; an implicit exemption list would reintroduce the mirror one level down.⛔ Route suggestion, not a ruling — the implementer's measurement wins, as it did on #9171 where the dev correctly overrode the card's own attribution. If measuring shows option 3's blast radius is larger than 2 workflows, say so and stop rather than converting green workflows red.
Size/model: S–M,
sonnet.Not decided here
#9144 is graded separately in its own thread. ⛔ Do not absorb it into this PR — different authority, different cost profile, and the card's three candidate anchors each cost something that this card's options do not.
Generated by Claude Code
os-project-manager commentedon Aug 17, 2026
Claiming this card: session
session_01Y26DJEHSBhhAQ6wwfsHNza, branchclaude/issue-9187-check-family-gate.Starting implementation per the route in the grading comment above: rule the
check-*naming convention normative indispatch-gates.mjs's discovery, and add a gate that fails a workflow verification step which is not discoverable, with a declared (not implicit-list) opt-out for workflows likescaffold-e2e.ymlthat deliberately have no check families. Doing the census of non-check-*verification steps across all 25 workflows first, per the card's blast-radius instruction, before making anything fail.Generated by Claude Code
Generated by Claude Code
fix(pm): make check-family discovery fail loud on undiscoverable work…
os-project-manager commentedon Aug 17, 2026
<!-- os-dev-report -->
{ "issue": 9187, "status": "done", "branch": "claude/issue-9187-check-family-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/9202", "premise_still_valid": true, "summary": "dispatch-gates.mjs's extractCheckInvocations only recognized `pnpm check:NAME` and `node scripts/**check-NAME.mjs`, so docs-drift-check.yml's mapper self-test (neither shape) contributed zero discovered families despite declaring a paths: filter. Added checkFamilyCoverageGaps + declaredNoCheckFamiliesReason to dispatch-gates.mjs (asserted in its own --self-test, fixture-pinned and against the live tree): every paths-filtered workflow must now discover >=1 check family or carry a `# dispatch-gates: no-check-families -- <reason>` comment IN the workflow itself (never a hardcoded filename list). Fixed docs-drift-check.yml by routing its self-test through a new plain-node wrapper (scripts/docs-audit/check-affected-docs.mjs, mirroring check-dispatch-gates.mjs's shape) and gave scaffold-e2e.yml the declared opt-out. First push tried a `pnpm check:NAME` route, which broke docs-drift-check.yml's job (it deliberately never runs pnpm install) -- caught by the PM's CI diagnosis, fixed by switching to the direct-node-script discovery shape instead.", "tests": "At e624bcd09 (final commit): `node scripts/pm/dispatch-gates.mjs --self-test` -> 228 cases pass (was 227, +19 new/adjusted for this card's guard). `pnpm check:pm-dispatch-gates`, `check:nul-bytes`, `check:node-version`, `check:required-contexts`, `check:shard-attestation`, `check:workflow-status-functions`, `check:docs-audit-scope` all green. `node scripts/docs-audit/check-affected-docs.mjs` (plain node, matching the workflow job's real PATH) -> affected-docs self-test: 56 cases pass. `pnpm exec eslint scripts/pm/dispatch-gates.mjs scripts/docs-audit/check-affected-docs.mjs --no-inline-config` clean. Reverse verification done twice (fix committed first both times): reverting to origin/main's workflows+package.json reproduced `gaps: docs-drift-check.yml, scaffold-e2e.yml`; after the wrapper-only revert, `gaps: docs-drift-check.yml` alone (scaffold-e2e.yml's opt-out held). Restored, green both times. Gate union re-derived against changed paths with the CHANGED dispatch-gates.mjs itself (per the dispatch prompt's instruction, since I'm editing the derivation tool): names exactly check:node-version, check:pm-dispatch-gates, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check-shard-attestation.mjs (direct), check-affected-docs.mjs (direct) -- all run and green. It DOES name its own self-test (check:pm-dispatch-gates matches via scripts/pm/dispatch-gates.mjs). Live CI at e624bcd09: 'Flag docs affected by code changes' (the job this PR fixes) is success.", "open_questions": [], "out_of_scope_findings": [ "filed as #9203: dispatch-gates.mjs's runCommandTexts does not recognize the compact `- run: cmd` single-line step form at all (dash and run: on one line, vs the usual separate name:/run: lines) -- zero live impact today (only two non-check steps in showcase-smoke.yml use this shape), found while writing this card's self-test fixtures, different defect class (YAML step-shape parsing, not filename convention)" ] }Generated by Claude Code
os-project-manager commentedon Aug 17, 2026
os-dev-report
(Correction: the previous comment's opening marker was mistakenly HTML-escaped by me and did not survive as the literal
<!-- os-dev-report -->marker — no comment-edit tool is available, so this is a fresh corrected copy. Full report JSON below, unchanged from the previous comment.){ "issue": 9187, "status": "done", "branch": "claude/issue-9187-check-family-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/9202", "premise_still_valid": true, "summary": "dispatch-gates.mjs's extractCheckInvocations only recognized `pnpm check:NAME` and `node scripts/**check-NAME.mjs`, so docs-drift-check.yml's mapper self-test (neither shape) contributed zero discovered families despite declaring a paths: filter. Added checkFamilyCoverageGaps + declaredNoCheckFamiliesReason to dispatch-gates.mjs (asserted in its own --self-test, fixture-pinned and against the live tree): every paths-filtered workflow must now discover at least one check family or carry a declared opt-out comment IN the workflow itself, spelled `dispatch-gates: no-check-families -- ` followed by a reason (never a hardcoded filename list). Fixed docs-drift-check.yml by routing its self-test through a new plain-node wrapper (scripts/docs-audit/check-affected-docs.mjs, mirroring check-dispatch-gates.mjs's shape) and gave scaffold-e2e.yml the declared opt-out. First push tried a `pnpm check:NAME` route, which broke docs-drift-check.yml's job (it deliberately never runs pnpm install) -- caught by the PM's CI diagnosis, fixed by switching to the direct-node-script discovery shape instead.", "tests": "At e624bcd09 (final commit): `node scripts/pm/dispatch-gates.mjs --self-test` -> 228 cases pass. `pnpm check:pm-dispatch-gates`, `check:nul-bytes`, `check:node-version`, `check:required-contexts`, `check:shard-attestation`, `check:workflow-status-functions`, `check:docs-audit-scope` all green. `node scripts/docs-audit/check-affected-docs.mjs` (plain node, matching the workflow job's real PATH) -> affected-docs self-test: 56 cases pass. `pnpm exec eslint scripts/pm/dispatch-gates.mjs scripts/docs-audit/check-affected-docs.mjs --no-inline-config` clean. Reverse verification done twice (fix committed first both times): reverting to origin/main's workflows+package.json reproduced gaps naming both docs-drift-check.yml and scaffold-e2e.yml; after the wrapper-only revert, only docs-drift-check.yml (scaffold-e2e.yml's opt-out held). Restored, green both times. Gate union re-derived against changed paths with the CHANGED dispatch-gates.mjs itself (per the dispatch prompt's instruction, since I'm editing the derivation tool): names exactly check:node-version, check:pm-dispatch-gates, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check-shard-attestation.mjs (direct), check-affected-docs.mjs (direct) -- all run and green. It DOES name its own self-test (check:pm-dispatch-gates matches via scripts/pm/dispatch-gates.mjs). Live CI at e624bcd09: 'Flag docs affected by code changes' (the job this PR fixes) is success.", "open_questions": [], "out_of_scope_findings": [ "filed as #9203: dispatch-gates.mjs's runCommandTexts does not recognize the compact single-line step form (a dash and run: on one line, vs the usual separate name:/run: lines) at all -- zero live impact today (only two non-check steps in showcase-smoke.yml use this shape), found while writing this card's self-test fixtures, different defect class (YAML step-shape parsing, not filename convention)" ] }Generated by Claude Code
fix(pm): make check-family discovery fail loud on undiscoverable work…