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
workflows key anywhere in ObjectSchemaBase (grep for workflow in packages/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-data skill's "CRM Schema Blueprint" table lists:
Lifecycle workflow | src/objects/*.object.ts | Use workflows[] for field updates triggered by record changes
So authors (and agents) are actively told to write workflows[] on objects — a field the spec doesn't accept.
Asks
- Don't strip silently. Reject unknown top-level keys on
ObjectSchema.create() / at objectstack build (or at minimum emit a located build warning). Mirrors ADR-0032 §1c "no silent fallback", applied to metadata shape. A typo'd validation/hoooks/workflows should 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
ObjectSchema field, or (b) if lifecycle hooks and top-level record_change flows are the only supported paths, say so — and have the build flag workflows explicitly pointing authors at the supported mechanism.
- Fix the docs/skill. Remove/repair the
objectstack-data CRM-blueprint row that teaches workflows[].
Refs
Summary
Object-level
workflows: [...]authored on anObjectSchemais silently dropped at build — no error, no warning, andtsc --noEmitpasses. 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
ObjectSchemaBaseis a plainz.object({...})(Zod default =.strip()), andObjectSchema.create()parses through it:packages/spec/src/data/object.zod.ts:334—const ObjectSchemaBase = z.object({ ... }):524—validations: z.array(ValidationRuleSchema).optional()✅ (supported)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'dvalidation/indexs/etc.) is silently removed. The genericcreate()signature also did not trigger a TypeScript excess-property error for the extraworkflowskey (the templates typechecked clean with it present).Reproduction
objectstack buildon a package full of these prints✓ Build completewith no diagnostic; the composed artifact contains0object-workflows.Why this is bad
objectstack-dataskill's "CRM Schema Blueprint" table lists:Asks
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.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.objectstack-dataCRM-blueprint row that teachesworkflows[].Refs
docs/adr/0032-unified-expression-layer.md) — "validate-by-default, never fail silently" (same principle, applied here to schema keys).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.