Repository navigation
Description
Blocked-by: #5248
发现于 #4001 批 20(data/object.zod.ts 内层块收紧)的逐站点测量。本 issue 只记录,不在批 20 里修——批 20 因此有意保留 IndexSchema 未收紧,是该批 14 个站点里唯一没关的一个。
症状
objectui 的 console 为 object.indexes[] 里的条目自带了一份手抄的 JSON-Schema:
packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx→FALLBACK_SCHEMAS.index
它存在的原因是合理的:index 是内嵌专用子类型,没有自己的元数据类型,框架没有槽位发布它的 schema(文件里的注释也这么写:"Once / if the framework grows an embedded-sub-type registry, this can move server-side too.")。注释同时声称这些 fallback "mirror the framework's Zod definitions" —— 而它已经不再 mirror 了:
| console 表单提供 | IndexSchema 声明 |
后果 |
|---|---|---|
where(标题 "Partial-index predicate") |
partial |
键名不同,当前被静默丢弃 |
枚举含 brin |
['btree','hash','gin','gist','fulltext'] |
值非法,今天就会被拒 |
为什么它现在是静默失败
编辑器的保存路径(同文件,spliceEmbedded → client.save(parentType, …))把表单结果拼回父对象再 PUT 整个对象。服务端 saveMetaItem(packages/metadata-protocol/src/protocol.ts)对 body 做 safeParse 并在失败时 422,但逐字保留原 body。
IndexSchema 目前是 zod 默认的 .strip,所以管理员在 "Partial-index predicate" 里填的内容:
- 保存成功,没有任何提示;
where作为未声明键被 spec 在每次后续 parse 时丢掉;- 即使它没被丢掉也没用 —— 见下。
两个方向都是死的
partial 和 type 都没有任何 DDL 消费者。driver-sql 的 syncDeclaredIndexes 只读 name / fields / unique(以及 ADR-0120 D3/D4 之后的 nullSafeColumns);DeclaredIndexInput(packages/plugins/driver-sql/src/schema-drift.ts)的字段列表就是这四个。
注意区分:
sql-driver.ts里确实有读partial的地方(introspectIndexes/parseIndexDdl),但那是从数据库自身的 DDL 解析出来做漂移检测的,和声明式元数据里的IndexSchema.partial无关。
为什么这会阻塞 #4001
关掉 IndexSchema 会把上面第 1 步变成 422,而 422 打在 console 自己渲染的控件上 —— 这正是 #5114 那一类(区别是这次在发布之前抓到)。
但"先修生产者再关"还不够。因为 partial 本身也是死的,收紧后把作者指向 partial,是一条声称超出平台实际交付的 guidance —— 台账 finding 18 记录的正是本战役已经发过的四条假 guidance。
所以关掉 IndexSchema 至少需要两件事先落地:
- 生产者侧(contract-first):objectui 把
where改成partial,并把枚举对齐 spec(去掉brin,或在 spec 里声明它——后者需要有驱动读它)。 - ADR-0049 enforce-or-remove:给
IndexSchema.type和IndexSchema.partial一个结论——要么接上驱动,要么退役。
顺带值得考虑的第三条:这类 fallback schema 本身就是漂移源。框架长一个内嵌子类型注册表(把 index 的 schema/form 发布出去,像 validation 已经通过 HAND_CRAFTED_SCHEMAS.validation 做的那样)能一次性消灭这个复制品。
现状记录在三个地方
packages/spec/src/data/object.zod.ts的IndexSchemaJSDoc(明确写了"不要顺手补完");packages/spec/src/data/object-strictness-batch20.test.ts§4 —— 把当前的 strip 行为钉住,使它改变的那天是有意改变;docs/audits/2026-07-unknown-key-strictness-ledger.md的data/行。
复现(当前 main + 批 20 分支上行为一致):
ObjectSchema.parse({
name: 'x', label: 'X', fields: { name: { type: 'text', label: 'N' } },
indexes: [{ fields: ['name'], where: "status = 'open'" }],
}).indexes[0].where // => undefined,静默丢弃
Activity
docs(spec): 把批 19/20 的两个保留判定接到已归档的 issue 上(#5247/#5248/#5249)
os-zhuang commentedon Aug 5, 2026
分诊结论:
pm:queue+repo:objectui已在
origin/main复核两侧代码,漂移仍然存在,与 issue 描述完全一致:packages/spec/src/data/object.zod.ts(第 309-341 行)IndexSchema声明的是partial(string,可选)与type: z.enum(['btree','hash','gin','gist','fulltext'])。packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx的FALLBACK_SCHEMAS.index表单字段名是where(标题 "Partial-index predicate"),type枚举里是brin而非fulltest。packages/plugins/driver-sql/src/schema-drift.ts的DeclaredIndexInput(第 946-953 行):只有name/fields/unique/nullSafeColumns四个字段,partial和type确实没有任何 DDL 消费者,与 issue 引用的结论一致。跨仓去重:搜索了 objectstack/objectui/cloud 三仓的 open issues 与 PRs(关键词
IndexSchema、FALLBACK_SCHEMAS、EmbeddedItemEditor),没有发现在途的竞争 PR 或未认领的重复 issue。PR #5250(已合并)是本 issue 的源头 PR(#4001 批 20),#5247/#5248/#5249 是它派生出的记录型 issue,不是重复。分类为
pm:queue:这是一个有明确文件位置和可复现的具体缺陷(字段名不一致 + 枚举值不一致),按 issue 自己的框定,生产者(objectui 的手抄 fallback schema)是漂移的一侧,spec 侧的partial命名是既有约定 —— 因此修复应落在 objectui,标repo:objectui(而非domain:spec)。需要指出:issue 正文里同时说明,仅改名还不足以关闭 #4001 对
IndexSchema的收紧,因为partial/type本身在 driver-sql 侧仍是死键,真正解除阻塞需要本 issue(生产者对齐)与 #5248(ADR-0049 enforce-or-remove 结论)两者都落地。这一阻塞关系已经记录在 PR #5250 与两个 issue 的正文里,故未对 #4001 做任何编辑,仅在此评论中说明关系:Blocks: #4001(经由 #5248 联合阻塞)。「本评论来自分诊座位 Routine(#5474 试点),不构成认领」
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
前提更新 + 挂
pm:blocked(Blocked-by: #5248,已写入正文首行):维护者 2026-08-06 已裁IndexSchema.type/partial双双退役(见 #5248 裁决评论)。本单修向随之改变——不再是「where→partial对齐拼写」,而是随退役同步 console 编辑器:FALLBACK_SCHEMAS.index删除where/"Partial-index predicate" 输入项与brin枚举值,只保留name/fields/unique(含 ADR-0120 的 scope 语义)。#5248 的退役 PR 落地后解锁 sweep 回捡本单。正文第三条建议(内嵌子类型注册表消灭 fallback 漂移源)仍然成立,可在实施时评估顺手程度或另立单。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
feat(spec)!: object 内层块拒绝未知键 —— 13 站点收紧,2 个站点依据实测保留 (objectstack-ai#4001
os-project-manager commentedon Aug 8, 2026
Unblocked —
pm:blockedremoved, back in the queue. Unlock sweep, 2026-08-07.Upstream #5248 (⚠️ Dispatchable since then — ~2 days unswept.
IndexSchema.type/partialenforce-or-remove) closed as completed 2026-08-06T09:06Z.Claim-time note: #5248's disposition decides what this card converges the console editor to, so read its outcome first — if
partialwas retired, thewherevspartialdrift is resolved by removal rather than by renaming.Generated by Claude Code
os-zhuang commentedon Aug 16, 2026
Claim: PM loop round 1 (spec seat shift 2026-08-16)
Session:
session_01SgModTWkJPeXMgbg7enL5ZBranch:
claude/issue-5247-console-index-fallbackWorktree:
objectui-issue-5247Domain: cross-lane simple-blocker takeover — home lane of this card is
repo:objectui; the claiming seat isdomain:spec(+surface/tooling), which this card blocks (last real upstream of #4001'sIndexSchemaclose, per the strictness ledger)File surface:
packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx+ its tests (stop on breach; explain in the report)Container & model: S mechanical,
mode:subagent,model: opusSerial constraints cleared: no open objectui PR touches
EmbeddedItemEditor.tsx(searched 2026-08-16); objectui seat post #6025 names no hot-file queue for it; the seat's in-flight batches 29–33 (#3417/#3424/#3425/#3430/#3378+#3288) do not touch this file.Authorization context: maintainer in-session instruction this shift, verbatim, untranslated: 「4001 可以继续」— this card is the campaign's remaining upstream gate. Takeover is under the standing simple-blocker rule (mechanical, clearly specified, S-size, blocks the claiming lane); a completion note will be left for the
repo:objectuiseat.Scope as updated by this thread's 2026-08-06 premise comment (post-#5248 ruling, both keys retired via PR #5842): converge
FALLBACK_SCHEMAS.indexto the spec's currentIndexSchema— delete thetype/"Algorithm" control (retired key, and itsbrinvalue was never legal) and thewhere/"Partial-index predicate" control (edits a key that never existed;partialis also retired), keepingname/fields/uniquewith the spec's currentuniquesemantics re-read from objectstackorigin/mainat implementation time. Drift re-verified present on objectuiorigin/mainthis morning (brinat :328,whereat :336).Generated by Claude Code
os-zhuang commentedon Aug 16, 2026
{ "issue": 5247, "status": "done", "branch": "claude/issue-5247-console-index-fallback", "pr": "https://github.com/objectstack-ai/objectui/pull/4772", "premise_still_valid": true, "summary": "Drift confirmed present on objectui origin/main exactly as the PM described (brin at :328, where at :336) and converged. Deleted the `type`/\"Algorithm\" control and the `where`/\"Partial-index predicate\" control from FALLBACK_SCHEMAS.index in EmbeddedItemEditor.tsx. One premise detail is now WORSE than the card recorded: measured against the installed @objectstack/spec@17.0.0-rc.6, `type` and `partial` are retiredKey tombstones that REJECT at any value, so the Algorithm select was not a silent-drop control — every one of its five options produced a 422 on the parent save (the #5114 class, live today). Only `where` was the silent strip the card describes. Also converged `unique` onto the ADR-0120 scope union per the claim comment's instruction to re-read the spec's current unique semantics: the console previously rendered a boolean switch, so the only spelling it could emit was the deprecated bare `true` that protocol 18 rejects (#5082); it now offers 'global'/'organization' for new indexes while the union's boolean branch keeps legacy indexes rendering and saving unchanged. Spec's live authorable surface re-read from objectstack origin/main and from the installed package is exactly name/fields/unique — no nullSafeColumns (that is driver-side DeclaredIndexInput, not spec).", "tests": "Union re-run AFTER the final commit at 2be19d6 (clean tree). `pnpm exec vitest run packages/app-shell/src/views/metadata-admin/` -> 'Test Files 168 passed (168) / Tests 1684 passed | 1 skipped (1685)'. New pin + sibling: `pnpm exec vitest run .../EmbeddedItemEditor.indexFallback.test.tsx .../EmbeddedItemEditor.preview.test.tsx` -> 'Test Files 2 passed (2) / Tests 21 passed (21)'. `pnpm --filter @object-ui/app-shell run type-check` exit 0 (tsc --noEmit && tsc -p tsconfig.test.json) — required `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build` first, without which tsc reports 20+ 'Cannot find module @object-ui/*' false reds. eslint on the two changed files: 0 errors, 10 warnings. Repo gates all PASS: check:phantom-deps, check:control-bytes, lint:coverage (46/46 packages, 0 errors), check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:spec-symbols, check-changeset-presence, check-changeset-no-major. REVERSE VERIFICATION: fix committed first, then ablated (export kept so failure is the drift, not a missing import); expected direction RED, observed RED with 8 failures each naming the real defect — `type` -> \"expected '`indexes[].type` was removed in @obje…' to be ''\" (the 422 half), `where` -> 'expected undefined to deeply equal sample' (the silent-strip half, which is why the pin asserts round-trip survival rather than unrecognized_keys — IndexSchema is deliberately not .strict()), completeness -> \"expected ['fields','name','type',…(2)] to deeply equal ['fields','name','unique']\", and the rendered 'Algorithm' label -> 'expected <label …> to be null'. No ablation-on-dist involved (source-resolved package, not a dogfood run). Repo-specific note: `pnpm --filter <pkg> test` and `-- <paths>` are both blocked by objectui's vitest-invocation-guard as silent-false-green traps; the correct form is `pnpm exec vitest run <path>` from the repo root, which is what every run above used.", "open_questions": [], "out_of_scope_findings": [ "filed as objectstack-ai/objectui#4765 (label `finding`, unassigned): `UniquenessValidation`'s @deprecated JSDoc in packages/types/src/data-protocol.ts prescribes `partial` (retired in spec 17.0.0) and `unique: true` (deprecated positional spelling) as the replacement for the removed uniqueness validation — the same false-guidance class this card cites from ledger finding 18, but in a different package, so filed rather than fixed under the declared file surface" ] }Notes the JSON does not carry well
EmbeddedItemEditor.tsx, its new test, and a.changeset/*.md(mandatory in objectui for any change to a released package'ssrc/) are touched.FALLBACK_SCHEMASis nowexported so the pin reads the real object rather than regexing source text; that tripsreact-refresh/only-export-componentsas a warning (lint still exits 0). Moving the constant to its own module would clear it but adds a file outside the declared surface — flagged in the PR for the reviewer to call.@objectstack/spec, so it re-derives on every spec bump instead of re-stating expected key names in its own source. Completeness runs the other direction too (probe each spec shape key; a tombstone rejects every probe, a live key accepts one), so a spec key with no console control also fails — with no hand-listed key set to maintain.Generated by Claude Code
os-zhuang commentedon Aug 16, 2026
ACCEPT — PR objectstack-ai/objectui#4772 (draft, base
main,Fixes objectstack-ai/objectstack#5247first line verified).Reviewed against GitHub, not the report's self-description:
EmbeddedItemEditor.tsx+ newEmbeddedItemEditor.indexFallback.test.tsx+ one changeset — declared surface honoured. TheFALLBACK_SCHEMASexport (react-refresh warning, lint still green) is a declared, deliberate trade so the pin reads the real object; follow-up module split left to a future card if anyone cares.uniqueconverged onto the ADR-0120 scope union with the scope branch first (new indexes can no longer author the deprecated baretrue; legacy booleans keep rendering) — this was instructed at claim time, not scope creep.typewas measured as a live 422 on every option (retiredKey tombstone), not the silent drop the card recorded; onlywherewas the silent strip. The card's framing survives — both controls were false authoring surface.@objectstack/spec(no second hand copy; completeness probed in both directions). It stays green when 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001's IndexSchema close later lands spec-side.UniquenessValidation's deprecation JSDoc points authors at the retiredindexes[].partialkey objectui#4765 verified filed (unassigned,finding).CI gate jobs still
in_progressat review time — flip-to-ready + auto-merge happens only after the gate family concludes green (flip check armed). #4001'sIndexSchemaclose slice dispatches once this PR is MERGED.Generated by Claude Code
fix(app-shell): converge the console's index fallback schema to Index…
docs(audits): re-verdict data/field.zod.ts authorable -> no door; rec…