fix(cli): os lint --json reports the ADR-0087 conversions defineStack applied - #20617
objectstack-fleet[bot] merged 3 commits into
Conversation
…pplied into conversions os lint filled conversions only from its own normalizeStackInput pass over the loaded config, which a defineStack default export hands over already canonical, so the notice reached stderr alone. Fold LoadedConfig.stackConversions right after loadConfig, the step 1b fold os validate / os build make. No one-authoring-shape refusal is added: an unbuilt export carries no record and converts through the pass as before. Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289 Co-authored-by: Claude <noreply@anthropic.com>
…applied Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289 Co-authored-by: Claude <noreply@anthropic.com>
…nversions-lint-and-refusal
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: Inputs, and nothing else: card #20583 (body and all 4 comments: triage Check-runs at read (newest per name, 31 names): 21 completed, 18 success and 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke); 10 in progress (Dogfood Regression Gate 1/3 to 3/3, Lint and Repo Gates, Test Core 1/6 to 4/6, Type Check consumer gates, Type Check workspace); 0 failed, 0 queued. Success includes Build Core, Test Core 5/6 and 6/6, Type Check source gates and debt ledger, Dogfood Verify CLI, Temporal Conformance, Check Changeset, Check PR Size, both single-claim guards, ① Derived judgmentsThe diff is one executable line in (1) (2) A conversion cannot be listed twice, and the pins prove exactly-once. RIGHT, for the driven conversion. The one array has two fillers: the fold (what the producer converted, recorded on the default export before the spread drops it) and the pass over the merged (3) The Public surface: no export, flag, error code or schema key moves. ② Semver level
③ Boundary flagsDev deviations (
PR-body flag: verification ran UNLOCKED (no Escalations: none. Implemented-by: VERDICT: PASS |
Part of #20583
Clause-②: no
What this lands: location 1 only
os lint --jsonnow foldsLoadedConfig.stackConversionsinto itsconversionslist, right afterloadConfig: the same one-line foldvalidate.ts/compile.tsmake at their step 1b since PR #20579.defineStackconverts at load, so the configos lintreceived was already canonical and its ownnormalizeStackInputpass found nothing of the default export to convert. The notice reached stderr alone.os lintkeeps accepting exactly what it accepts today. There is norefuseUnbuiltStackhere and none is implied; the one-shape rule is not extended to this command. An unbuilt default export carries no record, sostackConversionsOfanswers an empty list for it and the command's own pass converts it as before.--jsonkey, andpassed,issues, the counts and the exit code do not move. The text face prints the folded notice in its warning block, asos builddoes.What this leaves: location 2 is a fork, not shipped
A
defineStackthat converts and then refuses still reportsconversions: []onos validate --json,os build --jsonandos 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:loadConfigloses notices.warnConversionNoticeis warn-once per process, keyed on conversion id, path, from and to. The record is not subject to that warn-once.eb4b17c346:composeStacks([defineStack(A), defineStack(B)]), where both carrypage:headerdescriptionatpages[0]. The record carries 2 notices, and stderr carries 1 line.requires: ['no-such-capability']), the one stderr line belongs to A, whosedefineStackdid not refuse. B's own notice was suppressed.surface,toMajor,messageandcodefields, 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.normalizeStackInputon the authored argument is a second conversion pass. The CLI would shimdefineStackin every config load, or re-load the module in authored-source mode, and rerun the pass when the call refuses. ThestackConversionsOfTSDoc 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.warnConversionNoticeand its warn-once set are module-private, and the refusal errors (StackRefusalErrorsubclasses) carryissuesonly.So the only channel that is not a workaround is a spec change: the refusal carries the notices it applied.
packages/specbelongs 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)eb4b17c346)ca74de14aa)os lint --json, the card'spage:headerdescriptioncaseconversions: [], 1 stderr lineconversions= the onepage-header-subtitle-aliasnotice, 1 stderr lineos validate --json, convert-then-refuse (requires: ['no-such-capability'])STACK_CAPABILITY_UNKNOWN,conversions: []os build --json, the same configSTACK_CAPABILITY_UNKNOWN,conversions: []os lint --json, the same configSTACK_CAPABILITY_UNKNOWN,conversions: [](location 2, third door)Tests
packages/cli/test/stack-conversion-record-door.test.tsgains anos lint --jsonblock. 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-PRintegrationtier. The existinglint-conversion-notices.e2e.test.tsis*.e2e.*and runs nightly only, which is why the new rows are not in it.ca74de14aa: theunittier passed 234 of 234 files (3342 tests), and the door file passed 15 of 15 tests.pnpm --filter @objectstack/cli typecheckexits 0, and the door file is in the test-layer program (--listFilesOnly).lint-conversion-notices.e2e.test.tsunderOS_TEST_TIERS=nightlypassed 6 of 6 ata80b61dad0. Its unbuiltexport defaultfixtures still lint and convert through the pass.a80b61dad0throughscripts/ablation-replace.mjs(WRAP mode, with a trap). Deleting the fold line took the anchor count from 1 to 0 on disk (blob37bf1203bff7to95dcca7f308a).37bf1203bff7) andgit diff HEADis empty.lint.tsis loaded fromsrc/by the child, so nodist/sits on the measured path.Gates (at
ca74de14aa, after mergingorigin/main)dispatch-gates --commands: 63 commands, all exit 0.dispatch-gates --ranreconciles 63 derived, 63 run, 0 not measured, 0 unrun, each with its exit code. The first runs ofcheck:dual-build-cjs-loadsandcheck:i18n-coverageanswered PREREQUISITE NOT MET (exit 3) before a full build. Both were rerun green afterpnpm turbo run build --filter=!@objectstack/docs.check-closing-target-claim,check-partof-closing-keywordandcheck-single-claim-pathsneed PR context and are rerun against this PR.pnpm lint(the repo-wideeslint . --no-inline-config) exits 0 in 29s.node scripts/check-issue-citations.mjs --base origin/mainexits 0 (1 citation, resolves).Declared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usable
flock. The sharedverify lock is declared Linux-only (
flockis util-linux, and a stock macOS doesnot 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
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 adefineStackconfig with a retiring spelling, in the same wording asos build.check:i18n-coveragerunsos lintover the 13 example configs and stays green.test/published-subpath-console.pin.test.tsandtest/published-subpath-hook-body.pin.test.tsfail 5 assertions whenTMPDIRis the/var/folders/...symlink. The resolver answers the/private/var/...realpath. WithTMPDIRset to its realpath, both pass 29 of 29. CI runs on Linux. Carrier: none.content/docs/deployment/cli.mdx's "Warnings checked" list foros validatenames no conversion notices.Generated by Claude Code