From 1056c001953497cb83bba50debd6f4784c0d962a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 02:01:44 +0000 Subject: [PATCH] docs(cli): #18769's pending changeset declares the `os validate --strict` narrowing an at-tier review exhibited MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.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 --- ...18677-validate-per-package-authoring-pass.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.changeset/18677-validate-per-package-authoring-pass.md b/.changeset/18677-validate-per-package-authoring-pass.md index 53b968962fe..7ea2f9e0c0a 100644 --- a/.changeset/18677-validate-per-package-authoring-pass.md +++ b/.changeset/18677-validate-per-package-authoring-pass.md @@ -17,6 +17,21 @@ After: both report 4, the same set, in the same order. **The loop is now one seam, not two copies.** `runPerPackageAuthoringRules` lives beside `artifactPackages` / `packageBodyAsStack` in `utils/artifact-packages.ts`, whose header already forbids a second copy of that shape by name. What would have drifted between two hand-written loops is not the package reading but the **verdict** — the de-duplication key, the severity split, the `where` prefix. `os build`'s observable output is unchanged (text face byte-identical modulo timings; `--json` payload identical). -**Severity mapping is `os build`'s, unchanged.** A per-package `error` refuses (exit 1); an advisory joins `warnings`. So `os validate` is narrowed only to the bar the command that *ships* already holds: every input it can now refuse is one `os build` already refuses, which means **nothing that builds today stops validating**. No newly-refused input could be exhibited on any fixture — across the repo's own two-package example and three constructed variants the observable change is advisory-only, because `packageBodyAsStack` hands each package the artifact's whole `packages[]` as resolution context and the reference-integrity suite resolves object names through it. Graded `minor` rather than `patch` for the new observable step line, the new advisories and the newly reachable non-zero exit; ⛔ **not** declared breaking, because the narrowing could not be exhibited and is bounded by an existing gate. +**Severity mapping is `os build`'s, unchanged.** A per-package `error` refuses (exit 1); an advisory joins `warnings`. So `os validate` is narrowed only to the bar the command that *ships* already holds: every input it can now refuse is one `os build` already refuses. + +**BREAKING** — `os validate --strict` can now fail a project it passed before. Measured on a two-package fixture whose union fold is clean and whose per-package run is not (`core` owns `pp_account`; a sibling package owns the view that displays `pp_account.industry`), driving the CLI from source: + +| `os validate` on that fixture | before | after | +|---|---|---| +| `--json` | warnings 0, exit 0 | warnings 1, exit 0 | +| `--json --strict` | exit 0 | **exit 1** | + +The one warning is `field-no-consumers` at `package 'com.example.ppflip.core' — object "pp_account" · field "industry"`, which `os build` already reports on the same fixture: nothing is refused here that `os build` does not already refuse, and the default (non-strict) face is unchanged in that measurement. A run that must keep its old verdict drops `--strict`; a project that wants to keep the flag fixes what the per-package pass reports, which is what `os build` has been reporting all along. + +Why the union fold does not see it: `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 therefore live to the union run and inert to the per-package run, and that is the shape that reaches `--strict`. + +Graded `minor` rather than `patch` for the new observable step line, the new advisories and the newly reachable non-zero exit; the launch window refuses `major`, so the breaking-ness is carried by the banner above and the ADR-0087 disposition below. Unchanged and out of scope: the ADR-0130 D4 union fold (#17069, fixed — `authoringRuleUnionStack` is in both commands), `--json` rendering (#11727), and disagreements *within* the per-package pass's verdicts (#18204). `os lint` still runs the union pass alone; its `artifactPackages` / `packageBodyAsStack` imports serve its own intra-package duplicate-name advisory, not the shared table. + +