fyflow-orchestration 0.7.0 → 0.9.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 +1895 -206
- package/README.md +89 -104
- package/dist/browser/chunks/{chunk-YVOP3DIN.js → chunk-3U4UZC34.js} +2683 -449
- package/dist/browser/index.js +19 -9
- package/dist/browser/scheduler/index.js +158 -67
- package/dist/node/chunks/{chunk-YVOP3DIN.js → chunk-3U4UZC34.js} +2683 -449
- package/dist/node/index.js +19 -9
- package/dist/node/scheduler/index.js +158 -67
- package/dist/types/src/definition/assist.d.ts +29 -5
- package/dist/types/src/definition/condition.d.ts +49 -0
- package/dist/types/src/definition/regions.d.ts +12 -1
- package/dist/types/src/definition/registry.d.ts +52 -10
- package/dist/types/src/definition/schema.d.ts +20 -8
- package/dist/types/src/definition/types.d.ts +152 -23
- package/dist/types/src/definition/validate.d.ts +68 -20
- package/dist/types/src/definition/wiring.d.ts +58 -10
- package/dist/types/src/definition/witness.d.ts +12 -4
- package/dist/types/src/driver/driver.d.ts +827 -61
- package/dist/types/src/driver/listener.d.ts +7 -1
- package/dist/types/src/driver/log.d.ts +20 -5
- package/dist/types/src/driver/routing.d.ts +10 -5
- package/dist/types/src/fold/accumulator.d.ts +94 -0
- package/dist/types/src/fold/policies.d.ts +4 -5
- package/dist/types/src/fold/run-state.d.ts +275 -17
- package/dist/types/src/scheduler/bootstrap.d.ts +18 -26
- package/dist/types/src/scheduler/envelope-worker.d.ts +6 -5
- package/dist/types/src/scheduler/index.d.ts +2 -2
- package/dist/types/src/scheduler/runtime.d.ts +144 -39
- package/package.json +3 -3
|
@@ -6,6 +6,11 @@ export interface TaskDetailLike {
|
|
|
6
6
|
result?: unknown;
|
|
7
7
|
error?: string;
|
|
8
8
|
attempts?: number;
|
|
9
|
+
/**
|
|
10
|
+
* The scheduler's 1-based number of the attempt, read on `task.running` ONLY — `RN-R40`, W186, by the driver while its
|
|
11
|
+
* listener runs. Never on a record: the record's `attempts` is the scheduler's count (`OR-R4`, P36 (i)).
|
|
12
|
+
*/
|
|
13
|
+
attemptNumber?: number;
|
|
9
14
|
timestamp?: number;
|
|
10
15
|
startTime?: number;
|
|
11
16
|
endTime?: number;
|
|
@@ -15,7 +20,7 @@ export interface TaskDetailLike {
|
|
|
15
20
|
* `OR-R6`'s refusal. **It sets its own `name`**, as `BoundaryRefused` and `ResumeRefused` do, so the
|
|
16
21
|
* error's own render — `String(e)`, a stack's first line — reads `UncloneableResult: <message>`. Since
|
|
17
22
|
* W133 the MESSAGE no longer carries the class name (Q63), and without this the error itself would
|
|
18
|
-
* say `Error:` everywhere but `RunFailure.kind` and `
|
|
23
|
+
* say `Error:` everywhere but `RunFailure.kind` and `haltedBy`.
|
|
19
24
|
*/
|
|
20
25
|
export declare class UncloneableResult extends Error {
|
|
21
26
|
constructor(message?: string, options?: ErrorOptions);
|
|
@@ -29,6 +34,7 @@ export declare class UncloneableResult extends Error {
|
|
|
29
34
|
* done task.completed -> 1 record
|
|
30
35
|
* failed (optional task) task.failed -> 1 record
|
|
31
36
|
* user_action task.failed, task.user_action -> 1 record, on the SECOND
|
|
37
|
+
* cancelled task.cancelled -> 1 record (ADR 0094)
|
|
32
38
|
*
|
|
33
39
|
* The third row is why this exists. `task.failed` fires for a task that did not
|
|
34
40
|
* fail, its detail already reads `state: 'user_action'`, and a driver keying on
|
|
@@ -15,7 +15,12 @@ export interface OutcomeRecord {
|
|
|
15
15
|
result: CloneableValue | null;
|
|
16
16
|
error: string | null;
|
|
17
17
|
attempts: number;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The scheduler's stamp — and **`null` for an ASK's effective outcome** (`spec/outcome-record.md` v0.18, P35 (e),
|
|
20
|
+
* [ADR 0109](../../doc/decisions/0109-an-ask-parks-with-nothing-dispatched-and-its-answer-is-its-result.md)): no
|
|
21
|
+
* scheduler was involved. A SETTLEMENT always carries a number. **BREAKING in 0.8.0** for a consumer typed on `number`.
|
|
22
|
+
*/
|
|
23
|
+
schedulerTimestamp: number | null;
|
|
19
24
|
observedAt: number;
|
|
20
25
|
startTime: number | null;
|
|
21
26
|
endTime: number | null;
|
|
@@ -39,8 +44,14 @@ export type LogRecord = OutcomeRecord | ResolutionRecord;
|
|
|
39
44
|
* second resolution refusable.
|
|
40
45
|
*/
|
|
41
46
|
export declare function lastRecordFor(log: readonly LogRecord[], taskId: string): LogRecord | undefined;
|
|
42
|
-
/**
|
|
43
|
-
|
|
47
|
+
/**
|
|
48
|
+
* `RS-R1` — a task is suspended when its last record is an outcome in `user_action`; **or, since v0.18, when it was
|
|
49
|
+
* ASKED and has no record at all** (P35 (b), ADR 0109): an ask parks with an `asked` fold input — not a log record,
|
|
50
|
+
* since section 1 fixes the log at two kinds — and the person's answer is the first record its id ever gets, so a
|
|
51
|
+
* second answer is refused as a second resolution is. `asked` is the caller's: whether the fold-input sequence holds an
|
|
52
|
+
* `asked` element for the id, which this function cannot see.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isSuspended(log: readonly LogRecord[], taskId: string, asked?: boolean): boolean;
|
|
44
55
|
/**
|
|
45
56
|
* `spec/outcome-record.md` section 6 — the record ROUTING reads, which is never
|
|
46
57
|
* the log directly.
|
|
@@ -52,8 +63,12 @@ export declare function isSuspended(log: readonly LogRecord[], taskId: string):
|
|
|
52
63
|
* `OR-R8`: derived, never appended. Appending it would make the log contain its
|
|
53
64
|
* own conclusions, and a second fold would derive a third record from the second.
|
|
54
65
|
*
|
|
66
|
+
* **An ASK's** — a resolution with NO settlement before it, which `RS-R1` admits only for an asked task — is DERIVED
|
|
67
|
+
* from the resolution alone (`spec/outcome-record.md` section 7, v0.18, P35 (e), ADR 0109): the answer is the node's
|
|
68
|
+
* result, `attempts` `0` and every scheduler field `null`, because no dispatch happened for those fields to describe.
|
|
69
|
+
*
|
|
55
70
|
* @returns `null` when the task has not settled, or has settled `user_action` and
|
|
56
|
-
* has no resolution — a suspended node has no
|
|
57
|
-
* exactly right: it has not finished and nothing routes.
|
|
71
|
+
* has no resolution, or was asked and has no answer — a suspended node has no
|
|
72
|
+
* effective outcome, which is exactly right: it has not finished and nothing routes.
|
|
58
73
|
*/
|
|
59
74
|
export declare function effectiveOutcome(log: readonly LogRecord[], taskId: string): OutcomeRecord | null;
|
|
@@ -21,12 +21,16 @@ import type { OutcomeRecord } from './log.js';
|
|
|
21
21
|
export interface DecisionContext {
|
|
22
22
|
/**
|
|
23
23
|
* `OR-R9` — ALWAYS the node's effective outcome, so a predicate sees one shape
|
|
24
|
-
* whether the node settled or was resolved, and `terminalState` is never undefined.
|
|
24
|
+
* whether the node settled or was resolved, and `terminalState` is never undefined. **For an ASK it is derived from
|
|
25
|
+
* the person's answer** (`spec/outcome-record.md` section 7, v0.18): `result` the answer, `attempts` `0`, and
|
|
26
|
+
* `schedulerTimestamp` and the timing fields `null` — no scheduler was involved.
|
|
25
27
|
*/
|
|
26
28
|
outcome: OutcomeRecord;
|
|
27
29
|
/**
|
|
28
30
|
* **What this EDGE declared it needs** — the object its `needs` describes, resolved
|
|
29
|
-
* against the run's own outputs exactly as a node's is (`FD-C1`, `FD-C2`)
|
|
31
|
+
* against the run's own outputs exactly as a node's is (`FD-C1`, `FD-C2`) — and, since
|
|
32
|
+
* v0.44, against the value the RUN entered with, for a `"$payload"` key (`FD-C3`,
|
|
33
|
+
* ADR 0117): so a predicate routes on `needs.run.amount` with no task before it.
|
|
30
34
|
*
|
|
31
35
|
* **`{}` when the edge declares none, never `undefined`**, for rule 4's own reason
|
|
32
36
|
* one level out: *a predicate written `ctx.needs.flag` must mean one thing*, and a
|
|
@@ -58,9 +62,10 @@ export declare function eligibleEdges(node: FlowNode, terminalState: TerminalSta
|
|
|
58
62
|
* **The ONE edge a gate takes for a route** — `spec/flow-definition.md` section 2's gate,
|
|
59
63
|
* [ADR 0066](../../doc/decisions/0066-a-gate-is-a-decision-with-memory-that-names-a-registered-type-and-wires-every-route-exactly-once.md)
|
|
60
64
|
* rule 4, [W93](../../doc/work-items/w93-what-a-gate-does-in-a-run.md). An edge carries the route it names,
|
|
61
|
-
* and **an edge with no `route` carries the
|
|
62
|
-
*
|
|
63
|
-
* `
|
|
65
|
+
* and **an edge with no `route` carries the gate's DEFAULT route** — never every route, and not the first.
|
|
66
|
+
* `defaultRoute` is the GATE's resolved set's (`routesOf`, ADR 0092 rule 3), never read off the type here.
|
|
67
|
+
* Pure, as `selectEdge` is: the caller has already held `route` to the gate's resolved routes (`FD-R5`), and
|
|
68
|
+
* `FD-V41` wires every route of that set by exactly one edge, so this is total over what reaches it — `null` is
|
|
64
69
|
* the assertion that `FD-V41` held, and the driver stops the run on it rather than routing nowhere.
|
|
65
70
|
*/
|
|
66
71
|
export declare function gateEdge(node: GateNode, defaultRoute: string, route: string): Edge | null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FoldInput, NodeRun, RunOutcome, RunState } from './run-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* **A count of the work an accumulator did** — P39 decision 6 and stop-and-ask (e): one per input applied, and one per
|
|
4
|
+
* entry, element or cell it reads or writes while loading, copying or materialising. Clock-free, so a ratio of two
|
|
5
|
+
* counts is a property of the code and not of a machine (Q179). Owned by the caller; absent, nothing is counted.
|
|
6
|
+
*/
|
|
7
|
+
export interface FoldCounter {
|
|
8
|
+
visits: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class Accumulator {
|
|
11
|
+
private runId;
|
|
12
|
+
private outcome;
|
|
13
|
+
private haltedBy;
|
|
14
|
+
private cancelRequest;
|
|
15
|
+
private interruption;
|
|
16
|
+
private paused;
|
|
17
|
+
/** Entries written since the load, by task id, in first-write order. A task id not here reads through to the base. */
|
|
18
|
+
private readonly nodes;
|
|
19
|
+
/** Instances touched since the load, by opener and then by the opener's branch path, in first-touch order. */
|
|
20
|
+
private readonly regions;
|
|
21
|
+
private readonly pathEnds;
|
|
22
|
+
private readonly resumes;
|
|
23
|
+
private readonly gated;
|
|
24
|
+
private counts;
|
|
25
|
+
private readonly base;
|
|
26
|
+
private readonly counter;
|
|
27
|
+
private constructor();
|
|
28
|
+
/** An accumulator over `state`, which it reads and never mutates. `counter`, when given, is charged for its work. */
|
|
29
|
+
static load(state: RunState, counter?: FoldCounter): Accumulator;
|
|
30
|
+
private tick;
|
|
31
|
+
/**
|
|
32
|
+
* **The outcome as the last applied input left it** — what {@link materialise} would write into `outcome`, read in
|
|
33
|
+
* O(1). The runtime's `RunHandle.outcome()` and `done`'s predicate read this, never a materialisation (P39 prediction 4).
|
|
34
|
+
*/
|
|
35
|
+
outcomeNow(): RunOutcome;
|
|
36
|
+
/**
|
|
37
|
+
* **One task's entry, or `undefined`** — the object {@link materialise} would place at `nodes[taskId]`, read in O(1).
|
|
38
|
+
* An OWN entry only: a task id naming an `Object.prototype` member (`toString`, `__proto__`) is a task the run never
|
|
39
|
+
* dispatched, where `prev.nodes[taskId]` on a plain record would read the inherited member.
|
|
40
|
+
*/
|
|
41
|
+
entry(taskId: string): NodeRun | undefined;
|
|
42
|
+
/** `prev.nodes[taskId]` — the entry written since the load, else the base's own read, prototype and all. */
|
|
43
|
+
private node;
|
|
44
|
+
/** `prev.regions[regionId]?.[regionPath]` — the touched instance, else the base's. */
|
|
45
|
+
private instance;
|
|
46
|
+
/** The counts, taken from everything the state holds on first need — O(the state), once per accumulator. */
|
|
47
|
+
private tally;
|
|
48
|
+
private countNode;
|
|
49
|
+
private stateCount;
|
|
50
|
+
/** `nodes[taskId] = entry`, the counts kept. An entry is REPLACED, never mutated: a materialised state may hold the old one. */
|
|
51
|
+
private setNode;
|
|
52
|
+
/**
|
|
53
|
+
* The instance at (`regionId`, `regionPath`) to write to — the touched one, else a COPY of the base's (O(its branch
|
|
54
|
+
* paths), once), else an empty one; with its share of the region counts taken out, for {@link settleRegion} to put back.
|
|
55
|
+
*/
|
|
56
|
+
private touchRegion;
|
|
57
|
+
private unweigh;
|
|
58
|
+
/** The instance's share of the region counts put back after a write. */
|
|
59
|
+
private settleRegion;
|
|
60
|
+
private addPathEnd;
|
|
61
|
+
/**
|
|
62
|
+
* **Apply one input.** `true` when the arm moved the state — where `step` before W194 returned a NEW object — and
|
|
63
|
+
* `false` where it returned the state it was handed, so `step` can return that same object. Throws where
|
|
64
|
+
* that `step` threw: a malformed task id (`parseTaskId`), a kind it does not name (`input.record` undefined) — within
|
|
65
|
+
* the module header's stated limit on region paths.
|
|
66
|
+
*/
|
|
67
|
+
apply(input: FoldInput): boolean;
|
|
68
|
+
private applyRegion;
|
|
69
|
+
/**
|
|
70
|
+
* **Section 3's precedence, read off the counts** — `RN-R7`, `RN-R8`, `RN-R10`, `RN-R28`, `RN-R30` and `RN-R32` in the
|
|
71
|
+
* one order they have always had. Called by exactly the arms that recomputed before W194. Always `true`: an arm that
|
|
72
|
+
* recomputes has moved the state.
|
|
73
|
+
*/
|
|
74
|
+
private withOutcome;
|
|
75
|
+
private precedence;
|
|
76
|
+
/**
|
|
77
|
+
* **One plain `RunState`**, built from the base and what was written since the load: a record or list nothing wrote is
|
|
78
|
+
* the base's own object, and one that was written is a new one with the base's entries in their places and the new
|
|
79
|
+
* entries after them, in first-write order — the key order a spread-per-step produced. O(what it builds).
|
|
80
|
+
*/
|
|
81
|
+
materialise(): RunState;
|
|
82
|
+
private list;
|
|
83
|
+
private materialiseRegion;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* **The fold, with its work counted** — the INTERNAL entry point P39 decision 6 names: `fold` is this with no counter.
|
|
87
|
+
* One accumulator, every input applied in append order (`RN-R3`), materialised once: linear in `inputs`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function foldWith(inputs: readonly FoldInput[], counter?: FoldCounter): RunState;
|
|
90
|
+
/**
|
|
91
|
+
* **One step, with its work counted** — `step` is this with no counter: load, apply, materialise, and the state it was
|
|
92
|
+
* handed itself back wherever the arm moves nothing. O(the state it is handed).
|
|
93
|
+
*/
|
|
94
|
+
export declare function stepWith(prev: RunState, input: FoldInput, counter?: FoldCounter): RunState;
|
|
@@ -91,11 +91,10 @@ export interface ArrivalCountState {
|
|
|
91
91
|
* That rule's own section says *"`arrival-count` is immune, because its `N` comes
|
|
92
92
|
* from `region.branches.length` at `open`"* — true until this change and false
|
|
93
93
|
* after it. Every `branch.opened` for a region precedes any other event for it, so
|
|
94
|
-
* `need` is final before any arrival can be counted against it.
|
|
95
|
-
*
|
|
96
|
-
* `
|
|
97
|
-
*
|
|
98
|
-
* policies rather than one.
|
|
94
|
+
* `need` is final before any arrival can be counted against it. *(This read "the clause remains unfalsifiable through
|
|
95
|
+
* a conformant driver" until W180: since `spec/flow-definition.md` v0.42 a branch entry or a map's branch may be an
|
|
96
|
+
* `ask`, which EMITS on entry, so the clause's guarantor is the driver's two-pass emission — `spec/join-policy.md`
|
|
97
|
+
* v0.23.)* What RESTS on it is now both counting policies rather than one.
|
|
99
98
|
*/
|
|
100
99
|
export declare const arrivalCount: JoinPolicy<ArrivalCountState>;
|
|
101
100
|
export interface LiveTokenState {
|
|
@@ -6,18 +6,29 @@
|
|
|
6
6
|
* ladder L3 checks by folding the same log twice and deep-comparing, and it is
|
|
7
7
|
* what makes [Q14](../../doc/questions/q14.md)'s answer an answer rather than a
|
|
8
8
|
* restated leaning.
|
|
9
|
+
*
|
|
10
|
+
* **Since v0.29 the arms live in ONE place, `src/fold/accumulator.ts`** (`RN-R43`,
|
|
11
|
+
* [ADR 0120](../../doc/decisions/0120-the-fold-is-one-accumulator-and-step-stays-pure.md)): an accumulator mutated in
|
|
12
|
+
* place WITHIN one call and materialised into a plain state, so nothing survives a call and `RN-R1` holds as before.
|
|
13
|
+
* This module keeps the types, `emptyState`, `isTerminal`, `nodeOf` and `foldLog`, and `fold` and `step` call into it.
|
|
14
|
+
* That module is internal: `index.ts` re-exports this one and never it.
|
|
9
15
|
*/
|
|
10
16
|
import type { LogRecord, OutcomeRecord } from '../driver/log.js';
|
|
11
17
|
import type { RegionEvent } from '../driver/events.js';
|
|
12
18
|
/**
|
|
13
|
-
* `spec/run-state.md` section 4.
|
|
19
|
+
* `spec/run-state.md` section 4. Five, not the scheduler's six.
|
|
14
20
|
*
|
|
15
21
|
* `user_action` is `suspended` here — it is not terminal at flow level. And there
|
|
16
22
|
* is **no `pending`**: `OR-R4` says `task.running` writes no record, so the only
|
|
17
23
|
* signal the fold has is the dispatch itself, and a task is `running` from that
|
|
18
|
-
* moment. A state nothing can produce is not a state.
|
|
24
|
+
* moment. A state nothing can produce is not a state. *(Since v0.26 `task.running` produces a FOLD INPUT, `attempted`
|
|
25
|
+
* (`RN-R40`, W186) — still no record, and it moves no state: a task is `running` from its dispatch either way.)*
|
|
26
|
+
*
|
|
27
|
+
* **`cancelled` is W158's** ([ADR 0094](../../doc/decisions/0094-a-cancelled-task-is-a-terminal-state-that-routes-no-edge-and-ends-its-run-cancelled.md)),
|
|
28
|
+
* copied verbatim from the settlement as `done` and `failed` are. **Widening this union is BREAKING for a
|
|
29
|
+
* consumer switching on it exhaustively** — `resumed`'s and `gated`'s precedent.
|
|
19
30
|
*/
|
|
20
|
-
export type NodeState = 'running' | 'done' | 'failed' | 'suspended';
|
|
31
|
+
export type NodeState = 'running' | 'done' | 'failed' | 'suspended' | 'cancelled';
|
|
21
32
|
/**
|
|
22
33
|
* `spec/run-state.md` section 3. Closed, and ours — an author labels why a path
|
|
23
34
|
* ended, not what the run is.
|
|
@@ -26,17 +37,93 @@ export type NodeState = 'running' | 'done' | 'failed' | 'suspended';
|
|
|
26
37
|
* [ADR 0015](../../doc/decisions/0015-a-run-whose-join-can-no-longer-fire-is-stuck.md):
|
|
27
38
|
* nothing is live, nothing waits on a person, and a region's join has not fired,
|
|
28
39
|
* so nothing will ever move again. v0.2 called that run **`completed`**.
|
|
40
|
+
*
|
|
41
|
+
* **`cancelled` is v0.15's seventh**, added by W158 under
|
|
42
|
+
* [ADR 0094](../../doc/decisions/0094-a-cancelled-task-is-a-terminal-state-that-routes-no-edge-and-ends-its-run-cancelled.md):
|
|
43
|
+
* a task of the run settled `cancelled`, so the driver halted it (`RN-R28`). **Widening this union is BREAKING
|
|
44
|
+
* for a consumer switching on it exhaustively** — `resumed`'s and `gated`'s precedent; a consumer comparing
|
|
45
|
+
* strings sees a value it never named, and `isTerminal` already calls it terminal.
|
|
46
|
+
*
|
|
47
|
+
* **`halted` is v0.16's NAME for the outcome 0.7.0 called `stopped`** — W171,
|
|
48
|
+
* [ADR 0102](../../doc/decisions/0102-a-run-the-driver-halts-is-halted-and-a-stored-stopped-reads-as-one.md), P34
|
|
49
|
+
* decision 2 and stop-and-ask (f): a refusal terminated the run (`OR-R10`, `RN-R8`). *Stop* is P34's resumable stop
|
|
50
|
+
* of a run (W174), so the old name leaves this union ENTIRELY: **BREAKING**, and loudly — a consumer's
|
|
51
|
+
* `outcome === 'stopped'` no longer compiles, rather than silently changing what it means. The count does not move.
|
|
52
|
+
*
|
|
53
|
+
* **`cancelling` is v0.18's EIGHTH**, added by W173 under
|
|
54
|
+
* [ADR 0104](../../doc/decisions/0104-a-run-its-owner-cancels-is-cancelling-until-its-last-task-has-stopped.md): the
|
|
55
|
+
* run's OWNER cancelled it (`driver.cancel`, a `cancelRequested` element) and a task is still stopping (`RN-R30`).
|
|
56
|
+
* **NOT terminal** — the third outcome {@link isTerminal} calls unfinished — so a runtime's `done` waits for the last
|
|
57
|
+
* task to stop. **BREAKING in 0.8.0** (P34 decision 10): a consumer switching on it exhaustively, or keying a
|
|
58
|
+
* `Record<RunOutcome, …>`, fails to compile; one comparing strings sees a value it never named.
|
|
59
|
+
*
|
|
60
|
+
* **`interrupted` is v0.19's NINTH**, added by W174 under
|
|
61
|
+
* [ADR 0105](../../doc/decisions/0105-a-run-its-owner-stops-goes-deaf-before-it-cancels-and-resumes-elsewhere.md): the
|
|
62
|
+
* run's OWNER stopped it to resume it elsewhere (`driver.stop`, an `interrupted` element), and no `resumed` element has
|
|
63
|
+
* followed (`RN-R32`). **Terminal** — {@link isTerminal} needs no change, and a runtime's `done` settles on it once the
|
|
64
|
+
* stop's cancels have resolved — **and the one terminal outcome a resume ACCEPTS**. **BREAKING in 0.8.0** (P34 decision
|
|
65
|
+
* 10), as `cancelling` is.
|
|
66
|
+
*/
|
|
67
|
+
export type RunOutcome = 'running' | 'completed' | 'failed' | 'awaiting_action' | 'halted' | 'stuck' | 'cancelled' | 'cancelling' | 'interrupted';
|
|
68
|
+
/**
|
|
69
|
+
* **Who cancelled the run, and why** — `RunState.cancelRequest`, `RN-R30`, v0.18. What the `cancelRequested` element
|
|
70
|
+
* carried: `actor` as the caller gave it, `reason` the caller's or `null` when it gave none.
|
|
71
|
+
*/
|
|
72
|
+
export interface CancelRequest {
|
|
73
|
+
actor: string;
|
|
74
|
+
reason: string | null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* **Who stopped the run, and why** — `RunState.interruption`, `RN-R32`, v0.19. What the `interrupted` element carried:
|
|
78
|
+
* `actor` as the caller gave it, `reason` the caller's or `null` when it gave none.
|
|
29
79
|
*/
|
|
30
|
-
export
|
|
80
|
+
export interface Interruption {
|
|
81
|
+
actor: string;
|
|
82
|
+
reason: string | null;
|
|
83
|
+
}
|
|
31
84
|
/** How a branch left its region. `null` while it is still live. */
|
|
32
85
|
export type BranchSettlement = 'arrived' | 'exited' | 'lost';
|
|
86
|
+
/**
|
|
87
|
+
* **One task's entry in `RunState.nodes`** — `RN-R5`, and since v0.27 its ATTRIBUTION as fields (`RN-R41`,
|
|
88
|
+
* [ADR 0114](../../doc/decisions/0114-a-tasks-attribution-is-fields-and-its-messages-and-progress-are-passed-through.md), P36
|
|
89
|
+
* stop-and-ask (j)): the run, the flow-level attempt and the scheduler's attempt number, so a reader holding an entry on
|
|
90
|
+
* its own needs no parse of its key. **`runId`, `flowAttempt` and `attemptNumber` are REQUIRED, and that is BREAKING in
|
|
91
|
+
* 0.8.0 for code that builds a `NodeRun` literal** (P36 (p)); a reader is unaffected. Fold OUTPUT only: no fold input
|
|
92
|
+
* carries a field derived from the task id (`spec/outcome-record.md` section 2), and none moves.
|
|
93
|
+
*/
|
|
33
94
|
export interface NodeRun {
|
|
95
|
+
/**
|
|
96
|
+
* **The run the task belongs to** — v0.27, `RN-R41`: the task id's first segment (`spec/task-id.md` section 1), equal to
|
|
97
|
+
* `RunState.runId` on every entry of a run's fold. On the entry so it travels with it.
|
|
98
|
+
*/
|
|
99
|
+
runId: string;
|
|
34
100
|
nodeId: string;
|
|
35
101
|
/** `'-'` when the node is in no fan-out region. */
|
|
36
102
|
branchPath: string;
|
|
103
|
+
/**
|
|
104
|
+
* **The FLOW-LEVEL attempt** — v0.27, `RN-R41`: the task id's fourth segment, `0` on every id this library mints today
|
|
105
|
+
* ([Q16](../../doc/questions/q16.md)'s flow half mints nothing above it). NOT the scheduler's attempt number: that is
|
|
106
|
+
* {@link NodeRun.attemptNumber}, named apart so the two cannot be confused (P36 (j)).
|
|
107
|
+
*/
|
|
108
|
+
flowAttempt: number;
|
|
109
|
+
/**
|
|
110
|
+
* **The SCHEDULER's attempt number** — v0.27, `RN-R41`: the 1-based `attemptNumber` the LATEST `attempted` element for this
|
|
111
|
+
* task carried (`RN-R40`), so a retried task reads the attempt it is on, or settled on. **`null`** before its first
|
|
112
|
+
* attempt starts, on an ASK (never handed to a scheduler), and on every entry of a log written before 0.8.0, which holds
|
|
113
|
+
* no `attempted` element — never derived from a settlement's `attempts`.
|
|
114
|
+
*/
|
|
115
|
+
attemptNumber: number | null;
|
|
37
116
|
state: NodeState;
|
|
38
117
|
result: unknown;
|
|
39
118
|
error: string | null;
|
|
119
|
+
/**
|
|
120
|
+
* **What REACHED an ask** — `RN-R36`, v0.22, P35 (f),
|
|
121
|
+
* [ADR 0109](../../doc/decisions/0109-an-ask-parks-with-nothing-dispatched-and-its-answer-is-its-result.md): the value
|
|
122
|
+
* its `needs` composed (or the edge's value without one), as its `asked` element carried it, and KEPT after the answer.
|
|
123
|
+
* **Present only on an ask's node**, so every other node's entry has the five fields it always had — OPTIONAL, and
|
|
124
|
+
* additive: a consumer building a `NodeRun` literal is unaffected.
|
|
125
|
+
*/
|
|
126
|
+
input?: unknown;
|
|
40
127
|
}
|
|
41
128
|
export interface RegionRun {
|
|
42
129
|
/** Branch paths opened, in the order the driver emitted them (`JP-R5`). */
|
|
@@ -95,8 +182,37 @@ export interface RunState {
|
|
|
95
182
|
reason: 'end' | 'no-eligible-edge';
|
|
96
183
|
at: string;
|
|
97
184
|
}[];
|
|
98
|
-
/**
|
|
99
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Set when a refusal HALTED the run (`OR-R10`, `RN-R8`): the refusal's class, `: `, and its message. *(0.7.0's
|
|
187
|
+
* `stoppedBy` — renamed with the outcome by ADR 0102; BREAKING, and a consumer reading the old name fails to compile.)*
|
|
188
|
+
*/
|
|
189
|
+
haltedBy: string | null;
|
|
190
|
+
/**
|
|
191
|
+
* **Set when the run's OWNER cancelled it** — `RN-R30`, v0.18, W173,
|
|
192
|
+
* [ADR 0104](../../doc/decisions/0104-a-run-its-owner-cancels-is-cancelling-until-its-last-task-has-stopped.md): the
|
|
193
|
+
* FIRST `cancelRequested` element's `{ actor, reason }`, and `null` for a run nobody cancelled — including one a cancel
|
|
194
|
+
* the scheduler made on its own ended (`RN-R28`), which carries no actor. **A new cell, BREAKING in 0.8.0 for a
|
|
195
|
+
* consumer that builds a `RunState` literal** (P34 decision 10).
|
|
196
|
+
*/
|
|
197
|
+
cancelRequest: CancelRequest | null;
|
|
198
|
+
/**
|
|
199
|
+
* **Set while the run's OWNER has STOPPED it** — `RN-R32`, v0.19, W174,
|
|
200
|
+
* [ADR 0105](../../doc/decisions/0105-a-run-its-owner-stops-goes-deaf-before-it-cancels-and-resumes-elsewhere.md): the
|
|
201
|
+
* `interrupted` element's `{ actor, reason }`, and `null` again once a `resumed` element follows it. **CURRENT, not
|
|
202
|
+
* cumulative** — `regions[*].suspended`'s precedent (`RN-R6`): that a resumed run was ever stopped is in the input
|
|
203
|
+
* sequence, not here. **A new cell, BREAKING in 0.8.0 for a consumer that builds a `RunState` literal** (P34 decision 10).
|
|
204
|
+
*/
|
|
205
|
+
interruption: Interruption | null;
|
|
206
|
+
/**
|
|
207
|
+
* **Whether the run's OWNER has paused it NOW** — `RN-R34`, v0.20, W175,
|
|
208
|
+
* [ADR 0106](../../doc/decisions/0106-a-run-its-owner-pauses-routes-on-and-holds-every-dispatch-until-unpaused.md): `true`
|
|
209
|
+
* from a `paused` element until an `unpaused` one. **A flag, NOT an outcome** (P34 (e)): a paused run still routes, and
|
|
210
|
+
* each dispatch it holds is `dispatched` and so `running` in `nodes` — so `outcome` is exactly what it would be without
|
|
211
|
+
* the pause, and no precedence moves. **CURRENT, not cumulative**: that a run was ever paused is in the input sequence.
|
|
212
|
+
* Neither a stop, a cancel nor a resume lifts it — only an `unpaused` element does, so a run stopped while paused
|
|
213
|
+
* resumes paused. **A new cell, BREAKING in 0.8.0 for a consumer that builds a `RunState` literal** (P34 decision 10).
|
|
214
|
+
*/
|
|
215
|
+
paused: boolean;
|
|
100
216
|
/**
|
|
101
217
|
* **Every resume this run has had, in order, each the task ids it handed a scheduler AGAIN** —
|
|
102
218
|
* `RN-R22`, [ADR 0065](../../doc/decisions/0065-a-resume-replays-the-stored-stimuli-refuses-what-it-cannot-regenerate-and-re-dispatches-what-was-in-flight.md),
|
|
@@ -167,7 +283,7 @@ export interface GateDecision {
|
|
|
167
283
|
*
|
|
168
284
|
* Neither is a log record, and neither belongs in one: `spec/outcome-record.md`
|
|
169
285
|
* section 1 fixes the log at two kinds. This is the fold's input sequence, which
|
|
170
|
-
* already carried region events and a
|
|
286
|
+
* already carried region events and a halt for the same reason.
|
|
171
287
|
*
|
|
172
288
|
* **`joinSettled` is W5's, and it is the third fact of this shape**
|
|
173
289
|
* ([ADR 0015](../../doc/decisions/0015-a-run-whose-join-can-no-longer-fire-is-stuck.md)):
|
|
@@ -221,6 +337,25 @@ export type FoldInput = {
|
|
|
221
337
|
} | {
|
|
222
338
|
kind: 'dispatched';
|
|
223
339
|
taskId: string;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* **A branch reached an ASK, which parked** — `RN-R36`, `RN-R37`, v0.22,
|
|
343
|
+
* [W180](../../doc/work-items/w180-asking-and-answering-in-a-run.md),
|
|
344
|
+
* [ADR 0109](../../doc/decisions/0109-an-ask-parks-with-nothing-dispatched-and-its-answer-is-its-result.md), P35
|
|
345
|
+
* stop-and-ask (b): appended by the driver where a branch ENTERS an ask, with NOTHING handed to the scheduler — `taskId`
|
|
346
|
+
* the ask's own (`spec/task-id.md`'s scheme, attempt `0`) and `input` what reached it, composed through its `needs`
|
|
347
|
+
* (P35 (f)). The fold reads the node `suspended` from here, its `input` kept, until the person's `resolution`.
|
|
348
|
+
*
|
|
349
|
+
* **A structural fact, not a terminal state** (P35 decision 5, fact 13): `dispatched`'s precedent — no outcome record is
|
|
350
|
+
* ever written for an ask, and its only terminal fact is the person's answer. **REGENERATED on resume, never FED**
|
|
351
|
+
* (`RN-R19`): the replay re-enters the ask and compares the input it composes with the stored one (`RN-R20`). **The
|
|
352
|
+
* first kind whose payload is a value of ANY shape**, so it is deep-copied on the way out (`freezeInput`, `RN-R17`).
|
|
353
|
+
* **A new kind, BREAKING in 0.8.0 for a consumer switching on `kind` exhaustively** (P35 decision 8).
|
|
354
|
+
*/
|
|
355
|
+
| {
|
|
356
|
+
kind: 'asked';
|
|
357
|
+
taskId: string;
|
|
358
|
+
input: unknown;
|
|
224
359
|
} | {
|
|
225
360
|
kind: 'pathEnded';
|
|
226
361
|
reason: 'end' | 'no-eligible-edge';
|
|
@@ -231,10 +366,77 @@ export type FoldInput = {
|
|
|
231
366
|
regionPath: string;
|
|
232
367
|
joinId: string;
|
|
233
368
|
fired: boolean;
|
|
234
|
-
}
|
|
235
|
-
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* **A refusal HALTED the run** — `OR-R10`, `RN-R8`; appended by the driver's `failRun`, `reason` the refusal's class,
|
|
372
|
+
* `: ` and its message. **0.7.0 wrote this element as `{ kind: 'stopped', reason }`** and ADR 0102 renamed it
|
|
373
|
+
* (W171, P34 stop-and-ask (f)). **The old kind is READ, never written, and is not a member of this union**: a stored
|
|
374
|
+
* sequence is data from before the rename, so {@link step} folds an element whose `kind` is `'stopped'` exactly as
|
|
375
|
+
* this one and `resumeRefusal` refuses it `halted` at its position — `CHECKPOINT_FORMAT` stays `1`, and a 0.7.0
|
|
376
|
+
* checkpoint of a halted run is refused for the right reason while one of a live run still resumes. Nothing in this
|
|
377
|
+
* library writes `'stopped'` again, and the type says so: a consumer's `kind === 'stopped'` no longer compiles.
|
|
378
|
+
* **BREAKING in 0.8.0** (P34 decision 10): a consumer that switched on, compared or CONSTRUCTED the old kind fails
|
|
379
|
+
* to compile.
|
|
380
|
+
*/
|
|
381
|
+
| {
|
|
382
|
+
kind: 'halted';
|
|
236
383
|
reason: string;
|
|
237
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* **The run's OWNER cancelled it** — `RN-R30`, `RN-R31`, v0.18,
|
|
387
|
+
* [W173](../../doc/work-items/w173-cancelling-a-run.md),
|
|
388
|
+
* [ADR 0104](../../doc/decisions/0104-a-run-its-owner-cancels-is-cancelling-until-its-last-task-has-stopped.md), P34
|
|
389
|
+
* decision 4: appended ONCE by `Driver.cancel`, after the hold and BEFORE the scheduler is asked to cancel anything,
|
|
390
|
+
* so every `task.cancelled` it causes folds under it. `reason` is `null` when the caller gave none. The fold reads the
|
|
391
|
+
* run `cancelling` from here while a dispatched task is unsettled, and `cancelled` once none is.
|
|
392
|
+
*
|
|
393
|
+
* **Never fed to a replay**: a resume refuses a stored one `cancelled` before replaying anything (`RN-R23`). **A new
|
|
394
|
+
* kind, BREAKING in 0.8.0 for a consumer switching on `kind` exhaustively** (P34 decision 10).
|
|
395
|
+
*/
|
|
396
|
+
| {
|
|
397
|
+
kind: 'cancelRequested';
|
|
398
|
+
actor: string;
|
|
399
|
+
reason: string | null;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* **The run's OWNER stopped it, to resume it elsewhere** — `RN-R32`, `RN-R33`, v0.19,
|
|
403
|
+
* [W174](../../doc/work-items/w174-stopping-a-run-to-resume-it-elsewhere.md),
|
|
404
|
+
* [ADR 0105](../../doc/decisions/0105-a-run-its-owner-stops-goes-deaf-before-it-cancels-and-resumes-elsewhere.md), P34
|
|
405
|
+
* decision 3 and stop-and-ask (d): appended ONCE by `Driver.stop`, after the hold and the driver going DEAF and BEFORE
|
|
406
|
+
* it asks the scheduler to cancel anything, so no `task.cancelled` the stop causes — nor any settlement at all — follows it. `reason` is `null` when the
|
|
407
|
+
* caller gave none. The fold reads the run `interrupted` from here until a `resumed` element.
|
|
408
|
+
*
|
|
409
|
+
* **FED to a replay** (`RN-R19`), at its own position — the one terminal element a resume does not refuse. **A new
|
|
410
|
+
* kind, BREAKING in 0.8.0 for a consumer switching on `kind` exhaustively** (P34 decision 10).
|
|
411
|
+
*/
|
|
412
|
+
| {
|
|
413
|
+
kind: 'interrupted';
|
|
414
|
+
actor: string;
|
|
415
|
+
reason: string | null;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* **The run's OWNER paused it** — `RN-R34`, `RN-R35`, v0.20,
|
|
419
|
+
* [W175](../../doc/work-items/w175-pausing-a-run.md),
|
|
420
|
+
* [ADR 0106](../../doc/decisions/0106-a-run-its-owner-pauses-routes-on-and-holds-every-dispatch-until-unpaused.md), P34
|
|
421
|
+
* stop-and-ask (e): appended ONCE by `Driver.pause`, at rest — between two of the driver's steps — and from it on every
|
|
422
|
+
* dispatch is appended and HELD. The fold sets `RunState.paused`; no outcome moves.
|
|
423
|
+
*
|
|
424
|
+
* **FED to a replay** (`RN-R19`), at its own position, so a run stopped while paused resumes paused. **A new kind,
|
|
425
|
+
* BREAKING in 0.8.0 for a consumer switching on `kind` exhaustively** (P34 decision 10).
|
|
426
|
+
*/
|
|
427
|
+
| {
|
|
428
|
+
kind: 'paused';
|
|
429
|
+
actor: string;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* **The run's OWNER unpaused it** — `RN-R34`, `RN-R35`, v0.20, W175, ADR 0106: appended ONCE by `Driver.unpause`, at
|
|
433
|
+
* rest, BEFORE the held tasks are handed on, in dispatch order. The fold clears `RunState.paused`. **FED to a replay**;
|
|
434
|
+
* **a new kind, BREAKING in 0.8.0** (P34 decision 10).
|
|
435
|
+
*/
|
|
436
|
+
| {
|
|
437
|
+
kind: 'unpaused';
|
|
438
|
+
actor: string;
|
|
439
|
+
}
|
|
238
440
|
/**
|
|
239
441
|
* **The run was RESUMED, and these are the tasks handed to a scheduler again** — `RN-R22`,
|
|
240
442
|
* [W89](../../doc/work-items/w89-what-resuming-is.md), P23 stop-and-ask (a), answered: *a resume
|
|
@@ -269,17 +471,61 @@ export type FoldInput = {
|
|
|
269
471
|
*/
|
|
270
472
|
| ({
|
|
271
473
|
kind: 'gated';
|
|
272
|
-
} & GateDecision)
|
|
474
|
+
} & GateDecision)
|
|
475
|
+
/**
|
|
476
|
+
* **An ATTEMPT of a task started** — `RN-R40`, v0.26,
|
|
477
|
+
* [W186](../../doc/work-items/w186-attempts-recorded-and-carried-across-a-resume.md),
|
|
478
|
+
* [ADR 0113](../../doc/decisions/0113-a-tasks-attempts-are-recorded-and-carried-across-a-resume.md), P36 stop-and-ask (f):
|
|
479
|
+
* appended by the driver for the scheduler's `task.running` of a task THIS run dispatched, carrying the scheduler's
|
|
480
|
+
* 1-based `attemptNumber` — and ONLY when that number EXCEEDS the last one recorded for the task, so a requeue (which
|
|
481
|
+
* repeats its number) appends nothing. For every task, with or without a `retryPolicy`. **Appended at REST**: a
|
|
482
|
+
* `task.running` heard on the driver's own step — an inline pool fires it INSIDE `addTask` — is appended when that step
|
|
483
|
+
* ends, so the element always lies BETWEEN two steps, where a resume's replay feeds it (`RN-R19`).
|
|
484
|
+
*
|
|
485
|
+
* **FED to a replay** (`RN-R19`'s seventh kind): only the scheduler can produce it, so the replay cannot regenerate it;
|
|
486
|
+
* the highest number the replay was fed for a task is what a resume re-hands it with as `attemptsSpent` (`RN-R21`), so a
|
|
487
|
+
* task's retry budget is NOT reset by a crash (Q365). **Moved nothing in `RunState` at v0.26; since v0.27 it sets ONE
|
|
488
|
+
* cell, its task's `NodeRun.attemptNumber`** (`RN-R41`, W187) — the run's outcome, every state and every other cell are
|
|
489
|
+
* what they would be without it. **Never a log record**
|
|
490
|
+
* (`OR-R4`: `task.running` writes none) — `cancelRequested`'s precedent. **A new kind, BREAKING in 0.8.0 for a consumer
|
|
491
|
+
* switching on `kind` exhaustively** (P36 decision 8, (p)).
|
|
492
|
+
*/
|
|
493
|
+
| {
|
|
494
|
+
kind: 'attempted';
|
|
495
|
+
taskId: string;
|
|
496
|
+
attemptNumber: number;
|
|
497
|
+
};
|
|
273
498
|
export declare function emptyState(): RunState;
|
|
274
499
|
/**
|
|
275
|
-
* One step. **
|
|
276
|
-
* what lets `RN-R2` hold (folding a prefix then one more record equals folding the
|
|
500
|
+
* One step. **Never mutates the state it was given, and returns a NEW one wherever the input moves something** — and
|
|
501
|
+
* that state ITSELF where it moves nothing (below) — which is what lets `RN-R2` hold (folding a prefix then one more record equals folding the
|
|
277
502
|
* whole) and what makes a fold usable while a run is still going.
|
|
503
|
+
*
|
|
504
|
+
* **Its work is O(the state it is handed), not O(the input)** — `RN-R43`, v0.29,
|
|
505
|
+
* [ADR 0120](../../doc/decisions/0120-the-fold-is-one-accumulator-and-step-stays-pure.md): it loads `prev` into the
|
|
506
|
+
* fold's one accumulator, applies the input and materialises a new plain state, and a pure function from one plain
|
|
507
|
+
* `RunState` to another cannot do less than copy the record it changes. So stepping every input of a long run costs
|
|
508
|
+
* O(run) per input; {@link fold} over the whole sequence is linear. A consumer that needs a run's state after every input
|
|
509
|
+
* reads it where a runtime keeps it, rather than stepping a copy of its own.
|
|
510
|
+
*
|
|
511
|
+
* **Each arm behaves on ANY state it is handed exactly as it did before v0.29** — not only on one a fold can reach. The
|
|
512
|
+
* outcome is recomputed only by the arms that recomputed it, so a hand-built state whose `outcome` disagrees with its
|
|
513
|
+
* cells keeps that outcome across `paused`, `unpaused`, `gated`, `attempted` and a `resumed` that ends no stop; and an
|
|
514
|
+
* input that moves nothing — a second cancel request, or one after the run is already `cancelled`, a second stop or
|
|
515
|
+
* pause, an unpause of a run not paused, a dispatch or an ask of a
|
|
516
|
+
* task already in `nodes`, a region instance already entered, an attempt of a task not in `nodes` — hands back `prev`
|
|
517
|
+
* ITSELF. A cell no input touched is `prev`'s own object in the result. (Its one stated limit, a region path naming an
|
|
518
|
+
* `Object.prototype` member, which no driver appends, is `src/fold/accumulator.ts`'s header's.)
|
|
278
519
|
*/
|
|
279
520
|
export declare function step(prev: RunState, input: FoldInput): RunState;
|
|
280
521
|
/**
|
|
281
522
|
* The fold. `RN-R2`: total over any prefix, so `fold(log.slice(0, n))` is defined
|
|
282
|
-
* for every `n` and `step(fold(prefix), next)
|
|
523
|
+
* for every `n` and `step(fold(prefix), next)` deep-equals `fold(prefix + next)`.
|
|
524
|
+
*
|
|
525
|
+
* **Linear in its inputs** — `RN-R43`, v0.29, [W194](../../doc/work-items/w194-the-fold-is-one-accumulator.md): ONE
|
|
526
|
+
* accumulator (`src/fold/accumulator.ts`, internal) has every input applied to it in place and is materialised into a
|
|
527
|
+
* plain state once, at the end. Before v0.29 this was a `step` per input, each copying the run, so a fold was quadratic
|
|
528
|
+
* in its run — 30 s for a 4,000-item map (P39 fact 1). What it returns is unchanged on every sequence whose region paths are branch paths (P39 decision 5; `RN-R43`'s one stated limit).
|
|
283
529
|
*
|
|
284
530
|
* `RN-R3`: read in **append order**, never sorted. Two settlements can share a
|
|
285
531
|
* millisecond; `schedulerTimestamp` is the scheduler's clock and `observedAt` is
|
|
@@ -292,14 +538,26 @@ export declare function foldLog(log: readonly LogRecord[]): RunState;
|
|
|
292
538
|
* Whether a run can still change — the predicate an L6 harness resolves on, and
|
|
293
539
|
* the reason it needs no clock.
|
|
294
540
|
*
|
|
295
|
-
* `running` and `awaiting_action` are the
|
|
296
|
-
* coming: a dispatched node has not settled, or a person has
|
|
297
|
-
*
|
|
298
|
-
*
|
|
541
|
+
* `running` and `awaiting_action` are two of the three outcomes that say more input is
|
|
542
|
+
* coming that can MOVE the run (`cancelling`, the third, below): a dispatched node has not settled, or a person has
|
|
543
|
+
* not answered. `completed`, `failed` and `stuck` are reached only when nothing is
|
|
544
|
+
* live. `halted` and — since W158 — `cancelled` are reached with dispatched nodes
|
|
545
|
+
* possibly still unsettled: the driver has HALTED the run, so what is in flight
|
|
546
|
+
* settles into `nodes` routed nowhere and moves no outcome. Either way the run is
|
|
547
|
+
* over. **This is `stuck`'s point:** before
|
|
299
548
|
* [ADR 0015](../../doc/decisions/0015-a-run-whose-join-can-no-longer-fire-is-stuck.md)
|
|
300
549
|
* a permanently stuck run was `completed`, so a harness waiting on this predicate
|
|
301
550
|
* would have reported the hang as a success rather than waiting forever — which
|
|
302
551
|
* is the worse of the two failures.
|
|
552
|
+
*
|
|
553
|
+
* **`cancelling` is the THIRD unfinished outcome since v0.18** (`RN-R12`, `RN-R30`, ADR 0104): the run's owner
|
|
554
|
+
* cancelled it and a task is still stopping, so its `task.cancelled` is still coming and moves the outcome to
|
|
555
|
+
* `cancelled`. Calling it terminal would settle a runtime's `done` at the request — before the tasks have stopped,
|
|
556
|
+
* the app's #1 complaint (P34 fact 6).
|
|
557
|
+
*
|
|
558
|
+
* **`interrupted` is TERMINAL, unchanged by v0.19** (`RN-R12`, `RN-R32`, ADR 0105): the run's owner stopped it and the
|
|
559
|
+
* driver went deaf, so nothing more can move it on THIS runtime — its in-flight set is moved by a resume on another,
|
|
560
|
+
* which is not this driver's input. A runtime's `done` settles on it once the stop's cancels have resolved (`RN-R33`).
|
|
303
561
|
*/
|
|
304
562
|
export declare function isTerminal(s: RunState): boolean;
|
|
305
563
|
/** A node's entry, or `null`. Not an outcome record: it has no `attempts` and no timing fields. */
|