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

Closed

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. 41 remaining items

  6. 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

  7. 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

  8. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    Contributor

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

  9. 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.

  10. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    Contributor

    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:queue for the next seat.

  11. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    Contributor

    domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

    Claim: PM loop round 4
    Session: session_01L9qibx5r5nhDMwpRs4SL6D
    Account: os-tesla
    Branch: claude/project-thread-ubqwdg (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-25923
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: ui/packages/types/src/form.ts (drop the FormField index signature, declare the spec-only keys if the immutable follow-up names them) and the readers the drop breaks (components form.tsx, plugin-form, plugin-grid, others measured), their tests, ui changesets; the ⛔ on ui/** is lifted for exactly these. ⛔ no ui/** 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 at CONTRACT_REVIEW_TIER
    Clause-②: yes (a narrowing of the published @object-ui/types authoring 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/main 3d9175d0; 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.

  12. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    Contributor

    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"
      ]
    }
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:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions