Skip to content

Commit dbb7be2

Browse files
test(cli): move the nightly-tier failure-warnings fixtures off the bare declared-index unique (#22242)
Fixes #22208 Clause-②: no Moves the two `@objectstack/cli` nightly-tier fixtures off the spelling that protocol 18 refuses. The refusal itself (PR #22103, `ef1fcb26a2`) is correct and is not touched here. Test-only: two files, +23 / -11, no assertion changed, and no test skipped, renamed or moved between tiers. ## What changed Both `--json` failure-warnings suites plant one advisory of each class and assert that each failure exit carries exactly the classes the run had computed. Their authoring-RULE advisory was a bare `unique: true` on a declared index. Since PR #22103 the schema parse refuses that spelling (`invalid_union` at `objects.0.indexes.0.unique`), so every fixture stopped at the parse exit and the payload carried none of the fields the tests read. | site (on `7b926f76`) | before | after | |:--|:--|:--| | `build-json-failure-warnings.e2e.test.ts:165` (shared `stack()`) | index `unique: true` | index `unique: 'global'` + field `title` `unique: true` | | `build-json-failure-warnings.e2e.test.ts:300` (3b `rulefail`) | index `unique: true` | index `unique: 'global'` + field `title` `unique: true` | | `validate-json-failure-warnings.e2e.test.ts:192` (shared `stack()`) | index `unique: true` | index `unique: 'global'` + field `title` `unique: true` | The new advisory is `unique/double-declaration` (ADR-0120 D5b): a field-level `unique: true` (per organization) beside a single-column declared index `unique: 'global'` on the same column. It is the shape PR #22103 already moved the two sibling parity suites to (`build-json-advisory-parity`, `build-json-undeclared-key-parity`). Why it is an authoring-RULE advisory and not a structural one: it is produced by the `lintUniqueDeclarations` entry of the author-time rule registry (`packages/lint/src/authoring-rules.ts`, tier `advisory`, commands `validate` + `build`). Its finding is `severity: 'warning'`, so `splitBySeverity` puts it in `ruleAdvisories` and never in the gating errors. It is a record with a non-`docs/` `rule` and no `token`, which is exactly what the suites' `ruleAdvisories` classifier selects. The structural advisory is a different list (`structuralWarnings`, a string computed last in `validate.ts`). Docblocks: the two fixture docblocks (`build :141`, `validate :168`) name the new advisory and say in one sentence why the old spelling left. The two `rulefail` comments (`build :291`, `validate :295`) name it too. No assertion named the bare-`unique` rule: every assertion reads the class COUNT (`rule: 1`), never the rule id. So no assertion line moves (`git diff -U0 | grep -c 'expect('` = 0). ## Readings (all on `origin/main` at the time; base `7b926f76`, then merged with `a87d8be2`) - **H1, reproduced before any edit.** `OS_TEST_TIERS=nightly`, `integration` project, the two files only, on `7b926f76`: `Tests 11 failed | 10 passed (21)`. The total is the same 11 as the card. Per file: build 6 failed / 5 passed (3f, 3e, 3c, 4b, catch-all, 3b red); validate 5 failed / 5 passed (rule errors, capability errors, doc errors, catch-all late, structural control red). - **H2, confirmed.** The validate structural control's own output quotes `` `indexes[].unique: true` was retired at protocol 18 (ADR-0120 D1) `` at path `objects / 0 / indexes / 0 / unique`. A direct `os build --json` probe of the 3b fixture on the base returned `{ success: false, errors: [invalid_union at objects.0.indexes.0.unique], warnings: [], conversions }`. That payload has no `error`, no `issues` and no advisory lists, which are the fields the ten other failures read as `undefined`. - **H3, used.** A probe on the new 3b fixture returned `error: 'author-time rules failed'`, `issues: ['expression-invalid']` and `warnings: [unique/double-declaration (warning)]`. The new validate control fixture reached `valid: true` with the rule record, the key string, the cap record and the structural string ("No apps or plugins defined"). - **H4, re-counted.** 80 nightly-tier files (`*.e2e.test.*`, `*.live.test.*`) on `7b926f76` have exactly three declared-index `unique: true` sites, the three above. The other three code hits are field-level `unique: true` (valid under ADR-0120 D1): `build-json-advisory-parity:149`, `build-json-undeclared-key-parity:141` and `:170`. The control spelling `unique: 'global'` hits 4 sites, so the grep sees. The card's own log names only `@objectstack/cli#test` red (59 of 60 tasks green). No other site found; nothing else is red for this cause. - **H5, done** as described above. ## Proof - **Fixed, nightly tier.** Same command on `e8d64c30` (fix) and again on `89d4dc47` (fix merged with `origin/main` `a87d8be2`): `Test Files 2 passed (2)` / `Tests 21 passed (21)`. All 21 tests are named in the verbose run, including the 11 that were red and the structural control. - **Per-PR tier untouched.** `vitest list --filesOnly` in `packages/cli`: with `OS_TEST_TIERS` unset and with `=queue`, 356 files and neither of these two; with `=nightly`, 80 files (the card's "(80)") and both, in `[integration]`. The diff touches no tier config, no file name and no `vitest-tiers.ts` input. - **Ablation** (`scripts/ablation-replace.mjs`, wrap mode, plus an outer EXIT/INT/TERM restore trap). The 3b `rulefail` fixture was put back to its base spelling: anchor x1 -> x0, replacement x0 -> x1, blob `7dfddae7` -> `bd478edf`, in-flight count base-spelling = 1. The build file then read `Tests 1 failed | 10 passed (11)`. The one red was 3b, `expected undefined to be 'author-time rules failed'`, the payload missing `error` again. Restore: blob `7dfddae7` == HEAD blob, `git diff HEAD` empty, index blob equal, porcelain empty. The direction matched the prediction written before the run. ## Local verification (final head `89d4dc47`) - `pnpm --filter @objectstack/cli typecheck`: exit 0. `check:test-typecheck` OK; `tsconfig.test.json` puts both files in its 1970-file program (`--listFiles`), and the 28 raw errors are the ledgered 28 in three other files, 0 in these two. - `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2`: `Test Files 263 passed (263)`, `Tests 3874 passed (3874)`. - Gates: the 50 commands `node scripts/pm/dispatch-gates.mjs --commands` derives for this change set are the same set as the dispatch order's list. 50 of 50 exit 0. `check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET: nine packages outside the cli closure had no `dist/`); after a full `pnpm build` (72/72) it exits 0. `dispatch-gates --ran`: `50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN`. - `pnpm lint` (full, `eslint . --no-inline-config`): exit 0. A JSON run over the two files reports 2 results, 0 errors, 0 warnings, and neither is ignored. - No changeset: test-only, nothing in any package's `files[]` moves. `skip-changeset` is the seat's to apply. - Not run locally, declared to CI: the rest of the `integration` project and the other nightly-tier files. Done for this card is the next `Nightly Tiers` run on a `main` that contains this change. ## Acceptance notes - The build file's fixture docblock still says "via `plantDocs`", a helper that does not exist in this file (docs are passed through `make(…, docs)`). This is pre-existing wording, kept as is because it is outside this card's surface. Noted, not filed. - `check-issue-citations` read 0 files for this diff: `packages/cli/test/**` is outside its declared surface, so it judged nothing about the `PR #22103` citation added in these docblocks, in either direction. - My first full `pnpm lint` crashed (exit 2, `ENOENT` on a transient `tsup.config.bundled_*.mjs`) because it ran beside my own concurrent `pnpm build`. That is a local race and not a lint verdict. The re-run after the build finished is the reading above. --- _Generated by [Claude Code](https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent ffb31fc commit dbb7be2

2 files changed

Lines changed: 23 additions & 11 deletions

File tree

‎packages/cli/test/build-json-failure-warnings.e2e.test.ts‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,16 @@ const PLANTED_KEY = 'zzzUndeclaredProbeKey';
140140

141141
/**
142142
* A stack raising ONE advisory of each of the four classes while parsing
143-
* cleanly: a bare `unique: true` index (authoring-RULE advisory), an unknown
144-
* `requires` token (#3366 capability hint), an undeclared key inside
143+
* cleanly: `title` unique twice — per organization on the field, `'global'` on
144+
* a declared index (authoring-RULE advisory, `unique/double-declaration`), an
145+
* unknown `requires` token (#3366 capability hint), an undeclared key inside
145146
* `visibleWhen` (#3786 finding), plus — via `plantDocs` — a doc whose `tags:`
146147
* scalar the reader cannot parse (ADR-0046 doc advisory).
147148
*
149+
* The rule advisory was a bare declared `unique: true` until protocol 18 refused
150+
* that spelling at the parse (ADR-0120 D7, PR #22103), which then stopped every
151+
* run at step 3, before the gate under test.
152+
*
148153
* The key sits inside `visibleWhen` rather than on the object or field itself:
149154
* an undeclared key in either of those positions has been a hard PARSE error
150155
* since #4001, which would stop the run at 3 and never reach the gate under
@@ -162,11 +167,12 @@ export default defineStack({
162167
name: '${ns}_ticket',
163168
label: 'Ticket',
164169
sharingModel: 'private',
165-
indexes: [{ name: '${ns}_title_idx', fields: ['title'], unique: true }],
170+
indexes: [{ name: '${ns}_title_idx', fields: ['title'], unique: 'global' }],
166171
fields: {
167172
title: {
168173
type: 'text',
169174
label: 'Title',
175+
unique: true,
170176
visibleWhen: { dialect: 'cel', source: 'true', ${PLANTED_KEY}: 1 },
171177
},
172178
},
@@ -289,16 +295,16 @@ export default defineStack({
289295
`);
290296

291297
// 3b — an author-time rule ERROR (a `record.<field>` that does not resolve),
292-
// raised alongside the bare-`unique` advisory.
298+
// raised alongside the `unique/double-declaration` advisory.
293299
make('rulefail', `
294300
import { defineStack } from '@objectstack/spec';
295301
296302
export default defineStack({
297303
manifest: { id: 'com.example.rfail', name: 'rfail', version: '1.0.0', type: 'app', namespace: 'rfail' },
298304
requires: ['${PLANTED_TOKEN}'],
299305
objects: [{ name: 'rf_ticket', label: 'Ticket', sharingModel: 'private',
300-
indexes: [{ name: 'rf_title_idx', fields: ['title'], unique: true }],
301-
fields: { title: { type: 'text', label: 'Title',
306+
indexes: [{ name: 'rf_title_idx', fields: ['title'], unique: 'global' }],
307+
fields: { title: { type: 'text', label: 'Title', unique: true,
302308
visibleWhen: { dialect: 'cel', source: 'record.zzz_no_such_field' } } } }],
303309
}, { strict: false });
304310
`);

‎packages/cli/test/validate-json-failure-warnings.e2e.test.ts‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,17 @@ function payloadOf(run: Run, label: string): Record<string, unknown> {
166166
}
167167

168168
/**
169-
* A stack raising ONE advisory of each class while parsing cleanly: a bare
170-
* `unique: true` index (authoring-RULE advisory), an unknown `requires` token
171-
* (#3366 capability hint), an undeclared key inside `visibleWhen` (#3786
169+
* A stack raising ONE advisory of each class while parsing cleanly: `title`
170+
* unique twice — per organization on the field, `'global'` on a declared index
171+
* (authoring-RULE advisory, `unique/double-declaration`), an unknown `requires`
172+
* token (#3366 capability hint), an undeclared key inside `visibleWhen` (#3786
172173
* finding), and — by declaring neither `apps` nor `plugins` — the structural
173174
* advisory that must NOT appear on any failure exit.
174175
*
176+
* The rule advisory was a bare declared `unique: true` until protocol 18 refused
177+
* that spelling at the parse (ADR-0120 D7, PR #22103), which then stopped every
178+
* run at the parse gate, before the gate under test.
179+
*
175180
* The key sits inside `visibleWhen` rather than on the object or field itself:
176181
* an undeclared key in either of those positions has been a hard PARSE error
177182
* since #4001, which would stop the run at the parse gate and never reach the
@@ -189,11 +194,12 @@ export default defineStack({
189194
name: '${ns}_ticket',
190195
label: 'Ticket',
191196
sharingModel: 'private',
192-
indexes: [{ name: '${ns}_title_idx', fields: ['title'], unique: true }],
197+
indexes: [{ name: '${ns}_title_idx', fields: ['title'], unique: 'global' }],
193198
fields: {
194199
title: {
195200
type: 'text',
196201
label: 'Title',
202+
unique: true,
197203
visibleWhen: { dialect: 'cel', source: 'true', ${PLANTED_KEY}: 1 },
198204
},${extraFields}
199205
},
@@ -293,7 +299,7 @@ beforeAll(() => {
293299
broken: { type: 'this_is_not_a_field_type', label: 'Broken' },`));
294300

295301
// rule errors — an expression naming a field that does not resolve, raised
296-
// alongside the bare-`unique` advisory.
302+
// alongside the `unique/double-declaration` advisory.
297303
make('rulefail', stack('rfail', [PLANTED_TOKEN], `
298304
subject: { type: 'text', label: 'Subject', visibleWhen: { dialect: 'cel', source: 'record.zzz_no_such_field' } },`));
299305

0 commit comments

Comments
 (0)