@telorun/sdk 0.73.0 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,4 +39,25 @@ export declare function isBigIntJsonEnabled(): boolean;
39
39
  * Everything that wants exact digits on the wire needs no replacer at all.
40
40
  */
41
41
  export declare function bigIntAt(holder: unknown, key: string): bigint | undefined;
42
+ /**
43
+ * A declared-integer input read as a JS number, whichever representation the
44
+ * call site produced.
45
+ *
46
+ * A CEL integer is an int64 — a BigInt — and the kernel normalizes a declared
47
+ * `type: integer` OUTPUT to that form, so one resource's result reaching another
48
+ * resource's input arrives as a BigInt while a YAML literal at the same slot
49
+ * arrives as a plain number. A controller that reads such an input with
50
+ * `Number.isInteger(...)` or plain arithmetic therefore works for one call site
51
+ * and throws `Cannot mix BigInt and other types` for the other. Inputs are
52
+ * deliberately NOT normalized (that would change the authoring surface of every
53
+ * module rather than repair a false declaration), so this is how a controller
54
+ * reads one.
55
+ *
56
+ * Returns `undefined` for anything that is not an integer in either
57
+ * representation — including a BigInt too large for a double, since silently
58
+ * rounding it would be the precision loss int64 support exists to remove — so a
59
+ * caller's own "must be a non-negative integer" check still rejects what it
60
+ * should.
61
+ */
62
+ export declare function integerInput(value: unknown): number | undefined;
42
63
  //# sourceMappingURL=bigint-json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bigint-json.d.ts","sourceRoot":"","sources":["../src/bigint-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;uCAGuC;AACvC,eAAO,MAAM,yBAAyB,QAAgB,CAAC;AAEvD,oFAAoF;AACpF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE"}
1
+ {"version":3,"file":"bigint-json.d.ts","sourceRoot":"","sources":["../src/bigint-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;uCAGuC;AACvC,eAAO,MAAM,yBAAyB,QAAgB,CAAC;AAEvD,oFAAoF;AACpF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAK/D"}
@@ -45,3 +45,31 @@ export function bigIntAt(holder, key) {
45
45
  const source = holder?.[key];
46
46
  return typeof source === "bigint" ? source : undefined;
47
47
  }
48
+ /**
49
+ * A declared-integer input read as a JS number, whichever representation the
50
+ * call site produced.
51
+ *
52
+ * A CEL integer is an int64 — a BigInt — and the kernel normalizes a declared
53
+ * `type: integer` OUTPUT to that form, so one resource's result reaching another
54
+ * resource's input arrives as a BigInt while a YAML literal at the same slot
55
+ * arrives as a plain number. A controller that reads such an input with
56
+ * `Number.isInteger(...)` or plain arithmetic therefore works for one call site
57
+ * and throws `Cannot mix BigInt and other types` for the other. Inputs are
58
+ * deliberately NOT normalized (that would change the authoring surface of every
59
+ * module rather than repair a false declaration), so this is how a controller
60
+ * reads one.
61
+ *
62
+ * Returns `undefined` for anything that is not an integer in either
63
+ * representation — including a BigInt too large for a double, since silently
64
+ * rounding it would be the precision loss int64 support exists to remove — so a
65
+ * caller's own "must be a non-negative integer" check still rejects what it
66
+ * should.
67
+ */
68
+ export function integerInput(value) {
69
+ if (typeof value === "number")
70
+ return Number.isInteger(value) ? value : undefined;
71
+ if (typeof value !== "bigint")
72
+ return undefined;
73
+ const asNumber = Number(value);
74
+ return Number.isSafeInteger(asNumber) ? asNumber : undefined;
75
+ }
@@ -1,9 +1,35 @@
1
1
  import type { Invocable } from "./capabilities/invokable.js";
2
+ import { type InvokeContext } from "./cancellation.js";
2
3
  import type { KindRef, ScopeContext } from "./ref.js";
3
4
  import { type ResourceInstance } from "./resource-instance.js";
4
- /** Retry policy for a single invoke step, passed through to `ctx.invoke`. */
5
+ /**
6
+ * Retry policy for a single invoke step.
7
+ *
8
+ * The field names are `Http.Request`'s, deliberately: two spellings of one
9
+ * concern in one standard library is how an author learns that backoff means
10
+ * something different depending on where it is written. `delay` is the older
11
+ * duration-string spelling, kept because published manifests carry it, and read
12
+ * as `initialDelay` when that is absent.
13
+ *
14
+ * Consumed HERE, in the leaf, rather than passed to `ctx.invoke`. The leaf has
15
+ * four dispatch branches and only one of them went through `ctx.invoke`, so a
16
+ * policy handed downstream was silently ignored for a pre-injected `!ref` — the
17
+ * dominant shape — and no kernel path ever read it. Owning it at the one place
18
+ * every branch passes through is what makes the field mean anything at all.
19
+ */
5
20
  export interface InvokeStepRetry {
21
+ /** Re-attempts after the first try. 0 (or absent) disables retrying. */
6
22
  attempts?: number;
23
+ /** Milliseconds before the first re-attempt. */
24
+ initialDelay?: number;
25
+ /** Multiplier applied to the delay after each re-attempt. */
26
+ factor?: number;
27
+ /** Ceiling on the delay between re-attempts, in milliseconds. */
28
+ maxDelay?: number;
29
+ /** `full` picks each delay uniformly from [0, delay], decorrelating a fleet
30
+ * that failed together. */
31
+ jitter?: "none" | "full";
32
+ /** DEPRECATED duration string (`"250ms"`, `"1s"`) — read as `initialDelay`. */
7
33
  delay?: string;
8
34
  }
9
35
  /**
@@ -19,15 +45,17 @@ export interface InvokeStep {
19
45
  retry?: InvokeStepRetry;
20
46
  }
21
47
  /** An inline flat invoke step on an Application's `targets`. Same as an
22
- * `InvokeStep` but `name` is optional (only needed for `steps.<name>.result`
23
- * plumbing; the boot runner synthesizes one when omitted) and `retry` is not
24
- * supported the boot invoke path takes no retry options, so it is omitted
25
- * from the surface rather than silently ignored. */
48
+ * {@link InvokeStep} but `name` is optional — it is only needed for
49
+ * `steps.<name>.result` plumbing, and the boot runner synthesizes one when
50
+ * omitted. Everything else a dispatch site carries applies here, `retry`
51
+ * included: both are the same kernel-owned shape, and the schema half now says
52
+ * so too. Control flow (`if`/`while`/`switch`/`try`) is still Run's. */
26
53
  export interface InlineInvokeTarget {
27
54
  name?: string;
28
55
  when?: string;
29
56
  invoke: KindRef<Invocable> | Invocable;
30
57
  inputs?: Record<string, unknown>;
58
+ retry?: InvokeStepRetry;
31
59
  }
32
60
  /** A single Application `targets` entry. The kernel boot runner dispatches by
33
61
  * shape: a bare string or resolved `{ kind, name }` runs a Runnable/Service;
@@ -67,6 +95,24 @@ export interface InvokeStepState {
67
95
  steps: Record<string, unknown>;
68
96
  cel?: Record<string, unknown>;
69
97
  scope?: ScopeContext;
98
+ /**
99
+ * The invocation this step runs inside, forwarded from the composer's own
100
+ * `invoke(inputs, ctx)`.
101
+ *
102
+ * Needed for the WAIT, not for the dispatch. The kernel refuses a dispatch
103
+ * reached after the tree was cancelled, so every step boundary is already a
104
+ * cancellation point through the ambient context — but a backoff between two
105
+ * attempts is time spent inside this leaf, where the kernel's gate cannot see
106
+ * it and the ambient store is deliberately not on the SDK surface (it is one
107
+ * runtime's mechanism; a second-language leaf has no `AsyncLocalStorage`).
108
+ * Passing it explicitly is what makes the wait interruptible in any runtime.
109
+ *
110
+ * Present at boot too: the boot runner forwards the kernel's boot cancellation,
111
+ * which the CLI's SIGINT handler trips, so Ctrl-C ends a target parked in a
112
+ * backoff. Absent only for a caller that assembled a step in code and had no
113
+ * invocation to forward.
114
+ */
115
+ invokeCtx?: InvokeContext;
70
116
  }
