Repository navigation
Commit f397608
fix(cli)!: os verify runs the author-time rules first, and a stack they refuse fails verify with the findings os validate reports (#21364)
Fixes #21323
Clause-②: yes (narrowing)
`os verify` now runs the author-time rules first, through the pipeline
`os validate` uses, and a stack they refuse fails `verify` with the
findings `os validate` reports — before the runtime stage boots
anything. The narrowing: `verify` newly exits 1 on a stack the
author-time rule registry refuses (or that does not parse against the
protocol after lowering); `os build` already refuses every such stack.
The widening: `os verify --json` gains an `errors` key on that new
failure exit.
## What changed
- `packages/cli/src/utils/author-time-rules.ts` (new) —
`judgeAuthorTimeRules(command, config, configDir)`: the stage `os
validate` runs, step for step, through the same functions —
`normalizeStackInput`, `lowerCallables` before the parse,
`ObjectStackDefinitionSchema.safeParse`, `resolveJsxGateManifest` beside
the config, the union run over `authoringRuleUnionStack` tiers with
`stackFilterJudge` and the lowered hook refs, then
`runPerPackageAuthoringRules`. It names no rule and lists no rules:
which rules run is `authoringRulesFor(command)` in `@objectstack/lint`'s
one table. Prints nothing; returns `{ refusal, advisories }`.
- `packages/cli/src/commands/verify.ts` — stage 1 runs right after
`loadConfig`, before `bootStack`. Door: `VERIFY_RULE_COMMAND =
'validate'`.
- text face: a step line, then either `✓ Author-time rules passed (N
rules)` (plus an advisory count pointing at `os validate`) or `✗
Author-time rules failed (N issues) — the runtime stage did not run` and
each finding with its rule and location; exit 1.
- `--json`: the command's existing failure envelope (`error`, compact,
via `emitJson`) plus one new key, `errors`, carrying the findings in the
shape `os validate --json` carries them under `errors` (rule findings,
with `package` on a per-package one; Zod issues on a schema refusal).
The report of a run that reaches the runtime stage is unchanged.
- `content/docs/deployment/cli.mdx` — `### Quality` gains a table row
and a `#### os verify` entry: the two stages, the exit mapping, the
three `--json` shapes, and what stage 1 does not cover (package docs,
capability providers, picklist-reference and view-container-name checks,
`--strict` stay `os validate`'s).
- `.changeset/21323-verify-author-time-rules.md` — `@objectstack/cli`
`minor` (BREAKING narrowing under the launch-window convention),
ADR-0087 disposition `not-required (no-migration-prescription)`.
## Why the stage is not lifted out of `validate.ts` / `compile.ts`
The dispatch route was to lift the `runAuthoringRules` segment of
`validate.ts` (and `compile.ts`) into one shared function all doors
call. Measured before writing it, four existing source-scan pins hold
each door's wiring in that door's own file, so the lift would move the
text they read:
- `packages/lint/src/authoring-rule-wiring.test.ts` — each of
`validate.ts` / `compile.ts` / `lint.ts` must contain a
`runAuthoringRules(` call;
- `packages/cli/test/validate-build-gate-parity.test.ts` — that call's
`normalized` / `parsed` tiers must resolve, by `const` bindings in the
same file, to `authoringRuleUnionStack(`; every call site in
`compile.ts` / `validate.ts` must sit in a ledger;
- `packages/cli/test/lint-per-package-authoring-seam.test.ts` — each
door must contain a `runPerPackageAuthoringRules(` call;
- `packages/cli/src/utils/sdui-manifest.test.ts` — each door must
contain exactly one `resolveJsxGateManifest(…, dirname(absolutePath));`.
Retargeting them is outside this card's file surface (one is in
`@objectstack/lint`), and the doors' output interleaves with the stage
(step lines, the JSX notice, an exit between the union run and the
per-package pass), which a function returning one verdict cannot
reproduce byte for byte. So `validate.ts` and `compile.ts` are
untouched, and the shared pipeline lives in the new util that `os
verify` calls. What holds that copy of the wiring to the doors is
behaviour: the spawned pin requires `os verify --json`'s `errors` to
deep-equal `os validate --json`'s, and the util's unit pin covers the
union fold and the per-package pass, which a single-package stack cannot
tell apart.
## Real repro (scaffold from the in-repo `create-objectstack`, plus a
small task app)
The card's three planted mistakes (lookup to `tasks_app_projects`,
action `visible: 'done != true'`, list column `{ field: 'priority' }`),
and a clean control with them corrected. Before = CLI built at
`222ecc27f9`; after = CLI built at this branch; identical inputs.
| app | command | before | after |
|---|---|---|---|
| planted | `os validate` | 1 — the three findings | 1 — the three
findings |
| planted | `os build` | 1 — the three | 1 — the three |
| planted | `os lint` | 1 — the three, as errors | 1 — the three, as
errors |
| planted | `objectstack verify` | **0** — `✓ verify passed — no runtime
failures` | **1** — `✗ Author-time rules failed (3 issues) — the runtime
stage did not run`, the three findings, no boot |
| planted | `verify --json` | 0, report polluted by boot logs | 1, one
document `{ error, errors }`; `errors` deep-equals `validate --json`'s
`errors` |
| clean | all four | 0 | 0 (`✓ Author-time rules passed (49 rules)`, `6
advisory finding(s)`, then `✓ verify passed`) |
`os validate` / `os build` / `os lint` text and `os validate --json`
outputs are identical before and after on both apps (timings and
absolute paths normalized).
## Tests
All readings on `d3e368d5ea` (this branch after merging `origin/main` at
`6c5bef5f4e`) unless noted.
- New pin `packages/cli/test/verify-author-time-stage.test.ts`
(integration tier: spawns the CLI): planted stack fails `verify --json`
with `errors` deep-equal to `validate --json`'s, and one JSON document
on stdout; planted stack fails the text face naming each rule; clean
control passes both stages. Red before the fix (planted `verify` exit 0
on both faces), green after: 3/3.
- New pin `packages/cli/src/utils/author-time-rules.test.ts` (unit):
door is `'validate'`; option-B stack (definitions only in `packages[]`)
is refused through the union fold, control passes; per-package-only
finding reaches the verdict; a stack that does not parse is refused.
4/4.
- `@objectstack/cli` unit project: 244/245 files at `5d0486c67d`; the
one red was `test/docs-cli-enumeration-parity.test.ts` refusing an
ungoverned `**Options.**` table in the new docs entry — the flags moved
into the stage prose (`d3e368d5ea`), and that file plus the other unit
files reading `cli.mdx` (`init.test.ts`,
`create-example-retired-docs-parity.test.ts`) re-ran green: 3 files / 87
tests.
- `pnpm --filter @objectstack/cli typecheck` (tsc + test-layer debt
ratchet): exit 0, debt unchanged.
- Integration tier, narrowed to the doors this diff touches: the new
pin, `authoring-rule-command-parity`, `build-text-face-advisory-count`,
`build-view-container-name`, `lint-per-package-authoring-parity`,
`union-fold-command-parity`, `validate-per-package-authoring-parity`,
`validate-view-container-name`: 8 files / 48 tests green. Nightly
`config-miss-stdout-purity.e2e` (its family includes `verify`): 174/174.
The rest of the integration tier is declared to CI.
- Gates: `dispatch-gates --commands` derived 93 commands on this tree;
all 93 run on `d3e368d5ea`, all exit 0; `--ran` reconciliation: 93 run,
0 NOT-MEASURED (derived from recorded exit codes).
- `pnpm lint` (full repo, `eslint . --no-inline-config`): exit 0 on
`d3e368d5ea`.
### Ablations (fix committed first; each leg through
`scripts/ablation-replace.mjs`, restore proven blob == HEAD and `git
diff HEAD` empty)
- A1 `verify.ts`: the stage-1 call replaced by a marker statement →
`pnpm --filter @objectstack/cli build` → `ablation-dist-preflight` found
the marker in `dist/commands/verify.js` → spawned pin red 3/3. Restore →
rebuild → preflight `--absent` (marker in 0 of 564 built files, tree
clean) → pin green 3/3. The CLI child resolves commands from `dist/`
here (see Acceptance notes), hence the rebuild per leg.
- A2 util: tiers handed unfolded → only the option-B case red.
- A3 util: per-package pass skipped → only the per-package case red.
- A4 util: schema refusal returns `null` → only the schema case red.
- A5 util: door `'build'` → only the door case red.
## Acceptance notes
- `bin/run-dev.js` (and so every spawned CLI pin in this package)
resolves commands from `packages/cli/dist`, not `src/`, in this
container: oclif's tsx registration fails on tsx 4.23.15 (`Could not
find tsx. Skipping tsx registration`). That is the area issue #21308
tracks; the new pin is correct either way in CI, which builds before
testing, but a local run needs `pnpm --filter @objectstack/cli build`
first.
- `os verify --json` on a run that reaches the runtime stage still
carries boot log lines on stdout — that is issue #21324, queued behind
this card and not touched here. A stage-1 refusal is one clean document
because nothing boots.
- Stage 1 is the rule registry, not all of `os validate`: a stack `os
validate` refuses only for package docs, a capability provider, a
picklist reference or a view-container name (the last one `os serve`
also refuses at boot) can still reach the runtime stage. The docs entry
says so.
- `packages/cli/README.md` carries no `os verify` row, so nothing there
went stale.
- The `**Options.**` table for `os verify` was left out: the docs pin
binds every such table to the oclif declarations through its `GOVERNED`
map (`test/docs-cli-enumeration-parity.test.ts`), which is outside this
card's surface; adding `verify` there would also need a number word for
zero positionals.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent fbe2deb commit f397608
6 files changed
Lines changed: 683 additions & 6 deletions
File tree
- .changeset
- content/docs/deployment
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1614 | 1614 | | |
1615 | 1615 | | |
1616 | 1616 | | |
| 1617 | + | |
1617 | 1618 | | |
1618 | 1619 | | |
1619 | 1620 | | |
| |||
1679 | 1680 | | |
1680 | 1681 | | |
1681 | 1682 | | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
| 1690 | + | |
| 1691 | + | |
| 1692 | + | |
| 1693 | + | |
| 1694 | + | |
| 1695 | + | |
| 1696 | + | |
| 1697 | + | |
| 1698 | + | |
| 1699 | + | |
| 1700 | + | |
| 1701 | + | |
| 1702 | + | |
| 1703 | + | |
| 1704 | + | |
| 1705 | + | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
| 1714 | + | |
| 1715 | + | |
| 1716 | + | |
| 1717 | + | |
| 1718 | + | |
| 1719 | + | |
| 1720 | + | |
| 1721 | + | |
| 1722 | + | |
| 1723 | + | |
| 1724 | + | |
| 1725 | + | |
| 1726 | + | |
| 1727 | + | |
| 1728 | + | |
| 1729 | + | |
| 1730 | + | |
| 1731 | + | |
| 1732 | + | |
| 1733 | + | |
| 1734 | + | |
| 1735 | + | |
| 1736 | + | |
| 1737 | + | |
| 1738 | + | |
1682 | 1739 | | |
1683 | 1740 | | |
1684 | 1741 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| 23 | + | |
22 | 24 | | |
| 25 | + | |
23 | 26 | | |
24 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
25 | 33 | | |
26 | 34 | | |
27 | 35 | | |
| |||
60 | 68 | | |
61 | 69 | | |
62 | 70 | | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
| 71 | + | |
67 | 72 | | |
68 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
69 | 83 | | |
70 | 84 | | |
71 | 85 | | |
72 | | - | |
| 86 | + | |
73 | 87 | | |
74 | 88 | | |
75 | 89 | | |
| |||
148 | 162 | | |
149 | 163 | | |
150 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
151 | 169 | | |
152 | 170 | | |
153 | 171 | | |
| |||
263 | 281 | | |
264 | 282 | | |
265 | 283 | | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
266 | 357 | | |
0 commit comments