Skip to content

test(scripts): batch 10a — class-3 floors at the verdict site for four self-tests - #15327

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-13799-batch10a-class3-remainder
Sep 4, 2026
Merged

baozhoutao merged 1 commit into
mainfrom
claude/issue-13799-batch10a-class3-remainder

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Part of #13799
Part of #14842

Batch 10a: the four remaining class-3 self-tests — the shape where the body that
REGISTERS cases is not the body that prints the verdict. Copies the placement PR #15309
settled: roster, sink and ledger at module scope, and the FLOOR CHECK evaluated at the
verdict site, on a path reached only from --self-test.

Floors are measured on a run (roster pinned at 9999, read off the breach line), never
counted from source. No assertion condition, message or argument is changed.

The four files

file recipe roster floors (measured) check sits in
scripts/check-osv-exemptions.mjs recipe A (literal table + driving loop) 17 rows, size pin 17 1 each — every row registered exactly 1 main()'s --self-test branch
scripts/typecheck-configs.mjs recipe A over both tables, ONE roster 11 rows, size pin 11 1 each the --self-test dispatch block
scripts/check-comment-mask-corpus.mjs b3 (concise arrow sink → block-bodied helper) + b5 (one hoisted battery) 1 battery, size pin 1 12 selfTest()
scripts/check-workspace-manifest-cycles.mjs Tier B multi-battery, one per named section banner 7 batteries, size pin 7 13 / 10 / 2 / 1 / 2 / 4 / 2 = 34 runSelfTest()

registerCase() is the FIRST statement of each driving loop body (recipe A) or of the
file's own case sink (b3/b5/Tier B). Each handshake is kept exactly as it landed — two
selfTestReachedVerdict flags, two SELF_TEST_VERDICT sentinels. Handshake and floor stay
orthogonal.

Roster cross-checked against the source both ways, by this branch:
check-osv-exemptions 17 roster keys vs 17 name: rows, 0 duplicates either side, empty
difference both directions; typecheck-configs 11 vs 11, same; check-workspace-manifest-cycles
7 roster keys vs 7 battery() openers, and each opener's string is byte-equal to the banner
above it (⛔ no comment was promoted to a section head — the seven names are the seven
banners the file already had).

Two library scopings, proven rather than asserted

  • typecheck-configs is a library. Its selfTest() is folded into
    check-type-check-coverage.mjs and its predicates are imported by
    check-type-source-resolution.mjs. Those importers call selfTest(), which registers into
    the module ledger — but the FLOOR lives in this file's own --self-test dispatch, which an
    importer never reaches. Neither importer is edited.
  • check-comment-mask-corpus's production path calls runSelfTestCases() on every sweep.
    The floor is in selfTest(), which the sweep never calls. SELF_TEST_CASE_COUNT and its
    production read are untouched; the roster does not replace it.

Deletions — sink/verdict lines only, declared

