Skip to content

fix(metadata-protocol): os migrate packageless-overlays also moves overlay rows bound to the read-only base itself into its .custom pair - #29477

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

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-4uryde

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #29392

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:os migrate packageless-overlays 的盘点现在也列出「绑定在只读基础包本身」的视图、仪表盘、报表、翻译、邮件模板行(metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 关门前用 ?package=基础包 保存留下的),--apply 把它们像无包行一样搬进 .custom 配对包并声明 overrides(或作为配对包自己的语言成员),结果词不变。
  • 为什么改:这类行一直盖住基础包文档,但盘点只读无包行看不到它们,唯一出路是删除,删除会丢掉定制。
  • 风险与回滚:只多搬行、不拒绝任何写入;同一项既有无包行又有绑定行时先搬无包行,绑定行保留并报 failed,下次预览列为 shadowed,绝不覆盖。回滚即撤销本 PR。
  • 先看:packages/metadata-protocol/src/protocol-packageless-overlay-migration.ts 的 listPackagelessOverlays(binding 判定)与 migratePackagelessOverlays(先后顺序与 movedFrom 防覆盖);packages/cli/src/commands/migrate/packageless-overlays.ts 的渲染。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/protocol-packageless-overlay-migration.test.ts -t 29392(需先 build metadata-protocol),应见 6 个用例全过。
  • 需要你做的:无,席位落地。

What changes

The census (listPackagelessOverlays, protocol-packageless-overlay-migration.ts). Besides the package-less rows, every row of the five overlay types whose package_id is a READ-ONLY code package that ships the name is now in the population: a row bound to the base itself, as a save naming the base stored it before #28905 closed that write. "Read-only package that ships the name" is the reading the #28905 close calls packaged (overlayBaseShippers = readOnlyShippingPackages over shippedArtifactsOf), the same function the census already reads for a package-less row. Such a row:

  • is classified by the same classifyPackagelessRow, with its binding as the one shipper, so a name several packages ship is never multi_shipped here (the binding names the base);
  • carries boundToBase: true (a new optional field on the census row and in the cli --json rows);
  • takes addedMember exactly as a package-less row does (an email template locale the base does not ship).

A row bound to a writable package (the pair included) or to a read-only package that does not ship the name is not listed. Package-less rows are classified exactly as before.

The move (migratePackagelessOverlays). Unchanged steps: ensure the pair, save the declared body (or the undeclared own member) into the pair through the save door, read it back, then delete the source row by its own address, now packageId: BASE for a bound row. Same outcomes (moved with basedOn, or moved with addedMember); no new outcome word.

One item, two bindings. A package-less row and a row bound to the base of the same item would both land on the pair's one row. The move takes the package-less rows first; a row of an item this run already moved a row of the other binding into is kept, reported failed ("'PAIR' now holds this item … kept beside it, never merged"), and the next preview lists it shadowed with that disposition's two ways out. Nothing is overwritten.

The cli (packageless-overlays.ts, render only): the preview names a bound row ("bound to PACKAGE → PAIR"), --json rows carry boundToBase, and a bound foreign_declaration row's remedy names the delete naming the base and the save into the pair (a save naming the base is closed now, so the package-less row's "remove the key in place" remedy does not apply). Help text and three wording spots no longer say "package-less" where the row may be bound.

Changeset: .changeset/29392-packageless-overlays-base-bound.md, patch for @objectstack/metadata-protocol and @objectstack/cli.

