Skip to content

refactor(types)!: derive the object-form and object-calendar spec keys, rename the legacy zod ActionSchema (#25923) - #26751

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-gru2pt
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-gru2pt

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Part of #25923 (work item 4 only; item 3 and the FormField follow-up stay on the card)

Clause-②: yes (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — skills/objectstack-ui/references/react-blocks.md already teaches contentLayout and confirmOnDiscard; object-calendar locale and loading are taught only by the spec rows' .describe() text, with no skills/** or ui/content/docs page; no skills/** page names the zod ActionSchema, and the two ui docs pages that imported it are updated in this diff

维护者速读

  1. 卡面"33 个 spec-only 键"实测是同名碰撞:spec ActionSchema 对 objectui 无关的旧 type: 'action' 节点 schema。真实配对上的缺口是 11 个键。
  2. 4 个有渲染器读取的键按引用派生:ObjectFormSchema.contentLayout/confirmOnDiscard、ObjectCalendarSchema.locale/loading(均为节点键,不是 refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block #26411 移走的日历字段名键)。
  3. 其余 7 个是 FormFieldSchema 上 spec 表单视图条目的键(两层同名词汇),逐键登记在 zod-mirror-parity.test.ts 的普查里:2 translated、4 carried-not-rendered、1 spec-retired。
  4. @object-ui/types/zod 的 ActionSchema 改名 CRUDActionSchema:0 个生产导入方,改名比退役破坏最少;接受集不变。
  5. 收窄:旧导出名消失,宽容面对 4 个键的坏值现在按 spec 拒绝 ⇒ Clause-②: yes (narrowing)、Metadata-protocol: narrowed,changeset 带 BREAKING Before/Now/Fix。
  6. 门禁:dispatch-gates 34/34 exit 0 已对账;ui vitest(types、plugin-form、plugin-calendar)660/660 文件通过;依赖 @object-ui/types 的 39 个包 type-check 108/108 通过。

Measurement (main 43731031)

The key-drift probe pairs each @object-ui/types/zod mirror with the spec schema of the same export name in @objectstack/spec/data ∪ @objectstack/spec/ui, and each node mirror with its ComponentPropsMap row by type literal. A key the spec declares that the mirror neither declares nor refuses is "spec-only".

Pair Spec-only keys on main Note
ActionSchema ↔ spec ActionSchema 33 Not a pair: objectui's ActionSchema is the legacy type: 'action' node and shares no vocabulary with the spec's action metadata. This is the card's "33".
FormFieldSchema ↔ spec FormFieldSchema 7 Two layers under one name (runtime field vs form-view entry)
ObjectFormSchema ↔ ComponentPropsMap['object-form'] 2
ObjectCalendarSchema ↔ ComponentPropsMap['object-calendar'] 2
every other pair 0

Real gap over the real pairs: 11 keys. After this PR: 0 unrecorded, judged by the new census in zod-mirror-parity.test.ts.

Per-key disposition

Key Disposition Where
ObjectFormSchema.contentLayout derived by reference (SpecObjectFormProps['contentLayout'], zod .shape.contentLayout) ModalForm reads it
ObjectFormSchema.confirmOnDiscard derived by reference ModalForm, DrawerForm read it
ObjectCalendarSchema.locale derived by reference plugin-calendar resolveAuthoredLocale(rest.locale)
ObjectCalendarSchema.loading derived by reference plugin-calendar resolveExternalLoading(rest.loading)
FormFieldSchema.field recorded: translated normalizeSectionField writes it as the runtime name
FormFieldSchema.helpText recorded: translated written as the runtime description
FormFieldSchema.immutable recorded: declared, not rendered copied onto the runtime field; no renderer reads it
FormFieldSchema.keyField recorded: declared, not rendered copied through for the record widget; no widget reads it yet
FormFieldSchema.disclosure recorded: declared, not rendered copied through for composite widgets; no widget reads it yet
FormFieldSchema.precision recorded: declared, not rendered number widgets read scale and the object field's metadata
FormFieldSchema.publicPicker recorded: spec-retired the spec's own retiredKey() tombstone refuses it with its remedy

No key needed a new z.never refusal: the only retired key (publicPicker) is already refused by the spec with its remedy, and objectui's runtime layer never had it.

Seat guard on the two calendar keys: locale and loading are members the spec authors on the object-calendar node and the renderer reads off the node. They are not the calendar field-name keys PR #26411 moved to the hand-off type, so nothing returns to the published face that item 1 took off it.

ActionSchema: rename, not retire

Importers of the zod ActionSchema on main: 0 production, 7 test files, 2 docs pages. Retiring it would also break the action arm of the CRUD discriminated union and the actions arrays of CRUDDialogSchema, DetailSchema and the chain, which all hold it. Renaming changes only the export name, so it breaks fewest. The TypeScript twin keeps its deprecated ActionSchema name in @object-ui/types.

What changes

  • ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/objectql.ts: the four keys, by reference on both faces; ObjectFormSlotKey carries the two form keys.
  • ui/packages/types/src/zod/crud.zod.ts, index.zod.ts: ActionSchema → CRUDActionSchema, every internal reference with it.
  • Tests: the seven test importers renamed; object-view-slot-key-lists.test.ts pins ObjectFormSchema at 71 members (was 69).
  • Docs: the two pages that import the const.
  • .changeset/25923-spec-only-keys-crud-action-schema.md: @object-ui/types minor, BREAKING Before/Now/Fix.

Tests

zod-mirror-parity.test.ts is extended (no new gate) with a census, "every spec-only key over the spec pairs is derived, refused or recorded":

  • no spec-only key without a row; no dead row; every reason at least 40 characters;
  • non-vacuity: the derived pairs include the four named above;
  • identity pins for the four derived keys, and contentLayout: 'tabs' refused at path contentLayout;
  • the zod barrel has no ActionSchema and has CRUDActionSchema, while @objectstack/spec/ui still has ActionSchema.

Reverse check: removing the contentLayout derivation turns the census red naming ObjectFormSchema.contentLayout; restored afterwards.

Acceptance notes

Verification

Head 38c2841a (readings below were taken on 96c8d55d, with main 94bab5a2 merged in; d18ba32e adds a reasoned eslint-disable-next-line no-restricted-imports on the census's import * as SpecUi from '@objectstack/spec/ui', the form spec-ui-schema-reexports.test.ts uses, which turned Lint UI green; 38c2841a corrects enhanced-actions.mdx to name CRUDActionSchema). The vitest, gates and type-check readings below were taken at b5f1b90b (before the merge); after the merge, @objectstack/spec was rebuilt and cd ui && pnpm exec vitest run packages/types/ re-run under os-verify-lock: 393/393 files, 10691 passed, exit 0. #26365 is still an open draft; it edits zod-mirror-parity.test.ts in different regions, and whichever lands later merges main and re-runs that file.

  • node scripts/pm/dispatch-gates.mjs --ran: 34 derived, 34 run, 0 NOT-MEASURED; every exit 0. pnpm --dir ui exec vitest run scripts/ first exited 1 on an unbuilt @objectstack/core dist in this container, and exited 0 once it was built.
  • Under os-verify-lock, cd ui && pnpm exec vitest run packages/types/ packages/plugin-form/ packages/plugin-calendar/: 660/660 files, 13570 passed, 96 skipped, exit 0.
  • Under os-verify-lock, cd ui && pnpm exec turbo run type-check --filter='...@object-ui/types' --concurrency=2 --continue: 39 packages in scope, 108/108 tasks successful, exit 0.
  • node scripts/check-platform-checklist.mjs --changed-from origin/main: exit 0 (no anchored file in the change set).
  • node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c

… legacy zod ActionSchema

Work item 4 of #25923. ObjectFormSchema gains contentLayout and
confirmOnDiscard, ObjectCalendarSchema gains locale and loading, each the
ComponentPropsMap row member by reference on both faces. The zod
ActionSchema (legacy type: 'action' node) is renamed CRUDActionSchema so
@object-ui/types/zod no longer publishes the spec's ActionSchema name for
an unrelated contract. zod-mirror-parity.test.ts gains the spec-only
census over the derived spec pairs.

Claude-Session: https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c
Co-authored-by: Claude <noreply@anthropic.com>
The member-count pin in object-view-slot-key-lists moves from 69 to 71:
`contentLayout` and `confirmOnDiscard` are now declared by reference to
`ComponentPropsMap['object-form']`, and the `form` slot carries both.

Claude-Session: https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c
Co-authored-by: Claude <noreply@anthropic.com>
…pace importer (#25923)

The census walks the whole `@objectstack/spec/ui` surface to pair each
mirror with its same-name spec schema, so the namespace import trips the
#3090 `no-restricted-imports` entry for the spec `FormFieldSchema`. It reads
the schemas at runtime only; the disable carries that reason, as the other
sanctioned namespace importers do.

Claude-Session: https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 96c8d55d3a6c53b212be4fc2f6482e10694d380f
Local-runs: none
Scope: ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/zod/crud.zod.ts, ui/packages/types/src/zod/index.zod.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/tests/zod-mirror-parity.test.ts, ui/packages/types/src/tests/object-view-slot-key-lists.test.ts, ui/packages/types/src/tests/action-callback-retired-7068.test.ts, ui/packages/types/src/tests/disabled-twin-symmetry-7087.test.ts, ui/packages/types/src/tests/duration-unit-key-renames-25762.test.ts, ui/packages/types/src/tests/handler-keys-string-any-mirrors-7344.test.ts, ui/packages/types/src/tests/phase2-schemas.test.ts, ui/packages/types/src/tests/zod-lazy-getter-identity-7918.test.ts, .changeset/25923-spec-only-keys-crud-action-schema.md, ui/content/docs/core/enhanced-actions.mdx, ui/content/docs/guide/schema-overview.md + packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/action.zod.ts, packages/spec/src/shared/strict-object.ts, ui/packages/types/src/zod/base.zod.ts, ui/packages/types/src/zod/imported-defaults.ts, ui/packages/types/src/zod/form.zod.ts, ui/packages/types/src/form.ts, ui/packages/types/src/crud.ts, ui/packages/types/src/strict-authoring-face.ts, ui/packages/types/src/tests/spec-ui-schema-reexports.test.ts, ui/packages/plugin-form/src/ModalForm.tsx, ui/packages/plugin-form/src/DrawerForm.tsx, ui/packages/plugin-form/src/sectionFields.ts, ui/packages/plugin-form/src/index.tsx, ui/packages/plugin-calendar/src/index.tsx, ui/packages/plugin-calendar/src/calendar-view-renderer.tsx, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/eslint.config.js, .github/workflows/ci.yml, skills/objectstack-ui/references/react-blocks.md, scripts/check-adr-0087-registration.mjs, scripts/pm/record-recognisers.mjs, .claude/skills/pm-dispatch/references/execution-duties.md

Inputs: card #25923 body and every comment through the os-dev-report 6445173979; PR #26751 body, file list (15 files, +307/-50), zero PR comments and reviews, the net diff against main at this head (merge-base 94bab5a2); the head's check-runs, read 2026-10-27T14:53Z: 52 runs, every one completed. The eight required contexts: Lint & Repo Gates success, TypeScript Type Check success, Test Core success, Build Core skipped (path filter), Governed Surface Queue Guard success, Lint UI FAILURE, Type Check UI success, Build & E2E UI success; Test UI 16/16 shards success; Check Changeset success. Written 2026-10-27T15:09Z. Read-only: git fetch of the head into a review ref, git show / git grep on it and on the merge-base, GitHub GET only; nothing built, run or re-run.

① Derived judgments

  1. Counts — the dev's 11 holds; the card's 33 is the non-pair. Re-derived statically from the head's tree. Spec ObjectFormPropsSchema (component.zod.ts:6840-6958, second strictObject argument) authors 39 keys; the mirror at the merge-base (50 keys plus BaseSchema's 16) lacked exactly contentLayout, confirmOnDiscard (2), at head 0. Spec ObjectCalendarPropsSchema (:6077-6151) authors 10 keys; the merge-base mirror (14 plus base) lacked exactly locale, loading (2), at head 0. Spec FormFieldSchema is FormFieldBaseSchema.extend({ fields }).strict() (view.zod.ts:3467, 3790), 29 keys; against form.zod.ts FormFieldSchema (72 keys) the spec-only set is disclosure field helpText immutable keyField precision publicPicker — exactly the 7 rows. 2 + 2 + 7 = 11. The spec strictObject helper keeps aliases / guidanceSets out of the shape (packages/spec/src/shared/strict-object.ts:59-80: they build the unknown-key error map), so no alias key such as the calendar row's filters hides as spec-only; the census's 0-unrecorded reading is consistent with the static read. The 33: actionObject() (action.zod.ts:903-1865) declares 41 keys plus the MetadataProtectionFields spread, of which 11 (confirmText description errorMessage icon label method name onSuccess successMessage type variant) are also on CRUDActionSchema ∪ BaseSchema — about 30–33 spec-only on the spec ActionSchema against objectui's unrelated type: 'action' node. That is the card's number, and it is not a real-pair gap. Premise correction accepted.

  2. The 4 derived keys are authored on the node and read by the renderer; none is an item-1 key. Spec: component.zod.ts:6911 contentLayout: z.enum(['simple', 'tabbed']).optional(), :6913 confirmOnDiscard: z.boolean().optional(), :6135 locale: z.string().optional(), :6136 loading: z.boolean().optional() — node members of the object-form / object-calendar rows. Renderer reads: ModalForm.tsx:954 (schema.contentLayout === 'tabbed' with more than one group), ModalForm.tsx:382 and DrawerForm.tsx:289 (schema.confirmOnDiscard !== false), plugin-calendar/src/index.tsx:271,273 (resolveAuthoredLocale(rest.locale), resolveExternalLoading(rest.loading)) and calendar-view-renderer.tsx:329. Mirror by reference on both faces: objectql.zod.ts:1042-1043, 3182-3183 through the established stripImportedDefaults(SpecX).shape.KEY pattern (:975 layout is the precedent; imported-defaults.ts:382 returns the walked node, so the identity pin in the census is sound), objectql.ts:2288, 2296, 4322, 4329 through the already-imported SpecObjectFormProps / SpecObjectCalendarProps. Item 1's hand-off type is Pick of the five field-name keys only (ObjectCalendar.tsx:148-150); at the merge-base neither face declared any of the four (0 hits), so nothing item 1 moved off returns. ObjectFormSlotKey carries both form keys (objectql.ts:2505-2506); the object-form block arm already derives the whole row (objectql.zod.ts:4727) and refuses the flat spelling there, unchanged. No declared-but-unread key.

  3. The 7 FormField rows: recording is honest, and ruling 6412606977 is respected. The two FormFieldSchemas are different layers (runtime widget config vs. spec form-view entry; ruling objectui#3090, enforced by the no-restricted-imports tripwire at ui/eslint.config.js:239-257), so deriving by reference would cross the layer and refusing would contradict normalizeSectionField. Verified per row: sectionFields.ts:181-187 reads field as the runtime name; :417, 460, 461 copy immutable, keyField, disclosure onto the runtime field, and git grep finds no non-test reader of those three on the runtime field under plugin-form, components, fields; precision reaches only the fields number formatters as a parameter (fields/src/index.tsx:663), not from the runtime form field; publicPicker is a spec retiredKey() tombstone. Strict face refuses the 7 (derived from declared keys), tolerant face passes them unexamined, spec accepts six of them — that disagreement is the layer boundary the rows now name, not a defect of this PR. form.ts:1945 index signature untouched (the file is not in the diff).

  4. Rename complete, no alias, consistent with !. crud.zod.ts:81 CRUDActionSchema; dialog.actions (:105), chain (:135), DetailSchema.actions (:205), CRUDDialogSchema.actions (:276), the union cast (:306) and CRUDActionSchemaType (:328) all renamed; barrel index.zod.ts:378. git grep at head: no importer of a zod ActionSchema from @object-ui/types/zod or crud.zod outside the changeset's own Before/Fix lines; ActionSchemaType / CRUDActionSchemaType have 0 importers; 'ActionSchema' in ZodBarrel is pinned false in the census. No deprecated alias is kept, which is what the ! title and the BREAKING banner say. The TS twin crud.ts:40 ActionSchema already carried @deprecated (towards UIActionSchema) and is unchanged, as the changeset states. One prose leftover: ui/content/docs/core/enhanced-actions.mdx:304 still says the shape is refused by "ActionSchema's zod schema" — now CRUDActionSchema; cosmetic, fix with the next head.

  5. Declarations. Clause-②: the PR's yes (narrowing) is right for this diff and the claim's no (narrowing) is not. The diff widens a published accept set and the public face — the strict authoring face (strict-authoring-face.ts, derived from declared keys) now accepts contentLayout, confirmOnDiscard, locale, loading that it refused as unrecognised keys before; the ObjectFormSchema / ObjectCalendarSchema interfaces gain four optional members; CRUDActionSchema and CRUDActionSchemaType are new names on the ./zod entry — and it narrows (the tolerant face refuses bad values at the key's own path; the old export name is gone). execution-duties.md:66 makes yes the answer to 「判据:本卡放宽接受集或扩大公开面吗」; line 105, 「收窄已发布接受集的卡是 Clause-②: no,不移车道;入队前欠一次契约复审档复核」, names the pure-narrowing card; record-recognisers.mjs:188, 1628 read yes (narrowing) as "a diff that widens and narrows owes the narrowing review" — this face, this tier. Same disposition the seat amended the refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block #26411 claim to. Metadata-protocol: narrowed is right under the precedence rule (narrowed over added); the claim's widened is outside the closed set, as the dev reported. Skills-docs: none — REASON: true for what it names (react-blocks.md:27-28 teaches both form keys; no skills/** page names the zod ActionSchema; both docs importers are in the diff), but it says nothing about object-calendar locale / loading, which no skills/** or ui/content/docs page teaches — the spec's .describe() rows are the only teaching. none stands; the reason should name them on the next head.

② Semver level

  • .changeset/25923-spec-only-keys-crud-action-schema.md: '@object-ui/types': minor, Clause-②: yes (narrowing), Metadata-protocol: narrowed, BREAKING Before / Now / Fix with the FROM / TO import rewrite and the contentLayout value fix. Matches what the diff publishes: @object-ui/types (18.2.0, public, ./zod subpath) is the only package that moves; ui/content/docs/** is not a package. Kind narrowed takes at least minor on that package (AGENTS.md Post-Task Checklist 3), so minor is the level; the ! in the title and the changeset summary agree with the (narrowing) arm. The claim's Metadata-protocol: widened is malformed; the dev's narrowed is the reading.
  • Dogfood battery owed by ! / (narrowing) (review-checklist:51): dev-reported at this head, exit 0, 302 files passed; not re-run here.
  • ADR-0087 marker not-required (no-migration-prescription): the body's Fix line (:25) is a FROM / TO rewrite of a consumer import — a migration prescription in substance. The gate's own header (check-adr-0087-registration.mjs:147-154) says the category is refused when the body carries one, and (:203-213) that an exemption held by a detector miss is the anti-pattern it was written against; the dev states in the changeset and the report that the pass rests on the detector not reading the arrowless line, and that runtime-export-retired refuses a *.zod.ts operand on the ./zod subpath. Precedent in this lane: types,react,skills,docs: retire the node-level visibility dialects visibleOn / hiddenOn / disabledOn / visible and teach only visibleWhen #25926 and refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block #26411 (contract review PASS 6436950621) carry the same category beside a Fix line. Escalated in ③, not a FAIL reason on its own.

③ Boundary flags

  1. FAIL — required context Lint UI is failure on this head, and the diff causes it. ui/packages/types/src/__tests__/zod-mirror-parity.test.ts:961 adds import * as SpecUi from '@objectstack/spec/ui'; the job's annotation on that line reads: namespace import is invalid because 'FormField' and 'FormFieldSchema' from '@objectstack/spec/ui' are restricted (no-restricted-imports, ui/eslint.config.js:247-257, the objectui#3090 tripwire); pnpm run lint in ui/packages/types exited 1 (job 122248905282, completed 2026-10-27T14:35Z). The one sanctioned namespace import of that module, spec-ui-schema-reexports.test.ts:23-25, carries eslint-disable-next-line no-restricted-imports with its reason on the line above. Fix on the next head: named imports for the schemas the census reads, or that disable line with the reason that this census is the sanctioned cross-layer instrument. The report's gate line (dispatch-gates --ran 34/34) did not cover ui lint and said "CI check-runs on the PR: not yet reported"; the next report reads them. A new head is a new record.
  2. Deviation Clause-②: yes (narrowing) against the claim's no (narrowing) — accepted (①.5); the seat rewrites the claim line. Deviation Metadata-protocol: narrowed against the claim's widened — accepted.
  3. ADR-0087 disposition — escalated to the seat: either rule that no-migration-prescription stands for a ./zod-subpath export rename on the lane's precedent (types,react,skills,docs: retire the node-level visibility dialects visibleOn / hiddenOn / disabledOn / visible and teach only visibleWhen #25926, refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block #26411), or file the gate gap (runtime-export-retired cannot name a *.zod.ts operand outside the main entry) as its own card. The changeset text need not change for either.
  4. Skills-docs: reason — name the calendar keys' coverage on the next head (①.5); not a REWORK on its own.
  5. Out-of-scope finding "immutable / keyField / disclosure / precision carried, not rendered": immutable accepted by the spec, copied by normalizeSectionField, enforced by no renderer, is declared ≠ enforced on an edit-mode form (Prime Directive chore: version packages #10); the dev recorded it in the Acceptance notes with reach unmeasured and filed no card. Seat: file it, or fold it into the FormField follow-up row on 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 at ACCEPT.
  6. Prose leftover enhanced-actions.mdx:304 (①.4) — fix with the lint head.
  7. refactor(ui)!: retire @object-ui/plugin-ai and its three node types #26365 also edits zod-mirror-parity.test.ts in other regions — ordinary concurrency; the later lander merges main and re-runs that file.

Implemented-by: claude/project-thread-gru2pt
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: FAIL

…ma (#25923)

The enhanced-actions page still named the zod schema `ActionSchema`; the
const is `CRUDActionSchema` on `@object-ui/types/zod` since the rename.

Claude-Session: https://claude.ai/code/session_01WGufzhgTn8yFwqXxanJd4c
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 38c2841aebc947ce9f3abab447b64faefb8e58a3
Local-runs: none
Scope: ui/packages/types/src/zod/objectql.zod.ts, ui/packages/types/src/zod/crud.zod.ts, ui/packages/types/src/zod/index.zod.ts, ui/packages/types/src/objectql.ts, ui/packages/types/src/tests/zod-mirror-parity.test.ts, ui/packages/types/src/tests/object-view-slot-key-lists.test.ts, ui/packages/types/src/tests/action-callback-retired-7068.test.ts, ui/packages/types/src/tests/disabled-twin-symmetry-7087.test.ts, ui/packages/types/src/tests/duration-unit-key-renames-25762.test.ts, ui/packages/types/src/tests/handler-keys-string-any-mirrors-7344.test.ts, ui/packages/types/src/tests/phase2-schemas.test.ts, ui/packages/types/src/tests/zod-lazy-getter-identity-7918.test.ts, .changeset/25923-spec-only-keys-crud-action-schema.md, ui/content/docs/core/enhanced-actions.mdx, ui/content/docs/guide/schema-overview.md + packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/action.zod.ts, packages/spec/src/shared/strict-object.ts, ui/packages/types/src/zod/base.zod.ts, ui/packages/types/src/zod/imported-defaults.ts, ui/packages/types/src/zod/form.zod.ts, ui/packages/types/src/form.ts, ui/packages/types/src/crud.ts, ui/packages/types/src/strict-authoring-face.ts, ui/packages/types/src/tests/spec-ui-schema-reexports.test.ts, ui/packages/plugin-form/src/sectionFields.spec-parity.test.ts, ui/packages/plugin-form/src/ModalForm.tsx, ui/packages/plugin-form/src/DrawerForm.tsx, ui/packages/plugin-form/src/sectionFields.ts, ui/packages/plugin-form/src/index.tsx, ui/packages/plugin-calendar/src/index.tsx, ui/packages/plugin-calendar/src/calendar-view-renderer.tsx, ui/packages/plugin-calendar/src/ObjectCalendar.tsx, ui/eslint.config.js, .github/workflows/ci.yml, skills/objectstack-ui/references/react-blocks.md, scripts/check-adr-0087-registration.mjs, scripts/pm/record-recognisers.mjs, .claude/skills/pm-dispatch/references/execution-duties.md

Second record on this PR; it supersedes the FAIL record on 96c8d55d (comment 6445632813). Inputs: card #25923 body and every comment through the os-dev-report 6445173979 and the amended claim 6442948738 (now Clause-②: yes (narrowing), Metadata-protocol: narrowed); PR #26751 body as rewritten by the seat (head line names 38c2841a; Skills-docs: names the calendar keys; the seat's ADR-0087 ruling line), file list (15 files, +314/-52), zero PR comments and reviews; the net diff against main at 96c8d55d (merge-base 94bab5a2) plus GET compare 96c8d55d...38c2841a: 2 commits ahead, 0 behind, 2 files, +7/-2 — (a) d18ba32e: four comment lines above the census's import * as SpecUi from '@objectstack/spec/ui' in zod-mirror-parity.test.ts:961-964, ending in eslint-disable-next-line no-restricted-imports; (b) 38c2841a: enhanced-actions.mdx:304-306 prose, "refused by the zod schema (CRUDActionSchema in @object-ui/types/zod)". Nothing else moved, so every judgment on the schema diff is the one taken on 96c8d55d, re-read against this head. Check-runs on 38c2841a, read 2026-10-27T15:12Z: 34 runs so far — 7 success (Governed Surface Queue Guard, Test Core, Check PR Size, Auto Label, filter, Dogfood Regression Gate, 18.x opt-in skipped aside), 11 skipped (Build Core by path filter among them), 16 in_progress (every Lint · and Type Check · lane, Check Changeset, Build UI Deps, the PR-body guards); Lint UI, Type Check UI, Build & E2E UI and Test UI not yet created at that instant (they wait on Build UI Deps). An in-progress gate is a reading, not a pass: the required set is not yet green on this head. On the previous head d18ba32e — identical test-file hunk, no mdx change — all 52 runs completed with every required context success (Lint UI included) and Test UI 16/16. Written 2026-10-27T15:14Z. Read-only: git fetch of the first head into a review ref, git show / git grep on it and on the merge-base, GitHub GET only; nothing built, run or re-run.

① Derived judgments

  1. The delta from 96c8d55d is what the author says; the disable is honest; the prose fix is complete. The lint disable sits on the line directly above the namespace import, so it binds to that import and nothing else. Its reason names the census as the instrument that walks the whole spec/ui surface to pair same-name exports, read at runtime only, and cites the docs(site): landing v2 — narrative redesign (loop · gates · vocabulary) #3090 tripwire — the same shape as the two sanctioned precedents, spec-ui-schema-reexports.test.ts:23-25 ("import is its instrument, a sanctioned importer (docs(site): landing v2 — narrative redesign (loop · gates · vocabulary) #3090 tripwire)") and sectionFields.spec-parity.test.ts:35-37. Honest on the facts: the census never uses the spec FormField as a type (objectOf reads _zod.def.shape through an unknown cast), and the layer collision the tripwire guards is exactly what SPEC_ONLY_KEY_ROWS records key by key. Lint UI went from failure on 96c8d55d to success on d18ba32e with this hunk. The mdx fix closes the leftover I flagged (①.4 on the first record): the prose now names CRUDActionSchema on the ./zod entry; the const complexAction: ActionSchema at :483 is the TS twin from @object-ui/types, which keeps its name, so it stays. Both commits carry the model-free trailer pair.

  2. Counts — the dev's 11 holds; the card's 33 is the non-pair. Re-derived statically from the tree. Spec ObjectFormPropsSchema (component.zod.ts:6840-6958, second strictObject argument) authors 39 keys; the mirror at the merge-base lacked exactly contentLayout, confirmOnDiscard (2), at head 0. Spec ObjectCalendarPropsSchema (:6077-6151) authors 10 keys; the merge-base mirror lacked exactly locale, loading (2), at head 0. Spec FormFieldSchema is FormFieldBaseSchema.extend({ fields }).strict() (view.zod.ts:3467, 3790), 29 keys; against form.zod.ts FormFieldSchema (72 keys) the spec-only set is disclosure field helpText immutable keyField precision publicPicker — exactly the 7 rows. 2 + 2 + 7 = 11. The spec strictObject helper keeps aliases / guidanceSets out of the shape (packages/spec/src/shared/strict-object.ts:59-80), so no alias key such as the calendar row's filters hides as spec-only. The 33: actionObject() (action.zod.ts:903-1865) declares 41 keys plus the MetadataProtectionFields spread, 11 of them shared with CRUDActionSchema ∪ BaseSchema — about 30–33 spec-only on the spec ActionSchema against objectui's unrelated type: 'action' node. That is the card's number, not a real-pair gap. Premise correction accepted.

  3. The 4 derived keys are authored on the node and read by the renderer; none is an item-1 key. Spec: component.zod.ts:6911 contentLayout, :6913 confirmOnDiscard, :6135 locale, :6136 loading — node members of the rows. Renderer reads: ModalForm.tsx:954 (schema.contentLayout === 'tabbed' with more than one group), ModalForm.tsx:382 and DrawerForm.tsx:289 (schema.confirmOnDiscard !== false), plugin-calendar/src/index.tsx:271,273 (resolveAuthoredLocale(rest.locale), resolveExternalLoading(rest.loading)), calendar-view-renderer.tsx:329. Mirror by reference on both faces: objectql.zod.ts:1042-1043, 3182-3183 (the stripImportedDefaults(SpecX).shape.KEY pattern, :975 layout the precedent), objectql.ts:2288, 2296, 4322, 4329. Item 1's hand-off type is Pick of the five field-name keys only (ObjectCalendar.tsx:148-150); at the merge-base neither face declared any of the four. ObjectFormSlotKey carries both form keys (objectql.ts:2505-2506). No declared-but-unread key.

  4. The 7 FormField rows: recording is honest, and ruling 6412606977 is respected. Two layers under one name (ruling objectui#3090, the tripwire at ui/eslint.config.js:239-257), so deriving would cross the layer and refusing would contradict normalizeSectionField. Per row: sectionFields.ts:181-187 reads field as the runtime name; :417, 460, 461 copy immutable, keyField, disclosure, and no non-test reader of those three exists on the runtime field under plugin-form, components, fields; precision reaches only the fields number formatters as a parameter (fields/src/index.tsx:663); publicPicker is a spec retiredKey() tombstone. form.ts:1945 index signature untouched (file not in the diff).

  5. Rename complete, no alias, consistent with !. crud.zod.ts:81 CRUDActionSchema; :105, :135, :205, :276, the union cast :306 and CRUDActionSchemaType :328 renamed; barrel index.zod.ts:378. No importer of a zod ActionSchema from @object-ui/types/zod or crud.zod remains outside the changeset's Before/Fix lines; ActionSchemaType / CRUDActionSchemaType have 0 importers; 'ActionSchema' in ZodBarrel pinned false in the census. No deprecated alias kept — what the ! title and the BREAKING banner say. TS twin crud.ts:40 already @deprecated, unchanged. The one prose leftover is fixed on this head (①.0).

  6. Declarations. Clause-②: yes (narrowing) is right for this diff. It widens a published accept set and the public face — the strict authoring face (derived from declared keys) now accepts the four keys it refused as unrecognised before; the two interfaces gain four optional members; CRUDActionSchema / CRUDActionSchemaType are new names on the ./zod entry — and it narrows (bad values refused at the key's path; the old export name gone). execution-duties.md:66 makes yes the answer to 「判据:本卡放宽接受集或扩大公开面吗」; line 105, 「收窄已发布接受集的卡是 Clause-②: no,不移车道;入队前欠一次契约复审档复核」, names the pure-narrowing card; record-recognisers.mjs:188, 1628 read yes (narrowing) as the narrowing face — this tier. The claim 6442948738 now carries the same two lines (yes (narrowing), narrowed), so claim, PR body and changeset agree. Metadata-protocol: narrowed is right (narrowed over added). Skills-docs: none — REASON now names all three: react-blocks.md:27-28 teaches the two form keys; object-calendar locale / loading are taught only by the spec rows' .describe() text (no skills/** or ui/content/docs page teaches them — verified); no skills/** page names the zod ActionSchema; both docs importers are in the diff. The line is present and true.

② Semver level

③ Boundary flags

  1. Lint UI red on 96c8d55d — resolved by d18ba32e (①.0); success on that head. On 38c2841a the UI contexts had not reported at read time (see Inputs). Landing still waits for the required set to be green on THIS head; this record does not stand in for that.
  2. Deviations Clause-②: yes (narrowing) and Metadata-protocol: narrowed against the original claim — accepted; the claim is amended to match (6442948738).
  3. ADR-0087 disposition — ruled by the seat (② above): stands on precedent, detector gap carried by check-adr-0087-registration: PACKAGE_ROOTS omits ui/packages, ui/apps and ui/examples, so no @object-ui package can use the runtime-export-retired disposition #26220. Closed.
  4. Skills-docs: reason — amended in the PR body to name the calendar keys. Closed.
  5. Out-of-scope finding "immutable / keyField / disclosure / precision carried, not rendered": immutable accepted by the spec, copied by normalizeSectionField, enforced by no renderer, is declared ≠ enforced on an edit-mode form (Prime Directive chore: version packages #10). The seat folds it into 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's FormField follow-up and records it at landing. Closed for this PR.
  6. Body notes: the Verification section's measurement readings were taken on 96c8d55d and say so; the head line now names 38c2841a. The os-dev-report 6445173979 names 96c8d55d; the delta since is two comment/prose commits. Not a FAIL reason.
  7. refactor(ui)!: retire @object-ui/plugin-ai and its three node types #26365 also edits zod-mirror-parity.test.ts in other regions — ordinary concurrency; the later lander merges main and re-runs that file.

Implemented-by: claude/project-thread-gru2pt
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 (席位终稿)

  • 改了什么:@object-ui/types 补齐 spec 已声明、镜像却缺的键。表单的 contentLayout / confirmOnDiscard 和日历的 locale / loading 按引用从 spec 派生;表单字段上的 7 个键逐个登记进镜像对照清单。另外,@object-ui/types/zod 里与 spec 同名却毫不相干的旧 ActionSchema 改名为 CRUDActionSchema。
  • 为什么改:镜像缺键时,objectui validate 和 spec 对同一份文档给出不同结论;同名的 ActionSchema 也容易让作者和 AI 误用。卡上说缺 33 个键,实测是同名碰撞,真实缺口是 11 个。
  • 风险与回滚:旧导出名 ActionSchema 消失(仓内没有生产代码引用它,只改了测试和两页文档)。宽容面对这 4 个键的坏值现在按 spec 拒绝。回滚 = revert 本 PR。
  • 先看:ui/packages/types/src/__tests__/zod-mirror-parity.test.ts(新的逐键对照清单)、ui/packages/types/src/zod/crud.zod.ts(改名)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/types/src/__tests__/zod-mirror-parity.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

席位意见:接受。契约复核 PASS(6445777977)。首轮 FAIL 的 Lint UI 红和一句过时文档,都已在新 head 修好。

席位核对 · ACCEPT · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T15:33Z

Head: 38c2841aebc947ce9f3abab447b64faefb8e58a3 · card #25923 (work item 4) · claim 6442948738 (amended at 15:10Z)

  1. 形态: a draft PR to main whose body opens with Part of #25923 (work item 4 only; item 3 and the FormField follow-up stay on the card). It has no closing keyword. 15 paths, +314 / −52. check-governed-merges --pr 26751: NOT governed, 366 lines ≤ 3000, so the ordinary queue landing applies. The file list is inside the claim surface: ui/packages/types/src/**, the two ui docs pages that named the old export, and the changeset. The FormField index signature (ruling 6412606977) and the item-1 flat keys are untouched.
  2. 前提实测: the premise was corrected in the claim. There are 11 spec-only keys over the real pairs, and the card's 33 came from the spec ActionSchema against objectui's unrelated type: 'action' node. The review re-derived both counts independently (record ①). origin/main 0f3d068c has not touched ui/packages/types since the branch's merge of 94bab5a2, per the dev's reading and the clean mergeable_state.
  3. 门禁 check-runs (on this head): 56 runs, 45 success, 11 skipped, none red or pending. Lint UI, Type Check UI, Build & E2E UI, Test UI 1–16, Lint & Repo Gates, TypeScript Type Check and Test Core are success. The contract-tier review is PASS, record 6445777977 (narrowing face). Record 6445632813 (FAIL on 96c8d55d, a Lint UI red from the census's namespace import) is closed by a reasoned eslint-disable-next-line no-restricted-imports in the sanctioned precedent's form. The dogfood battery ran full on 96c8d55d and was green (os-dev-report 6445173979); the later two commits touch one test comment line and one doc paragraph.
  4. 接受的偏差: (a) Clause-②: yes (narrowing) / Metadata-protocol: narrowed. The claim's no (narrowing) / widened were wrong, because the diff widens the strict face by the derived keys and also narrows by the rename. The review ruled on this, and the claim was amended. (b) The ADR-0087 marker not-required (no-migration-prescription) stands as on PR refactor(types,plugin-calendar,plugin-gantt,plugin-list,plugin-view)!: flat object-calendar / object-gantt keys leave the published schemas; producers emit the block #26411, by the seat's ruling. The detector cannot see @object-ui/types exports, and check-adr-0087-registration: PACKAGE_ROOTS omits ui/packages, ui/apps and ui/examples, so no @object-ui package can use the runtime-export-retired disposition #26220 carries that gap. (c) The 7 FormField keys are recorded, not derived. One of them, immutable, is carried but not rendered, so declared ≠ enforced; it folds into 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's FormField follow-up and is recorded at landing.
  5. 范围外去向: card 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 keeps item 3 (the options bag and its census) and the FormField follow-up (the index signature, now with immutable's carried-not-rendered). Both are recorded at landing. Skills-docs: none is true per the amended line: react-blocks.md teaches the form keys, the calendar keys are taught only by the spec .describe() text, and no skill names the zod ActionSchema.
  6. 落地条款: the prose faces were checked sentence by sentence against the diff: the changeset's BREAKING Before / Now / Fix and the two docs pages (enhanced-actions.mdx:304-306 now names CRUDActionSchema in @object-ui/types/zod). PR refactor(ui)!: retire @object-ui/plugin-ai and its three node types #26365 (plugin-ai retirement) edits other rows of index.zod.ts and zod-mirror-parity.test.ts; whichever lands later merges main. Ready and auto-merge go through the relay in this act, and the seat follows the PR to MERGED.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 15:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 15:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 607d3bb Oct 27, 2026
57 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-gru2pt branch October 27, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants