docs(cli): #18769's pending changeset declares the os validate --strict narrowing an at-tier review exhibited - #18867
Conversation
…ict` narrowing an at-tier review exhibited `.changeset/18677-validate-per-package-authoring-pass.md` declined its `**BREAKING**` banner on the strength of a negative — "not declared breaking, because the narrowing could not be exhibited" — and the negative is false. The fixture that exhibits it did not exist when that entry was written: on the two-package `CONFIG_FLIP` config shipped in `packages/cli/test/lint-per-package-authoring-parity.test.ts`, `os validate --json --strict` reads exit 0 / 0 warnings with `validate.ts` restored to its pre-#18769 blob `bafa54b07f` and exit 1 / 1 warning at head. The entry is still pending and unreleased (`@objectstack/cli` 17.4.0 on npm equals the manifest version, and the CHANGELOG carries none of this text), so this is a forward edit to an unpublished file, not a rewrite of landed history. It adds the banner, the measured table, the mechanism that explains why the union fold cannot see the finding, and the ADR-0087 disposition the banner owes; it also narrows "nothing that builds today stops validating", which was the sentence an upgrader would have been misled by. ⛔ Out of scope, deliberately: the landed clause-② declaration on #18677 and what a `yes (narrowing)` that shipped declared `no` owes beyond this file. That is the maintainer's, and this commit does not answer it. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
|
Ruling: batch #200 item 2 · letter A · maintainer 「同意」 2026-09-20T16:03Z Director seat, summon #25, Ruling — A: the release declares the
|
…the source states it (objectstack-ai#19241) Fixes objectstack-ai#18893 `content/docs/deployment/validating-metadata.mdx` was the last place in the tree still asserting, as a claim, the sentence the CLI source explicitly forbids restating — of the per-package walk: *"what it reports is exactly the set the union could not see"*. PR objectstack-ai#18878 (card objectstack-ai#18779) removed that sentence from eight code carriers because it is false, and the two notes that replaced it are this page's acceptance baseline: - `packages/cli/src/utils/artifact-packages.ts` — the `findingKey` docblock records that the claim the pass is entitled to make *"is narrower than"* that sentence, and that the key is *"position-insensitive, ⛔ not collision-proof"*. - `packages/cli/src/commands/compile.ts` — *"⛔ Do not re-inflate that to"* it, beside the settled statement of what does survive. ## What changed One sentence, one file. The page now states the bound in the source's own settled words — the set of per-package findings no union finding already carried under the same rule, `where`, message and non-top-level position — says why the leading collection index is neutralised (a package body re-bases its collections from 0, so one finding would otherwise get two keys), and carries the narrowness note the source wrote down so the next reader does not re-inflate it. The surrounding paragraph's teaching is untouched. ⛔ No source file was changed. The source is the authority here; the page is what was wrong. ## Measurement Whitespace-normalised, because the target sentence **wraps across two lines** and a line-oriented `grep -F` returns `0` on it — a zero triage and two seats each paid for once on this very card: | needle | base `e233db9` | after | |:--|--:|--:| | `exactly the set the union could not see` | 1 | **0** | | lit control `one gate, four doors` | 2 | 2 | | dark control `zzzNotARealToken` | 0 | 0 | The lit control still fires after the edit, so that `0` is a reading and ⛔ not an instrument artefact. The naive line-oriented `grep -F` reads `0` both before and after — recorded here so nobody re-derives a clearance from it. ## Gates 39 families derived from the **actual diff** (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, change set taken by the script itself from the merge base), every one run, every one exit `0`, reconciled `39 derived / 39 run / 0 UNRUN`. Plus the full `pnpm lint` union, which `dispatch-gates` does not name. Four of the 39 first exited `3`/`1` carrying PREREQUISITE-NOT-MET text — *"Nothing was measured: this gate exited before running a single check"* — because workspace packages were unbuilt. They were re-run to a real verdict after building `@objectstack/spec`, `@objectstack/formula`, `@objectstack/lint` and `@objectstack/client-react`. ⛔ Those refusals are recorded as not-measured-then-measured, never as a failed measurement. ## Changeset `skip-changeset`, **derived rather than assumed**: the one changed path lives under no package directory except the private monorepo root (`@objectstack/spec-monorepo`, `private: true`), so no published package's tarball can contain it whatever decides its contents; and no published package names `content/docs` in its `files[]`. ⛔ No label was written — this dispatch forbids label writes, so the label is the seat's to apply. ## Acceptance notes - **Only one carrier on this page.** The card asked for a grep rather than an assumption, since PR objectstack-ai#18872 introduced the whole section in one landing: probed whitespace-normalised for `de-duplicat`, `union could not`, `could not see`, `only what`, `echo` and `duplicate` across the page — the corrected sentence was the single restatement. No second one exists. - **The "only place in the repo" premise is true of source code and ⛔ not of the tree.** Re-derived at the branch base, whitespace-normalised: 13 non-doc occurrences across 13 files. Eleven are the settled shapes — one bound declaration, one prohibition, and nine quoted corrections in `compile.ts`, `lint.ts`, `validate.ts` and five CLI pin tests. **Two are still assertions**: `.changeset/18677-validate-per-package-authoring-pass.md` states *"By `compile.ts`' own description the survivors of that second pass are …"* and `.changeset/18778-lint-per-package-authoring-pass.md` states *"every finding that pass produces — … — in the build command's own words"*. Both attribute the sentence to source text that no longer says it, both are unreleased, and a changeset body ships verbatim into `CHANGELOG.md` as the text an upgrading agent greps. ⛔ Not fixed here — out of this card's declared one-file surface, and `.changeset/18677-…` is the claimed surface of in-flight card objectstack-ai#18823 (PR objectstack-ai#18867) for a different defect. Reported for filing. Clause-②: no --- _Generated by [Claude Code](https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf)_ Co-authored-by: Claude <noreply@anthropic.com>
…objectstack-ai#18677 / objectstack-ai#18778 pending changesets (objectstack-ai#19531) Fixes objectstack-ai#19245 Clause-②: no Two **pending, unreleased** changesets each asserted the sentence `packages/cli`'s own source now explicitly forbids restating, and each attributed it to that source. A changeset body ships verbatim into `packages/cli/CHANGELOG.md`, so this correction costs a diff today and a published falsehood after the release that consumes them. **Prose only** — two `.changeset/*.md` files, no code path changes, no export, no key, no accept set moved. **The two shas this diff is actually between:** merge base `eec56c37dfc89086658c1659bb2da869dfa08d4f` (the tip of `origin/main` at branch time *and* at every measurement below) → head `2babd1876786eb135509df70ab4fc2ee0113a7f3`. Verified with `git merge-base origin/main HEAD`, not assumed from the branch point. ## 1. The settled bound, read at the head this branch points at ⛔ No fourth phrasing was invented. Both replacement sentences are the tree's own, read from the two docblocks the card names: `packages/cli/src/commands/compile.ts:465-470` > `findingKey` now neutralises the top-level collection index, so what survives is the set of per-package findings no union finding already carried under the same rule, `where`, message and non-top-level position. ⛔ Do not re-inflate that to "exactly the set the union could not see" — `utils/artifact-packages.ts` states the bound and why it is narrower than that sentence. `packages/cli/src/utils/artifact-packages.ts:237-244` > What reaches the lists below is therefore the set of per-package findings whose `rule`, `where`, `message` and NON-top-level position no union finding already carried.⚠️ That is the whole claim, and it is deliberately narrower than "exactly the set the union could not see" — ⛔ do not restate it as that sentence. Two entries rendering the same `where` still collapse (see `findingKey`) … Both halves are carried into both entries: the **bound**, and the **reason it is narrower** (two entries rendering the same `where` still collapse). An amended note that stated the bound and dropped the caveat would be the same defect one notch smaller, so neither entry drops it.⚠️ Deliberately, **neither replacement reproduces the retired sentence verbatim**, not even as the "narrower than" contrast the source uses. The source can quote it because the source is the thing that prohibits it; a release note quoting it would put the sentence back into `CHANGELOG.md`, which is the entire cost this card exists to avoid — and it would leave the population sweep in §3 reading 3 again. ## 2. What each entry now says | file | before | after | |:--|:--|:--| | `.changeset/18677-validate-per-package-authoring-pass.md:7` | "By `compile.ts`' own description the survivors of that second pass are «exactly the set the union could not see», so that whole set was findings `os build` reported and `os validate` **structurally could not**." | "By `compile.ts`' own description the survivors of that second pass are the per-package findings no union finding already carried under the same rule, `where`, message and non-top-level position — deliberately narrower than everything the union run missed, because two entries rendering the same `where` still collapse. That whole set was findings `os build` reported and `os validate` **structurally could not**." | | `.changeset/18778-lint-per-package-authoring-pass.md:10-13` | "every finding that pass produces — «exactly the set the union could not see», in the build command's own words — was reported by the command that ships and invisible on the fastest of the three doors." | "every finding that pass produces — in the build command's own words, the per-package findings no union finding already carried under the same rule, `where`, message and non-top-level position — was reported by the command that ships and invisible on the fastest of the three doors. That bound is deliberately narrower than everything the union run missed: two entries rendering the same `where` still collapse." | Both attributions are now **accurate**: `compile.ts` does say the replacement, in those words. The downstream conclusion each entry draws is untouched and still holds — a survivor is by construction something the union run did not report, so it remains a finding `os build` reported and the other door structurally could not. Only the **size** claimed for that set moves, which is exactly the correction objectstack-ai#18779 landed in the code and did not reach these two notes. ⭐ **One correction, not two.** The two entries needed the same substantive change; only the sentence surgery differed, because one file is unwrapped prose and the other is hard-wrapped at 80 columns. Each file's own wrapping convention is preserved. ⭐ **The `**BREAKING**` section PR objectstack-ai#18867 just added to `18677-…md` is not touched.** The assertion sits at `:7`; that banner and its table occupy `:22-29`. `git diff` shows one changed line in that file. ## 3. The sweep over the whole `.changeset/` population, with lit controls⚠️ **The first instrument was wrong and is reported rather than quietly replaced.** Its normaliser collapsed `\s+` only. That is enough for markdown, but inside a block comment the sentence's line wrap carries a `*` continuation marker, so a bare `\s+` bridge does **not** join `"exactly the` to `set the union could not see"`. On the tree scan in §4 it silently undercounted **13 → 11**. The corrected normaliser strips each line's comment-continuation prefix (`*`, `//`, `#`) *before* collapsing whitespace. Every number below is from the corrected instrument; both are kept in the report. Whole population, whitespace-normalised, case-insensitive, **every hit opened**: | run | population | lit control | needle | |:--|:--|:--|:--| | `.changeset/` @ `origin/main` `eec56c37df` (before) | **554** files | superset "the union could not see" → **3** occurrences, INSTRUMENT LIT | **3** | | `.changeset/` @ head `2babd18767` (after) | **554** files | superset → **1**, INSTRUMENT LIT | **1** | The three before, each opened, ⛔ not counted: 1. `18677-…md:7` — assertion, attributed to `compile.ts` → **corrected here** 2. `18778-…md:11` — assertion, attributed to the build command → **corrected here** 3. `18779-…md:52` — *"Also corrected: the sentence «…», **which was false** for as long as the key was positional"* → **quoted correction, untouched** The one after is row 3. ✅ **The only surviving hit in the whole population is `18779`'s quoted correction**, which is what the claim comment asked to be shown rather than trusted.⚠️ A note on the population figure: the card recorded **482** changesets at `847e5773a`. At `eec56c37df` it is **554**. The tree moved; the count is re-measured here, ⛔ not inherited. The needle count is unchanged at 3, which is the number that matters.⚠️ A second lit control was run on the before-sweep and is recorded because a zero is not a reading until a control hits: the distinctive string `position-insensitive, not collision-proof` returned exactly **1** file (`18779-…md`), confirming the loop was reading real bytes at real paths — the failure mode the card records (a loop that double-prefixed `.changeset/` and returned a wholly convincing `0`). ## 4. `.changeset/18779-…md` is untouched, and this PR makes its completeness claim true `18779-…md:55-56` claims the sentence *"is now stated at the bound the pass can actually hold, **in every file that carried it**"*. That claim was **false on `origin/main`**, falsified by the two rows above. It is true at this head. Measured over the **whole tree**, not just `.changeset/` — 9128 tracked text files at head, corrected normaliser, lit control 15 occurrences of the superset in 10 files: **13 occurrences in 10 files, every one opened and classified. Zero are assertions.** | site | class | |:--|:--| | `.changeset/18779-per-package-dedup-positional-key.md` | quoted correction | | `packages/cli/src/commands/compile.ts` ×2 | 1 quoted correction ("used to end … and that was FALSE"), 1 **prohibition** ("⛔ Do not re-inflate that to") | | `packages/cli/src/utils/artifact-packages.ts` ×3 | 1 "narrower than" contrast (`:101`), 1 historical account that names **objectstack-ai#18677 and objectstack-ai#18778 by number** as the two that quoted it, 1 **prohibition** ("⛔ do not restate it as that sentence") | | `packages/cli/src/commands/lint.ts` | quoted correction | | `packages/cli/src/commands/validate.ts` | quoted correction | | `packages/cli/test/{lint,validate}-per-package-authoring-{parity,seam}.test.ts` ×4 | quoted corrections | | `packages/cli/test/per-package-dedup-positional-echo.test.ts` | quoted correction | ⇒ Nothing in the tree still **asserts** the sentence. Editing `18779-…md` would be a no-op that spends a third file on the serial. ⛔ Left alone, exactly as ordered. ## 5. ⭐ The written confirmation `Check Changeset` route 0 asks for — and why that check stays red `node scripts/check-empty-changeset.mjs --base origin/main` exits **1** here, and that is **by design**, ⛔ not a defect and ⛔ not a finding about the gate. The route-0 discriminator `.github/workflows/pr-automation.yml` prescribes was run rather than reasoned about: ``` $ git merge-base origin/main HEAD eec56c3 $ git diff --name-status eec56c3 HEAD -- '.changeset/*.md' M .changeset/18677-validate-per-package-authoring-pass.md M .changeset/18778-lint-per-package-authoring-pass.md ``` Every row is `M`, none is `A` ⇒ the **DELIBERATE CORRECTION** class. The workflow's own instruction for it, verbatim: > -> do NOT apply 'skip-changeset'. Write the confirmation on the PR -- name the note and what changed under it, and get it confirmed there in writing -- and LEAVE THIS CHECK RED. > … Ruled on objectstack-ai#18375 (ruling D, maintainer 2026-09-18): the 'skip-changeset' label is never applied to a PR that edits an existing changeset. **So, saying it, as the gate asks:** - **The notes:** `.changeset/18677-validate-per-package-authoring-pass.md` (PR objectstack-ai#18769's, amended by PR objectstack-ai#18867) and `.changeset/18778-lint-per-package-authoring-pass.md` (PR objectstack-ai#18778's). Both pending, both unreleased. - **What changed under them:** ⛔ nothing in this PR's code — this PR contains none. What changed under them earlier is **objectstack-ai#18779**, which neutralised the top-level collection index in `findingKey` and in the same stroke retired the sentence both notes quote. Both notes were written before that landed and were never revisited; the source they cite was, and now prohibits the sentence by name. - **What is asked:** confirmation that these two pending release notes may be corrected in place. ⛔ Restoring either from the base would republish a sentence the shipping code contradicts. This PR stays **draft** until that confirmation.⚠️ **This PR carries no label, and the red is expected on CI.** Step 11 of `changeset-check` ("Require a changeset (or the skip-changeset label)") fails first — this PR adds no changeset of its own — which short-circuits the job, so the foreign-changeset refusal above never executes on CI at all. `Check Changeset` is not a required context, so its red blocks no merge and an approver merges over it. ⭐ Measured on the adjacent precedent rather than recalled: PR objectstack-ai#18867's head `1056c00195` shows `Check Changeset` = **failure** on both runs, its final label set is `documentation, size/s, tooling` with **no `skip-changeset`**, and it merged. The route-0 block and ruling D are present in `pr-automation.yml` at objectstack-ai#18867's own merge commit `03008c7e1a`, so that ruling is not newer than the precedent. ## 6. Verification All **19** commands derived for this surface, re-derived at this head — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, which reported `--repo … checked against this checkout's 'origin' remote — it holds`, change set `2 path(s) vs merge base eec56c3`, and emitted **exactly the 19** the dispatch named. Exit codes captured by redirecting first (⛔ never through a pipe). | # | command | exit | |:--|:--|:--| | 1 | `node scripts/check-adr-0087-registration.mjs --base origin/main` | **0** | | 2 | `node scripts/check-adr-0087-registration.mjs --self-test` | **0** | | 3 | `node scripts/check-changeset-no-major.mjs --base origin/main` | **0** | | 4 | `node scripts/check-changeset-no-major.mjs --self-test` | **0** | | 5 | `node scripts/check-closing-keyword-parity.mjs` | **0** | | 6 | `node scripts/check-closing-keyword-parity.mjs --self-test` | **0** | | 7 | `node scripts/check-comment-mask-corpus.mjs` | **0** | | 8 | `node scripts/check-empty-changeset.mjs --base origin/main` | **1 — by design, §5** | | 9 | `node scripts/check-empty-changeset.mjs --self-test` | **0** | | 10 | `node scripts/pm/release-rehearsal-clone.mjs --self-test` | **0** | | 11 | `pnpm check:changeset-gate-self-tests` | **0** | | 12 | `pnpm check:driver-memory-census` | **0** | | 13 | `pnpm check:gitlink-declared` | **0** | | 14 | `pnpm check:nul-bytes` | **0** | | 15 | `pnpm check:objectui-changeset` | **0** | | 16 | `pnpm check:pm-changeset-deadline-census` | **0** | | 17 | `pnpm check:published-files` | **0** | | 18 | `pnpm check:refd-timer-probe` | **0** | | 19 | `pnpm check:watch-hint-literal` | **0** | Gate 8's refusal names both files and the DELIBERATE CORRECTION class; §5 is its remedy. Gate 1 reads `.changeset/18677-…md [BREAKING] not-required (no-migration-prescription)` — the disposition PR objectstack-ai#18867 added survives this edit intact. Beyond the 19, run because the derivation refuses to call their silence a clearance: | what | result | |:--|:--| | `node scripts/check-changeset-fixed.mjs` — flagged ⛔ by the derivation, "roster under `.changeset`, which one of your paths is in" | **exit 0** — config `fixed` group in sync with 70 public packages | | `pnpm check:pm-governed-prose` | **exit 0** — and it names the 6 governed surfaces (`docs/adr/**` · `.claude/**` · `skills/**` · `AGENTS.md` · `CLAUDE.md` · `docs/NORTH-STAR.md`). `.changeset/**` is not among them, so no governed-surface obligations attach to this diff | | `pnpm lint` — whole repo, `eslint . --no-inline-config`, ⛔ **not narrowed**, so no narrowing argument is owed | **exit 0** at `2babd18767` | | control characters — `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over both changed files | no hits (grep exit 1) | **No package test or typecheck is owed and none is claimed.** The diff touches no package source, no `exports`, no spec contract, no built artefact and no test. `turbo`'s affected set is empty of packages for a `.changeset/*.md` edit; that is stated as "not owed", ⛔ not as "green". **No changeset is owed for this PR.** It declares no release of its own — route 0 above is the discriminator, and it also forbids the `skip-changeset` label that would otherwise declare that in writing. **No label is written by this PR**, as ordered, and the order is independently correct: ruling D forbids `skip-changeset` on exactly this class. ## 7. Where this PR's reading differs from the dispatch order ⭐ Reported rather than silently accommodated, per the order's own instruction. - **`Clause-②: no` is declared above exactly as the seat declared it**, and this PR's reading of the finished diff **agrees**: prose in two unreleased notes, no export, no key, no member, no registration, no accept set moved in either direction. -⚠️ **The order's label reasoning is right and its prediction is wrong.** "Write no labels" is correct — ruling D forbids `skip-changeset` here by name. But "⇒ your round needs no label to go green" does not hold: PR objectstack-ai#18867, the precedent cited for it, went **red** on `Check Changeset` and was merged over, as measured in §5. This round will be red too, and ⛔ that red is not "a real finding about the gate" — it is the gate's documented behaviour for this class, and §5 is the prose-and-person remedy it prescribes. - **Nothing else in the order needed refusing.** The settled bound fits both sentences; `18677`'s `**BREAKING**` section is untouched; the two entries needed one correction, not two. ## Boundaries — what this PR deliberately does not do - ⛔ It does not touch `.changeset/18779-per-package-dedup-positional-key.md`. §4 shows why that would be a no-op. - ⛔ It does not touch `packages/cli/src/**`, any test, `content/docs/releases/`, or any `packages/*/CHANGELOG.md`. - ⛔ It does not add or change a `Clause-②:` line inside either changeset **body**. `18778-…md` carries `Clause-②: yes (narrowing)` at `:36`; that is a landed declaration about its own release and ⛔ not this card's to re-grade. - ⛔ It writes no label, and it did not route around that anywhere. ## Acceptance notes - **Noted, not filed:** `.changeset/18677-…md:9-16` pins a measurement to `origin/main 09e16a5` — `os build --json warnings: 4` against `os validate --json warnings: 3` — and objectstack-ai#18779 has since moved that fixture's build count from 4 to 3. It is ⛔ not a defect: the reading is explicitly bound to a named sha, it was true there, and `18779-…md:19-24` publishes the 4→3 move in the same release, so a CHANGELOG reader gets both. Rewriting a correctly-dated historical measurement would be the larger error. **Carrier:** none — no PR is in flight on that file and none is predicted; recorded here because the "somebody will touch this anyway" fallback ⛔ does not hold for `.changeset/*`. - **Noted, not filed:** the first sweep instrument used for §4 undercounted the tree scan 13 → 11 by collapsing `\s+` without first stripping block-comment continuation markers. That is a fact about a throwaway script in this session, ⛔ not about any tracked file — no gate, helper or committed tool has the defect. It is written down because the card's own history records the opposite failure of the same instrument class (a convincing `0`), and the pair is the argument for the lit control. **Carrier:** none; nothing in the repo carries this code. --- _Generated by [Claude Code](https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #18823
Clause-②: no
.changeset/18677-validate-per-package-authoring-pass.md— PR #18769's still-pending, still-unreleased entry — declined its**BREAKING**banner on the strength of a negative, verbatim: "⛔ not declared breaking, because the narrowing could not be exhibited and is bounded by an existing gate", alongside "No newly-refused input could be exhibited on any fixture". The negative is false. This PR edits that one file: it adds the banner, the measured table, the mechanism that explains why the union fold cannot see the finding, and the ADR-0087 disposition the banner owes — and it narrows the sentence an upgrader would have been misled by.⛔ A forward edit to an unpublished file. ⛔ Not a rewrite of landed history, and ⛔ not a ruling on #18677's landed
Clause-②: no— see "Boundaries" below.1. The clock, re-confirmed at this branch's base
be7aeb8275— ⛔ not inheritedThe card is
priority:p1because the entry is unconsumed. Three readings, all taken here:origin/maingit ls-tree)npm view @objectstack/cli versionpackages/cli/package.jsonversion onorigin/mainpackages/cli/CHANGELOG.md⇒ unconsumed. Amending it now costs a diff; amending it after the release that consumes it costs a published version number that cannot be recalled.
2. ⭐ The reviewer's table, REPRODUCED here — ⛔ not copied
The measurement is PR #18813's isolated at-tier contract review (record
5722342660, finding F2). The card's first instruction is to reproduce it rather than build on it. Driven in this worktree, on theCONFIG_FLIPtwo-package fixture planted verbatim frompackages/cli/test/lint-per-package-authoring-parity.test.ts(lines 115-166, sha256d5fb835ac5…), throughpackages/cli/bin/run-dev.jswith tsx:os validate --json --strictonCONFIG_FLIPpackages/cli/src/commands/validate.tsrestored to its pre-#18769 blobbafa54b07f340cec6253)It reproduces. The one warning is
field-no-consumersatpackage 'com.example.ppflip.core' — object "pp_account" · field "industry".Ablation hygiene, because a mutation that never reached disk reads exactly like a clean run:
git hash-objecton the file returnedbafa54b07f…, equal to the target blob, and the marker countrunPerPackageAuthoringRulesmoved 3 → 0 in that file;git checkout HEAD -- packages/cli/src/commands/validate.tsfrom atrap … EXIT INT TERMwith an absolute path, verified by hash equality back to340cec6253…and bygit diff HEADbeing empty, not by an exit code;git status --porcelainis empty after the run. ⛔ No landed code is modified by this PR — the ablation is a one-off measurement, andvalidate.tsis untouched in the diff.Extra reading this PR took that the record did not, and the changeset now states: the default (non-strict) face of
os validate --jsonon the same fixture at head is exit 0 with 1 warning. So on this fixture only the--strictdoor moved.3. What the file now says
**BREAKING**banner naming the door that moved:os validate --strictcan now fail a project it passed before.os buildalready reports it — so the narrowing is still bounded by the command that ships.--strictto keep the old verdict, or fix what the per-package pass reports.packageBodyAsStackhands each package the artifact's wholepackages[]as resolution context, so a cross-package reference still resolves and the reference-integrity rules stay quiet — but a reachability rule asks what the stack reads, and per package the stack is that one package's own body. A field whose only consumer lives in a sibling package is live to the union run and inert to the per-package run. That is the shape the earlier fixtures could not produce, and it is why "could not be exhibited" was a statement about the fixtures rather than about the property.adr-0087:disposition marker, in the HTML-comment form the gate reads, claimingnot-required (no-migration-prescription): nothing an author writes changes, soobjectstack migrate metahas nothing to rewrite.--strict, and it is the sentence the card names as the one that would mislead an upgrader.minor. ⛔ Nothing here asks formajor; the launch window refuses it and the banner plus the disposition are what carry breaking-ness.4. This PR's own changeset — MEASURED, with controls both ways
The question "does a PR that only edits a changeset file publish anything?" was answered by running
changeset versionin a throwaway comparison worktree at this branch's base and readingpackages/cli/CHANGELOG.md, whichpackages/cli'sfiles[]ships (["dist","README.md","CHANGELOG.md"]). Four legs:@objectstack/cliversionpackages/cli/CHANGELOG.mdd132f18a05…scripts/check-adr-0087-registration.mjs)10ccd96910…, 20 diff lines, all inside the entry #18677 already schedules'@objectstack/cli': patch)⇒ Two facts, and they point in different directions, so both are stated:
changeset-checkjob uses for theskip-changesetexemption, and it is what the positive control above makes visible rather than assumed.⇒ Route taken:
skip-changeset. Of the three routes theCheck Changesetlog itself names, route 3 (an empty-frontmatter changeset) is closed — newly added ones are rejected (#5471) because an all-empty set makeschangesets/actionreturn green while publishing nothing (#4898). Between the other two, the positive control above is what decides it: adding a real changeset would add one new published CHANGELOG bullet — a user-facing release note announcing a correction to the release note directly above it — while moving no version. This PR amends the prose of a bump that is already declared; it adds none. That is the exemption's own wording.node scripts/pm/label-write.mjs --issue 18867 --repo objectstack-ai/objectstack --add skip-changesetwas refused by this session's local permission classifier (reason:[CI Bypass]) before any request was issued — ⛔ not by GitHub, and ⛔ no status code was reached. This dev did ⛔ not route around that refusal through a second channel. The measurement is above and the route is declared; applying the label is left to the dispatching seat. Until it is applied, theCheck Changesetred below stands.5.⚠️ The pending-note correction still needs a person's word — ⛔ and the red on this PR is NOT the gate that asks for it
Run locally,
node scripts/check-empty-changeset.mjs --base origin/mainexits 1 here, naming this file and this class:and closing:
So, saying it, as the gate asks:
.changeset/18677-validate-per-package-authoring-pass.md, PR fix(cli):os validateruns the per-package author-time rule passos buildalready ran #18769's, pending and unreleased.CONFIG_FLIP, shipped by PR fix(cli):os lintruns the per-package author-time rule pass the other two doors already ran #18813) exhibits the newly-refused input the note declared unexhibitable. The note's runtime claims are otherwise untouched and undisputed.skip-changesetmust be withheld so the refusal above would stay red on CI and summon a person. Measured on this PR's own failing run (job 105457719184, step list read from the Actions API, ⛔ not inferred from the check name), that argument is false:check-empty-changeset --baserunsStep 11 short-circuits the job, so the foreign-changeset refusal never executes on CI at all — labelled or not. Withholding the label therefore hides nothing and reveals nothing; what it does instead is leave a misleading headline red ("This PR adds no changeset ... run
pnpm changeset") on a PR that correctly adds none. ⇒ the refusal's "say so on the PR and get it confirmed" is a prose-and-person requirement, discharged by this section, ⛔ not by a CI red that does not happen.skip-changeseton, the wholechangeset-checkjob is exempt, so steps 12 and 13 do not run here either. Both were run locally at1056c00195:check-empty-changeset --base origin/mainexit 1 (by design, the refusal quoted above) andcheck-adr-0087-registration --base origin/mainexit 0, reading.changeset/18677-…md [BREAKING] not-required (no-migration-prescription). The live ADR-0087 check also runs over the whole pending stock at RC-cut time (cut-rc.yml,--base $SNAPSHOT_SHA), so the disposition is still checked before any release consumes this entry — just not on this PR.skip-changeset. Measured, not recalled: the foreign-changeset refusal landed in #18146 at0ffb4963e52026-09-14T06:56:58Z and #18126 merged atf3b41e87d42026-09-14T04:04:11Z;git merge-base --is-ancestor 0ffb4963e5 f3b41e87d4exits 1, with a control leg (f3b41e87d4^againstf3b41e87d4) at exit 0 on a non-shallow checkout. So the refusal post-dates both by about three hours and ⛔ neither is precedent for it — which is exactly why the reading above was measured on this PR rather than borrowed from them.6. Verification
node scripts/check-adr-0087-registration.mjs --base origin/main.changeset/18677-…md [BREAKING] not-required (no-migration-prescription)node scripts/check-changeset-no-major.mjs --base origin/mainnode scripts/check-empty-changeset.mjs --base origin/mainscripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(self-tests,check:nul-bytes,check:published-files,check:objectui-changeset,check:pm-changeset-deadline-census, …)pnpm lint— the whole repo,eslint . --no-inline-config, ⛔ not narrowed1056c00195grep -naPover the changed file for[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]: no hitsdispatch-gates.mjsdoes not name thepnpm lintfamily; it was run anyway, unnarrowed, so no narrowing argument is owed. Its own provenance line readsobjectstack-ai/objectstackat1056c00195, and--repowas asserted and held.No package test or typecheck is owed: the diff touches no package source, no
exports, no spec contract and no built artefact.packages/cli's dependency closure was built only to drive the CLI for §2.Boundaries — what this PR deliberately does not do
validate.tsor any landed code. The diff is one file.Clause-②:line to the changeset body, though the sibling.changeset/18778-lint-per-package-authoring-pass.mdcarries one. Writingyes (narrowing)into [finding]os buildruns a per-package authoring-rule walkos validatedoes not — the residue #17069 left, in the same false-clean direction #18677's note would be a retro-correction of a landed declaration, and the card marks that "Not asserted" and routes it above this seat. The banner and the ADR-0087 disposition are release-facing and are what the card prescribes; the governance declaration is not.yes (narrowing)that shipped declarednoowes beyond this file, nor on whether [finding]os buildruns a per-package authoring-rule walkos validatedoes not — the residue #17069 left, in the same false-clean direction #18677's mandatory contract review is now owed. That is the maintainer's.content/docs/releases/, anypackages/*/CHANGELOG.md,packages/cli/src/commands/compile.ts,packages/qa/vitest-filter-preflight/**orpackages/cli/vitest.config.ts.Acceptance notes
.changeset/18778-lint-per-package-authoring-pass.mdcarries itsClause-②: yes (narrowing)line inside the changeset body, i.e. in text that ships verbatim into the published CHANGELOG. Whether that governance token belongs in a user-facing release note is a question about changeset convention, not a defect: no gate reads it there, nothing is falsified by it, and it is out of this card's one-file surface. Carrier: the next PR to touch changeset conventions; no PR is in flight on it.5722459190which asks for it on the discriminant list). It has no home inAGENTS.mdor any gate. Placing it is a governed-surface edit and so is not this PR's to make. Carrier: the triage seat that asked for it.Authored by Claude Code in session
session_01DvvamiacK328idtBYJBxV3; the branch isclaude/issue-18823-pending-changeset-breaking-banner. (Attribution is stated here in prose on purpose: this body is edited through a channel measured to store only a bare footer, which carries no session id.)Generated by Claude Code