Skip to content

Commit f3bbbef

Browse files
claude[bot]claude
andauthored
feat(spec): assignment values may be a CEL value envelope — the expression ledger gains the value role (#15113)
* wip(spec): assignment value role — ledger entry, wildcard, envelope contract, pins, docs, changeset Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * wip(spec): eager value schema so the xExpression marker reaches JSON Schema; array-form prescription on the record type error Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * feat(spec): assignment value may be a CEL value envelope — the expression ledger gains the value role FLOW_NODE_EXPRESSION_PATHS gains the assignment entry at assignments.* with the new value role; ledger paths accept a * segment (every key of an object, the sibling of []); resolveFlowNodeExpressions emits only envelope-shaped objects for a value slot, so a plain string stays {token} interpolation and every pre-existing entry resolves byte-identically. AssignmentConfigSchema / AssignmentValueSchema / AssignmentExpressionValueSchema declare the assignment node's value contract (ExpressionSchema, safeExtended to the cel dialect, beside interpolation and literals); a malformed envelope is refused at the variable's path with a fixed leading sentence; the map value carries xExpression: value as the ledger's declaration channel. Docs page, generated artifacts, and a spec minor changeset. The executor half (evaluation, validateExpression value checks, the reconciliation ratchet's marker table) is service-automation's and is reported, not edited. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * fix(spec): ADR-0122 Parsed aliases for the assignment value types; ADR-0090 reserved word in the flows page Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * wip(spec,service-automation): A2 — assignment value slot reaches the ledger ratchet; ratchet learns the value marker and the additionalProperties walk Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * chore(spec): regenerate api-surface and export-origins for the ledger-declared node config map Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * chore(spec): regenerate the unknown-key strictness ledger from the merged tree The os-regen merge driver deferred docs/audits/2026-07-unknown-key-strictness-ledger.counts.md (changed on both sides since the merge base); regenerated with pnpm --filter @objectstack/spec gen:strictness-ledger on the merged tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e37456e commit f3bbbef

17 files changed

Lines changed: 920 additions & 50 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): an `assignment` value may be a CEL value envelope — the expression ledger gains the `value` role
6+
7+
An assignment's whole job is to compute a value into a variable, yet CEL was
8+
reachable from flow metadata only where the answer had to be a boolean
9+
(`condition`, `decision.conditions[].expression`, a screen field's
10+
`visibleWhen`): `FLOW_NODE_EXPRESSION_PATHS` declared two roles, `predicate`
11+
and `flow-template`, and the `assignment` node's values were `{token}`
12+
interpolation only. So the stdlib the platform already declares, documents and
13+
tests — `joinNonEmpty` and the rest of `CEL_STDLIB_FUNCTIONS` — could not be
14+
called from metadata, and the commonest outbound shape a business application
15+
has (one digest message listing a recipient's N records) needed a `script`
16+
node.
17+
18+
Ruled (maintainer, 2026-09-02): the rendering half only, no new vocabulary.
19+
20+
- `FlowNodeExpressionRole` gains `'value'`: a slot whose authored value may be a
21+
`{ dialect: 'cel', source }` expression envelope evaluated by the expression
22+
engine to the value the variable takes — not a predicate, not a template.
23+
The ledger gains the `assignment` entry at `assignments.*`; ledger paths now
24+
accept a `*` segment ("every key of this object", the sibling of `[]`), and
25+
`resolveFlowNodeExpressions` emits only envelope-shaped objects for a `value`
26+
slot — a plain string there stays `{token}` interpolation. Every entry that
27+
existed before resolves byte-identically. `isExpressionEnvelopeShaped` is
28+
the exported recognizer both halves discriminate on.
29+
- `AssignmentConfigSchema` / `AssignmentValueSchema` /
30+
`AssignmentExpressionValueSchema` declare the `assignment` node's value
31+
contract: a string (`{token}` interpolation), a CEL value envelope (the
32+
`ExpressionSchema` spelling, narrowed to the `cel` dialect), or any other
33+
literal. Every value that parsed before still parses; the one newly refused
34+
shape is a malformed envelope (no `source`, an empty or non-string `source`,
35+
a `template` / `cron` / unknown dialect), refused at the variable's path with
36+
a fixed leading sentence. The map value carries `.meta({ xExpression:
37+
'value' })`, the declaration channel the ledger reads for this slot.
38+
39+
The executor half is a separate change in `@objectstack/service-automation`:
40+
until it lands, the built-in `assignment` executor still writes an envelope
41+
object into the variable verbatim and `notify` renders it as JSON, and the
42+
ledger's reconciliation ratchet there does not yet know the `value` marker.

content/docs/automation/flows.mdx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,47 @@ or missing-`required` violation (#4277). A node type that publishes no
176176
}
177177
```
178178

179+
**Assignment (set variables):**
180+
181+
```typescript
182+
{
183+
id: 'build_digest',
184+
type: 'assignment',
185+
label: 'Build digest',
186+
config: {
187+
assignments: {
188+
// `{token}` flow interpolation — a sole token keeps the token's type,
189+
// text with holes stays text
190+
owner_name: '{manager.name}',
191+
// CEL value envelope — evaluated by the expression engine to a value, so
192+
// the declared CEL stdlib is reachable from metadata: one line per task
193+
digest: { dialect: 'cel', source: 'joinNonEmpty(overdue_tasks.map(t, t.subject), "\\n")' },
194+
},
195+
},
196+
}
197+
```
198+
199+
A value's **shape** selects its form — there is no mode key. A plain string is
200+
always `{token}` interpolation (a bare `a + b` is the literal text `a + b`, not
201+
CEL); an object that names a `dialect` is an expression envelope and must be a
202+
valid `cel` one — a missing or empty `source`, or a `template` / `cron`
203+
dialect, is refused at the variable's path. Numbers, booleans, arrays and plain
204+
objects are assigned as literals. A later `notify` node renders the variable as
205+
any other: `message: '{digest}'`.
206+
207+
<Callout type="warn" title="The contract landed first; the executor half follows">
208+
209+
The envelope form is what `@objectstack/spec` declares — the expression
210+
ledger's `value` entry and `AssignmentConfigSchema`, the half ruled in
211+
[#14149]. Until the matching `@objectstack/service-automation` change lands,
212+
the built-in `assignment` executor still writes an envelope object into the
213+
variable verbatim and `notify` renders it as JSON. For a digest body today,
214+
call a registered function from a `script` node.
215+
216+
[#14149]: https://github.com/objectstack-ai/objectstack/issues/14149
217+
218+
</Callout>
219+
179220
**Create Record:**
180221

181222
```typescript

content/docs/references/automation/builtin-node-config.mdx

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ description: Builtin Node Config protocol schemas
77

88
Config contracts for the remaining flat builtins — the CRUD quartet
99
(`get_record` / `create_record` / `update_record` / `delete_record`),
10-
`screen`, and `map` (#4045). Sibling of `io-node-config.zod.ts`
11-
(notify / http) and `control-flow.zod.ts` (loop / parallel / try_catch).
10+
`screen`, `map` (#4045) and, since #14149, `assignment`'s value contract.
11+
Sibling of `io-node-config.zod.ts` (notify / http) and `control-flow.zod.ts`
12+
(loop / parallel / try_catch).
1213

1314
## Provenance — written from the executors, not from the forms
1415

@@ -58,11 +59,15 @@ finding is that a bespoke guard's detection generalizes for free the moment
5859
a default flips, while its PROSE does not, so the prose is copied to the new
5960
door rather than left behind at the old one.
6061

62+
`assignment` is described by its VALUES, not by a key set (#14149): the
63+
canonical `assignments` map's keys are the author's variable names, and with
64+
no `assignments` wrapper the TOP-LEVEL config keys are (logic-nodes.ts), so
65+
`AssignmentConfigSchema` below declares one key and an open catchall and puts
66+
the contract on what a value may be. The form↔Zod ledger test still pins the
67+
descriptor's free-form `assignments` map as the openness it is; that pin and
68+
this contract describe the same surface from the two sides.
69+
6170
Deliberately absent:
62-
- `assignment` — its config cannot be described by a fixed key set: with no
63-
`assignments` wrapper the TOP-LEVEL config keys ARE the author's variable
64-
names (logic-nodes.ts). The ledger test pins that exemption with its
65-
reason instead of pretending a shape.
6671
- `decision` / `script` / `subflow` / `wait` / `connector_action` — the
6772
descriptor-schemaless class (config-schemas.test.ts). `wait` and
6873
`connector_action` keep their contracts in FlowNodeSchema's sibling
@@ -79,13 +84,47 @@ Deliberately absent:
7984
## TypeScript Usage
8085

8186
```typescript
82-
import { CreateRecordConfigSchema, DeleteRecordConfigSchema, GetRecordConfigSchema, MapConfigSchema, ScreenConfigSchema, ScreenFieldConfigSchema, UpdateRecordConfigSchema } from '@objectstack/spec/automation';
83-
import type { CreateRecordConfig, DeleteRecordConfig, GetRecordConfig, MapConfig, ScreenConfig, ScreenFieldConfig, UpdateRecordConfig } from '@objectstack/spec/automation';
87+
import { AssignmentConfigSchema, AssignmentExpressionValueSchema, AssignmentValueSchema, CreateRecordConfigSchema, DeleteRecordConfigSchema, GetRecordConfigSchema, MapConfigSchema, ScreenConfigSchema, ScreenFieldConfigSchema, UpdateRecordConfigSchema } from '@objectstack/spec/automation';
88+
import type { AssignmentConfig, AssignmentExpressionValue, AssignmentValue, CreateRecordConfig, DeleteRecordConfig, GetRecordConfig, MapConfig, ScreenConfig, ScreenFieldConfig, UpdateRecordConfig } from '@objectstack/spec/automation';
8489

8590
// Validate data
86-
const result = CreateRecordConfigSchema.parse(data);
91+
const result = AssignmentConfigSchema.parse(data);
8792
```
8893

94+
---
95+
96+
## AssignmentConfig
97+
98+
### Properties
99+
100+
| Property | Type | Required | Description |
101+
| :--- | :--- | :--- | :--- |
102+
| **assignments** | `Record<string, any>` | optional | Variables to set: each key is a variable name, each value a `{token}` template, a CEL value envelope, or a literal |
103+
104+
105+
---
106+
107+
## AssignmentExpressionValue
108+
109+
CEL value envelope `{ dialect: 'cel', source }` — evaluated by the expression engine to the value the variable takes; the whole CEL stdlib (`joinNonEmpty`, …) is reachable
110+
111+
### Properties
112+
113+
| Property | Type | Required | Description |
114+
| :--- | :--- | :--- | :--- |
115+
| **dialect** | `'cel'` || |
116+
| **source** | `string` | optional | |
117+
| **ast** | `any` | optional | |
118+
| **meta** | `{ rationale?: string; generatedBy?: string }` | optional | |
119+
120+
121+
---
122+
123+
## AssignmentValue
124+
125+
Value the variable takes: a string (`{token}` flow interpolation — a sole token keeps its type), a CEL value envelope `{ dialect: 'cel', source }` evaluated by the expression engine (the CEL stdlib such as `joinNonEmpty` is reachable), or any other literal
126+
127+
89128
---
90129

91130
## CreateRecordConfig

content/docs/references/index.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Protocol Reference
3-
description: Every schema published by @objectstack/spec — 1589 schemas across 14 protocol modules
3+
description: Every schema published by @objectstack/spec — 1592 schemas across 14 protocol modules
44
---
55

66
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with
2121
| :--- | ---: | ---: | :--- |
2222
| [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
2323
| [API Protocol](/docs/references/api) | 31 | 436 | REST contracts, endpoints, routing, realtime, batch, discovery. |
24-
| [Automation Protocol](/docs/references/automation) | 13 | 69 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
24+
| [Automation Protocol](/docs/references/automation) | 13 | 72 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2525
| [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. |
2626
| [Data Protocol](/docs/references/data) | 29 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
2727
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
@@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with
3333
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3434
| [System Protocol](/docs/references/system) | 36 | 291 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3535
| [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
36-
| **Total** | **199** | **1589** | 14 protocol modules |
36+
| **Total** | **199** | **1592** | 14 protocol modules |
3737

3838
---
3939

@@ -103,15 +103,15 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
103103

104104
## Automation Protocol
105105

106-
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **13 pages, 69 schemas**
106+
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **13 pages, 72 schemas**
107107

108108
Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records.
109109

110110
| File | Schemas |
111111
| :--- | :--- |
112112
| [`approval.zod.ts`](/docs/references/automation/approval) | `ApprovalDecision`, `ApprovalEscalation`, `ApprovalNodeApprover`, `ApprovalNodeConfig`, `ApproverType`, `DecisionOutputDef` |
113113
| [`bpmn-interop.zod.ts`](/docs/references/automation/bpmn-interop) | `BpmnDiagnostic`, `BpmnElementMapping`, `BpmnExportOptions`, `BpmnImportOptions`, `BpmnInteropResult`, `BpmnUnmappedStrategy`, `BpmnVersion` |
114-
| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `CreateRecordConfig`, `DeleteRecordConfig`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` |
114+
| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` |
115115
| [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` |
116116
| [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` |
117117
| [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` |

docs/audits/2026-07-unknown-key-strictness-ledger.counts.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ regenerate.
2121
| Measure | Value |
2222
|---|---|
2323
| Triaged directories | 5 |
24-
| Object sites in them | 439 |
24+
| Object sites in them | 440 |
2525
| Still-open (strip) sites | 124 |
2626
| Files carrying at least one | 22 |
2727

@@ -46,10 +46,10 @@ The `strict` column is the one the campaign schedules against; it counts both th
4646
|---|---|---|---|---|---|
4747
| `ui/` | 169 | 157 | 5 | 0 | 7 |
4848
| `data/` | 157 | 76 | 1 | 0 | 80 |
49-
| `automation/` | 66 | 42 | 0 | 0 | 24 |
49+
| `automation/` | 67 | 42 | 0 | 1 | 24 |
5050
| `security/` | 20 | 7 | 0 | 0 | 13 |
5151
| `studio/` | 27 | 27 | 0 | 0 | 0 |
52-
| **total** | **439** | **309** | **6** | **0** | **124** |
52+
| **total** | **440** | **309** | **6** | **1** | **124** |
5353

5454
## File-level triage — site counts
5555

@@ -115,7 +115,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit
115115
|---|---|
116116
| `approval.zod.ts` | 4 |
117117
| `bpmn-interop.zod.ts` | 5 |
118-
| `builtin-node-config.zod.ts` | 8 |
118+
| `builtin-node-config.zod.ts` | 9 |
119119
| `control-flow.zod.ts` | 6 |
120120
| `execution.zod.ts` | 13 |
121121
| `flow-function.zod.ts` | 1 |
@@ -126,7 +126,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit
126126
| `state-machine.zod.ts` | 6 |
127127
| `time-relative-trigger.zod.ts` | 1 |
128128
| `webhook.zod.ts` | 1 |
129-
| **total** | **66** |
129+
| **total** | **67** |
130130

131131
### `security/` — sites
132132

@@ -204,7 +204,7 @@ over it is here.
204204

205205
### `automation/` — open
206206

207-
**24 strip of 66**, in 5 file(s).
207+
**24 strip of 67**, in 5 file(s).
208208

209209
| File | Strip | Sites |
210210
|---|---|---|
@@ -213,7 +213,7 @@ over it is here.
213213
| `execution.zod.ts` | 13 | 13 |
214214
| `flow.zod.ts` | 1 | 11 |
215215
| `node-executor.zod.ts` | 4 | 4 |
216-
| **total** | **24** | **66** |
216+
| **total** | **24** | **67** |
217217

218218
| Bucket | Sites |
219219
|---|---|

packages/services/service-automation/src/builtin/config-expression-ledger.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ interface SchemaNode {
4646
type?: string;
4747
properties?: Record<string, SchemaNode>;
4848
items?: SchemaNode;
49+
/** `true` = an open map with untyped values; an object = the schema every value takes. */
50+
additionalProperties?: boolean | SchemaNode;
4951
xExpression?: string;
5052
}
5153

@@ -57,15 +59,26 @@ interface SchemaNode {
5759
* enforces the ADR-0032 §3 double-brace text template, a different dialect that
5860
* rejects a single `{x}`. Conflating the two would fail every valid
5961
* `loop.collection`.
62+
*
63+
* `'value'` (#14149) maps to the ledger's `value` role: a slot whose authored
64+
* value may be a `{ dialect: 'cel', source }` envelope evaluated by the
65+
* expression engine to a value — the `assignment` node's `assignments.*`. It
66+
* IS `validateExpression`'s `'value'` role (CEL, any result type); what makes
67+
* it a distinct ledger role is the slot's shape rule: only an envelope-shaped
68+
* object is an expression there, a plain string stays `{var}` interpolation.
6069
*/
6170
const ROLE_BY_MARKER: Record<string, FlowNodeExpressionRole> = {
6271
expression: 'predicate',
6372
template: 'flow-template',
73+
value: 'value',
6474
};
6575

6676
/**
6777
* Collect every `xExpression`-marked property in a configSchema, as the same
68-
* `key[].nested` path syntax the ledger uses.
78+
* `key[].nested` path syntax the ledger uses — and, since #14149, the same
79+
* `key.*` syntax: an object-valued `additionalProperties` is the schema every
80+
* value of an open map takes, so a marker there declares "every authored key
81+
* of this map", the sibling of `items` for arrays.
6982
*/
7083
function collectExpressionProps(
7184
schema: SchemaNode | undefined,
@@ -88,6 +101,18 @@ function collectExpressionProps(
88101
else out.push(...collectExpressionProps(prop, here));
89102
}
90103
}
104+
105+
// An open map whose VALUES carry a schema contributes `key.*` — the ledger's
106+
// spelling for "every authored key of this map" (`assignments.*`). A bare
107+
// `additionalProperties: true` (untyped values — the `assignment`
108+
// DESCRIPTOR's own shape) declares nothing, so the descriptor channel and
109+
// the spec channel cannot double-declare the same slot.
110+
const values = schema.additionalProperties;
111+
if (values && typeof values === 'object') {
112+
const here = prefix ? `${prefix}.*` : '*';
113+
if (typeof values.xExpression === 'string') out.push({ path: here, marker: values.xExpression });
114+
out.push(...collectExpressionProps(values, here));
115+
}
91116
return out;
92117
}
93118

@@ -203,6 +228,23 @@ describe('configSchema ↔ expression-ledger reconciliation (#4027)', () => {
203228
expect(declaredFromDescriptors().map(key)).not.toContain(key(decision!));
204229
});
205230

231+
it('assignment.assignments.* is covered — the #14149 value slot, declared where the descriptor cannot carry it', () => {
232+
const assignment = FLOW_NODE_EXPRESSION_PATHS.find(
233+
(e) => e.nodeType === 'assignment' && e.path === 'assignments.*',
234+
);
235+
expect(assignment, 'the ruled slot: an assignment value may be a CEL envelope').toBeDefined();
236+
expect(assignment!.role).toBe('value');
237+
// The descriptor declares `assignments` as `additionalProperties: true`
238+
// (the openness IS its contract, pinned by the form↔Zod ledger), so the
239+
// marker rides the spec Zod's map value and reaches this ratchet through
240+
// the JSON map — never through the descriptor channel.
241+
expect(declaredFromSchemalessConfigs().map(key)).toContain(key(assignment!));
242+
expect(declaredFromDescriptors().map(key)).not.toContain(key(assignment!));
243+
// And the marker is mapped, not merely tolerated: an unknown marker still
244+
// fails `roleOf`, and a ledger entry with no declaration still reads stale.
245+
expect(ROLE_BY_MARKER.value).toBe('value');
246+
});
247+
206248
it('screen.fields[].visibleWhen is covered — the #3528 regression', () => {
207249
const screen = FLOW_NODE_EXPRESSION_PATHS.find(
208250
(e) => e.nodeType === 'screen' && e.path === 'fields[].visibleWhen',

0 commit comments

Comments
 (0)