Skip to content

feat(spec): a blueprint formula field can state its expression - #4577

Merged
os-zhuang merged 2 commits into
mainfrom
claude/blueprint-formula-expression
Aug 2, 2026
Merged

feat(spec): a blueprint formula field can state its expression#4577
os-zhuang merged 2 commits into
mainfrom
claude/blueprint-formula-expression

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

The framework half of ADR-0078 Phase 2 (#4544, A1 — the authoring path that drops correctly-authored config). Companion change in the cloud repo carries the value through to materialization.

A type it could name but never complete

BlueprintFieldSchema.type is the full FieldType enum, so the AI-build design step could always author { type: 'formula' }. But neither the lenient schema nor the OpenAI-strict mirror the model actually generates against had any key for the body.

There was no way, anywhere on that surface, to say what the formula computed.

So it materialized bare — and cloud's graph-lint then correctly reported formula_without_expression with the fix "Set field expression to a CEL formula", which the agent could not write in the blueprint it was holding. Detected, but unfixable on the surface that produced it. The only escape was to abandon the blueprint path for granular apply_edit calls.

The same hole, one field type over

This is exactly what summaryOperations closed for roll-ups in cloud#970. From this file's own test:

"z.object STRIPS unknown keys, so before this slot existed a blueprint that correctly declared { type:'summary', summaryOperations:{…filter…} } lost the config at the parse waist and materialized runtime-dead (cloud#970)."

formula was simply left behind.

It bites hardest through nameField, whose own guidance tells the model to point at a formula for numbered entities:

"For a numbered entity (invoice/ticket), set it to a formula field that composes number + name (e.g. {order_no} · {customer})."

Following that advice produced a record title that is blank on every card, lookup chip and breadcrumb (ADR-0079).

The pin matters more than the key

A1's root cause is not a forgotten property. It is that two schemas describe the same shape and nothing forced them to agree:

  • the strict mirror is what the model may EMIT (OpenAI structured outputs — every key present, optional → nullable)
  • the lenient schema is what downstream READS

Drift in either direction silently drops authored config. That asymmetry is how formula got left behind when summaryOperations was added.

A new test asserts the two field schemas carry exactly the same keys, so the next key added to one cannot go missing from the other.

Verification

  • Blueprint tests 29/29 (parity pin, expression presence in both schemas, and a formula round-trip through the lenient schema)
  • Full monorepo suite 132/132
  • check:api-surface unchanged without regeneration — a Zod field adds no exports
  • check:authorable-surface flagged exactly one new key, ai/BlueprintField:expression, recorded via gen:schema

Generated by Claude Code

`BlueprintFieldSchema.type` is the FULL `FieldType` enum, so the AI-build
design step could always NAME a `formula` field — but neither the lenient
schema nor the OpenAI-strict mirror the model generates against had any key for
the body. There was no way, anywhere on that surface, to say what the formula
computed. It materialized bare, and cloud graph-lint then correctly reported
`formula_without_expression` with the fix "Set field expression to a CEL
formula" — which the agent could not write in the blueprint it was holding.
Detected, but unfixable on the surface that produced it.

This is the same hole `summaryOperations` closed for roll-ups in cloud#970;
`formula` was left behind. It bites hardest through `nameField`, whose own
guidance tells the model to use a formula for numbered entities (invoice /
ticket) composing "number · name" — following that advice produced a record
title blank on every card, lookup chip and breadcrumb.

The PIN matters more than the key. The root cause is not a forgotten property:
two schemas describe the same shape and nothing forced them to agree. The mirror
is what the model may EMIT, the lenient schema is what downstream READS, and
drift either way silently drops authored config. A new test asserts the two
field schemas carry exactly the same keys, so the next key added to one cannot
go missing from the other.

`authorable-surface.json` records exactly one new key
(`ai/BlueprintField:expression`). The API surface is unchanged — a Zod field
adds no exports.

Verification: blueprint tests 29/29; full suite 132/132; check:api-surface
unchanged; check:authorable-surface green after gen:schema.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 8:35am

Request Review

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 08:33
@os-zhuang
os-zhuang enabled auto-merge August 2, 2026 08:33
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ai tooling size/s labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…n` key

`content/docs/references/ai/solution-blueprint.mdx` is generated from the
schema and enumerates BlueprintField's keys in a table, so adding `expression`
makes it stale. Regenerated with `gen:docs`; the diff is exactly the one new
row. `check:docs` reports 259 generated files in sync.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 4820f55 Aug 2, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/blueprint-formula-expression branch August 2, 2026 08:57
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 protocol:ai size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants