Repository navigation
types: the strict authoring face accepts spellings the spec refuses (object-calendar flat keys), 74 zod mirrors silently strip unknown keys, and the list view's options bag is a second contract #25923
Description
Premise checked before dispatch (domain:spec seat 2, 2026-10-26): readings and corrections in comment 6409923362 (#25923 (comment)). The goal below is unchanged.
维护者速读
三件事同一个病根:objectui 的镜像声明了一些「只读不写」的键,而严格授权面是从声明派生的,于是 objectui validate 放行 spec 会拒绝的文档。(1) StrictAnyComponentSchema 接受 object-calendar 上的扁平 startDateField,spec 拒绝并给出处方;gantt 有 32 个同类扁平键。(2) 74 个导出的 zod 镜像是 strip 模式:拼错的键「校验通过」然后消失,spec 会说「Did you mean label」。(3) list view 的顶层 options 包被 spec 以 z.never 拒绝,objectui 仍声明、仍双读 30 多处、ObjectView 仍在合成它。另有 33 个 spec 键 objectui 既不镜像也不声明。
Measured (runtime probes under tsx against the pinned spec dist 18.0.0; scripts drift2.mts / probe4-6.mts in the review scratchpad)
- Flat keys on the authoring face
spec ObjectCalendarPropsSchema.safeParse({objectName, startDateField})→ REFUSE "Unrecognized key(s) on thisobject-calendar:startDateField. • Write this as a key of thecalendarconfig object".objectui StrictAnyComponentSchema.safeParse({type:'object-calendar', objectName, startDateField})→ ACCEPT. Cause:objectCalendarFlatFieldinpackages/types/src/zod/objectql.zod.tsdeclares the five flat keys and says in its own comment "The flat spelling is read, not authored …@objectstack/specrefuses all five" (objectui#8831). The strict face (packages/types/src/strict-authoring-face.ts) is derived from declared keys, so prose cannot narrow it.- Gantt:
ObjectGanttSchemainpackages/types/src/objectql.tsdeclares 32 live flat keys with no tombstones; the tolerant zod face accepts flat start/end, while theproperties-bag arm correctly refuses with the spec's text. Readers:packages/plugin-calendar/src/ObjectCalendar.tsx("The flat spelling, whichObjectViewandListViewemit") andpackages/plugin-gantt/src/ObjectGantt.tsx("The internal flat form — the ObjectView / ListView flatten product"). - AGENTS.md #0.1 (objectui#6356): a key only the host writes at runtime is typed on a package-local hand-off type, never on the authoring face.
- Strip-mode mirrors
- Over every
*Schemaexport ofpackages/types/src/zod/index.zod.ts: passthrough 176, strict 9, strip 74 (plainz.object()).FormFieldSchema.safeParse({name, type, labell: 'oops'})→ ACCEPT with the key dropped; specFormFieldSchema→ REFUSE "Did you mean labell → label". Strip list includesFormFieldSchema,TableColumnSchema,DetailViewFieldSchema,DetailViewSectionSchema,DetailViewTabSchema,TabItemSchema,KanbanColumnSchema,SelectOptionSchema,SortConfigSchema,FilterGroupSchema,DashboardWidgetLayoutSchema. The TSFormFieldstill carries an open index signature (packages/types/src/form.ts).
- Over every
- The
optionsbag- Spec
packages/spec/src/ui/view.zod.ts:options: z.never({ error: () => VIEW_ITEM_OPTIONS_REFUSED }); probe confirmsunrecognized_keys … options. objectui:NamedListView.optionsis a Record of string to any inpackages/types/src/objectql.ts; the zodListViewSchema.optionsis a live 8-kind bag;packages/plugin-list/src/ListView.tsxreadsschema.X || schema.options?.Xat 30+ sites (its header calls the double read "the convention already set by every sibling visualization");packages/app-shell/src/views/ObjectView.tsxstill composesoptions: { ...legacyOptions, kanban: … }fromstoredLegacyOptions(viewDef).
- Spec
- Key drift over 16 zod pairs: 33 spec-only keys objectui neither mirrors nor types (e.g.
ObjectFormcontentLayout,confirmOnDiscard;FormFieldhelpText,immutable,keyField,disclosure,publicPicker— 0 TS declarations for those seven, positive controlstartDateField8), 121 live objectui-only keys;@object-ui/types/zodexports anActionSchema(crud.zod.ts,type: 'action') sharing 0 keys with the spec'sActionSchema;PageSchemaandDashboardSchemahave no objectui mirror at all.
Recommendation
- Move the calendar (5) and gantt (32) flat keys off the authoring face onto a package-local hand-off type; have
ObjectView/ListViewemit the spec block (calendar: {…},gantt: {…}); leavealiasKeyRefusaltombstones likedateField/endField. Re-pointcalendar-flat-color-allday-8466.test.tsand the objectui#6576schemaReadscensus. (M) - Make the 74 strip objects
.passthrough()(the strict twin keeps refusing); dropFormField's index signature (as objectui#12166 did forstep/className); add a "no strip-mode object" assertion besideassertionNoVacuousEntryinzod-mirror-parity.test.ts— an existing instrument, not a new gate. (S–M) -
optionsbag: first a stored-row census (thebody-dialect-census.mjspattern) over real console / hotcrm metadata — 未实测; if small, migrate rows, delete the composer inObjectView.tsxand theInterfaceListPagetwin, then the 30+ double reads and theNamedListView.optionsrow, leaving az.neverrefusal with the spec remedy. (M–L) - For each of the 33 spec-only keys: derive by reference (
specFieldsExcept/LIST_VIEW_LOCAL_OVERRIDESpattern) or record az.neverrefusal; rename or retire the non-specActionSchemaexport. (M)
Benefit: os validate, objectui validate and the renderer give one verdict per document; 37 authorable-but-wrong keys leave the published typings.
Refs: objectui#8831, objectui#11980, objectui#11993, objectui#6356, objectui#5250, objectui#5684, objectui#9256, objectui#7927, objectui#12166, objectui#12014, objectui#11571, objectui#10380, objectui#4115, objectui#11073. No open card covers any of the four.
Filed by the read-only whole-repo review session https://claude.ai/code/session_01BDtwYUWhLGyWLzYQjZ95Cf on 2026-10-24; the maintainer approved filing (「同意,创建任务」). Full evidence files sit in that session's scratchpad (objectui-review-report.md, findings-*.md). ⛔ Not a claim; for triage to grade and route.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 24, 2026
Triage: first grade —
bug·priority:p2·domain:spec·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T15:02Z⛔ Not a claim, ⛔ not a dispatch.
findingcomes off.domain:spec(objectui's types / schema-corpus lane), notdomain:ui. The defect is thatobjectui validateaccepts documents the spec refuses..passthrough(), with the assertion besidezod-mirror-parity.test.ts. That is an existing instrument, not a new gate. S–M.optionsbag: the stored-row census first, before anything is deleted.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Transferred 2026-10-26T04:13Z from objectstack-ai/objectui#13451, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the
ui/workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.Paths. Read every repository path in the body with a
ui/prefix:packages/types/src/zod/objectql.zod.tsis nowui/packages/types/src/zod/objectql.zod.ts;packages/types/src/strict-authoring-face.tsis nowui/packages/types/src/strict-authoring-face.ts;packages/types/src/objectql.tsis nowui/packages/types/src/objectql.ts;packages/plugin-calendar/src/ObjectCalendar.tsxis nowui/packages/plugin-calendar/src/ObjectCalendar.tsx;packages/plugin-gantt/src/ObjectGantt.tsxis nowui/packages/plugin-gantt/src/ObjectGantt.tsx;packages/types/src/zod/index.zod.tsis nowui/packages/types/src/zod/index.zod.ts;packages/types/src/form.tsis nowui/packages/types/src/form.ts;packages/plugin-list/src/ListView.tsxis nowui/packages/plugin-list/src/ListView.tsx; and 1 more the same way.packages/spec/src/ui/view.zod.tswas already objectstack's own path and stays as written.Labels.
sdui,typeshad no twin in this repository and were dropped by the transfer; every other label stayed.No stale
pm:dispatchedor assignee to strip.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Premise check before dispatch · domain:spec seat 2 · 2026-10-26T06:41Z
Read-only premise reading on origin/main
d5f4c2a2a(dispatch-runbook 派发前置). The goal of this card is unchanged; only the readings below move the landing point or the method, and the dispatch order carries them.corrected on counts, substance holds. Static scan of ui/packages/types/src/zod/*.zod.ts: ~75 strip-mode z.object (63 exported
…Schema), 4 passthrough, 1 strict; the card's runtime probe said 74: re-measure with the card's probe and put the number in the PR body. Examples: ReportFieldSchema, TabItemSchema, SortConfigSchema, FormFieldSchema (form.zod.ts:~1097).Each count above is a re-measurable reading, not a ruling; the dev re-measures before relying on it.
41 remaining items
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27026 comment 6461454849 ·
domain:specseat 3 · 2026-10-28T05:30ZWork item 3, PR 1: the calendar/gantt flat fallback is retired. Contract review round 2 PASS (6459158552). The head
c70b9cd5merges main; the PR diff is unchanged. The PR is ready, with auto-merge armed. Next on this card: one PR that closes theoptionsbag door, adds the ADR-0087 D2 fold and deletes the objectui readers (ruling 6460225523).Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed in part (work item 3, PR 1) ·
domain:specseat 3 ·os-support-ai· 2026-10-28T06:19Zd8a9cc598forigin/maind8a9cc59contains the merge commit (git merge-base --is-ancestor)Delivered: the calendar and gantt renderers read their binding from the
calendar/ganttblock only. The flat fallback read and the hand-off typesObjectCalendarFlatFields/ObjectGanttFlatFieldsare retired.search/searchableFieldsstay on the package-localObjectGanttSearchFields. The three comment polishes from 6437594464 are applied.Next on this card, same claim and same thread: one PR that closes the list overlay door's
options(retiredKey), adds the ADR-0087 D2 foldlist-view-options-bag-foldedand its D3 note, and deletes the objectui readers (ruling 6460225523). It also carries theGanttConfigSchemarefused-value census.pm:dispatchedstays on.Part of #25923, so the card stays open.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
ACCEPT (Tier H, awaiting an authorized approval) → PR #27206 comment 6468447600
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-28T20:44Zos-zhuang(Tier H) and merged through the merge queue at 2026-10-28T20:43Z, head8a4ad1fba, merge commit8eeb3b3b92origin/maingit merge-base --is-ancestor)Delivered (work item 3, with #27026):
optionsbag is retired. A stored bag folds onto the top-level blocks, and the list, calendar and gantt renderers read the blocks alone (refactor(plugin-calendar,plugin-gantt)!: retire the flat fallback read; the calendar and gantt renderers read the block only #27026 retired the calendar and gantt flat fallback read).optionsrow.Still open on this card, per the body: FormField's index signature (ruling 6412606977) and the immutable follow-up.
pm:dispatchedstays; this card is not closed by the merge.objectstack-fleet commentedon Oct 31, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Released by spec seat 3 · 2026-10-31T03:23Z
Spec seat 3 goes off duty (the maintainer, 2026-10-30: 「当前任务处理完,合并后就下班」). Work items 3 and 4 have landed (see the Landed records above). What is still open on this card, per its body: the FormField index signature (ruling 6412606977) and the immutable follow-up. Neither is in progress anywhere. The seat releases its claim and assignee and returns the card to
pm:queuefor the next seat.objectstack-fleet commentedon Oct 31, 2026
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D
Claim: PM loop round 4
Session:
session_01L9qibx5r5nhDMwpRs4SL6DAccount:
os-teslaBranch:
claude/project-thread-ubqwdg(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-25923Domain:
domain:specSeat:
domain:spec#2File surface:
ui/packages/types/src/form.ts(drop theFormFieldindex signature, declare the spec-only keys if the immutable follow-up names them) and the readers the drop breaks (componentsform.tsx, plugin-form, plugin-grid, others measured), their tests, ui changesets; the ⛔ onui/**is lifted for exactly these. ⛔ noui/**and no governed path (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT,effort: high; contract review by the lane review thread atCONTRACT_REVIEW_TIERClause-②: yes (a narrowing of the published
@object-ui/typesauthoring face; the thread measures whether any spec accept set moves)Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6538368518
Premise: holds (re-read at
origin/main3d9175d0; the gap named on the card is still there)Serial constraints cleared: spec seat 1 withdrew its thread for this card (mailbox #28146, 05:20Z); no open PR edits
form.ts; the earlier claims were released (6538368518)Ruling-ref: none on the shape; dispatch authority = the maintainer's 「p3 也可以开发」 (2026-10-30T12:41Z) and 「每个座位并发可以5」 (2026-10-30T15:04Z); seat ruling 6412606977 (letter A) and the release 6538368518 by spec seat 3
Dispatched 2026-10-31T05:23Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.
objectstack-fleet commentedon Oct 31, 2026
domain:spec#2 · os-tesla · session_01L9qibxnmxDxesWCZGKX6jW
os-dev-report
{ "issue": 25923, "status": "done", "branch": "claude/project-thread-ubqwdg", "pr": "https://github.com/objectstack-ai/objectstack/pull/28363", "head": "a280b7b5d2c88b2caee3a7d633f9c6c5391e3f49", "session": "session_01L9qibxnmxDxesWCZGKX6jW", "premise_still_valid": true, "summary": "The two items left on the card. (1) Ruling 6412606977 letter A: FormField drops [key: string]: any. Readers re-measured on origin/main 501c864d: components form.tsx (7 sites: 5 isServerOwnedValue + 2 carrier reads) and plugin-form fieldGroups.ts; the 2 plugin-grid errors from 2026-10-26 do not reproduce (ObjectGrid.tsx no longer imports FormField) and WizardForm clears with the core fix. Fixed by declaring keys where producer and reader live: core FieldWithDeclaredDefault loses its own index signature; components types the metadata carrier a Record of string to unknown, or FormField; plugin-form gets FormFieldWithGroup (group) and a module-local SectionDividerRow (collapsible/collapsed/onToggle). No as-any or Record cast added. (2) The immutable follow-up, defined in landing record 6448239783 ('render it, or record a refusal'): rendered. normalizeSectionField writes immutable: true as disabled when !isCreateFormMode(ctx); census row moves to translated. No spec accept set moves: no packages/spec path, no *.zod.ts in the diff.", "files_changed": 13, "line_budget": "+127 / -27 = 154 changed lines vs origin/main 22ce3d13 (threshold 3000: under)", "tests": "ui type-check, all dependents of @object-ui/types, under os-verify-lock at 43cf87a8: Tasks 105 successful / 105, VERDICT command-exit 0 (first measurement with only the drop: components#build + type-check red, cascade TS7016 downstream; then plugin-form fieldGroups.ts:369 once components was fixed). ui vitest at 6692b1a4 (plugin-form/, core/src/validation/, components/src/renderers/form/, the new pin, zod-mirror-parity): Test Files 310 passed (310), Tests 3186 passed | 18 skipped, VERDICT command-exit 0. Reverse check on the new closed-face pin: signature re-added -> tsc -p tsconfig.test.json reds form-field-closed-face-25923.test.ts(29,5) and (31,5) TS2578; restored with git checkout HEAD, git diff HEAD empty. No ablation of the immutable translation (the spec-parity row asserts both directions).", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 6692b1a4: 34 commands, 34 exit 0 after the changeset fix (check-adr-0087-registration first exit 1: type-surface-only refused for an index-signature removal, then runtime-interface-only refused the FROM label; prose consumer fix -> exit 0, changeset gates re-run). --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --dir ui exec vitest run scripts/: 169 passed | 2 skipped files, exit 0. CI on a280b7b5: all 10 required contexts success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core skipped by filter, Governed Surface Queue Guard, Lint UI, Type Check UI, Test UI 16/16, Build & E2E UI, Live E2E UI), Check Changeset success, PR-body guards success; mergeable_state clean.", "dogfood_battery": "a280b7b5 · os-verify-lock VERDICT command-exit 0 · Test Files 361 passed | 3 skipped (364) · Tests 2917 passed | 28 skipped (2945) · no failures (first attempt: closure build red on cloud-connection TS2307 @objectstack/observability, a stale install after merging main; pnpm install --frozen-lockfile, re-run green)", "mcp_calls": "7 — GitHub MCP reads only: issue_read x3 (get, get_comments x2), pull_request_read x3 (get, get_check_runs x2); plus mcp__claude-code-remote__subscribe_pr_activity x1. No GitHub write tool.", "api_writes": "2 — fleet-write relay pr_create x1 (#28363, 7987 bytes sent / 7987 stored, no assignees); this os-dev-report comment via post-stamped. git push x6 to claude/project-thread-ubqwdg is not a REST write.", "deviations": [ "Clause-②: yes (narrowing) in the PR body and changeset, against the claim's 'yes (a narrowing … measures …)': the closed pair needs the arm, and dropping an index signature from a published type is the narrowing; title carries !.", "ADR-0087 marker is not-required (runtime-interface-only) on both interfaces, not type-surface-only: the D8 limb admits only an any index signature re-typed with the member count unchanged, and a removal moves the count (74 -> 73). The consumer fix is prose because the inherited no-migration-prescription refusal reads a FROM label.", "File surface: @object-ui/core server-owned-value.ts and its test joined as a measured reader (its own index signature is what made every FormField call site of isServerOwnedValue fail); plugin-form flatFields.ts/.test.ts joined (second writer of group).", "WIP commit ac34dde5 and merge commit a280b7b5 carry no trailer pair; the other four do. Not rewritten (shared-branch rule)." ], "open_questions": [ { "question": "normalizeSectionField still copies keyField, disclosure and precision onto the runtime field through its any-typed base; nothing reads them. Keep the ADR-0007 pass-through, or drop the copy?", "options": ["A — keep (status quo, census rows already record it)", "B — drop the copy and move the three keys to the spec-parity EXEMPT list with reasons"], "recommendation": "A for this card; B only if the seat wants 'nothing writes an undeclared key' to hold literally, as its own small card." } ], "out_of_scope_findings": [ "components form.tsx section-divider branch reads its row through fieldProps as any (pre-existing); this PR types the producer side only · carrier: PR #28363 Acceptance notes · noted, not filed" ] }