Skip to content

fix(pm): make check-family discovery fail loud on undiscoverable workflow verification steps - #9202

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-9187-check-family-gate
Aug 17, 2026
Merged

fix(pm): make check-family discovery fail loud on undiscoverable workflow verification steps#9202
os-project-manager merged 2 commits into
mainfrom
claude/issue-9187-check-family-gate

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes#9187

What

extractCheckInvocations in scripts/pm/dispatch-gates.mjs only recognized two shapes as a discoverable check family: pnpm [--filter PKG] [run] check:NAME and node scripts/**check-NAME.mjs. A workflow verification step using neither shape contributed zero discovered families and was invisible to both halves of the tool's output — not matched, not undetermined, not silent. docs-drift-check.yml's mapper self-test (node scripts/docs-audit/affected-docs.mjs --self-test) was exactly that case, and — after #9171/#9188 landed the CI-trigger match key — it became the one paths:-filtered workflow whose trigger bought the derivation nothing, because there was no discovered family to attach it to.

Per the route in the grading comment (option 3): rule the check-* naming convention normative and add a gate that fails a workflow verification step which is not discoverable, with a declared (not implicit hard-coded) opt-out for workflows that genuinely have no check family.

Blast-radius census (per the card's instruction, before making anything fail)

Of the 25 workflow files, only 6 declare a paths: filter (the scope this gate applies to — see the code comment for why it's scoped that way):

workflowdiscovered families before this PR
docs-drift-check.yml0 ← fixed here
scaffold-e2e.yml0 ← exempted here
engine-split-metric.yml1
prerelease-pin-watch.yml1
spec-liveness-check.yml4
validate-deps.yml3

Exactly the 2 known cases from the issue — no larger blast radius, so proceeding with the failing gate is safe. (Unfiltered workflows run on every PR regardless of any family, so they're out of this gate's scope by design.) I also found two heavier, non-paths:-filtered scripts that are genuinely verification-with-a-verdict but not check--named — scripts/downstream-smoke.sh (pre-publish hotcrm compat gate) and scripts/publish-smoke.sh (publish-artifact smoke) — both already use an established, distinct, deliberately-different *-smoke.sh naming convention (network/registry-dependent, not a quick local pre-push gate the way check:* is), and neither lives in a paths:-filtered workflow, so they're unaffected by this gate and out of scope for this card.

Changes

  • scripts/pm/dispatch-gates.mjs: adds declaredNoCheckFamiliesReason (reads a workflow's own opt-out comment, spelled # dispatch-gates: no-check-families -- followed by the reason text) and checkFamilyCoverageGaps (every paths:-filtered workflow must discover at least one check family, or carry that declared reason). Both are asserted in the tool's own --self-test, fixture-pinned and against the live workflow tree — mirroring the existing "census guard" ([finding] check:engine-double-contract is a PM judgement call in dispatch-gates.mjs, not a convention entry — and a mechanical trigger for it exists #8632) pairing. The opt-out is declared by the workflow, never a hardcoded filename list in this script (the exact mirror-one-level-down shape the card calls out).
  • docs-drift-check.yml: the mapper self-test now runs through a new wrapper script, scripts/docs-audit/check-affected-docs.mjs, instead of a raw node scripts/docs-audit/affected-docs.mjs --self-test call, so it becomes discoverable (now 1 family, not 0).
  • scripts/docs-audit/check-affected-docs.mjs (new): a thin spawn-and-relay wrapper, mirroring scripts/pm/check-dispatch-gates.mjs's own established shape — plain node, zero dependencies. Not a pnpm check:NAME package.json script: docs-drift-check.yml's job deliberately never runs pnpm install (the mapper it wraps has zero deps, kept that way so this job stays fast on every packages/** PR), so a pnpm invocation isn't just unwired, pnpm itself isn't on PATH there. First push of this PR tried the pnpm check:NAME route and broke this exact job (pnpm: command not found, exit 127) — fixed by switching to the direct-node-script discovery shape instead, which needs nothing this job doesn't already have.
  • scaffold-e2e.yml: carries the declared opt-out — its steps are an install/build/boot/docker e2e pipeline, not a named local verification.

Reverse verification (both fix shapes): reverted the workflow/script changes back to origin/main and re-ran --self-test each time — it failed exactly as expected (gaps: docs-drift-check.yml, scaffold-e2e.yml for the full revert; gaps: docs-drift-check.yml for the final wrapper-only revert). Restored, green again both times.

Tests

At e624bcd09 (final commit):

node scripts/pm/dispatch-gates.mjs --self-test
✓ dispatch-gates self-test: 228 cases pass.
pnpm check:pm-dispatch-gates # ✓ (runs the self-test above)
pnpm check:nul-bytes # ✓
pnpm check:node-version # ✓
pnpm check:required-contexts # ✓
pnpm check:shard-attestation # ✓
pnpm check:workflow-status-functions # ✓
pnpm check:docs-audit-scope # ✓ (affected-docs.mjs is unchanged; sanity-checked anyway)
node scripts/docs-audit/check-affected-docs.mjs # ✓ affected-docs self-test: 56 cases pass. (run with plain `node`, matching what the workflow job actually has on PATH)
pnpm exec eslint scripts/pm/dispatch-gates.mjs scripts/docs-audit/check-affected-docs.mjs --no-inline-config # clean

Re-derived the gate union against the changed paths with the changed version of the script itself (I'm editing dispatch-gates.mjs, so its own derivation output is only trustworthy read from the edited copy):

node scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.mjs .github/workflows/docs-drift-check.yml .github/workflows/scaffold-e2e.yml scripts/docs-audit/check-affected-docs.mjs

names exactly: check:node-version, check:pm-dispatch-gates, check:required-contexts, check:shard-attestation, check:workflow-status-functions, node scripts/check-shard-attestation.mjs, node scripts/docs-audit/check-affected-docs.mjs (all run above, all green). It does name its own self-test — check:pm-dispatch-gates matches via scripts/pm/dispatch-gates.mjs, so a card editing this file always derives its own gate.

Live check run at this head: "Flag docs affected by code changes" (the job this PR's fix targets) — success.

Not in scope


Generated by Claude Code

…flow verification steps
dispatch-gates.mjs only recognized `pnpm check:NAME` and `node scripts/**check-NAME.mjs`
as check-family invocations, so a workflow verification step using neither shape
contributed zero discovered families and was invisible to BOTH halves of the tool's
output (#9187). Measured: of the 25 workflow files, only 6 declare a `paths:` filter,
and of those, `docs-drift-check.yml` (self-test via a non-`check-`-named script) and
`scaffold-e2e.yml` (a genuine e2e pipeline, no check family at all) were the only two
with zero discovered families — matching the issue's own count, so the fix is scoped to
exactly those two rather than a broader regex widening.
- dispatch-gates.mjs: add `checkFamilyCoverageGaps` + `declaredNoCheckFamiliesReason`,
asserted in the tool's own self-test (both fixture-pinned and against the live
workflow tree) — every paths-filtered workflow must now discover at least one check
family, or declare why not via a `# dispatch-gates: no-check-families -- <reason>`
comment carried IN the workflow file itself (never a hardcoded filename list here).
- docs-drift-check.yml: name the mapper self-test through a new `check:docs-drift-mapper`
package.json script instead of a raw `node` invocation, so it becomes discoverable.
- scaffold-e2e.yml: declare the opt-out — its steps are an e2e build, not a named
verification.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
@os-project-manageros-project-manager added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 17, 2026 — with Claude
@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file labels Aug 17, 2026
…ng pnpm
docs-drift-check.yml's job never runs `pnpm install` (the mapper it self-tests has
zero dependencies, kept that way deliberately so the job stays fast on every PR
touching packages/**), so a `pnpm check:NAME` invocation broke it outright
(`pnpm: command not found`, PR #9202 CI). Route it through a thin wrapper script
instead — scripts/docs-audit/check-affected-docs.mjs, spawned via a plain `node`
call, mirroring scripts/pm/check-dispatch-gates.mjs's own established shape — which
satisfies dispatch-gates.mjs's OTHER discovery form (`node scripts/**check-NAME.mjs`)
without adding a pnpm dependency to a job that deliberately has none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude