Skip to content

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

Open

Description

@os-zhuang

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)

  1. Flat keys on the authoring face
    • spec ObjectCalendarPropsSchema.safeParse({objectName, startDateField}) → REFUSE "Unrecognized key(s) on this object-calendar: startDateField. • Write this as a key of the calendar config object".
    • objectui StrictAnyComponentSchema.safeParse({type:'object-calendar', objectName, startDateField}) → ACCEPT. Cause: objectCalendarFlatField in packages/types/src/zod/objectql.zod.ts declares the five flat keys and says in its own comment "The flat spelling is read, not authored … @objectstack/spec refuses 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: ObjectGanttSchema in packages/types/src/objectql.ts declares 32 live flat keys with no tombstones; the tolerant zod face accepts flat start/end, while the properties-bag arm correctly refuses with the spec's text. Readers: packages/plugin-calendar/src/ObjectCalendar.tsx ("The flat spelling, which ObjectView and ListView emit") and packages/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.
  2. Strip-mode mirrors
    • Over every *Schema export of packages/types/src/zod/index.zod.ts: passthrough 176, strict 9, strip 74 (plain z.object()). FormFieldSchema.safeParse({name, type, labell: 'oops'}) → ACCEPT with the key dropped; spec FormFieldSchema → REFUSE "Did you mean labell → label". Strip list includes FormFieldSchema, TableColumnSchema, DetailViewFieldSchema, DetailViewSectionSchema, DetailViewTabSchema, TabItemSchema, KanbanColumnSchema, SelectOptionSchema, SortConfigSchema, FilterGroupSchema, DashboardWidgetLayoutSchema. The TS FormField still carries an open index signature (packages/types/src/form.ts).
  3. The options bag
    • Spec packages/spec/src/ui/view.zod.ts: options: z.never({ error: () => VIEW_ITEM_OPTIONS_REFUSED }); probe confirms unrecognized_keys … options. objectui: NamedListView.options is a Record of string to any in packages/types/src/objectql.ts; the zod ListViewSchema.options is a live 8-kind bag; packages/plugin-list/src/ListView.tsx reads schema.X || schema.options?.X at 30+ sites (its header calls the double read "the convention already set by every sibling visualization"); packages/app-shell/src/views/ObjectView.tsx still composes options: { ...legacyOptions, kanban: … } from storedLegacyOptions(viewDef).
  4. Key drift over 16 zod pairs: 33 spec-only keys objectui neither mirrors nor types (e.g. ObjectForm contentLayout, confirmOnDiscard; FormField helpText, immutable, keyField, disclosure, publicPicker — 0 TS declarations for those seven, positive control startDateField 8), 121 live objectui-only keys; @object-ui/types/zod exports an ActionSchema (crud.zod.ts, type: 'action') sharing 0 keys with the spec's ActionSchema; PageSchema and DashboardSchema have 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 / ListView emit the spec block (calendar: {…}, gantt: {…}); leave aliasKeyRefusal tombstones like dateField / endField. Re-point calendar-flat-color-allday-8466.test.ts and the objectui#6576 schemaReads census. (M)
  • Make the 74 strip objects .passthrough() (the strict twin keeps refusing); drop FormField's index signature (as objectui#12166 did for step / className); add a "no strip-mode object" assertion beside assertionNoVacuousEntry in zod-mirror-parity.test.ts — an existing instrument, not a new gate. (S–M)
  • options bag: first a stored-row census (the body-dialect-census.mjs pattern) over real console / hotcrm metadata — 未实测; if small, migrate rows, delete the composer in ObjectView.tsx and the InterfaceListPage twin, then the 30+ double reads and the NamedListView.options row, leaving a z.never refusal with the spec remedy. (M–L)
  • For each of the 33 spec-only keys: derive by reference (specFieldsExcept / LIST_VIEW_LOCAL_OVERRIDES pattern) or record a z.never refusal; rename or retire the non-spec ActionSchema export. (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

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    and removed on Oct 24, 2026
  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    Contributor

    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. finding comes off.

    • Lane: domain:spec (objectui's types / schema-corpus lane), not domain:ui. The defect is that objectui validate accepts documents the spec refuses.
    • Order:
      • (2) the 74 strip-mode mirrors → .passthrough(), with the assertion beside zod-mirror-parity.test.ts. That is an existing instrument, not a new gate. S–M.
      • (1) the calendar / gantt flat keys off the authoring face. M.
      • (3) the options bag: the stored-row census first, before anything is deleted.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    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.ts is now ui/packages/types/src/zod/objectql.zod.ts; packages/types/src/strict-authoring-face.ts is now ui/packages/types/src/strict-authoring-face.ts; packages/types/src/objectql.ts is now ui/packages/types/src/objectql.ts; packages/plugin-calendar/src/ObjectCalendar.tsx is now ui/packages/plugin-calendar/src/ObjectCalendar.tsx; packages/plugin-gantt/src/ObjectGantt.tsx is now ui/packages/plugin-gantt/src/ObjectGantt.tsx; packages/types/src/zod/index.zod.ts is now ui/packages/types/src/zod/index.zod.ts; packages/types/src/form.ts is now ui/packages/types/src/form.ts; packages/plugin-list/src/ListView.tsx is now ui/packages/plugin-list/src/ListView.tsx; and 1 more the same way.

    packages/spec/src/ui/view.zod.ts was already objectstack's own path and stays as written.

    Labels. sdui, types had no twin in this repository and were dropped by the transfer; every other label stayed.

    No stale pm:dispatched or assignee to strip.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    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.

  5. 30 remaining items

  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 3 · 2026-10-27T23:39Z
    Session: session_01WGufzgbJhFoXYsLSe5WSPP
    Account: os-support-ai
    Branch: claude/project-thread-eqo5sw (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-25923-<slug>)
    Worktree: objectstack-issue-25923
    Domain: domain:spec
    Seat: domain:spec#3
    Scope: work item 3 only (the list view's top-level options bag and its stored-row census; then the calendar / gantt flat fallback read and the hand-off types ObjectCalendarFlatFields / ObjectGanttFlatFields, with the three comment polishes from 6437594464). Not item 2's FormField index signature, not the immutable follow-up.
    File surface: ui/packages/app-shell/src/views/{ObjectView,objectViewListRenderer,InterfaceListPage}.tsx, ui/packages/plugin-list/src/ListView.tsx, ui/packages/types/src/{objectql.ts,zod/objectql.zod.ts}, ui/packages/plugin-calendar/** and ui/packages/plugin-gantt/** (flat fallback only), their tests, packages/spec/src/ui/view.zod.ts (only refusal text / JSDoc naming the double read), any ADR-0087 entry or migration the census forces, one changeset per touched published package (stop on breach; explain in the report)
    Container & model: M–L, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (published authoring-face narrowing; one CONTRACT_REVIEW_TIER review owed before the queue, run by the seat), effort: high
    Clause-②: no (narrowing) (provisional: the objectui faces stop accepting options and flat keys; the dev re-judges from the diff)
    Metadata-protocol: none (provisional; ADR-0087: marker owed if the census forces a stored conversion)
    Responsibility: the spec lane already refuses options (view.zod.ts z.never, VIEW_ITEM_OPTIONS_REFUSED); this round removes objectui's second contract and its readers
    Thread-read: 6448239783
    Premise: holds on origin/main 912076f8 — storedLegacyOptions still composes in objectViewListRenderer.tsx (:322), InterfaceListPage.tsx (:448) has the twin, and ListView.tsx has 38 options?. reads; the dev measures the stored-row census before editing
    Serial constraints cleared: no open PR on these files that the seat knows of (the dev re-checks before editing); item 4 (PR #26751) and item 1 (PR #26411) have landed

  7. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Claim amendment — census, split and a seat ruling on the options bag · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T23:44Z · same claim (6456306486), same branch claude/project-thread-eqo5sw.

    Census by the dev on origin/main 4b8f8cdc (an AST, JSON and YAML walk over 19,856 files):

    • Top-level options bag on a view: 158 sites. 157 are tests. The other one is the composer itself (objectViewListRenderer.tsx ~:395).
    • Flat calendar nodes: 12. Flat gantt nodes: 23. All are tests, except one plugin-gantt demo host, which is not stored.
    • Shipped metadata (examples, apps, console, seeds, platform objects, fixtures): 0.
    • Not reachable, so not counted as zero: hotcrm metadata, production tenant sys_metadata rows, cloud.

    Premise correction. The spec refuses options only on the ViewItem record member. The flattened list overlay write door (ListViewOverlayWireSchema, packages/spec/src/ui/view.zod.ts ~:6548) still declares the bag as ListViewOverlayOptionsSchema, judged key by key, under ruled direction A on objectui#10380. So PUT /api/v1/meta/view can store a bag today. ViewPreview.tsx and plugin-view ObjectView.tsx also read it.

    Seat ruling: route A. The ruling on objectui#10380 ruled out refusing the bag whole for one stated reason: "which would break the legacy options.map path objectui pins". That objectui path is exactly what this card, filed with the maintainer's approval, retires. Once the reader goes, the reason goes with it. The retirement follows the standard path for a legacy wire spelling: an ADR-0087 fold for stored rows, never tolerance at a consumer (Prime Directive #12). Order of work:

    1. PR (this branch, now), Part of #25923. Retire the calendar and gantt flat fallback read and the hand-off types, and apply the three comment polishes from 6437594464. search / searchableFields, which ListView writes at runtime, stay on a package-local hand-off type.
    2. PR, Part of #25923. Close the list overlay door: options becomes z.never, with the remedy "write the block top-level". Add an ADR-0087 D2 conversion that folds stored options.KIND onto KIND, with the top-level key winning per key (the spec's declared precedence), replayed on stored rows. Add a D3 migration note. The ListViewOverlayOptionsSchema JSDoc records that this ruling supersedes direction A on objectui#10380, and why. Clause-②: no (narrowing).
    3. PR, closes the item. Delete the objectui side: the composer, the InterfaceListPage twin, the ListView double reads, the ViewPreview fold, the plugin-view relay and NamedListView.options, and set the zod ListViewSchema.options to z.never with the spec's remedy text.

    File surface added (for PRs 2 and 3):

    • packages/spec/src/ui/view.zod.ts (the overlay door and its JSDoc), with its tests and the ADR-0087 registration and conversion entry.
    • ui/packages/app-shell/src/views/ViewPreview.tsx.
    • ui/packages/plugin-view/src/ObjectView.tsx (only the options relay).
    • One changeset per touched published package.

    Each PR re-checks for open PRs on its files before editing. The seat runs a contract-tier review on each PR.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Claim amendment — PR 1 file surface · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T23:50Z · same claim (6456306486), same branch claude/project-thread-eqo5sw.

    The dev measured four spec files that PR 1 would make false. Retiring the flat fallback read makes the published sentence "the flat spelling is only a stored-document fallback the renderer still reads" untrue. That sentence appears in:

    • packages/spec/src/ui/component.zod.ts: the OBJECT_CALENDAR_FLAT_FIELD_GUIDANCE and OBJECT_GANTT_FLAT_CONFIG_GUIDANCE prescriptions, and the notes beside them;
    • packages/spec/src/kernel/functional-completeness.ts (two comments);
    • packages/spec/src/ui/component.test.ts (one comment);
    • packages/spec/liveness/view.json (the list.calendar / list.gantt read-point notes, if they carry the same clause).

    A sentence this round makes false is fixed in this round, so all four files join PR 1's surface. Only the false clause is rewritten. The prescription itself does not change: "write it in the calendar / gantt block". The PR adds an @objectstack/spec patch changeset. Any regenerated reference docs and pinned refusal-text snapshots that follow from the wording move with it.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 25923,
      "status": "done",
      "branch": "claude/project-thread-eqo5sw",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27026",
      "head": "6001470dceeae8a1413c05c3943d99d97cedef3e",
      "session": "session_01WGufzevL8Q6rTfGz2XHXGT",
      "premise_still_valid": false,
      "summary": "Work item 3, PR 1 of the 3 the seat ruled (route A): getCalendarConfig and getGanttConfig read the calendar / gantt block only; the flat fallback, ObjectCalendarFlatFields, ObjectCalendarHandoff and ObjectGanttFlatFields are removed, with search/searchableFields kept on the package-local ObjectGanttSearchFields. The three polishes from 6437594464 are applied, the false clause of the two spec refusal texts is rewritten with an @objectstack/spec patch changeset, and every renderer and mirror test that mounted a flat node now writes the block. Premise correction (recorded before the ruling): the spec list overlay door still accepts the options bag, so its objectui readers cannot go before PR 2 closes the door with an ADR-0087 fold; PR 2 and PR 3 follow after this merges.",
      "tests": "ui (os-verify-lock): calendar+gantt+consumer set Test Files 818 passed | 3 skipped (821), Tests 18599 passed, exit 0; wider set 75/75 files, 1693 tests, exit 0; 5 moved files 111 tests exit 0; plugin-gantt rerun 101 passed | 1 skipped, exit 0. type-check types, plugin-calendar, plugin-gantt, plugin-list, app-shell (tsc --noEmit + tsconfig.test.json) exit 0. spec: Test Files 738/738, Tests 22130 passed, exit 0. Census after: flat calendar nodes 12 to 7, flat gantt 23 to 9, all refusal pins or type-level mirror pins; 0 stored sites before and after.",
      "gates": "dispatch-gates --commands: 84; each run with exit code recorded; --ran: 84 derived, 83 run, 1 NOT-MEASURED, 0 UNRUN; 83 exit 0; check:dual-build-cjs-loads exit 3 (needs full pnpm build, no judged package in the diff). check-platform-checklist --changed-from origin/main exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "6001470dc · os-verify-lock VERDICT command-exit 1 · Test Files 1 failed | 343 passed | 3 skipped (347) · Tests 2 failed | 2782 passed | 20 skipped (2804) · failures: test/packaged-action-disabled-dispatch-live.dogfood.test.ts acceptance[0] and acceptance[5]/[2]; the same file passes alone on this head (8/8, exit 0); the diff touches no server, action or showcase package",
      "mcp_calls": "2 — mcp__github__issue_read x2 (reads only)",
      "api_writes": "1 — relay pr_create (objectstack-ai/objectstack#27026); this os-dev-report comment is the 2nd, relay comment op on #25923",
      "open_questions": [],
      "out_of_scope_findings": [
        "packaged-action-disabled-dispatch-live dogfood file red inside the full battery, green alone on the same head; observation only, not filed · carrier: none"
      ]
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    Claim amendment — PR 2 and PR 3 merge into one · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-28T03:52Z · same claim (6456306486), same branch line claude/project-thread-eqo5sw.

    The dev measured the split from amendment 6456384663 and found it cannot hold.

    • @object-ui/types builds its ListViewSchema.options as listOverlay.shape.options.unwrap().extend(...). Once the door closes, that member is a retiredKey tombstone, and the unwrap throws when the module loads.
    • Pointing the mirror at the tombstone by reference breaks @object-ui/plugin-list instead: 30 TS errors in ListView.tsx, which reads schema.options?.KIND.
    • The only way to keep PR 2 apart from PR 3 would be a transitional local copy of the old bag on the ui face. That face would then accept what the door refuses, which objectui#8327 forbids.

    Seat ruling: one PR (dev's option A). The door closure, the ADR-0087 D2 fold, the D3 note and the objectui reader deletion land together, as #26677 did for navigation.mode. The ui mirror takes the tombstone by reference, so no face accepts what the door refuses at any commit on main. This PR closes work item 3 after PR 1 (#27026) has landed. Its body carries Fixes #25923 only if work item 2 is also done; otherwise it carries Part of #25923 and says what is left.

    The file surface is the union of amendment 6456384663's PR 2 and PR 3 lists.

    Census for the body (the dev's reading):

    • 158 bag sites: 157 tests plus the composer. 0 stored rows reachable. hotcrm, tenant sys_metadata and cloud are named as unreachable, not counted as zero.
    • 166 blocks: 49 fold valid, 79 would be refused after the fold, as test fixtures to move.
    • GanttConfigSchema refuses 4 of 4 static options.gantt values.

    Clause-②: no (narrowing). The ADR-0087 marker names list-view-options-bag-folded.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    ACCEPT → PR #27026 comment 6461454849 · domain:spec seat 3 · 2026-10-28T05:30Z

    Work item 3, PR 1: the calendar/gantt flat fallback is retired. Contract review round 2 PASS (6459158552). The head c70b9cd5 merges main; the PR diff is unchanged. The PR is ready, with auto-merge armed. Next on this card: one PR that closes the options bag door, adds the ADR-0087 D2 fold and deletes the objectui readers (ruling 6460225523).


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    Landed in part (work item 3, PR 1) · domain:spec seat 3 · os-support-ai · 2026-10-28T06:19Z

    reading value
    PR #27026 merged through the queue at 2026-10-28T06:18Z, merge commit d8a9cc598f
    origin/main d8a9cc59 contains the merge commit (git merge-base --is-ancestor)

    Delivered: the calendar and gantt renderers read their binding from the calendar / gantt block only. The flat fallback read and the hand-off types ObjectCalendarFlatFields / ObjectGanttFlatFields are retired. search / searchableFields stay on the package-local ObjectGanttSearchFields. 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 fold list-view-options-bag-folded and its D3 note, and deletes the objectui readers (ruling 6460225523). It also carries the GanttConfigSchema refused-value census. pm:dispatched stays on. Part of #25923, so the card stays open.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    ACCEPT (Tier H, awaiting an authorized approval) → PR #27206 comment 6468447600

  14. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

    Landed · domain:spec seat 3 · os-support-ai · 2026-10-28T20:44Z

    reading value
    PR #27206 approved by os-zhuang (Tier H) and merged through the merge queue at 2026-10-28T20:43Z, head 8a4ad1fba, merge commit 8eeb3b3b92
    origin/main contains the merge commit (git merge-base --is-ancestor)

    Delivered (work item 3, with #27026):

    Still open on this card, per the body: FormField's index signature (ruling 6412606977) and the immutable follow-up. pm:dispatched stays; this card is not closed by the merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:specpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions