refactor(spec)!: retire indexes[].type 与 indexes[].partial —— 两个零 DDL 消费者的声明键 (#5248, #4943) - #5842
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…, #4943) Both keys were authorable with zero DDL consumers. `syncDeclaredIndexes` creates every declared index through knex's `table.index()` / `table.unique()`, and the drift differ's `DeclaredIndexInput` carries `name`/`fields`/`unique`/`nullSafeColumns` — so an authored `type` selected no access method and an authored `partial` produced a FULL index with the predicate silently discarded. `type` additionally carried `.default('btree')`, materializing an inert knob into every parse output (ADR-0078). Retirement kit: `retiredKey()` tombstones at the bottom of the IndexSchema shape (#5606 renderer note), ADR-0087 conversion `object-index-type-partial-removed` opening the protocol-18 step, both real producers flipped (`sys_metadata`, `sys_view_definition`) with their comments corrected, published skill + docs + liveness note + generated baselines. DDL equivalence is proven against the statements SQLite actually stores, not asserted: packages/drivers/driver-sql/src/declared-index-retired-keys.test.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
`toMajor: 18` → `17`; `step18` deleted and `object-index-type-partial-removed` wired into the existing step-17 chain (its rationale extended). Tombstone prescriptions now say "removed in @objectstack/spec 17.0.0" and point at `os migrate meta --from 16`, matching every other toMajor-17 entry. Skill, docs, liveness note and changeset retargeted; generated artifacts (spec-changes.json, upgrade guide, reference docs) regenerated. Changeset stays `major`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
os-zhuang
force-pushed
the
claude/issue-5248-index-type-partial-retire
branch
from
August 6, 2026 08:41
1e37ac2 to
62ec212
Compare
os-zhuang
marked this pull request as ready for review
August 6, 2026 08:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5248
Closes #4943
按 2026-08-06 维护者裁决(#5248 评论区):
type与partial双双 remove,走 spec-property-retirement 全套。前提复核(rebase 到
origin/main@4615a1866)issue 正文的前提成立,但有一处路径已过时:
packages/plugins/driver-sql/src/sql-driver.tspackages/drivers/driver-sql/type/partial无 DDL 消费者逐站点核对:
SqlDriver.syncDeclaredIndexes(sql-driver.ts:5279)对每个声明索引只解构const { name, columns, unique } = norm,再走 knex 的table.unique(columns, { indexName: name })/table.index(columns, name)—— knex 两个 builder 都无法表达访问方法或谓词。normalizeDeclaredIndex(schema-drift.ts:970)只读fields/unique/nullSafeColumns/name;DeclaredIndexInput(:946)的接口字段就是这四个,从来没有type/partial。packages/**/examples/**无任何读取点。消费者清点表
namesyncDeclaredIndexes(索引名)fieldssyncDeclaredIndexes(列)uniquesyncDeclaredIndexes+ ADR-0120 D3/D4 的'organization'scopetypepartialtype是更响的一半:它带.default('btree'),所以会出现在每一次 parse 的输出里 —— 一个从未影响过任何语句的访问方法旋钮,被渲染成生效配置(ADR-0078 形状)。这一点甚至被钉进了测试:service-realtime的sys-presence.object.test.ts断言type: 'btree',而sys-presence.object.ts根本没声明过type—— 断言钉的是默认值物化出来的幻影。partial是更危险的一半:它读起来像正确性控制。平台自己的sys_metadata就用它声明 overlay 唯一性,而声明单独物化出来的是无谓词的全量唯一索引。⛔ 勿伤确认
sql-driver.ts的introspectIndexes/parseIndexDdl/schema-drift.ts的isSyncReproducibleIndex读的partial是partial: boolean,从数据库自身的CREATE INDEXDDL 反向解析而来,供漂移检测豁免 DB 自建的部分索引。方向相反(DB → 我们,而非我们 → DB),类型也不同(boolean vs string 谓词),一个字未动。DeclaredIndexInput本就无这两键,未动。先证红 → 转绿闭环
方向预先写死:退役前两键 parse 照收(绿),墓碑落地后携带两键的 parse 拒收且报文含处方(红转绿)。
退役前(基线实测)
退役后
反向验证(把删掉的肢体接回去) —— 预测方向是标准的 Red:把两键恢复成活键后,新增的钉子应当全红。实测 7 条全红,且失败原因正确(
expected true to be false,键又被接受了),不是附带原因:恢复后重跑 178 passed。
DDL 等价性 —— 实测而非断言
生产者翻转的核心主张是"删声明零行为变化"。这条主张被钉成了常驻测试(
packages/drivers/driver-sql/src/declared-index-retired-keys.test.ts),对真实 SQLite 存储的 DDL 逐字节比对,而不是比对中间结构:两两完全相同,且都不含
WHERE—— 这正是谓词一直无效的原因。生产者翻转清单
packages/metadata-core/src/objects/sys-metadata.object.tsidx_sys_metadata_overlay_activepartial;注释改写为诚实版本packages/metadata-core/src/objects/sys-view-definition.object.tsidx_sys_view_def_activepartial;注释改写(两个 issue 都没点名的第二个生产者)packages/services/service-realtime/.../sys-presence.object.test.tstype: 'btree'幻影断言packages/drivers/driver-sql/.../sql-driver-overlay-index-drift.test.tssys_metadata的注释此前写着"this declaration is the fallback shape for drivers without the runtime migration",隐含 fallback 也是活跃行范围的 —— 实际不是。已改写为:声明一直物化的就是无限制唯一索引,真正交付活跃行范围的是metadata-protocol的ensureOverlayIndex运行时迁移。sys_view_definition情况更重:它没有任何等价的运行时迁移,所以"活跃行唯一"在任何一层都没实现。这是既有行为缺口、修它属行为变更,不在本单,已单独立 #5839。退役套件
retiredKey()(IndexSchema是刻意的.strip(),平删会变成静默剥离 —— 用一个 no-op 换另一个 no-op,DataQualityRulesSchema 是 2026-06 字段剪除留下的孤儿:字段键已删,schema + 类型仍公开导出 #3726/ComputedFieldCacheSchema 是 2026-06 字段剪除留下的第二个孤儿(#3726 表格误记为「已清理」) #3733 的 ADR-0104 类)。放在 shape 最底部([docs-gen] 生成的 reference 把retiredKey()墓碑渲染成any—— 嵌套两层时连[REMOVED]处方都没有,退役键读起来像自由槽 #5606 渲染器注记)。object-index-type-partial-removed,toMajor: 17,并入既有step17(该步 rationale 已扩写);同时走objects[]与objectExtensions[](后者也内嵌IndexSchema);retiredFromLoadPath: true(墓碑负责教活作者,本条保证 ≤16 存量行经applyConversionsToStoredItem重放干净);fixture 3 条 notice。liveness/object.json的indexes仍为live(活键确实驱动 DDL),note记录两个子键的退役 + 勿与 driver 侧partial: boolean混淆,加verifiedAt。authorable-surface.json两行转… [RETIRED];authorable-surface.base.json锚点前移到本单 base(纯增 8 行、零删除,未挟带任何他人的删除,check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 相符);spec-changes.json/ 升级指南 / reference docs 重生成。skills/objectstack-data/(SKILL.md + rules/indexing.md)—— 原文大篇幅教这两个键(索引类型表、Partial Index 小节、全文/GIN/GIST 例子),改写为"声明面只有三个键 + 访问方法与部分索引属数据库层"。content/docs/data-modeling/objects.mdx(含一个{/* os:check */}编译块)。@objectstack/specmajor(退役类惯例:workflow-slot-retired/theme-inert-token-scales-removed/tool-inert-keys-removed皆是;墓碑立即在 parse 层硬拒,确属破坏性),含 FROM → TO 映射表与一行修法。未动
docs/adr/0005-...md:其partial代码块是 ADR 当时的历史快照(同一块里还留着早已退役的project_id/scope),且紧邻的正文本就写着"Drivers ignoreindexesdeclarations on synced tables today, so a new idempotent migration is provided"—— 与本次退役理由一致。按 Prime Directive #13,历史决策记录不在代码 PR 里改写。验证(retarget 后复跑)
pnpm --filter @objectstack/spec testdriver-sql/metadata-core/service-realtimetesttypecheck(spec / driver-sql / metadata-core)pnpm check:i18nnode scripts/check-nul-bytes.mjs[\x00-\x08\x0b\x0c\x0e-\x1f]自扫,零命中)链路落点确认(生成物自证):
spec-changes.json的 17 段新增本条 conversion;升级指南object-index-type-partial-removed行标记retired — migrate meta only;step17 rationale 已含本次退役段落。相关
sys_view_definition的"活跃行唯一"从未生效——归档视图仍占用 (name, organization_id, owner) 名额 #5839(本 PR 新立):sys_view_definition的活跃行唯一从未交付 —— 由本次清点暴露,修法属行为变更,不在本单。retiredKey()墓碑渲染成any—— 嵌套两层时连[REMOVED]处方都没有,退役键读起来像自由槽 #5606:本次退役新增两个实例(ObjectSchema.indexes/ObjectExtensionSchema.indexes内联摘要印出type?: any)。IndexSchema退役后只剩 3 个活键 + 2 个墓碑,INLINE_KEY_LIMIT = 4,"把墓碑挪到 shape 底部"这个规避在数学上不可能生效 —— 已在 [docs-gen] 生成的 reference 把retiredKey()墓碑渲染成any—— 嵌套两层时连[REMOVED]处方都没有,退役键读起来像自由槽 #5606 登记该实例并说明只有渲染器的never分支能修;按该单正文要求不搭车修。wherevspartial、枚举里的brin—— 阻塞 #4001 对 IndexSchema 的收紧 #5247(Blocked-by 本单):console 索引编辑器的where/brin输入项现在编辑的是两个已不存在的键,按裁决改为"随退役删除输入项"。IndexSchema的收紧随本单解锁 ——IndexSchema的.strip()仍刻意保留(注释已更新),因为 console 的where控件仍在渲染,闭合会 422 掉平台自己渲染的控件(控制台保存筛选条件会 422:ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114 类),须等 console 的内嵌索引编辑器与 IndexSchema 漂移:wherevspartial、枚举里的brin—— 阻塞 #4001 对 IndexSchema 的收紧 #5247 修完生产者。🤖 Generated with Claude Code
https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D