Repository navigation
Description
Summary
Object-level workflows: [...] authored on an ObjectSchema is silently dropped at build — no error, no warning, and tsc --noEmit passes. The result: declarative on-update automation that authors believe they shipped never runs, and nothing anywhere tells them.
This surfaced in objectstack-ai/templates: 15 object-level workflows across 8 objects / 6 packages (timestamp stamping, contract auto-activate/expire, order auto-receive, etc.) were authored, built green, and were completely absent from every built artifact — dead from day one. We have since migrated them to lifecycle hooks (templates-side fix), but the silent-drop behavior is the platform problem.
This is the metadata-key analogue of ADR-0032's "no silent failure" principle: today a misauthored expression is caught at build, but a misauthored schema key is silently discarded.
Root cause
ObjectSchemaBase is a plain z.object({...}) (Zod default = .strip()), and ObjectSchema.create() parses through it:
packages/spec/src/data/object.zod.ts:334—const ObjectSchemaBase = z.object({ ... }):524—validations: z.array(ValidationRuleSchema).optional()✅ (supported)- No
workflowskey anywhere inObjectSchemaBase(grep forworkflowinpackages/spec/src/data/returns only unrelated hits). :740—create: <const T extends z.input<typeof ObjectSchemaBase>>(config) => ObjectSchemaBase.parse(withDefaults)→ unknown keys are stripped on parse.
So any unknown top-level key (workflows, or a typo'd validation/indexs/etc.) is silently removed. The generic create() signature also did not trigger a TypeScript excess-property error for the extra workflows key (the templates typechecked clean with it present).
Reproduction
import { ObjectSchema } from '@objectstack/spec/data';
const Obj = ObjectSchema.create({
name: 'demo',
fields: { status: { type: 'text' } },
workflows: [{ // ← not a real ObjectSchema field
name: 'stamp', objectName: 'demo', triggerType: 'on_update',
criteria: 'record.status == "done"', active: true,
actions: [{ name: 'x', type: 'field_update', field: 'done_at', value: 'now()' }],
}],
});
console.log('workflows' in Obj); // → false (silently stripped; no error, no warning)objectstack build on a package full of these prints ✓ Build complete with no diagnostic; the composed artifact contains 0 object-workflows.
Why this is bad
- Silent no-op automation. Exactly the failure mode ADR-0032 set out to eliminate ("a malformed thing must come back to the author as a precise, fixable error, never a runtime no-op") — but for schema shape rather than expressions. An AI author (the ADR's design center) gets zero signal and ships dead metadata with full confidence.
- The platform's own guidance teaches the dropped shape. The
objectstack-dataskill's "CRM Schema Blueprint" table lists:Lifecycle workflow |
src/objects/*.object.ts| Useworkflows[]for field updates triggered by record changes
So authors (and agents) are actively told to writeworkflows[]on objects — a field the spec doesn't accept.
Asks
- Don't strip silently. Reject unknown top-level keys on
ObjectSchema.create()/ atobjectstack build(or at minimum emit a located build warning). Mirrors ADR-0032 §1c "no silent fallback", applied to metadata shape. A typo'dvalidation/hoooks/workflowsshould be a fixable build error, not a vanished field. - Decide the object-workflow story. Either (a) make object-level declarative on-update automation a first-class
ObjectSchemafield, or (b) if lifecycle hooks and top-level record_change flows are the only supported paths, say so — and have the build flagworkflowsexplicitly pointing authors at the supported mechanism. - Fix the docs/skill. Remove/repair the
objectstack-dataCRM-blueprint row that teachesworkflows[].
Refs
- ADR-0032 (
docs/adr/0032-unified-expression-layer.md) — "validate-by-default, never fail silently" (same principle, applied here to schema keys). - Sibling of Date-typed formula fields always evaluate to null — objectql applyFormulaPlan binds raw string values + silently swallows the CEL type fault (ADR-0032 §1c) #1530 (date-typed formula fields → null) — both found while taking
objectstack-ai/templatesto 7.6.0 (templates issue feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14). Date-typed formula fields always evaluate to null — objectql applyFormulaPlan binds raw string values + silently swallows the CEL type fault (ADR-0032 §1c) #1530 is silent-null in expression evaluation; this is silent-strip in metadata authoring.
Activity
fix(example-todo): remove silently-stripped object-level workflows[] (#…
fix(spec): reject unknown top-level keys on ObjectSchema.create() (#1535
xuyushun441-sys commentedon Jun 3, 2026
Confirmed fixed in 7.7.0 ✅ —
objectstack buildnow rejects unknownObjectSchemakeys with a located, actionable error:Verified while taking
objectstack-ai/templatesto 7.7.0: the new validator caught both silently-stripped keys —workflows(the one this issue reported) andstateMachines(14 objects whose lifecycle machines had never been enforced). Both have since been migrated to supported mechanisms (hooks /state_machinevalidation rules). Thanks for the quick turnaround.fix(cli): drop stale `ownership` key from init scaffold object templa…
fix(spec): declare `ownership` as a first-class ObjectSchema field (#…
feat(spec)!: remove dead enable.trash/mru capability flags (#2377 clo…
feat(spec)!: reject unknown keys on flow and permission authoring sch…
feat(spec)!: ObjectSchema closes on the PARSE path, not only in creat…
feat(spec)!: translation 两个门都关上,objectstack-ai#3778 的十键守卫退役进错误信息(obje…
fix(spec,metadata-protocol)!: 排序节点写 `direction` 不再静默排反方向,两扇门同一个 chang…
feat(spec)!: object 内层块拒绝未知键 —— 13 站点收紧,2 个站点依据实测保留 (objectstack-ai#4001
test(spec): the fourth data/ file group's test titles state each cite…