Tests

  • packages/objectql/src/protocol-packageless-overlay-migration.test.ts, new describe [#29392] (6 tests, in-memory topology, rows laid down through the engine as an older release stored them):
    • the dry run lists active and draft rows bound to the base, movable, with base and pair, writing nothing; --apply moves both with overrides: { package: BASE, basedOn: '1.2.0' } in their own state; the layered read at the base shows the base's document as the code layer and the customization as effective; the five reads serve the customization; a re-run moves 0;
    • a name two read-only packages ship, bound to one: movable into that base's pair, not multi_shipped;
    • a bound foreign_declaration and a bound shadowed row: listed and left;
    • a package-less row and a bound row of one item: the package-less one moves, the bound one is kept failed, nothing overwritten, next census lists it shadowed;
    • CONTROL: a package-less row still moves as before; rows bound to a writable package and to a read-only package that does not ship the name are not listed.
  • packages/objectql/src/environment-overlay-email-locale-restart.test.ts, one new case in the migrate: os migrate packageless-overlays fails on an email template row of a locale the base does not ship, so after #28576 that row can be neither written nor migrated #28904 describe: a zh-CN row of a template the package ships en-US only, bound to the package: listed movable + addedMember + boundToBase, moved as the pair's own member with no overrides, still sent in zh-CN at once and after a restart.
  • packages/cli/src/commands/migrate/packageless-overlays.integration.test.ts (SQLite topology): a second template database with a row bound to the package; the preview lists it and writes nothing; --apply moves it with the declaration, keeps the package-less control, a fresh boot serves the customization at all four reads, a re-run moves 0. The seeding moved into one insertRows helper (both templates use it); the existing three cases are unchanged.
  • No forced edits to existing tests.

Acceptance notes

Verification

Red, green and ablations measured on d74ab15d..7ebc8019 (the implementation is unchanged since); suites, typecheck, gates and lint re-run on the final head 30bfafa62 (main merged at a64416366), tree objectstack-ai/objectstack.

Red on main, then green

protocol-packageless-overlay-migration.ts restored from origin/main 7f277a8 in the worktree, metadata-protocol rebuilt (boundToBase in dist: 0), objectql file run: the 5 bound-row pins red, the dry run answering [] for the bound rows (+ [] against the two expected movable … bound lines). The CONTROL case was red in that run too, from an expected-order slip in the test itself ([OTHER, WORKSPACE] sorted), fixed before the green run; the email bound case was written after. Restored to HEAD, rebuilt (dist hits 3): protocol-packageless-overlay-migration.test.ts + environment-overlay-email-locale-restart.test.ts 68/68.

Ablations (rebuild + dist preflight each leg)
  1. The bound arm dropped (if (binding !== undefined && !shippers.includes(binding)) continue; → every bound row skipped), via scripts/ablation-replace.mjs, marker in both dist files: objectql 6 failed / 63 passed (the 5 bound-row pins plus the email bound case; CONTROL stays green); cli integration 2 failed / 3 passed (both bound-row cases). Restored: blob == HEAD, git diff HEAD empty, rebuilt, --absent preflight clean.
  2. The collision guard dropped (movedFrom check → never true): objectql 1 failed / 68 passed, the two-bindings case (AssertionError … ['package-less','moved'], … second outcome no longer failed). Restored the same way, --absent clean.
Suites and typecheck (under os-verify-lock, after a full `./packages/**` + `./examples/**` build)

Final head 30bfafa62, after pnpm install --frozen-lockfile and turbo run build --filter='./packages/**' --filter='./examples/**' (VERDICT command-exit 0, held 41m42s for the whole round, shared box):

  • pnpm --filter @objectstack/metadata-protocol typecheck exit 0; @objectstack/objectql exit 0; @objectstack/cli exit 0
  • metadata-protocol vitest run: 22161 passed, 26 skipped (22187)
  • objectql vitest run: 10326 passed (10326)
  • cli --project unit: 5267 passed (5267)
  • cli --project integration src/commands/migrate/packageless-overlays.integration.test.ts: 5 passed (5). The rest of the integration tier is declared to CI.
Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 30bfafa62: 62 commands, every one exit 0 (the same list as before the merge). --ran reconciliation: "62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED". check:type-check-debt re-measured 0 raw tsc errors, none above its record. check-plugin-teardown-shape --self-test needs its pinned fixture commit fetched first (exit 3 before, 48 cases pass after).

pnpm lint (repo-wide eslint, --no-inline-config): exit 0.

Dogfood: not owed (no !, no narrowing arm).


Generated by Claude Code

@github-actions github-actions Bot added the size/m label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/metadata-protocol, touching 8 documentable anchor(s).

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

  • content/docs/concepts/metadata-lifecycle.mdx (via os migrate packageless-overlays (command, read off packages/cli/src/commands/migrate/packageless-overlays.ts))
  • content/docs/deployment/cli.mdx (via os migrate packageless-overlays (command, read off packages/cli/src/commands/migrate/packageless-overlays.ts))
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 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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 — 42 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 186a83ab5d59986698a5b6d975d9e950286d05fc → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 186a83ab5d59986698a5b6d975d9e950286d05fc

⚠️ 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 186a83ab5d59986698a5b6d975d9e950286d05fc → 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#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

席位核对 · ACCEPT · PR #29477 · 2026-11-02T16:06Z

Head-sha: 30bfafa6245d488642029d837000a7a31f18c138

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #29392,与认领 6579240419 一致;6 个文件 +353/-36,都在申报面内(迁移模块与测试、CLI 仪式与集成测试、本卡 changeset,以及一份同族测试)。无被迫的测试改动。
  2. 前提实测:席位读了净 diff。绑定行只在其绑定的包正是该名字的出货包时才列入,并以该绑定作为唯一出货方,交给同一个 classifyPackagelessRow 分类。搬迁时无包行排在前,同一项已被另一种来源搬过就报失败、保留原行,源行按自身绑定删除。对照测试在:绑定可写包的行、绑定不出货该名字的只读包的行都不列入,无包行照旧搬迁。本面无须契约复审:无已发布 schema、无受管路径,Clause-②: no。
  3. 门禁:dev 报告(6581838503):typecheck ×3 绿;metadata-protocol 22161、objectql 10326、CLI 单元 5267、仪式集成 5/5;dispatch-gates 62/62 exit 0;pnpm lint 0。消融两处(绑定分支、冲突守卫)各转红,恢复后 --absent 干净。入队前席位读本 head 全部 check-run。
  4. 接受的偏差:dev 容器里留了一份 2 KB 的日志文件,与仓库无关,接受。
  5. 开放问题裁定:(1) A,不调用 namedBaseCloseSubject,只用它的「已打包」判读作为清点范围。它的可行性步骤会把修不了的行和 added-member 行重新藏起来,违背本卡目的;两个谓词的合并归 metadata-protocol: fold the two overlay-write close predicates (namedBaseCloseSubject, packagelessCloseSubject) into one fix-can-be-taken predicate #29397。(2) A,--json 保留新字段 boundToBase。这是运维报告的增量字段,不是接受集或导出,Clause-②: no 成立。(3) A,冲突时无包行优先。(4) A,已立 metadata-protocol: the #28905 overlay-write refusal sentence does not name os migrate packageless-overlays, the ceremony that now moves a base-bound row into its pair #29479(p3 建议):metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 的拒绝句点名本仪式。过时的 docblock 随 metadata-protocol: fold the two overlay-write close predicates (namedBaseCloseSubject, packagelessCloseSubject) into one fix-can-be-taken predicate #29397 一并处理。
  6. 落地条款:Clause-②: no,Metadata-protocol: none,PR 正文与 changeset 都有。semver:@objectstack/metadata-protocol、@objectstack/cli patch。check-governed-merges --pr 29477:NOT governed(入队前复核)。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

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