Skip to content

Commit 5a6267f

Browse files
feat(qa): os test prints suite and scenario names and selects scenarios by --tags (#20341)
Part of #20289 Clause-②: no ## Rework round 1 The seat's order of record is comment `5861125164` on #20289, which also amended the claim's file surface to these three files, text only. This PR had made three texts false or stale. Commit `5d31b4d587` corrects them. It is a new commit on top of `dd6b472c63`, with no rebase, amend or force-push. 1. **`docs/qa/platform-checklist/areas/cli.json`, item `cli.qa-suite-execution`:** - The knownGap that read "no scenario SELECTION exists: `os test` has exactly three flags …" now says what stays true. `--tags` selects scenarios (any-of, exact, case-sensitive). Deselected scenarios are counted and never passed. `requires` is declared but NOT CHECKED, pending the maintainer's decision on #20289. - The item's `source` note on `qa.json#tags` said the same false thing ("its dead `tags`/`requires` rows are why no scenario selection exists"), so it is corrected in the same edit. - `revision` goes from 4 to 5 with a history entry, as the checklist's change rule requires. 2. **`content/docs/deployment/cli.mdx`, § `os test`:** - Documents `--tags`: comma list, any-of, exact and case-sensitive. Deselected scenarios are counted and never passed. A listed tag no loaded scenario carries is named, and an empty entry is refused. A selection that matches no scenario exits 0, or 1 under `--fail-on-empty`. - Documents the report lines: the suite heading `NAME (FILE)`, the scenario line `NAME [ID]`, and a failed scenario's description. - States that `requires` is not checked. - Adds one example line. The page agrees with `os test --help`, read at this head. 3. **`packages/spec/scripts/liveness/check-liveness.mts`, the `qa` paragraph of the header comment:** `suite.name` and `scenario.tags` now have readers in `os test`. `scenario.requires` is the one key still unread: declared, NOT CHECKED, and its ledger row stays dead. This round changes no code, and the `requires` question stays with the maintainer. **Verification at `5d31b4d587`:** - `dispatch-gates --repo objectstack-ai/objectstack --commands` was re-derived for the widened diff. It now names 118 families: the 114 of round 0 plus `check:cli-examples-parity`, `check:pm-dispatch-gates`, `check:pm-governed-merges` and `bare-root-worklist.mjs --self-test`. - The first pass ran while the fresh worktree's prerequisites were still being rebuilt. The gates that read built output answered exit 3 (or exit 1 for `check:dts-closure`, "declarations missing"). - A clean second pass ran every family except `check:pm-dispatch-gates` once the rebuild had finished. All 117 exited 0. `check:pm-dispatch-gates` reads no built output; it exited 0 on the first pass after 1270 s. - Reconciliation with `--ran` (every line is `COMMAND :: exit CODE`): `118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN`. - `check:platform-checklist`: `OK — 15 areas, 266 items`. - The edited script's own tests are the 8 files that name `check-liveness`, 6 of them beside it in `scripts/liveness/`. All passed: 8 files, 262 tests. `os test` now reports the suite and scenario names an author writes and selects scenarios with `--tags`. That makes four of the `qa-runner` family's five ledger keys live. The fifth, `TestScenario.requires`, is not guessed. This PR leaves it dead with an honest describe(), and the report on #20289 puts the question to the maintainer. The card stays open for that half. This PR does not deliver the "unmet `requires` is SKIPPED with its reason" leg or its pin. ## What changes - **`@objectstack/core`:** `QA.TestResult` gains `scenarioName` and `description` on both result envelopes: the completed run and the setup-failure early return. It also gains `suiteName` on every result that `runSuite` produces. `suiteName` is absent only from a lone `runScenario` call, which has no suite. - **`@objectstack/cli` (`os test`):** - The suite heading is `📄 Running suite: NAME (FILE)`. A file that fails to load is still headed by the file alone. - Each scenario line is `Scenario: NAME [ID] (Nms)`, and shows the id once when the name and id are equal. - A failed scenario prints its `description` under its line. - A new `--tags TAG[,TAG...]` flag. Deselected scenarios are counted on the summary and never counted as passed. A requested tag that no loaded scenario carries is named there. An empty entry is refused at parse time. - **`@objectstack/spec`:** only the describe() of `requires`, `requires.params` and `requires.plugins` changed. They now say NOT CHECKED instead of reading as a guard, and the reference page was regenerated. In `liveness/qa.json`, `name`, `scenarios.name`, `scenarios.description` and `scenarios.tags` move to `live` and cite their readers as `file#symbol`. `tags` also cites a `producer`, the `--tags` value. `requires` stays `dead` with a re-measured note. The `state-counts.md` row moved from live 4 / dead 5 to live 8 / dead 1, and the README row was rewritten to match. ## `--tags` semantics: any-of, exact, case-sensitive A comma list selects a scenario that carries at least one listed tag. Why this form: - It is the reading of a comma list in Odoo's `--test-tags`, a metadata platform, and in Cucumber's comma form. - It is the everyday use of Playwright's `--grep "@smoke|@critical"`: name the sets you want and get their union. - An all-of form or an expression language (Cucumber tag expressions, pytest `-m`) is a grammar to specify, parse and report errors against. No suite has needed one. The only suite in this repo, `examples/app-showcase/qa/platform-smoke.test.json`, tags one scenario `smoke, crud`. Matching is exact and case-sensitive, because a tag is a name the author chose, not a pattern. With the flag, an untagged scenario is never selected. Without the flag, nothing changes: the selection is not consulted and every scenario runs. The help text states all of this. ## Exit status, and the convention it follows The repo's `os test` convention is that a run that executed nothing is not a failure by default, and `--fail-on-empty` is the opt-in strict reading. It is stated in `Test.description` in `packages/cli/src/commands/test.ts` and in `content/docs/deployment/cli.mdx`, § `os test`: "A pattern that matches no suite is not a failure by default". A `--tags` selection that matches no scenario takes that same posture: - It prints `No scenario matched --tags X.` and exits `0`. - Under `--fail-on-empty` it exits `1`. The flag's description now names both cases. A renamed tag in a CI step is the same trap as a renamed directory, and it gets the same opt-out. The `Found N test suites.`, `SUCCESS: All N scenarios passed.` and `FAILED: N scenarios failed. M passed.` lines keep their spelling. The platform checklist item for `os test` reads the latter two. ## Measured before and after The target was a stub HTTP server that answers every request with 200. The suite had four scenarios: - one tagged `smoke`; - one tagged `regression`; - one declaring `requires: { plugins: ["plugin-that-does-not-exist"], params: ["OS_TEST_PARAM_THAT_IS_NOT_SET"] }`; - one plain control. | probe | base `10ea9eb2ed` | this branch | |:--|:--|:--| | suite heading | `📄 Running suite: probe.test.json` | `📄 Running suite: Probe suite: names, tags and requires (probe.test.json)` | | scenario line | `✅ Scenario: s-tagged-smoke (28ms)`: the id only (control: the id is printed) | `✅ Scenario: A scenario tagged smoke [s-tagged-smoke] (23ms)` | | `--tags smoke` | exit 2, `Nonexistent flag: --tags` (control: `--fail-on-empty` parses, exit 0) | exit 0; 1 scenario run; `--tags smoke selected 1 of 4 scenarios; 3 deselected (not run, not counted as passed).` | | unmet `requires` | `✅` PASSED, same as the no-requirements control | unchanged, deliberately: see the next section | ## `requires`: why it is left dead and put to a decision The dispatch rule was: "A sub-key you cannot judge honestly is a `needs_decision`, and ⛔ you don't guess." Neither sub-key can be judged honestly today: - **`plugins`:** `os test` reaches the target only over HTTP, through `HttpTestAdapter`. No server surface lists the loaded plugins. The discovery document (`DiscoverySchema`) advertises `services` and `capabilities`, not plugins. `/.well-known/objectstack` does not list them either. `kernel.getPlugins()` is in-process only. The spelling is also undefined: the npm package name or `plugin.name`. - **`params`:** it is undefined whose environment is meant. The runner's `process.env` is observable, but no step can consume a runner variable, because `resolveVariables` interpolates only captured context. The server's environment is not observable at all. The decision is posted on #20289 with its options and the four-axis analysis. Until it is taken, the describe() says NOT CHECKED rather than implying a guard. The PR contains no speculative SKIPPED machinery, because every enforce option needs it and the retire option would leave it dead. ## Verification Every run below is at `dd6b472c63`, the final commit. - **core:** - `pnpm --filter @objectstack/core test`: 56 files and 1483 tests passed. `src/qa/runner.test.ts` has 20 tests, 3 of them new: names on every result, names on the setup-failure envelope, and a lone `runScenario` with no suite. - `pnpm --filter @objectstack/core typecheck`: exit 0. - **cli:** - `vitest run --project unit`: 227 of 230 files passed on the first run. Two files failed because `packages/cli` was not built yet. One file, `hook-timeout-override-refusal.test.ts`, timed out twice at 5000 ms under an 861 s shared-box run. After the CLI build, those 3 files passed 33 of 33 on a re-run. - The new `test/qa-tags-selection.test.ts` (unit) has 13 tests, including the unfiltered CONTROL. - The new `test/qa-names-and-tags-run.test.ts` (integration: it spawns `bin/run-dev.js` against a `node:http` stub) has 8 tests. They pin the names printed, `--tags smoke` exiting 0 because the failing scenario never ran, the unfiltered control exiting 1, a no-match selection exiting 0 by default and 1 under `--fail-on-empty`, and a malformed list refused before anything runs. - `pnpm --filter @objectstack/cli typecheck`: exit 0. - **spec:** - `vitest run src/qa`: 26 tests passed. - `check:generated`: `check:docs` was the one stale artifact. `--fix` regenerated it, and it is clean on re-check. - `check:liveness`: green, with `qa 9 classified (live 8, dead 1)`. - **Ablation (one leg, `node scripts/ablation-replace.mjs`, WRAP mode):** - The mutation was ` return hits.length > 0;` → ` return true;` in `selectScenariosByTags`. The tool reported `anchor 1 -> 0`, and the blob went from `774d2436cf4f` to `cab531acb335`. That is the only mutation. - Unit: 3 red out of 13. They were the any-of, exact-match and no-match pins. The CONTROL stayed green. - Integration: 3 red out of 8. They were `--tags smoke`, `--tags regression` and no-match. The unfiltered control and the names pins stayed green. - The direction was the expected one: the tests went red. - Restore: the blob after restore equals HEAD (`774d2436cf4f`), and `git diff HEAD` is empty. - The subject is imported through a relative path to `src/`, so no `dist/` leg applies. - **Gates:** there are 114 derived commands (`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`). - 108 exited 0 on the first pass. - 6 answered PREREQUISITE NOT MET (exit 3), which means not measured. After the missing builds, they were re-run: all 6 exited 0. They were `check:skill-examples`, `check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage`, `check:i18n-walk-parity` and `check:type-check-debt` (the last reported `--re-measure: OK … none above its recorded number`). The first pass of `check:type-check-debt` had died in its own internal build: `plugin-pinyin-search` could not find `@objectstack/objectql` declarations while a prerequisite build was rewriting `dist/`. The gate labelled that pass not measured itself. - Reconciliation with `--ran` (every line is `COMMAND :: exit CODE`): `114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN`. - **Lint, measured narrowly:** - `pnpm exec eslint --no-inline-config --format json` over the 6 changed TypeScript files: 6 results, 0 errors, 0 warnings. - The population comes from eslint's own config: `isPathIgnored` is false for all 6, and the 5 changed JSON, Markdown and MDX files are ignored. - Invariance: this repo's config enables no type-aware linting. `parserOptions.project` and `projectService` resolve to null for all 6, so this diff cannot move any untouched file's verdict. ## Acceptance notes - **The three texts that were outside the claimed file surface in round 0** (`cli.mdx`, the checklist knownGap and the checker's comment) are corrected in Rework round 1 above, after the seat amended the surface in `5861125164`. - **Semver:** - `@objectstack/cli` is `minor`, for the new flag. - `@objectstack/core` is also `minor`, not `patch`. `QA.TestResult` gains three public fields, which is an additive API that programmatic consumers can read. - `@objectstack/spec` is `patch`, because only describe text and ledger data changed. - All three are in the same changesets `fixed` group. - **Branch base:** `origin/main` at `d498113b50` is merged in with `scripts/pm/os-regen-merge.sh`, as merge commit `9056455134`. The one conflict was `packages/spec/liveness/README.md`: main's `translation` and `rest_api` rows and this PR's `qa` row are all kept. The generated `state-counts.md` was regenerated, not hand-resolved, in `76e9a48821`: `qa` is 8/1 on top of main's totals. Against that merge, `git diff d498113 HEAD` touches only this PR's 14 files. --- _Generated by [Claude Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a88a1bb commit 5a6267f

14 files changed

Lines changed: 624 additions & 42 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/core': minor
4+
'@objectstack/spec': patch
5+
---
6+
7+
`os test` reports the suite and scenario names an author writes, and selects scenarios with `--tags` (#20289)
8+
9+
Clause-②: no
10+
11+
A Quality Protocol suite's `name`, each scenario's `name` and `description`, and scenario `tags` were parsed at load and then read by nothing: the report headed each suite with its file's basename, printed every scenario by its `id`, and `os test --tags critical` failed with `Nonexistent flag: --tags`.
12+
13+
- **Names in the report.** The suite heading is now the suite's `name` followed by its file — `📄 Running suite: Accounts smoke (accounts.test.json)` — and each scenario line is its `name` with the `id` in brackets — `✅ Scenario: An account can be created [acct-create] (12ms)` (the id alone when the two are equal). A failed scenario's `description` is printed under its line, before the error. A suite whose file fails to load is still headed by the file alone, since no name was parsed.
14+
- **`--tags TAG[,TAG...]`** runs only the scenarios carrying AT LEAST ONE of the listed tags (any-of, exact, case-sensitive) — the comma-list reading of Odoo's `--test-tags` and the everyday use of Playwright's `--grep @a|@b`. With the flag, an untagged scenario is left out. Left-out scenarios are **deselected**: not run, counted on the summary (`--tags smoke selected 1 of 4 scenarios; 3 deselected (not run, not counted as passed).`), never counted as passed. A requested tag that no loaded scenario carries is named on the summary. An empty entry (`--tags smoke,`) is refused before anything runs. Without the flag nothing changes: every scenario runs.
15+
- **Exit status.** A selection that matches no scenario takes the posture an empty pattern already has: exit `0` with `No scenario matched --tags …`, and exit `1` under `--fail-on-empty`, whose description now covers both cases. The `Found N test suites.` line and the `SUCCESS: All N scenarios passed.` / `FAILED: …` summary lines keep their spelling.
16+
- **`@objectstack/core`:** `QA.TestResult` gains `scenarioName` and `description` on every result, and `suiteName` on every result `runSuite` produces (absent only from a lone `runScenario` call, which has no suite).
17+
- **`@objectstack/spec`:** `TestScenario.requires` (`params`, `plugins`) is still checked by nothing — its describe() now says **NOT CHECKED** instead of reading as a guard, so a scenario that declares a plugin the target lacks still runs, and the unmet requirement surfaces only as whatever failure it causes, if any. The liveness ledger (`liveness/qa.json`) moves the four keys above to `live`, citing their readers.

‎content/docs/deployment/cli.mdx‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,6 +1651,7 @@ os test --url http://localhost:4000 # Custom server URL
16511651
os test --token my-api-key # With authentication
16521652
os test 'qa/**/*.test.json' # Recursive — quote it, or the shell expands it first
16531653
os test --fail-on-empty # Matching no suite is a failure, not a pass
1654+
os test --tags smoke,critical # Only scenarios tagged smoke OR critical
16541655
```
16551656
16561657
The pattern accepts `*` (one path segment) and `**` (any number of segments);
@@ -1677,6 +1678,30 @@ switch and reported ✅, so a `contains` against a missing path was a test that
16771678
silently deleted itself. Assert absence with `is_null`; compare a scalar with
16781679
`equals`.
16791680
1681+
The report prints the **names the suite author wrote**. Each suite is headed by
1682+
its `name` and the file it was loaded from —
1683+
`📄 Running suite: Accounts smoke (accounts.test.json)` — and each scenario line
1684+
leads with the scenario's `name` and carries its `id` in brackets:
1685+
`✅ Scenario: An account can be created [acct-create] (12ms)`, or the id alone when
1686+
the two are equal. A failed scenario also prints its `description` under its line,
1687+
before the error, so a red run says what the scenario was checking. A file that is
1688+
refused at load time is headed by its file name alone, because no suite name was
1689+
ever parsed from it.
1690+
1691+
**`--tags` selects scenarios by their `tags`.** Pass a comma-separated list: a
1692+
scenario runs when it carries **at least one** of the listed tags (any-of), so
1693+
`--tags smoke,critical` runs everything tagged `smoke` or `critical`. Matching is
1694+
exact and case-sensitive — a tag is a name, not a pattern — and while the flag is
1695+
given an untagged scenario is never selected. The scenarios the selection leaves
1696+
out are **deselected**: not run, counted on the summary
1697+
(`--tags smoke selected 1 of 4 scenarios; 3 deselected (not run, not counted as passed).`),
1698+
and never counted as passed. A listed tag that no loaded scenario carries is named
1699+
on the summary, since a typo narrows the run without failing it, and an empty entry
1700+
(`--tags smoke,`) is refused before anything runs. Without the flag every scenario
1701+
runs. A scenario's `requires` block is **not checked**: a scenario that names a
1702+
plugin the target does not load still runs, and the unmet requirement surfaces
1703+
only as whatever failure it causes, if any — the scenario can still pass.
1704+
16801705
**A pattern that matches no suite is not a failure by default.** The run prints
16811706
`Found 0 test suites.` — the same machine-readable line a full run prints, so a
16821707
caller can tell "every suite passed" from "there were no suites" — and exits
@@ -1685,6 +1710,10 @@ build. That is a posture, not an oversight, and it has the cost you would expect
16851710
a CI step whose glob stops matching (a renamed directory, a moved suite) reports
16861711
success forever. Pass **`--fail-on-empty`** to opt into the strict reading, where
16871712
an empty match exits 1 (#7848).
1713+
A `--tags` selection that matches no scenario takes the same posture: it prints
1714+
`No scenario matched --tags nightly.` and exits **0**, and **`--fail-on-empty`**
1715+
makes it exit 1 — a renamed tag in a CI step is the same trap as a renamed
1716+
directory.
16881717
16891718
The **record-shaped** action types — `create_record`, `read_record`,
16901719
`update_record`, `delete_record`, `query_records` — **ask the server where the

‎content/docs/references/qa/testing.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ A complete test scenario with setup, execution steps, and teardown
115115
| **setup** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to run before main test (preconditions) |
116116
| **steps** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | ✅ | Main test sequence to execute |
117117
| **teardown** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to cleanup after test execution |
118-
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario |
118+
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario. NOT CHECKED by `os test` or the core TestRunner: the scenario runs whether or not they hold, and an unmet requirement surfaces only as the failure it causes |
119119

120120
### Nested Shape: `TestScenario.setup[number]`
121121

@@ -157,8 +157,8 @@ A single step in a test scenario, consisting of an action and optional assertion
157157

158158
| Property | Type | Required | Description |
159159
| :--- | :--- | :--- | :--- |
160-
| **params** | `string[]` | optional | Required environment variables or parameters |
161-
| **plugins** | `string[]` | optional | Required plugins that must be loaded |
160+
| **params** | `string[]` | optional | Environment variables or parameters the scenario needs. Declared only: nothing checks them before the scenario runs |
161+
| **plugins** | `string[]` | optional | Plugins the scenario needs loaded on the target. Declared only: nothing checks them before the scenario runs |
162162

163163

164164
---
@@ -223,7 +223,7 @@ A complete test scenario with setup, execution steps, and teardown
223223
| **setup** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to run before main test (preconditions) |
224224
| **steps** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | ✅ | Main test sequence to execute |
225225
| **teardown** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to cleanup after test execution |
226-
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario |
226+
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario. NOT CHECKED by `os test` or the core TestRunner: the scenario runs whether or not they hold, and an unmet requirement surfaces only as the failure it causes |
227227

228228

229229
---

‎docs/qa/platform-checklist/areas/cli.json‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@
438438
"title": "os test: a Quality Protocol suite is validated at LOAD, executed against a booted app, and its verdict is the exit code — capture/interpolation thread state, an unevaluable assertion FAILS",
439439
"since": "v17",
440440
"status": "active",
441-
"revision": 4,
441+
"revision": 5,
442442
"priority": "P1",
443443
"surface": "cli",
444444
"personas": ["operator (local shell)", "suite author (writes qa/*.test.json)"],
@@ -453,7 +453,7 @@
453453
"knownGaps": [
454454
"`run_script` has no adapter branch and fails by name (`Unsupported action type in HttpAdapter: run_script`) — the variant sweep records ONE refusal and that is the honest verdict, not a fixture gap to work around. The other seven members execute since #7848; the five record-shaped ones did not until then (see `negative`), so a sweep transcript predating that fix shows five 404s and is not comparable",
455455
"the record action types resolve the Data Protocol mount from the server's own `/discovery` (`routes.data`) since #7983, so a deployment that sets `crud.dataPrefix` IS reached; what remains out of reach is `api.apiPath`, which moves the discovery document itself out from under the probe (measured: `{apiBase}/discovery` 404s, and `/.well-known/objectstack` reports the DISPATCHER's `/api/v1/data`, not the REST mount). Against such a host the adapter falls back to the convention and says so — it names the mount it addressed, the probe that failed and the remedy, on the warning AND on every 404 — and those steps must still be written as `api_call`. The fixture boot is stock, so neither case bites here",
456-
"no scenario SELECTION exists: `os test` has exactly three flags (--url, --token, --fail-on-empty — commands/test.ts), none of which selects scenarios, `scenario.tags` filters nothing and `scenario.requires` is never checked (packages/spec/liveness/qa.json rows `tags`/`requires`), so the whole glob always runs and a suite cannot declare a precondition it will be skipped for"
456+
"scenario SELECTION is by tag only, and preconditions are NOT honoured: `os test --tags a,b` runs the scenarios carrying at least one listed tag (any-of, exact, case-sensitive — commands/test.ts `selectScenariosByTags`), counts the rest as deselected — not run, never counted as passed — and without the flag the whole glob runs; `scenario.requires` (`params`/`plugins`) is declared but NOT CHECKED — its describe() says so and its row in packages/spec/liveness/qa.json stays `dead` pending the maintainer's decision on #20289 — so a suite still cannot declare a precondition it will be skipped for, and a scenario naming a plugin the target lacks still runs, the unmet requirement surfacing only as whatever failure it causes, if any"
457457
]
458458
},
459459
"steps": [
@@ -548,7 +548,7 @@
548548
"packages/core/src/qa/runner.ts#scenario (scenario sequencing, `capture` + `{{var}}` interpolation, the assertion operators, setup/teardown semantics, the #7256 unevaluable-`contains` fix)",
549549
"packages/core/src/qa/http-adapter.ts#mount (the action-type switch — its case labels ARE the enum values; the record routes take their prefix from the one memoised `/discovery` probe per run, falling back to the RestApiConfigSchema + CrudEndpointsConfigSchema convention with a diagnostic that names the mount, #7848 / #7983)",
550550
"packages/spec/src/qa/testing.zod.ts#TestSuiteSchema (TestSuiteSchema — the shape enforced at load; TestActionTypeSchema pinned above)",
551-
"packages/spec/liveness/qa.json#tags (the ADR-0049 ledger whose existence this item is coverage.json's mapping for — its dead `tags`/`requires` rows are why no scenario selection exists)",
551+
"packages/spec/liveness/qa.json#tags (the ADR-0049 ledger whose existence this item is coverage.json's mapping for — its `tags` row is live on `os test --tags`, and its `requires` row stays dead: the precondition is declared and not checked)",
552552
"packages/cli/test/qa-suite-schema-load.test.ts, packages/cli/test/resolve-glob-lazy-walk.test.ts, packages/core/src/qa/runner.test.ts (the three unit pins — cited so a run knows what is already covered, NOT a substitute for driving a booted app)",
553553
"content/docs/deployment/cli.mdx §os test (the documented command contract)",
554554
"examples/app-showcase/qa/platform-smoke.test.json (the fixture suite this item drives)"
@@ -557,7 +557,8 @@
557557
{ "revision": 1, "date": "2026-08-11", "change": "new item: the `qa` capability's coverage.json mapping, authored rather than waived (#7347 triage ruling). `os test` is a shipped, documented CLI command, so the honest mapping is a surface:cli item that authors a real qa/*.test.json suite and drives it against a booted app — the fixture suite examples/app-showcase/qa/platform-smoke.test.json lands with this item and is the repo's first Quality Protocol suite. Every clause was measured on showcase before it was written: the green path, capture/interpolation, the #6247 load refusal, the #7256 unevaluable-contains failure, teardown-after-failure, the 8-member action-type sweep and the #7363 glob. `since: v17` records the release in which the surface became GOVERNED (liveness ledger seeded + TestSuiteSchema enforced at the load site, #6247 / PR #7255); the command itself predates it. No `automated` entry: the three unit pins cover pieces, none of them proves a suite reaches a real server", "ref": "#7347" },
558558
{"revision": 2, "date": "2026-08-12", "change": "the adapter was repaired, which this item's own `negative` clause declared to be a revision rather than a silent green (#7848). Item 1: the five record-shaped action types now round-trip against a stock server — the `${baseUrl}/api/data/:object` literal became a prefix DERIVED from the two schemas RestServer itself resolves from (RestApiConfigSchema `apiPath ?? {basePath}/{version}` + CrudEndpointsConfigSchema.dataPrefix), and `update_record` PATCHes where it used to PUT a route that has no PUT sibling. Re-measured on a booted showcase, one scenario per member with NO shared setup so no member's verdict is inferred from a sibling: 7 of 8 execute and assert, `run_script` still refuses by name. The `negative` clause is inverted accordingly — a 404 from a record action type is the regression now — and the knownGap it rested on is replaced by the narrower one that survives: the record types address the DEFAULT mount only, so a host that moves it with `api.apiPath`/`crud.dataPrefix` still needs `api_call`. Item 2: a zero-match glob still exits 0, deliberately (a repo that legitimately ships no suites must not start failing CI), but the posture is now DECLARED — stated in `--help`, opt out with the new `--fail-on-empty`, and `Found N test suites.` is emitted on EVERY run including `Found 0 test suites.`, which is the line this item's first acceptance clause already asks a run record to quote", "ref": "#7848"},
559559
{"revision": 3, "date": "2026-08-17", "change": "the record action types stopped ASSUMING the mount (#7983). They now resolve it from the server: one memoised `GET {apiBase}/discovery` per run, addressing whatever `routes.data` advertises, with the RestApiConfigSchema + CrudEndpointsConfigSchema convention as the fallback — the `@objectstack/client` `getRoute` pattern, copied rather than re-invented. Measured on a booted stack (REST generator + dispatcher bridge) before and after, three configs: stock stays green; `crud.dataPrefix: '/objects'` went from `HTTP Error 404` to a created record, so that row of the gap is CLOSED; `api.apiPath: '/api/2026-01'` still 404s and is NOT closed — `apiPath` moves the discovery document itself, and the one fixed-path document (`/.well-known/objectstack`) advertises the dispatcher's `/api/v1/data` under all three configs, so trusting it would attach a false provenance to the same 404. That row is narrowed instead: the fallback is announced (a warning naming the mount, the failed probe and the remedy) and every 404 from a record action now carries the mount it addressed and where that mount came from, so the failure can no longer read as the suite author's own URL mistake. `api_call` is unchanged and probes nothing — it remains the escape hatch for a host the probe cannot reach", "ref": "#7983"},
560-
{ "revision": 4, "date": "2026-08-18", "change": "corrected the flag count from two to three. packages/cli/src/commands/test.ts declares url, token AND fail-on-empty; the same revision's own acceptance already describes --fail-on-empty, so the knownGap contradicted its own item. The gap's point is unchanged and now stated directly: none of the three flags selects scenarios (#9417)", "ref": "#9386" }
560+
{ "revision": 4, "date": "2026-08-18", "change": "corrected the flag count from two to three. packages/cli/src/commands/test.ts declares url, token AND fail-on-empty; the same revision's own acceptance already describes --fail-on-empty, so the knownGap contradicted its own item. The gap's point is unchanged and now stated directly: none of the three flags selects scenarios (#9417)", "ref": "#9386" },
561+
{ "revision": 5, "date": "2026-09-28", "change": "the knownGap and the qa.json source note were falsified by #20289 (PR #20341) and are corrected: `os test` gained `--tags` (any-of, exact, case-sensitive; deselected scenarios are counted and never passed; a selection that matches no scenario exits 0, or 1 under --fail-on-empty), and the report now heads each suite with its `name` and prints each scenario's `name` beside its id. What stays true is narrower: `scenario.requires` is declared but NOT CHECKED, pending the maintainer's decision on #20289. Steps and acceptance are unchanged — the green-path clause's scenarioId is still on every scenario line, now in brackets after the name, or bare when the name equals the id", "ref": "#20289" }
561562
]
562563
},
563564
{

0 commit comments

Comments
 (0)