Repository navigation
[metadata] addSysMetadataOverlayIndex 用 ADR-0048 之前的键抢占 idx_sys_metadata_overlay_active——同名索引有两个生产者,先跑的赢 #6771
Description
Activity
Triage:
pm:queue+domain:metadata.- Anchors verified on
origin/main@e1e7629:add-sys-metadata-overlay-index.tsstill declares the pre-ADR-0048 key —COLUMNS = '(type, name, organization_id, environment_id, scope)'read directly from the file; both call sites live atpackages/metadata/src/loaders/database-loader.ts:345and:408. Two producers ofidx_sys_metadata_overlay_activewith different keys confirmed. - Routing: the stale producer lands in
packages/metadata(either deleted or key-aligned) ⇒domain:metadata. - Classification: concrete defect — boot-order-dependent silent non-enforcement of the ADR-0005/0048 overlay uniqueness invariant (restore-invariant class). The open direction question in the body (delete the producer vs align the key) hinges on a measurable fact — whether any
DatabaseLoaderdeployment shape never reachesensureOverlayIndex— which is the dev's measurement, not a maintainer ruling. No target, following family precedent [metadata-protocol] ensureOverlayIndex 先 DROP 后 CREATE:partial 索引建失败时 sys_metadata 会静默地失去覆盖层唯一约束 #6418 (no target). - Dedup: repo-scoped search for
idx_sys_metadata_overlay_activehits only [metadata-protocol] ensureOverlayIndex 先 DROP 后 CREATE:partial 索引建失败时 sys_metadata 会静默地失去覆盖层唯一约束 #6418 (PR fix(metadata-protocol): ensureOverlayIndex probes before it drops, and reports what it could not enforce (#6418) #6770 — ordering defect, scoped tometadata-protocolonly) and this card. Distinct defects, cross-referenced in the body already. Note for the dispatching seat: PR fix(metadata-protocol): ensureOverlayIndex probes before it drops, and reports what it could not enforce (#6418) #6770's landing flips which producer wins; both defects remain valid either way.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Anchors verified on
Claim: PM loop round 4 (
domain:metadataseat, sticker #6367)
Session:session_01W6bLax4KMrSfnE1ydFU8Dw
Branch:claude/issue-6771-single-overlay-index-producer
Worktree:objectstack-issue-6771
Domain:domain:metadata
File surface:packages/metadata/src/migrations/add-sys-metadata-overlay-index.ts+ its call sites inpackages/metadata/src/loaders/database-loader.ts(:345/:408) + tests + one.changeset/*.md;metadata-protocol'sensureOverlayIndexis read-only reference
Serial constraints cleared: PR #6770 MERGED (flips which producer wins — both defects remain, as triage noted). Direction hinges on the dev's measurement per triage: whether anyDatabaseLoaderdeployment shape never reachesensureOverlayIndex— measure, then delete-the-producer vs align-the-key, decided with evidence and documented; a genuinely unmeasurable fork is a STOP. Round-4 siblings touchmetadata-protocolfiles only — disjoint package surface.
Generated by Claude Code
发现于 #6418 的实施(为
ensureOverlayIndex找同名索引的既有生产者时逐行读到)。不在 PR #6770 范围内——那条只改packages/metadata-protocol。事实
idx_sys_metadata_overlay_active这一个索引名,树内有两个生产者,键不一样:ensureOverlayIndex(运行时,ADR-0048 现行)(type, name, organization_id, COALESCE(package_id, ''))WHERE state = 'active'packages/metadata-protocol/src/protocol.tsaddSysMetadataOverlayIndex(DatabaseLoader)(type, name, organization_id, environment_id, scope)WHERE state = 'active'packages/metadata/src/migrations/add-sys-metadata-overlay-index.ts:30-33第二个的键是 ADR-0005 Phase 1 的旧键,模块头注释自己写着
(type, name, organization_id, environment_id, scope)。而environment_id早已按 ADR-0005(2026-05 修订)退役——sys-metadata.object.ts的字段注释原文:「environment_idis no longer written by saveMetaItem and not consulted by overlay reads. Kept for legacy rows; new writes leave it NULL.」scope也不在现行判别式里。packages/metadata-core/src/objects/sys-metadata.object.ts的indexes声明同样用现行键(['type','name','organization_id','package_id']),并在注释里明确指认ensureOverlayIndex是真正交付 active 范围的那一层。所以旧键只剩这一个生产者还在写。后果
两个生产者都用
CREATE UNIQUE INDEX IF NOT EXISTS,所以先跑的那个占住名字,后跑的静默变成 no-op。调用点(
packages/metadata/src/loaders/database-loader.ts:345与:408)在DatabaseLoader.ensureSchema()里,两处的 catch 都是空的(// ignore — index is an optimization)。ensureOverlayIndex则由 7 个协议入口按需触发。谁先到取决于 boot 顺序,不由任何一处声明决定。addSysMetadataOverlayIndex赢的那条路径上,sys_metadata得到的是按旧键的唯一索引:environment_id现在恒为 NULL,scope有默认值,而 SQL UNIQUE 把 NULL 视为 DISTINCT —— 于是这个索引对environment_id IS NULL的行(即所有新行)根本不约束;package_id不在键里,两个包提供同名元数据会被判为冲突(ADR-0048 引入package_id正是为了拆开它们);ensureOverlayIndex的IF NOT EXISTS什么都不做——ADR-0005 现行的覆盖层唯一性一条都没落地。ensureOverlayIndex的探针会先建一个临时索引,再 DROP 正式名重建,所以运行时这一层现在会覆盖掉旧键索引(顺序依赖仍在,只是赢面反过来)。两者都不该继续存在。建议方向(未做决定,留给 triage)
看起来是 ADR-0048 迁移时漏掉的一处:
addSysMetadataOverlayIndex的键没跟着改。可选项至少有「删掉这个生产者,让运行时那层唯一负责」和「把键对齐到现行判别式」两条,取舍取决于DatabaseLoader路径上是否存在ensureOverlayIndex到不了的部署形态——我没有测量这一点,不在本单范围内。复现
读三个文件即可,无需运行:
add-sys-metadata-overlay-index.ts:30-33、database-loader.ts:345/:408、protocol.ts的ensureOverlayIndex。