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