Skip to content

ci(lint): run os-regen-merge's --self-test, mirroring the verify-lock step - #12944

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-12893-wire-regen-merge-selftest
Aug 28, 2026
Merged

ci(lint): run os-regen-merge's --self-test, mirroring the verify-lock step#12944
os-litant merged 1 commit into
mainfrom
claude/issue-12893-wire-regen-merge-selftest

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#12893

What

One step in the Lint & Repo Gates job of .github/workflows/lint.yml, mirroring the
existing Verify-lock entry-point self-test step 1:1:

- name: os-regen-merge self-testrun: bash scripts/pm/os-regen-merge.sh --self-test

File surface: .github/workflows/lint.yml only. Nothing else is touched. The collector
shape the card floats — a discovery step over scripts/pm/*.sh --self-test — is ruled
out of this card by the grading and is not built here; the step's comment records the
ruling and names a third such script as when to revisit it.

Why this shape

scripts/pm/os-regen-merge.sh ships a 23-case --self-test and, before this PR, no
workflow in the tree named the script at all (grep -rn os-regen-merge .github/ package.json matched nothing on main at 4d1142753).

The script itself has no CI path by design: it is run by hand inside a feature branch's
worktree, on a merge that exists only there. Its self-test is a different animal —
no worktree, no remote, no merge, only git and a temp dir — and it builds five small
fixture repos to pin the per-file merge-side selection, the per-path notices, the
staged-diff sentence, the uncommitted-hand-off refusal, and the four pre-existing
refusals. A few seconds, no network.

Two of the 23 cases are source scans of the script's own text: they assert step 2 keeps
the non-staging git restore --source spelling and never the staging git checkout
one. Those are the rows a future refactor invalidates in silence and the rows no
reviewer reads. This repo has the same shape on record twice already, both remedied at
the time — #11514 and #6008.

Placement. After PM ci-failure self-test and before the Claude hook guard self-tests collector, i.e. at the tail of the PM self-test family, rather than adjacent
to its sibling: the ci-failure step's comment calls itself "Fifth member of the PM
self-test family above", and inserting ahead of it would have made that ordinal
stale. The diff is therefore purely additive — 34 lines, no existing line changed.

One --self-test per run: block, deliberately. The masking shape
check-step-collectors.mjs guards is a block driving two or more distinct scripts; a
lone self-test in its own step is this file's dominant shape and is what the gate reads
as correct. Unconditional and un-if:-ed, like every self-test around it — an exemption
is precisely what a self-test must not have.

Required contexts: unchanged, verified mechanically

The card asked for this explicitly. A step is not a job, and the required-set is keyed
on job names. Parsed both sides of the diff with the yaml package:

origin/main job names: ["lint=>Lint & Repo Gates","typecheck-consumers=>Type Check · consumer gates","typecheck-debt=>Type Check · debt ledger","typecheck-source-gates=>Type Check · source gates","typecheck-workspace=>Type Check · workspace","typecheck=>TypeScript Type Check"]
branch job names: ["lint=>Lint & Repo Gates","typecheck-consumers=>Type Check · consumer gates","typecheck-debt=>Type Check · debt ledger","typecheck-source-gates=>Type Check · source gates","typecheck-workspace=>Type Check · workspace","typecheck=>TypeScript Type Check"]
IDENTICAL: true
lint job steps before/after: 121 122

And the gate agrees:

✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491)

The step's own command, run exactly as the workflow will

$ bash scripts/pm/os-regen-merge.sh --self-test
os-regen-merge --self-test
ok a clean run exits 0
...
ok step 2 uses the non-staging spelling
ok and never the staging one
...
✓ os-regen-merge self-test: all cases pass.
EXIT=0

Exit code captured before any pipe (redirect to a file, then EXIT=$?, then read the
file), so it is the script's status and not tail's.

Reverse verification: the new step has a real red path

A step that invokes a flag the script silently ignores would be green forever, which is
the failure mode this card exists to prevent, so both directions were measured.

A misspelled flag is not a quiet pass.bash scripts/pm/os-regen-merge.sh --selftest
exits 1 with ✗ unknown argument: --selftest and the usage block. A typo in the step
reddens rather than no-ops.

Ablation of the pinned property. Implementation committed first, so the restore leg's
reference already carried it. Mutated the step-2 spelling in the script from
git restore --source=origin/main -- "$p" to git checkout origin/main -- "$p",
confirmed the mutation on disk before reading anything (anchor text count 0, injected
text count 1, blob 9634d1cb becoming 701c04b9), then read the run. Restore leg pinned
to HEAD explicitly, never a bare git checkout --, and proven by observed state rather
than by an exit code:

anchor text (removed) count: 0 (expect 0)
injected text count: 1 (expect 1)
on-disk after: 701c04b9ed729be4642f204791be87c0b26ad350
MUTATED EXIT=1
FAIL step 2 uses the non-staging spelling
FAIL and never the staging one
✗ os-regen-merge self-test: 2 case(s) failed.
on-disk restored: 9634d1cb56171e8e88f90ba544323b04e3a4a5de
git diff HEAD -- scripts/pm/os-regen-merge.sh: EMPTY
restored blob == HEAD blob: OK
RESTORED EXIT=0
✓ os-regen-merge self-test: all cases pass.

The two rows that redden are exactly the two source scans, the ones with no instrument
other than this step. The script is byte-identical to HEAD again and is not in this
PR's diff. No build or dist/ is involved: the self-test runs the script from source.

Gates

Union derived mechanically, never listed by hand:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — 1 changed path
vs merge base 4d1142753, 19 families matched. Run on the final commit 91ea48c,
plus check:nul-bytes. All 20 green; each verdict line below is the gate's own, not a
bare $?:

✓ check-self-test-wired: every one of the 143 script(s) CI runs that ship a `--self-test` has that self-test run by CI.
scope: 193 file(s) under scripts/, 155 carrying `--self-test` in code (comments masked); 143 of those are run by 28 workflow(s); 139 have their self-test run through the flag, 4 through a recorded route.
✓ check-self-test-workflow-commands: no self-test CI runs prints a line the Actions runner would parse as a workflow command.
scope: 143 script(s) CI runs ship a `--self-test`; 14 mention a workflow-command token in code (comments masked) and were RUN, and their real stdout+stderr was scanned.
✓ check-step-collectors: 362 `run:` steps across 28 workflow(s); 3 step(s) run 2+ independent self-tests, all of them through a collector.
✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491)
✓ check-aggregator-roster: 3 aggregator(s) across 2 workflow(s); roster == needs: in both directions, and all 3 required-context aggregate(s) declared.
✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).
✓ check-workflow-status-functions --self-test: 34 assertions over temp fixture roots (real scan() path)
check-workflow-status-functions: OK (scanned 28 workflow file(s), 51 job(s), 25 job-level if: expression(s); 10 read needs.*.outputs.*, all naming a status function).
check-stall-guard-budget: OK (28 workflow file(s), 51 job(s), 536 step(s), 7 guard-wrapped step(s); every effective cap clears its job budget by at least one stall window).
check-node-version: OK (34 setup-node step(s) across 28 workflow(s), all on Node 22).
check-pnpm-acquisition: OK -- every job that runs pnpm acquires it first.
✓ check:pnpm-filter-targets: 140/177 `--filter` occurrence(s) across 30 file(s) resolve against 78 workspace package(s); 37 not judged (2 foreign, 15 interpolated, 20 path); 42 more in comments or step labels and 59 in this rule's own files, counted and not judged.
✓ check-agent-test-spelling: 0 violations — 391 file(s) · 4341 bare `--` token(s) · 1207 launcher-rooted run(s) · 9 separator(s) JUDGED · 5 vitest-backed script name(s) derived from 80 manifest(s)
✓ check-whole-set-label-write: 0 violations — 221 file(s) over 3 root(s) · 11 raw mention(s) · 11 in comments/prose (cleared) · 0 in EXECUTABLE content (judged) · 152 `uses:` pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
✓ check-drift-comment: 56 cases pass across 5 fixture diff(s).
OK self-test: (scripts/pm/ci-failure.mjs --self-test, exit 0)
check-nul-bytes: OK (scanned 7177 text file(s) -- 7177 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
check-type-check-coverage: OK — 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger (382 frozen raw errors), 1 exempt.
check-type-check-coverage --re-measure: OK — 31 ledger entr(ies) re-measured in 263.7s, 1570 raw tsc error(s) total, none above its recorded number.

check:type-check-debt refused on its first run with the prerequisite message it is
designed to print in a fresh worktree — --re-measure cannot run: 56 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk — which
is a NOT-MEASURED, not a red. Supplied the prerequisite exactly as lint.yml does before
that step (pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*',
70 tasks, 7m12s, under the shared verify lock) and re-ran it green; the line above is
from that run.

YAML validity is not asserted from a rule about the text: the file was parsed with the
yaml package and the new step read back out of the parse tree —
{"name":"os-regen-merge self-test","run":"bash scripts/pm/os-regen-merge.sh --self-test"}
at index 37 of jobs.lint.steps, between PM ci-failure self-test and
Claude hook guard self-tests (worktree-first · stash ban).

Release surface

None. Workflow-only diff, publishes nothing — carries the skip-changeset label instead
of a changeset.

Generated by Claude Code


Generated by Claude Code

… step
`scripts/pm/os-regen-merge.sh` ships a 23-case `--self-test` and nothing in
`.github/workflows/` ran it. The script itself has no CI path by design — it is
run by hand inside a feature branch's worktree, on a merge that exists only
there — but its self-test needs only `git` and a temp dir, and two of the 23
cases are source scans of the script's own text (step 2 must keep the
non-staging `git restore --source` spelling, never the staging `git checkout`
one) that a future refactor would invalidate in silence.
One step in the `Lint & Repo Gates` job, mirroring the existing
`Verify-lock entry-point self-test` step 1:1. Unconditional and un-`if:`-ed,
like every self-test around it. No job names change, so no required context
name changes.
Co-Authored-By: Claude <noreply@anthropic.com>
@os-litantos-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 28, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review August 28, 2026 10:00
@os-litant
os-litant enabled auto-merge August 28, 2026 10:00
@os-litant
os-litant added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit b9dd923Aug 28, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-12893-wire-regen-merge-selftest branch August 28, 2026 10:27
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/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] os-regen-merge.sh --self-test is not wired into any workflow — 23 rules-regression cases nothing runs

2 participants

@os-litant@claude