Repository navigation
Commit 09dd537
feat(scripts): scoped mode for check-platform-checklist (--changed-from REF judges only anchors into the change set) (#23677)
Fixes #23653
Clause-②: no
Ruling C item 1 (ruling record 6190922830, the maintainer's answer 「C
(推荐)」): this adds the scoped mode to the EXISTING
`scripts/check-platform-checklist.mjs`. One file changes. There is no
new gate script, no `dispatch-gates` edit, no CI wiring, and no
checklist data touched. The daily watchdog and the README's standing
decision (manual cadence, not a per-PR gate) are unchanged.
## The command (for the dev-contract line)
#23665 remains open; it cites the command below. Copy it verbatim:
```
node scripts/check-platform-checklist.mjs --changed-from origin/main
```
Run it on the branch before push, and quote its first output line in the
report's gates line. Exit codes:
- 0: nothing judged was lost, or nothing was judged (the line says
which).
- 1: an anchor was lost.
- 2: refused; nothing was judged.
## What it judges
- **The filter.** It runs the same registered sweep the full gate runs
(`scripts/symbol-anchors.mjs#sweepCorpus`, unchanged) and filters the
verdict. An anchor is judged only when its TARGET file is in the change
set. The run reds only on judged anchors that no longer resolve at the
working tree. Nothing else the full gate checks is judged in this mode:
ledger structure, coverage, traps, floors, residual upkeep, and anchors
into files outside the change set.
- **"Lost"** means a judged anchor that the shared resolver does not
resolve at the working tree. That is either `unresolved-symbol` (the
symbol is gone from the file) or `unresolved-path` (the file is gone
from the index).
- **Deleted or renamed targets: judged, and named.** A deleted file, or
the old side of a rename, is the strongest loss there is, and the full
gate already reds on it (`ANCHOR FILE NOT FOUND`). The mode narrows the
full gate and never widens it. Leaving deletions out would make the
scoped run greener than the full gate on exactly the refactor this card
is about. A rename is caught because the change set is computed with
rename detection off (`--no-renames`), so a move contributes its old
path, which is the path citations name. Measured below: the default
rename-detecting `git diff --name-only` lists only the new path.
- **Not judged, matching the full gate:**
- an anchor on the named residual (`SHARED_RESOLVER_RESIDUAL`);
- a bare citation with no `#symbol` (`checkBarePaths` is off for this
corpus);
- a cross-repo citation.
- **The change set** is the files that differ between `git merge-base
REF HEAD` and the working tree (committed, staged and unstaged, in one
read), plus untracked files. It is diffed from the merge base, never
from REF, so files that landed on REF after the branch was cut are not
counted as this branch's. These are the semantics of
`scripts/pm/dispatch-gates.mjs#changedPathsFromGit`, restated rather
than imported: that module is frozen, and its import chain is not this
gate's to load.
- **Absence is loud.** A change set with no anchored file prints `0
anchored files in the change set — nothing judged ...` with exit 0,
never the OK line. The following are refused with exit 2, nothing
judged, and no fallback to the full corpus or to an empty set:
- `--changed-from` without a REF;
- a near spelling (`--changed-from=REF`, `--changed ...`);
- `--changed-from` combined with `--anchor-census`;
- a REF that does not resolve;
- no merge base (a shallow checkout is named in the message);
- a git listing that fails;
- a file deleted from disk but not from the index (see Acceptance
notes).
- **Cost.** The scoped run takes the same wall time as the full gate
(about 17 s on this box): one whole-corpus sweep plus the inline
batteries. The filter sits on the verdict, not on the resolution.
## Ruling item 2, measured rather than asserted
- `scripts/pm/dispatch-gates.mjs` and
`scripts/pm/dispatch-gates.data.mjs` are untouched. The new cases live
inside the existing `selfTestSymbolAnchors` battery, whose body
`dispatch-gates` masks. There is no new compound self-test name, so
`COMPOUND_ANCHOR_LEDGER` needs no row.
- I ran `dispatch-gates`' own `extractWatchHints` and
`compoundAnchorDecls` over this file at `57994c311` (BASE) and at
`1ecec5a71` (head). The output is byte-identical: 13 watch hints and 7
compound names. No literal in the new live code became a derivation
input.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack packages/metadata-protocol/src/protocol.ts`
at `1ecec5a71` gives 48 commands, 0 of them `check:platform-checklist`.
That is the same reading the card took.
## Tests
- `node scripts/check-platform-checklist.mjs --self-test`: 233
assertions at BASE, 256 at head, exit 0. The `selfTestSymbolAnchors`
floor goes from 42 to 65:
- S1 to S9 drive the verdict's filter on fixture findings. The live
ledger resolves cleanly, so only a fixture can tell a working filter
from a deleted one.
- C1 to C7 drive the change set through a fake `git` that answers by its
arguments.
- Q1 to Q5 drive the request on argv.
- L4 checks that the anchor population the mode counts equals the
sweep's, document by document.
- L5 checks that the real `git` runner works in both directions.
- The ruling's three legs:
- S1: a changed file that lost an anchor reds.
- S2: a changed file that kept its anchors stays green.
- S3: an anchor broken outside the change set is not reported.
- `pnpm check:platform-checklist`: exit 0 at `1ecec5a71`. The full
gate's OK line is unchanged apart from `65 symbol-anchor` assertions.
### Ablations
All three ran at `1ecec5a71` through `scripts/ablation-replace.mjs` in
wrap mode. Each restore was proven: blob equal to HEAD, and `git diff
HEAD` empty.
1. The filter, `if (f.repo || !changed.has(f.path)) continue;` replaced
by `if (f.repo) continue;`. Result: 8 legs red, S1 to S7 and S9. S3 is
the ruling's "broken elsewhere is not reported" leg.
2. `--no-renames` dropped from the diff. Result: C1 and C2 red.
3. The diff taken from REF instead of the merge base. Result: C1 and C3
red.
The same three readings were taken at `0430b20fe` with identical
results. The first final-head run of ablation 1 was void: my shell loop
split its anchor on the pipe characters and planted a SyntaxError. It
was redone with the anchor passed directly, and that redo is the reading
above.
### Live proof on the real corpus
The run used scratch commits in the worktree. They were never pushed,
and an EXIT trap restored the tree to `1ecec5a71` (`git diff HEAD` 0
bytes, porcelain 0). Output is abbreviated.
```
# F: refusals, 0 s each, before the batteries
--changed-from -> exit 2 REFUSED: needs the REF to diff against
--changed-from=origin/main -> exit 2 REFUSED: not a spelling this gate reads
--changed-from no-such-ref-for-23653 -> exit 2 REFUSED: REF does not resolve to a commit
# B: the branch as pushed (only the gate script changed)
--changed-from origin/main -> exit 0, 17 s
0 anchored files in the change set — nothing judged. 1 changed file(s) since merge base 57994c3, ...
# C: protocol.ts touched (comment appended), anchors kept
--changed-from origin/main -> exit 0
OK — 1 anchored file(s) in the change set, 24 anchor(s) judged, 0 lost. 2 changed file(s) since merge base 57994c3. ...
# A: organizationScopedWriteRefusal renamed in protocol.ts (10 hits to 0 on disk), committed
--changed-from origin/main -> exit 1
2 LOST anchor(s). These checklist citations name what the change set's files no longer declare ...
✗ docs/qa/platform-checklist/areas/access-security.json line 1556: `packages/metadata-protocol/src/protocol.ts#organizationScopedWriteRefusal` — ... is no longer declared in packages/metadata-protocol/src/protocol.ts.
✗ docs/qa/platform-checklist/areas/access-security.json line 1610: (same anchor)
full gate (control) -> exit 1, the same 2 ABSENT SYMBOL problems
# D: same broken tree; change set = only packages/runtime/src/seed-loader.ts (anchors kept)
--changed-from HEAD -> exit 0
OK — 1 anchored file(s) in the change set, 1 anchor(s) judged, 0 lost. 1 changed file(s) since merge base bb3d7aaaa. ...
(the protocol.ts break is not reported; the full gate on this tree is red, leg A)
# E: git mv packages/runtime/src/seed-loader.ts to seed-loader-moved.ts, committed
default git diff --name-only origin/main...HEAD: seed-loader-moved.ts, check-platform-checklist.mjs
--no-renames git diff --name-only origin/main...HEAD: seed-loader-moved.ts, seed-loader.ts, check-platform-checklist.mjs
--changed-from origin/main -> exit 1
✗ docs/qa/platform-checklist/areas/platform-core.json line 2631: `packages/runtime/src/seed-loader.ts#SeedLoaderService` — packages/runtime/src/seed-loader.ts is not a tracked file any more (deleted, or the old side of a rename).
# G: rm packages/runtime/src/seed-loader.ts, NOT staged
--changed-from origin/main -> exit 2, 0 s
REFUSED — 1 file(s) are deleted from the working tree but not from the index (packages/runtime/src/seed-loader.ts). ...
full gate (control) -> exit 1, ENOENT stack trace from the shared sweep (Acceptance note 2)
```
## Gates
The gates were derived with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (no paths) at `1ecec5a71`.
The 30 commands are the same 30 the dispatch listed. All 30 were run at
`1ecec5a71` and every one exited 0:
- `check-ci-filter-parity`
- `check-closing-keyword-parity`, plus `--self-test`
- `check-comment-mask-corpus` (9159 files, 0 disagree)
- `check-declaration-mirrors`, plus `--self-test`
- `check-scripts-symbol-anchors`, plus `--self-test` (the new citations
into `dispatch-gates.mjs#changedPathsFromGit` and
`git-env.mjs#GIT_ENV_ISOLATION_RULE` resolve)
- `check-self-test-wired`, plus `--self-test`
- `check-self-test-workflow-commands`, plus `--self-test`
- `check-whole-set-label-write`, plus `--self-test`
- `scripts/pm/bare-root-worklist.mjs --self-test`
- `pnpm check:agent-test-spelling`, `check:bash32-floor`,
`check:cli-command-ids`, `check:cross-package-test-inputs`,
`check:driver-memory-census`, `check:entry-guard`,
`check:gitlink-declared`, `check:nul-bytes`, `check:parse-guard`,
`check:platform-checklist`, `check:pnpm-filter-targets`,
`check:ratchet-remedy-authority`, `check:refd-timer-probe`,
`check:watch-hint-literal`
- `pnpm check:pm-dispatch-gates`: fast tier, 1884 cases, run detached
`--ran` reconciliation over a record carrying every exit code: "30
derived, 30 run, 0 NOT-MEASURED, 0 UNRUN", with the zero derived from
the recorded codes.
The edited script's own suites:
- `node scripts/check-platform-checklist-watchdog.mjs --self-test` and
the plain run: exit 0.
- `node scripts/measure-self-test-floor.mjs --self-test`: exit 1,
pre-existing (Acceptance note 3).
## Acceptance notes
1. **Inherited red on a touched file.** An anchor already broken on REF,
in a file the branch also touches, is reported too: the mode judges the
working tree, not the delta against REF. This is stated in the script
header. With `main` held green by the daily watchdog it is rare, and the
dev touching that file is the cheapest fixer.
2. **Unstaged deletion crashes the full gate.** The shared sweep takes
its file list from the index (`git ls-files`) and reads each file from
disk. An anchored file deleted with a plain `rm` therefore kills the
full gate, and its `--self-test`, with an ENOENT stack trace and exit 1.
I measured this on this branch; the code path is the shared sweep, which
this diff does not touch. The scoped mode refuses first, with exit 2 and
the remedy, because there exit 1 means "lost". The full gate's behaviour
is unchanged here. Carrier: none; noted, not filed (it is a dev-tool
crash, not a public entrance).
3. **`measure-self-test-floor` is red on main.** `node
scripts/measure-self-test-floor.mjs --self-test` exits 1 at BASE
`57994c311`, measured in a throwaway worktree, and on this branch, with
the identical message: "THE HANDSHAKE CENSUS IS NOT COMPLETE", naming
`scripts/pm/check-prior-rulings.mjs` and
`scripts/pm/dispatch-gates.mjs`. It names nothing this diff touches, and
nothing wires it (no package alias, no workflow). Carrier: none; noted,
not filed.
4. **A stale timing comment.** The header says the inline battery is "~1
ms of a ~270 ms run". At `57994c311` the full gate measured 16.6 s wall
on this box: about 4.2 s per corpus sweep, four sweeps per run, three of
them inside the batteries. The comment is left as is because it is out
of scope.
5. **README not edited.** The script header's Usage line names the mode,
and #23665's contract line will quote the command.
## Changeset
`skip-changeset`. The root package `@objectstack/spec-monorepo` is
private. Of the 72 non-private workspace manifests, none owns
`scripts/check-platform-checklist.mjs`. Positive control: the same
method assigns `packages/spec/src/index.ts` to `@objectstack/spec`.
Not a governed surface: the diff touches only
`scripts/check-platform-checklist.mjs`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4feb2d0 commit 09dd537
1 file changed
Lines changed: 444 additions & 2 deletions
0 commit comments