Skip to content

Commit 24dc0b3

Browse files
committed
fix(ci): the future-spec-major gate and the artifact-door pins stop moving with the version label
On the generated Version Packages tree the spec label rolls to 18.0.0-next.0 and two pins that hold on `main` broke, with no byte of their own changed: - check-future-spec-major counted its own self-test fixtures against the tree's major (they cite 18 as "above 17" by construction), so the roll read as a gutted instrument; and its two major-18 quotation entries, whose sentences became legitimate citations at 18, reported "matched NOTHING". The floor now counts above the fixtures' own major, an entry at or below the published major is dormant (it can excuse nothing), and a self-test row drives the real corpus one major up under a prerelease-shaped label so the class reds on `main` first. - plugin-artifact-forward-conversion.test.ts claimed "the current surface" as `^<installed label>`, which at M.0.0 (and M.0.0-next.N) is the floor the door's major-step half opens on by design. It now derives the floor the sibling door tests use: the first x.y.z past the label and every retiredAfter, never an M.0.0. Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 287fd6e commit 24dc0b3

2 files changed

Lines changed: 202 additions & 24 deletions

File tree

‎packages/metadata/src/plugin-artifact-forward-conversion.test.ts‎

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,39 @@ import { readFileSync } from 'node:fs';
3434
import { dirname, join } from 'node:path';
3535
import { fileURLToPath } from 'node:url';
3636
import { resolveInstalledSpecVersion } from '@objectstack/metadata-core';
37+
import { ALL_CONVERSIONS } from '@objectstack/spec';
3738
import { MetadataPlugin } from './plugin.js';
3839

3940
const HERE = dirname(fileURLToPath(import.meta.url));
4041
const FIXTURE_PATH = join(HERE, '__fixtures__/hotcrm-17.1-built-permissions.artifact.json');
4142

43+
/**
44+
* The floor an artifact authored against the CURRENT surface declares — the
45+
* first `x.y.z` past both the installed spec's label and every retired entry's
46+
* `retiredAfter`, which no half of the door's window opens for. The same
47+
* derivation `plugin-unbound-form-predicate-roots.test.ts` uses for its
48+
* "current surface".
49+
*
50+
* ⛔ Never `^<installed label>`. That reads as current only while the label is
51+
* not the first release of a major: at `M.0.0` — and at every `M.0.0-next.N`
52+
* the version pass writes, which the door reads as `M.0.0` — the major-step
53+
* half opens every retired entry of step M by design (pinned at those labels
54+
* in metadata-core's `artifact-forward-conversion.test.ts`), and
55+
* `permission-allow-restore-purge-removed` belongs to step 18. So at the
56+
* roll the "current" artifact was converted, and both pins below read a
57+
* documented rescue as an amnesty. The patch bump keeps this floor off every
58+
* `M.0.0`.
59+
*/
60+
function currentSurfaceFloor(): string {
61+
const installed = resolveInstalledSpecVersion();
62+
if (!installed) return '';
63+
const triples = [installed, ...ALL_CONVERSIONS.flatMap((c) => (c.retiredFromLoadPath === true ? [c.retiredAfter] : []))]
64+
.map((v) => v.split(/[.-]/).slice(0, 3).map((n) => Number.parseInt(n, 10)) as [number, number, number])
65+
.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
66+
const [major, minor, patch] = triples[triples.length - 1]!;
67+
return `${major}.${minor}.${patch + 1}`;
68+
}
69+
4270
/** Fresh parse per test — `_parseAndRegisterArtifact` mutates items in place. */
4371
function loadFixture(): any {
4472
return JSON.parse(readFileSync(FIXTURE_PATH, 'utf8'));
@@ -138,14 +166,17 @@ describe('artifact door — the conversion is versioned, not a blanket amnesty (
138166
it('an artifact claiming the CURRENT spec version with a NON-default retired-key value still refuses with the tombstone', async () => {
139167
const installed = resolveInstalledSpecVersion();
140168
expect(installed).toMatch(/^\d+\.\d+\.\d+/); // spec is always resolvable here
169+
const current = currentSurfaceFloor();
170+
// Resolvable, and never a major's first release (a non-zero patch).
171+
expect(current).toMatch(/^\d+\.\d+\.[1-9]\d*$/);
141172

142173
const plugin = newPlugin();
143174
const fixture = loadFixture();
144175
// The manifest claims the running spec's own surface — the retired
145-
// window must stay closed. `^X.Y.Z` floors at exactly the installed
146-
// version, so this pin survives every future spec release without
147-
// edits.
148-
fixture.manifest.engines.protocol = `^${installed}`;
176+
// window must stay closed. Derived from the installed label and the
177+
// live registry (see `currentSurfaceFloor`), so this pin survives
178+
// every future spec release, the major roll included, without edits.
179+
fixture.manifest.engines.protocol = `^${current}`;
149180
// The real 17.1-built artifact carries only the emitted default
150181
// (150 × `false` — the premise guard above pins that), which is the
151182
// residue class #12845 now tolerates. The surviving refusal is the
@@ -172,12 +203,14 @@ describe('artifact door — the conversion is versioned, not a blanket amnesty (
172203
});
173204

174205
it('an artifact claiming the CURRENT spec version whose retired keys carry only the emitted default parses clean — the #12845 residue tolerance, at the schema layer, not this door', async () => {
175-
const installed = resolveInstalledSpecVersion();
206+
const current = currentSurfaceFloor();
207+
// Resolvable, and never a major's first release (a non-zero patch).
208+
expect(current).toMatch(/^\d+\.\d+\.[1-9]\d*$/);
176209
const plugin = newPlugin();
177210
const ctx = fakeCtx();
178211
const fixture = loadFixture(); // untouched: 150 × the emitted default
179212
const before = loadFixture();
180-
fixture.manifest.engines.protocol = `^${installed}`;
213+
fixture.manifest.engines.protocol = `^${current}`;
181214

182215
const total = await plugin._parseAndRegisterArtifact(ctx, fixture, 'fixture-current-residue');
183216
expect(total).toBe(before.permissions.length);

0 commit comments

Comments
 (0)