71
117
  /**
72
118
  * Execute one invoke step: evaluate the `when` guard, expand `inputs`, resolve
@@ -1 +1 @@
1
- {"version":3,"file":"invoke-step.d.ts","sourceRoot":"","sources":["../src/invoke-step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/E,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED;;;;qDAIqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;mFAGmF;AACnF,MAAM,MAAM,UAAU,GAClB,MAAM,GACN;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,kBAAkB,CAAC;AAEvB;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;IAC3D,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1F,cAAc,CAAC,OAAO,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,GAAG,CAAC,CAAC;IAChB;;qDAEiD;IACjD,uBAAuB,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;CACrF;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAuCf"}
1
+ {"version":3,"file":"invoke-step.d.ts","sourceRoot":"","sources":["../src/invoke-step.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,7 @@
1
+ import { ERR_INVOKE_CANCELLED, isCancellationError, } from "./cancellation.js";
2
+ import { isAmbientContractErrorCode } from "./contract-errors.js";
3
+ import { tryParseDurationMs } from "./duration.js";
4
+ import { InvokeError } from "./invoke-error.js";
1
5
  import { getRefIdentity } from "./resource-instance.js";
2
6
  /**
3
7
  * Execute one invoke step: evaluate the `when` guard, expand `inputs`, resolve
@@ -11,6 +15,124 @@ export async function executeInvokeStep(step, ctx, state) {
11
15
  return;
12
16
  const inputs = ctx.expandValue(step.inputs ?? {}, cel);
13
17
  const raw = step.invoke;
18
+ const result = await withStepRetry(step, state.invokeCtx, () => dispatch(raw, inputs, ctx, state));
19
+ state.steps[step.name] = { result };
20
+ }
21
+ /**
22
+ * Re-attempt a step's dispatch while its policy allows.
23
+ *
24
+ * Retries a DOMAIN failure and nothing else. There is no status to classify at
25
+ * this level, so the classification cannot be positive — what there is instead is
26
+ * an explicit author instruction, since a step carries `retry:` only because
27
+ * someone wrote it. So the rule is stated as exclusions, and both of them are
28
+ * decidable without judgement:
29
+ *
30
+ * - **Cancellation.** The invocation has been asked to stop; re-issuing it
31
+ * ignores that.
32
+ * - **A contract violation**, and a **resolution failure** — the target does not
33
+ * exist, or cannot be invoked. {@link isAmbientContractErrorCode} names the
34
+ * first set; {@link UNRETRYABLE_CODES} adds the second. Both are the KERNEL's
35
+ * verdict on the shape of the call rather than on the work, so they are a
36
+ * property of the manifest and every re-attempt fails identically — a budget
37
+ * spent on one is dead time between a typo and the diagnostic that names it,
38
+ * up to `attempts × maxDelay`. Nothing about a misspelled resource name gets
39
+ * truer after eight seconds of backoff.
40
+ *
41
+ * The WAIT between attempts is cancellable, for the first reason above. Every
42
+ * other point in a sequence already is — the kernel refuses a dispatch reached
43
+ * after the tree was cancelled — so a backoff is the one interval where a
44
+ * cancelled run would otherwise stay parked, for up to `attempts × maxDelay`.
45
+ *
46
+ * The defaults come from the schema (`Run` steps declare `default:` on every
47
+ * field, as `Http.Request.retry` does), so the `??` fallbacks here are the floor
48
+ * for a caller that assembled a policy in code rather than from a manifest — not
49
+ * a second, competing statement of what a default is.
50
+ */
51
+ async function withStepRetry(step, invokeCtx, dispatch) {
52
+ const policy = step.retry;
53
+ const attempts = policy?.attempts ?? 0;
54
+ if (!policy || attempts <= 0)
55
+ return dispatch();
56
+ const initial = policy.initialDelay ?? parseDuration(policy.delay) ?? 250;
57
+ const factor = policy.factor ?? 2;
58
+ const maxDelay = policy.maxDelay ?? 32_000;
59
+ const jitter = policy.jitter ?? "full";
60
+ for (let resend = 0;; resend++) {
61
+ try {
62
+ return await dispatch();
63
+ }
64
+ catch (err) {
65
+ if (resend >= attempts || !isRetryable(err))
66
+ throw err;
67
+ const backoff = Math.min(maxDelay, initial * Math.pow(factor, resend));
68
+ await waitBeforeResend(jitter === "full" ? Math.random() * backoff : backoff, invokeCtx?.cancellation, step, err);
69
+ }
70
+ }
71
+ }
72
+ /** Kernel verdicts on the CALL rather than on the work, beyond the ambient
73
+ * contract set. A dispatch that cannot resolve its target is a manifest defect;
74
+ * re-issuing it re-resolves the same name against the same registry. */
75
+ const UNRETRYABLE_CODES = new Set(["ERR_RESOURCE_NOT_FOUND", "ERR_RESOURCE_NOT_INVOKABLE"]);
76
+ function isRetryable(err) {
77
+ if (isCancellationError(err))
78
+ return false;
79
+ const code = err?.code;
80
+ if (typeof code !== "string")
81
+ return true;
82
+ return !isAmbientContractErrorCode(code) && !UNRETRYABLE_CODES.has(code);
83
+ }
84
+ /**
85
+ * Wait out the backoff, or give up the moment the invocation is cancelled.
86
+ *
87
+ * The failure that CAUSED the wait rides in the cancellation's `data`. Without
88
+ * it, cancelling mid-backoff would report only that the run was cancelled and
89
+ * the attempt's actual error — the thing the author is retrying because of —
90
+ * would be gone, which is exactly the swallowing a retry loop is prone to.
91
+ *
92
+ * `onCancelled` fires synchronously when the token is already cancelled, so an
93
+ * already-cancelled run clears the timer and rejects without waiting a tick.
94
+ */
95
+ function waitBeforeResend(ms, token, step, pending) {
96
+ return new Promise((resolve, reject) => {
97
+ const timer = setTimeout(() => {
98
+ unsubscribe?.();
99
+ resolve();
100
+ }, ms);
101
+ const unsubscribe = token?.onCancelled((reason) => {
102
+ clearTimeout(timer);
103
+ // Released on BOTH paths. The listener is the one thing this holds, and a
104
+ // subscription outliving the wait it belongs to is a leak per re-attempt.
105
+ unsubscribe?.();
106
+ reject(new InvokeError(ERR_INVOKE_CANCELLED, `Step "${step.name}": cancelled while waiting to re-attempt` +
107
+ `${reason ? ` (${reason})` : ""}.`, { step: step.name, pendingFailure: describeFailure(pending) }));
108
+ });
109
+ });
110
+ }
111
+ function describeFailure(err) {
112
+ const code = err?.code;
113
+ const message = err instanceof Error ? err.message : String(err);
114
+ return { ...(typeof code === "string" ? { code } : {}), message };
115
+ }
116
+ /**
117
+ * The deprecated `delay` duration string, in milliseconds.
118
+ *
119
+ * Delegates to the SDK's one duration grammar rather than restating it: a second
120
+ * spelling here would accept strings `telo check`'s `pattern` rejects, and the
121
+ * two would drift. A malformed value THROWS — falling back to the default would
122
+ * swallow a typo into a silently different backoff, and the schema's `pattern`
123
+ * means anything reaching this already failed static analysis.
124
+ */
125
+ function parseDuration(value) {
126
+ if (value === undefined)
127
+ return undefined;
128
+ const ms = tryParseDurationMs(value);
129
+ if (ms === null) {
130
+ throw new InvokeError("ERR_INVALID_VALUE", `Step retry: invalid 'delay' ${JSON.stringify(value)}; use a number with a unit, e.g. ` +
131
+ `"250ms", "2s", "1.5m", "1h" — or the preferred 'initialDelay', in milliseconds.`);
132
+ }
133
+ return ms;
134
+ }
135
+ async function dispatch(raw, inputs, ctx, state) {
14
136
  let result;
15
137
  if (raw && typeof raw.invoke === "function") {
16
138
  // A pre-injected live instance (a `!ref` resolved at Phase 5). Route it
@@ -39,8 +161,8 @@ export async function executeInvokeStep(step, ctx, state) {
39
161
  result = await ctx.invokeResolved(ref.kind, ref.name, instance, inputs);
40
162
  }
41
163
  else {
42
- result = await ctx.invoke(ref.kind, ref.name, inputs, { retry: step.retry });
164
+ result = await ctx.invoke(ref.kind, ref.name, inputs);
43
165
  }
44
166
  }
45
- state.steps[step.name] = { result };
167
+ return result;
46
168
  }
