test(cli): drive os migrate account-issuer --json in the json-stdout-purity family - #17805
Conversation
… family `json-stdout-purity.e2e.test.ts` discovers its family from the source tree — every command that calls `bootSchemaStack` and declares a `--json` flag — and reconciles the discovered set against the hand-listed `FAMILY`, whose values are the argv that actually drive each member through `describe.each`. #17454 landed `os migrate account-issuer`, a new `--json` face on that seam, without a `FAMILY` row, so discovery found 13 members against 12 listed and the reconciliation went red. It merged green because this file is `*.e2e.test.*` and therefore runs only under `OS_TEST_TIERS=nightly`, which no pull request exercises. The row is added with the argv that drives it, not merely listed: the bare form boots (it takes the `os migrate plan` shape — `deferSchemaDdl` + `readOnlyProbe` — so a missing sqlite file is opened as an empty in-memory database rather than created), and the driven run satisfies all three halves of the contract on its own: one JSON document on stdout, no kernel-logger record or `[StandaloneStack]` line there, and every boot diagnostic still on stderr. `CONFIG_MISS_FAMILY` is deliberately unchanged: the pre-boot family is the commands that refuse at `resolveConfigPath()`, and this one never reaches that helper — it does not import `utils/config.js`, and the source-read `discoverConfigMissFamily()` returns the same ten members. The overlap assertion stays `['migrate meta']`. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
ACCEPT — Gate state
The diff is 8 lines and every one of them was checkedOne
⭐ And the Red first, then green — on the tier that shows itReproduced at base ⭐ The ablation, and the part of it that is unflatteringLeg B set the row's argv to
No changeset — measured, not assumed
The open question — answered: A, keep
|
Part of #17633 — it repairs the one file the 2026-09-12T05:54Z sweep names, and carries no closing keyword on purpose.
test-nightly-tiers.ymlstates of its green path: "On green this workflow files nothing, edits nothing and closes nothing", so the card is closed by a seat reading a later nightly assuccess— a reading no merge can assert. #17633 therefore stays open after this lands; the half left behind is that confirmation.Clause-②: no
What was red
packages/cli/test/json-stdout-purity.e2e.test.ts, indescribe('the family this contract has to hold across'):That file DISCOVERS its family from the source tree — every command under
packages/cli/src/commandsthat callsbootSchemaStack(and declaresjson: Flags.boolean(— and reconciles the discovered set against the hand-listedFAMILY.FAMILYis not a list of names:describe.each(Object.keys(FAMILY))drives every key, and each value is the extra argv that drives it. So a row is a promise that the member is actually exercised.os migrate account-issuerlanded in9bd4344e4(by #17454) as a new--jsonface on that seam, without aFAMILYrow. Discovery found 13 members against 12 listed. It merged green because this file is*.e2e.test.*, which runs only underOS_TEST_TIERS=nightly— the class this card exists to see.Symbols, not line numbers:
FAMILYis at:82,discoverFamily()at:129, the reconciliation at:209,describe.eachat:248. All four are still at the line numbers the card body recorded at 05:54Z, on the tree this branch was cut from.The premise, measured before any edit
The premise to falsify was that
os migrate account-issuer --jsonalready SATISFIES the stdout-purity contract and only itsFAMILYrow is missing. It was driven by hand against the fixture this suite builds, before the row existed. It holds, on all three halves:JSON.parseof the whole stream succeeds[StandaloneStack][StandaloneStack] no compiled artifactBootstrap completeGraceful shutdown completeSo this is a test-only diff, and the branch the dispatch order reserved for a purity DEFECT was not taken.
The fix
One
FAMILYrow, with the argv that drives it —[], the bare form. It boots because this command takes theos migrate planshape (deferSchemaDdl: true+readOnlyProbe: true), so the fixture's absent sqlite file is opened as an empty in-memory database rather than brought into existence. Nosys_accounttable exists there, so the face driven is the command's REFUSAL face: theemitJson(…, 1, { compact: true })branch, which is the noisier of its two emit paths — every driver and kernel diagnostic the failed scan produces is emitted before it. The clean-report face is unreachable in this fixture by construction, and the header already states why the fixture stays uncompiled and minimal.CONFIG_MISS_FAMILY: decided NO, from the source rather than from the colourmigrate account-issuerdoes NOT belong to the pre-boot family, and the overlap assertion stays['migrate meta'].discoverConfigMissFamily()takes a command iff it declaresjson: Flags.boolean(AND imports fromutils/config.js— directly, or through a class it extends.account-issuer.tsimportsutils/format.jsandutils/schema-migrate.jsand extendsCommand; it never reachesresolveConfigPath(), so it has no refusal branch for that family to drive. Re-running the shipped discovery over this tree returns the same ten members,migrate account-issuernot among them, andpreBootis still length 10.This is the decision, not its consequence: had it been hand-added to
CONFIG_MISS_FAMILY, the:227reconciliation would have gone red precisely because the discovery disagrees — the pin catches a hand-addition, which is the same evidence read from the other side.Verification
All runs in one worktree, through
scripts/pm/os-verify-lock.sh; the verdict line quoted is the one the wrapper prints.8da783206before the edit:Byte-for-byte the failure the card records, down to the diff line and
:209:30.trap … EXIT INT TERMthat re-checks the hash against the HEAD blob (d30b0f4d06e56ca0ed0243207f7d2e42d6f52031). Nodistis involved: vitest loads this test file from source, so there is no built artifact for the mutation to fail to reach.LEG A — the positive control, the driven run's own output and exit code. A temporary case read the captured run for
migrate account-issuerand asserted its payload and its exit status, then was removed:That payload belongs to no other member of the family — it is this command's own
sys_accountrefusal — so the row is driving the command it names, not merely sitting in a list.LEG B — the mutation, a row that drives NOTHING. The row's argv became
['--drives-nothing'], which oclif refuses above the command:So a row that drives nothing cannot pass as one that does. Reported as measured, including the part that is not flattering: the THIRD case,
leaves no kernel-logger record on stdout, stayed GREEN through leg B. It is a pair of negative assertions, and they are vacuously true of an empty stdout — it binds purity, not existence. The two that do bind existence are enough for this row, and the trio is unchanged by this PR.Restore was verified on both legs by hash equality with the HEAD blob and an empty
git diff HEAD, not by an exit code.node scripts/pm/dispatch-gates.mjs --commandswith no paths, then reconciled with--rancarrying each recorded exit code:The one NOT MEASURED is
pnpm check:dual-build-cjs-loads, and it is not a red: it exits 3 withPREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ … ⛔ This is NOT a pass: nothing was measured, naming twelve packages outside this card's build closure. It needs a whole-repopnpm build; CI builds everything and is the authority on it.pnpm check:nul-bytesis in the 47 and green; a control-character sweep over the edited file (grep -naPover the C0 set plus DEL) also returns nothing.pnpm --filter @objectstack/cli build && pnpm --filter @objectstack/cli typecheck—VERDICT command-exit 0. Reported precisely, because the two halves do not cover the same files:tsconfig.jsondeclaresinclude: ["src"], so thetsc --noEmithalf does NOT reachtest/; the half that reaches this diff ischeck:test-typecheck, which reports@objectstack/cli's test layer compiles under packages/cli/tsconfig.test.json.The pin re-checked against the CURRENT
main, not only against the base this branch was cut from.origin/mainat310760d22touches neitherpackages/cli/src/commandsnorpackages/cli/testsince8da783206, and running the shipped discovery overorigin/main's own tree returns exactly the thirteen membersFAMILYcarries after this PR. So the set is right against the tree this will land on.node scripts/pm/check-clause2-carriers.mjs --pair 17805— exit 0: "the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell."Not run locally and left to CI, declared rather than implied:
packages/cli'sintegrationtier beyond this one file, the repo-widepnpm lint, and the whole-repo buildcheck:dual-build-cjs-loadsneeds.Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Changeset
None, deliberately. Measured rather than assumed:
@objectstack/clideclaresfiles: ["dist", "README.md", "CHANGELOG.md"], and afterpnpm --filter @objectstack/cli build, a string unique to this diff (the noisier of its two) has 0 hits across all three of those paths, while the positive control on the same pass —Pre-flight the retirement of sys_account.issuer, the command description that really does ship — has 1, inpackages/cli/dist/commands/migrate/account-issuer.js. The matcher fires, so the zero is a reading.packages/cli/test/**is not under any published path anddist/carries no compiled test file. Nothing published moves, so this is theskip-changesetcase rather than a missing one.Acceptance notes
leaves no kernel-logger record on stdoutis vacuously green against a run that never happened (leg B above). It is correct for what it asserts — purity, not existence — and the sibling cases in the same trio cover existence. Noted, not filed: it is a property of the wholedescribe.eachblock, no in-flight PR holds this file, and changing it is a pin redesign rather than this card."code":"RESOURCE_CONFLICT"for a missingsys_accounttable, viaerrorCodeFields()over the sql driver's deliberately unattributableDATABASE_ERROR. Recorded because it is what the driven run prints; no contract text and no repro of harm was gathered, so nothing is filed and no follow-up owner is claimed.CONFIG_MISS_FAMILYand the:227/:236overlap assertion were examined and deliberately left unchanged — the reasoning is in its own section above, from the command's imports and the shipped discovery, not from which answer happened to be green.Generated by Claude Code