@cotal-ai/runtime 0.0.1 → 0.25.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.
@@ -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,EAWL,KAAK,aAAa,EAClB,KAAK,YAAY,EAEjB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAyHxB;;;;;;;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"}