Skip to content

refactor(pm): extract the clause-② declaration reader into its own module - #19660

Merged
huangyiirene merged 4 commits into
mainfrom
claude/issue-19061-step1-clause2-line-module
Sep 22, 2026
Merged

huangyiirene merged 4 commits into
mainfrom
claude/issue-19061-step1-clause2-line-module

Conversation

@huangyiirene

@huangyiirene huangyiirene commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #19061 — STEP ① of the ruling's three-step order (record 5770886272, batch #211 item 3, letter B). Steps ② (the rule text, --pair, the double-carrier discipline, the independence-pair requirement) and ③ (the needs:contract-review label) are later rounds on the same card and are not addressed here; #19061 remains open.

Clause-②: no

What this does

readClause2Line — the fleet's one reader of the Clause-②: yes | no declaration line — moves out of scripts/pm/check-clause2-carriers.mjs into a new scripts/pm/clause2-line.mjs. The two CI gates the ruling names, plus a third importer it does not, re-point one import line each.

The move is mechanical: 412 lines lifted from the checker's Limb ② section, byte-identical except one added export keyword on quoteLine (proof below). Nothing was rewritten, nothing was copied, and no second reading of the declaration exists anywhere in the tree.

before after
what check-changeset-no-major.mjs and check-adr-0087-registration.mjs import the reader from scripts/pm/check-clause2-carriers.mjs, 10,613 lines, first-party closure 9 modules scripts/pm/clause2-line.mjs, 455 lines, first-party closure 0 modules — it imports nothing at all, not even a node builtin
scripts/pm/check-clause2-carriers.mjs 10,613 lines 10,205 lines
net across the tree +47 (+473 / −426)

The zero-import property is load-bearing twice: check-changeset-no-major.mjs states that it still runs before pnpm install, and both gates stage their own first-party closure into a throwaway git repo for their I1/I2 fixtures.

Measured closure — why the module is 455 lines and not the ruling's "~40"

The ruling's estimate is the CODE size and it is accurate: the reader and its helpers are about forty lines of executable text. The other ~370 are the docblocks that already sat on them — the #16421 arm rationale, #17098's two structural tells, #13914's trailing-reasoning calibration, the four-axis note on the alternation refusal. They moved with the code rather than being dropped, because they are the authority on why each boundary sits where it does.

The dependency closure was measured, not assumed, and it is clean — every symbol below is used only by the reader, except the one marked:

CLAUSE2_VALUES · CLAUSE2_ARMS · CLAUSE2_KEY_LINE · CLAUSE2_NEAR_MISS_LINE · CLAUSE2_KEY_TEXT · CLAUSE2_KEY_COLON · CLAUSE2_LINE_START_DECORATION · hasInlineClause2Key · matchValueToken · readValueToken · readArmToken · clause2LineDescribes · quoteLine (shared — five call sites remain in the checker: :3257, :3824, :6150, :6152, :6165) · readClause2Line

It drags no marker stripper and no board reader. quoteLine is the only shared helper, so it moves with the reader and is exported; the checker imports it back rather than keeping a copy. CLAUSE2_VALUES and CLAUSE2_ARMS are imported back for the checker's own self-test pins.

Files changed, and why each one had to move

  • scripts/pm/clause2-line.mjs — new. The reader, its two closed sets, the quoting helper, and a header stating what ruling 5770886272 retired and kept, so a reader of the small file knows why it exists.
  • scripts/pm/check-clause2-carriers.mjs — the moved block leaves; one import block arrives. Its own --self-test (1140 cases) still drives the imported symbols.
  • scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs — one import line each. These are the two CI gates.
  • scripts/pm/post-stamped.mjs — one import line. A third importer the ruling does not name; measured at claim time and re-pointed so no second edge to the big file survives.
  • scripts/objectui-changeset-digest.mjs — not on the claim's declared file surface, and required by the move: its fw-gate sandbox asserts the staged first-party closure of check-adr-0087-registration.mjs names the declaration reader by path. The extraction changes that path, so the assertion is re-pointed. Its sibling assertion inside check-adr-0087-registration.mjs itself is re-pointed in the same stroke — that pair of sites is exactly the two-hand-lists hazard first-party-closure.mjs exists for, and re-pointing one and not the other would have reproduced it.

Four refusal / docblock sentences that named the reader's old home follow it, so no prescription points at a file the reader no longer lives in.

Verification

Every exit code was captured before any pipe. Final head 6aac105; the whole union below was re-run on that head after the origin/main merge.

Gate union — 37 derived, 37 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) and reconciled with --ran carrying each command's captured exit:

✓ dispatch-gates --ran: 37 derived famil(ies) accounted for — 37 run, 0 NOT-MEASURED
  (a DERIVED zero — all 37 recorded an exit code and none of them is 3).

Headline verdict lines:

✓ check-empty-changeset --self-test: 159 assertions over real temp git repos (real scan() path)
✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos
✓ check-changeset-no-major --self-test: 365 assertions
✓ check-clause2-carriers self-test: 1140 cases pass
✓ post-stamped self-test: 562 cases pass across 19 batteries — offline, no network, no token.
✓ check-governed-queue-guard self-test: 312 cases pass
✓ objectui-range --self-test: all checks passed        (pnpm check:objectui-changeset)
✓ check-adr-0087-registration: this PR adds no declared-breaking changeset

The four workflows the ruling names — which jobs actually execute the module on a PR event:

workflow job PR event? reading
lint.yml lint / Lint & Repo Gates (required context) yes (pull_request:, no branch filter) runs check:pm-clause2-carriers (:1268), check:pm-post-stamped (:1365), check:objectui-changeset (:3425), check:changeset-gate-self-tests (:3489) — all four green locally on 6aac105
pr-automation.yml changeset-check / Check Changeset yes (pull_request:) runs check-adr-0087-registration.mjs --self-test and --base MERGE_BASE (:939-940) and check-changeset-no-major.mjs --base MERGE_BASE (:1075) — both run locally with --base origin/main, exit 0
cut-rc.yml cut no — workflow_dispatch: only, deliberately no push: and no schedule: its exact step command lines were run locally with SNAPSHOT_SHA=eff0a9622340f59068651f2b82d4afcd177546a8: the ADR-0087 pair exit 0, the no-major pair exit 0 (printing its LEVEL AXIS: NOT APPLICABLE line — correct, a cut is not a PR and has no declaration to read), the empty-changeset pair exit 0
release.yml — no — push: main / schedule / workflow_dispatch it names these gates in prose only (:358, :590, :1115 are comments explaining that a major cannot reach the lane while check-changeset-no-major.mjs holds). No run: step in this workflow invokes either gate, so this workflow never loads the extracted module at all

