Repository navigation
feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) - #23469
Conversation
…eclares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) WIP: the engine reads and writes that still name organization_id on these four tables are not yet changed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…ization_id on the sys_metadata family (C7b S2) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…organization read, byte-identical (C7b S2) The undeclared organization column makes that read inexpressible, so S2 carries the same hunks S1 lands first; the overlap merges clean. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…y; legacy uninstall path and legacy-row report deleted (C7b S2) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…s stop naming organization_id (C7b S2) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…shape database until the ownership ceremony is verified (ADR-0131 D10 item 5, C7b S5) WIP: tests not yet run. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… unique environment-wide before column-drops (seat answer Q2 A, C7b S2) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…on claude/issue-15211-c7b-promotions) The card (#23429) places the Q2 A renumbering in #15211 S1, and S1's branch carries it. S2 holds only the family's schema and its readers. This reverts commit 5326201. Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…tadata-family # Conflicts: # packages/metadata-protocol/src/sys-metadata-repository.ts
…stop naming organization_id; the last protocol filter goes (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…ation-scoped stored rows (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…op seeding legacy organization rows (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…e environment's rows only (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…ow the organization-free family; the legacy-row boot report's pin goes with it (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…ng legacy organization rows (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…ry's retired organizationId option; flow, audit and store pins follow (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…pins drop their organization legs (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…s left unused (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…tion scope (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…on-free family (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…ly's changeset (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
… the form door reads one view layer (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…rified marker and a fresh database boot (ADR-0131 D10 item 5, C7b S5) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…5, C7b S5) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…-free family and the boot refusal (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…e docs for the boot refusal; changeset wording (C7b S5) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…hape (C7b S5) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…anization rows (C7b S2) Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ Co-authored-by: Claude <noreply@anthropic.com>
…odule load (check:test-source-alias) (#23429) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ
Contract reviewServed-tier: Scope. This is one re-review of the combined S2 + S5 head (ruling 6180024026). It covers:
What was read.
Gate verdicts: 49 check-runs, re-read at 04:36Z after the last one finished. 42 succeeded, 7 were skipped, none failed and none is pending.
The landing tree.
① Derived judgments1. B1: met.
2. B2: met. No reader whose The seam (
How the record's five readers are covered:
Reads outside the seam:
The other packages the brief names.
The repository (
The two deliberate exceptions: right.
The single-row form's re-read past a legacy hit: correct.
The plural form filters after
The transaction join is explicit only.
Fails closed: yes.
3. B2 pins (
4. S5 on the combined head: holds.
5. The earlier S2 items from 6171128604 still hold.
6. The dogfood census: no change.
7. Public-surface and accept-set changes, each judged against the changesets:
8. Size and tier.
② Semver levelThe levels.
BREAKING, markers and placement.
Clause-②: yes (narrowing) ③ Boundary flags6167186090.
6168648434.
6170427033 and 6172828957. The body notes are the seat's; nothing is open. 6179430370.
6181023527. The per-hunk claims check out. "No overlapping hunk" in 6182409899.
6184164699.
Seat answers.
NOT MEASURED:
Implemented-by: VERDICT: PASS Non-blocking:
Generated by Claude Code |
Fixes #23429 · Part of #15211
Clause-②: yes (narrowing)
维护者速读
改了什么:本 PR 合并了 C7b 的 S2 和 S5(裁决 6180024026:两者合为一个 PR,#23461 已关闭并入此处)。
sys_metadata、sys_metadata_audit、sys_metadata_commit、sys_metadata_history)不再声明organization_id列(systemFields.tenant: false),索引去掉组织前缀后重建。protocol.ts里的同一个接缝,读物理列把遗留的组织行排除在外。读不到这一列时直接拒绝,不会当作已迁移的库。studio.access(E2)。os serve启动时,若某张已无组织列声明的表在物理上仍带organization_id,且仪式标记adr-0131-organization-ownership未验证,就拒绝启动,并点名os migrate organization-ownership。os migrate apply在仪式完成前拒绝删除清单内表上的organization_id列(E1)。为什么改:ADR-0131 D6/D7 规定元数据属于环境、这些表不再带组织列;D10 第 5 项要求 v18 运行时在旧库跑完仪式前拒绝启动。
风险与代价(含回滚):
os serve启动会被拒绝,必须先执行os migrate organization-ownership --out PLAN,再执行--apply --plan-file PLAN --backup-taken。没有绕过开关。single姿态下,普通成员通过数据门读 audit、history、commit 三张轨迹表会被拒绝。Studio 作者和平台管理员不受影响。席位意见:建议合入(ACCEPT 6184588618)。
main无冲突。single下普通成员读轨迹表被拒)是席位裁定的(E2 → A),changeset 已写明。main的全量测试;席位会盯首轮结果,红了立即修。你要做的:回复「可以进队列」即可。席位随后通过 relay 把本 PR 转为 ready 并开启自动合并,由合并队列在全绿后合入。
What changed
S2: the metadata family holds no organization column (ADR-0131 D6/D7)
packages/metadata-core/src/objects/sys-metadata*.object.ts): noorganization_idfield;systemFields: { tenant: false }. Indexes re-keyed:idx_sys_metadata_overlay_active→ (type,name,package_id,bundle_member);event_seq) and unique (type,name,version); its other indexes lose the organization prefix;idx_sys_metadata_org_typeis dropped.metadata-protocol/src/migrations/overlay-index.ts):OVERLAY_INDEX_COLUMNSand the duplicate probe take the new key.organization_idon the four tables, because the engine refuses an undeclared field (INVALID_FIELD). TheorganizationIdoption onSysMetadataRepositoryOptionsis removed.DatabaseLoaderandhistory-cleanup.tsacceptorganizationIdand ignore it, as they doenvironmentId.protocol.tsreads the family through one seam,findEnvironmentRows/findEnvironmentRow. Each runs the engine read, then leaves out the rows thatreadLegacyOrganizationRowsfinds on the physical column, joining the write's transaction.getMetaItem,getMetaItemLayeredviafindServedOverlayRow), the item lock (overlayLockLayerAt) and the package-less address (packagelessAddress, including fix(metadata-protocol): the read naming no package serves the row the package-less save writes, beside a package-unbound row (S6) #23489'spackagelessReadAddress) go through that seam. So do the list, draft, package, commit and history reads.migrateStoredMetadatareports such a row.revertCommitrefuses a legacy commit by name.metadata'sDatabaseLoader,plugin-security's security-catalog echo andservice-automation's flow-credential migration.LegacyOrganizationRowsUnreadable(codeLEGACY_ORGANIZATION_ROWS_UNREADABLE). It is never taken for a migrated database.SqlDriver.listPhysicalColumnsis added, so the seam can ask whether the column exists.isLegacyOrganizationRowis removed.studio.accessgatessys_metadata_audit,_historyand_commitin every posture (seat answers 6171152049, 6179490127). Undersingle, a plain member's data-door read of these three tables was a bypass of what the/metatrail doors refuse. That bypass is closed. The/metatrail doors admittingsetup.accessis a separate card, rest/runtime: under a wall, the /meta history, audit and package-commits doors admit setup.access, which organization admins hold, so an organization admin reads the environment-wide authoring trail of any item #23546.getMetaItems'legacyFormOrganizationIdand its callers in the REST form doors (seat 2's pointer 6165850696).packages/cli.security-catalog-overlaysreads the physical column through the raw-SQL seam (legacyOrganizationRowIds), so an unmigrated database's organization control row is never listed or deleted.18.sys-metadata{,-audit,-commit,-history}-organization-column-retired), and the census (scripts/platform-object-tenancy-census.json): the four objects move out of reach.S5: the v18 boot refusal (ADR-0131 D10 item 5; ruling 6163114098, Q3 → A)
OrganizationOwnershipBootGuardPlugin(packages/runtime/src/organization-ownership-boot-guard.ts). Atkernel:readyit asks whether a table whose object declares no organization column still carries a physicalorganization_id.sys_migrationmarkeradr-0131-organization-ownershipis not verified, it throwsOrganizationOwnershipBootRefusal(codeORGANIZATION_OWNERSHIP_MIGRATION_REQUIRED). The refusal names the tables and the command, and stopsbootstrap()beforekernel:listening.SqlDriver.dialectName, so the defaultSqliteWasmDriveris covered.os serveonly (packages/cli/src/commands/serve.ts), besideMigrationRecoveryPlugin. It is never increateStandaloneStackordata-migration-plugins.ts. Those are the CLI data and schema boots, the ceremony's ownbootSchemaStackamong them, and a guard there would refuse the one command that clears it.os migrate applyrefusesdrop_column organization_idon any table in the ceremony's inventory (ORGANIZATION_OWNERSHIP_INVENTORY) while the marker is not verified.os migrate organization-ownership --out PLAN, then--apply --plan-file PLAN, and exits 1. Under--jsonit answersrefused_organization_ownership_unmigrated.--allow-destructiverun, as Adopt better-auth's account-issuer rollback: drop sys_account.issuer, retire the backfill, raise the family to 1.7.3 #17440 and v18 upgrade drops stored comment reactions (9963322fe8 does not migrate sys_comment.reactions) — under the maintainer's rule an upgrade keeps data #23281 do: nothing in it is created, applied or dropped. A run without--allow-destructiveis unchanged.stack.applyMigrationEntriesdirectly and is not gated.ORGANIZATION_OWNERSHIP_MIGRATION_IDmoves to@objectstack/spec/system(migration.zod.ts), so the runtime and the ceremony read one constant.Both
ERROR_CODE_LEDGER:ORGANIZATION_OWNERSHIP_MIGRATION_REQUIRED(@objectstack/runtime) andLEGACY_ORGANIZATION_ROWS_UNREADABLE. The publishedApiError.code/ErrorCodeenum widens by both, and the changeset names it.gen:schema && gen:docs).contract.mdxreads… +333 moreonmainatfa6a6085c0.minor.registered, with the four ADR-0087 ids.already-registered.minor.service-automationis bumped.Landing
contract.mdxline, so two queued PRs would eject each other. S5 (ba6d9c5de8) is merged in asad23169210, and feat(runtime,cli)!: os serve refuses a v17-shape database until the organization-ownership ceremony has verified it (ADR-0131 D10 item 5, C7b S5) #23461 is closed unmerged as superseded.mainas869a381dc9, so the ceremony promotes before it drops (6163669810).Measured choices
main. The build-ahead's copy (5326201dff) is reverted here (4ba2ea3c), not rewritten.main's S1 version, except the form-door organization read, which S2 deletes.MigrationRecoveryPluginis also composed by the CLI data-migration boot that the ceremony runs on, so a refusal inside it would refuse the ceremony itself.kernel:ready, after every plugin'sstart()and its additive schema sync, so S3'ssys_user_permission_set.permission_setalready exists when the ceremony runs. The dogfood pin below proves the order end to end.Pins
S2's unmigrated-database pins (
packages/cli/test/meta-stored-legacy-organization.integration.test.ts): a real engine on a v17-shape SQLite database, with the column added raw, run on the ceremony's own--applyboot.door.saveof an item an organization holds bound to a package writes an environment row, and leaves the organization row untouched._lock: fullis not the item's lock.kernel:readysecurity-catalog echo resolves the environment's permission overlay, not the organization's.overlayLockLayerAtput back on the raw read turns the lock pin red.E2: the 14-case
studio.accesspin on the real SecurityPlugin middleware.S5's guard:
organization-ownership-boot-guard.test.ts(unit, real SQLite) andorganization-ownership-boot-guard.kernel.test.ts, which runs on a realObjectKernel. They cover:E1:
apply-organization-ownership-gate.integration.test.ts, real CLI, 5 cases:The upgrade order, end to end (
organization-ownership-boot-refusal-order.dogfood.test.ts):sys_user_permission_set.permission_set;os migrate organization-ownership --out PLAN, then--apply --plan-file PLAN --backup-taken, verifies the marker with no boot in between;Dogfood runs on
main's full battery, not on PR CI.Acceptance notes
rowsRead,ServedOverlayRowCandidate.scope/organizationIdandorgId/itemOrgIdinprotocol.ts. Every row that reaches them is now the environment's, so they are inert. Removing them changes the list merge's signatures across the file, a behaviour-neutral refactor.listPhysicalColumnswas read, not run./metatrail doors admitsetup.access;dialectOf/resolvePlannedDriverExecreadconfig.clientas a string only.Verification
Latest round, on head
e4811b67f2(the dev's report 6184164699, all in the foreground):metadata-coremetadata-protocolobjectqlrestruntimeplugin-securityservice-automationservice-datasourcedriver-sqlmetadatacliunitcliintegration (the full project)sys_metadataor the ceremony)metadata-protocol,metadata,service-automation,plugin-security,driver-tursoandcli.check:docsandspec check:generatedgreen;check-i18n-bundlesOK.node scripts/pm/dispatch-gates.mjs --commandsderived 159 families, and all 159 ran; 154 exited 0.check:test-source-aliaswas red on the new pin's in-body import and was fixed bye4811b67f2. Four are NOT MEASURED, because they refused on a local prerequisite; CI runs them:check-engine-split-ratioandcheck-plugin-teardown-shape --self-test: the shallow clone;check:skill-examplesandcheck:dual-build-cjs-loads: unbuilt packages.Earlier rounds: os-dev-reports 6168648434, 6170427033, 6179430370, 6181023527 and 6182409899 on #23429.
Contract reviews:
e4811b67f2: PASS 6184549574. Its non-blocking items are carded as metadata: after #23469, the legacy-organization read seam's loose ends (ambient transaction, filter-after-limit, an unpinned by-name filter, DatabaseLoader._count, a 404 where a named refusal belongs) and the ceremony wording #23609.Body rewritten for the one-PR form by
domain:engineseat 1 (session_01Rerax7QTjKMPCUZxQUtPFR) from the dev's notes and #23461's body; the 维护者速读 was finalized after the PASS at 2026-10-15T04:43Z.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR