From 6820d6ef8ce66e044a19fe3c453e346349fce5f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 20:35:40 +0000 Subject: [PATCH 1/2] docs(spec): name the real flow-definition write door in the edge-condition upgrade entry The ADR-0087 D3 entry flow-edge-condition-evaluated-slot-source-required named `POST /flows` in its `surface` and `acceptanceCriteria`, and the pending changeset for the same change repeated it. No such route is mounted. The door a flow definition is created through on a composed runtime is `POST /api/v1/automation` (dispatcher-plugin mounts `${prefix}/automation` with the default `/api/v1` prefix; route ledger row `POST /automation`, client `automation.create`). Entry text only; `migrations/registry.ts` regenerated with `gen:migration-registry`. No runtime or schema change. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude --- .changeset/flow-edge-condition-evaluated-slot.md | 2 +- .../18.flow-edge-condition-evaluated-slot-source-required.ts | 4 ++-- packages/spec/src/migrations/registry.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/flow-edge-condition-evaluated-slot.md b/.changeset/flow-edge-condition-evaluated-slot.md index 37edaaed6fb..fe35b6ae163 100644 --- a/.changeset/flow-edge-condition-evaluated-slot.md +++ b/.changeset/flow-edge-condition-evaluated-slot.md @@ -80,7 +80,7 @@ the refusal itself carries the prescription. **A flow ALREADY STORED in `sys_metadata` stops running entirely — the whole flow, not just the edge.** The paragraph above is the author's remedy, at -`objectstack validate` / `POST /flows`; a stored row has no author in front of +`objectstack validate` / `POST /api/v1/automation`; a stored row has no author in front of it. Stored flows are deliberately NOT canonicalized by `applyConversionsToStoredItem` (`spec/src/conversions/stored.ts`, and the same skip in `metadata/src/loaders/database-loader.ts`'s `rowToData`) — flow-node diff --git a/packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.ts b/packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.ts index b6a058ad533..b8c1cf8c886 100644 --- a/packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.ts +++ b/packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.ts @@ -18,7 +18,7 @@ export const entry: SemanticMigration = { + 'than deriving a second one; it is the same decision reaching the second slot, which is why ' + 'it is named here instead of in an entry of its own. Reachable wherever a flow is authored ' + 'or stored: defineStack({ flows }) sources, an exported stack passed to objectstack validate, ' - + 'a POST /flows body, and a flow row already sitting in sys_metadata', + + 'a POST /api/v1/automation body, and a flow row already sitting in sys_metadata', replacement: 'a non-blank `source` — `{ dialect: \'cel\', source: \'record.amount > 10\' }`, or the bare ' + 'string `\'record.amount > 10\'` — if the edge was meant to branch; or REMOVE the ' @@ -60,7 +60,7 @@ export const entry: SemanticMigration = { acceptanceCriteria: 'Grep every authored structural condition — BOTH `edges[].condition` and a node\'s ' + '`config.condition` (a `decision` node\'s predicate, and on a `start` node the trigger ' - + 'gate) — in `defineStack({ flows })` sources, exported stacks and `POST /flows` bodies, and ' + + 'gate) — in `defineStack({ flows })` sources, exported stacks and `POST /api/v1/automation` bodies, and ' + 'every flow row in `sys_metadata`, for an envelope with no `source` key and for a `source` ' + '(or bare string) that is empty after trimming. ⚠️ Sweeping only the edge key leaves the ' + 'node key unswept, and the node key is the one with no schema in front of it. For each ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 30cf33582d0..f14b1dffd01 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -9065,7 +9065,7 @@ const step18: MigrationStep = { + 'than deriving a second one; it is the same decision reaching the second slot, which is why ' + 'it is named here instead of in an entry of its own. Reachable wherever a flow is authored ' + 'or stored: defineStack({ flows }) sources, an exported stack passed to objectstack validate, ' - + 'a POST /flows body, and a flow row already sitting in sys_metadata', + + 'a POST /api/v1/automation body, and a flow row already sitting in sys_metadata', replacement: 'a non-blank `source` — `{ dialect: \'cel\', source: \'record.amount > 10\' }`, or the bare ' + 'string `\'record.amount > 10\'` — if the edge was meant to branch; or REMOVE the ' @@ -9107,7 +9107,7 @@ const step18: MigrationStep = { acceptanceCriteria: 'Grep every authored structural condition — BOTH `edges[].condition` and a node\'s ' + '`config.condition` (a `decision` node\'s predicate, and on a `start` node the trigger ' - + 'gate) — in `defineStack({ flows })` sources, exported stacks and `POST /flows` bodies, and ' + + 'gate) — in `defineStack({ flows })` sources, exported stacks and `POST /api/v1/automation` bodies, and ' + 'every flow row in `sys_metadata`, for an envelope with no `source` key and for a `source` ' + '(or bare string) that is empty after trimming. ⚠️ Sweeping only the edge key leaves the ' + 'node key unswept, and the node key is the one with no schema in front of it. For each ' From 66417af31f21671b927f69d9feb1d5af488e24ec Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 21:23:54 +0000 Subject: [PATCH 2/2] docs(changeset): the edge-condition note reads true in the release it ships in Two sentences of the pending flow-edge-condition-evaluated-slot note were true of their own change but are overtaken by sibling notes in the same pending release. - The whitespace-only `config.condition` ruling is untouched by this change, but the service-automation and lint notes in that release refuse the value at `registerFlow` and at `objectstack validate`. The sentence now says so and points at them. - The start node's `config.condition` producer-side gate is no longer the shape refusal alone: the structural pass at both doors follows it with a blank-source check running `EvaluatedExpressionInputSchema`. Changeset text only. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude --- .changeset/flow-edge-condition-evaluated-slot.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.changeset/flow-edge-condition-evaluated-slot.md b/.changeset/flow-edge-condition-evaluated-slot.md index fe35b6ae163..9b93572b236 100644 --- a/.changeset/flow-edge-condition-evaluated-slot.md +++ b/.changeset/flow-edge-condition-evaluated-slot.md @@ -61,7 +61,10 @@ edges: refused by `evaluateCondition` with the same sentence, instead of answering a silent `false`. An `ast` BESIDE a string `source` is still admitted everywhere. The whitespace-only STRING ruling on `config.condition` (#15662: - consistent `false` on both sides) is untouched. + consistent `false` on both sides) is untouched by this change, but it does not + survive the release that carries it: two sibling notes in that release refuse + the value, at `registerFlow` (#17322, `@objectstack/service-automation`) and + at `objectstack validate` (#17495, `@objectstack/lint`). - **Three doors agree, through the spec.** `registerFlow` refuses the flow at `FlowSchema.parse` (edge) or at its structural pass (`config.condition`); `objectstack validate` refuses it at its `ObjectStackDefinitionSchema` parse @@ -104,5 +107,9 @@ door, `objectstack validate`, which locates the same edge at judgment for a consumer replaying the chain. Not touched here: `start.config.condition` has no Zod schema to narrow (the -start node's `config` is an open record); its producer-side gate is the -structural refusal above, which this change tightens but does not type. +start node's `config` is an open record). Its producer-side gate is the +structural pass at `registerFlow` and `objectstack validate`: the shape refusal +above, which this change tightens but does not type, and after it a blank-source +check that runs this change's `EvaluatedExpressionInputSchema` on the +condition's `source` (added by #17322 at `registerFlow` and by #17495 at +`objectstack validate`).