You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(spec): TryCatchErrorValueSchema declares the optional open-string code key the try_catch engine binds (#15672)
* feat(spec): TryCatchErrorValueSchema declares the optional open-string code key the try_catch engine binds
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
* chore(spec): regenerate authorable-surface and the control-flow reference for the new code key
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec): `TryCatchErrorValueSchema` declares the `code` key the `try_catch` engine binds (#14954)
6
+
7
+
`TryCatchErrorValue` — the ONE shape the catch region's author, the engine and the run log share for the value a `try_catch` binds to `errorVariable` (default `$error`) — gains an optional `code: string`: the platform-classified error code (ADR-0112) the failing node's own result carried, e.g. `create_record`'s `DUPLICATE_RECORD`. The engine has bound it since `@objectstack/service-automation`'s #14419 change; the schema was a plain `z.object` that did not declare it, so a round-trip through the declared shape silently STRIPPED the key the engine had put there, and the generated reference page documented four keys where the runtime binds five. The `errorVariable` description on `TryCatchConfig` names `code` too, so the authorable surface documents branching on `$error.code`.
8
+
9
+
Typed as an open `string`, deliberately not `StandardErrorCode` and not the ledger union: ADR-0112 D3/D4 with the #9106 amendment make the code vocabulary `StandardErrorCode` ∪ registered ledger codes ∪ tenant-authored codes, and `NodeExecutor` is third-party-registrable, so a closed type would be false the moment anyone registers an executor that throws its own code. The closed-at-every-door rule governs `ApiErrorSchema.code` at an HTTP door; this value is bound in-process and never crosses one.
10
+
11
+
Additive and optional: every value that parsed before parses byte-identically, and a binding without a classified code still carries no `code` key — absent means "no classified code", never "nothing failed". Semver: a new optional key on a published schema widens the accept set and the exported `TryCatchErrorValue` type without retiring or renaming anything ⇒ `minor`; no ADR-0087 entry is owed because there is nothing an upgrader must migrate.
|**catch**|`{ nodes: object[]; edges?: object[] }`| optional | Handler region run when the try region fails |
234
-
|**errorVariable**|`string`| optional (default: `"$error"`) | Variable holding the caught error in the catch region — a `TryCatchErrorValue`: `nodeId`, `message`, and `iteration` / `item` when the failure happened inside a loop body |
234
+
|**errorVariable**|`string`| optional (default: `"$error"`) | Variable holding the caught error in the catch region — a `TryCatchErrorValue`: `nodeId`, `message`, `code` when the failing node carried a platform-classified error code (ADR-0112 — branch on `$error.code` to tell "the row is already there" from "the store is down"), and `iteration` / `item` when the failure happened inside a loop body |
235
235
|**retry**|`{ maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; maxRetryDelayMs?: integer; … }`| optional | Optional retry policy for the try region |
236
236
237
237
### Nested Shape: `TryCatchConfig.try`
@@ -270,6 +270,7 @@ const result = FlowRegionSchema.parse(data);
270
270
| :--- | :--- | :--- | :--- |
271
271
|**nodeId**|`string`| ✅ | Node the failure is attributed to |
272
272
|**message**|`string`| ✅ | Message of the error that ended the try region, after any retries |
273
+
|**code**|`string`| optional | Platform-classified error code (ADR-0112) of the failure that ended the try region, e.g. `create_record`'s `DUPLICATE_RECORD`; present only when the failing node's own result carried one, so a catch region branching on `$error.code` treats "unset" as "no classified code", never as "nothing failed". An open `string`, not a closed enum: the vocabulary is `StandardErrorCode` plus registered ledger codes plus tenant-authored codes, and third-party node executors bind their own |
273
274
|**iteration**|`integer`| optional | Zero-based iteration of the enclosing loop when the failure happened inside a loop body; absent outside a loop |
274
275
|**item**|`any`| optional | The loop item being processed (the enclosing loop's `iteratorVariable` value) when the failure happened inside a loop body; absent outside a loop |
catch: FlowRegionSchema.optional().describe('Handler region run when the try region fails'),
316
316
/** Variable the caught error is bound to inside the catch region. */
317
-
errorVariable: z.string().default('$error').describe('Variable holding the caught error in the catch region — a `TryCatchErrorValue`: `nodeId`, `message`, and `iteration` / `item` when the failure happened inside a loop body'),
317
+
errorVariable: z.string().default('$error').describe('Variable holding the caught error in the catch region — a `TryCatchErrorValue`: `nodeId`, `message`, `code` when the failing node carried a platform-classified error code (ADR-0112 — branch on `$error.code` to tell "the row is already there" from "the store is down"), and `iteration` / `item` when the failure happened inside a loop body'),
318
318
retry: RetryPolicySchema.optional().describe('Optional retry policy for the try region'),
319
319
},
320
320
));
@@ -336,6 +336,21 @@ export type TryCatchConfigParsed = z.infer<typeof TryCatchConfigSchema>;
336
336
* try/catch outside any loop binds neither, so their absence means "not in a
337
337
* loop", never "row unknown".
338
338
*
339
+
* `code` (#14419 / #14954) is the platform-classified error code (ADR-0112)
340
+
* the failing node's own result carried — `create_record`'s `DUPLICATE_RECORD`
341
+
* is the founding case — bound so a catch region can tell "the row is already
342
+
* there" from "the store is down" by branching on `$error.code` instead of
343
+
* parsing `message`. Present only when a classified code was carried, so its
344
+
* absence means "no classified code", never "nothing failed". It is
345
+
* deliberately an OPEN `string`, not `StandardErrorCode` and not the ledger
346
+
* union: ADR-0112 D3/D4 with the #9106 amendment make the code vocabulary
347
+
* `StandardErrorCode` ∪ registered ledger codes ∪ tenant-authored codes, and
348
+
* `NodeExecutor` is third-party-registrable, so a closed type here would be
349
+
* false the moment anyone registers an executor that throws its own code.
350
+
* The closed-at-every-door rule governs `ApiErrorSchema.code` at an HTTP
351
+
* door; this value never crosses one — it is bound in-process, before any
352
+
* demotion to `declaredCode` could apply.
353
+
*
339
354
* A plain `z.object`, closed by convention rather than `strictObject`: this is
340
355
* a value the engine assembles, not a surface an author writes, so the
341
356
* unknown-key prescription an authoring surface owes has nobody to address.
@@ -345,6 +360,8 @@ export type TryCatchConfigParsed = z.infer<typeof TryCatchConfigSchema>;
nodeId: z.string().describe('Node the failure is attributed to'),
347
362
message: z.string().describe('Message of the error that ended the try region, after any retries'),
363
+
code: z.string().optional()
364
+
.describe('Platform-classified error code (ADR-0112) of the failure that ended the try region, e.g. `create_record`\'s `DUPLICATE_RECORD`; present only when the failing node\'s own result carried one, so a catch region branching on `$error.code` treats "unset" as "no classified code", never as "nothing failed". An open `string`, not a closed enum: the vocabulary is `StandardErrorCode` plus registered ledger codes plus tenant-authored codes, and third-party node executors bind their own'),
348
365
iteration: z.number().int().min(0).optional()
349
366
.describe('Zero-based iteration of the enclosing loop when the failure happened inside a loop body; absent outside a loop'),
0 commit comments