@cotal-ai/runtime 0.0.1 → 0.24.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/dist/fork.d.ts +144 -0
- package/dist/fork.d.ts.map +1 -0
- package/dist/fork.js +310 -0
- package/dist/fork.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/journal-store.d.ts +54 -0
- package/dist/journal-store.d.ts.map +1 -0
- package/dist/journal-store.js +60 -0
- package/dist/journal-store.js.map +1 -0
- package/dist/mesh-handler.d.ts +310 -0
- package/dist/mesh-handler.d.ts.map +1 -0
- package/dist/mesh-handler.js +731 -0
- package/dist/mesh-handler.js.map +1 -0
- package/dist/migrate.d.ts +147 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +256 -0
- package/dist/migrate.js.map +1 -0
- package/dist/resolve-checkpoint.d.ts +75 -0
- package/dist/resolve-checkpoint.d.ts.map +1 -0
- package/dist/resolve-checkpoint.js +108 -0
- package/dist/resolve-checkpoint.js.map +1 -0
- package/dist/run-context.d.ts +42 -0
- package/dist/run-context.d.ts.map +1 -0
- package/dist/run-context.js +55 -0
- package/dist/run-context.js.map +1 -0
- package/dist/run-driver.d.ts +124 -0
- package/dist/run-driver.d.ts.map +1 -0
- package/dist/run-driver.js +228 -0
- package/dist/run-driver.js.map +1 -0
- package/package.json +31 -16
- package/README.md +0 -6
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resolveCheckpoint` — the run-driver command through which a checkpoint is answered.
|
|
3
|
+
*
|
|
4
|
+
* **Nothing outside presents the token.** A checkpoint's resume is holder-bound (SPEC §13.10), and
|
|
5
|
+
* a workflow checkpoint's holder is the run driver: it is the one principal guaranteed to exist for
|
|
6
|
+
* the pause's whole life. So an observer UI, a notification action, or another agent does not talk
|
|
7
|
+
* to `endpoint-checkpoint` at all — each calls this, which authorizes the caller under the run's
|
|
8
|
+
* own ACL, files their answer, and then presents the token as the driver. "Resolvable from
|
|
9
|
+
* anywhere" is true at the product level while resume stays holder-bound at the protocol level, and
|
|
10
|
+
* §13.10 is not weakened by a millimetre. The cost is that the driver must be reachable to answer a
|
|
11
|
+
* checkpoint, which is the same condition under which the run advances at all.
|
|
12
|
+
*
|
|
13
|
+
* **The answer is written BEFORE the token is presented,** and the two are separate facts on
|
|
14
|
+
* purpose. The record is the payload; the settle is the one-use arbiter that releases the run. In
|
|
15
|
+
* that order a crash in between leaves an answer nobody accepted — orphaned, read by nothing, and
|
|
16
|
+
* harmless. In the other order it would leave a run released with its answer nowhere.
|
|
17
|
+
*
|
|
18
|
+
* **The step is addressed by its KEY, not by its token.** A token is `ctx.requestId`, derived from
|
|
19
|
+
* the run, the step key, the input hash and the attempt, and a resolver has none of those: it knows
|
|
20
|
+
* "the checkpoint named `approve` in this run". The journal is what maps one to the other, and it is
|
|
21
|
+
* also what says whether that step is still open — which is the question a resolver most needs
|
|
22
|
+
* answered before it collects a human's decision.
|
|
23
|
+
*/
|
|
24
|
+
import { replayRunJournal, newTakeoverId, recordCheckpointAnswer, checkpointAnswerId, resumeCheckpoint, } from "@cotal-ai/core";
|
|
25
|
+
import { journalEntryKeyString } from "@cotal-ai/lang";
|
|
26
|
+
/** No open checkpoint answers to this address in this run. */
|
|
27
|
+
export class CheckpointNotOpen extends Error {
|
|
28
|
+
runId;
|
|
29
|
+
stepKey;
|
|
30
|
+
why;
|
|
31
|
+
constructor(runId, stepKey, why) {
|
|
32
|
+
super(`run ${runId} has no open checkpoint at ${stepKey}: ${{
|
|
33
|
+
unknown: "no step is recorded under that key",
|
|
34
|
+
settled: "that step has already settled",
|
|
35
|
+
"not-a-checkpoint": "that step is not a checkpoint",
|
|
36
|
+
"no-identity": "that step is pending but carries no request id, so the checkpoint it is waiting on cannot be named",
|
|
37
|
+
}[why]}`);
|
|
38
|
+
this.runId = runId;
|
|
39
|
+
this.stepKey = stepKey;
|
|
40
|
+
this.why = why;
|
|
41
|
+
this.name = "CheckpointNotOpen";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Answer a run's open checkpoint.
|
|
46
|
+
*
|
|
47
|
+
* Refusals are the plane's own and are not softened here: a checkpoint already resumed is a
|
|
48
|
+
* `conflict` (resume authorization is one-use) and one already expired is a `failed-precondition`
|
|
49
|
+
* (expiry fails closed). Both mean the answer arrived too late, and both leave this resolver's
|
|
50
|
+
* record filed and unaccepted — which is what the caller needs to be told rather than a success
|
|
51
|
+
* that names a settlement somebody else won.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveCheckpoint(deps, req) {
|
|
54
|
+
const entries = await replayRunEntries(deps, req.runId);
|
|
55
|
+
const token = openCheckpointToken(entries, req.runId, req.stepKey);
|
|
56
|
+
const answerId = checkpointAnswerId({
|
|
57
|
+
token,
|
|
58
|
+
by: req.by,
|
|
59
|
+
...(req.value !== undefined ? { value: req.value } : {}),
|
|
60
|
+
...(req.artifact !== undefined ? { artifact: req.artifact } : {}),
|
|
61
|
+
});
|
|
62
|
+
await recordCheckpointAnswer(deps.kv, deps.endpoint, {
|
|
63
|
+
v: 1,
|
|
64
|
+
token,
|
|
65
|
+
answerId,
|
|
66
|
+
...(req.value !== undefined ? { value: req.value } : {}),
|
|
67
|
+
...(req.artifact !== undefined ? { artifact: req.artifact } : {}),
|
|
68
|
+
by: req.by,
|
|
69
|
+
at: req.now,
|
|
70
|
+
});
|
|
71
|
+
const settle = await resumeCheckpoint(deps.kv, deps.js, deps.jsm, deps.space, {
|
|
72
|
+
ref: { endpoint: deps.endpoint, token },
|
|
73
|
+
presenter: deps.holder,
|
|
74
|
+
now: req.now,
|
|
75
|
+
answerId,
|
|
76
|
+
});
|
|
77
|
+
return { token, answerId, settle };
|
|
78
|
+
}
|
|
79
|
+
/** The token of the open checkpoint at this address, or a loud refusal naming which it is not. */
|
|
80
|
+
export function openCheckpointToken(entries, runId, stepKey) {
|
|
81
|
+
// Append order, later record wins: a settled step has a settled entry written after its pending
|
|
82
|
+
// one, and answering the pending one would present a token whose pause is already over.
|
|
83
|
+
let entry;
|
|
84
|
+
for (const e of entries)
|
|
85
|
+
if (journalEntryKeyString(e) === stepKey)
|
|
86
|
+
entry = e;
|
|
87
|
+
if (entry === undefined)
|
|
88
|
+
throw new CheckpointNotOpen(runId, stepKey, "unknown");
|
|
89
|
+
if (entry.kind !== "checkpoint")
|
|
90
|
+
throw new CheckpointNotOpen(runId, stepKey, "not-a-checkpoint");
|
|
91
|
+
if (entry.state !== "pending")
|
|
92
|
+
throw new CheckpointNotOpen(runId, stepKey, "settled");
|
|
93
|
+
if (entry.requestId === undefined)
|
|
94
|
+
throw new CheckpointNotOpen(runId, stepKey, "no-identity");
|
|
95
|
+
return entry.requestId;
|
|
96
|
+
}
|
|
97
|
+
/** The run's step entries, in append order. Read-only: this replays under its own consumer name and
|
|
98
|
+
* activates nothing, so it never contends with the driver actually holding the run. */
|
|
99
|
+
async function replayRunEntries(deps, runId) {
|
|
100
|
+
const replay = await replayRunJournal(deps.js, deps.jsm, deps.space, runId, newTakeoverId());
|
|
101
|
+
const entries = [];
|
|
102
|
+
for (const stored of replay.records) {
|
|
103
|
+
if (stored.record.kind === "step")
|
|
104
|
+
entries.push(stored.record.entry);
|
|
105
|
+
}
|
|
106
|
+
return entries;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=resolve-checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-checkpoint.js","sourceRoot":"","sources":["../src/resolve-checkpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<run-context>` render: what a workflow's notices look like in an agent's context.
|
|
3
|
+
*
|
|
4
|
+
* **A fixed key→value table, never a sentence.** The property this buys is the whole reason
|
|
5
|
+
* `notify` exists in its bounded form: a notice reads as DATA in the receiving agent's context and
|
|
6
|
+
* cannot be mistaken for an instruction from the workflow. Prose would be an instruction whatever
|
|
7
|
+
* intended, because an agent reading prose in its prompt has no way to tell who wrote it.
|
|
8
|
+
*
|
|
9
|
+
* Eight short scalars in a labelled table is not enough room to write an instruction. That is the
|
|
10
|
+
* bound's purpose rather than a side effect of it, and it is enforced at the effect boundary
|
|
11
|
+
* (L3043) before a notice is ever written — so this renderer never has to decide what to do with a
|
|
12
|
+
* value it cannot render. It REFUSES one instead: a value that could end a line could forge a row
|
|
13
|
+
* or a closing tag, and a renderer that quietly escaped it would be the one place where the bound
|
|
14
|
+
* is a formatting convention rather than a rule.
|
|
15
|
+
*/
|
|
16
|
+
import type { RunNoticeRead } from "@cotal-ai/core";
|
|
17
|
+
/** A notice this renderer cannot put in one row. Loud, because the alternative is a forged row. */
|
|
18
|
+
export declare class UnrenderableNotice extends Error {
|
|
19
|
+
readonly noticeId: string;
|
|
20
|
+
readonly field: string;
|
|
21
|
+
constructor(noticeId: string, field: string);
|
|
22
|
+
}
|
|
23
|
+
export interface RunContextRender {
|
|
24
|
+
/** The run whose notices these are. */
|
|
25
|
+
readonly run: string;
|
|
26
|
+
/** The step the addressee is about to take — the coordinates of the turn this precedes, not of
|
|
27
|
+
* the steps that decided the notices. Each notice's own step is a fact about the program's
|
|
28
|
+
* history and belongs to the run, not to this header. */
|
|
29
|
+
readonly step: string;
|
|
30
|
+
readonly notices: readonly RunNoticeRead[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Render the notices addressed to one agent, ahead of one turn.
|
|
34
|
+
*
|
|
35
|
+
* Columns are padded to a fixed layout so the table reads as a table; the detail column is
|
|
36
|
+
* `key=value` pairs in the order the notice recorded them, which is the order the program wrote
|
|
37
|
+
* them. An empty set renders as an empty table rather than as nothing: "no decisions were told to
|
|
38
|
+
* you" and "nobody rendered your context" are different facts, and only one of them is this
|
|
39
|
+
* renderer's to state.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderRunContext(req: RunContextRender): string;
|
|
42
|
+
//# sourceMappingURL=run-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.d.ts","sourceRoot":"","sources":["../src/run-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAOpD,mGAAmG;AACnG,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAAxC,QAAQ,EAAE,MAAM,EAAW,KAAK,EAAE,MAAM;CAO9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;8DAE0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAUD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,MAAM,CA0B9D"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** A value that would break out of one table row — a control character, a line separator, or one
|
|
2
|
+
* of the characters the header line is built from. The effect boundary already excludes these from
|
|
3
|
+
* a notice; this is the second reader of the same rule, at the point where breaking out matters. */
|
|
4
|
+
const UNRENDERABLE = /[\u0000-\u001f\u007f-\u009f\u2028\u2029"<>]/;
|
|
5
|
+
/** A notice this renderer cannot put in one row. Loud, because the alternative is a forged row. */
|
|
6
|
+
export class UnrenderableNotice extends Error {
|
|
7
|
+
noticeId;
|
|
8
|
+
field;
|
|
9
|
+
constructor(noticeId, field) {
|
|
10
|
+
super(`notice ${noticeId} carries a line break, a control character or a delimiter in ${field}; ` +
|
|
11
|
+
`a run-context row is one line, and a value that can end a line can forge a row or the closing tag`);
|
|
12
|
+
this.noticeId = noticeId;
|
|
13
|
+
this.field = field;
|
|
14
|
+
this.name = "UnrenderableNotice";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const HEADER = { decision: "decision", outcome: "outcome", detail: "detail" };
|
|
18
|
+
/**
|
|
19
|
+
* Render the notices addressed to one agent, ahead of one turn.
|
|
20
|
+
*
|
|
21
|
+
* Columns are padded to a fixed layout so the table reads as a table; the detail column is
|
|
22
|
+
* `key=value` pairs in the order the notice recorded them, which is the order the program wrote
|
|
23
|
+
* them. An empty set renders as an empty table rather than as nothing: "no decisions were told to
|
|
24
|
+
* you" and "nobody rendered your context" are different facts, and only one of them is this
|
|
25
|
+
* renderer's to state.
|
|
26
|
+
*/
|
|
27
|
+
export function renderRunContext(req) {
|
|
28
|
+
for (const [field, value] of [["run", req.run], ["step", req.step]])
|
|
29
|
+
if (UNRENDERABLE.test(value))
|
|
30
|
+
throw new UnrenderableNotice(`<${field}>`, field);
|
|
31
|
+
const rows = req.notices.map((n) => {
|
|
32
|
+
const fact = n.spec.fact;
|
|
33
|
+
for (const [field, value] of [["decision", fact.decision], ["outcome", fact.outcome]])
|
|
34
|
+
if (UNRENDERABLE.test(value))
|
|
35
|
+
throw new UnrenderableNotice(n.noticeId, field);
|
|
36
|
+
const detail = Object.entries(fact.detail ?? {}).map(([k, v]) => {
|
|
37
|
+
if (UNRENDERABLE.test(k))
|
|
38
|
+
throw new UnrenderableNotice(n.noticeId, `detail key ${k}`);
|
|
39
|
+
if (typeof v === "string" && UNRENDERABLE.test(v))
|
|
40
|
+
throw new UnrenderableNotice(n.noticeId, `detail.${k}`);
|
|
41
|
+
return `${k}=${String(v)}`;
|
|
42
|
+
});
|
|
43
|
+
return { decision: fact.decision, outcome: fact.outcome, detail: detail.join(" ") };
|
|
44
|
+
});
|
|
45
|
+
const all = [HEADER, ...rows];
|
|
46
|
+
const dW = Math.max(...all.map((r) => r.decision.length));
|
|
47
|
+
const oW = Math.max(...all.map((r) => r.outcome.length));
|
|
48
|
+
const line = (r) => `${r.decision.padEnd(dW)} ${r.outcome.padEnd(oW)} ${r.detail}`.trimEnd();
|
|
49
|
+
return [
|
|
50
|
+
`<run-context run="${req.run}" step="${req.step}">`,
|
|
51
|
+
...all.map(line),
|
|
52
|
+
"</run-context>",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=run-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.js","sourceRoot":"","sources":["../src/run-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { JetStreamClient, JetStreamManager } from "@nats-io/jetstream";
|
|
2
|
+
import type { KV } from "@nats-io/kv";
|
|
3
|
+
import { type EffectHandler, type JournalEntry, type RunResult } from "@cotal-ai/lang";
|
|
4
|
+
/**
|
|
5
|
+
* The lease this driver holds the run under, from the work pool.
|
|
6
|
+
*
|
|
7
|
+
* `fencingToken` is `WorkLease.fencingToken` — monotonic per item, and the coordinate the barrier
|
|
8
|
+
* orders takeovers by. `holder` and `epoch` are the identity it is bound to: a token alone would let
|
|
9
|
+
* a returning process re-establish itself on a lease it still remembers, which is the same defect
|
|
10
|
+
* the barrier exists to prevent.
|
|
11
|
+
*/
|
|
12
|
+
export interface RunLease {
|
|
13
|
+
readonly holder: string;
|
|
14
|
+
readonly epoch: number;
|
|
15
|
+
readonly fencingToken: number;
|
|
16
|
+
/**
|
|
17
|
+
* The takeover id this driver's journal credential was minted for.
|
|
18
|
+
*
|
|
19
|
+
* It names the replay consumer, and a consumer name is one subject token, so it cannot be covered
|
|
20
|
+
* by a grant pattern — it has to be known when the rows are minted. That is when the lease is
|
|
21
|
+
* handed out, which is why it arrives with the lease rather than being chosen here.
|
|
22
|
+
*/
|
|
23
|
+
readonly takeoverId: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A pause an operator can ask for while a run is being driven.
|
|
27
|
+
*
|
|
28
|
+
* The driver reads it before each effect it has not already recorded, so a pause takes effect at the
|
|
29
|
+
* next boundary and never in the middle of one. It is one-way on purpose: resuming is starting a
|
|
30
|
+
* drive again, which is the same act as any other takeover and goes through the barrier like one.
|
|
31
|
+
*/
|
|
32
|
+
export declare class PauseToken {
|
|
33
|
+
private why;
|
|
34
|
+
pause(reason: string): void;
|
|
35
|
+
get reason(): string | undefined;
|
|
36
|
+
}
|
|
37
|
+
export interface DriveRequest {
|
|
38
|
+
readonly space: string;
|
|
39
|
+
/**
|
|
40
|
+
* The endpoint HOSTING this driver — the manager daemon. It leads the run record's
|
|
41
|
+
* key, so a retirement drain and a per-endpoint enumeration both work by prefix.
|
|
42
|
+
*/
|
|
43
|
+
readonly endpoint: string;
|
|
44
|
+
readonly runId: string;
|
|
45
|
+
/** The program. A resume MUST be handed the same source: a different one is a fork, not a resume. */
|
|
46
|
+
readonly source: string;
|
|
47
|
+
readonly lease: RunLease;
|
|
48
|
+
readonly handler: EffectHandler;
|
|
49
|
+
/**
|
|
50
|
+
* The records bucket. NOT optional, and deliberately so: the run record holds the pins a resume
|
|
51
|
+
* must read back and the high-water ordinal that is the only thing able to see a truncated
|
|
52
|
+
* journal tail. A driver that could run without it would skip both silently, which is the failure
|
|
53
|
+
* mode both exist to prevent.
|
|
54
|
+
*/
|
|
55
|
+
readonly kv: KV;
|
|
56
|
+
readonly seed?: string;
|
|
57
|
+
readonly file?: string;
|
|
58
|
+
readonly effectCeiling?: number;
|
|
59
|
+
readonly stepBudget?: number;
|
|
60
|
+
/**
|
|
61
|
+
* The ABSOLUTE work horizon this driver accepted the item under (`WorkLease.workExpiry`).
|
|
62
|
+
*
|
|
63
|
+
* Past it the pool has already reconciled the item, so a driver still appending is writing into a
|
|
64
|
+
* run the authority considers finished with. Nothing needs to be read to know this: the horizon is
|
|
65
|
+
* fixed at acceptance and never re-set (SPEC 13.8), which is exactly what makes it safe to check
|
|
66
|
+
* locally — unlike a lease deadline, whose current value is a fact on another machine and whose
|
|
67
|
+
* check would be a read-then-publish with a gap that cannot be closed.
|
|
68
|
+
*/
|
|
69
|
+
readonly workExpiry?: number;
|
|
70
|
+
/** An operator's pause, honoured at the next effect boundary. */
|
|
71
|
+
readonly pause?: PauseToken;
|
|
72
|
+
/**
|
|
73
|
+
* Repair external state bound to the PREVIOUS holder, once this driver holds the run and before
|
|
74
|
+
* the program is resumed.
|
|
75
|
+
*
|
|
76
|
+
* The case that exists is timers: a checkpoint's armed schedule fires onto a subject derived from
|
|
77
|
+
* the instance and epoch that armed it, so a run adopted by another host has live timers firing
|
|
78
|
+
* where nobody is listening, and resuming the program does not repair that — the pause replays as
|
|
79
|
+
* pending and goes straight back to waiting. What to re-arm is the mesh handler's business, not
|
|
80
|
+
* the driver's, so the driver only says WHEN: after the activation, because arming timers for a
|
|
81
|
+
* run this process turned out not to hold would point another driver's fires at this one.
|
|
82
|
+
*
|
|
83
|
+
* A failure here is a failure to take the run over, and is not swallowed: a driver that resumed a
|
|
84
|
+
* program whose pauses it could not re-arm would look like it was holding a run it cannot advance.
|
|
85
|
+
*
|
|
86
|
+
* OPTIONAL BECAUSE IT IS AN OVERRIDE, NOT BECAUSE THE REPAIR IS. A handler that knows how to
|
|
87
|
+
* repair its own external state says so with an `adopted` method, and the driver calls that when
|
|
88
|
+
* no callback is supplied — found by review, which noticed that this hook had NO caller anywhere
|
|
89
|
+
* in the tree, so every adopted run left its timers armed at the predecessor's coordinates. A
|
|
90
|
+
* seam that every future host must remember to wire is a defect waiting on its first host.
|
|
91
|
+
*/
|
|
92
|
+
readonly onActivated?: (entries: readonly JournalEntry[]) => Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A handler that owns external state bound to whoever was holding the run, and can repair it.
|
|
96
|
+
*
|
|
97
|
+
* Declared here rather than on `EffectHandler` because it is the DRIVER's concern: the language's
|
|
98
|
+
* handler interface describes performing effects, and nothing in `packages/lang` has a notion of one
|
|
99
|
+
* host taking a run over from another.
|
|
100
|
+
*/
|
|
101
|
+
export interface AdoptingHandler {
|
|
102
|
+
adopted(entries: readonly JournalEntry[]): Promise<unknown>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What a drive attempt did, as a two-exit answer rather than a value plus exceptions.
|
|
106
|
+
*
|
|
107
|
+
* `completed` is the program finishing under this driver. `released` is this driver ceasing to hold
|
|
108
|
+
* the run — superseded, stalled, retired — which says nothing about the program and must never be
|
|
109
|
+
* recorded as if it did. A program that FAILS is still `completed`: the failure is the run's result,
|
|
110
|
+
* and the journal has it.
|
|
111
|
+
*/
|
|
112
|
+
export type DriveOutcome = {
|
|
113
|
+
readonly status: "completed";
|
|
114
|
+
readonly result: RunResult;
|
|
115
|
+
} | {
|
|
116
|
+
readonly status: "released";
|
|
117
|
+
readonly reason: Error;
|
|
118
|
+
};
|
|
119
|
+
/** Start a run that has never been driven. Refused if the journal already has records. */
|
|
120
|
+
export declare function startRun(js: JetStreamClient, jsm: JetStreamManager, req: DriveRequest): Promise<DriveOutcome>;
|
|
121
|
+
/** Take over a run that already exists and drive it to quiescence. Refused if its journal is empty:
|
|
122
|
+
* a run whose records were purged is retired, and re-running it would repeat what it already did. */
|
|
123
|
+
export declare function driveRun(js: JetStreamClient, jsm: JetStreamManager, req: DriveRequest): Promise<DriveOutcome>;
|
|
124
|
+
//# sourceMappingURL=run-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-driver.d.ts","sourceRoot":"","sources":["../src/run-driver.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EASL,KAAK,aAAa,EAClB,KAAK,YAAY,EAEjB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAGxB;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAAqB;IAEhC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;CACF;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qGAAqG;IACrG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC5B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7D;AAOD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;CAAE,CAAC;AAE5D,0FAA0F;AAC1F,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,eAAe,EACnB,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED;sGACsG;AACtG,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,eAAe,EACnB,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,YAAY,CAAC,CAEvB"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run driver: what actually advances a workflow run, and the only thing allowed to.
|
|
3
|
+
*
|
|
4
|
+
* A driver is not a scheduler and not a supervisor. Its whole job is to be the ONE process holding a
|
|
5
|
+
* run, to hand the interpreter a journal that is durable and complete, and to stop the moment it is
|
|
6
|
+
* no longer that process. Everything hard about the first and third parts lives in the activation
|
|
7
|
+
* barrier (`@cotal-ai/core`); everything hard about the middle lives in the language. This file is
|
|
8
|
+
* the join, and it is deliberately small — a driver with its own opinions about durability would be
|
|
9
|
+
* a second answer to a question that already has one.
|
|
10
|
+
*
|
|
11
|
+
* **Start and resume are the same act with one bit of difference**, and the bit is not cosmetic. A
|
|
12
|
+
* run's journal cannot say whether an empty subject means "never started" or "retired by purge", so
|
|
13
|
+
* the caller states which it means: `startRun` activates with `expect: "new"` and `driveRun` with
|
|
14
|
+
* `expect: "existing"`, and the barrier refuses the mismatch either way. That is why there are two
|
|
15
|
+
* entry points rather than one that guesses from what it finds.
|
|
16
|
+
*
|
|
17
|
+
* **The prefix the barrier replayed IS the journal the interpreter resumes from.** Not a copy of it,
|
|
18
|
+
* not a second read: the appender hands back exactly the records it validated as complete before it
|
|
19
|
+
* activated, so there is no window in which the driver could resume from a different prefix than the
|
|
20
|
+
* one it took the run over on.
|
|
21
|
+
*
|
|
22
|
+
* **A durability failure is not a run failure.** If the journal refuses an append — superseded,
|
|
23
|
+
* stalled, retired — the run is not "failed", it is no longer THIS process's to speak for. The
|
|
24
|
+
* distinction is the whole reason `RunJournalStore` maps those to L5010: a driver that reported them
|
|
25
|
+
* as an outcome would be writing down a conclusion about work it can no longer see.
|
|
26
|
+
*/
|
|
27
|
+
import { activateRun, readRunRecord, createRunSpec, writeRunStatus, assertJournalTailIntact, RunJournalTailTruncated, RunSuperseded, RunJournalStalled, StaleLeaseToken, ActivationNotAuthorized, RunNotResumable, RunAlreadyStarted, RunJournalPrefixTruncated, } from "@cotal-ai/core";
|
|
28
|
+
import { Journal, JournalAppendRejected, run as runProgram, resume as resumeProgram, RunReleased, resolvePins, LANGUAGE_VERSION, PIN_DEFAULTS, } from "@cotal-ai/lang";
|
|
29
|
+
import { RunJournalStore } from "./journal-store.js";
|
|
30
|
+
/**
|
|
31
|
+
* A pause an operator can ask for while a run is being driven.
|
|
32
|
+
*
|
|
33
|
+
* The driver reads it before each effect it has not already recorded, so a pause takes effect at the
|
|
34
|
+
* next boundary and never in the middle of one. It is one-way on purpose: resuming is starting a
|
|
35
|
+
* drive again, which is the same act as any other takeover and goes through the barrier like one.
|
|
36
|
+
*/
|
|
37
|
+
export class PauseToken {
|
|
38
|
+
why;
|
|
39
|
+
pause(reason) {
|
|
40
|
+
this.why ??= reason;
|
|
41
|
+
}
|
|
42
|
+
get reason() {
|
|
43
|
+
return this.why;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const adoptionOf = (handler) => typeof handler?.adopted === "function"
|
|
47
|
+
? handler
|
|
48
|
+
: undefined;
|
|
49
|
+
/** Start a run that has never been driven. Refused if the journal already has records. */
|
|
50
|
+
export async function startRun(js, jsm, req) {
|
|
51
|
+
return await drive(js, jsm, req, "new");
|
|
52
|
+
}
|
|
53
|
+
/** Take over a run that already exists and drive it to quiescence. Refused if its journal is empty:
|
|
54
|
+
* a run whose records were purged is retired, and re-running it would repeat what it already did. */
|
|
55
|
+
export async function driveRun(js, jsm, req) {
|
|
56
|
+
return await drive(js, jsm, req, "existing");
|
|
57
|
+
}
|
|
58
|
+
async function drive(js, jsm, req, expect) {
|
|
59
|
+
// The record FIRST, because two of the things it holds decide whether this attempt may proceed at
|
|
60
|
+
// all: the pins the run was started under, and how far its journal is known to have got.
|
|
61
|
+
const record = await readRunRecord(req.kv, req.endpoint, req.runId);
|
|
62
|
+
const recordedStatus = record?.status?.value;
|
|
63
|
+
let pins;
|
|
64
|
+
let statusRevision;
|
|
65
|
+
if (expect === "new") {
|
|
66
|
+
if (record !== undefined) {
|
|
67
|
+
// Not `RunAlreadyStarted` from the journal — this run has a SPEC, which is the stronger
|
|
68
|
+
// statement: it was started, pinned, and those pins are not this attempt's to re-decide.
|
|
69
|
+
return { status: "released", reason: new RunAlreadyStarted(req.runId, 0) };
|
|
70
|
+
}
|
|
71
|
+
// Resolved ONCE, here, from the host clock read exactly once — from here on the epoch is a
|
|
72
|
+
// recorded fact, and a second read could not be the same run's epoch.
|
|
73
|
+
pins = resolvePins({
|
|
74
|
+
runId: req.runId,
|
|
75
|
+
...(req.seed !== undefined ? { seed: req.seed } : {}),
|
|
76
|
+
...(req.effectCeiling !== undefined ? { effectCeiling: req.effectCeiling } : {}),
|
|
77
|
+
...(req.stepBudget !== undefined ? { stepBudget: req.stepBudget } : {}),
|
|
78
|
+
}, req.handler.now());
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (record === undefined)
|
|
82
|
+
return { status: "released", reason: new RunNotResumable(req.runId, req.runId) };
|
|
83
|
+
// READ BACK, never re-derived. A default is a property of the interpreter, and the interpreter
|
|
84
|
+
// is the thing that may have changed between attempts.
|
|
85
|
+
pins = record.spec.value.pins;
|
|
86
|
+
statusRevision = record.status?.revision;
|
|
87
|
+
}
|
|
88
|
+
let appender;
|
|
89
|
+
try {
|
|
90
|
+
appender = await activateRun(js, jsm, {
|
|
91
|
+
space: req.space,
|
|
92
|
+
runId: req.runId,
|
|
93
|
+
holder: req.lease.holder,
|
|
94
|
+
fencingToken: req.lease.fencingToken,
|
|
95
|
+
epoch: req.lease.epoch,
|
|
96
|
+
takeoverId: req.lease.takeoverId,
|
|
97
|
+
at: req.handler.now(),
|
|
98
|
+
expect,
|
|
99
|
+
}, {
|
|
100
|
+
// BETWEEN the replay and the activation, which is the only place this check is worth
|
|
101
|
+
// anything: an activation appended over a rolled-back head is another record written into a
|
|
102
|
+
// journal already known to be wrong, at an ordinal the deleted records already used.
|
|
103
|
+
onReplayed: (replay) => {
|
|
104
|
+
assertJournalTailIntact(req.runId, recordedStatus, lastOrdinal(replay.records));
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Every one of these means "this driver does not hold this run", and none of them is a fact
|
|
110
|
+
// about the program. A caller that treated them as a run result would be recording a conclusion
|
|
111
|
+
// about work it never saw.
|
|
112
|
+
if (isNotOurs(e))
|
|
113
|
+
return { status: "released", reason: e };
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
const resumed = appender.steps();
|
|
117
|
+
// The explicit callback wins; otherwise a handler that declares `adopted` repairs its own state.
|
|
118
|
+
// The default is the point: the hook was optional AND unwired, which is indistinguishable at
|
|
119
|
+
// runtime from a driver that has nothing to repair.
|
|
120
|
+
const adopt = req.onActivated ?? adoptionOf(req.handler)?.adopted.bind(req.handler);
|
|
121
|
+
if (adopt !== undefined)
|
|
122
|
+
await adopt(resumed);
|
|
123
|
+
const store = new RunJournalStore(appender);
|
|
124
|
+
const journal = new Journal({
|
|
125
|
+
run: req.runId,
|
|
126
|
+
// The prefix the barrier validated and activated on, not a second read of the subject.
|
|
127
|
+
entries: resumed,
|
|
128
|
+
store,
|
|
129
|
+
});
|
|
130
|
+
// Asked before every effect that is not already recorded. Both reasons are the HOST's and neither
|
|
131
|
+
// is the program's, so neither may be recorded as its outcome — the run stops where its journal
|
|
132
|
+
// already says it is, and the next driver resumes from there.
|
|
133
|
+
const shouldStop = () => {
|
|
134
|
+
if (req.workExpiry !== undefined) {
|
|
135
|
+
const now = req.handler.now();
|
|
136
|
+
if (now >= req.workExpiry)
|
|
137
|
+
return `the work horizon ${req.workExpiry} passed at ${now}`;
|
|
138
|
+
}
|
|
139
|
+
return req.pause?.reason;
|
|
140
|
+
};
|
|
141
|
+
const options = {
|
|
142
|
+
runId: req.runId,
|
|
143
|
+
handler: req.handler,
|
|
144
|
+
shouldStop,
|
|
145
|
+
pins,
|
|
146
|
+
...(req.seed !== undefined ? { seed: req.seed } : {}),
|
|
147
|
+
...(req.file !== undefined ? { file: req.file } : {}),
|
|
148
|
+
...(req.effectCeiling !== undefined ? { effectCeiling: req.effectCeiling } : {}),
|
|
149
|
+
...(req.stepBudget !== undefined ? { stepBudget: req.stepBudget } : {}),
|
|
150
|
+
};
|
|
151
|
+
// The spec is created AFTER the activation, not before: until this driver holds the run it has
|
|
152
|
+
// decided nothing, and a spec written by a driver that then lost the activation race would pin a
|
|
153
|
+
// run for a winner who never agreed to those pins.
|
|
154
|
+
if (expect === "new") {
|
|
155
|
+
await createRunSpec(req.kv, req.endpoint, req.runId, {
|
|
156
|
+
pins,
|
|
157
|
+
createdAt: pins.startedAt,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const specRevision = expect === "new"
|
|
161
|
+
? (await readRunRecord(req.kv, req.endpoint, req.runId)).spec.revision
|
|
162
|
+
: record.spec.revision;
|
|
163
|
+
statusRevision = await note(req, "running", appender.journalHigh, specRevision, statusRevision);
|
|
164
|
+
try {
|
|
165
|
+
const result = expect === "new"
|
|
166
|
+
? await runProgram(req.source, { ...options, journal })
|
|
167
|
+
: await resumeProgram(req.source, journal, options);
|
|
168
|
+
await note(req, "completed", appender.journalHigh, specRevision, statusRevision);
|
|
169
|
+
return { status: "completed", result };
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
// L5010 is the journal saying it could not record — the run is not this driver's any more. It
|
|
173
|
+
// arrives here rather than at the append because the interpreter is what was holding the stack.
|
|
174
|
+
if (e instanceof JournalAppendRejected) {
|
|
175
|
+
// Recorded on a BEST-EFFORT basis and never allowed to mask the real answer: the record is a
|
|
176
|
+
// projection, and a driver that could not append to the journal may not be able to write here
|
|
177
|
+
// either. What it must not do is turn a lost run into a different-looking failure.
|
|
178
|
+
await noteQuietly(req, "released", appender.journalHigh, specRevision, statusRevision);
|
|
179
|
+
return { status: "released", reason: e };
|
|
180
|
+
}
|
|
181
|
+
// The host stopping is the same kind of answer: this driver no longer holds the run, and the
|
|
182
|
+
// program has neither failed nor finished.
|
|
183
|
+
if (e instanceof RunReleased) {
|
|
184
|
+
await note(req, "released", appender.journalHigh, specRevision, statusRevision);
|
|
185
|
+
return { status: "released", reason: e };
|
|
186
|
+
}
|
|
187
|
+
// A program that FAILED is a fact about the program, and the run record is where a reader looks
|
|
188
|
+
// for it. Recorded, then re-raised: swallowing it here would leave the caller believing the
|
|
189
|
+
// driver finished normally.
|
|
190
|
+
await note(req, "failed", appender.journalHigh, specRevision, statusRevision);
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function isNotOurs(e) {
|
|
195
|
+
return (e instanceof RunSuperseded ||
|
|
196
|
+
e instanceof RunJournalStalled ||
|
|
197
|
+
e instanceof StaleLeaseToken ||
|
|
198
|
+
e instanceof ActivationNotAuthorized ||
|
|
199
|
+
e instanceof RunNotResumable ||
|
|
200
|
+
e instanceof RunAlreadyStarted ||
|
|
201
|
+
e instanceof RunJournalPrefixTruncated);
|
|
202
|
+
}
|
|
203
|
+
/** The last ordinal in a replayed prefix, or -1 for a run that has none yet. */
|
|
204
|
+
function lastOrdinal(records) {
|
|
205
|
+
return records.length === 0 ? -1 : records[records.length - 1].record.n;
|
|
206
|
+
}
|
|
207
|
+
/** Write the run's status. Returns the new revision so the next write can pin it. */
|
|
208
|
+
async function note(req, state, journalHigh, observedSpecRevision, expectedRevision) {
|
|
209
|
+
return await writeRunStatus(req.kv, req.endpoint, req.runId, {
|
|
210
|
+
observedSpecRevision,
|
|
211
|
+
state,
|
|
212
|
+
holder: req.lease.holder,
|
|
213
|
+
epoch: req.lease.epoch,
|
|
214
|
+
fencingToken: req.lease.fencingToken,
|
|
215
|
+
journalHigh,
|
|
216
|
+
at: req.handler.now(),
|
|
217
|
+
}, expectedRevision);
|
|
218
|
+
}
|
|
219
|
+
/** The same write, where failing to make it must not replace the answer the caller is owed. */
|
|
220
|
+
async function noteQuietly(req, state, journalHigh, observedSpecRevision, expectedRevision) {
|
|
221
|
+
try {
|
|
222
|
+
await note(req, state, journalHigh, observedSpecRevision, expectedRevision);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
/* the record is a projection; the journal is the authority and it has already spoken */
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=run-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-driver.js","sourceRoot":"","sources":["../src/run-driver.ts"],"names":[],"mappings":"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"}
|