Ablation — a red-first proof that the extracted module is the live reader for both CI gates. Run through scripts/ablation-replace.mjs, which proves the mutation landed on disk (anchor count and git hash-object blob) and proves the restore against HEAD. Expected direction: turns red. Observed: turns red, both legs.

① anchor CLAUSE2_VALUES ['yes','no'] -> ['yes']
   blob 0b23b13975bb -> 9ababd2ce610   (mutation landed on disk)
   node scripts/check-changeset-no-major.mjs --self-test
   ✗ check-changeset-no-major --self-test — 21 failure(s)
     • control: the bare line IS a declaration — got {"kind":"malformed","value":"no", …}
   restore: blob == HEAD (0b23b13975bb) and `git diff HEAD` is empty

② anchor CLAUSE2_ARMS ['widening','narrowing'] -> ['widening']
   blob 0b23b13975bb -> bca974b5d407   (mutation landed on disk)
   node scripts/check-adr-0087-registration.mjs --self-test
   ✗ check-adr-0087-registration --self-test -- 6 failure(s)
   restore: blob == HEAD (0b23b13975bb) and `git diff HEAD` is empty

Both restore legs verified by git diff HEAD being empty against the absolute path, not by an exit code. No ablation artefact remains: git status --porcelain is empty on 6aac105.

Move byte-identity. The 412 moved lines were compared line-for-line against git show HEAD:scripts/pm/check-clause2-carriers.mjs at the pre-move commit: 412 lines compared, exactly 1 differs, and it is the added export on quoteLine.

Patch round 1 (cf3d884, merged forward over origin/main as 5f926f8): the header at :20–:39 struck release.yml from the list of workflows the two gates run in — it names them in prose only (:358, :590) and never loads this module — and gained the step ① warning that the needs:contract-review label, --pair, the double-carrier discipline and the independence pair are retired BY THE RULING and still RUN in this tree until steps ② and ③ land. 27 docblock lines changed, 0 executable lines (the file stripped of docblocks is byte-identical on both heads); the review of record went FAIL 5771683235 → PASS 5771857466.

Lint — a proven narrowing, not a skipped scan. The repo-wide eslint . --no-inline-config is CI's run. Run here: the six changed files, --no-inline-config --format json, exit 0, 6 files judged, 0 errors, 0 warnings, at head 6aac105. The three readings the narrowing owes:

  1. Population from eslint's own config, not a guess: the config object at eslint.config.mjs:971 declares files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], so .mjs is inside the judged population and all six files were judged rather than ignored.
  2. Count read from --format json: the result array has length 6, one entry per changed file.
  3. Invariance for untouched files, stated by the config itself at eslint.config.mjs:327-329: this repo "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file". No verdict on a file this diff does not touch can move because of this diff.

Control bytes. pnpm check:nul-bytes exit 0, plus a direct scan of the six files for the wider control-byte class: no match.

Changeset — skip-changeset, measured

