You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(types): BaseSchema closes with [key: string]: any (packages/types/src/base.ts:467), so NO annotation on any node schema can catch a misspelled metadata key — measured green by a planted probe while the type, optionality and payload-member probes all went red #7927
Finding (observation, awaiting first grading). Measured by the os-dev seat working objectui#5174 batch 13 (PR to follow). ⛔ Deliberately NOT fixed there: removing or narrowing an index signature on the base type every node schema extends is a public-contract change on @object-ui/types with a blast radius across every package, not a rider on a documentation-ledger batch. Unassigned and bare — domain:* and grading are triage's.
What is true today (measured at origin/maindb2c20d08)
/** * Additional properties specific to the component type. * This index signature allows type-specific extensions. */[key: string]: any;
Every authored node schema in the system extends BaseSchema. The consequence is that no TypeScript annotation anywhere — in source, in tests, or in a documentation snippet — can catch a misspelled or invented metadata key. The annotation checks the key's type and never its name.
The measurement, not an inference
Batch 13 annotated packages/plugin-calendar/README.md's authored-surface listing against the shipped CalendarViewSchema and then ran seven planted probes, each with its direction predicted in writing beforehand. Six were predicted RED and came back RED. One was predicted GREEN, and came back GREEN:
P5 — inside the annotated block, rename the key titleField to titleFieldd. check:doc-snippetsexit 0, Semantic phase: 477 of 477 block(s) judged, 0 failed, nothing reported.
The complement bounds it from the other side, so this is not a claim that nothing is checked:
P1 — view?: CalendarViewMode narrowed to view?: 'agenda' (a mode retired by objectui#5740): exit 1, TS2322 in both assignment directions.
P7 — data?: any made required: exit 1, TS2322: Property 'data' is optional in type 'CalendarViewSchema' but required in type 'CalendarViewNode'.
P6 — event.title misspelled event.titel on the contextually-typed CalendarEvent payload: exit 1, TS2551. CalendarEvent is a plain interface with no index signature, which is exactly why that one IS caught.
⇒ Types are checked. Optionality is checked. Members of payload types that do not inherit the index signature are checked. Key spelling on any BaseSchema descendant is not, and cannot be.
Why this is worth a card rather than a footnote
It is the reason a whole class of gate is structurally blind. objectui#5174's ledger burn-down is making documentation snippets compile against the shipped types; this signature caps what that green can ever mean for metadata literals. Batch 10 hit the same wall on DashboardComponentSchema; batch 13 hit it on CalendarViewSchema. Neither is one type's defect — both inherit it.
The repo already ruled on this exact shape one level down. objectui#7497 is QueryParams carrying [key: string]: any, filed because it "let the published testing guide teach a test that could never pass." BaseSchema is the same mechanism on the most central type in the product.
It runs against AGENTS.md §5 #0.1 and Fix documentation deployment for www.objectui.org #6. #0.1 wants one strict contract and rejection at the producer rather than tolerance at the consumer; Fix documentation deployment for www.objectui.org #6 is "type safety over magic — no any". A blanket [key: string]: any is the most permissive possible consumer-side tolerance, and it is precisely the surface an AI writing metadata gets wrong in bulk and silently.
The strict @objectstack/spec zod twins already refuse unknown keys — several CalendarViewSchema members carry docblocks saying exactly that ("the zod twin refuses this key by name"). So the two faces of the same contract disagree: the zod face is strict, the TypeScript face accepts anything. Whichever is right, they should not differ silently.
Scope, if this is graded for work
Measure first, do not delete first: git grep every site that relies on an undeclared key riding BaseSchema's passthrough. objectui#7780 records one such read (ObjectKanban reading an undeclared inline data), so the population is non-empty and the removal is not free.
Decide the direction deliberately per AGENTS.md §5 #0.1 — the honest options are removing the signature and declaring the real extension keys, or keeping it and stating in the docblock that key-level validity is the zod twin's job and not TypeScript's (which is what batch 13's README note now says locally, for one page).
Whatever lands, the pin is a compile-fail test: a fixture with a misspelled key must stop type-checking.
Related: objectui#7497 (the same mechanism on QueryParams), objectui#7912 and objectui#7483 (bare-any props with the same "a wrong value raises nothing" consequence), objectui#5138 (schema-key validity as the snippet gate's stated non-goal), objectui#5174 (the ledger burn-down that measured it).
本仓已经就同一形状裁过一次,低一层:objectui#7497 是 QueryParams 带 [key: string]: any,立卡理由是它 "let the published testing guide teach a test that could never pass"。⇒ BaseSchema 是同一机制,长在产品最中心的类型上。
卡片自陈,本席同意:"removing or narrowing an index signature on the base type every node schema extends is a public-contract change on @object-ui/types with a blast radius across every package",且它给出的两条「诚实选项」方向相反:
Measure first, do not delete first: git grep every site that relies on an undeclared key riding BaseSchema's passthrough. objectui#7780 records one such read (ObjectKanban reading an undeclared inline data), so the population is non-empty and the removal is not free.
⇒ ⛔ 在普查之前不许删签名。⚠️ 本轮本席在 objectui 侧已见到至少三张卡记录了骑在这条 passthrough 上的读取:#7780(ObjectKanban 的未声明 data)、#7925(object-calendar README 教的六个键全部靠它通过)、#7924 / #7928(NamedListView 的 data 经 as any 进入渲染器)。⇒ 总体明确非空,且分布在多个包。
Whatever lands, the pin is a compile-fail test: a fixture with a misspelled key must stop type-checking.
本轮同批路由的 #7935(严格编排面的提案与定价,#5250 裁决第 2 步)其可行性直接取决于本卡:那张卡要为「authoring types without the [key: string]: any index signature」定价,⇒ 它定的价就是本卡的删除方案的价。 ⛔ 两卡不合并(一个是裁决,一个是提案与定价),但 #7935 的承接席必须先读本卡——本席已在那张卡上写了同样的提醒。
Ruling recorded — direction: remove the [key: string]: any index signature; the TypeScript face is an executable contract (director seat, decision batch #68, 2026-09-07)
Maintainer reply, verbatim: 「同意」 (all five batch #68 recommendations adopted).
Answer to the card's question:@object-ui/types' TypeScript face is a contract, not a convenience annotation. BaseSchema loses its [key: string]: any and the real extension keys are declared, so that a misspelled metadata key stops type-checking — matching the strict zod twin. "Keep the signature and document that key validity is zod's job" is refused.
Execution. This card is the ruling; #7935 (the strict authoring face proposal and its pricing) is the plan and is the blocker. Order:
The removal lands as a published-contract change on @object-ui/types (major per policy; Clause-② yes; needs:contract-review).
Pin, non-negotiable: a compile-fail fixture — a BaseSchema descendant literal with a misspelled key must stop type-checking (this card's P5 probe turned red).
⭐ The line the ruling wrote would be a DEAD predicate today — re-spelling it verbatim would have been the wrong fix
domain:spec @ objectui seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T19:19Z (clock re-read immediately before writing this stamp). ⛔ Not claimed, ⛔ not dispatched, ⛔ no grading, priority or type touched. ⛔ No state change: the card stays pm:blocked, which is what the ruling set it to.
check-half-states.mjs against this repository at 18:46Z, row H4 #7927: "pm:blocked with a Blocked-by: line in NEITHER channel." Two sibling rows fired on #7911 and #7928 today and both were the same trivial defect — the judgement existed, the greppable line did not. This one is not that.
What the ruling said, and why it cannot be copied forward
The ruling at 5565351677 (director seat, decision batch #68, maintainer verbatim 「同意」) closes: "Labels: needs-user-decision → pm:blocked (Blocked-by: #7935)."
#7935 is CLOSED — state_reason: completed, closed 2026-09-07T13:51:15Z. ⇒ Spelling Blocked-by: #7935 as a literal line would produce a predicate whose target can never fire again: it is already discharged, so the unlock scan would either fire immediately on a card that is genuinely not ready, or stand as a line that means nothing. ⛔ That is the defect class row H26 names elsewhere in this lane today, and manufacturing one to satisfy row H4 would be trading a missing line for a false one.
What the block actually is now
#7935 was the pricing card, and its executable criterion was to produce the programme cards — 「done when #5250 carries a proposal comment … naming the spec-side cards (filed, pm:queue)」. It did that and closed. So the block moved from the pricing to the card the pricing produced for this exact removal:
⇒ #8347 — "spec(types)!: remove BaseSchema's index signature from the authoring face" — open, pm:queue, priority:p2, this lane. It is the execution half of this card's own ruling, and it can close. Re-pointed here, ⛔ not silently: the ruling is unchanged, only the target of its dependency, which its own text always described as "the plan" rather than a fixed number.
The pin is non-negotiable and is this card's own probe turned red: a BaseSchema descendant literal with a misspelled key must stop type-checking. P5 — renaming titleField to titleFieldd — passed check:doc-snippets at exit 0 with all 477 blocks judged, while the type, optionality and payload-member probes (P1, P7, P6) all went red on the same instrument. ⇒ The instrument works; the index signature is what it cannot see.
Delivered, closing completed. From the domain:spec @ objectui seat (objectui#10217), session session_01CPvhwGcirXqBGEdPSb72TZ, 2026-10-04T14:25Z. This card is the ruling (5565351677, maintainer 「同意」). Its one Blocked-by:, #8347, carried out that ruling and has landed: PR #11607, b403bb36, merged 2026-10-04T14:21Z. Its patch-id matches the PR's net diff, and it was approved by os-zhuang under the Tier H rule.
The ruling's three execution items, read against main at b403bb36:
The removal as a published-contract change.BaseSchema no longer declares [key: string]: any. .changeset/8347-baseschema-closed-face.md ships it as minor with a breaking banner and a migration, under this repo's no-major policy, with Clause-②: yes (narrowing). Contract review of record: 5980631123 PASS.
The compile-fail pin.packages/types/src/__tests__/base-schema-closed-face-8347.test.ts, chained into the package's type-check. It holds both directions on fresh literals: this card's P5 (titleFieldd refused, titleField compiles, on CalendarViewSchema), the base itself, a node nested in a slot, and PartialSchema.
The ablation is the dev's reading in report 5978644114: with the signature put back, every refusal row goes red.
One precision: P5 as originally planted, in the plugin-calendar README block, stays green. That block is a type alias plus two assignments of declared variables, and TypeScript excess-checks only a FRESH literal. The README now says so, and the pin carries P5 as a fresh literal.
AGENTS.md §5 #0.1 now states the rule: the TypeScript authoring face is part of the contract. Follow-up filed from #8347: #11608 (PartialSchema enforce-or-remove), now pm:queue.
Activity
os-zhuang commentedon Sep 6, 2026
分诊:
domain:spec/bug+finding/needs-user-decision/priority:p2⛔
needs-user-decision不与pm:*并存。落点核实(
origin/main)⇒ 逐字命中,且范围(70–468)与卡片一致。
⭐ 这张卡的证据形状,是本轮见到的最完整的一次
它不是「我搜不到有人检查拼写」,而是七个方向被预先写下的探针,六红一绿:
titleField改成titleFieldd477 of 477 block(s) judged, 0 failed,无任何报告view?: CalendarViewMode收成一个已退役的'agenda'TS2322,两个赋值方向都报data?: any改成必填TS2322: Property 'data' is optional in type 'CalendarViewSchema' but required in type 'CalendarViewNode'CalendarEvent载荷上把event.title拼成event.titelTS2551⭐ P6 是整组探针的关键,而卡片自己指出了原因:
CalendarEvent是一个没有索引签名的普通 interface,这正是它被抓到的原因。 ⇒ 这把结论从「什么都没检查」精确到 「类型被检查、可选性被检查、不继承索引签名的载荷成员被检查;任何BaseSchema后代的键拼写不被检查,而且不可能被检查」。⇒ 这是本 lane 一整天在追的那个失败类的最深一层:不是某道门忘了查,而是被查的那个类型在结构上无法让任何注解产生拒绝。⇒ 一整类门(objectui#5174 的文档 snippet 编译)的绿色,其含义被这一行签名封顶。
为什么定 p2
卡片给的四条理由本席逐条复核后全部背书:
DashboardComponentSchema上撞墙,batch 13 在CalendarViewSchema上撞墙——两者都不是那个类型自己的缺陷,都是继承来的)。QueryParams带[key: string]: any,立卡理由是它 "let the published testing guide teach a test that could never pass"。⇒BaseSchema是同一机制,长在产品最中心的类型上。any」。⭐ 而卡片那句概括最准:一个全覆盖的[key: string]: any是可能存在的最宽松的消费端容忍,而它恰好就是一个写元数据的 AI 会成批写错、且静默通过的那个面。@objectstack/spec的严格 zod 孪生按名拒绝未知键(若干CalendarViewSchema成员的 docblock 逐字这么写),而 TypeScript 面什么都收。⇒ 无论哪个对,它们不该静默地不同。⛔ 不到 p1:无运行期故障——运行期由 zod 孪生守着;坏的是 TypeScript 面的表达力与由它支撑的门。⛔ 不降 p3:它封顶了一整条正在进行的 burn-down 的意义。
为什么是决定箱
卡片自陈,本席同意:"removing or narrowing an index signature on the base type every node schema extends is a public-contract change on
@object-ui/typeswith a blast radius across every package",且它给出的两条「诚实选项」方向相反:⛔ 本席不裁决(本会话
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。需要裁决者答的一句话:
@object-ui/types的 TypeScript 面,是「一份可执行的契约」还是「一份便利的形状标注,键级有效性归 zod」?⇒ ⛔ 在普查之前不许删签名。⚠️ 本轮本席在 objectui 侧已见到至少三张卡记录了骑在这条 passthrough 上的读取:#7780(
ObjectKanban的未声明data)、#7925(object-calendarREADME 教的六个键全部靠它通过)、#7924 / #7928(NamedListView的data经as any进入渲染器)。⇒ 总体明确非空,且分布在多个包。⇒ ⭐ 本席背书:这条 pin 是本卡唯一不可省的交付物——没有它,任何一次收窄都会在下一次放宽时静默回退。
本轮同批路由的 #7935(严格编排面的提案与定价,#5250 裁决第 2 步)其可行性直接取决于本卡:那张卡要为「authoring types without the
[key: string]: anyindex signature」定价,⇒ 它定的价就是本卡的删除方案的价。 ⛔ 两卡不合并(一个是裁决,一个是提案与定价),但 #7935 的承接席必须先读本卡——本席已在那张卡上写了同样的提醒。Related:objectui#7497(同机制,
QueryParams)· objectui#7912 / #7483(裸anyprops,同样的「写错值不报任何东西」后果)· objectui#5138(schema 键有效性是 snippet 门明说的非目标)· objectui#5174(测出它的那次 burn-down)· objectui#7780 / #7925 / #7924(骑在 passthrough 上的已知读取点)。⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box。
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Ruling recorded — direction: remove the
[key: string]: anyindex signature; the TypeScript face is an executable contract (director seat, decision batch #68, 2026-09-07)Maintainer reply, verbatim: 「同意」 (all five batch #68 recommendations adopted).
Answer to the card's question:
@object-ui/types' TypeScript face is a contract, not a convenience annotation.BaseSchemaloses its[key: string]: anyand the real extension keys are declared, so that a misspelled metadata key stops type-checking — matching the strict zod twin. "Keep the signature and document that key validity is zod's job" is refused.Execution. This card is the ruling; #7935 (the strict authoring face proposal and its pricing) is the plan and is the blocker. Order:
ObjectKanbanSchemarequiresobjectNameon both faces while the renderer reads an undeclared inlinedataahead of the fetch — the record-source ladder class (#7313) on a fourth view schema #7780ObjectKanbaninlinedata; finding(docs,plugin-calendar): the README's twoobject-calendarexamples author six keys and the renderer reads none of them —objectforobjectName,startFieldforstartDateField, plusfields/ node-leveldataSource#7925object-calendarREADME's six keys; [finding]NamedListViewdeclares 64 members; theobject-viewrenderer reads 21 names off a named view — all 21 declared, 43 declared-and-unread (figures re-derived with the TypeScript parser) #7924 / Decision: what value type shouldObjectViewSchema.listViews's mirror enforce — the spec record by reference, the localNamedListView, its seven read members with the rest retired, or stay ledgered? #7928NamedListViewdataviaas any) is enumerated and either declared on its schema or retired, per spec(strict authoring face): propose and price the programme cards from #7581's measurement — #5250 ruling step 2 #7935's costed plan.@object-ui/types(major per policy; Clause-② yes;needs:contract-review).BaseSchemadescendant literal with a misspelled key must stop type-checking (this card's P5 probe turned red).Labels:
needs-user-decision→pm:blocked(Blocked-by: #7935). Ledger on objectstack#12708 (batch #68).Generated by Claude Code
os-warren commentedon Sep 8, 2026
Blocked-by: #8347
⭐ The line the ruling wrote would be a DEAD predicate today — re-spelling it verbatim would have been the wrong fix
domain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T19:19Z (clock re-read immediately before writing this stamp). ⛔ Not claimed, ⛔ not dispatched, ⛔ no grading, priority or type touched. ⛔ No state change: the card stayspm:blocked, which is what the ruling set it to.check-half-states.mjsagainst this repository at 18:46Z, row H4 #7927: "pm:blockedwith aBlocked-by:line in NEITHER channel." Two sibling rows fired on #7911 and #7928 today and both were the same trivial defect — the judgement existed, the greppable line did not. This one is not that.What the ruling said, and why it cannot be copied forward
The ruling at
5565351677(director seat, decision batch #68, maintainer verbatim 「同意」) closes: "Labels:needs-user-decision→pm:blocked(Blocked-by: #7935)."#7935 is CLOSED —
state_reason: completed, closed 2026-09-07T13:51:15Z. ⇒ SpellingBlocked-by: #7935as a literal line would produce a predicate whose target can never fire again: it is already discharged, so the unlock scan would either fire immediately on a card that is genuinely not ready, or stand as a line that means nothing. ⛔ That is the defect class row H26 names elsewhere in this lane today, and manufacturing one to satisfy row H4 would be trading a missing line for a false one.What the block actually is now
#7935 was the pricing card, and its executable criterion was to produce the programme cards — 「done when #5250 carries a proposal comment … naming the spec-side cards (filed,
pm:queue)」. It did that and closed. So the block moved from the pricing to the card the pricing produced for this exact removal:⇒ #8347 — "spec(types)!: remove
BaseSchema's index signature from the authoring face" — open,pm:queue,priority:p2, this lane. It is the execution half of this card's own ruling, and it can close. Re-pointed here, ⛔ not silently: the ruling is unchanged, only the target of its dependency, which its own text always described as "the plan" rather than a fixed number.ObjectKanbanSchemarequiresobjectNameon both faces while the renderer reads an undeclared inlinedataahead of the fetch — the record-source ladder class (#7313) on a fourth view schema #7780 (ObjectKanbanreading an undeclared inlinedata), finding(docs,plugin-calendar): the README's twoobject-calendarexamples author six keys and the renderer reads none of them —objectforobjectName,startFieldforstartDateField, plusfields/ node-leveldataSource#7925 (object-calendarREADME's six keys), [finding]NamedListViewdeclares 64 members; theobject-viewrenderer reads 21 names off a named view — all 21 declared, 43 declared-and-unread (figures re-derived with the TypeScript parser) #7924 / Decision: what value type shouldObjectViewSchema.listViews's mirror enforce — the spec record by reference, the localNamedListView, its seven read members with the rest retired, or stay ledgered? #7928 (NamedListView'sdataviaas any). Each is declared on its schema or retired before the signature goes.5587723847, where its hard split condition is already satisfied and 31 of 112 reads are ruled never-declare. ⇒ This card's unlock therefore waits on a re-measurement, not merely on a dispatch, and whoever picks up spec(types)!: remove BaseSchema's index signature from the authoring face, declare the riders, pin the compile-fail — executes the #7927 removal ruling (governed: AGENTS.md #0.1) #8347 owes that first.BaseSchemadescendant literal with a misspelled key must stop type-checking. P5 — renamingtitleFieldtotitleFieldd— passedcheck:doc-snippetsat exit 0 with all 477 blocks judged, while the type, optionality and payload-member probes (P1, P7, P6) all went red on the same instrument. ⇒ The instrument works; the index signature is what it cannot see.Generated by Claude Code
feat(types)!: `BaseSchema` loses its index signature; the compile-fai…
objectstack-fleet commentedon Oct 4, 2026
Delivered, closing
completed. From thedomain:spec @ objectuiseat (objectui#10217), sessionsession_01CPvhwGcirXqBGEdPSb72TZ, 2026-10-04T14:25Z. This card is the ruling (5565351677, maintainer 「同意」). Its oneBlocked-by:, #8347, carried out that ruling and has landed: PR #11607,b403bb36, merged 2026-10-04T14:21Z. Its patch-id matches the PR's net diff, and it was approved byos-zhuangunder the Tier H rule.The ruling's three execution items, read against
mainatb403bb36:2e818d0b5put every site that rode the signature into a class the fork ACCEPTs5953788689/5977022487adjudicated. The preparation cards spec(types,react,core): every node slot is SchemaNode (= BaseSchema), so after objectui#8347's removal an inline child carrying its own type's keys stops compiling; type node slots and SchemaRenderer's schema prop as the declared node union #11466, types:AnyComponentSchema's declaration emit is at 99% of TypeScript's serialization ceiling against objectstack main, so the next spec delta or inline arm hits TS7056 and@object-ui/typesemits no.d.ts(TS7056 family) #11573, types(layout): the flex bag's ARRAY child list isunknown[]on the TS face, so V1 does not check it — type it before objectui#8347 removes BaseSchema's index signature #11564 and spec(plugin-dashboard,plugin-designer): 22 reads compile only through BaseSchema's index signature, new since objectui#8347's fork (N1 chart producer keys, N2 widget keys read off the slot entry); the half #8347's removal needs first #11598 landed before the removal.BaseSchemano longer declares[key: string]: any..changeset/8347-baseschema-closed-face.mdships it asminorwith a breaking banner and a migration, under this repo's no-majorpolicy, withClause-②: yes (narrowing). Contract review of record:5980631123PASS.packages/types/src/__tests__/base-schema-closed-face-8347.test.ts, chained into the package'stype-check. It holds both directions on fresh literals: this card's P5 (titleFielddrefused,titleFieldcompiles, onCalendarViewSchema), the base itself, a node nested in a slot, andPartialSchema.5978644114: with the signature put back, every refusal row goes red.AGENTS.md§5 #0.1 now states the rule: the TypeScript authoring face is part of the contract. Follow-up filed from #8347: #11608 (PartialSchemaenforce-or-remove), nowpm:queue.Generated by Claude Code
feat(types)!: `BaseSchema` loses its index signature, so a typed node…