Skip to content

fix(cli): os migrate record-currency-codes judges --code before the probe, prompt and boot; document the command and the record-mode switch - #28416

Draft
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-et4dvl
Draft

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-et4dvl

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sam · project thread
Fixes #28168

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:os migrate record-currency-codes 现在一进来就判 --code,不在 ISO 4217 里的码在探库、--apply 确认提示和 boot 之前就以 INVALID_FIELD / 400 退出;CLI 文档和 Currency 文档页补上这条命令、切换时的 409 与 dry run / --apply 两步。
  • 为什么改:之前坏码要等 boot 之后才被引擎拒绝,而 --apply 的 boot 会先建表建列;文档里兄弟命令都有条目,唯独这条没有。
  • 风险与回滚:只在命令开头加一个纯函数判断,判据与引擎同一个 isValueDomainMember('iso_4217_currency', …),引擎侧检查原样保留;回滚即 revert 本 PR。
  • 先看:packages/cli/src/commands/migrate/record-currency-codes.ts(新函数与 run() 开头那段),其测试文件新增的 describe,content/docs/data-modeling/currency.mdx 新增小节。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run --project integration src/commands/migrate/record-currency-codes.test.ts,预期 12 个用例全绿,含「坏码不探库、不弹提示、不 boot」两条。
  • 需要你做的:无,席位落地。

What changes

  • S2, packages/cli/src/commands/migrate/record-currency-codes.ts. run() now judges --code first, right after the header and before the occupancy probe, the --apply confirmation prompt and bootSchemaStack. The predicate is the one backfillRecordCurrencyCodes applies, isValueDomainMember('iso_4217_currency', code) from @objectstack/spec/shared (a light import; @objectstack/objectql stays lazily loaded). The error is the engine's envelope: code: 'INVALID_FIELD', status and httpStatus 400, so --json answers { error, code: 'INVALID_FIELD', httpStatus: 400 } and exit 1, as it did after the boot. The human face prints the message and exits 1.
  • Wording. The message keeps the engine's prefix and its sentences: [record-currency-backfill] 'usd' is not an ISO 4217 currency code, so … would refuse it: give the uppercase alphabetic code of the currency these amounts are in (for example USD). One part cannot match word for word: the engine names the code field ('opportunity.currency_code'), which is read from the object's declaration, and that is not loaded before the boot. The early refusal names the amount instead (the currency code field of 'opportunity.amount') and ends Refused before the database was opened or any row was read.
  • Engine unchanged. packages/objectql/src/record-currency-backfill.ts is untouched and keeps its own check for every other caller. The CLI's post-boot catch comment now says the code is judged before the boot.
  • The --code flag description now says a non-ISO code is refused before the database is opened.
  • S3, content/docs/deployment/cli.mdx. A row in the data-migrations table, after summary-nulls, and a #### os migrate record-currency-codes section shaped like summary-nulls: why the rows owe a code, the three invocations, the code is the operator's and never derived, the early --code refusal and the post-boot OBJECT_NOT_FOUND / INVALID_FIELD refusals, writes through the engine, idempotent, no flag.
  • S3, content/docs/data-modeling/currency.mdx. A ### Switching a field that already holds rows section under the record mode: the 409 destructive_change with finding currency_mode_to_record, lifted by re-submitting with force (?force=true on PUT /api/v1/meta/:type/:name), then the dry run and --apply. It says the code is the one given with --code and is never derived, and links to the CLI section. Both pages are hand-written trees under AGENTS.md's Documentation Guardrails, not release-owned.
  • .changeset/28168-record-currency-code-before-boot.md: @objectstack/cli patch, both declaration lines.

Tests

packages/cli/src/commands/migrate/record-currency-codes.test.ts (integration tier, as the file already was):

  • New describe a code outside ISO 4217 is refused before the probe, the prompt and the boot:
    • --json --apply with usd and no --yes: exit 1, the envelope is INVALID_FIELD / 400 (the same code and httpStatus as the real engine's refusal of the same code, which the test produces), never confirmation_required; the message's leading clause equals the engine's; bootSchemaStack, probeMigrationTarget and the backfill are never called.
    • Human face, --apply on a terminal (stdin.isTTY true) with EURO: exit 1, the engine's leading clause is printed, no readline prompt is opened, and neither the boot nor the probe is called.
    • Control: a member code (USD) still reaches the boot and the backfill.
  • The existing envelope case fed the engine's code refusal through a mocked backfill. That path is now unreachable for a bad code, so the case now feeds the engine's other pre-walk refusal (a member code on a field that is not record-mode), which only the booted engine can make, and still pins INVALID_FIELD / 400 / the message reaching --json.

Acceptance notes

  • The message names 'OBJECT.FIELD''s code field instead of the code field by name (reason above). The envelope (code, httpStatus) and every other sentence are the engine's.
  • No out-of-scope findings.

Verification

All readings at head 738019ef.

Tests and typecheck
  • pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 src/commands/migrate/record-currency-codes.test.ts: Test Files 1 passed, Tests 12 passed; lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/migrate-confirmation-exit.pin.test.ts: Tests 38 passed.
  • pnpm --filter @objectstack/cli typecheck: exit 0 (tsc, then check:test-typecheck OK).
  • The rest of the unit and integration layers are declared to CI.
Ablation

node scripts/ablation-replace.mjs replaced the call refuseNonMemberCurrencyCode(flags.object, flags.field, flags.code); with a marker (anchor 1 to 0, blob 6bf55f78 to 9501b58a), then ran the test file: the two new refusal cases failed (Tests 2 failed, 10 passed) and the control passed. Restored: the file's blob equals HEAD's (6bf55f78) and git diff HEAD is empty. The command imports from src, so no rebuild was involved.

Gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 93 families. All 93 ran, each recorded as COMMAND :: exit N. Five (check:skill-examples, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity) first exited 3 (PREREQUISITE NOT MET); after a full pnpm build (lock VERDICT command-exit 0, Tasks 116 successful of 116) all five exited 0.
  • dispatch-gates --ran: 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.
  • pnpm lint: exit 0.
  • CI check-runs are cited in the report once they settle.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English, except the 维护者速读 summary, which the PM loop writes in Chinese.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QUs3BvyNBMezKJUd7aydyG

…e record-currency-codes

A code outside ISO 4217 is refused with the backfill's INVALID_FIELD / 400
envelope before the occupancy probe, the --apply prompt and bootSchemaStack.
The engine-side check is unchanged. Document the command in cli.mdx and the
record-mode switch (409 destructive_change, dry run, --apply) in currency.mdx.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3BvyNBMezKJUd7aydyG
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 5 documentable anchor(s).

22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 212 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 26835f90ed1dccbd03482071841990652992392a — the merge of head 2a029310c284aae891814fa8a823d28d190624cb into base af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 26835f90ed1dccbd03482071841990652992392a && git checkout 26835f90ed1dccbd03482071841990652992392a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f 2a029310c284aae891814fa8a823d28d190624cb && git checkout -B drift-repro af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f && git merge --no-ff 2a029310c284aae891814fa8a823d28d190624cb

node scripts/docs-audit/affected-docs.mjs --json af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs af6ca001b6ce63e4aef0ac3237b1e23d1f77e29f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…cord-currency-codes section

The currency-mode closure pin (rule A) reds a defaultCurrency sentence that
does not carry the fixed condition. Name the field's defaultCurrency as the
one it had under fixed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3BvyNBMezKJUd7aydyG

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant