Skip to content

FieldSchema.defaultValue has the same z.unknown() acceptance hole as the action param one, but it is NARROWER — a literal must first be told apart from the runtime-token / CEL vocabulary before it can be checked #7127

Description

@os-zhuang

Found while implementing #6970 (PR #7126), which closed the same hole one layer up on ActionParamSchema.defaultValue. Filed unassigned per Prime Directive #10. Deliberately not fixed in that PR — it is not the same bug, and unlike #6970 it is not decision-free.

Measured

FieldSchema.defaultValue is z.unknown().optional() (packages/spec/src/data/field.zod.ts:513). Probed against FieldSchema.safeParse on origin/main (2026-08-09):

DEFAULT_VALUE_TOKENS = ["NOW()","current_user"]
FIELD number field + literal "abc" -> ACCEPT
FIELD datetime field + wall clock -> ACCEPT
FIELD select field + non-member -> ACCEPT
FIELD number field + current_user TOKEN -> ACCEPT
FIELD datetime field + NOW() TOKEN -> ACCEPT
FIELD date field + CEL envelope -> ACCEPT

The first three are the residual hole: a literal that can never satisfy the field's own valueSchemaFor contract is accepted silently at authoring.

Why the #6970 fix does NOT transfer

An action param's defaultValue is a pure LITERAL — objectui's ActionParamDialog seeds dialog state with it verbatim and serializeParamValues resolves nothing — so valueSchemaFor is the whole contract, and #6970 could run the default through it unchanged.

A FIELD's defaultValue is polymorphic by design, three legal shapes, documented in packages/spec/src/data/default-value-tokens.ts:

  1. a literal ('open', 0, false)
  2. a runtime TOKEN — DEFAULT_VALUE_TOKENS: NOW(), current_user
  3. an Expression envelope — { dialect: 'cel', source: 'today()' } (ROADMAP §M9.9b)

Running valueSchemaFor naively over this key would reject shipped, correct metadata. Measured instances:

  • examples/app-showcase/src/data/objects/field-zoo.object.ts:120Field.user({ defaultValue: 'current_user' })
  • cloudpackages/service-ai/src/objects/*.object.ts — many datetime fields with defaultValue: 'NOW()' (ai-conversation, ai-trace, ai-message, ai-eval-run, ai-eval-case)

datetime + 'NOW()' is legitimate and would fail InstantValueSchema.

What has to be decided before anything is enforced

Any fix must first subtract the token/expression vocabulary, and that subtraction is a real design question rather than a mechanical tightening:

  • Is the token set gated per type? Is NOW() legal on a number? Is current_user legal on anything but a user / lookup('sys_user')? Today nothing says.
  • The DDL split already documented in default-value-tokens.ts (NOW() becomes a physical column DEFAULT, current_user deliberately does not) suggests the answer is per-token AND per-type, not one flat allow-list.
  • A CEL envelope's RESULT type is not knowable at parse time at all, so that shape can only ever be accepted structurally.

That is why this is filed rather than folded into #6970: that card's triage explicitly ruled it "decision-free 的契约收紧 …… 不是新设计", and this one is not.

Why it still matters

Same argument the parent card makes about AI-authored metadata: a wrong literal here is accepted at authoring and only surfaces as bad data (a number column seeded with 'abc') or a runtime failure far from the cause. No shipped metadata in objectstack or cloud currently carries such a literal — the hole is real but has no known live instance, which is why it is being recorded rather than escalated.

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions