Skip to content

feat(automation): xExpression marker on the loop collection config field (#3304) - #3313

Merged
os-zhuang merged 1 commit into
mainfrom
claude/step-logs-run-observability-plxc2p
Jul 19, 2026
Merged

feat(automation): xExpression marker on the loop collection config field (#3304)#3313
os-zhuang merged 1 commit into
mainfrom
claude/step-logs-run-observability-plxc2p

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

First increment of #3304 — the descriptor-side counterpart to objectui #2670 Phase 3 (merged as objectui #2699).

What

Introduce an xExpression: 'expression' | 'template' marker on flow-node config string properties, riding the exact same Zod .meta() → JSON-Schema channel as the existing xRef / xEnumDeprecated markers. It tells the flow designer whether a string is bare CEL or an interpolate() single-brace {var} template, so the designer renders the right editor (mono + {var} picker, and whether the CEL brace-trap applies) instead of guessing from the field name.

Apply it to the one field where it closes a live divergence today: the loop node's collection (a {tasks} template).

Why this scope

There are two separate channels that produce a node's published configSchema:

  1. Zod → z.toJSONSchema — only approval (getApprovalNodeConfigJsonSchema). .meta() flows through here verbatim.
  2. Hand-written JSON literals in service-automation/builtin/*.tsloop / parallel / try_catch / http / connector_action / notify. These ignore their Zod schemas.

A further 11 node types publish no configSchema at all (decision, assignment, CRUD×4, screen, script, wait, subflow, map).

Among every node that already ships a configSchema, loop.collection is the only template/expression field — and it's exactly the divergence #3304 leads with: online the designer rendered it as plain text while the offline hardcoded form rendered a mono expression editor, and the CEL brace-trap false-flagged {tasks} as a malformed condition. (Marking other config-schema string fields such as http.url would create a mono-vs-text divergence, since objectui renders those as plain text — so they are deliberately left unmarked.)

Changes

  • packages/spec/src/automation/control-flow.zod.tsLoopConfigSchema.collection gains .meta({ xExpression: 'template' }) (canonical source; emitted via z.toJSONSchema).
  • packages/services/service-automation/src/builtin/loop-node.ts — the shipped descriptor's configSchema literal (the JSON objectui actually reads) gains xExpression: 'template' on collection. loop's literal doesn't derive from Zod, so both are annotated so they agree.

Follow-up (tracked in #3304, not this PR)

The same marker on map.collection (template), decision.expression / start.condition (expression), script body (expression + multiline) — each lives on a node type that publishes noconfigSchema yet, so each needs a new config schema authored (using objectui's hardcoded flow-node-config as the spec of record) plus a ratchet-manifest update. Kept out of this PR to land the concrete fix cleanly.

Verification

  • New tests: LoopConfigSchema emits xExpression: 'template' (with description preserved) through z.toJSONSchema (control-flow.test.ts); the registered loop descriptor's configSchema.collection carries the marker (loop-node.test.ts). Both green.
  • tsc --noEmit clean for @objectstack/spec and @objectstack/service-automation; pnpm gen:schema succeeds with no ratchet-manifest drift (annotating an existing schema doesn't change schema keys).
  • Additive + backward-compatible: an unknown xExpression value is ignored by the designer, runtime behavior unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi


Generated by Claude Code

…eld (#3304)
The flow designer generates a node's config form from its published
`configSchema` (ADR-0018). Introduce an `xExpression: 'expression' | 'template'`
marker on string properties — riding the same Zod `.meta()` → JSON-Schema
channel as `xRef` / `xEnumDeprecated` — that declares whether a string is bare
CEL or an `interpolate()` single-brace `{var}` template.
Apply it to the `loop` node's `collection` (a `{tasks}` template):
• LoopConfigSchema.collection — the canonical Zod source (control-flow.zod.ts),
emitted via z.toJSONSchema.
• the shipped descriptor's configSchema literal (service-automation
loop-node.ts) — the JSON objectui actually reads. loop's configSchema is a
hand-written literal that doesn't derive from the Zod schema, so both are
annotated so they agree.
Closes the live divergence: without the marker the designer rendered
`collection` as plain text online while the offline hardcoded form rendered it
as a mono expression editor, and the CEL brace-trap false-flagged `{tasks}`.
objectui #2670 Phase 3 (#2699) already consumes the marker.
Additive + backward-compatible: an unknown value is ignored, runtime unchanged.
Follow-up (tracked in #3304): the same marker on map/decision/script and the
node types that publish no configSchema yet.
Tests: LoopConfigSchema emits xExpression through z.toJSONSchema (spec); the
registered loop descriptor's configSchema.collection carries it (service-automation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
@vercel

vercelBot commented Jul 19, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJul 19, 2026 5:36pm

Request Review

@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation tests tooling labels Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): packages/services, @objectstack/spec.

105 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 packages/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 packages/services, @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/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.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/validating-metadata.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/audit-service.mdx(via packages/services)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx(via packages/services)
  • 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/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 packages/services, @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 packages/services, @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/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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 19, 2026 18:07
@os-zhuang
os-zhuang merged commit 158aa14 into mainJul 19, 2026
18 of 20 checks passed
@os-zhuang
os-zhuang deleted the claude/step-logs-run-observability-plxc2p branch July 19, 2026 18:07
os-zhuang added a commit that referenced this pull request Jul 20, 2026
…s (designer parity, #3304) (#3327)
assignment, create/update/delete/get_record, and screen now publish configSchemas mirroring objectui's hardcoded field groups — free-form maps as JSON-Schema open objects (additionalProperties: true) rendered by the designer's keyValue editor (objectui #2708), object references as xRef, screen's visibleWhen as an xExpression CEL column. decision/wait/script/subflow stay deliberately schemaless (documented + tested — a partial schema would drop editors). Verified: parity tests, tsc, 335-test suite green. Descriptor metadata only; no runtime behavior change.
Closes the #3304 implementation series: loop (#3313), map (#3321), objectui adapter (#2708), this PR.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

allow-majordocumentationImprovements or additions to documentationsize/steststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude