Skip to content

test(pm): run the half-state sweeper self-test in CI - #8544

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-8528-half-states-selftest-ci
Aug 13, 2026
Merged

test(pm): run the half-state sweeper self-test in CI#8544
hotlong merged 1 commit into
mainfrom
claude/issue-8528-half-states-selftest-ci

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#8528

scripts/pm/check-half-states.mjs carried a 79-case --self-test — the H1 through H7 predicates, the seat-sticker parser, the transport classifier and its measured container classes — that no CI job ran. It executed only when a human or an agent typed it, which makes it a check whose coverage is a function of who remembered. Fourth member of the same family as the dispatch-gates self-test, check:skill-frame-freshness and check:dev-prereqs, and the one with a blocking consumer: since the part-of/closing-keyword guard landed, a PR-scoped blocking check imports this file's h7PartOfWithClosingKeyword predicate (and stripMarkdownCode beneath it), so a break there now reddens — or silently greens — every PR in the repo.

What lands

  • check:pm-half-states in the root package.json, running the self-test only.
  • One unconditional named step in lint.yml, beside the other pm gates: no if:, no label read, no paths filter, and the lint job itself carries none either (verified by parsing the workflow, not by reading the diff).

The live sweep stays out of CI deliberately. It reads a shared board over the GitHub API, is report-only by design (a completed sweep exits 0 whether it found 0 or 40 half-states), and its non-zero exits classify the environment — no token, exhausted quota, unreachable host — which is not a verdict about whichever PR happens to run it. The script's own header argues both halves. The self-test is offline: no network, no token, ~0.05s.

The one measured decision: direct entry, no thin gate file

check:pm-dispatch-gates needs its own gate file because that tool's self-test fixtures are path strings, which become watch hints and fabricate MATCHED leads across most of the tree. The card asked whether the same applies here rather than copying the answer. Measured on this tree, under the module-body masking that now blanks comments and self-tests before the scan:

scriptnon-blank module-body lines after maskingwatch hints
scripts/pm/dispatch-gates.mjs3444 (.github/workflows, packages/plugins, packages/drivers, packages/services)
scripts/pm/check-half-states.mjs4221 (objectstack-ai/objectstack)

The single hint is the repo slug in the API base (process.env.PM_SWEEP_REPO ?? …). It is not a repo path, and hintCovers cannot pair it with any repo-relative input. The issue-shaped fixtures and prose specimens all sit inside the masked self-test. So the pollution that forced a separate file next door does not exist here, and the entry is pointed straight at the script — the same shape as check:partof-closing-keyword one line down.

Verified by derivation rather than by argument, after wiring:

  • family census 93 to 94 — the new family is discovered from the workflow;
  • identity derivation works, so a card editing the script now derives its own gate automatically: pnpm check:pm-half-states [lint.yml] matched via scripts/pm/check-half-states.mjs;
  • eight probe paths spanning packages/spec, packages/objectql, packages/rest, packages/plugins, .changeset, .claude/agents, .claude/skills and content/docs derive zero half-states leads;
  • the undetermined bucket stays at 35, so the family did not land there either.

Reverse verification

Direction predicted before running: the new gate red, the pre-existing coverage green — because the point of the card is that the pre-existing coverage is bounded to H7.

Ablating h3QueueAndDispatched (&& to ||) from the committed state:

pnpm check:pm-half-states -> exit 1 x check-half-states self-test: 1 of 79 case(s) failed.
pnpm check:partof-closing-keyword -> exit 0 check-partof-closing-keyword self-test: 28 cases pass.

Both halves as predicted: the new gate has teeth, and the incidental H7 coverage is blind to H3 — the gap this PR closes is real, not a formality. Restored with git checkout from the commit; 79/79 green again.

Gates

Derived against the actual diff (node scripts/pm/dispatch-gates.mjs package.json .github/workflows/lint.yml) and run as the union with the dispatch list — all green: check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check:nul-bytes, check:filter-alias-parity, check:type-source-resolution, check:changeset-gate-self-tests, check:type-check-coverage, check:type-check-debt (after turbo run build over the packages closure, as lint.yml does before that step), check:pm-dispatch-gates, check:pm-half-states, plus check-changeset-no-major.mjs, check-empty-changeset.mjs, check-shard-attestation.mjs. The re-derivation surfaced no family the dispatch list had missed.

No changeset: workflow plus a root-private script entry releases nothing, matching the dispatch-gates wiring PR that shipped the identical surface. skip-changeset applied. The packages/spec gate-to-generator ledger reconciles that package's own package.json, not the root's, so a root check: entry is out of its scope.


Generated by Claude Code

`scripts/pm/check-half-states.mjs` carried a 79-case --self-test that no CI
job ran — it executed only when a human or an agent typed it. Since the
part-of/closing-keyword guard landed, a PR-scoped BLOCKING check imports the
file's H7 predicate, so a break in it now reddens or silently greens every PR
in the repo; that guard's own self-test pins only the H7 behaviours it
depends on, leaving H1-H6, the seat-sticker parser and the transport
classifier with no CI coverage at all.
- new `check:pm-half-states` running the self-test only, wired as an
unconditional step in lint.yml beside the other pm gates
- the live sweep stays out of CI: it is report-only by design (a completed
sweep exits 0 on any finding count) and its non-zero exits classify the
environment, not the PR
- pointed straight at the script, no gate file in between: measured on this
tree under the landed module-body masking, it yields exactly one watch hint
(the repo slug in its API base), which covers no input path — so the
fixture-pollution that forced a separate file for check:pm-dispatch-gates
does not apply here
Fixes#8528
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 6:28pm

Request Review

@hotlonghotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 13, 2026 — with Claude
@github-actionsgithub-actionsBot added ci/cd dependencies Pull requests that update a dependency file labels Aug 13, 2026
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 18:43
@hotlong
hotlong added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 4e16119Aug 13, 2026
30 checks passed
@hotlong
hotlong deleted the claude/issue-8528-half-states-selftest-ci branch August 13, 2026 19:00
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/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] check-half-states.mjs has a 79-case --self-test that no CI job runs — and a blocking gate now imports its predicate

2 participants

@hotlong@claude