Found while runtime-verifying #616 (booting a fresh install to count hook throws); filed unassigned per Prime Directive #10.
What happens
Ten business objects declare their owner field with a CEL default:
// src/objects/account.object.ts:130 — and campaign, case, contact, contract,
// forecast, knowledge_article, lead, opportunity, task
defaultValue: cel`os.user.id`,
On every insert the engine tries to evaluate it and fails:
WARN Failed to evaluate default expression
{"object":"crm_account","field":"owner",
"error":{"kind":"type","message":"Unknown variable: os\n\n> 1 | os.user.id\n ^"}}
127 such warnings on one fresh boot (pnpm dev --fresh on a clean .objectstack/data, 277 seeded rows), covering all ten objects:
crm_account, crm_campaign, crm_case, crm_contact, crm_contract,
crm_forecast, crm_knowledge_article, crm_lead, crm_opportunity, crm_task
The declared default therefore never applies: a record created without an explicit owner gets owner = null, not the current user. It is a WARN, so nothing fails — the field is just quietly empty. Seed data hides it because the seeds set owner explicitly; a record created through a form, an API call or a flow does not.
Why it looks like the spelling is right
os.user.id is the form the platform documents for the current user — @objectstack/spec references it in two places:
expression.zod-*.d.ts: "scope is the same as CEL ({{record.x}}, {{os.user.id}})"
external-catalog.zod-*.d.ts: "expressions embedded in seed records (e.g. owner_id: cel\os.user.id``)"
So either the field-default evaluation path does not seed the os root into the CEL scope (a platform bug — the docs would then be describing a capability the runtime does not deliver on this path), or the authoring convention for a defaultValue is something else and ten objects are written against a spelling that only works elsewhere. Worth answering before either side is "fixed": the fix belongs at whichever end is actually wrong, not as a hook that papers over it.
Repro
rm -rf .objectstack/data
pnpm dev -- --fresh -p 38617 --seed-admin
# then, in the log:
grep -c 'Failed to evaluate default expression' <log> # 127
Relationship to #548
Related but distinct. #548 is about owner (the app-authored lookup) vs owner_id (the platform's ownership column) diverging in meaning — reassigning Owner does not move access. This one is narrower and upstream of it: the owner lookup is not even getting populated by the default it declares. Fixing #548 in either direction still leaves this expression broken.
Suggested next step
Decide which end is wrong before writing code:
- A. Platform bug — the default-expression evaluator does not expose
os. Then this is an upstream issue and HotCRM's metadata is correct as written; the fix is a framework issue plus a version bump.
- B. Wrong authoring convention —
defaultValue on a field expects a different scope/spelling than a seed record does. Then the ten objects get migrated to the correct one, and the platform should reject the unknown-variable expression at publish/validate time rather than warning per row at runtime, so an AI-authored object cannot ship a default that silently never fires.
B's publish-time rejection is worth having either way: a defaultValue that throws on every evaluation is exactly the kind of declared-≠-enforced metadata that only surfaces as "the field is mysteriously empty".
Generated by Claude Code
Found while runtime-verifying #616 (booting a fresh install to count hook throws); filed unassigned per Prime Directive #10.
What happens
Ten business objects declare their
ownerfield with a CEL default:On every insert the engine tries to evaluate it and fails:
127 such warnings on one fresh boot (
pnpm dev --freshon a clean.objectstack/data, 277 seeded rows), covering all ten objects:The declared default therefore never applies: a record created without an explicit
ownergetsowner = null, not the current user. It is a WARN, so nothing fails — the field is just quietly empty. Seed data hides it because the seeds setownerexplicitly; a record created through a form, an API call or a flow does not.Why it looks like the spelling is right
os.user.idis the form the platform documents for the current user —@objectstack/specreferences it in two places:expression.zod-*.d.ts: "scope is the same as CEL ({{record.x}},{{os.user.id}})"external-catalog.zod-*.d.ts: "expressions embedded in seed records (e.g.owner_id: cel\os.user.id``)"So either the field-default evaluation path does not seed the
osroot into the CEL scope (a platform bug — the docs would then be describing a capability the runtime does not deliver on this path), or the authoring convention for adefaultValueis something else and ten objects are written against a spelling that only works elsewhere. Worth answering before either side is "fixed": the fix belongs at whichever end is actually wrong, not as a hook that papers over it.Repro
Relationship to #548
Related but distinct. #548 is about
owner(the app-authored lookup) vsowner_id(the platform's ownership column) diverging in meaning — reassigning Owner does not move access. This one is narrower and upstream of it: theownerlookup is not even getting populated by the default it declares. Fixing #548 in either direction still leaves this expression broken.Suggested next step
Decide which end is wrong before writing code:
os. Then this is an upstream issue and HotCRM's metadata is correct as written; the fix is a framework issue plus a version bump.defaultValueon a field expects a different scope/spelling than a seed record does. Then the ten objects get migrated to the correct one, and the platform should reject the unknown-variable expression at publish/validate time rather than warning per row at runtime, so an AI-authored object cannot ship a default that silently never fires.B's publish-time rejection is worth having either way: a
defaultValuethat throws on every evaluation is exactly the kind of declared-≠-enforced metadata that only surfaces as "the field is mysteriously empty".Generated by Claude Code