Skip to content

Commit 3702f92

Browse files
baozhoutaoclaude
andauthored
fix(ci): align the live-e2e backend pin with the lockfile, and pin the rule that says it must (#7970)
* fix(ci): align live-e2e backend pin with the lockfile and pin the rule `e2e/live/ci/backend.env` pinned `OBJECTSTACK_VERSION=17.0.0-rc.2` while `pnpm-lock.yaml` resolved `@objectstack/spec` 17.2.0. Both the file's own header and `content/docs/guide/ci-cd-pipeline.md` state the two must match, and that a mismatched pair "proves nothing" -- so for two minor versions the `Live E2E (informational)` lane produced a green signal its own contract declares meaningless. Aligns the pair to the value the lockfile actually resolves (17.2.0, not the `^17.0.0` range) and moves `OBJECTSTACK_REF` with it, to the commit the `@objectstack/cli@17.2.0` release tag points at -- the pairing rule the file header already states, and the one the previous pin honoured for rc.2. Adds the missing comparison to `scripts/__tests__/ci-cd-pipeline-doc.test.ts`, whose charter is pinning that page's claims to CI reality: it reads `OBJECTSTACK_VERSION`, reads every `@objectstack/spec` version the lockfile resolves (two independent readings, union must be one), and requires equality, with both anti-vacuity floors the file's culture asks for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M * docs(e2e): name the live-lane backend pin by file, not by version literal `e2e/live/saved-view-filter.spec.ts`'s header stated the measurement was taken on `@objectstack/*@17.0.0-rc.2` -- the pair the previous commit just moved off. It was the second reader of that literal on the fenced surfaces, and it went stale the moment the pin did. Names `OBJECTSTACK_VERSION` / `OBJECTSTACK_REF` in `e2e/live/ci/backend.env` instead, and says the pair has moved since the measurement rather than implying it has not. A comment that carries the value is a copy of the pin that nothing updates; a comment that carries the file name cannot go stale behind it. Comment only -- no assertion, import or code line is touched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ed5fee9 commit 3702f92

3 files changed

Lines changed: 189 additions & 4 deletions

File tree

‎e2e/live/ci/backend.env‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77
# "this console x that published backend" as a matched pair. Testing a
88
# mismatched pair (backend rc.N vs console pinned rc.M) proves nothing —
99
# bump this in the same PR that bumps the console's @objectstack pins.
10+
# Pinned by scripts/__tests__/ci-cd-pipeline-doc.test.ts, which reads this
11+
# value and the version pnpm-lock.yaml resolves and requires them equal. It
12+
# had drifted two minor versions (17.0.0-rc.2 against a 17.2.0 lockfile)
13+
# before anything compared the two numbers — objectui#7689.
1014
#
1115
# OBJECTSTACK_REF — the objectstack-ai/objectstack commit the showcase app
1216
# metadata is checked out from. Always the commit the release tag
1317
# `@objectstack/cli@${OBJECTSTACK_VERSION}` points at, so the app source and
1418
# the published packages it runs on come from the same tree. (Mirror image
1519
# of the framework repo's `.objectui-sha` console pin.)
16-
OBJECTSTACK_VERSION=17.0.0-rc.2
17-
OBJECTSTACK_REF=89d2a4eb3f3b6b8f8c0fbc4cb3953cbe8218dc66
20+
# NOT machine-checked, and deliberately so: resolving that tag needs the
21+
# objectstack repository, which the unit lane cannot reach. Move this by
22+
# hand whenever the version above moves, from the tag's own commit.
23+
OBJECTSTACK_VERSION=17.2.0
24+
OBJECTSTACK_REF=e7d2cc67fdef7fee9d2c6d65d7363fe1c78ce6a4

‎e2e/live/saved-view-filter.spec.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ import { test, expect } from '@playwright/test';
66
*
77
* This is the empirical pin for the defect, run against the REAL stack because
88
* only the server can answer the question the issue asked: does it accept a
9-
* rule array in `$filter`? It does not. Measured on
10-
* `@objectstack/*@17.0.0-rc.2` with the showcase app:
9+
* rule array in `$filter`? It does not. Measured against the published backend
10+
* pair this lane pins — `OBJECTSTACK_VERSION` and `OBJECTSTACK_REF` in
11+
* `e2e/live/ci/backend.env`, which move together and are now held to the
12+
* lockfile by `scripts/__tests__/ci-cd-pipeline-doc.test.ts` (objectui#7689).
13+
* That pair has moved since this measurement was first taken, which is why it
14+
* is named by FILE and not by value: a version literal written here goes stale
15+
* behind the pin silently, and the whole point of objectui#7689 is that it did
16+
* — for two minor versions, in the pin itself. The showcase app is the one that
17+
* ref checks out:
1118
*
1219
* GET /api/v1/data/showcase_task
1320
* ?$filter=[{"field":"status","operator":"equals","value":"in_progress"}]

‎scripts/__tests__/ci-cd-pipeline-doc.test.ts‎

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,3 +1263,174 @@ describe('ci-cd-pipeline.md — contexts that can never be required (#4170)', ()
12631263
).toEqual([]);
12641264
});
12651265
});
1266+
1267+
/**
1268+
* objectui#7689 — the page's live-e2e section states a pin rule that nothing read.
1269+
*
1270+
* `content/docs/guide/ci-cd-pipeline.md` says the backend pins "must match the
1271+
* `@objectstack/spec` version in `pnpm-lock.yaml` — bump both in the same PR, or the
1272+
* run proves nothing", and `e2e/live/ci/backend.env` repeats the same MUST in its own
1273+
* header. Both were true statements about what a reader should do and false statements
1274+
* about what CI checked: `git grep -l backend.env scripts/ .github/ e2e/` reached only
1275+
* the workflow that caches on its hash and the script that sources it. Nothing compared
1276+
* the two numbers, so the pin sat at `17.0.0-rc.2` against a lockfile resolving `17.2.0`
1277+
* for two minor versions while `Live E2E (informational)` reported green.
1278+
*
1279+
* That is the shape this file exists for, and the worst instance of it yet. The size
1280+
* regime in #3197 advertised a guardrail CI did not have; here the lane's OWN contract
1281+
* says a mismatched pair "proves nothing", so every green run it produced over those two
1282+
* minors was a check whose documentation declared it meaningless. A stale constant is a
1283+
* chore; a green light its own spec disclaims is worse than a red one.
1284+
*
1285+
* ## What this asserts, and the one half it deliberately does not
1286+
*
1287+
* ASSERTED: `OBJECTSTACK_VERSION` equals the `@objectstack/spec` version the lockfile
1288+
* resolves, read two independent ways (the resolution keys under `packages:`/`snapshots:`
1289+
* and the `version:` line of every workspace importer) whose union must hold exactly one
1290+
* value. Two readings rather than one because "the resolved version" is only a
1291+
* well-formed question while the tree agrees with itself: a lockfile carrying two
1292+
* `@objectstack/spec` versions has no single number for the pin to match, and naming that
1293+
* is more useful than picking one of them and comparing to it.
1294+
*
1295+
* NOT asserted: the file's other pin, `OBJECTSTACK_REF`, whose stated rule is that it is
1296+
* the commit the `@objectstack/cli@${OBJECTSTACK_VERSION}` release tag points at. Reading
1297+
* that tag needs the objectstack repository over the network, which this lane has not got,
1298+
* so the pairing moves by hand and this file says so rather than implying coverage it has
1299+
* not got. What IS checked is the shape a hand move can still get wrong in a way the lane
1300+
* only discovers 300 seconds later: `start-backend.sh` fetches the ref with
1301+
* `git fetch --depth 1 origin "$OBJECTSTACK_REF"`, which needs a full object name.
1302+
*
1303+
* ## Anti-vacuity
1304+
*
1305+
* Each half is floored. The doc sentence is required to still be on the page, because
1306+
* this whole block is the enforcement of that sentence and a page that stopped making the
1307+
* claim should retire the pin deliberately rather than leave it running on prose nobody
1308+
* reads. The lockfile scan is required to have matched something, so a lockfile format
1309+
* change turns this red instead of green-with-an-empty-set — the failure mode #3451 keeps
1310+
* teaching this page.
1311+
*/
1312+
const backendEnvPath = path.join(repoRoot, 'e2e/live/ci/backend.env');
1313+
const lockfilePath = path.join(repoRoot, 'pnpm-lock.yaml');
1314+
const backendEnv = fs.readFileSync(backendEnvPath, 'utf8');
1315+
1316+
/** The page's statement of the rule, whitespace-normalised because the source wraps it. */
1317+
const PIN_RULE_SENTENCE =
1318+
/Backend pins live in `e2e\/live\/ci\/backend\.env` and must match the `@objectstack\/spec` version in `pnpm-lock\.yaml`/;
1319+
1320+
const readEnvKey = (key: string): string | null =>
1321+
backendEnv.match(new RegExp('^' + key + '=(.+)$', 'm'))?.[1].trim() ?? null;
1322+
1323+
/**
1324+
* Every `@objectstack/spec` version the lockfile resolves, from both spellings.
1325+
*
1326+
* The resolution keys carry the peer-suffixed identity (`17.2.0(ai@…)`) under
1327+
* `snapshots:` and the bare one under `packages:`; the importer entries carry the same
1328+
* value on their `version:` line. Each is truncated at the first `(` so the three
1329+
* spellings collapse to one comparable number.
1330+
*/
1331+
function resolvedSpecVersions(): string[] {
1332+
const lock = fs.readFileSync(lockfilePath, 'utf8');
1333+
const found = new Set<string>();
1334+
1335+
for (const m of lock.matchAll(/^\s{0,4}'?@objectstack\/spec@([0-9][^'(\s:]*)/gm)) {
1336+
found.add(m[1]);
1337+
}
1338+
for (const m of lock.matchAll(
1339+
/^\s*'@objectstack\/spec':\s*\n\s*specifier:.*\n\s*version:\s*([0-9][^\s(]*)/gm,
1340+
)) {
1341+
found.add(m[1]);
1342+
}
1343+
return [...found].sort();
1344+
}
1345+
1346+
describe('ci-cd-pipeline.md — live-e2e backend pin (#7689)', () => {
1347+
it('still carries the sentence this block enforces', () => {
1348+
expect(
1349+
PIN_RULE_SENTENCE.test(doc.replace(/\s+/g, ' ')),
1350+
'content/docs/guide/ci-cd-pipeline.md no longer states that the backend pins in ' +
1351+
'`e2e/live/ci/backend.env` must match the `@objectstack/spec` version in ' +
1352+
'`pnpm-lock.yaml`. Everything below is the enforcement of that sentence, so losing it ' +
1353+
'would leave these assertions running on a rule the page stopped teaching. If the rule ' +
1354+
'was retired, retire this describe with it; if the sentence merely moved or was ' +
1355+
'reworded, update PIN_RULE_SENTENCE.',
1356+
).toBe(true);
1357+
});
1358+
1359+
it('resolves exactly one @objectstack/spec version to compare against', () => {
1360+
const versions = resolvedSpecVersions();
1361+
1362+
expect(
1363+
versions.length,
1364+
`Nothing in ${path.relative(repoRoot, lockfilePath)} matched either reading of an ` +
1365+
'`@objectstack/spec` resolution. That is a green this test must never report: the ' +
1366+
'comparison below would run against an empty set and pass no matter what ' +
1367+
'`e2e/live/ci/backend.env` says. Either the dependency is genuinely gone — in which ' +
1368+
'case the live lane has nothing to pin and this block should go — or the lockfile ' +
1369+
'format moved and the two regexes above need updating.',
1370+
).toBeGreaterThan(0);
1371+
1372+
expect(
1373+
versions,
1374+
'The lockfile resolves more than one `@objectstack/spec` version:\n' +
1375+
versions.map((v) => ` - ${v}`).join('\n') +
1376+
'\n\nThere is then no single "the resolved version" for `OBJECTSTACK_VERSION` to ' +
1377+
'match, so the live lane cannot be a matched pair against any of them. Resolve the ' +
1378+
'workspace onto one version first; this test deliberately names the split rather than ' +
1379+
'picking a winner.',
1380+
).toHaveLength(1);
1381+
});
1382+
1383+
it('pins OBJECTSTACK_VERSION to the version the lockfile resolves', () => {
1384+
const versions = resolvedSpecVersions();
1385+
if (versions.length !== 1) return; // reported by the test above
1386+
1387+
const pinned = readEnvKey('OBJECTSTACK_VERSION');
1388+
expect(
1389+
pinned,
1390+
`${path.relative(repoRoot, backendEnvPath)} declares no OBJECTSTACK_VERSION. ` +
1391+
'`start-backend.sh` sources this file and installs published `@objectstack/*` at that ' +
1392+
'value, so an absent key is not a lighter failure than a wrong one.',
1393+
).not.toBeNull();
1394+
1395+
expect(
1396+
pinned,
1397+
`The live-e2e lane is pinned to an unmatched pair:\n` +
1398+
` ${path.relative(repoRoot, backendEnvPath)} OBJECTSTACK_VERSION=${pinned}\n` +
1399+
` ${path.relative(repoRoot, lockfilePath)} @objectstack/spec ${versions[0]}\n\n` +
1400+
'content/docs/guide/ci-cd-pipeline.md states the rule: "Backend pins live in ' +
1401+
'`e2e/live/ci/backend.env` and must match the `@objectstack/spec` version in ' +
1402+
'`pnpm-lock.yaml` — bump both in the same PR, or the run proves nothing." The header ' +
1403+
'of backend.env says the same thing in its own words. A run of `Live E2E ' +
1404+
'(informational)` against this pair therefore carries no information, green or red — ' +
1405+
'it exercises one published backend against a console built for another.\n\n' +
1406+
'Fix it in whichever direction the change came from: a lockfile bump must move ' +
1407+
'OBJECTSTACK_VERSION (and, by hand, OBJECTSTACK_REF to the commit the matching ' +
1408+
'`@objectstack/cli` release tag points at), and a pin bump must be a lockfile bump. ' +
1409+
'⛔ Do not resolve this by reverting the pin to whatever was green last: a failing ' +
1410+
'matched pair carries strictly more information than a green mismatched one ' +
1411+
'(objectui#7689).',
1412+
).toBe(versions[0]);
1413+
});
1414+
1415+
it('keeps OBJECTSTACK_REF in the one shape start-backend.sh can fetch', () => {
1416+
const ref = readEnvKey('OBJECTSTACK_REF');
1417+
expect(
1418+
ref,
1419+
`${path.relative(repoRoot, backendEnvPath)} declares no OBJECTSTACK_REF, which ` +
1420+
'`start-backend.sh` needs to sparse-checkout the showcase app.',
1421+
).not.toBeNull();
1422+
1423+
// Which COMMIT it should be is the half this file cannot read (see the header): that
1424+
// needs the objectstack release tag. The shape it must have is readable here, and it
1425+
// is the one a hand move gets wrong — `git fetch --depth 1 origin <ref>` wants a full
1426+
// object name, and an abbreviated one fails 300 seconds into the lane, in a log nobody
1427+
// reads until the job goes red.
1428+
expect(
1429+
ref,
1430+
`OBJECTSTACK_REF is ${JSON.stringify(ref)}. start-backend.sh fetches it with ` +
1431+
'`git fetch --depth 1 origin "$OBJECTSTACK_REF"`, which needs a full 40-character ' +
1432+
'commit sha — an abbreviated one, a branch name or a tag is refused by the remote and ' +
1433+
'the lane only says so once the backend fails to boot.',
1434+
).toMatch(/^[0-9a-f]{40}$/);
1435+
});
1436+
});

0 commit comments

Comments
 (0)