scripts/** publishes nothing. Measured rather than assumed, with a positive control:

  • the repo-root package.json is private: true and declares no files[];
  • every package.json under packages/, apps/ and examples/ was read: none has a files[] entry that escapes its own directory, so no published tarball can reach repo-root scripts/;
  • probe: git grep -l 'readClause2Line|clause2-line|check-clause2-carriers' -- packages/ returns 0 files;
  • positive control on the same pathspec and the same instrument: git grep -l 'defineStack' -- packages/ returns 405 files. The instrument finds what is there.

Acceptance notes

  • readValueToken (moved with the block, unchanged) is defined and never called — its every remaining reference is docblock prose in four files. Dead code, so ⛔ not filed as a card; noted here. It moved rather than staying behind because it is matchValueToken's sibling, and leaving it in the checker would have cost the checker an import for an unused function. Carrier: step ③ of Retire the in-seat clause-② contract review — the rule, its gate label, and the 9k-line checker's gate role #19061, which the ruling says may delete check-clause2-carriers.mjs entirely once nothing but the extracted module is imported from it.
  • The check-adr-0087-registration.mjs and objectui-changeset-digest.mjs docblocks narrate the [finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 incident as "that gate gained one import, whose own closure is nine modules deep". After this PR that closure is zero modules deep, so the two sentences were adjusted to describe the incident without asserting a depth that is no longer true. The derivation rule they justify is untouched: two sites, one graph, neither holding an opinion about it.
  • ⛔ Not touched, by the ruling's own sequencing: .claude/**, AGENTS.md, packages/**, .github/workflows/**, the needs:contract-review label, --pair, CONTRACT_REVIEW_TIER in dispatch-gates.mjs, and the ## Contract review record recognisers that check-governed-queue-guard.mjs lazy-loads from check-clause2-carriers.mjs (RECOGNISER_SOURCES.tier, pinned at the checker's own :4407 equivalent). The ruling KEEPS the record, so those recognisers stay in the checker this round — verified: the queue guard reads locateReviewOfRecord, deliveredCardNumber, servedTierStands, isModelIdentifierToken, REVIEW_OF_RECORD_THREADS and REVIEW_OF_RECORD_LOCATION, none of which is in the moved block.

Governance

scripts/** is not a governed surface — node scripts/pm/dispatch-gates.mjs --tier printed, verbatim, "Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled." Opened as draft. The card's later steps do touch Tier H surfaces; this one does not.

维护者速读(草稿)

改了什么 —— 把那条 Clause-②: yes | no 声明行的读取器,从一万行的 check-clause2-carriers.mjs 里原样搬进一个新的小文件 scripts/pm/clause2-line.mjs。代码一个字没改写(412 行逐行比对,只多了一个 export 关键字),只是换了个住处。两个每次 PR 都跑的 CI 门禁,加上第三个调用方,各改一行 import。

为什么改 —— 裁决 5770886272 的第 ① 步。这两个门禁要的只是「这张 PR 有没有放宽公开面」这个 semver 判断,和「谁复核了这份 diff」毫无关系;但它们为了拿到四十行的读行逻辑,过去要把一万行、九个模块深的巡检机器整个拖进来。搬完之后它们的依赖闭包是零个模块——新文件连一个 node 内置模块都不 import。这件事有实际后果:check-changeset-no-major.mjs 声称自己能在 pnpm install 之前就跑起来,两个门禁还要把自己的依赖闭包复制进临时 git 仓做自检,闭包小了这两件事都更稳。

风险与代价(含回滚) —— 风险很低:纯搬家,无行为变化,全树净增 47 行。为了证明「搬完之后门禁读的确实是新文件」,做了两次消融:把新文件里的常量改坏,两个 CI 门禁分别转红(21 个失败 / 6 个失败),再还原并用 blob 哈希与 git diff HEAD 证明树干净。37 个派生门禁族全绿、零未测量。回滚就是 revert 这一个 PR,没有数据迁移、没有发布面变化(scripts/** 不进任何 npm 包,已用正控实测)。唯一超出认领申报文件面的是 objectui-changeset-digest.mjs:它断言那个门禁的依赖闭包里有声明读取器的路径,不跟着改就必然变红。

席位意见 ——

你要做的 —— 这一步不碰 .claude/** 也不碰 AGENTS.md,所以不是 Tier H,按常规队列落地即可;但它是三步裁决的第 ① 步,第 ② 步(删规则文本、--pair、双载体、独立性对)和第 ③ 步(删标签)会碰 Tier H 面,那两轮才需要您的手。如果您认同「读取器搬家」这件事本身,这个 PR 就可以走。


Generated by Claude Code

…dule

The two CI gates that read `Clause-②: yes | no` imported `readClause2Line`
from `scripts/pm/check-clause2-carriers.mjs` — ten thousand lines of sweep
machinery, a nine-module first-party closure, reached for forty lines of line
reading. The reader, its two closed sets and the quoting helper move verbatim
into `scripts/pm/clause2-line.mjs`, which imports nothing at all; the sweep
imports them back rather than keeping a second copy.

`check-changeset-no-major.mjs`, `check-adr-0087-registration.mjs` and
`pm/post-stamped.mjs` re-point one import line each. The two derived
first-party-closure assertions and the refusal sentences that name the
reader's home follow it.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>
@huangyiirene huangyiirene added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 22, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6aac10592ddf3d7aa6c9128998cbc7cf5a996b07

① Derived judgments

  • (a) It is a MOVE — RIGHT. The checker's removed hunk (old :899 onward, 413 lines) against clause2-line.mjs:44-455 (412 lines) differs on exactly two lines: export added to quoteLine (module :366) and one trailing blank line dropped. Nothing reordered, nothing added, nothing else dropped; all 14 top-level names moved as one block in original order. The seat's measurement is confirmed.
  • (b) Exactly one reader — RIGHT. readClause2Line is defined only at clause2-line.mjs:408, CLAUSE2_VALUES at :53, CLAUSE2_ARMS at :91; no second definition anywhere under scripts. The only other declaration-shaped regex in the tree is check-clause2-carriers.mjs:927, which reads the Clause-②-correction key — a different line, not a second reading. The checker imports the four symbols back at :757. No surviving CODE reference to a moved private helper: every remaining hit is docblock prose (hasInlineClause2Key :916; readValueToken :938, :3125, :8185, :8188; clause2LineDescribes :8195; CLAUSE2_KEY_LINE :421, :916, :918, :970, :3045, :8422; plus check-changeset-no-major.mjs:1400).
  • (c) Importers re-pointed consistently — RIGHT, one stale prose site. check-adr-0087-registration.mjs:374, check-changeset-no-major.mjs:394, post-stamped.mjs:820, checker :757; four static importers, zero dynamic. Measured closures at this head: the ADR-0087 gate falls from 14 first-party modules to 5, no-major from 13 to 4, and scripts/pm/clause2-line.mjs is in both — so the ADR-0087 staged-closure assertion (:7040) and objectui's gateDeps assertion (:2349, actual closure 5, requirement "at least 2") both agree with the real import graph. Every remaining path reference to the checker is still true (check-governed-queue-guard.mjs:627, :638, :728, :739 — the checker still imports check-half-states.mjs; check-governed-merges.mjs:331/:1028/:1585; post-stamped.mjs:1760; dispatch-gates.mjs:6799) — except first-party-closure.mjs:21, the third site of the same [finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 sentence the dev rewrote at ADR :7027 and objectui :2324. It is past-tense narration and its "nine modules deep" was already stale on the base (measured 11 at base, 12 at head), so this PR did not make it false — NOTE, not a defect of this round; fold it in when step ② edits the same narration.
  • (d) The module imports nothing — RIGHT and load-bearing. No import, no require in 455 lines. It matters because check-changeset-no-major.mjs:391-392 still states it runs before pnpm install, and both gates stage their own first-party closure into a throwaway repo (check-adr-0087-registration.mjs:7040, objectui-changeset-digest.mjs:2345-2349) — that fixture now stages 5 files instead of 14.
  • (e) H3 workflow proof — the dev's reading is RIGHT, the CI half is thinner than the table suggests. PR-event executors: lint.yml Lint & Repo Gates (:1268 check:pm-clause2-carriers, :1365 check:pm-post-stamped, :3425 check:objectui-changeset, :3489 check:changeset-gate-self-tests, which is both gates' self-test per package.json:149) and pr-automation.yml Check Changeset (:243, gates at :932-940 and :1075). Not reachable from a PR: cut-rc.yml:77-79 is workflow_dispatch only; release.yml:163-189 is push/schedule/dispatch and no run: step invokes either gate (prose at :358, :590; its gate block :614-622 lists seven other checks; the only indirection is setup-pnpm and docker-publish.yml). On this head Check Changeset is SKIPPED by its own guard at :289-291 because the PR carries skip-changeset, so the --base MERGE_BASE legs of both gates got no CI execution here; the only CI job that loads the extracted module is Lint & Repo Gates, which is still in_progress at review time. Honest reading, but the seat must see it success before enqueue.
  • (f) The 43-line header — one FAIL item and one note. FAIL: clause2-line.mjs:20-21 asserts the two gates "run in lint.yml, pr-automation.yml, cut-rc.yml and release.yml". False at this head for release.yml, and contradicted by the PR body's own workflow table in the same round. NOTE: :10-14 says the ruling "retired" the label, --pair, the double-carrier discipline and the independence pair. True of the ruling, so not a falsehood about the code — but steps ② and ③ have not landed, and those mechanisms still RUN in this tree (--pair still judges C4/C6/C7; check-governed-merges.mjs:331/:1028/:1585 still prescribe --pair N at 0; the label still exists), so a reader of the tree today is misled. One sentence fixes it: state that this file is step ① and that the label, --pair and the double-carrier discipline still run until steps ② and ③ land.
  • (g) Size — moving the docblocks is RIGHT, no trim owed. 455 lines, roughly 45 of executable text. The rest is the [finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 arm rationale, the [finding] check-clause2-carriers reads a key-INITIAL describing line as a declaration — its own self-test asserts the general property "only DESCRIBES ⇒ MISSING", and the fixture pinning it covers only the other half #17098 structural tells, the The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 trailing-reasoning calibration and the four-axis alternation note, each sitting on the definition it justifies; trimming them would delete the only authority for where each boundary sits and would change no closure. The ruling's "small module" concern is the dependency closure, and that is what was delivered (14 to 5, 13 to 4, module closure 0). The "~40 lines" estimate was of the code and is accurate.
  • (h) Batteries are non-vacuous — RIGHT, judged from the code paths. check-changeset-no-major.mjs:4259-4300 drives the imported readClause2Line on four real shapes plus a null negative control; ablation ① (CLAUSE2_VALUES narrowed to one value) breaks the bare-line control assertion at :4297, which is exactly the first failure the report quotes — red is earned, not asserted. check-adr-0087-registration.mjs:639-640 reads the arm through the same imported reader, so ablation ② (CLAUSE2_ARMS narrowed) kills the clause-②-narrowing signal — red is earned. The checker's self-test pins the imported symbols at :6708, :6723, :6726, :8508 (1140 cases read from the report; ⛔ not re-run here). readValueToken (:224) is defined and never called — matchValueToken (:212) is the one invoked at :426 — dead code moved with its sibling, correctly noted and correctly not filed. One measurement slip in the PR body, not in the code: quoteLine has FIVE call sites left in the checker (:3257, :3824, :6150, :6152, :6165), not "four".

② Semver level

None, and skip-changeset is the right carrier. scripts/** is inside no package's files[]; the probe over packages/ for the reader returns 0 with a 405-file positive control on the same instrument, so the instrument finds what is there. The diff touches no package.json and no lockfile. One consequence worth recording: that same label is what skipped Check Changeset on this head, so the semver call in ② and the missing CI leg in ①(e) are one fact, not two.

③ Boundary flags

  1. scripts/objectui-changeset-digest.mjs beyond the claim's surface — ACCEPT. Required, not elective: at this head the ADR-0087 gate's closure no longer contains the checker, so :2349's old membership test would be false and check:objectui-changeset (lint.yml:3425) would go red. Re-pointing one twin site and not the other is the [finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 defect itself.
  2. Merge of origin/main into the branch — ACCEPT. git diff --name-only 97aec39 6aac105 touches none of the PR's six files; it carries .changeset, content/docs, and packages/{spec,rest,services,core} only.
  3. §9 full refresh not run — ACCEPT. No package.json and no pnpm-lock.yaml in the diff, no gate in the union reads a built dist/. Declared as a narrowing rather than left silent.
  4. pnpm lint narrowed to the six files — ACCEPT. Both structural readings verified at this head: eslint.config.mjs:971 declares the population that contains .mjs, and :327-329 states this repo enables no type-aware linting for ANY file, so no untouched file's verdict can move.
  5. --pair 19660 exit 4 on C6 — not a flag. Expected resting state until this record exists; it clears on the record, not on the diff.
  6. The dispatch's pnpm check:pm-governed-queue-guard — confirmed absent from package.json. The seat's error, not the dev's; running the script's own --self-test is the correct substitute and no verification was lost.
  7. CI in progress at report time — honest and within the standing clause. At review time it is still the binding constraint: Lint & Repo Gates is the only job that loads the module on this head and it has not concluded, so this head is not enqueueable on the check axis regardless of this verdict. All other checks are success or skipped.

What must change for PASS: one sentence in scripts/pm/clause2-line.mjs:20-21 — strike release.yml from the list of workflows the two gates run in, and say instead that release.yml names them in prose only (:358, :590) and never loads this module. Fold the ①(f) note into the same edit: one sentence saying this file is step ① and that the label, --pair and the double-carrier discipline still run in this tree until steps ② and ③ land. Nothing else in the diff needs to move.

Implemented-by: claude/issue-19061-step1-clause2-line-module
Reviewed-by: session_01Wnstp2kTth7sGXfr8fXypc

VERDICT: FAIL


Generated by Claude Code

… step ①

Two header facts were wrong for a reader of this tree. `release.yml` was listed
among the workflows the two gates run in; it names them in prose only and runs
neither, so it never loads this module. And the paragraph describing what the
ruling retired read as a description of the tree: the label, `--pair`, the
double-carrier discipline and the independence pair are retired by the ruling
and still run here until steps ② and ③ land.

Comment-only. No executable line moves.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5f926f8ae2863bec676111e22ff59510cc15d275

① Derived judgments

Rendered in-seat by the skills seat 1 session, whose served tier reads as the fable tier word at get_session → external_metadata.last_served_model (the authoritative reading, platform-readings.md:384) — the maintainer's word of this date, recorded on seat post #7623 (5771798588): 「复核档应该就是 fable 啊」. Patch round 1 on the FAIL record 5771683235 (head 6aac105); every judgment of that record re-walked on this head, not inherited.

  • (a) The move is a MOVE — right, unchanged by the patch. git diff 6aac105 5f926f8 touches scripts/pm/clause2-line.mjs only in its header (:20–:39, +20 / −9) and scripts/check-tenant-audit-census.mjs only as origin/main's side of the merge (git diff cf3d884 5f926f8 --name-only = that one file; 0 of the PR's six paths). Against the merge base 8f30c18 the PR is 6 files, +484 / −426. The module body (:44 onward) is byte-identical to the block the checker lost: at the previous head every module line after the header existed verbatim in the pre-move checker except the added export on quoteLine (:366 then, :377 now), and the body did not move in this round.
  • (b) Exactly one reader — right. git grep at this head: function readClause2Line at clause2-line.mjs:419 only; CLAUSE2_VALUES and CLAUSE2_ARMS defined there only; the checker imports the four symbols back at check-clause2-carriers.mjs:757 (CLAUSE2_ARMS, CLAUSE2_VALUES, quoteLine, readClause2Line); no code reference in the checker to a moved private helper — every remaining hit for hasInlineClause2Key / matchValueToken / readValueToken / readArmToken / clause2LineDescribes / CLAUSE2_KEY_* is docblock prose.
  • (c) Importers and both hand-maintained closure lists — right. check-adr-0087-registration.mjs:374, check-changeset-no-major.mjs:394, post-stamped.mjs:820 import from ./pm/clause2-line.mjs / ./clause2-line.mjs; the staged-closure assertion (check-adr-0087-registration.mjs around :7040) and objectui's gateDeps assertion (objectui-changeset-digest.mjs around :2349) both name scripts/pm/clause2-line.mjs. first-party-closure.mjs:21 still carries the older 「nine modules deep」 narration — stale on the base already, a step-② rider, not this round's defect.
  • (d) The module imports nothing — right. grep -c '^import' = 0 at this head. Load-bearing: check-changeset-no-major.mjs runs before pnpm install, and both gates stage their first-party closure into a throwaway repo for the I1/I2 fixtures.
  • (e) The workflow reading — right, and the header now says it. lint.yml Lint & Repo Gates (:1268, :1365, :3425, :3489) and pr-automation.yml Check Changeset (:939–:940, :1075) execute the gates on a PR event; cut-rc.yml is workflow_dispatch only; release.yml names the gates in prose only (:358, :590) and runs neither. On this head Check Changeset is skipped by its own skip-changeset guard, so Lint & Repo Gates is the one CI job that loads the module — its success on this head is the landing condition the seat reads before enqueue.
  • (f) The header — the FAIL item is fixed, and nothing false remains. :20–:24 now reads that the gates run in lint.yml and pr-automation.yml on every PR, in cut-rc.yml on a dispatched cut, and that release.yml 「names them in PROSE only (:358, :590) … runs neither, and therefore never loads this module」 — true at this head (git grep -n 'check-changeset-no-major\|check-adr-0087' -- .github/workflows/release.yml hits comments only). :32–:39 adds the step ① warning: the label, --pair, the double-carrier discipline and the independence pair are retired BY THE RULING and still RUN in this tree until steps ② and ③ land, with 「⛔ Read the paragraph above as what was DECIDED, never as what this tree already does」 — exactly the sentence the previous record asked for. Every other header sentence was judged true at the previous head and did not change.
  • (g) Size — right, no trim owed. The module is 466 lines, ~45 of executable text; the rest is the docblocks that sat on the moved definitions ([finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 arm rationale, [finding] check-clause2-carriers reads a key-INITIAL describing line as a declaration — its own self-test asserts the general property "only DESCRIBES ⇒ MISSING", and the fixture pinning it covers only the other half #17098 tells, The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 calibration, the four-axis alternation note). The ruling's 「~40 lines」 is the code, and the dependency closure it wanted small is what landed (ADR-0087 gate 14 → 5 first-party modules, no-major 13 → 4, the module itself 0).
  • (h) Batteries — non-vacuous, re-run by this seat on a clean worktree at this head. pnpm check:pm-clause2-carriers 1140 cases · check:pm-post-stamped 562 · check:changeset-gate-self-tests (159 + 441 + 365, the [finding] Check Changeset fails with an entirely empty check-run output, so two different refusals are indistinguishable from the API #18263 battery driving the imported reader on four shapes plus the null control) · check:objectui-changeset · check:nul-bytes · both gates --base origin/main · eslint over the module — all exit 0. The dev's two ablations (CLAUSE2_VALUES narrowed ⇒ no-major self-test red at :4297; CLAUSE2_ARMS narrowed ⇒ ADR-0087 self-test red via :639–:640) read as earned from those code paths; readValueToken is defined and never called — dead code moved with its sibling, noted, correctly not filed.

② Semver level

None — no release. scripts/** is inside no package's files[] (the dev's probe over packages/ for the reader returns 0 against a 405-file positive control), no package.json and no lockfile in the diff; skip-changeset is the correct carrier and is what skips Check Changeset on this head — one fact, recorded once.

③ Boundary flags

  • scripts/objectui-changeset-digest.mjs beyond the claim's declared surface — accepted. Its gateDeps assertion tests the derived closure by path; left alone it would red check:objectui-changeset in Lint & Repo Gates. Re-pointing one twin list and not the other is the [finding] An accept-set narrowing owes a **BREAKING** banner in core but not in platform-objects — and the ADR-0087 classifier reads the banner #16421 defect itself.
  • Merges of origin/main (5ce3705, then 8f30c18) — clean. Each carries only main's side; 0 of the PR's paths move in either merge commit.
  • §9 full refresh not run; pnpm lint narrowed — accepted as declared. No package or lockfile in the diff and no gate reads a built dist/; the lint population (eslint.config.mjs:971) and the no-type-aware invariance (:327–:329) hold, so no untouched file's verdict can move.
  • --pair 19660 reads exit 4 on C6 until this record exists — the expected resting state; it clears on this record.
  • CI on this head: Lint & Repo Gates was in_progress at the seat's last read, every other check success or skipped; the seat lands only on its success and on check-expected-skips --pr 19660 exit 0.
  • Rendering basis: this record is rendered by the seat session itself at the served tier the maintainer named today; the constants on origin/main (CONTRACT_REVIEW_TIER at the opus id and RETIRED_TIER_WORDS carrying the fable word since PR pm(tooling): move the contract-review tier off the retired ceiling, and give the fuse the case it never had #19573) still state the previous reading and are the subject of the reversal card put to the maintainer — a boundary flag, not a defect of this diff.

Implemented-by: claude/issue-19061-step1-clause2-line-module
Reviewed-by: session_01Wnstp2kTth7sGXfr8fXypc

VERDICT: PASS


Generated by Claude Code

@huangyiirene
huangyiirene marked this pull request as ready for review September 22, 2026 05:56
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit f2681c5 Sep 22, 2026
40 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-19061-step1-clause2-line-module branch September 22, 2026 06:37
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… double carrier, independence requirement (step ②) (objectstack-ai#19694)

Part of objectstack-ai#19061
Clause-②: no

Step ② of the ruled three-step order on the clause-② contract review
(ruling 5770886272, letter B; scope narrowed by the maintainer's word of
2026-09-22, 5772289798: 「降档保险丝 不留」). Step ① landed as PR objectstack-ai#19660. Step ③
(the `needs:contract-review` label itself and every script that names
it) is a later round and is NOT in this PR — objectstack-ai#19061 remains open.

## 维护者速读(草稿)

**改了什么**:按裁决 B 的第 ② 步,把技能文本里守「条款②契约复核」的三层机器删掉两层半:`needs:contract-review`
标签的挂与清纪律、`--pair`
入队前检、双载体、以及「独立性对」作为硬性要求;`references/contract-review.md`
的〈载体纪律〉与〈降档保险丝〉两节整节删除(您的原话「降档保险丝 不留」),该文件 60 行缩到 24
行;SKILL.md、core-rules.md、platform-readings.md 各删或修剪对应句子(净减 44
行);`packages/spec`
错误码台账源文件顶部注释里那句话去掉标签名(「登记新码是条款②改动」的表述保留),生成的参考页同步重生成,并附一条
`@objectstack/spec` 的 patch changeset(这段注释随 `.d.ts`
与源码一起发布)。`platform-readings.md`
里「强制档不因临缺授权降档」这条原则按复核要求以独立一行恢复(不再提保险丝)。`scripts/pm/dispatch-gates.mjs`
自检里原来钉住〈降档保险丝〉两句话的两条断言,换成一条「这两句话不得回流」的守卫。留下的只有一条 `## Contract review`
记录作为 Tier S(`.claude/**`)的落地记录,队列守卫继续读它。

**为什么改**:您 9-18 说「不需要 AI 不能自审契约改动 这种」,9-22 批准了 B
方案(三层收成一层):标签、`--pair`、记录三层守的是同一件事,多出来的两层是纯成本;席内达档复核就是 Tier S 的落地方式。

**风险与代价(含回滚)**:规则文本删除加一处自检断言改写,不改任何门禁的判定逻辑;自检在本分支最终 head 上实测 1905/1905
通过。回滚 = revert 本 PR(纯文本加一条自检守卫,无迁移)。仍有六处 `.claude/` 散文与 AGENTS.md
一处仍点名旧机器,列在下文,归步骤 ③。

**席位意见**:技能席 1(`session_01Wnstp2kTth7sGXfr8fXypc`)已按 GitHub 与拉取的 head
`c7ea483b8` 复核两轮。第 0 轮记录 FAIL 5773967201 只欠两件:`platform-readings.md`
被删的原则行须以不带保险丝名的独立规则补回(≤120 字节),以及 `@objectstack/spec` 的 patch
changeset(docblock 会进发布的 `.d.ts`)。补丁轮 1 恰好只做了这两件——PR 路径上两轮之间的 diff =
+6(一行 119 字节的补回行、五行 changeset),`origin/main` 的合并未触及 PR
路径,`dispatch-gates.mjs` 与第 0 轮逐字节相同。本席在独立 worktree 复跑 ratchet(SKILL.md
815/819 · contract-review.md 24/60 · platform-readings.md 468/469 ·
core-rules.md
151/151)、skill-id-lint、frame-sync、governed-prose、doc-authoring、nul-bytes、三个
changeset 门禁、token ratchet 均 exit 0;第 0 轮头上 1905 例 dispatch-gates 自测
exit 0(脚本两轮未变),本头由本席另起后台复跑。`skip-changeset` 已由本席摘掉,`Check Changeset`
随即跑绿,CI 35 项全绿。复核记录 PASS 5774400593(席内 fable),`--pair` 0,ACCEPT
5774446589 在卡上。登记簿读 Tier S,但裁决 B 写的是 Tier H——以您的裁决为准,PR 留 draft
等您的手;若您要本席按 Tier S 入队,一句话即可。步骤 ③(删标签、队列卫士的标签腿、六处散文残留与指针)另开一轮。

**你要做的**:合并这个 draft PR(受管面,按您定的方式落地)。

## What this PR does

- `.claude/skills/pm-dispatch/references/contract-review.md` (60 → 24
lines): the 〈载体纪律(挂与清)〉 and 〈降档保险丝(机读)〉 sections are deleted whole.
〈复核归属与资格(按面)〉 keeps who renders the record, its shape (`Served-tier:` =
the constant name, the head sha, `Implemented-by:`/`Reviewed-by:` as
record lines, PASS/FAIL) and the landing pre-check, which is now two
items (① the at-tier record on file, ② every check green); the
pure-regeneration exception line moves under ①. The title and the
pointer line are reworded to what remains. No new sentence was added:
the current-head binding of the record is stated by AGENTS.md Prime
Directive objectstack-ai#14 and enforced by the queue guard, so the moved exception
line stands on that.
- `.claude/skills/pm-dispatch/SKILL.md` (819 → 815): in 〈入队与落地〉 the
bullets that hang the label, run `--pair N` on PR open, park the PR
behind the label and point at the retired sections go; the Tier S
landing sentence keeps 「PASS ⇒ ready、auto-merge;FAIL ⇒ 补丁轮;⛔ 免复核不放行」.
Elsewhere only in-line trims: the label example and the gate-label
clause in the label-write rules, the self-review-ban half of the PM
tooling exception (the authorization-quote condition stays), the
machine-read note on the record lines, the triage 「不挂」 clause, and the
dangling 「细则见」 pointer on the subagent-verdict rule.
- `references/core-rules.md` (151 → 151): the two mirrored clauses
trimmed (自审自合; 闸门标签同此).
- `references/platform-readings.md` (469 → 468): the `--pair` proxy
reading and the fuse-named rule go; the three readings whose subject was
the fuse keep their platform fact (get_session semantics, the
minute-wall example). The principle the fuse rule carried comes back as
a standalone line without the fuse name (patch round 1, the review's
①(b)): 「强制档不因临缺授权降档:临缺非退役,退役唯维护者裁;不达档起隔离达档子代理或等档。」 (119 bytes, at :393
in the served-tier readings block). The `needs:contract-review`
mergeable-state reading at :26 stays this round — it is a reading about
a label that still exists until step ③.
- `packages/spec/src/api/error-code-ledger.zod.ts` docblock :102:
「Registering a code widens this face and is therefore a Clause-② change,
door or no door」 — the label name is gone, the Clause-② statement stays.
`content/docs/references/api/error-code-ledger.mdx` regenerated by
`gen:docs` (the diff is exactly that sentence).
`.changeset/19061-error-code-ledger-docblock-label.md` declares a
`patch` for `@objectstack/spec` (patch round 1, the review's ①(e)/②):
the docblock ships in the `.d.ts` declaration chunks and the `.zod.ts`
source is in the package's `files[]`, so the diff publishes bytes from a
released package — `AGENTS.md`'s changeset clause is the stricter one
over the comment fast lane. `Clause-②: no` stands (no accept / reject
behaviour, no public-surface change).
- `scripts/pm/dispatch-gates.mjs` (the coordinator's mid-run increment;
the file is a self-test, not a gate's logic): its self-test read
`references/contract-review.md` and pinned two sentences of the deleted
fuse section in two `t(…)` cases — 「…and its fuse keeps the quota
exemption pointed at DISPATCH, never at the review」 and 「…and carries
the case it never had: a RETIRED tier is not an exhausted one, and it is
a maintainer ruling, ⛔ never a seat's reading」. They are replaced by ONE
retirement guard in the file's own pattern — 「the rulebook no longer
carries the retired 降档保险丝 lines (maintainer 2026-09-22 「降档保险丝 不留」,
objectstack-ai#19061 comment 5772289798)」 — which asserts the new truth and reds if
the text creeps back; the `fuseRules` read and the
`CONTRACT_REVIEW_TIER` case stay as they were. A plain removal of the
two cases was made and measured first (1904/1904 on the merged head) but
committing it was refused by the harness's action policy as a test
removal; the guard form is what landed.

## Inventory — every grep hit on the base (`97f4f8c828`), its
disposition and where it lands

| base hit | what | disposition | after |
|---|---|---|---|
| SKILL.md:25 | 自审自合 on the PM tooling exception | TRIMMED (independence
half goes; authorization-quote clause stays) | 25 |
| SKILL.md:155 | gate-label hang/clear example (`needs:contract-review`)
| DELETED (+ :154 gate-label clause trimmed) | — |
| SKILL.md:239 | record lines `Implemented-by:`/`Reviewed-by:`, 自审机读可见 |
TRIMMED (machine-read note goes; record lines stay) | 238 |
| SKILL.md:342 | ⛔ 不挂 `needs:contract-review` (triage) | TRIMMED (label
clause goes; 裁定写方向 stays) | 341 |
| SKILL.md:536 | 条款②自审资格按车道 (at-tier in-seat / isolated subagent) | KEPT
(who renders the record) | 535 |
| SKILL.md:635 | skills-lane own PR at-tier 自审 | KEPT (who renders) |
634 |
| SKILL.md:656 | label hung by the seat on PR/card | DELETED | — |
| SKILL.md:657 | `--pair N` on PR open, C5 | DELETED | — |
| SKILL.md:659 | pointer naming 载体纪律 / 降档保险丝 | TRIMMED (pointer reworded
to what remains) | 655 |
| SKILL.md:773 | director duty title names the label | DEFERRED to step
③ (twin lanes/director.md:27 is off this surface) | 769 |
| contract-review.md:3 | pointer: 载体纪律、归属资格与降档保险丝 | REWORDED to what
remains | 3 |
| contract-review.md:5–:22 | 〈载体纪律(挂与清)〉 | DELETED whole; :21
(pure-regen exception) MOVED | 22 |
| contract-review.md:30 | record shape lists 独立性对 | KEPT (shape) | 12 |
| contract-review.md:31 | `Served-tier:` = constant name | KEPT (shape)
| 13 |
| contract-review.md:35 | 独立性件 + 保险丝 ⇒ isolated subagent | DELETED
(independence requirement + fuse) | — |
| contract-review.md:38 | 独立性对(机读) values | TRIMMED (「(机读)」 goes;
record-line values stay) | 19 |
| contract-review.md:39 | SELF-REVIEW verdict | DELETED | — |
| contract-review.md:40 | 清标即落地 / 剥双载体 | DELETED | — |
| contract-review.md:43 | ② 双载体已清 via `--pair N` | DELETED (pre-check
renumbered 三条 → 两条, ③ → ②) | — |
| contract-review.md:44 | 确定性行 / C5 | DELETED | — |
| contract-review.md:48–:60 | 〈降档保险丝(机读)〉 | DELETED whole (maintainer
「不留」) | — |
| core-rules.md:23 | 自审自合 mirror | TRIMMED (mirror of SKILL.md:25) | 23
|
| core-rules.md:40 | 闸门标签同此 (mirror of SKILL.md:155) | TRIMMED | 40 |
| platform-readings.md:26 | label is a merge gate (mergeable reading) |
KEPT this round — a reading about the label object; goes at step ③ with
the label | 26 |
| platform-readings.md:106 | 双载体清标 as a rate-limit example | TRIMMED
(example goes; reading stays) | 106 |
| platform-readings.md:120 | `--pair` proxy reading | DELETED (its
general fact stays at :117–:119) | — |
| platform-readings.md:385 | ⛔ 不作保险丝输入 | TRIMMED (fuse clause goes;
reading stays) | 384 |
| platform-readings.md:386 | ⇒ 保险丝无输入 | TRIMMED (fuse clause goes;
reading stays) | 385 |
| platform-readings.md:394 | 强制档不得因不可用而降档 (fuse rule) | REPLACED — the
fuse-named sentence goes; the principle returns as a standalone line
without the fuse name (patch round 1) | 393 |
| AGENTS.md:278 | PD#14 `--pair N` / `Served-tier:` | OUTSIDE (not
edited; step ③, the maintainer's file) | 278 |
| error-code-ledger.zod.ts:102 | docblock names the label | REWORDED
(Clause-② statement stays) | 102 |

Two non-keyword neighbours were trimmed with their hits because they are
the same discipline: SKILL.md:154's 「闸门标签被剥不是红灯是放行」 and SKILL.md:76's
dangling pointer 「细则见 references/contract-review.md」 (the details it
pointed at were the fuse's subagent-adoption lines).

## Line budget (ratchet ceilings untouched; every file at or under)

| file | before | after | ceiling |
|---|---|---|---|
| `.claude/skills/pm-dispatch/SKILL.md` | 819 | 815 | 819 |
| `references/contract-review.md` | 60 | 24 | 60 |
| `references/core-rules.md` | 151 | 151 | 151 |
| `references/platform-readings.md` | 469 | 468 | 469 |
| `packages/spec/src/api/error-code-ledger.zod.ts` | 1679 | 1678 | — |
| `scripts/pm/dispatch-gates.mjs` | +4 / −4 (comment lines and the one
guard) | | — |
| `.changeset/19061-error-code-ledger-docblock-label.md` | — | new, 6
lines (`@objectstack/spec`: patch) | — |

Diff against `origin/main` (`7e1b048`, merged in at patch round 1; head
`c7ea483`): 8 files, +32 / −70.

## File-surface increments beyond the claim (declared)

1. `content/docs/references/api/error-code-ledger.mdx` — the generated
reference page carried the sentence; regenerated by `pnpm --filter
@objectstack/spec gen:docs`, `check:docs` and `check:generated` (15/15)
green.
2. `scripts/pm/dispatch-gates.mjs` — the two fuse-pin cases of its
self-test, replaced by one retirement guard (described above). PR objectstack-ai#19684
(objectstack-ai#19680) landed on `main` and edited the same file in a different
region; it is merged in.

## Verification (final head `c7ea483`, patch round 1)

- Derivation: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` (no paths; the changed set read
from the merge-base) → 132 families (the changeset gates joined:
`check-empty-changeset` `--base` and `--self-test`,
`check:objectui-changeset`, `check:pm-changeset-deadline-census`,
`release-rehearsal-clone --self-test`). Reconciliation with `--ran`: 132
derived, 129 run, 3 NOT-MEASURED, 0 UNRUN. Every exit captured before
any pipe.
- The 3 NOT-MEASURED, each a prerequisite refusal (exit 3), none touched
by this diff: `pnpm --filter @objectstack/spec run check:skill-examples`
(reads `packages/client-react/dist`, not built), `pnpm
check:dual-build-cjs-loads` (needs every package's dist), `pnpm
check:lean-entry-closure` (needs `packages/objectql/dist`). A whole-repo
build is CI's run, not this docblock-only spec change's.
- `pnpm check:pm-dispatch-gates` at `c7ea483`, run detached with the
exit recorded: exit 0, 「✓ dispatch-gates self-test: 1905 cases pass」,
709.7 s; the retirement guard and the 「spells NO model id」 walk over the
28 rulebook files (which read the restored line) are among them. Without
the script change the same gate read 「✗ dispatch-gates self-test: 2 of
1904 case(s) failed」, exit 1 — the expected direction for prose a
self-test had pinned.
- Changeset gates at this head, all exit 0: `check-changeset-no-major
--base origin/main` 「✓ This diff introduces no `major` bump.」;
`check-adr-0087-registration --base origin/main` 「this PR adds no
declared-breaking changeset (1 non-breaking changeset(s) seen)」;
`check-empty-changeset --base origin/main` 「No empty-frontmatter
changeset introduced by this diff (1 declaring changeset(s) added)」 and
「No changeset from the merge base modified or deleted」;
`check-empty-changeset --self-test` (159 assertions);
`check:changeset-gate-self-tests`; `check:pm-changeset-deadline-census`;
`check:objectui-changeset`.
- Named gates, all exit 0 at this head: `check:pm-skill-ratchet`
(SKILL.md 815 / 819, contract-review.md 24 / 60, platform-readings.md
468 / 469, core-rules.md 151 / 151; every line within the 120-byte cap),
`check:pm-skill-id-lint` (30 files clean), `check:skill-frame-sync`,
`check:pm-governed-prose`, `check:doc-authoring`, `check:nul-bytes`
(plus a direct grep of the seven changed files for the wider
control-byte class: no match), `check-skills-token-ratchet` (34 files
within ceilings), `check-skill-frame-freshness`,
`check:pm-clause2-carriers` (1140), `check-governed-queue-guard
--self-test` (312), `check:pm-half-states` (5160),
`check:pm-expected-skips`, `check-published-list-mirrors`,
`check-self-test-wired`, `check-scripts-symbol-anchors`,
`check-declaration-mirrors`.
- `packages/spec`: `build` exit 0 (four times — base and after each of
the three `origin/main` merges); `gen:docs`; `check:docs`;
`check:generated` 「All 15 generated artifacts are up to date」 (at
`c7ea483` too); `typecheck` exit 0 and `vitest run
src/api/error-code-ledger.test.ts` 20/20 at `c7ea483` (declared
narrowing: the diff in this package is a comment plus a changeset; the
whole suite is CI's).
- eslint narrowing: `pnpm exec eslint --no-inline-config --format json`
over the two lintable changed files (`error-code-ledger.zod.ts`,
`dispatch-gates.mjs`): exit 0, 2 judged, 0 errors, 0 warnings.
Population: `eslint.config.mjs:971`
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`); invariance:
`eslint.config.mjs:327–:329` — type-aware linting is never enabled, so
no untouched file's verdict can move because of this diff.
- `node scripts/pm/check-governed-merges.mjs --pr 19694`: GOVERNED —
Tier S (4 of 6 paths hit the register, all under `.claude/**`; no Tier H
path), 88 changed lines at that reading, under the human-merge
threshold.

## Changeset — measured, not assumed

The dispatch's assumption 「the docblock sentence changes no emitted dist
byte」 is FALSIFIED on the dist half: the file-level docblock of
`error-code-ledger.zod.ts` IS emitted into the declaration bundle. Base
build vs post-edit build of `packages/spec/dist` (216 files each): 12
differ — the two `.d.ts`/`.d.mts` chunks that carry the docblock
(`automation-api.zod-*`, renamed by content hash), the four entry
`.d.ts` files that import them, four sourcemaps (line shift) and the two
build-input stamps. Runtime `.js`/`.mjs`/`.cjs`: 0 differ.
`needs:contract-review` in dist after: 0 hits (base: 2). `files[]` of
`@objectstack/spec` also ships `src/**/*.zod.ts`, so the source comment
ships too. So the diff publishes bytes from a released package and takes
a `patch` changeset for `@objectstack/spec` (added in patch round 1 on
the review's word; `AGENTS.md`'s clause is the stricter one over the
comment fast lane). The `skip-changeset` label applied at round 0 is the
wrong carrier and the seat removes it at the landing act; this dev did
not touch it.

## Acceptance notes — riders off this surface (not filed; prose drift is
outside the fileable classes)

- Six `.claude/` prose sites still name the retired machinery and are
off this step's claimed surface: `.claude/agents/os-dev.md:303` (the
label + `--pair PR-NUMBER` reading),
`references/dispatch-runbook.md:161` (独立性件),
`references/lanes/director.md:27` (duty title names the label) and `:35`
(pointer to 载体纪律), `references/lanes/ui.md:38` (独立性件与降档保险丝),
`references/state-machine.md:10` (the label's card-side hang). Carrier:
step ③ of objectstack-ai#19061 (the label), or a sub-issue of it.
- `references/lanes/director.md:33` points at 「`../contract-review.md`
落地前检③」; after the renumbering here that item is ②. Same carrier.
- `scripts/pm/dispatch-gates.mjs` docblock line 「Rule text:
`references/contract-review.md` 「降档保险丝」」 (near the
`CONTRACT_REVIEW_TIER` constant) names a section that no longer exists;
outside the two cases this PR was allowed to touch — step ③'s territory.
- `AGENTS.md:278` (Prime Directive objectstack-ai#14) still names
`check-clause2-carriers.mjs --pair N` and the label — Tier H, the
maintainer's own edit at step ③.
- The `--template` pointers kept in `contract-review.md` :13 and :19
point at `check-clause2-carriers.mjs --template`; step ③ decides that
file's fate.
- The derivation did not list `check:pm-dispatch-gates` for a diff
touching only `references/contract-review.md`, although that gate reads
the file at runtime; it appeared once `dispatch-gates.mjs` itself was in
the diff.

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

---
_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