@@ -46,16 +46,19 @@ export type ParsedArgs = Partial<Record<string, string | boolean | string[]>> &
46
46
  /**
47
47
  * Per-call options for a by-name dispatch.
48
48
  *
49
- * A bag rather than a positional context parameter because this slot already
50
- * carried one meaning — `retry`, consumed by the step leaf — and
51
- * `ResourceContext` satisfies `InvokeStepContext` structurally, so a positional
52
- * `InvokeContext` here silently receives a step's retry options instead.
49
+ * A bag rather than a positional context parameter, because `ResourceContext`
50
+ * satisfies `InvokeStepContext` structurally: a positional fourth argument here
51
+ * is reachable from the step leaf's own `invoke` call, so the two surfaces would
52
+ * have to agree on its meaning forever. A named key cannot be filled by accident.
53
+ *
54
+ * `retry` USED to live here and was read by nobody — the step leaf handed it down
55
+ * on one of its four dispatch branches and no kernel path looked at it. It now
56
+ * belongs to the leaf, which is the one place every branch passes through; a key
57
+ * kept here would be a second, inert way to ask for the same thing.
53
58
  */
54
59
  export interface InvokeByNameOptions {
55
60
  /** Seeds the invocation context, replacing the ambient. */
56
61
  ctx?: InvokeContext;
57
- /** Retry policy, read by `executeInvokeStep`. */
58
- retry?: unknown;
59
62
  }
