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