From ce243bdb308b64e3fa89446fd5af571775900cf9 Mon Sep 17 00:00:00 2001 From: Noah Schatz Date: Fri, 25 Sep 2026 21:20:56 +0000 Subject: [PATCH] Let the 0.1.0 release commit pass the version tests The Version Packages pull request moves package.json and the VERSION export to 0.1.0 and consumes the pending changesets, and at that commit three test files went red on assertions that pinned the 0.0.x line: - test/sanity.test.ts asserted VERSION matches 0.0.x. The operator moved every package to 0.1.x, so it now pins the 0.x line (a plain 0.minor.patch, never a 1.0, a major or a prerelease). - test/release-readiness.test.ts asserted package.json and VERSION read exactly 0.0.11, the version the readiness audit measured. The version may now also sit on the release line the audit certified, read from the committed surface inventory (certifiedFor 0.1.0): 0.1.0 itself, or a later 0.1.x patch, which the classification rule keeps off the public surface and the surface tests hold exactly. Any other version (0.0.12, 0.2.0, 1.0.0, a prerelease) still reds, and a new case proves that. - Two package-agreement controls in test/docs-set.test.ts graded their seeded installation page against the live package.json, so they flipped with the version. They now grade against this repository's manifest with only the version replaced, on both sides of the 0.0.x line, so each arm is shown to go red in both directions whatever the tree's version is. What still reds at 0.1.0 is the real installation page, which says the package is not installable from the public registry: the gate reads a 0.1.0 manifest as published, so that page changes in the version pull request itself. Verified on a simulated version commit with that page flipped: the full ladder passes. No change under src/. Claude-Session: https://claude.ai/code/session_01U24bnHKNxQWMxmfN7ny7w9 --- test/docs-set.test.ts | 109 +++++++++++++++++++++++---------- test/release-readiness.test.ts | 47 ++++++++++++-- test/sanity.test.ts | 7 ++- 3 files changed, 124 insertions(+), 39 deletions(-) diff --git a/test/docs-set.test.ts b/test/docs-set.test.ts index 630061b..e7c5515 100644 --- a/test/docs-set.test.ts +++ b/test/docs-set.test.ts @@ -15,7 +15,7 @@ */ import { spawnSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; @@ -55,6 +55,37 @@ function graded(files: Record, arms: readonly ArmName[]): Findin } } +/** + * Grade a control against a `package.json` that is this repository's own with only its `version` + * replaced, so the package-agreement controls say the same thing whatever version the tree is at. + * The gate reads a `0.0.x` version as not yet on the public registry and anything later as on it, + * so each control is graded on both sides of that line rather than on whichever side the tree + * happens to sit. + */ +function gradedAtVersion( + files: Record, + arms: readonly ArmName[], + version: string, +): Finding[] { + const dir = mkdtempSync(join(tmpdir(), "fhir-docs-control-")); + const manifestDir = mkdtempSync(join(tmpdir(), "fhir-docs-manifest-")); + try { + for (const [name, body] of Object.entries(files)) seed(dir, name, body); + const manifest = JSON.parse(readFileSync(join(REPO_ROOT, "package.json"), "utf8")) as object; + const packageJsonPath = join(manifestDir, "package.json"); + writeFileSync(packageJsonPath, JSON.stringify({ ...manifest, version })); + return checkDocsSet(dir, { arms, packageJsonPath }); + } finally { + rmSync(dir, { recursive: true, force: true }); + rmSync(manifestDir, { recursive: true, force: true }); + } +} + +/** A `0.0.x` version, which the gate reads as not on the public registry. */ +const UNPUBLISHED_LINE = "0.0.11"; +/** A version past `0.0.x`, which the gate reads as on the public registry. */ +const PUBLISHED_LINE = "0.1.0"; + /** * Grade a control against an allow-list that declares nothing. * @@ -234,45 +265,61 @@ describe("every arm goes red on a seeded control", () => { expect(messages(findings)).toContain("every sidebar item must be a page id string"); }); + /** An installation page stating a Node range and exactly one registry claim. */ + const installationPage = (nodeRange: string, claim: string): Record => ({ + "installation.md": page( + "installation", + "Installation", + [ + "Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.", + "", + `It needs Node \`${nodeRange}\`, ships ESM, and also offers CommonJS.`, + "", + claim, + ].join("\n"), + ), + }); + const INSTALLABLE = "The package is installable from the public npm registry today."; + const NOT_INSTALLABLE = "The package is not installable from the public npm registry today."; + it("package-agreement: a Node range and a registry claim that disagree with package.json", () => { - const findings = graded( - { - "installation.md": page( - "installation", - "Installation", - [ - "Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.", - "", - "It needs Node `>=18.0.0`, ships ESM, and also offers CommonJS.", - "", - "The package is installable from the public npm registry today.", - ].join("\n"), - ), - }, + const findings = gradedAtVersion( + installationPage(">=18.0.0", INSTALLABLE), ["package-agreement"], + UNPUBLISHED_LINE, ); expect(messages(findings)).toContain('does not state the supported Node range ">=22.0.0"'); expect(messages(findings)).toContain("disagrees with package.json"); }); - it("package-agreement: the truthful page passes the same arm", () => { - const findings = graded( - { - "installation.md": page( - "installation", - "Installation", - [ - "Install `@cosyte/fhir` with `pnpm add @cosyte/fhir`.", - "", - "It needs Node `>=22.0.0`, ships ESM, and also offers CommonJS.", - "", - "The package is not installable from the public npm registry today.", - ].join("\n"), - ), - }, + it("package-agreement: the opposite registry claim disagrees once the version leaves 0.0.x", () => { + const findings = gradedAtVersion( + installationPage(">=22.0.0", NOT_INSTALLABLE), ["package-agreement"], + PUBLISHED_LINE, ); - expect(messages(findings)).toBe(""); + expect(messages(findings)).toContain("disagrees with package.json"); + }); + + it("package-agreement: the truthful page passes the same arm", () => { + expect( + messages( + gradedAtVersion( + installationPage(">=22.0.0", NOT_INSTALLABLE), + ["package-agreement"], + UNPUBLISHED_LINE, + ), + ), + ).toBe(""); + expect( + messages( + gradedAtVersion( + installationPage(">=22.0.0", INSTALLABLE), + ["package-agreement"], + PUBLISHED_LINE, + ), + ), + ).toBe(""); }); it("synthetic-identifiers: every identifying element is graded against the declaration", () => { diff --git a/test/release-readiness.test.ts b/test/release-readiness.test.ts index d6d4ba0..6e95b1d 100644 --- a/test/release-readiness.test.ts +++ b/test/release-readiness.test.ts @@ -1466,19 +1466,54 @@ describe("the exports map publishes exactly the certified subpaths", () => { describe("this tree performs no publication and makes none inevitable", () => { /** * The version measured on this tree when the audit was written. It is asserted rather than - * derived so that a bump ANYWHERE reds here: the point is that this change did not move it. - * `test/sanity.test.ts` separately asserts VERSION against package.json; this is the different - * question of whether either one MOVED, and the two coexist. + * derived so that a bump the audit did not certify reds here. `test/sanity.test.ts` separately + * asserts VERSION against package.json; this is the different question of whether either one + * MOVED, and the two coexist. */ const MEASURED_VERSION = "0.0.11"; - it("leaves package.json at the version this work measured", () => { - expect(PACKAGE_JSON.version).toBe(MEASURED_VERSION); + /** + * The one move the audit sanctions: the release it certified, read from the committed surface + * inventory rather than restated, and patches on that release's line. Changesets' version commit + * moves `package.json` and the `VERSION` export together, so the release commit reads the + * certified version and must pass here. A patch cannot change a public observable under the + * classification rule above, and the surface tests hold the certified surface exactly in every + * state, so a patch on the certified line keeps the certification. A new minor (or any other + * version) is a release the audit never certified, and it reds until the surface is certified + * for it. + */ + function isCertifiedMove(version: string): boolean { + const certified = /^(\d+)\.(\d+)\.(\d+)$/.exec(INVENTORY.certifiedFor); + const candidate = /^(\d+)\.(\d+)\.(\d+)$/.exec(version); + if (certified === null || candidate === null) return false; + return ( + candidate[1] === certified[1] && + candidate[2] === certified[2] && + Number(candidate[3]) >= Number(certified[3]) + ); + } + + const accountedFor = (version: string): boolean => + version === MEASURED_VERSION || isCertifiedMove(version); + + it("leaves package.json at the version this work measured, or on the release line it certified", () => { + expect( + accountedFor(PACKAGE_JSON.version), + `package.json reads ${PACKAGE_JSON.version}, which is neither the measured ${MEASURED_VERSION} nor on the ${INVENTORY.certifiedFor} line the audit certified`, + ).toBe(true); }); it("leaves the VERSION export agreeing with it", () => { expect(VERSION).toBe(PACKAGE_JSON.version); - expect(VERSION).toBe(MEASURED_VERSION); + expect(accountedFor(VERSION)).toBe(true); + }); + + it("has teeth: a version the audit neither measured nor certified is refused", () => { + expect(accountedFor(MEASURED_VERSION)).toBe(true); + expect(accountedFor(INVENTORY.certifiedFor)).toBe(true); + for (const uncertified of ["0.0.12", "0.0.10", "0.2.0", "1.0.0", "0.1.0-next.0", "0.1"]) { + expect(accountedFor(uncertified), uncertified).toBe(false); + } }); it("leaves every classified changeset still pending in .changeset/", () => { diff --git a/test/sanity.test.ts b/test/sanity.test.ts index 79cc352..3053331 100644 --- a/test/sanity.test.ts +++ b/test/sanity.test.ts @@ -25,7 +25,10 @@ describe("@cosyte/fhir scaffold", () => { expect(VERSION).toBe(pkg.version); }); - it("starts on the v0.0.x pre-alpha ladder", () => { - expect(VERSION).toMatch(/^0\.0\.\d+$/); + it("stays on the 0.x line, where a breaking change bumps the minor version", () => { + // The operator moved every package to 0.1.x, so the ladder this pins is the 0.x one: a plain + // `0.minor.patch`, never a 1.0, a major or a prerelease. The classification rule in + // `test/release-readiness.test.ts` is what keeps a break off the patch position. + expect(VERSION).toMatch(/^0\.\d+\.\d+$/); }); });