60
63
  export interface ResourceContext extends ControllerContext {
61
64
  readonly args: ParsedArgs;
@@ -1 +1 @@
1
- {"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource-context.d.ts","sourceRoot":"","sources":["../src/resource-context.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,12 @@ export type ValueTypeRepresentation = "json" | "instance";
38
38
  * second parameter can be added without a migration. */
39
39
  export interface ValueTypeParameter {
40
40
  readonly name: string;
41
+ /** This parameter's argument is what ITERATING a value of the type yields.
42
+ * Declared here so "what is the element of this collection" is answered by
43
+ * the vocabulary rather than by a consumer that knows one type's name — the
44
+ * same reason `live` is a field and not a check against `Telo.Stream`. At
45
+ * most one parameter per entry may carry it. */
46
+ readonly element?: boolean;
41
47
  readonly description?: string;
42
48
  }
43
49
  /** One value type, exactly as its entry file declares it. */
@@ -124,6 +130,17 @@ export declare function isLiveSlot(schema: unknown): boolean;
124
130
  /** True when the node declares a type represented as a runtime instance —
125
131
  * the values no manifest literal can ever be. */
126
132
  export declare function isInstanceSlot(schema: unknown): boolean;
133
+ /**
134
+ * The schema of what iterating a value at this slot yields, or undefined when
135
+ * the slot declares no value type, or one with no element parameter.
136
+ *
137
+ * The whole point of reading it from the entry is that no consumer names a type:
138
+ * a future iterable value type is covered by declaring `element` on its own
139
+ * parameter, with nothing to change here or in the analyzer. An element
140
+ * parameter left unsupplied means *any*, exactly as every other omitted argument
141
+ * does, so an unparameterized use degrades to permissive rather than to nothing.
142
+ */
143
+ export declare function elementSchemaOf(schema: unknown): unknown | undefined;
127
144
  /** The binding row for a schema node's declared type, or undefined when it
128
145
  * declares none / declares a `json` one. */
129
146
  export declare function bindingOf(schema: unknown): ValueTypeBinding | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"value-type.d.ts","sourceRoot":"","sources":["../src/value-type.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"value-type.d.ts","sourceRoot":"","sources":["../src/value-type.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC;;;;;4EAK4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,UAAU,CAAC;AAE1D;;;yDAGyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;qDAIiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,uBAAuB,CAAC;IACjD,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;iFAC6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;yCAEyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B;oEACgE;IAChE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;qDAGiD;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CACtC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAG1E,CAAC;AA6EF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,cAAc,CA2D/E;AAqCD,sEAAsE;AACtE,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAmB,CAAC;AAEhF;8BAC8B;AAC9B,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B;gEAC4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;8DAE0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAuB5E;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAEvE;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAExD;AAED;0EAC0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAEnD;AAED;kDACkD;AAClD,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAKpE;AAED;6CAC6C;AAC7C,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAGvE;AAED;;sEAEsE;AACtE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAEzE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAIhE;AAED;;sEAEsE;AACtE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAE5E;AAED;8DAC8D;AAC9D,wBAAgB,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxD"}
@@ -80,20 +80,34 @@ function readParameters(file, raw) {
80
80
  return [];
81
81
  if (!Array.isArray(raw))
82
82
  throw new ValueTypeEntryError(file, "'parameters' must be a sequence");
83
- return raw.map((entry, i) => {
83
+ const params = raw.map((entry, i) => {
84
84
  if (!isPlainObject(entry)) {
85
85
  throw new ValueTypeEntryError(file, `parameters[${i}] must be a mapping`);
86
86
  }
87
87
  for (const key of Object.keys(entry)) {
88
- if (key !== "name" && key !== "description") {
88
+ if (key !== "name" && key !== "description" && key !== "element") {
89
89
  throw new ValueTypeEntryError(file, `parameters[${i}] has no key '${key}'`);
90
90
  }
91
91
  }
92
+ if (entry.element !== undefined && typeof entry.element !== "boolean") {
93
+ throw new ValueTypeEntryError(file, `parameters[${i}].element must be a boolean when present`);
94
+ }
92
95
  const name = requireString(file, entry, "name");
93
- return entry.description === undefined
94
- ? { name }
95
- : { name, description: requireString(file, entry, "description") };
96
+ return {
97
+ name,
98
+ ...(entry.element === true ? { element: true } : {}),
99
+ ...(entry.description === undefined
100
+ ? {}
101
+ : { description: requireString(file, entry, "description") }),
102
+ };
96
103
  });
104
+ // Two element parameters would make "the element of this value" ambiguous, and
105
+ // the reader is the only place that can refuse it — every consumer takes the
106
+ // first match and would silently pick one.
107
+ if (params.filter((p) => p.element).length > 1) {
108
+ throw new ValueTypeEntryError(file, "at most one parameter may declare 'element'");
109
+ }
110
+ return params;
97
111
  }
98
112
  /**
99
113
  * Read one entry file's parsed data.
@@ -242,6 +256,23 @@ export function isLiveSlot(schema) {
242
256
  export function isInstanceSlot(schema) {
243
257
  return valueTypeOf(schema)?.representation === "instance";
244
258
  }
259
+ /**
260
+ * The schema of what iterating a value at this slot yields, or undefined when
261
+ * the slot declares no value type, or one with no element parameter.
262
+ *
263
+ * The whole point of reading it from the entry is that no consumer names a type:
264
+ * a future iterable value type is covered by declaring `element` on its own
265
+ * parameter, with nothing to change here or in the analyzer. An element
266
+ * parameter left unsupplied means *any*, exactly as every other omitted argument
267
+ * does, so an unparameterized use degrades to permissive rather than to nothing.
268
+ */
269
+ export function elementSchemaOf(schema) {
270
+ const slot = readValueTypeSlot(schema);
271
+ const parameter = slot?.entry?.parameters.find((p) => p.element);
272
+ if (!parameter)
273
+ return undefined;
274
+ return slot.args[parameter.name] ?? {};
275
+ }
245
276
  /** The binding row for a schema node's declared type, or undefined when it
246
277
  * declares none / declares a `json` one. */
247
278
  export function bindingOf(schema) {
@@ -7,6 +7,7 @@
7
7
  "parameters": [
8
8
  {
9
9
  "name": "of",
10
+ "element": true,
10
11
  "description": "The element the stream yields. Any schema node — an inline shape, a value type, a `!ref` to a named shape, or another parameterized type. Omitted means any element."
11
12
  }
12
13
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/sdk",
3
- "version": "0.73.0",
3
+ "version": "0.75.0",
4
4
  "description": "Telo SDK - Public API for Telo module authors.",
5
5
  "keywords": [
6
6
  "telo",
@@ -36,9 +36,12 @@
36
36
  ],
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",
39
- "typescript": "^5.0.0"
39
+ "typescript": "^5.0.0",
40
+ "vitest": "^2.1.8"
40
41
  },
41
42
  "scripts": {
42
- "build": "tsc -p tsconfig.lib.json"
43
+ "build": "tsc -p tsconfig.lib.json",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest"
43
46
  }
44
47
  }
@@ -49,3 +49,30 @@ export function bigIntAt(holder: unknown, key: string): bigint | undefined {
49
49
  const source = (holder as Record<string, unknown> | null | undefined)?.[key];
50
50
  return typeof source === "bigint" ? source : undefined;
51
51
  }
52
+
53
+ /**
54
+ * A declared-integer input read as a JS number, whichever representation the
55
+ * call site produced.
56
+ *
57
+ * A CEL integer is an int64 — a BigInt — and the kernel normalizes a declared
58
+ * `type: integer` OUTPUT to that form, so one resource's result reaching another
59
+ * resource's input arrives as a BigInt while a YAML literal at the same slot
60
+ * arrives as a plain number. A controller that reads such an input with
61
+ * `Number.isInteger(...)` or plain arithmetic therefore works for one call site
62
+ * and throws `Cannot mix BigInt and other types` for the other. Inputs are
63
+ * deliberately NOT normalized (that would change the authoring surface of every
64
+ * module rather than repair a false declaration), so this is how a controller
65
+ * reads one.
66
+ *
67
+ * Returns `undefined` for anything that is not an integer in either
68
+ * representation — including a BigInt too large for a double, since silently
69
+ * rounding it would be the precision loss int64 support exists to remove — so a
70
+ * caller's own "must be a non-negative integer" check still rejects what it
71
+ * should.
72
+ */
73
+ export function integerInput(value: unknown): number | undefined {
74
+ if (typeof value === "number") return Number.isInteger(value) ? value : undefined;
75
+ if (typeof value !== "bigint") return undefined;
76
+ const asNumber = Number(value);
77
+ return Number.isSafeInteger(asNumber) ? asNumber : undefined;
78
+ }
@@ -1,10 +1,44 @@
1
1
  import type { Invocable } from "./capabilities/invokable.js";
2
+ import {
3
+ type CancellationToken,
4
+ ERR_INVOKE_CANCELLED,
5
+ type InvokeContext,
6
+ isCancellationError,
7
+ } from "./cancellation.js";
8
+ import { isAmbientContractErrorCode } from "./contract-errors.js";
9
+ import { tryParseDurationMs } from "./duration.js";
10
+ import { InvokeError } from "./invoke-error.js";
2
11
  import type { KindRef, ScopeContext } from "./ref.js";
3
12
  import { getRefIdentity, type ResourceInstance } from "./resource-instance.js";
4
13
 
5
- /** Retry policy for a single invoke step, passed through to `ctx.invoke`. */
14
+ /**
15
+ * Retry policy for a single invoke step.
16
+ *
17
+ * The field names are `Http.Request`'s, deliberately: two spellings of one
18
+ * concern in one standard library is how an author learns that backoff means
19
+ * something different depending on where it is written. `delay` is the older
20
+ * duration-string spelling, kept because published manifests carry it, and read
21
+ * as `initialDelay` when that is absent.
22
+ *
23
+ * Consumed HERE, in the leaf, rather than passed to `ctx.invoke`. The leaf has
24
+ * four dispatch branches and only one of them went through `ctx.invoke`, so a
25
+ * policy handed downstream was silently ignored for a pre-injected `!ref` — the
26
+ * dominant shape — and no kernel path ever read it. Owning it at the one place
27
+ * every branch passes through is what makes the field mean anything at all.
28
+ */
6
29
  export interface InvokeStepRetry {
30
+ /** Re-attempts after the first try. 0 (or absent) disables retrying. */
7
31
  attempts?: number;
32
+ /** Milliseconds before the first re-attempt. */
33
+ initialDelay?: number;
34
+ /** Multiplier applied to the delay after each re-attempt. */
35
+ factor?: number;
36
+ /** Ceiling on the delay between re-attempts, in milliseconds. */
37
+ maxDelay?: number;
38
+ /** `full` picks each delay uniformly from [0, delay], decorrelating a fleet
39
+ * that failed together. */
40
+ jitter?: "none" | "full";
41
+ /** DEPRECATED duration string (`"250ms"`, `"1s"`) — read as `initialDelay`. */
8
42
  delay?: string;
9
43
  }
10
44
 
@@ -22,15 +56,17 @@ export interface InvokeStep {
22
56
  }
23
57
 
24
58
  /** An inline flat invoke step on an Application's `targets`. Same as an
25
- * `InvokeStep` but `name` is optional (only needed for `steps.<name>.result`
26
- * plumbing; the boot runner synthesizes one when omitted) and `retry` is not
27
- * supported the boot invoke path takes no retry options, so it is omitted
28
- * from the surface rather than silently ignored. */
59
+ * {@link InvokeStep} but `name` is optional — it is only needed for
60
+ * `steps.<name>.result` plumbing, and the boot runner synthesizes one when
61
+ * omitted. Everything else a dispatch site carries applies here, `retry`
62
+ * included: both are the same kernel-owned shape, and the schema half now says
63
+ * so too. Control flow (`if`/`while`/`switch`/`try`) is still Run's. */
29
64
  export interface InlineInvokeTarget {
30
65
  name?: string;
31
66
  when?: string;
32
67
  invoke: KindRef<Invocable> | Invocable;
33
68
  inputs?: Record<string, unknown>;
69
+ retry?: InvokeStepRetry;
34
70
  }
35
71
 
36
72
  /** A single Application `targets` entry. The kernel boot runner dispatches by
@@ -73,6 +109,24 @@ export interface InvokeStepState {
73
109
  steps: Record<string, unknown>;
74
110
  cel?: Record<string, unknown>;
75
111
  scope?: ScopeContext;
112
+ /**
113
+ * The invocation this step runs inside, forwarded from the composer's own
114
+ * `invoke(inputs, ctx)`.
115
+ *
116
+ * Needed for the WAIT, not for the dispatch. The kernel refuses a dispatch
117
+ * reached after the tree was cancelled, so every step boundary is already a
118
+ * cancellation point through the ambient context — but a backoff between two
119
+ * attempts is time spent inside this leaf, where the kernel's gate cannot see
120
+ * it and the ambient store is deliberately not on the SDK surface (it is one
121
+ * runtime's mechanism; a second-language leaf has no `AsyncLocalStorage`).
122
+ * Passing it explicitly is what makes the wait interruptible in any runtime.
123
+ *
124
+ * Present at boot too: the boot runner forwards the kernel's boot cancellation,
125
+ * which the CLI's SIGINT handler trips, so Ctrl-C ends a target parked in a
126
+ * backoff. Absent only for a caller that assembled a step in code and had no
127
+ * invocation to forward.
128
+ */
129
+ invokeCtx?: InvokeContext;
76
130
  }
77
131
 
78
132
  /**
@@ -91,6 +145,158 @@ export async function executeInvokeStep(
91
145
 
92
146
  const inputs = ctx.expandValue(step.inputs ?? {}, cel) as Record<string, unknown>;
93
147
  const raw = step.invoke as unknown;
148
+ const result = await withStepRetry(step, state.invokeCtx, () =>
149
+ dispatch(raw, inputs, ctx, state),
150
+ );
151
+
152
+ state.steps[step.name] = { result };
153
+ }
154
+
155
+ /**
156
+ * Re-attempt a step's dispatch while its policy allows.
157
+ *
158
+ * Retries a DOMAIN failure and nothing else. There is no status to classify at
159
+ * this level, so the classification cannot be positive — what there is instead is
160
+ * an explicit author instruction, since a step carries `retry:` only because
161
+ * someone wrote it. So the rule is stated as exclusions, and both of them are
162
+ * decidable without judgement:
163
+ *
164
+ * - **Cancellation.** The invocation has been asked to stop; re-issuing it
165
+ * ignores that.
166
+ * - **A contract violation**, and a **resolution failure** — the target does not
167
+ * exist, or cannot be invoked. {@link isAmbientContractErrorCode} names the
168
+ * first set; {@link UNRETRYABLE_CODES} adds the second. Both are the KERNEL's
169
+ * verdict on the shape of the call rather than on the work, so they are a
170
+ * property of the manifest and every re-attempt fails identically — a budget
171
+ * spent on one is dead time between a typo and the diagnostic that names it,
172
+ * up to `attempts × maxDelay`. Nothing about a misspelled resource name gets
173
+ * truer after eight seconds of backoff.
174
+ *
175
+ * The WAIT between attempts is cancellable, for the first reason above. Every
176
+ * other point in a sequence already is — the kernel refuses a dispatch reached
177
+ * after the tree was cancelled — so a backoff is the one interval where a
178
+ * cancelled run would otherwise stay parked, for up to `attempts × maxDelay`.
179
+ *
180
+ * The defaults come from the schema (`Run` steps declare `default:` on every
181
+ * field, as `Http.Request.retry` does), so the `??` fallbacks here are the floor
182
+ * for a caller that assembled a policy in code rather than from a manifest — not
183
+ * a second, competing statement of what a default is.
184
+ */
185
+ async function withStepRetry<T>(
186
+ step: InvokeStep,
187
+ invokeCtx: InvokeContext | undefined,
188
+ dispatch: () => Promise<T>,
189
+ ): Promise<T> {
190
+ const policy = step.retry;
191
+ const attempts = policy?.attempts ?? 0;
192
+ if (!policy || attempts <= 0) return dispatch();
193
+
194
+ const initial = policy.initialDelay ?? parseDuration(policy.delay) ?? 250;
195
+ const factor = policy.factor ?? 2;
196
+ const maxDelay = policy.maxDelay ?? 32_000;
197
+ const jitter = policy.jitter ?? "full";
198
+
199
+ for (let resend = 0; ; resend++) {
200
+ try {
201
+ return await dispatch();
202
+ } catch (err) {
203
+ if (resend >= attempts || !isRetryable(err)) throw err;
204
+ const backoff = Math.min(maxDelay, initial * Math.pow(factor, resend));
205
+ await waitBeforeResend(
206
+ jitter === "full" ? Math.random() * backoff : backoff,
207
+ invokeCtx?.cancellation,
208
+ step,
209
+ err,
210
+ );
211
+ }
212
+ }
213
+ }
214
+
215
+ /** Kernel verdicts on the CALL rather than on the work, beyond the ambient
216
+ * contract set. A dispatch that cannot resolve its target is a manifest defect;
217
+ * re-issuing it re-resolves the same name against the same registry. */
218
+ const UNRETRYABLE_CODES = new Set(["ERR_RESOURCE_NOT_FOUND", "ERR_RESOURCE_NOT_INVOKABLE"]);
219
+
220
+ function isRetryable(err: unknown): boolean {
221
+ if (isCancellationError(err)) return false;
222
+ const code = (err as { code?: unknown } | null | undefined)?.code;
223
+ if (typeof code !== "string") return true;
224
+ return !isAmbientContractErrorCode(code) && !UNRETRYABLE_CODES.has(code);
225
+ }
226
+
227
+ /**
228
+ * Wait out the backoff, or give up the moment the invocation is cancelled.
229
+ *
230
+ * The failure that CAUSED the wait rides in the cancellation's `data`. Without
231
+ * it, cancelling mid-backoff would report only that the run was cancelled and
232
+ * the attempt's actual error — the thing the author is retrying because of —
233
+ * would be gone, which is exactly the swallowing a retry loop is prone to.
234
+ *
235
+ * `onCancelled` fires synchronously when the token is already cancelled, so an
236
+ * already-cancelled run clears the timer and rejects without waiting a tick.
237
+ */
238
+ function waitBeforeResend(
239
+ ms: number,
240
+ token: CancellationToken | undefined,
241
+ step: InvokeStep,
242
+ pending: unknown,
243
+ ): Promise<void> {
244
+ return new Promise<void>((resolve, reject) => {
245
+ const timer = setTimeout(() => {
246
+ unsubscribe?.();
247
+ resolve();
248
+ }, ms);
249
+ const unsubscribe = token?.onCancelled((reason) => {
250
+ clearTimeout(timer);
251
+ // Released on BOTH paths. The listener is the one thing this holds, and a
252
+ // subscription outliving the wait it belongs to is a leak per re-attempt.
253
+ unsubscribe?.();
254
+ reject(
255
+ new InvokeError(
256
+ ERR_INVOKE_CANCELLED,
257
+ `Step "${step.name}": cancelled while waiting to re-attempt` +
258
+ `${reason ? ` (${reason})` : ""}.`,
259
+ { step: step.name, pendingFailure: describeFailure(pending) },
260
+ ),
261
+ );
262
+ });
263
+ });
264
+ }
265
+
266
+ function describeFailure(err: unknown): { code?: string; message: string } {
267
+ const code = (err as { code?: unknown } | null | undefined)?.code;
268
+ const message = err instanceof Error ? err.message : String(err);
269
+ return { ...(typeof code === "string" ? { code } : {}), message };
270
+ }
271
+
272
+ /**
273
+ * The deprecated `delay` duration string, in milliseconds.
274
+ *
275
+ * Delegates to the SDK's one duration grammar rather than restating it: a second
276
+ * spelling here would accept strings `telo check`'s `pattern` rejects, and the
277
+ * two would drift. A malformed value THROWS — falling back to the default would
278
+ * swallow a typo into a silently different backoff, and the schema's `pattern`
279
+ * means anything reaching this already failed static analysis.
280
+ */
281
+ function parseDuration(value: string | undefined): number | undefined {
282
+ if (value === undefined) return undefined;
283
+ const ms = tryParseDurationMs(value);
284
+ if (ms === null) {
285
+ throw new InvokeError(
286
+ "ERR_INVALID_VALUE",
287
+ `Step retry: invalid 'delay' ${JSON.stringify(value)}; use a number with a unit, e.g. ` +
288
+ `"250ms", "2s", "1.5m", "1h" — or the preferred 'initialDelay', in milliseconds.`,
289
+ );
290
+ }
291
+ return ms;
292
+ }
293
+
294
+ async function dispatch(
295
+ raw: unknown,
296
+ inputs: Record<string, unknown>,
297
+ ctx: InvokeStepContext,
298
+ state: InvokeStepState,
299
+ ): Promise<unknown> {
94
300
  let result: unknown;
95
301
 
96
302
  if (raw && typeof (raw as Invocable).invoke === "function") {
@@ -119,9 +325,8 @@ export async function executeInvokeStep(
119
325
  const instance = state.scope.getInstance(ref.name) as unknown as ResourceInstance;
120
326
  result = await ctx.invokeResolved(ref.kind, ref.name, instance, inputs);
121
327
  } else {
122
- result = await ctx.invoke(ref.kind, ref.name, inputs, { retry: step.retry });
328
+ result = await ctx.invoke(ref.kind, ref.name, inputs);
123
329
  }
124
330
  }
125
-
126
- state.steps[step.name] = { result };
331
+ return result;
127
332
  }
@@ -61,16 +61,19 @@ export type ParsedArgs = Partial<Record<string, string | boolean | string[]>> &
61
61
  /**
62
62
  * Per-call options for a by-name dispatch.
63
63
  *
64
- * A bag rather than a positional context parameter because this slot already
65
- * carried one meaning — `retry`, consumed by the step leaf — and
66
- * `ResourceContext` satisfies `InvokeStepContext` structurally, so a positional
67
- * `InvokeContext` here silently receives a step's retry options instead.
64
+ * A bag rather than a positional context parameter, because `ResourceContext`
65
+ * satisfies `InvokeStepContext` structurally: a positional fourth argument here
66
+ * is reachable from the step leaf's own `invoke` call, so the two surfaces would
67
+ * have to agree on its meaning forever. A named key cannot be filled by accident.
68
+ *
69
+ * `retry` USED to live here and was read by nobody — the step leaf handed it down
70
+ * on one of its four dispatch branches and no kernel path looked at it. It now
71
+ * belongs to the leaf, which is the one place every branch passes through; a key
72
+ * kept here would be a second, inert way to ask for the same thing.
68
73
  */
69
74
  export interface InvokeByNameOptions {
70
75
  /** Seeds the invocation context, replacing the ambient. */
71
76
  ctx?: InvokeContext;
72
- /** Retry policy, read by `executeInvokeStep`. */
73
- retry?: unknown;
74
77
  }
75
78
 
76
79
  export interface ResourceContext extends ControllerContext {
package/src/value-type.ts CHANGED
@@ -44,6 +44,12 @@ export type ValueTypeRepresentation = "json" | "instance";
44
44
  * second parameter can be added without a migration. */
45
45
  export interface ValueTypeParameter {
46
46
  readonly name: string;
47
+ /** This parameter's argument is what ITERATING a value of the type yields.
48
+ * Declared here so "what is the element of this collection" is answered by
49
+ * the vocabulary rather than by a consumer that knows one type's name — the
50
+ * same reason `live` is a field and not a check against `Telo.Stream`. At
51
+ * most one parameter per entry may carry it. */
52
+ readonly element?: boolean;
47
53
  readonly description?: string;
48
54
  }
49
55
 
@@ -136,20 +142,34 @@ function requireString(file: string, node: Record<string, unknown>, key: string)
136
142
  function readParameters(file: string, raw: unknown): ValueTypeParameter[] {
137
143
  if (raw === undefined) return [];
138
144
  if (!Array.isArray(raw)) throw new ValueTypeEntryError(file, "'parameters' must be a sequence");
139
- return raw.map((entry, i) => {
145
+ const params = raw.map((entry, i) => {
140
146
  if (!isPlainObject(entry)) {
141
147
  throw new ValueTypeEntryError(file, `parameters[${i}] must be a mapping`);
142
148
  }
143
149
  for (const key of Object.keys(entry)) {
144
- if (key !== "name" && key !== "description") {
150
+ if (key !== "name" && key !== "description" && key !== "element") {
145
151
  throw new ValueTypeEntryError(file, `parameters[${i}] has no key '${key}'`);
146
152
  }
147
153
  }
154
+ if (entry.element !== undefined && typeof entry.element !== "boolean") {
155
+ throw new ValueTypeEntryError(file, `parameters[${i}].element must be a boolean when present`);
156
+ }
148
157
  const name = requireString(file, entry, "name");
149
- return entry.description === undefined
150
- ? { name }
151
- : { name, description: requireString(file, entry, "description") };
158
+ return {
159
+ name,
160
+ ...(entry.element === true ? { element: true as const } : {}),
161
+ ...(entry.description === undefined
162
+ ? {}
163
+ : { description: requireString(file, entry, "description") }),
164
+ };
152
165
  });
166
+ // Two element parameters would make "the element of this value" ambiguous, and
167
+ // the reader is the only place that can refuse it — every consumer takes the
168
+ // first match and would silently pick one.
169
+ if (params.filter((p) => p.element).length > 1) {
170
+ throw new ValueTypeEntryError(file, "at most one parameter may declare 'element'");
171
+ }
172
+ return params;
153
173
  }
154
174
 
155
175
  /**
@@ -334,6 +354,23 @@ export function isInstanceSlot(schema: unknown): boolean {
334
354
  return valueTypeOf(schema)?.representation === "instance";
335
355
  }
336
356
 
357
+ /**
358
+ * The schema of what iterating a value at this slot yields, or undefined when
359
+ * the slot declares no value type, or one with no element parameter.
360
+ *
361
+ * The whole point of reading it from the entry is that no consumer names a type:
362
+ * a future iterable value type is covered by declaring `element` on its own
363
+ * parameter, with nothing to change here or in the analyzer. An element
364
+ * parameter left unsupplied means *any*, exactly as every other omitted argument
365
+ * does, so an unparameterized use degrades to permissive rather than to nothing.
366
+ */
367
+ export function elementSchemaOf(schema: unknown): unknown | undefined {
368
+ const slot = readValueTypeSlot(schema);
369
+ const parameter = slot?.entry?.parameters.find((p) => p.element);
370
+ if (!parameter) return undefined;
371
+ return slot!.args[parameter.name] ?? {};
372
+ }
373
+
337
374
  /** The binding row for a schema node's declared type, or undefined when it
338
375
  * declares none / declares a `json` one. */
339
376
  export function bindingOf(schema: unknown): ValueTypeBinding | undefined {
@@ -7,6 +7,7 @@
7
7
  "parameters": [
8
8
  {
9
9
  "name": "of",
10
+ "element": true,
10
11
  "description": "The element the stream yields. Any schema node — an inline shape, a value type, a `!ref` to a named shape, or another parameterized type. Omitted means any element."
11
12
  }
12
13
  ],