fyflow-orchestration 0.9.1 → 0.11.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.
- package/AGENTS.md +322 -29
- package/README.md +1 -1
- package/dist/browser/chunks/{chunk-I3KDEOLB.js → chunk-5CVSWDCB.js} +442 -111
- package/dist/browser/index.js +541 -3
- package/dist/browser/scheduler/index.js +13 -2
- package/dist/node/chunks/{chunk-I3KDEOLB.js → chunk-5CVSWDCB.js} +442 -111
- package/dist/node/index.js +541 -3
- package/dist/node/scheduler/index.js +13 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/src/definition/definition-schema.d.ts +23 -0
- package/dist/types/src/definition/types.d.ts +2 -1
- package/dist/types/src/definition/validate.d.ts +41 -2
- package/dist/types/src/definition/wiring.d.ts +10 -0
- package/dist/types/src/driver/driver.d.ts +161 -11
- package/dist/types/src/fold/run-state.d.ts +17 -0
- package/dist/types/src/scheduler/runtime.d.ts +17 -1
- package/package.json +2 -1
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
derivePool,
|
|
12
12
|
emptyState,
|
|
13
13
|
groupsNamedBy,
|
|
14
|
+
inputOf,
|
|
14
15
|
isDerivation,
|
|
15
16
|
isTerminal,
|
|
16
17
|
languagesOf,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
retryPolicyProblem,
|
|
26
27
|
validateDefinition,
|
|
27
28
|
workersOf
|
|
28
|
-
} from "../chunks/chunk-
|
|
29
|
+
} from "../chunks/chunk-5CVSWDCB.js";
|
|
29
30
|
|
|
30
31
|
// src/scheduler/bootstrap.ts
|
|
31
32
|
import { WorkerManager } from "fyflow-scheduler";
|
|
@@ -350,6 +351,12 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
350
351
|
return p;
|
|
351
352
|
}
|
|
352
353
|
} : view;
|
|
354
|
+
const reading = {
|
|
355
|
+
...registry,
|
|
356
|
+
joinPolicy: startOptions.joinPolicy ?? liveToken,
|
|
357
|
+
...options.defaultRetryPolicy !== void 0 ? { defaultRetryPolicy: options.defaultRetryPolicy } : {},
|
|
358
|
+
...options.validateBoundaries !== void 0 ? { validateBoundaries: options.validateBoundaries } : {}
|
|
359
|
+
};
|
|
353
360
|
const driver = new Driver(flow, {
|
|
354
361
|
runId,
|
|
355
362
|
scheduler: scheduler2,
|
|
@@ -408,7 +415,11 @@ function createRuntime(document, code = {}, options = {}) {
|
|
|
408
415
|
catchUp();
|
|
409
416
|
const entry = acc.entry(taskId);
|
|
410
417
|
return entry === void 0 ? null : freezeDeep(entry);
|
|
411
|
-
}
|
|
418
|
+
},
|
|
419
|
+
// **`RN-R45` — the reading, over this run's own events** (W205, P42 decision 7): the export, handed the options this
|
|
420
|
+
// function built the run's driver with — `registry`, its `joinPolicy`, and the two runtime-wide ones — so a reading
|
|
421
|
+
// builds the driver `start` built. The live driver is only READ (`checkpoint()`, a copy).
|
|
422
|
+
inputOf: (target) => inputOf(flow, reading, driver.checkpoint(), target)
|
|
412
423
|
};
|
|
413
424
|
}
|
|
414
425
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./src/definition/regions.js";
|
|
|
3
3
|
export * from "./src/definition/validate.js";
|
|
4
4
|
export * from "./src/definition/schema.js";
|
|
5
5
|
export * from "./src/definition/registry.js";
|
|
6
|
+
export * from "./src/definition/definition-schema.js";
|
|
6
7
|
export * from "./src/definition/witness.js";
|
|
7
8
|
export * from "./src/definition/wiring.js";
|
|
8
9
|
export * from "./src/definition/assist.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Registry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* **What `definitionSchema()` returns** — a plain, JSON-serialisable JSON Schema draft 2020-12 document, owned by the
|
|
4
|
+
* caller: a new value on every call, so mutating one changes no other.
|
|
5
|
+
*/
|
|
6
|
+
export type DefinitionSchema = {
|
|
7
|
+
$schema: string;
|
|
8
|
+
} & Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* **A flow definition's shape as a JSON Schema, draft 2020-12** — P43 (a1), ADR 0132; the registry form W211's. A
|
|
11
|
+
* plain object, a NEW one on every call, JSON-serialisable.
|
|
12
|
+
*
|
|
13
|
+
* - **No argument: the STATIC shape.** It carries every rule a node, an edge or a value decides by its own shape and
|
|
14
|
+
* names `validateDefinition` as the authority for the rest; what it refuses, `validateDefinition` refuses under
|
|
15
|
+
* EVERY registry.
|
|
16
|
+
* - **A registry: the shape SPECIALISED to it** (`spec/flow-definition.md` section 1, v0.51). The registry is first held
|
|
17
|
+
* to `checkRegistry` — the check `new Driver` runs over the registry it is handed — and one that check refuses THROWS
|
|
18
|
+
* that refusal, never specialised. Then the static shape gains what the registry decides of ONE node: a task's
|
|
19
|
+
* `type`, `pool`, `settings`, a transform-only type's `transform`, `workerGroups`, `transform.lang`, a predicate
|
|
20
|
+
* name, a gate's `type`, `settings` and — for a STATIC gate type — `route` (`specialise`, below). What it refuses,
|
|
21
|
+
* `validateDefinition(flow, registry)` refuses against THAT registry.
|
|
22
|
+
*/
|
|
23
|
+
export declare function definitionSchema(registry?: Registry): DefinitionSchema;
|
|
@@ -38,10 +38,11 @@ export type TerminalState = 'done' | 'failed' | 'user_action' | 'cancelled';
|
|
|
38
38
|
* | `"$payload"` | **the value the RUN entered with** — what `start()` was handed, from any node and any edge (v0.44, [ADR 0117](../../doc/decisions/0117-a-reference-may-name-what-reached-the-run.md)). Always there, so no `?` and no array form. `$` is outside `FD-V16`'s charset, and the whole `$` prefix is reserved: `"$item"` — a map instance's item — is W191's, and any other `$` spelling is `FD-V26` |
|
|
39
39
|
* | `"score"` | that node's output **on this node's own branch path**. Exactly one, or the run stops (`FD-R3`) |
|
|
40
40
|
* | `["score"]` | that node's outputs as an **array**, in ascending segment-wise-numeric `branchPath` order. Legal only where the reader is outside a region containing the named node (`FD-V28`) |
|
|
41
|
+
* | `"score!error"` | **that node's ERROR** rather than its result, on either form and with `?` last (`["score!error?"]`): the effective outcome's `error` string, `null` where it succeeded — v0.49, [ADR 0131](../../doc/decisions/0131-a-reference-may-name-a-nodes-error.md). Aimed as `"score"` is; `!` is outside `FD-V16`'s charset |
|
|
41
42
|
*
|
|
42
43
|
* **The array form uses JSON's own array syntax rather than a sigil**, so the
|
|
43
44
|
* grammar invents tokens only for what has no node id: `"."`, the `?` modifier
|
|
44
|
-
* (v0.13),
|
|
45
|
+
* (v0.13), — v0.44 — the `$` prefix, and — v0.49 — the `!error` part. *(This read *"exactly one invented token"*
|
|
45
46
|
* until v0.13 and stayed so until W190.)* A `string` covers every token, so the
|
|
46
47
|
* TYPE does not move; `referenceTarget`'s parse does.
|
|
47
48
|
*/
|
|
@@ -19,6 +19,19 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { Finding, FlowDefinition, GateType, GateTypes, JsonSchema, StaticGateType, LanguageAdapter, Languages, NodeTypeEntry, NodeTypes, Pools, Registry, WorkerEntry, Workers } from './types.js';
|
|
21
21
|
export { readKeyPath } from './condition.js';
|
|
22
|
+
declare const KINDS: readonly ["task", "fanout", "map", "join", "end", "gate", "ask"];
|
|
23
|
+
/**
|
|
24
|
+
* **`FD-V16`'s charset — a flow's and a node's id** ([spec/task-id.md](../../spec/task-id.md)'s) — v0.50 (W210,
|
|
25
|
+
* [ADR 0132](../../doc/decisions/0132-a-definitions-shape-is-a-schema-built-from-the-validator.md)), so
|
|
26
|
+
* `definitionSchema()` builds its `pattern` from the validator's expression. **A COPY** — `compile()` on it, or a
|
|
27
|
+
* consumer's edit, moves no verdict (W210's conformity reader, S2: the live object did).
|
|
28
|
+
*/
|
|
29
|
+
declare const ID_RE_COPY: RegExp;
|
|
30
|
+
/** **The seven node kinds `FD-V24` admits** — v0.50 (W210, ADR 0132), for `definitionSchema()`'s `kind` enum; a FROZEN copy. */
|
|
31
|
+
declare const KINDS_COPY: readonly (typeof KINDS)[number][];
|
|
32
|
+
export { ID_RE_COPY as ID_RE, KINDS_COPY as KINDS };
|
|
33
|
+
/** **The terminal states an edge's `on` may name** (`FD-V18`) — a copy of the validator's own list, v0.50 (W210, ADR 0132). */
|
|
34
|
+
export declare const ROUTABLE_STATES: readonly string[];
|
|
22
35
|
/**
|
|
23
36
|
* **The six keys a `task` declares that an `ask` REFUSES, each by `FD-V47` and by nothing else** — v0.42, W179,
|
|
24
37
|
* P35 stop-and-ask (a) and decision 6. Every one is read by a task's WORKER or its scheduler task — a type and its
|
|
@@ -49,6 +62,17 @@ export declare const TASK_KEYS_DECLARED: readonly string[];
|
|
|
49
62
|
* P16 decision 3's silent-key hole one row over.
|
|
50
63
|
*/
|
|
51
64
|
export declare const NODE_KEYS_DECLARED: Readonly<Record<string, readonly string[]>>;
|
|
65
|
+
/**
|
|
66
|
+
* **The REQUIRED half of each kind's keys, the edge's and the document's** — `FD-V23`'s three lists, copied, v0.50
|
|
67
|
+
* (W210, [ADR 0132](../../doc/decisions/0132-a-definitions-shape-is-a-schema-built-from-the-validator.md)):
|
|
68
|
+
* `definitionSchema()` reads its `required` from these, so a key the validator starts requiring is one the schema
|
|
69
|
+
* requires, and a key it stops requiring one the schema stops requiring. Every document key is required.
|
|
70
|
+
*/
|
|
71
|
+
export declare const NODE_KEYS_REQUIRED: Readonly<Record<string, readonly string[]>>;
|
|
72
|
+
/** The edge's required keys — `FD-V23`'s *an edge without `to`*, copied (v0.50, W210). */
|
|
73
|
+
export declare const EDGE_KEYS_REQUIRED: readonly string[];
|
|
74
|
+
/** The document's keys, every one required (`FD-V23`) and none other declared (`FD-V17`) — copied, v0.50 (W210). */
|
|
75
|
+
export declare const DOC_KEYS_DECLARED: readonly string[];
|
|
52
76
|
/**
|
|
53
77
|
* **The two keys a `retryPolicy` carries, and no others** — `FD-V30`. The scheduler's own
|
|
54
78
|
* constructor at 0.5.2 destructures exactly these; a key beside them is a typo the scheduler
|
|
@@ -324,6 +348,12 @@ export declare function settingsProblem(schema: JsonSchema | undefined, defaults
|
|
|
324
348
|
* reads `conditional`. A `"."` may not carry one — it resolves to the edge's own
|
|
325
349
|
* value, which is always there — and the remainder after the suffix is stripped must
|
|
326
350
|
* still be an id, so `"?"`, `"n??"` and `".?"` are all `null` with no extra clause.
|
|
351
|
+
*
|
|
352
|
+
* **And since v0.49 the `!error` PART** — [ADR 0131](../../doc/decisions/0131-a-reference-may-name-a-nodes-error.md),
|
|
353
|
+
* P43 (c1): `"n!error"` names node `n`'s ERROR rather than its result, in both forms and with `?` (`"n!error?"`,
|
|
354
|
+
* `["n!error"]`, `["n!error?"]`). It is a fourth field, `part`, beside `form` and `conditional` and orthogonal to both —
|
|
355
|
+
* **present only on that form** — because it changes WHAT is read of the outputs and never which outputs are named:
|
|
356
|
+
* `FD-V27`-`FD-V29` aim it as the plain reference and the driver resolves it through the same index (P43 decision 5).
|
|
327
357
|
*/
|
|
328
358
|
export declare function referenceTarget(ref: unknown): {
|
|
329
359
|
form: 'self';
|
|
@@ -337,11 +367,20 @@ export declare function referenceTarget(ref: unknown): {
|
|
|
337
367
|
form: 'item';
|
|
338
368
|
node: null;
|
|
339
369
|
conditional: false;
|
|
340
|
-
} |
|
|
370
|
+
} | AimedReference | null;
|
|
371
|
+
/**
|
|
372
|
+
* **A reference that AIMS at a node** — `"n"` or `["n"]`, either with `?`, and since v0.49 either naming the node's ERROR
|
|
373
|
+
* (`"n!error"`, `["n!error?"]`, …) — [ADR 0131](../../doc/decisions/0131-a-reference-may-name-a-nodes-error.md), P43 (c1).
|
|
374
|
+
* **`part` is PRESENT only on the error form**, so every spelling before v0.49 parses to the object it parsed to (P43
|
|
375
|
+
* prediction 3), and a reader that does not ask for `part` reads the error form as the node it aims at — which is what
|
|
376
|
+
* `FD-V27`-`FD-V29` are meant to do (P43 decision 5) and what a RESOLVER must not (`resolveReference`, the wiring check).
|
|
377
|
+
*/
|
|
378
|
+
export type AimedReference = {
|
|
341
379
|
form: 'one' | 'all';
|
|
342
380
|
node: string;
|
|
343
381
|
conditional: boolean;
|
|
344
|
-
|
|
382
|
+
part?: 'error';
|
|
383
|
+
};
|
|
345
384
|
export declare function validateDefinition(doc: FlowDefinition, registry: Registry): Finding[];
|
|
346
385
|
/** Convenience: the rule ids violated, deduplicated and sorted. */
|
|
347
386
|
export declare function violatedRules(findings: Finding[]): string[];
|
|
@@ -127,7 +127,17 @@ export interface WiringVerdict {
|
|
|
127
127
|
/** The relation's own reason, in the words `fact-3-controls.log` prints. */
|
|
128
128
|
why: string;
|
|
129
129
|
sample?: WiringSample;
|
|
130
|
+
/**
|
|
131
|
+
* **Why a `transform-fails` verdict failed — present on that kind and ABSENT on every other** (v0.48,
|
|
132
|
+
* [W208](../../doc/work-items/w208-a-transforms-cause-and-when-an-ask-was-asked.md), P43 (d1), decision 6): `'threw'` where the transform did not produce a result — the language's worker half failing to import, having
|
|
133
|
+
* no default export, its `compile` throwing or its `evaluate` throwing, alike — and `'schema'` where it produced one
|
|
134
|
+
* and the consumer's `inputSchema` refused it. `why` says the same in words and is unmoved; this says it as data.
|
|
135
|
+
* Additive in 0.11.0: `VerdictKind`, `VERDICT_KINDS` and every count are unmoved.
|
|
136
|
+
*/
|
|
137
|
+
cause?: TransformFailureCause;
|
|
130
138
|
}
|
|
139
|
+
/** What a `transform-fails` verdict's {@link WiringVerdict.cause} names: the transform threw, or its result failed the consumer's schema. */
|
|
140
|
+
export type TransformFailureCause = 'threw' | 'schema';
|
|
131
141
|
export interface WiringReport {
|
|
132
142
|
flowId: string;
|
|
133
143
|
verdicts: WiringVerdict[];
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* driver turns out to need a second copy of run truth, that is a finding about the
|
|
13
13
|
* spec rather than a thing to build.
|
|
14
14
|
*/
|
|
15
|
-
import type { Finding, FlowDefinition, GateTypes, Languages, NodeTypes, Pools, TerminalState, Workers } from '../definition/types.js';
|
|
15
|
+
import type { Finding, FlowDefinition, FlowNode, GateTypes, Languages, NodeTypes, Pools, TerminalState, Workers } from '../definition/types.js';
|
|
16
16
|
import { type SchemaFinding } from '../definition/schema.js';
|
|
17
17
|
import { type LogRecord, type OutcomeRecord } from './log.js';
|
|
18
18
|
import { type PredicateRegistry } from './routing.js';
|
|
@@ -377,6 +377,68 @@ export declare class ResumeRefused extends Error {
|
|
|
377
377
|
* function, two callers, so the two cannot refuse different checkpoints.
|
|
378
378
|
*/
|
|
379
379
|
export declare function resumeRefusal(definition: FlowDefinition, checkpoint: unknown, runId?: string): ResumeRefused | null;
|
|
380
|
+
/**
|
|
381
|
+
* **What a reading asks about** — `RN-R45`, P42 decision 2 and stop-and-ask (c1). A node id and a branch path, for ANY node
|
|
382
|
+
* kind the definition has; a node id ALONE, which asks for every path the run reached it on (decision 4); or a task id —
|
|
383
|
+
* a task's or an ask's — which names both, as `spec/task-id.md` mints it (attempt `0`).
|
|
384
|
+
*/
|
|
385
|
+
export type InputTarget = {
|
|
386
|
+
readonly taskId: string;
|
|
387
|
+
} | {
|
|
388
|
+
readonly nodeId: string;
|
|
389
|
+
readonly branchPath?: string;
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* **Which value a node was handed** — `RN-R45`, P42 decision 3: a task's `envelope` (`{ settings, input, transform? }`,
|
|
393
|
+
* `FD-C5`, `FD-C6` — what `dispatch` built for `createTask`, before any transform); the value `enter` composed for an ask,
|
|
394
|
+
* a gate, a fanout, a map or an end (`received`); and for a JOIN, an `arrival` at an arriving branch's path — what that
|
|
395
|
+
* branch carried in, a LATE arrival after an early firing included — or the `collection` it handed on when it fired, at
|
|
396
|
+
* its opener's path.
|
|
397
|
+
*/
|
|
398
|
+
export type HandedAs = 'envelope' | 'received' | 'arrival' | 'collection';
|
|
399
|
+
/** **One thing a node was handed, on one branch path** — `RN-R45`. */
|
|
400
|
+
export interface NodeInput {
|
|
401
|
+
readonly nodeId: string;
|
|
402
|
+
readonly kind: FlowNode['kind'];
|
|
403
|
+
/** The branch path it was handed on — a task's and an ask's the one their id carries; a join's arrival the arriving branch's. */
|
|
404
|
+
readonly branchPath: string;
|
|
405
|
+
readonly handed: HandedAs;
|
|
406
|
+
/**
|
|
407
|
+
* The value, AS THE READING'S DRIVER BUILT IT: not copied again and not frozen beyond what the composition already
|
|
408
|
+
* froze — a value a `needs` reference composed is that driver's shared frozen copy (ADR 0126), the rest is the caller's
|
|
409
|
+
* own. It reaches nothing a live run holds: the reading's driver is its own and is dropped when the reading returns.
|
|
410
|
+
*/
|
|
411
|
+
readonly value: unknown;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* **Why a reading has no answer** — `RN-R45`, P42 decision 5. Returned, never thrown.
|
|
415
|
+
*
|
|
416
|
+
* | reason | when | `index` |
|
|
417
|
+
* |---|---|---|
|
|
418
|
+
* | `format` | the run is not `{ header, events[] }`, its `header.format` is not {@link CHECKPOINT_FORMAT}, its header's three fields are not format 1's types, its `runId` is not a run id (`ID-R2`), or its entry payload does not clone — `resumeRefusal`'s header half, the one function | `null` |
|
|
419
|
+
* | `flow` | `header.flowId` or `header.version` is not the definition's — no migration | `null` |
|
|
420
|
+
* | `unknown` | the target cannot name anything in this run: a node id the definition lacks, a branch path that is not `spec/task-id.md`'s grammar, or a task id that does not parse, names another run, an attempt other than `0`, or a node that is neither a task nor an ask | `null` |
|
|
421
|
+
* | `not-reached` | the replay did not hand the target anything within the stored prefix a reading verifies — which ends at the first `halted`, `stopped` or `cancelRequested` element, or at the stored events' end | the prefix's length |
|
|
422
|
+
* | `diverged` | the replay produced something other than a stored element BEFORE it reached the target — `RN-R20`'s refusal, by its position | that position |
|
|
423
|
+
*/
|
|
424
|
+
export type InputRefusalReason = 'format' | 'flow' | 'unknown' | 'not-reached' | 'diverged';
|
|
425
|
+
/** **What a reading returns** — every answer for its target, in the order the replay reached them, or why there is none. */
|
|
426
|
+
export type InputReading = {
|
|
427
|
+
readonly ok: true;
|
|
428
|
+
readonly inputs: readonly NodeInput[];
|
|
429
|
+
} | {
|
|
430
|
+
readonly ok: false;
|
|
431
|
+
readonly reason: InputRefusalReason;
|
|
432
|
+
readonly index: number | null;
|
|
433
|
+
readonly message: string;
|
|
434
|
+
};
|
|
435
|
+
/**
|
|
436
|
+
* **What a reading builds its driver from** — the `DriverOptions` a run's routing reads, and nothing else (`RN-R45`, P42
|
|
437
|
+
* decision 1): the registry's members — `runtime.registry` IS this minus `joinPolicy` — and the three options a runtime
|
|
438
|
+
* adds. **No callback, no scheduler, no `createTask`, no run id**: a member not named here is not read, so a reading cannot
|
|
439
|
+
* call a consumer. `joinPolicy` omitted is `liveToken`, as `runtime.start` and `runtime.resume` default it.
|
|
440
|
+
*/
|
|
441
|
+
export type InputOfOptions = Pick<DriverOptions, 'predicates' | 'workers' | 'nodeTypes' | 'pools' | 'groups' | 'languages' | 'gateTypes' | 'joinPolicy' | 'defaultRetryPolicy' | 'validateBoundaries'>;
|
|
380
442
|
/**
|
|
381
443
|
* A settlement carrying THIS driver's run id for a task it never dispatched —
|
|
382
444
|
* `ID-R4`, and the inbound counterpart of `ID-R1`
|
|
@@ -1054,7 +1116,8 @@ export declare class Driver {
|
|
|
1054
1116
|
* - **`held`** — map id → the instance's branch path → its item. Written by the MAP ARM, where the instance opens —
|
|
1055
1117
|
* by a run and by a resume's replay alike, so a replay REBUILDS it (`RN-R19`; P38's brief: *taken at rest*, never by
|
|
1056
1118
|
* a caller's call) — **BY REFERENCE to the element the map received** (the driver's own: an edge-hop clone, or a
|
|
1057
|
-
* `needs` composition's
|
|
1119
|
+
* `needs` composition's value — since W202 an element of a reference's SHARED copy, which this keeps alive while held),
|
|
1120
|
+
* and as its OWN copy only where `kept` says that element is the caller's
|
|
1058
1121
|
* (P35 (f)'s reason for `asked.input`). Every READ clones (`JP-R11`), so no node receives the held object.
|
|
1059
1122
|
* **Released when NOTHING of that instance may still be entered** — its own branch terminal (`branch.arrived`,
|
|
1060
1123
|
* `branch.lost` or `branch.exited`, `JP-R5`: one per opened branch) AND no branch of a region NESTED in it still live
|
|
@@ -1397,10 +1460,47 @@ export declare class Driver {
|
|
|
1397
1460
|
private replayStored;
|
|
1398
1461
|
/** One fed element — a settlement, a resolution, an earlier resume, a stop, a pause, an unpause or an attempt — at stored position `i`. */
|
|
1399
1462
|
private feedOne;
|
|
1400
|
-
/**
|
|
1463
|
+
/**
|
|
1464
|
+
* The listener's boundary, for the replay: a refusal routing raises stops the run (`OR-R10`); a refused resume passes
|
|
1465
|
+
* through — and so does a reading's cut (`InputCut`, W205), which is no refusal and halts nothing.
|
|
1466
|
+
*/
|
|
1401
1467
|
private feed;
|
|
1402
1468
|
/** `RN-R20`'s refusal, naming the position and both elements there. */
|
|
1403
1469
|
private diverged;
|
|
1470
|
+
/**
|
|
1471
|
+
* **A cut site offers what a node was handed** (`RN-R45`, W205). An offer for another path is dropped. `own` is the
|
|
1472
|
+
* position of the node's OWN element where it is already appended — a task's `dispatched`, a join's firing — and `null`
|
|
1473
|
+
* where it is the NEXT append, which `append` confirms (`Reading.pending`). Either way it is TAKEN only at a STORED
|
|
1474
|
+
* position, so a composition whose element lies past the prefix the reading verifies is never answered.
|
|
1475
|
+
*/
|
|
1476
|
+
private offer;
|
|
1477
|
+
/** **An offer taken** — kept, and for a target with a path the replay ends HERE: nothing after it is replayed (`InputCut`). */
|
|
1478
|
+
private take;
|
|
1479
|
+
/**
|
|
1480
|
+
* **A READING — what a node was handed, by replaying the run to that node's composition** — `RN-R45`, W205,
|
|
1481
|
+
* [ADR 0128](../../doc/decisions/0128-what-a-node-was-handed-is-read-by-replaying-the-run-to-its-composition.md), P42
|
|
1482
|
+
* decisions 1-7. The export {@link inputOf} and `RunHandle.inputOf` are this and nothing else. In order:
|
|
1483
|
+
*
|
|
1484
|
+
* 1. **The header**, refused by `resumeRefusal`'s own header half (`format`, `flow`) — never its halt half.
|
|
1485
|
+
* 2. **The target**, refused `unknown` where it cannot name anything in this run.
|
|
1486
|
+
* 3. **A driver built exactly as `runtime.resume` builds one** (`RN-R25`) — the definition, the registry's members, a
|
|
1487
|
+
* `joinPolicy` defaulting to `liveToken` — with NO callback and a scheduler that is handed nothing (`DEAF_SCHEDULER`).
|
|
1488
|
+
* 4. **The replay** (`readStep`) over the stored PREFIX it can verify, the cut armed: it stops at the target's
|
|
1489
|
+
* composition and returns what it found.
|
|
1490
|
+
*
|
|
1491
|
+
* Private and STATIC, so the export and the handle reach one road, and a test reaches the reading's own driver — its
|
|
1492
|
+
* count — by a cast (ADR 0121 rule 7): `driver` is `null` where a refusal came before one was built.
|
|
1493
|
+
*/
|
|
1494
|
+
private static read;
|
|
1495
|
+
/**
|
|
1496
|
+
* **The reading's replay** — `resume()`'s first half (`resumeStep` to `replayStored`) with the cut armed, and nothing of
|
|
1497
|
+
* its second: no in-flight set, no `resumed`, nothing handed on. **The prefix it verifies ENDS at the first `halted`,
|
|
1498
|
+
* `stopped` or `cancelRequested` element** (P42 decision 5): after one a halted driver composes nothing more, whether the
|
|
1499
|
+
* replay regenerates it (a predicate's throw) or cannot (a consumer's callback, a refused result, an owner's call) —
|
|
1500
|
+
* what follows is `not-reached`, never `diverged` (P42 retro (a1) corrected the reason). Everything
|
|
1501
|
+
* before is compared as `RN-R20` compares it.
|
|
1502
|
+
*/
|
|
1503
|
+
private readStep;
|
|
1404
1504
|
/**
|
|
1405
1505
|
* **What is IN FLIGHT: dispatched, and no settlement in the log** — `RN-R21`, in dispatch order. A
|
|
1406
1506
|
* `user_action` settlement IS a settlement: a parked task is waiting on a person, not on a worker, and
|
|
@@ -1705,7 +1805,8 @@ export declare class Driver {
|
|
|
1705
1805
|
* described **today** before this method existed, and this adds **no retention**.
|
|
1706
1806
|
* *(Q19 is RESOLVED since W53 —
|
|
1707
1807
|
* [ADR 0046](../../doc/decisions/0046-a-run-retains-what-its-tasks-returned-and-a-region-releases-its-collection-when-its-join-becomes-terminal.md)
|
|
1708
|
-
* — and `RN-R13` names `outputsCache` by id: it holds results **by reference
|
|
1808
|
+
* — and `RN-R13` names `outputsCache` by id: it holds results **by reference** — and since W202 (ADR 0126) one shared
|
|
1809
|
+
* COPY per output a reference has read, the one thing in it that is not the log's object,
|
|
1709
1810
|
* asserted by IDENTITY at ladder L2 rather than by a byte count, because a
|
|
1710
1811
|
* reachability SET cannot tell an alias from a copy. W53's readers found that
|
|
1711
1812
|
* from both ends — the rule's stated test returned a third name on a `needs`
|
|
@@ -1733,9 +1834,14 @@ export declare class Driver {
|
|
|
1733
1834
|
* node or an edge that declares a `needs` asks, and a run that declares none never builds it.
|
|
1734
1835
|
*/
|
|
1735
1836
|
private outputs;
|
|
1837
|
+
/**
|
|
1838
|
+
* The projection, made empty on first need and NOT caught up — `outputs()` catches it up; a `"$payload"` read, which
|
|
1839
|
+
* reads no output, only needs somewhere to keep its shared copy (W202).
|
|
1840
|
+
*/
|
|
1841
|
+
private cache;
|
|
1736
1842
|
/**
|
|
1737
1843
|
* **One task's entry, made what its effective outcome now says** — `outputs()`'s catch-up, for a task one of whose records
|
|
1738
|
-
* was just read. An entry is `{ path, result }` with `result` the record's own object (`RN-R13`); a node's list is kept
|
|
1844
|
+
* was just read. An entry is `{ path, result, error }` with `result` the record's own object (`RN-R13`) and `error` its string or `null` (v0.49, ADR 0131); a node's list is kept
|
|
1739
1845
|
* in its tasks' FIRST-RECORD order, the order the rebuild grouped them in.
|
|
1740
1846
|
*
|
|
1741
1847
|
* **`!eff` — a suspended node contributes no entry.** The first record of a `user_action` task has none, and its
|
|
@@ -1747,6 +1853,10 @@ export declare class Driver {
|
|
|
1747
1853
|
* that LOSES its effective outcome is removed — **reachable**: `ID-R4` asks only whether a settlement's id was dispatched,
|
|
1748
1854
|
* so a settlement event repeated for a settled task is appended, and a `done` then a `user_action` leaves the task with
|
|
1749
1855
|
* none (W195's conformity reader, C4; `test/driver/log-view.test.ts` drives it and the resolution that places it again).
|
|
1856
|
+
*
|
|
1857
|
+
* **Both a replacement and a removal let go of the entry OBJECT, and with it the entry's shared copy** (W202, ADR 0126,
|
|
1858
|
+
* `OutputEntry.shared`): a replaced output's next read makes a new copy of the new result, and a removed one's copy is
|
|
1859
|
+
* held nowhere — `RN-R13`'s v0.33 term, released with what it belongs to (ADR 0046).
|
|
1750
1860
|
*/
|
|
1751
1861
|
private place;
|
|
1752
1862
|
/**
|
|
@@ -1777,7 +1887,8 @@ export declare class Driver {
|
|
|
1777
1887
|
/**
|
|
1778
1888
|
* The projection above, CAUGHT UP to `at` — `RN-R13` v0.30, ADR 0121 (W195). **Not run state and not a second copy of
|
|
1779
1889
|
* it**: `byNode`'s entries hold each result BY REFERENCE (the log record's own object) — so it is a holder of results and
|
|
1780
|
-
*
|
|
1890
|
+
* not a copy of them; since W202 an entry a reference has read also holds its ONE shared copy (`OutputEntry.shared`,
|
|
1891
|
+
* `RN-R13` v0.33), and `payload` the run's payload's — `tasks` is one slot per task id
|
|
1781
1892
|
* read — its node, its path, its first-record position and its current entry — and `orders` the same positions, per
|
|
1782
1893
|
* node, parallel to its list. Everything here is the merge `effectiveOutcome` computed from the task's own records at the moment they were
|
|
1783
1894
|
* read, and `at` says which records have been; so there is nothing here that could disagree with the log. `ADR 0014`'s
|
|
@@ -1808,7 +1919,7 @@ export declare class Driver {
|
|
|
1808
1919
|
*
|
|
1809
1920
|
* **Extracted by [W42](../../doc/work-items/w42-what-a-decision-sees.md) rather
|
|
1810
1921
|
* than copied.** An EDGE's `needs` resolves by exactly these rules — same grammar,
|
|
1811
|
-
* same visibility predicate, same order, same
|
|
1922
|
+
* same visibility predicate, same order, same shared copy (since W202, ADR 0126) — and the ONE difference
|
|
1812
1923
|
* is that an edge has no `"."` form, which is a property of the caller rather than
|
|
1813
1924
|
* of this expression. *Two callers, one resolution, so a change to what a reference
|
|
1814
1925
|
* means cannot mean two different things to a worker and to a predicate*, which is
|
|
@@ -1819,12 +1930,21 @@ export declare class Driver {
|
|
|
1819
1930
|
* @throws UnresolvedReference — `FD-R3`.
|
|
1820
1931
|
*/
|
|
1821
1932
|
private resolveReference;
|
|
1933
|
+
/** **An output entry's shared copy**, made at its first read and kept on the entry (`OutputEntry.shared`) — counted. */
|
|
1934
|
+
private sharedOf;
|
|
1935
|
+
/**
|
|
1936
|
+
* **`"$payload"`'s value in a run: the run's ONE shared copy** — W202, ADR 0126, P41 decision 2: `sharedCopy` of the
|
|
1937
|
+
* header's `entryPayload` (`runPayload()`), made at the first `"$payload"` read of the run — or of a resume, whose driver
|
|
1938
|
+
* is a new one — and handed to every reader at both sites. Counted once. `FD-R4` does not come here: it composes from
|
|
1939
|
+
* the caller's own value, before a header exists, and keeps its clone.
|
|
1940
|
+
*/
|
|
1941
|
+
private sharedPayload;
|
|
1822
1942
|
/**
|
|
1823
1943
|
* **The value the run ENTERED with — the header's copy** (`RN-R15`, P38 fact 4 and (f)): taken at `start()` from the
|
|
1824
1944
|
* caller's payload, or on a resume from the stored header, and held for the life of the run. What a `"$payload"`
|
|
1825
1945
|
* reference composes from, in both doors; `FD-R4` hands the caller's own value instead, because it runs before the
|
|
1826
|
-
* header exists. Read only where a node or an edge composes the token — a run declaring none never
|
|
1827
|
-
* (P38 decision 2). The header is set before the first `enter` in both doors, so `null` here is not a run.
|
|
1946
|
+
* header exists. Read only where a node or an edge composes the token — a run declaring none never copies it
|
|
1947
|
+
* (P38 decision 2); since v0.47 read ONCE, by `sharedPayload`, whose one shared copy every reader is handed. The header is set before the first `enter` in both doors, so `null` here is not a run.
|
|
1828
1948
|
*/
|
|
1829
1949
|
private runPayload;
|
|
1830
1950
|
/**
|
|
@@ -1850,8 +1970,8 @@ export declare class Driver {
|
|
|
1850
1970
|
* received anyway* is `ctx.outcome`, which it already has. So there is no
|
|
1851
1971
|
* `edgeValue` parameter here and none is derivable — see the call site. **A
|
|
1852
1972
|
* `"$payload"` key needs none either** (v0.44, ADR 0117): it is not derived from what
|
|
1853
|
-
* the NODE received but read from the run's header
|
|
1854
|
-
* copy
|
|
1973
|
+
* the NODE received but read from the run's header — since v0.47 the run's ONE shared
|
|
1974
|
+
* copy of it, `sharedPayload()` above, the object a worker's `"$payload"` key is handed (ADR 0126).
|
|
1855
1975
|
*
|
|
1856
1976
|
* @throws UnresolvedReference — `FD-R3`, from the same expression a node's does.
|
|
1857
1977
|
*/
|
|
@@ -2153,3 +2273,33 @@ export declare class Driver {
|
|
|
2153
2273
|
effectiveOutcomeOf(taskId: string): OutcomeRecord | null;
|
|
2154
2274
|
terminalStateOf(taskId: string): TerminalState | null;
|
|
2155
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* **What a node was HANDED, read after the fact by replaying the run to that node's composition** — `RN-R45`, W205,
|
|
2278
|
+
* [ADR 0128](../../doc/decisions/0128-what-a-node-was-handed-is-read-by-replaying-the-run-to-its-composition.md), P42
|
|
2279
|
+
* (a1)-(d1) and decisions 1-6. `run` is a run's stored header and events — `driver.checkpoint()`, or what a store appended
|
|
2280
|
+
* (`RN-R14`, `RN-R18`); `options` the registry and options the run's driver was built with (`runtime.registry`, plus its
|
|
2281
|
+
* `joinPolicy` where it was not `liveToken`).
|
|
2282
|
+
*
|
|
2283
|
+
* **It IS the driver's composition, not a second one**: a driver built for the stored run, deaf — no callback, nothing
|
|
2284
|
+
* handed to a scheduler — replays the stored events through `enter`, `compose` and `dispatch` exactly as a resume does and
|
|
2285
|
+
* stops at the target's composition. So an answer is EXACT, and it costs the run's prefix up to the target — O(run) at
|
|
2286
|
+
* worst, never O(the answer) — and a reading re-runs the run's predicates and gate types, as a resume does (ADR 0002 rule
|
|
2287
|
+
* 4, Q9: an impure one diverges, or answers what its re-run computes).
|
|
2288
|
+
*
|
|
2289
|
+
* | target | kind | `inputs` |
|
|
2290
|
+
* |---|---|---|
|
|
2291
|
+
* | `{ taskId }`, or `{ nodeId, branchPath }` | a task | ONE `envelope`: `{ settings, input, transform? }` — its one dispatch's, retries and a resolution included; BEFORE its transform (ADR 0051) |
|
|
2292
|
+
* | the same | an ask | ONE `received` — equal to its `asked` element's `input` |
|
|
2293
|
+
* | `{ nodeId, branchPath }` | a gate, fanout, map or end | ONE `received` — what `enter` composed for it on that path |
|
|
2294
|
+
* | `{ nodeId, branchPath }` | a join, at an ARRIVING branch's path | ONE `arrival` — what that branch carried in, a late arrival included |
|
|
2295
|
+
* | `{ nodeId, branchPath }` | a join, at its OPENER's path | ONE `collection` — what it handed on when it fired |
|
|
2296
|
+
* | `{ nodeId }` | any | EVERY one of the above the replay reached, in the order it reached them — one replay to the stored end |
|
|
2297
|
+
*
|
|
2298
|
+
* A path target answers the FIRST composition on its path: an end shared by two sibling regions can be reached twice on one
|
|
2299
|
+
* path (`end` is exempt from `FD-V19`), and only the node id alone lists both.
|
|
2300
|
+
*
|
|
2301
|
+
* **Never throws for the run or the target** — `InputReading`'s refusals; it throws what `new Driver` throws for a
|
|
2302
|
+
* definition or a registry that cannot run (`InvalidDefinition`, `MissingJoinPolicy`, a `RegistryError`), as a resume does.
|
|
2303
|
+
* **What it does not detect**: a definition or a registry changed AFTER the target — the cut verifies the prefix it replays.
|
|
2304
|
+
*/
|
|
2305
|
+
export declare function inputOf(flow: FlowDefinition, options: InputOfOptions, run: Checkpoint, target: InputTarget): InputReading;
|
|
@@ -124,6 +124,14 @@ export interface NodeRun {
|
|
|
124
124
|
* additive: a consumer building a `NodeRun` literal is unaffected.
|
|
125
125
|
*/
|
|
126
126
|
input?: unknown;
|
|
127
|
+
/**
|
|
128
|
+
* **WHEN an ask was asked** — `RN-R36`, v0.35,
|
|
129
|
+
* [ADR 0130](../../doc/decisions/0130-a-regenerated-asked-element-keeps-the-stored-runs-time.md), W208, P43 (e1): its
|
|
130
|
+
* `asked` element's `observedAt`, KEPT after the answer as {@link NodeRun.input} is. **Present only on an ask's node, and
|
|
131
|
+
* only where its element carried one** — an element written before 0.11.0 carries none, so its entry has none. OPTIONAL
|
|
132
|
+
* and additive.
|
|
133
|
+
*/
|
|
134
|
+
askedAt?: number;
|
|
127
135
|
}
|
|
128
136
|
export interface RegionRun {
|
|
129
137
|
/** Branch paths opened, in the order the driver emitted them (`JP-R5`). */
|
|
@@ -351,11 +359,20 @@ export type FoldInput = {
|
|
|
351
359
|
* (`RN-R19`): the replay re-enters the ask and compares the input it composes with the stored one (`RN-R20`). **The
|
|
352
360
|
* first kind whose payload is a value of ANY shape**, so it is deep-copied on the way out (`freezeInput`, `RN-R17`).
|
|
353
361
|
* **A new kind, BREAKING in 0.8.0 for a consumer switching on `kind` exhaustively** (P35 decision 8).
|
|
362
|
+
*
|
|
363
|
+
* **`observedAt` — WHEN the ask was asked** (v0.35, `RN-R37`,
|
|
364
|
+
* [ADR 0130](../../doc/decisions/0130-a-regenerated-asked-element-keeps-the-stored-runs-time.md), W208, P43 (e1)): the
|
|
365
|
+
* driver's injected `now` where the branch entered the ask — the clock an outcome record's `observedAt` is read from.
|
|
366
|
+
* **A replay KEEPS the stored run's** (`RN-R19`): within the stored prefix the regenerated element takes the stored
|
|
367
|
+
* element's `observedAt`, or carries none where the stored one carries none, so it compares equal (`RN-R20`); past the
|
|
368
|
+
* stored tail it is `now()`. **OPTIONAL**: an element written before 0.11.0 has none, and a consumer building one
|
|
369
|
+
* literally is unaffected. `CHECKPOINT_FORMAT` does not move.
|
|
354
370
|
*/
|
|
355
371
|
| {
|
|
356
372
|
kind: 'asked';
|
|
357
373
|
taskId: string;
|
|
358
374
|
input: unknown;
|
|
375
|
+
observedAt?: number;
|
|
359
376
|
} | {
|
|
360
377
|
kind: 'pathEnded';
|
|
361
378
|
reason: 'end' | 'no-eligible-edge';
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
import { FyflowScheduler } from 'fyflow-scheduler';
|
|
65
65
|
import type { ResourceGroup, WorkerManager } from 'fyflow-scheduler';
|
|
66
66
|
import { Driver } from '../driver/driver.js';
|
|
67
|
-
import type { Checkpoint, CheckpointHeader, DriverOptions, RunStopOptions } from '../driver/driver.js';
|
|
67
|
+
import type { Checkpoint, CheckpointHeader, DriverOptions, InputReading, InputTarget, RunStopOptions } from '../driver/driver.js';
|
|
68
68
|
import type { NodeRun, RunOutcome, RunState } from '../fold/run-state.js';
|
|
69
69
|
import type { Finding, FlowDefinition, JsonSchema, RegistryCode, RegistryDocument, ResolvedPool, ResolvedRegistry, RetryPolicy, TaskNode } from '../definition/types.js';
|
|
70
70
|
import type { PoolFactory } from './bootstrap.js';
|
|
@@ -361,6 +361,22 @@ export interface RunHandle {
|
|
|
361
361
|
* materialising the rest (`RN-R44`); the first read of an entry freezes it once, O(the entry). `null` for a task the run never dispatched or asked, whatever the id names (`toString` included).
|
|
362
362
|
*/
|
|
363
363
|
node(taskId: string): NodeRun | null;
|
|
364
|
+
/**
|
|
365
|
+
* **What a node of this run was HANDED, read by replaying the run so far to that node's composition** — `RN-R45`, W205,
|
|
366
|
+
* [ADR 0128](../../doc/decisions/0128-what-a-node-was-handed-is-read-by-replaying-the-run-to-its-composition.md), P42
|
|
367
|
+
* decision 7: the root export {@link inputOf} over THIS run's own `driver.checkpoint()`, with the registry, `joinPolicy`
|
|
368
|
+
* and options this runtime built the run's driver with — one function, so a live answer and a stored one cannot differ.
|
|
369
|
+
* A task's answer is its dispatch's envelope `{ settings, input, transform? }`; an ask's, a gate's, a fanout's, a map's
|
|
370
|
+
* and an end's what reached it; a join's an arrival at an arriving branch's path or its collection at its opener's; a
|
|
371
|
+
* node id ALONE every path the run reached it on. A target not yet reached is `not-reached`.
|
|
372
|
+
*
|
|
373
|
+
* **Synchronous, O(the run's prefix), never cached**: each call copies the run's events (`checkpoint()`) and replays them
|
|
374
|
+
* on a driver of its own, deaf — nothing is appended to this run, nothing handed to a scheduler, no callback called, and
|
|
375
|
+
* no object of this run's reachable through an answer. Read it at a quiescent point (`RN-R12`); mid-step it reads the
|
|
376
|
+
* prefix appended so far. On a released run (`done` settled) it answers from what the run delivered. **BREAKING in
|
|
377
|
+
* 0.10.0 for code that BUILDS a `RunHandle`** (P42 decision 9) — a new member, as `state` was in 0.9.0.
|
|
378
|
+
*/
|
|
379
|
+
inputOf(target: InputTarget): InputReading;
|
|
364
380
|
}
|
|
365
381
|
export interface Runtime {
|
|
366
382
|
/** **Exactly the object this runtime hands its own drivers** (decision 14) — spread it into `new Driver(…)` beside a `runId`, a `scheduler` and a `joinPolicy` and you have built what `start` builds. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fyflow-orchestration",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Flow orchestration over fyflow-scheduler: sequence, decision, fan-out and join",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"workflow",
|
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
},
|
|
125
125
|
"license": "MIT",
|
|
126
126
|
"devDependencies": {
|
|
127
|
+
"@cfworker/json-schema": "4.1.1",
|
|
127
128
|
"@types/node": "^24.13.3",
|
|
128
129
|
"esbuild": "^0.25.12",
|
|
129
130
|
"fyflow-scheduler": "0.6.0",
|