Commit ed6f734
fix(cli): os lint --json reports the ADR-0087 conversions defineStack applied (#20617)
Part of #20583
Clause-②: no
## What this lands: location 1 only
`os lint --json` now folds `LoadedConfig.stackConversions` into its
`conversions` list, right after `loadConfig`: the same one-line fold
`validate.ts` / `compile.ts` make at their step 1b since PR #20579.
`defineStack` converts at load, so the config `os lint` received was
already canonical and its own `normalizeStackInput` pass found nothing
of the default export to convert. The notice reached stderr alone.
- `os lint` keeps accepting exactly what it accepts today. There is no
`refuseUnbuiltStack` here and none is implied; the one-shape rule is not
extended to this command. An unbuilt default export carries no record,
so `stackConversionsOf` answers an empty list for it and the command's
own pass converts it as before.
- One conversion is listed once. The producer's output is canonical
wherever it converted, so the pass finds only what no producer saw: an
unbuilt export, or a key merged from a named export. Pinned by the
exactly-one rows below.
- No new `--json` key, and `passed`, `issues`, the counts and the exit
code do not move. The text face prints the folded notice in its warning
block, as `os build` does.
## What this leaves: location 2 is a fork, not shipped
A `defineStack` that converts and then refuses still reports
`conversions: []` on `os validate --json`, `os build --json` and `os
lint --json` (the third door, measured below). The dispatch's hypothesis
was that the CLI can recover those conversions without a spec edit.
Measured, it cannot recover them; it can only reconstruct them:
1. **Capturing the producer's stderr lines during `loadConfig` loses
notices.** `warnConversionNotice` is warn-once per process, keyed on
conversion id, path, from and to. The record is not subject to that
warn-once.
- Measured at `eb4b17c346`: `composeStacks([defineStack(A),
defineStack(B)])`, where both carry `page:header` `description` at
`pages[0]`. The record carries 2 notices, and stderr carries 1 line.
- In the refusing variant (B adds `requires: ['no-such-capability']`),
the one stderr line belongs to A, whose `defineStack` did not refuse.
B's own notice was suppressed.
- The line also lacks the `surface`, `toMajor`, `message` and `code`
fields, so the structured notice would have to be re-derived from prose.
The conversion types document that prose as derived, never the source of
truth.
2. **Recomputing through `normalizeStackInput` on the authored argument
is a second conversion pass.** The CLI would shim `defineStack` in every
config load, or re-load the module in authored-source mode, and rerun
the pass when the call refuses. The `stackConversionsOf` TSDoc rules
this out: a door never runs a second pass to reconstruct the record. It
would also copy the producer's record formula (input record plus pass
notices) into the CLI, where it drifts.
3. The spec exposes no other channel: `warnConversionNotice` and its
warn-once set are module-private, and the refusal errors
(`StackRefusalError` subclasses) carry `issues` only.
So the only channel that is not a workaround is a spec change: the
refusal carries the notices it applied. `packages/spec` belongs to the
spec seat under this dispatch, so no spec edit is made here. The dev
report carries the fork, with options.
## Measurements (CLI from source, `bin/run-dev.js`)
| run | before (`eb4b17c346`) | after (`ca74de14aa`) |
|:--|:--|:--|
| `os lint --json`, the card's `page:header` `description` case | exit
0, `conversions: []`, 1 stderr line | exit 0, `conversions` = the one
`page-header-subtitle-alias` notice, 1 stderr line |
| `os validate --json`, convert-then-refuse (`requires:
['no-such-capability']`) | exit 1, `STACK_CAPABILITY_UNKNOWN`,
`conversions: []` | unchanged (location 2) |
| `os build --json`, the same config | exit 1,
`STACK_CAPABILITY_UNKNOWN`, `conversions: []` | unchanged (location 2) |
| `os lint --json`, the same config | not measured | exit 1,
`STACK_CAPABILITY_UNKNOWN`, `conversions: []` (location 2, third door) |
## Tests
- `packages/cli/test/stack-conversion-record-door.test.ts` gains an `os
lint --json` block. It covers the card's plain case, the record across
the named-export spread, `composeStacks`, a key merged from a named
export (the pass converts it once, with no producer stderr line), and
the canonical control. Every non-empty row asserts exactly one entry.
This file is in the per-PR `integration` tier. The existing
`lint-conversion-notices.e2e.test.ts` is `*.e2e.*` and runs nightly
only, which is why the new rows are not in it.
- At `ca74de14aa`: the `unit` tier passed 234 of 234 files (3342 tests),
and the door file passed 15 of 15 tests. `pnpm --filter @objectstack/cli
typecheck` exits 0, and the door file is in the test-layer program
(`--listFilesOnly`). `lint-conversion-notices.e2e.test.ts` under
`OS_TEST_TIERS=nightly` passed 6 of 6 at `a80b61dad0`. Its unbuilt
`export default` fixtures still lint and convert through the pass.
- Ablation, run from the committed state `a80b61dad0` through
`scripts/ablation-replace.mjs` (WRAP mode, with a trap). Deleting the
fold line took the anchor count from 1 to 0 on disk (blob `37bf1203bff7`
to `95dcca7f308a`).
- Exactly the three record-dependent lint rows went red (plain, record
across spread, composeStacks). The named-export-pass row, the lint
control and all 10 validate / build / strict rows stayed green: 3
failed, 12 passed.
- Restore was proven: the blob equals HEAD (`37bf1203bff7`) and `git
diff HEAD` is empty. `lint.ts` is loaded from `src/` by the child, so no
`dist/` sits on the measured path.
## Gates (at `ca74de14aa`, after merging `origin/main`)
- `dispatch-gates --commands`: 63 commands, all exit 0. `dispatch-gates
--ran` reconciles 63 derived, 63 run, 0 not measured, 0 unrun, each with
its exit code. The first runs of `check:dual-build-cjs-loads` and
`check:i18n-coverage` answered PREREQUISITE NOT MET (exit 3) before a
full build. Both were rerun green after `pnpm turbo run build
--filter=!@objectstack/docs`.
- The artifact-roster rows: 36 of 39 exit 0.
`check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths` need PR context and are rerun against this
PR.
- `pnpm lint` (the repo-wide `eslint . --no-inline-config`) exits 0 in
29s.
- `node scripts/check-issue-citations.mjs --base origin/main` exits 0 (1
citation, resolves).
**Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
could not take the shared verify lock on this host: no usable `flock`.
The shared
verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
not ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held
for this
run, nor for any sibling agent in this container while it ran.
## Acceptance notes
- **Location 2 stays open on the card.** This PR says `Part of`, so
merging it leaves the card open for the fork above.
- **`os lint`'s text face now prints the producer's notice** in its
warning block for a `defineStack` config with a retiring spelling, in
the same wording as `os build`. `check:i18n-coverage` runs `os lint`
over the 13 example configs and stays green.
- **Host-only reading, not a defect:** on macOS,
`test/published-subpath-console.pin.test.ts` and
`test/published-subpath-hook-body.pin.test.ts` fail 5 assertions when
`TMPDIR` is the `/var/folders/...` symlink. The resolver answers the
`/private/var/...` realpath. With `TMPDIR` set to its realpath, both
pass 29 of 29. CI runs on Linux. Carrier: none.
- Carried over from the card, not filed:
`content/docs/deployment/cli.mdx`'s "Warnings checked" list for `os
validate` names no conversion notices.
---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_
---------
Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent c1d8051 commit ed6f734
3 files changed
Lines changed: 93 additions & 1 deletion
File tree
- .changeset
- packages/cli
- src/commands
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
945 | 945 | | |
946 | 946 | | |
947 | 947 | | |
948 | | - | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
949 | 969 | | |
950 | 970 | | |
951 | 971 | | |
952 | 972 | | |
953 | 973 | | |
954 | 974 | | |
955 | 975 | | |
| 976 | + | |
956 | 977 | | |
957 | 978 | | |
958 | 979 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
34 | 44 | | |
35 | 45 | | |
36 | 46 | | |
| |||
130 | 140 | | |
131 | 141 | | |
132 | 142 | | |
| 143 | + | |
| 144 | + | |
133 | 145 | | |
134 | 146 | | |
135 | 147 | | |
| |||
207 | 219 | | |
208 | 220 | | |
209 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
210 | 268 | | |
211 | 269 | | |
212 | 270 | | |
| |||
0 commit comments