8 deleted lines in total, in two files; the other three add only.

  • check-comment-mask-corpus — 1: the concise arrow sink ok = (label, cond) => cases.push(...),
    replaced by the block-bodied helper that calls registerCase() and then pushes the same object
    (the b3 conversion, PR test(scripts): give six more self-tests a battery roster and floor (#13799 batch 3) #15156's shape).
  • check-osv-exemptions — 7, the boolean→counter conversion 8b landed on
    check-override-consistency: let passed = true, if (!ok) passed = false, return { passed, lines },
    the @returns JSDoc, const { passed, lines } = …, if (!passed) {, and the old red verdict line.
    The red run now prints the uniform N failure(s) (cases and floor); the GREEN line is untouched.

⚠️ One declared deviation from the dispatch's wording. The class-3 spec says the floor goes
"after the existing failures branch and immediately before the success line". In
check-osv-exemptions the same dispatch also ordered the boolean→counter conversion so the floor
shares the counted sink — and those two cannot both hold, because a shared sink means ONE branch,
not one after another. The counter conversion won (it is the per-file instruction, with a named
precedent); the floor is still immediately before the success line. The other three files have a
separate floor branch after the existing failures branch, exactly as the pilot does.

The instrument row (Part of #14842)

node scripts/measure-self-test-floor.mjs --probe still reads
scripts/check-workspace-manifest-cycles.mjs as NOT MEASURED (ambiguous entry (selfTest,
runSelfTest) and no ENTRY_BY_HAND row)
on this pin — checked before editing, so the row #14842
prescribes is still needed. Added as one line (plus a three-line reason). With it, the probe reads:

{"verdict":"HELD","entry":"runSelfTest","baselineExit":0,"mutatedExit":1,"mutatedBytes":220,
 "mutatedHead":"✗ check-workspace-manifest-cycles self-test: runSelfTest() returned without reaching its verdict,"}

Ablations — 18 legs across the split, mutation AND restore proven on disk

Every leg: the mutation is proven by counting the text it was anchored on (removed count → 0,
injected count ≥ 1) and the blob hash moving; the restore is git checkout HEAD -- an
ABSOLUTE path in a finally (with SIGTERM/SIGINT handlers, so a cap kill cannot leave a mutated
tree), proven by git hash-object equalling the HEAD blob and an empty git diff HEAD — an
empty hash is read as FAILURE, not as "nothing to compare". All 18 restores passed; the tree is
clean. These are plain .mjs scripts executed from source with no dist, so no rebuild leg
applies — stated rather than skipped.

leg osv typecheck-configs comment-mask manifest-cycles
(a) battery/row renamed in the roster exit 1, both halves named exit 1, both halves exit 1, both halves exit 1, both halves
(b) registerCase() removed exit 1, 17 × DID NOT RUN, every case still ✓ exit 1, 11 × DID NOT RUN exit 1, DID NOT RUN (0/12) exit 1, 7 × DID NOT RUN
(c) floor check deleted at the verdict site exit 0, vacuous green exit 0, OK -- 11 cases hold exit 0, All 12 … passed exit 0, all cases passed
(d) early return in the registering body handshake fires first handshake fires first exit 1, DID NOT RUN (0/12) exit 1, batteries 4–7 DID NOT RUN

⭐ (d) came out in two directions, and both are reported as observed rather than as the template
predicted.
Where the handshake flag is set on the last line of the registering body
(check-osv-exemptions, typecheck-configs), an early return trips the handshake before the
floor is ever consulted — the two holes catching the same defect at different depths, which is what
orthogonal looks like in practice. For those two an extra leg (d2) injects a skip guard into
the driving loop instead — the "a guard that now skips" defect the floor's own remedy text names —
and it lands as exactly one battery DID NOT RUN with every other case green, count below floor.
Where the handshake sits on the deciding body (check-comment-mask-corpus,
check-workspace-manifest-cycles), (d) reaches the floor as designed; in manifest-cycles the set
difference names the four banners that stopped and leaves the three that ran at their floors.

Byte comparison vs the installed base

A second worktree detached at the same pin 103677652, pnpm install exit 0 (an uninstalled base
would be exit 3 = NOT MEASURED, not a reading). Both modes, stdout and stderr compared separately
with cmp:

run mode stdout stderr
check-osv-exemptions --self-test / normal identical (843 B / 76 B) identical (0 B / 0 B)
typecheck-configs --self-test / normal identical (51 B / 54 B) identical
check-comment-mask-corpus --self-test identical (837 B) identical
check-comment-mask-corpus normal (corpus sweep) ⚠ one field varies — see below identical
check-workspace-manifest-cycles --self-test / normal identical (70 B / 175 B) identical
check-type-source-resolution (importer) --self-test / normal identical (44 B / 230 B) identical
check-type-check-coverage (importer, folds typecheck-configs) --self-test / normal identical (198 B / 972 B) identical

The one varying line, proven to vary base-against-base. The corpus sweep prints its own
elapsed time. Branch 58.9s vs base 56.4s — and a second run of the BASE tree against the first
gives 63.7s vs 56.4s, so the field varies with nothing changed at all. Every other byte of that
line is equal across all three runs (5889 files, 0 disagree, 0 unparseable,
comparator self-test: 12 cases pass), and masking the seconds makes base and branch
byte-identical.

Both importers of typecheck-configs are byte-identical in both modes, in both directions —
that is the proof that the floor is scoped to this file's own dispatch and cannot fire inside a
fold-in. Neither importer is edited. check-type-check-coverage --self-test still reports its six
families (48 + 68 + 45 + 28 + 19 + 18) unchanged.

⚠️ A verdict-line caveat, recorded rather than glossed. The two comparison batches were passed
to os-verify-lock.sh as parts joined with ;, so the lock's own line says
VERDICT batch-last-exit 0 · ⚠ NOT A VERDICT ON THE WHOLE COMMAND. It is right to say so: that 0
covers only the last part. The evidence here is not that number — each of the nine runs printed and
captured its own exit code (TCCST_W=0 TCCST_B=0 TCCN_W=0 TCCN_B=0 CMN_W=0 CMN_B=0 CMN_B2=0 TSRN_W=0 TSRN_B=0), and those are what is reported. The pnpm check:pm-dispatch-gates run was a
single command and its line reads a real VERDICT command-exit 0.

Instrument before/after

node scripts/measure-self-test-floor.mjs --json, run in each tree, exit 0, 170 rows both times:

tree ROSTER NONE COUNT
base worktree at 103677652 156 13 1
this branch 160 9 1

NONE drops by exactly 4 — check-comment-mask-corpus, check-osv-exemptions,
check-workspace-manifest-cycles, typecheck-configs — and nothing entered NONE. The
symmetric difference between the set that left and this batch's worklist is empty.

extractWatchHints (imported from scripts/pm/dispatch-gates.mjs), computed in both trees:
identical on all four target files. The fifth edited file, measure-self-test-floor.mjs,
gains exactly one hint — scripts/check-workspace-manifest-cycles.mjs, the path its new row
names, which is the point of the row. pnpm check:watch-hint-literal exit 0.

Gates

Exit codes captured by redirect BEFORE any pipe throughout (cmd > out 2>&1; ec=$?), and each
result is quoted from the gate's own verdict line, never from a bare $?.

  • Derived family, node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
    (no hand-built path list — the script takes its own change set from the merge base; it reports
    5 paths, committed 5 / working tree 0 / untracked 0): 26 commands, 26 exit 0.
  • Always-runs tail, all exit 0: pnpm check:pm-dispatch-gates in the foreground under
    scripts/pm/os-verify-lock.sh — ✓ dispatch-gates self-test: 1381 cases pass., lock VERDICT
    command-exit 0 · held 367s · waited 160s; check-self-test-workflow-commands.mjs and its
    --self-test; check:declared-population-live; check-self-test-wired.mjs and its
    --self-test; check:ratchet-remedy-authority; check:nul-bytes; check:watch-hint-literal.
  • Each edited script's own wiring, normal mode AND --self-test, all exit 0:
    node scripts/check-osv-exemptions.mjs ×2 (validate-deps.yml runs exactly these two — there is
    no check:osv-exemptions package script), pnpm check:workspace-manifest-cycles,
    pnpm check:type-source-resolution, pnpm check:type-check-coverage,
    node scripts/check-comment-mask-corpus.mjs, node scripts/typecheck-configs.mjs --self-test.
  • Whole-repo pnpm lint (eslint . --no-inline-config) exit 0 — the full farm, no narrowing
    claimed and none needed.
  • scripts/measure-self-test-floor.mjs ships no --self-test (its controls run inline on every
    invocation) and no test file in the tree names it, so running the instrument IS its own suite:
    exit 0 in both trees.

Control-byte scan (grep -naP over the five edited files and over every body posted for this
batch): no hits.

Classification (measurement only, no edit)

scripts/audits/14744-before-update-per-row-value-census.mjs — its census row is in the report
comment on #13799. Headline: recipe A, 10 literal table rows, no handshake in any of the
three landed spellings
, verdict INSIDE the body (so not class-3), a derived
SELF_TEST_CASES.length nothing compares, 0 banners, and zero CI wiring — no package.json
script, no workflow mention. Measuring it also turned up an instrument defect, filed as #15324:
the probe awards HELD on a non-zero exit code alone, and this file exits 1 printing zero bytes
on an early return, so a file with no handshake at all scores HELD.


🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…self-tests

The class-3 shape: the body that REGISTERS cases is not the body that prints
the verdict, so the floor is evaluated at the verdict site — the place that
prints the green line — with roster, sink and ledger at module scope. Copies
the placement PR #15309 settled.

- check-osv-exemptions: recipe A over the 17 literal table rows; floor at the
  verdict site in main()'s --self-test branch. Boolean `passed` converted to a
  `failures` counter so the floor shares the counted sink (sink/verdict lines
  only).
- typecheck-configs: recipe A over BOTH literal tables under one roster (11
  keys, size pin 11); check in the --self-test dispatch block, so the two
  importers that fold this self-test in never reach it.
- check-comment-mask-corpus: concise arrow sink converted to a block-bodied
  helper calling registerCase(); one hoisted battery, floor 12; check at the
  verdict site in selfTest(), so the production sweep in main() — which calls
  runSelfTestCases() too — never reaches it. SELF_TEST_CASE_COUNT and its
  production read are untouched.
- check-workspace-manifest-cycles: Tier B multi-battery, one battery per named
  section banner (7), floors measured per battery on a run; check at the
  verdict site in runSelfTest().

Every floor is read off a run with the roster pinned at 9999, never counted
from source. No assertion condition, message or argument is changed.

Also adds the one ENTRY_BY_HAND row the instrument needs to probe
check-workspace-manifest-cycles at all: it defines two self-test-shaped
functions and reads NOT MEASURED without it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@github-actions github-actions Bot added the size/l label Sep 4, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 4, 2026 11:55
@baozhoutao
baozhoutao enabled auto-merge September 4, 2026 11:56
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 607534f Sep 4, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13799-batch10a-class3-remainder branch September 4, 2026 12:32
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…E on objectstack-ai#18373) (objectstack-ai#18946)

Fixes objectstack-ai#18373

Clause-②: no

`skip-changeset` — measured, not asserted; see Verification below.

Executes the maintainer ruling of 2026-09-18 on this card (batch objectstack-ai#153
item 3, **letter E**):
retire `check-type-source-resolution`. This PR is EXECUTION — it does
not re-argue A/B/C/D.

## What the ruling ordered, and where each piece landed

| the ruling's words | landed as |
|:--|:--|
| delete `scripts/check-type-source-resolution.mjs` and its self-test |
file deleted. Its "self-test" was the file's OWN `--self-test` dispatch,
not a separate file, so it went with it (`git ls-files` matched exactly
one path for the name). |
| `KNOWN_DIST_RESOLVED_TYPE_IMPORTS` with it | that registry lived
inside the deleted file; the identifier now has **0** occurrences
anywhere in the tree. |
| the `check:type-source-resolution` entry in the root `package.json` |
removed (one line). |
| the 「Type-source resolution gate」 step in `.github/workflows/lint.yml`
| step removed, together with the 23-line comment block that exists only
to explain it. |
| `AGENTS.md` and any doc that names the gate as a standing check |
**`AGENTS.md` names it zero times — re-measured here, see §1. This diff
does not touch the governed surface.** |
| `docs/audits/gate-census-2026-09.md:215` verdict | rewritten to the
ruling's exact text, see §2. |
| PR objectstack-ai#18708 | not touched. |
| `check:test-source-alias` (the vitest-axis sibling) | not touched, and
its ledger is not touched. |

## 1. `AGENTS.md`: the ruling's sentence describes a sentence that does
not exist

Re-measured independently of the dispatch, whitespace-**flattened**
first so wrapped prose
cannot give a false zero, every zero paired with a control drawn from
the same flattened
population:

```
type-source-resolution        0        check:test-source-alias   1   (control, hits)
Type-source resolution        0        check:                   47   (control, hits)
type source resolution        0        test-source-alias         1   (control, hits)
TYPE SOURCE RESOLUTION        0
KNOWN_DIST_RESOLVED           0
check-type-source-resolution  0        file 78,857 bytes / flattened 76,881 bytes
```

My reading **agrees with the dispatch's**. So the ruling's `AGENTS.md`
clause has no
referent, no line was hunted for there, and `AGENTS.md` / `CLAUDE.md` /
`.claude/**` /
`skills/**` / `docs/adr/**` are all absent from this diff.

## 2. The two censuses — deliberately different acts

**`docs/audits/gate-census-2026-09.md` — verdict REWRITTEN.** Its
verdict column is a
forward-looking *disposition* (what should happen to the gate), which is
exactly what a
later ruling can override. The row's verdict now reads
`retire · maintainer ruling 2026-09-18 on objectstack-ai#18373`, the ruling's own
spelling.
Because that is a NEW verdict spelling, the document's own verdict-count
table was kept
arithmetically true in the same edit: `keep` 133 to 132, a new row for
the new spelling at
1, `retire (all spellings)` 59 to 60, `keep (all spellings)` 150 to 149.
The union still
sums to 225 rows. Nothing else on the row moved — class, contract, blast
radius and the
measured catch window are what the census measured, and this PR did not
re-measure them.

**`docs/audits/2026-09-self-test-shape-census.md:341` — deliberately
LEFT ALONE.** The
dispatch flagged it as a second carrier the ruling did not name; it
holds a
`ROSTER | HELD` row for this gate. It gets nothing, for a reason, not by
omission:

- That document's header pins it to a tree — `origin/main` at
`d30ccb9bd`, re-verified at
`1be26b0de`. Its rows are not dispositions; each one is **the result of
a behavioural
probe run against that sha**. Retiring the gate today does not make "at
`d30ccb9bd` this
  script's self-test exited non-zero when it ran zero cases" untrue.
- **Deleting** the row would break the document's own arithmetic — 179
total, 165 HELD, 4
DEFEATED, 1 ACCIDENT, 9 NOT MEASURED — and with it the whole
reconciliation the document
exists to perform against objectstack-ai#15410's competing count of 170. A record you
can subtract
  rows from is not a record.
- **Rewriting** the verdict would assert a measurement nobody took.

Rule applied, and the same rule decides every prose carrier below: **a
sentence that makes
a present-tense claim about the gate acting is now false and is
repaired; a sentence
recording a past measurement or why a past change happened is not.**

## 3. The hard coupling: `check:ratchet-remedy-authority`, measured
before and after

That gate keeps a hand-classified control corpus keyed on gate FILENAME,
and its self-test
asserts the sweep reaches every entry. Both legs, run from this
worktree:

| leg | `--self-test` | main run |
|:--|:--|:--|
| **before** any change (at `02bdeaaf2`) | exit **0** | exit **0** — 257
scripts swept, 15 marked, 6 refused, control corpus 31 |
| **after deleting the file only** | exit **1** — `the sweep still
REACHES every known instance; it no longer reaches:
check-type-source-resolution.mjs` | exit **1** — `STALE: the control
corpus ... covers scripts/check-type-source-resolution.mjs, which is no
longer in the corpus. Drop the entry, or restore the file.` |
| **after the repair in this PR** | exit **0** | exit **0** — 256
scripts swept, 15 marked, 5 refused, control corpus 30 |

**The repair is the gate's own prescribed remedy, and no floor moved.**
What that gate pins
is `SELF_TEST_BATTERIES` — a roster of battery NAMES with a per-battery
count floor and a
pinned roster SIZE, and its own comment at the roster says deleting an
entry silences a
floor as effectively as zeroing it. That roster is a **different
registry** from the
control corpus, and it is untouched in substance:

```
declared batteries: 21      SELF_TEST_BATTERY_FLOOR: 21      sum of counts: 30
battery (12) count: 1   (unchanged)
```

The control corpus (`CONTROL`) has no pinned size — the gate prints
`Object.keys(CONTROL).length`
— and its STALE branch names dropping the entry as the fix. 257 to 256
swept, 6 to 5 refused
and 31 to 30 classified are the mechanical consequence of one file
leaving the corpus, not a
weakened floor.

Three further carriers in that same file, each judged by the rule in §2:

- `:16` "The precedents are ..." — present tense, names four files a
reader is told to open.
  The dead name is dropped; the other three stay.
- `:1221` the author-facing remedy "turn it down outright the way
`check-type-source-resolution.mjs` does" —
present tense, and after this PR it points an author at a file that does
not exist. The
exemplar is swapped to `check-test-source-alias.mjs`, the co-precedent
of the identical
PREDICATION shape that this same file already names at `:16` and in
battery (12).
  ⛔ This names that gate; it does not touch it or its ledger.
- battery (12)'s label and its assertion text ("the shape the two
registry gates use") —
present tense, now one gate. Label renamed, assertion reworded. Roster
size and the
  battery's own count are unchanged, so nothing is unpinned.
- `:662` "…which turned check-type-source-resolution's CORRECT remedy
into a reported
violation" — a record of a measurement that was taken and rejected.
**Historical: kept.**

## 4. The coupling the dispatch did not name:
`check-type-check-coverage.mjs`

Found by re-measuring rather than by the brief. That gate's **live,
author-facing** TEST_DEBT
graduation remedy told an author route (b) was "Available ONLY while
`pnpm check:type-source-resolution` still passes with the tests
re-admitted ... Run it before
you commit". After this PR that is a command that does not exist, in a
message whose whole
job is to tell an author which of two routes is open.

Repaired so it keeps the WARNING and loses the dead instruction: it now
records that the
gate that decided the route was retired under this ruling, that its
silence is ⛔ not a
clearance, that what it measured has not changed (the re-admitted tests
import workspace
packages the src program never held; it read red on 14 of the 18 entries
with an exclusion
to drop), and that (a) is the route to prefer.

**⛔ The self-test that pins that message is NOT weakened.** Its
`present` needles
(`check:type-source-resolution`, `SHRINK-ONLY`, `tsconfig.test.json`)
and the sibling
FUTURE_DEBT case's `absent` needles are left **byte-identical** — the
rewritten message
still carries all three, because it names the retired gate and its
former registry
explicitly. Only the case LABEL and its explanatory `why` changed. `pnpm
check:type-check-coverage`
exits **0** after the edit.

The other five mentions in that file (`:934`, `:986`, `:1099`, `:4462`,
and the `:537` /
`:5629` provenance notes) are records of measurements — "MEASURED as a
red `main`", "SINCE
MEASURED ... by dropping each entry's exclusion and reading
`check:type-source-resolution`",
"measured by doing it". Under the §2 rule the measurements are kept; the
two that also made
a present-tense claim about a live consumer (`:537`, `:5629`) now say
the gate was retired.

## 5. The other repo-root tooling carriers

- `scripts/typecheck-configs.mjs` — this library existed *because* two
gates needed the same
predicate. One is gone. Its self-test does **not** assert a consumer set
(checked: no
consumer array, only prose), so nothing reds; but "Two gates need this
predicate", "both
consumers resolve", "the two callers" and ":201
`check-type-source-resolution.mjs` imports
the predicates" were all present-tense and false. Repaired to name the
one live consumer and
record the retirement. ⛔ Folding the module back into its remaining
caller is explicitly
left as a separate decision — its cases are floored in its own dispatch
(PR objectstack-ai#15327) and a
  fold-in would not inherit that floor.
- `scripts/check-undeclared-dep-imports.mjs:42` — "the two gates that
look adjacent" is now one.
- `scripts/workspace-enumerator.mjs:66` — the `WORKSPACE_PARENT_GLOBS`
declaration list named
the deleted file; it now names the live one and records where the other
went.
- `scripts/pm/dispatch-gates.mjs` — five mentions, **all left alone**:
every one is a recorded
measurement in a docblock (pair counts of a matcher variant that was
measured and refused).
Historical under the §2 rule. The tool derives its families from
`package.json` and the
workflows at run time, so the retired gate simply leaves its output; it
needs no edit and
  reds nothing.
- `scripts/pm/check-clause2-carriers.mjs:8209` / `:8250` — **verified
offline before deciding,
and left alone.** The record is a frozen inline array of comment bodies
passed
`headSha: 'offline'`; nothing in it resolves a remote ref, and the two
mentions are branch
names inside a historical claim-contest fixture about this card,
unrelated to the gate.
- `scripts/typecheck-configs.mjs:202`'s stale comment about its importer
— see above; that is
  the comment the dispatch flagged as pointing the other way.

## 6. Out of scope, on purpose

- ⛔ **`packages/*/CHANGELOG.md` (five files) — untouched.**
`AGENTS.md:686` is unconditional:
a factual error in a released entry is repaired in a dedicated docs-only
PR, ⛔ never as a
rider on code changes. They are also *correct* as historical records of
what those releases did.
Same for `content/docs/releases/**` (which names it zero times anyway).
- **About 30 prose carriers in `packages/**/tsconfig*.json` comments,
test docblocks,
`packages/cli/bin/run-dev.js` and `examples/*/tsconfig.json` —
untouched, and reported to the
PM as a residual.** Boundary applied: the ruling scoped this diff itself
when it moved the lane
to `domain:devx` 「the diff is repo-root tooling, `package.json` and the
lint workflow」.
Editing those comments would pull roughly 18 packages and 3 examples
into the changeset,
change the diff's lane, and multiply the derived gate set — for comments
that mostly explain
  *why a `paths` rule exists*, a reason that outlives the gate.

## 7. The workflow step removal leaves the required context intact

Measured, not asserted. The required context is the JOB's `name:`, and
no context name is
derived from a step:

```
BASE 02bdeaa : lint job `name:` = Lint & Repo Gates   steps = 179   (step present)
HEAD           : lint job `name:` = Lint & Repo Gates   steps = 178   (step absent)
```

The job keeps 178 other steps and its name is byte-identical, so the six
required contexts
are unchanged. `pnpm check:required-contexts` exits 0. ⚠️ One wording
note for the record:
the ruling writes the job as 「Lint and Repo Gates」; the job's actual
`name:` is
`Lint & Repo Gates` (ampersand). Same job, and the ruling's conclusion
holds.

## 8. `skip-changeset`, measured

Criterion: nothing already published moves. Measured against every
workspace manifest's
`files[]`, with a positive control proving the reader works rather than
merely reporting zeros.

```
changed paths (9)                              files[] reaches
  .github/workflows/lint.yml                     NONE
  docs/audits/gate-census-2026-09.md             NONE
  package.json                    (private:true) NONE
  scripts/check-ratchet-remedy-authority.mjs     NONE
  scripts/check-type-check-coverage.mjs          NONE
  scripts/check-type-source-resolution.mjs       NONE   (deleted)
  scripts/check-undeclared-dep-imports.mjs       NONE
  scripts/typecheck-configs.mjs                  NONE
  scripts/workspace-enumerator.mjs               NONE

POSITIVE CONTROL — must be reported as reached
  packages/spec/src/data/query.zod.ts            @objectstack/spec   (glob entry)
  packages/cli/dist/index.js                     @objectstack/cli    (directory entry)
  packages/spec/CHANGELOG.md                     @objectstack/spec   (literal entry)
```

3 of 3 controls hit, across two packages and all three `files[]` entry
kinds, so the zeros
above are readings and not an empty read. The root `package.json` is
`private: true` and is
never published at all.

## 9. Verification

Gate set derived in-worktree with
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths
passed — the tool takes its own change set from the merge base), then
run. Union taken at
`13b2b68ef`, the final commit.

- **69 of 69 derived commands run. 63 exit 0.**
- **6 exit 3 = `PREREQUISITE NOT MET`, NOT MEASURED, declared to CI**:
`check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:sourcemap-no-sources-content`,
`check:type-check-debt` and `@objectstack/lint
check:doc-formula-expressions`. Every one of
them refuses because this worktree has no build; each prints its own
"this is NOT a pass"
line and exits 3 rather than 1. They are derived from the root
`package.json` edit, and they
read the `dist/` of packages this diff does not touch — this diff
changes no package source,
  so their verdict cannot depend on it. CI's build lanes measure them.
- `pnpm lint` (`eslint . --no-inline-config`, the whole repo, no
narrowing) — exit **0**.
- `pnpm check:ratchet-remedy-authority` — exit 0, before/after table in
§3.
- `pnpm check:type-check-coverage` — exit 0.
- `pnpm check:pm-dispatch-gates` — exit **0**, `dispatch-gates
self-test: 1848 cases pass`
(976.3s on this box; run on its own because it does not fit a ten-minute
foreground window).
- `pnpm check:required-contexts`, `check:step-collectors`,
`check:self-test-wired`,
`check:self-test-workflow-commands`, `check:aggregator-roster`,
`check:scripts-symbol-anchors`,
`check:declaration-mirrors`, `check:ci-filter-parity`,
`check:nul-bytes`,
  `check:workflow-step-name-quoting` — all exit 0.
- Control-byte self-scan over every changed file
  (`grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'`) — clean.
- ⚠️ `dispatch-gates` prints its own warning that `--commands` is
**not** a complete account
of CI; the artifact-roster, wide-population, workflow-valued and
path-scheduled CI families
  are outside that list by construction.
- ⚠️ `dispatch-gates` also reported a STALE TREE note: `origin/main`
moved 3 commits after this
branch point and `scripts/pm/check-widening-tells.mjs` changed there.
The derivation itself
uses three-dot merge-base semantics, so the change set above is correct;
the note affects only
  that one family's shape. No path of this diff overlaps it.

## 10. Serial constraint with two in-flight PRs

Both **objectstack-ai#18889** (draft) and **objectstack-ai#18414** (open, non-draft) also edit
`.github/workflows/lint.yml`
and the root `package.json`. Re-checked immediately before pushing:
**both are still open and
unmerged**, so neither had landed under this branch. This diff is
written to survive either
landing first — ⛔ no line number was used as a reading:

- the workflow step is located by **its own name**, and the edit
asserted the literal text of
`- name: Type-source resolution gate`, its `run:` line and the first
line of its comment block
before removing anything; a shifted file fails the assertion instead of
deleting the wrong step.
- the `package.json` entry is matched as a **unique exact string**,
never by offset.

The ruling's `.github/workflows/lint.yml:4187` and the dispatch's
`package.json:172` were both
treated as clues; both happened to still be correct at `02bdeaaf2`, but
nothing here depends on that.

Also re-measured against a freshly fetched `origin/main` (`46559f61c`,
five commits past this
branch point): **none of those five commits touches any of this diff's
nine paths**, so no merge
was needed and no line re-derivation was owed.

**objectstack-ai#18708 is closed, unmerged** (2026-09-18T06:37Z) — confirmed here, not
assumed. This PR does not
touch it. **objectstack-ai#18903** is editing `scripts/pm/check-clause2-carriers.mjs`,
the file holding the
frozen objectstack-ai#18708 fixture this PR deliberately leaves alone — adjacent, not
overlapping.

## Acceptance notes

- Noted, not filed: the gate census's inventory counts (182 check files,
225 rows) are pinned
to the census's own tree and were deliberately not re-derived — only the
verdict column and
its roll-up were touched. Carrier for a future re-derivation: whoever
executes the next
  batch of the 58 remaining `retire` rows.
- Noted, not filed: `docs/audits/gate-census-2026-09.md` now carries a
verdict spelling
(`retire · maintainer ruling ...`) that no other row uses, where the
existing convention for a
ruling-driven retirement is the verdict `retire (ruled)` plus `ruled
retire: #NNNNN X` in
column 3. The ruling's literal text was followed rather than the
convention.

---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_

---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants