Repository navigation
[metadata-core] sys_view_definition 的"活跃行唯一"从未生效——归档视图仍占用 (name, organization_id, owner) 名额 #5839
Description
Activity
分诊:
needs-user-decision+domain:engine-core。落点锚定:声明面在
packages/metadata-core/src/objects/sys-view-definition.object.ts⇒ 域表packages/metadata*⇒domain:engine-core。选项 1 的运行时迁移会照ensureOverlayIndex()的形状落在packages/metadata-protocol/src/protocol.ts,同域,不跨车道;SqlDriver.syncDeclaredIndexes只是被引用来解释「为什么 partial 从没生效」,不是修复落点,故不打domain:drivers(连带:#5499 的 driver 冻结不适用于本单)。过时前提核验(
origin/main9a15446,逐条实读):sys-view-definition.object.ts:125的idx_sys_view_def_active与:128的partial: "state = 'active'"仍在——即 ADR-0049 enforce-or-remove:IndexSchema.type与IndexSchema.partial没有任何 DDL 消费者 #5248 的退役尚未落地,正文引的「退役前原文」目前仍是 main 上的现状;- 全仓检索
idx_sys_view_def_active只有这一处命中(声明处),证实「没有任何等价的运行时迁移」。零命中项按纪律做了邻近词反查:同一手法搜ensureOverlayIndex在metadata-protocol有命中,证明扫描器与路径都是好的。
判
needs-user-decision的理由:三个选项不是修法细节,是产品语义——「视图名在归档后能否回收」。选 1 交付注释承诺的语义(行为变更,用户可见);选 2 零代码、把承诺改成现状;选 3 是第三种语义。正文自己写明「这是行为决策,不是机械修复」,分诊座位不代拍。决策卡的最小提问:归档/软删一个视图后,同名视图是否应当可以重建?- 是 ⇒ 选项 1(补运行时 partial UNIQUE + MySQL 侧降级)
- 否 ⇒ 选项 2(删注释里的 "among active rows",零代码)
拍板后即可摘标转
pm:queue,落点已锚定、无需重新分诊。查重(三仓 open issue + PR 各一遍):无影子单。关联而非重复:#5248 / #4943(
IndexSchema.partial退役,本条的发现路径,只删死键不改 DDL)。⚠️ 顺序注记:若选项 1 被采纳而 #5248 已先合入,partial声明键届时已不存在,选项 1 的实现要直接写运行时迁移、不要试图恢复该键——请拍板时把两单的先后一并考虑。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:
裁「名额可回收」→ 选项 1——补运行时 partial UNIQUE 迁移(照 ensureOverlayIndex 范式),归档视图不再占 (name, organization_id, owner) 名额;MySQL 无 partial index 走既有降级路径。
⚠️ 实现约束:#5248(partial 声明退役)在飞——本单直接写运行时迁移,⛔ 勿恢复已退役的 partial 声明键,两单落地顺序由 engine-core 车道排。流转:摘
needs-user-decision→pm:queue,归 engine-core 车道。评估与落地会话:
session_01N3uGFF8teXbpgtbEJ1aYXu
Generated by Claude Code
认领(执行席 PM,engine-core 车道,第 13 轮)
- 会话:
session_019Q7oc7ASjh8yxyS3Yz78We - 分支:
claude/issue-5839-view-active-partial-unique - 工作树:
../objectstack-issue-5839 - 文件面:
packages/metadata-core(sys_view_definition)+ 运行时 partial UNIQUE 迁移(照 ensureOverlayIndex 范式的落点)+ 测试 + changeset;⛔ 勿恢复已退役的 partial 声明键(裁决约束);开工先核 ADR-0049 enforce-or-remove:IndexSchema.type与IndexSchema.partial没有任何 DDL 消费者 #5248 现状定顺序
按 2026-08-06 维护者裁决(选项 1「名额可回收」)执行。
Generated by Claude Code
- 会话:
迁移:
domain:engine-core→domain:metadata(维护者 2026-08-07 批准拆分;座位贴 #6367,SKILL PR #6370)。落点证据:metadata-core/src/objects/sys-view-definition.object.ts的「活跃行唯一」部分索引从未生效,修复镜像 protocol.ts 的ensureOverlayIndex。全量分类审计 49 卡三判,本卡 MOVE/high。分诊会话:session_01BickTBKm2JYSNnrtPT8ysa。
Generated by Claude Code
ACCEPT(执行席 PM 验收,第 13 轮)+ 向 metadata 席报备
PR #6415 验收通过,已翻 ready + auto-merge(CI 25 项零失败)。按维护者裁「名额可回收」落地,记档:① 迁移落点
assembleMetadataProtocol+kernel:ready(两种挂载模式共用入口 + 表先建后迁);② 对 ensureOverlayIndex 范式的先探针后替换改良采纳 —— 范式的 DROP-CREATE 失败窗口会让表零唯一索引,其自身缺陷已立 #6418(附可照搬修法);③ 范式裸getDriver()在 kernel:ready 会炸 bootstrap 的真错被消费半径扫描抓出(16 个启动测试翻红)、已修 + 回归钉;④ 存量冲突结构上不可能(新索引严格弱于旧),残余 out-of-band 路径按 ADR-0120 D4 口径拒建点名;⑤ NULL-distinct 缺口(共享/环境级视图不受约束)刻意不越裁决收紧,立 #6417(Blocked-by 本单)交产品拍板;⑥ #5248 顺序自洽(其注释本就把本单排为后续)。@metadata 席(座位贴 #6367):本单为 2026-08-07 拆域前的在飞认领,按「在飞认领由原认领者跟完」由 engine-core 席收尾至 MERGED;落点(metadata-protocol/metadata-core)自此归贵席领地,衍生单 #6417/#6418 均在贵席范围,已由分诊常规通道流转。
Generated by Claude Code
- added a commit that references this issue
on Aug 8, 2026 - added a commit that references this issue
on Aug 23, 2026
发现于 #5248(
IndexSchema.type/partial退役)的生产者清点。不在该 PR 范围内——退役本身是零 DDL 变化,这条是被退役暴露出来的既有行为缺口,修它是行为变更。事实
packages/metadata-core/src/objects/sys-view-definition.object.ts声明:注释说的是"among active rows",但
partial从来没有任何 driver 消费者:SqlDriver.syncDeclaredIndexes走 knex 的table.unique(fields, { indexName }),无法表达WHERE。所以实际建出来的一直是无谓词的全量 UNIQUE 索引。实测(#5248 PR 里的
packages/drivers/driver-sql/src/declared-index-retired-keys.test.ts同一手法,真实 SQLite 存储的 DDL):两者逐字节相同,且都不含
WHERE。与
sys_metadata的关键差别sys_metadata的同类声明(idx_sys_metadata_overlay_active)有一个运行时兜底:packages/metadata-protocol/src/protocol.ts的ensureOverlayIndex()用 raw SQL 发CREATE UNIQUE INDEX … WHERE state = 'active'(并且顺带修了COALESCE(package_id,'')的 NULL-distinct 问题),所以那张表的活跃行范围是真的交付了的。sys_view_definition没有任何等价的运行时迁移——全仓检索idx_sys_view_def_active只有声明处一个命中。也就是说这张表的"活跃行唯一"在任何一层都没有实现。用户可达的后果
sys_view_definition有state字段(defaultValue: 'active')。一旦某个视图被置为非 active(归档/重置/软删),它的(name, organization_id, owner)组合仍然被全量 UNIQUE 索引占用,于是:两种读法下当前行为都不是声明所描述的那个。
可选修法(需要拍板,故不自行实施)
ensureOverlayIndex的形状,为sys_view_definition发 partial UNIQUE,MySQL 侧降级为普通复合索引 + 应用层 upsert 语义。交付注释所承诺的语义。state纳入索引列——(name, organization_id, owner, state),允许同名的一个 active + 多个非 active;但这不等价于 partial(多个 active 之间仍互斥,而多个同 state 的归档行会互斥),语义要单独想清楚。倾向 1 或 2:取决于产品是否要求视图名可回收。这是行为决策,不是机械修复。
相关
IndexSchema.type与IndexSchema.partial没有任何 DDL 消费者 #5248 / [spec/driver-sql]IndexSchema.partialis authorable but no driver ever emits the WHERE clause (declared ≠ enforced) #4943 ——IndexSchema.partial退役(本条的发现路径);该 PR 只删声明面的死键,不改任何 DDL,因此没有顺手改这里。sys_metadata侧同类问题的既有解法参考。