Skip to content

fix(cli): os migrate value walks name the columns a disabled package's extension adds - #30696

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-9zmn2n
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-9zmn2n

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #30626

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:os migrate 的五个值迁移命令(blank-single-values、blank-multi-values、option-codes、text-scalars、value-shapes)在报告里逐条列出因所属模块包被停用而没读到的列(对象.字段 + 包 id),--json 里同名 withheldColumns。
  • 为什么改:模块包停用时,它扩展到别的对象上的字段从对外对象里隐去、但列和数据还在;这几个命令只看对外对象,于是跳过这些列,报告却像全扫过一样(实测:有待清理的空串,dry run 仍报「未发现」)。
  • 风险与回滚:只加报告行和一个 JSON 字段,读写范围一行没变;回滚即 revert 本 PR。
  • 先看:packages/cli/src/utils/migrate-withheld-columns.ts(判定:存储折叠减对外折叠,按停用扩展包归属);blank-single-values.ts 的 walkBlankColumns 返回处;value-shapes.ts(无列规则,列出全部被隐去字段)。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run src/commands/migrate/withheld-extension-columns.integration.test.ts,预期 9 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • New packages/cli/src/utils/migrate-withheld-columns.ts: withheldColumns(engine, objects, select?) reads, per walked object, the extend contributors whose package isPackageDisabled, takes the walk's own column rule over registry.getStorageObject(object) minus the same rule over the served engine.getObject(object), and attributes each remaining column to the disabled package whose extension declares it. formatWithheldColumns renders the list (nothing when empty). It lives in utils/ because every .ts under src/commands/** is an oclif command (pattern strategy); the seat amended the claim's file surface for it.
  • blank-single-values and blank-multi-values (they share walkBlankColumns, so one change covers both), option-codes, text-scalars: the report gains withheldColumns, the human plan prints it, the command's engine view passes registry.
  • value-shapes: the scan is objectql's (scanValueShapes) and its column rule (isScannableValueShapeField) is not exported, so the command names every withheld field of the scanned objects (no select), in the plan and in the --json document. No packages/objectql/** change.

Behaviour picked: name the skipped columns, not warn-or-refuse. Measured on origin/main (the integration fixture below with the module disabled): each walk reported columns: [] over a row still holding a value it would rewrite, so the plan said "✓ No stored empty string found." (and the like) with no mention of the column. Refusing while a module that extends a target object is disabled would block the run for every other object and column it can cover, and the operator's remedy (enable, re-run) is the same either way; naming the columns keeps the run useful and makes the gap explicit. The withheld values cannot be rewritten in place while disabled (the card's out of scope: engine.find refuses a withheld field with INVALID_FIELD).

Changed lines: 9 files, +321 / −1 (code 5 command files +58/−1, helper +89, tests +164, changeset +10).

Tests

  • src/commands/migrate/withheld-extension-columns.integration.test.ts (integration tier: new ObjectQL( + sqlite SqlDriver): an app package owns wx_account, a module package adds wx_tier / wx_level (select), wx_tags (multiselect), wx_note (text) by extension; a row below the door holds a value for each walk to rewrite. Per walk: control (enabled: the column is read, nothing named) and the pin (disabled: report.columns empty, withheldColumns equals the walk's in-scope extension columns with the package id, the plan prints wx_account.COLUMN (package com.qa.wx.loyalty)). One more case for the value-shapes path (no column rule: all four fields named).
  • src/utils/migrate-withheld-columns.test.ts (unit): attribution, the column-rule narrowing, an engine without the registry surface, the empty format.
Ablation (the four command files reverted to origin/main, helper and test kept; commit c2cdd7d)
  • Mutated leg: grep -c withheldColumns on the four files = 0 each; Tests 8 failed | 1 passed (9) (every walk case red, e.g. expected undefined to deeply equal [ Array(2) ]; the value-shapes helper case stays green as it does not go through a walk). Lock VERDICT command-exit 1.
  • Restore leg: git checkout HEAD -- FILES, git diff HEAD empty, each file's hash equals its HEAD blob.
  • Fixed tree: Tests 9 passed (9), VERDICT command-exit 0. The CLI tests import src/ directly, so no dist rebuild sits between the legs.

Acceptance notes

  • value-shapes --apply still records the adr-0104-value-shapes deployment flag on a full-scope run while withheld columns went unscanned; this PR only names them. Whether such a run should record no flag (as a --object-narrowed run does) is a posture question for the seat; carrier: none.
  • Other os migrate commands that read engine.getObject (files-to-references, comment-reactions, activity-changed-fields, the audit-* commands, organization-ownership) target platform objects or a scan in objectql; not measured for this blind spot here. carrier: none.

Verification

Local, on c2cdd7d (the implementation head; b94187d only merges 4 origin/main commits, none touching packages/cli/src/commands/migrate/** or packages/cli/src/utils/migrate-*):

  • pnpm --filter @objectstack/cli typecheck (includes check:test-typecheck): VERDICT command-exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: Test Files 390 passed (390), Tests 5435 passed (5435), VERDICT command-exit 0.
  • Integration layer, the files this diff touches or feeds (new pin + blank-single-values, blank-multi-values, option-codes, text-scalars integration suites + the helper unit test): the four sibling integration suites and the helper unit test 5 passed; the new pin, rerun after a fixture fix, Tests 9 passed (9). The rest of the integration layer is declared to CI.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 60 families, each run with its exit code captured before any pipe; 56 exit 0 first pass, 4 exit 3 (no dist: check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity) cleared by a whole-tree turbo run build and then exit 0. --ran: "60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED".
  • CI: the PR's check-runs are the record for every family CI runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cwPmVRHj9NxSn1E2Xz

…s extension adds

blank-single-values, blank-multi-values, option-codes, text-scalars and
value-shapes scope by the served fold, which withholds a disabled package's
objectExtensions fields (their columns are kept). Each report now names every
in-scope column it skipped that way (object, field, package id), in the plan
and in the --json document as withheldColumns, so the operator re-runs after
enabling the package.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwPmVRHj9NxSn1E2Xz
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/deployment/app-dependencies.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/deployment/cli.mdx (via os migrate blank-multi-values (command, read off packages/cli/src/commands/migrate/blank-multi-values.ts), os migrate blank-single-values (command, read off packages/cli/src/commands/migrate/blank-single-values.ts), os migrate option-codes (command, read off packages/cli/src/commands/migrate/option-codes.ts), os migrate text-scalars (command, read off packages/cli/src/commands/migrate/text-scalars.ts), os migrate value-shapes (command, read off packages/cli/src/commands/migrate/value-shapes.ts))
  • content/docs/deployment/validating-metadata.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/frontend/plugins/plugin-chatbot.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/kernel/contracts/metadata-service.mdx (via getObject (symbol, a method of interface WithheldColumnsEngine), packageId (symbol, a field of interface WithheldColumn), getPublished (sdk, the bare tail of client method meta.getPublished, bound to GET /api/v1/meta/:type/:name/published; the bare tail of client method meta.getPublished, bound to GET /meta/:type/:name/published))
  • content/docs/permissions/authorization.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/permissions/capabilities.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/permissions/permission-sets.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/protocol/kernel/plugin-spec.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/protocol/objectql/state-machine.mdx (via /object/:name/state/:field (route, bridged from symbol getObject — its route source's handler names it))
  • content/docs/protocol/objectql/types.mdx (via os migrate value-shapes (command, read off packages/cli/src/commands/migrate/value-shapes.ts))
  • content/docs/ui/doc-pages.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/upgrading.mdx (via os migrate value-shapes (command, read off packages/cli/src/commands/migrate/value-shapes.ts))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via os migrate value-shapes (command, read off packages/cli/src/commands/migrate/value-shapes.ts), getLegalNextStates (sdk, the bare tail of client method meta.getLegalNextStates, bound to GET /api/v1/meta/object/:name/state/:field; the bare tail of client method meta.getLegalNextStates, bound to GET /meta/object/:name/state/:field), meta.getLegalNextStates (sdk, the route ledger binds it to GET /api/v1/meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field; the route ledger binds it to GET /meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field))
  • content/docs/releases/v17/17-2.mdx (via getLegalNextStates (sdk, the bare tail of client method meta.getLegalNextStates, bound to GET /api/v1/meta/object/:name/state/:field; the bare tail of client method meta.getLegalNextStates, bound to GET /meta/object/:name/state/:field), meta.getLegalNextStates (sdk, the route ledger binds it to GET /api/v1/meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field; the route ledger binds it to GET /meta/object/:name/state/:field, selected by route anchor /object/:name/state/:field))
  • content/docs/releases/v17/17-3.mdx (via /:type/:name/published (route, bridged from symbol isPackageDisabled — its route source's handler names it))
  • content/docs/releases/v18/18-0.mdx (via packageId (symbol, a field of interface WithheldColumn))
  • content/docs/releases/v18/18-3.mdx (via packageId (symbol, a field of interface WithheldColumn), os migrate blank-single-values (command, read off packages/cli/src/commands/migrate/blank-single-values.ts), os migrate option-codes (command, read off packages/cli/src/commands/migrate/option-codes.ts), os migrate text-scalars (command, read off packages/cli/src/commands/migrate/text-scalars.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 cross-cutting symbol(s) contributed no route anchor: packageId (4 routes)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 788715ef5cb205d3e70052d25a595e13a8aa9d7c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f40cda2cf9e4e2154231cd63d4c2018e341f47b0 — the merge of head b94187d76821d0ca670478789e543511a9e67857 into base 788715ef5cb205d3e70052d25a595e13a8aa9d7c, 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 f40cda2cf9e4e2154231cd63d4c2018e341f47b0 && git checkout f40cda2cf9e4e2154231cd63d4c2018e341f47b0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 788715ef5cb205d3e70052d25a595e13a8aa9d7c b94187d76821d0ca670478789e543511a9e67857 && git checkout -B drift-repro 788715ef5cb205d3e70052d25a595e13a8aa9d7c && git merge --no-ff b94187d76821d0ca670478789e543511a9e67857

node scripts/docs-audit/affected-docs.mjs --json 788715ef5cb205d3e70052d25a595e13a8aa9d7c

⚠️ 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 788715ef5cb205d3e70052d25a595e13a8aa9d7c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

维护者速读

  1. 改了什么:os migrate 的五个值改写命令(blank-single-values、blank-multi-values、option-codes、text-scalars、value-shapes)在报告里列出因所属模块包停用而跳过的列(对象.字段 + 包 ID),--json 多一个 withheldColumns。
  2. 为什么改:模块包停用时,它扩展到别的对象上的字段被隐藏,这些命令读不到,以前报告却看起来像全部处理过了(package disable: a disabled package's objectExtensions fields stay in /meta/object, /data and on the record page, while its objects answer 404 OBJECT_PACKAGE_DISABLED #30304 / PR fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 复审发现)。
  3. 风险:低。只多打印一段;读和改写的范围不变,仍然只处理当前对外可见的字段。
  4. 先看:packages/cli/src/utils/migrate-withheld-columns.ts(新文件)。
  5. 两分钟验证:pnpm --filter @objectstack/cli exec vitest run withheld-extension-columns。
  6. 需要你做的:无,席位落地。非 BREAKING,不属 Tier H,不超 3000 行。

席位意见:ACCEPT(#30626)。运维看报告就知道「启用那个包后再跑一次」。

席位核对

Head-sha: b94187d76821d0ca670478789e543511a9e67857 · read 2026-11-05T23:08Z

Item Reading
Shape Draft → main via relay pr_create; Fixes #30626. 9 files, +321/−1. ⛔ packages/objectql/** untouched (the card's Out of scope).
Route The card's "smallest honest answer" (name the skipped columns), not warn/refuse. Surface amended by the seat (6670480963): the helper lives in packages/cli/src/utils/ because every .ts under commands/** is an oclif command, and blank-multi-values shares walkBlankColumns.
Seat diff read withheldColumns(engine, candidates, columnsOf) reads the registry's extend contributors of disabled packages against the served fold, filtered by each walk's own column rule; each report and formatWithheld­Columns print object.field (package ID); value-shapes names every withheld field of the object (its column rule is the engine's, not exported). The registry is optional on each engine shape, so a caller without it names nothing.
Evidence Integration on a real ObjectQL + sqlite: per walk a CONTROL (enabled ⇒ column read, nothing named) and the disabled case (plan names each in-scope column with its package); unit test for the helper. cli unit 5435/5435, typecheck 0; dispatch-gates 60/60 (os-dev-report 6671396177).
Gates (check-runs on this head) 48: 35 success, 13 skipped, 0 failed; mergeable_state clean.
Acceptance notes value-shapes --apply still records its flag while withheld columns go unscanned (now named, not gated); other os migrate readers of getObject not measured. Neither has a named reader yet, so not filed.
Semver @objectstack/cli: patch, Clause-②: no, Metadata-protocol: none.
Landing Not governed (check-governed-merges --pr 30696: NOT governed), 322 lines ≤ 3000, not BREAKING, so per pm-dispatch the seat lands it: pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 23:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 89dbc74 Nov 5, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-9zmn2n branch November 5, 2026 23:29
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os migrate value rewrites skip a disabled module's extension columns without saying so

1 participant