Skip to content

docs(cli): #18769's pending changeset declares the os validate --strict narrowing an at-tier review exhibited - #18867

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-18823-pending-changeset-breaking-banner
Sep 20, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-18823-pending-changeset-breaking-banner

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

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 inherited

The card is priority:p1 because the entry is unconsumed. Three readings, all taken here:

reading value
the file on origin/main present (git ls-tree)
npm view @objectstack/cli version 17.4.0
packages/cli/package.json version on origin/main 17.4.0 — equal, so no release has bumped it
the entry's own distinctive sentence in packages/cli/CHANGELOG.md 0 hits

⇒ 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 the CONFIG_FLIP two-package fixture planted verbatim from packages/cli/test/lint-per-package-authoring-parity.test.ts (lines 115-166, sha256 d5fb835ac5…), through packages/cli/bin/run-dev.js with tsx:

os validate --json --strict on CONFIG_FLIP exit warnings
packages/cli/src/commands/validate.ts restored to its pre-#18769 blob bafa54b07f 0 0
at head (blob 340cec6253) 1 1

It reproduces. The one warning is field-no-consumers at package 'com.example.ppflip.core' — object "pp_account" · field "industry".

Ablation hygiene, because a mutation that never reached disk reads exactly like a clean run:

  • the mutation was proven on disk before the CLI was driven — git hash-object on the file returned bafa54b07f…, equal to the target blob, and the marker count runPerPackageAuthoringRules moved 3 → 0 in that file;
  • restore is git checkout HEAD -- packages/cli/src/commands/validate.ts from a trap … EXIT INT TERM with an absolute path, verified by hash equality back to 340cec6253… and by git diff HEAD being empty, not by an exit code;
  • git status --porcelain is empty after the run. ⛔ No landed code is modified by this PR — the ablation is a one-off measurement, and validate.ts is 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 --json on the same fixture at head is exit 0 with 1 warning. So on this fixture only the --strict door moved.

3. What the file now says

  • **BREAKING** banner naming the door that moved: os validate --strict can now fail a project it passed before.
  • The before/after table above, plus the named finding and the fact that os build already reports it — so the narrowing is still bounded by the command that ships.
  • The remedy an upgrader owes: drop --strict to keep the old verdict, or fix what the per-package pass reports.
  • ⭐ The mechanism, which is what replaces the false negative: packageBodyAsStack hands each package the artifact's whole packages[] 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.
  • An adr-0087: disposition marker, in the HTML-comment form the gate reads, claiming not-required (no-migration-prescription): nothing an author writes changes, so objectstack migrate meta has nothing to rewrite.
  • The sentence "nothing that builds today stops validating" is removed. It was true of the default face and false of --strict, and it is the sentence the card names as the one that would mislead an upgrader.
  • Level is untouched at minor. ⛔ Nothing here asks for major; 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 version in a throwaway comparison worktree at this branch's base and reading packages/cli/CHANGELOG.md, which packages/cli's files[] ships (["dist","README.md","CHANGELOG.md"]). Four legs:

leg resulting @objectstack/cli version packages/cli/CHANGELOG.md
base, untouched 17.5.0 sha256 d132f18a05…
negative control — base + an edit to a file no package ships (scripts/check-adr-0087-registration.mjs) 17.5.0 byte-identical to base
this PR — base + the changeset amendment only 17.5.0 sha256 10ccd96910…, 20 diff lines, all inside the entry #18677 already schedules
positive control — base + a NEW changeset ('@objectstack/cli': patch) 17.5.0 one new bullet appears: a release entry of its own

⇒ Two facts, and they point in different directions, so both are stated:

  1. This PR does move bytes that ship. ⛔ It is not true that editing a changeset publishes nothing.
  2. This PR declares no release of its own — no new entry, no version movement — which is exactly the wording the changeset-check job uses for the skip-changeset exemption, and it is what the positive control above makes visible rather than assumed.

⇒ Route taken: skip-changeset. Of the three routes the Check Changeset log itself names, route 3 (an empty-frontmatter changeset) is closed — newly added ones are rejected (#5471) because an all-empty set makes changesets/action return 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.

⚠️ The label is not on this PR yet. node scripts/pm/label-write.mjs --issue 18867 --repo objectstack-ai/objectstack --add skip-changeset was 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, the Check Changeset red 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/main exits 1 here, naming this file and this class:

DELIBERATE CORRECTION -- your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: do NOT restore it -- say so on the PR and get it confirmed; restoring it from the base would put the false sentence back.

and closing:

Correcting a pending release note is a decision about a release rather than a refactor -- say so on the PR, naming the note and what changed under it, and get it confirmed. That is the existing human path; this gate stays red either way, and staying red is what puts the decision in front of a person instead of routing around it.

So, saying it, as the gate asks:

⚠️ A correction to an earlier reading in this very PR, stated rather than quietly dropped. This section first argued that skip-changeset must 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:

step outcome
11 · Require a changeset (or the skip-changeset label) failure
12 · Reject an empty-frontmatter changeset added by this PR — where check-empty-changeset --base runs skipped
13 · Require an ADR-0087 disposition on a declared-breaking changeset skipped
14-15 · allow-major re-read, major guard skipped

Step 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.

⚠️ What the label costs, so nobody reads a green board as more than it is: with skip-changeset on, the whole changeset-check job is exempt, so steps 12 and 13 do not run here either. Both were run locally at 1056c00195: check-empty-changeset --base origin/main exit 1 (by design, the refusal quoted above) and check-adr-0087-registration --base origin/main exit 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.

⚠️ For context, the two landed precedents for this act — #18126 (the same repair, BREAKING banner + ADR-0087 disposition onto a pending entry) and #17851 — both carried skip-changeset. Measured, not recalled: the foreign-changeset refusal landed in #18146 at 0ffb4963e5 2026-09-14T06:56:58Z and #18126 merged at f3b41e87d4 2026-09-14T04:04:11Z; git merge-base --is-ancestor 0ffb4963e5 f3b41e87d4 exits 1, with a control leg (f3b41e87d4^ against f3b41e87d4) 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

what result
node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 — .changeset/18677-…md [BREAKING] not-required (no-migration-prescription)
node scripts/check-changeset-no-major.mjs --base origin/main exit 0
node scripts/check-empty-changeset.mjs --base origin/main exit 1 — by design, see §5; it does not run on this PR's CI, labelled or not
the other 15 commands from scripts/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, …) all exit 0
pnpm lint — the whole repo, eslint . --no-inline-config, ⛔ not narrowed exit 0 at 1056c00195
control characters grep -naP over the changed file for [\x00-\x08\x0b\x0c\x0e-\x1f\x7f]: no hits

dispatch-gates.mjs does not name the pnpm lint family; it was run anyway, unnarrowed, so no narrowing argument is owed. Its own provenance line reads objectstack-ai/objectstack at 1056c00195, and --repo was 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

Acceptance notes

  • Noted, not filed: .changeset/18778-lint-per-package-authoring-pass.md carries its Clause-②: 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.
  • Noted, not filed: the ⭐ generalization this card turns on — a property that could not be exhibited with the fixtures on hand is UNEXHIBITED, ⛔ not absent — is currently recorded only in card prose ([finding] PR #18769's PENDING changeset declines the BREAKING banner on the ground that the narrowing "could not be exhibited" — an at-tier review exhibited it, and the file is still unreleased #18823, and triage 5722459190 which asks for it on the discriminant list). It has no home in AGENTS.md or 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 is claude/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

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

Copy link
Copy Markdown
Collaborator

Ruling: batch #200 item 2 · letter A · maintainer 「同意」 2026-09-20T16:03Z

Director seat, summon #25, session_012GcsUbuqFGBibkEDMRC1eE. Presented with the recommendation A (confirm the BREAKING banner on #18769's pending changeset; B refuse the banner and C soften it refused); the maintainer agreed 「同意」. This comment is the human path the changeset gate names: the maintainer's confirmation recorded on the PR, naming the note and what changed under it. It is also this seat's ACCEPT review record for the PR (a single changeset file, docs-only, Clause-②: no; no code moves).

Ruling — A: the release declares the os validate --strict narrowing as BREAKING

Four-facet reading (this seat's own): ① the note says what the tree measures; ② CI users running --strict are the ones hit after upgrading, and the banner is their only warning; ③ loud; ④ nothing new.

Prior rulings read: #18652 (batch #156 item 1, pending-note correction confirmed on the PR); #18823's own reading (5725718106: this correction changes the release's declared breaking-ness, so the #18652 ruling did not transfer on its own — hence this item).

Execution, same stroke

Ready for review; auto-merge (squash) armed; the queue lands it. #18823 closes on merge (Fixes); its pm:awaiting-maintainer label comes off with the close (this seat removes it if the platform leaves it).


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 20, 2026 16:04
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 20, 2026
Merged via the queue into main with commit 03008c7 Sep 20, 2026
33 of 35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-18823-pending-changeset-breaking-banner branch September 20, 2026 16:35
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

3 participants