@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,57 @@
1
+ /**
2
+ * The version-2 entry for the driver's engine table: a run on the compiled engine, hosted.
3
+ *
4
+ * What this file owns is exactly the three decisions the engine leaves to its host, and nothing
5
+ * the engine already decided:
6
+ *
7
+ * - THE TRANSFORM STEP. The driver holds the author's source (a run record names the program an
8
+ * author wrote), so the module the engine executes is produced here, per attempt, by the pure
9
+ * `transform`. Nothing is cached: the transform re-validates, and a cached module would be a
10
+ * second answer to "what does this source compile to" with no way to notice the disagreement.
11
+ *
12
+ * - THE EVALUATOR CHOICE, made by naming the worker entry and refusing every alternative. The
13
+ * entry is the compiled `worker-entry.js` of the installed `@cotal-ai/lang` — the file whose
14
+ * `lockdown()` and zero-endowment Compartment the engine suite grades — resolved through the
15
+ * package's own exports map. There is no in-process route here on purpose: evaluating the
16
+ * module in this process would either lock down the daemon's own realm or skip confinement,
17
+ * and both are decisions nobody made. A missing artifact is a loud refusal naming the build
18
+ * step, never a quiet fall back to the walker.
19
+ *
20
+ * - WHERE THE EFFECTS LIVE. The driver's handler is a live object (sockets, a mesh client, a
21
+ * lease-bound appender), so the run uses the BRIDGED route: handler and durable store stay in
22
+ * this process, the thread forwards the seam over a MessagePort, and no credential enters the
23
+ * isolate that holds the program. The pending-entry-durable-before-the-effect ordering is the
24
+ * store's own await, unchanged, because the bridge preserves the async append contract.
25
+ *
26
+ * The driver's outcome contract is the walker's, so a failure that crosses the thread boundary is
27
+ * rehydrated into the class `drive()` grades: L5010 back into `JournalAppendRejected` (from the
28
+ * store failure this process itself just witnessed — the thread only reflected it), L5012 back
29
+ * into `RunReleased`, and everything else into an error carrying the same name, code and message
30
+ * it failed with inside the thread.
31
+ */
32
+ import { type EffectHandler, type JournalEntry, type JournalStore, type RunPins, type RunResult } from "@cotal-ai/lang";
33
+ export interface EngineHostRequest {
34
+ readonly source: string;
35
+ readonly runId: string;
36
+ readonly pins: RunPins;
37
+ readonly handler: EffectHandler;
38
+ readonly store: JournalStore;
39
+ /** The activated prefix: recorded entries for a resume, empty for a fresh run. */
40
+ readonly entries: readonly JournalEntry[];
41
+ readonly shouldStop: () => string | undefined;
42
+ readonly file?: string;
43
+ readonly seed?: string;
44
+ readonly effectCeiling?: number;
45
+ readonly stepBudget?: number;
46
+ }
47
+ /**
48
+ * The compiled worker entry of the installed `@cotal-ai/lang`, and the refusal when it is absent.
49
+ *
50
+ * `import.meta.resolve` answers from the exports map without touching the disk, so existence is
51
+ * checked here: in this repo the artifact exists only after `pnpm --filter @cotal-ai/lang
52
+ * build:emit`, and a driver that cannot name a confined evaluator does not get to invent one.
53
+ */
54
+ export declare function resolveWorkerEntry(): URL;
55
+ /** Run or resume a program on the compiled engine, with the walker's `RunResult`. */
56
+ export declare function runOnHostedEngine(req: EngineHostRequest): Promise<RunResult>;
57
+ //# sourceMappingURL=engine-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-host.d.ts","sourceRoot":"","sources":["../src/engine-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,EASL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,SAAS,EAEf,MAAM,gBAAgB,CAAC;AAoBxB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC9C,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;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,GAAG,CAUxC;AAqDD,qFAAqF;AACrF,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAsElF"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The version-2 entry for the driver's engine table: a run on the compiled engine, hosted.
3
+ *
4
+ * What this file owns is exactly the three decisions the engine leaves to its host, and nothing
5
+ * the engine already decided:
6
+ *
7
+ * - THE TRANSFORM STEP. The driver holds the author's source (a run record names the program an
8
+ * author wrote), so the module the engine executes is produced here, per attempt, by the pure
9
+ * `transform`. Nothing is cached: the transform re-validates, and a cached module would be a
10
+ * second answer to "what does this source compile to" with no way to notice the disagreement.
11
+ *
12
+ * - THE EVALUATOR CHOICE, made by naming the worker entry and refusing every alternative. The
13
+ * entry is the compiled `worker-entry.js` of the installed `@cotal-ai/lang` — the file whose
14
+ * `lockdown()` and zero-endowment Compartment the engine suite grades — resolved through the
15
+ * package's own exports map. There is no in-process route here on purpose: evaluating the
16
+ * module in this process would either lock down the daemon's own realm or skip confinement,
17
+ * and both are decisions nobody made. A missing artifact is a loud refusal naming the build
18
+ * step, never a quiet fall back to the walker.
19
+ *
20
+ * - WHERE THE EFFECTS LIVE. The driver's handler is a live object (sockets, a mesh client, a
21
+ * lease-bound appender), so the run uses the BRIDGED route: handler and durable store stay in
22
+ * this process, the thread forwards the seam over a MessagePort, and no credential enters the
23
+ * isolate that holds the program. The pending-entry-durable-before-the-effect ordering is the
24
+ * store's own await, unchanged, because the bridge preserves the async append contract.
25
+ *
26
+ * The driver's outcome contract is the walker's, so a failure that crosses the thread boundary is
27
+ * rehydrated into the class `drive()` grades: L5010 back into `JournalAppendRejected` (from the
28
+ * store failure this process itself just witnessed — the thread only reflected it), L5012 back
29
+ * into `RunReleased`, and everything else into an error carrying the same name, code and message
30
+ * it failed with inside the thread.
31
+ */
32
+ import { existsSync } from "node:fs";
33
+ import { fileURLToPath } from "node:url";
34
+ import { EffectError, Journal, JournalAppendRejected, RunReleased, assertNodeFloor, journalEntryKeyString, runInWorker, transform, } from "@cotal-ai/lang";
35
+ /**
36
+ * How often the host polls its own stop conditions (work horizon, pause) with NO bridge traffic to
37
+ * ride on. The per-effect granularity the walker has does not come from this timer: every effect is
38
+ * bracketed by durable appends, and the store below asks `shouldStop` on each one, so the flag is
39
+ * current at every effect boundary the thread checks it at. What only the timer covers is a stop
40
+ * with no append coming to carry it: one arriving in the worker's boot window, after the upfront
41
+ * sample below but before the thread's first pre-effect check (measured: without the timer, one
42
+ * effect the stop should have prevented is dispatched), and one arriving while an effect is parked
43
+ * in this process for hours. The timer PUBLISHES; the run honours a published stop at its next
44
+ * pre-effect check, which is `shouldStop`'s one reader in the language, on both engines. Two
45
+ * consequences follow and are the language's own properties rather than this host's: a stop during
46
+ * a long PURE stretch waits for the next effect boundary or the fuel refusal (L4013), and a stop
47
+ * that lands after the run's LAST pre-effect check is published but never observed, on the walker
48
+ * exactly as here (measured, both engines). A stop read at the fuel yield would be a change to
49
+ * both engines, not to this host.
50
+ */
51
+ const STOP_POLL_MS = 100;
52
+ /**
53
+ * The compiled worker entry of the installed `@cotal-ai/lang`, and the refusal when it is absent.
54
+ *
55
+ * `import.meta.resolve` answers from the exports map without touching the disk, so existence is
56
+ * checked here: in this repo the artifact exists only after `pnpm --filter @cotal-ai/lang
57
+ * build:emit`, and a driver that cannot name a confined evaluator does not get to invent one.
58
+ */
59
+ export function resolveWorkerEntry() {
60
+ const url = new URL(import.meta.resolve("@cotal-ai/lang/engine/worker-entry"));
61
+ if (!existsSync(fileURLToPath(url))) {
62
+ throw new Error(`the compiled engine's worker entry is not built: ${fileURLToPath(url)} does not exist. ` +
63
+ `The version-2 engine runs only the compiled artifact (its thread cannot load TypeScript sources); ` +
64
+ `build it with \`pnpm --filter @cotal-ai/lang build:emit\`.`);
65
+ }
66
+ return url;
67
+ }
68
+ /**
69
+ * A store wrapper that remembers the failure it threw, so the class the thread reports back as
70
+ * L5010 can be rebuilt from the facts THIS process witnessed rather than parsed out of a message.
71
+ */
72
+ class WitnessedStore {
73
+ inner;
74
+ beforeAppend;
75
+ failure;
76
+ /** `beforeAppend` is the stop check riding the append: every effect begins with a pending append
77
+ * and ends with a settled one, so asking here is the walker's own between-effects granularity. */
78
+ constructor(inner, beforeAppend) {
79
+ this.inner = inner;
80
+ this.beforeAppend = beforeAppend;
81
+ }
82
+ async append(entry) {
83
+ this.beforeAppend();
84
+ try {
85
+ await this.inner.append(entry);
86
+ }
87
+ catch (e) {
88
+ this.failure = { entry, reason: e };
89
+ throw e;
90
+ }
91
+ }
92
+ }
93
+ function rehydrate(failed, store) {
94
+ if (failed.code === "L5010") {
95
+ // The append that failed happened in this process; the thread's L5010 is its reflection. A
96
+ // reflection with no witnessed failure behind it means the two sides disagree about what
97
+ // happened, and that is said rather than papered over with a parsed message.
98
+ if (store.failure === undefined) {
99
+ return new Error(`the engine thread reported L5010 (journal append rejected) but this host's store recorded no failed append; the run cannot be graded: ${failed.message}`);
100
+ }
101
+ return new JournalAppendRejected(journalEntryKeyString(store.failure.entry), store.failure.entry.state, store.failure.reason);
102
+ }
103
+ if (failed.code === "L5012") {
104
+ return new RunReleased(failed.reason ?? failed.message);
105
+ }
106
+ // An effect failure carries its domain across whole, because callers branch on `kind` exactly as
107
+ // they do when the walker raises the same class in-process.
108
+ if (failed.code !== undefined && failed.kind !== undefined) {
109
+ return new EffectError(failed.code, failed.kind, failed.message, failed.detail);
110
+ }
111
+ const e = new Error(failed.message);
112
+ e.name = failed.name;
113
+ if (failed.code !== undefined)
114
+ e.code = failed.code;
115
+ return e;
116
+ }
117
+ /** Run or resume a program on the compiled engine, with the walker's `RunResult`. */
118
+ export async function runOnHostedEngine(req) {
119
+ // The floor the engine itself enforces in the thread, asked here first: a driver below it must
120
+ // refuse BEFORE it stamps a fresh run with a version it cannot execute.
121
+ assertNodeFloor(process.versions.node);
122
+ const entry = resolveWorkerEntry();
123
+ const { module } = transform(req.source, req.file !== undefined ? { file: req.file } : {});
124
+ // Assigned right after the worker exists; every append comes FROM that worker, so no check can
125
+ // fire before the assignment. Publishing is idempotent and the reason is read where the run has
126
+ // always read it.
127
+ let publish = () => { };
128
+ const store = new WitnessedStore(req.store, () => {
129
+ const reason = req.shouldStop();
130
+ if (reason !== undefined)
131
+ publish(reason);
132
+ });
133
+ const worker = runInWorker({
134
+ source: req.source,
135
+ module,
136
+ runId: req.runId,
137
+ handler: "bridged",
138
+ pins: req.pins,
139
+ entries: req.entries,
140
+ ...(req.file !== undefined ? { file: req.file } : {}),
141
+ ...(req.seed !== undefined ? { seed: req.seed } : {}),
142
+ ...(req.effectCeiling !== undefined ? { effectCeiling: req.effectCeiling } : {}),
143
+ ...(req.stepBudget !== undefined ? { stepBudget: req.stepBudget } : {}),
144
+ }, { entry, bridge: { handler: req.handler, store } });
145
+ publish = (reason) => worker.stop(reason);
146
+ // A horizon that passed before the run began is published before the thread's first pre-effect
147
+ // check, so the run stops with ZERO effects — the same answer the walker gives.
148
+ const already = req.shouldStop();
149
+ if (already !== undefined)
150
+ worker.stop(already);
151
+ // The host's stop conditions with no bridge traffic to ride on; see STOP_POLL_MS. The callback
152
+ // runs on the timer's own stack, where a throwing `shouldStop` would otherwise become an
153
+ // UNCAUGHT exception that bypasses every catch the driver holds and leaves the worker running -
154
+ // the walker route surfaces the same throw inside the driver's try, so this route must answer
155
+ // the same way: stop the run, and re-raise the fault from the await below, on the caller's
156
+ // stack. (The same throw at an append boundary already answers honestly: the store's
157
+ // before-append check rejects that append, and the run is graded through the L5010 path.)
158
+ let pollFault;
159
+ const poll = setInterval(() => {
160
+ try {
161
+ const reason = req.shouldStop();
162
+ if (reason !== undefined)
163
+ worker.stop(reason);
164
+ }
165
+ catch (e) {
166
+ pollFault = e ?? new Error("the host's stop check threw a falsy value");
167
+ clearInterval(poll);
168
+ worker.stop("the host's stop check itself threw; the driver re-raises the fault");
169
+ }
170
+ }, STOP_POLL_MS);
171
+ try {
172
+ const result = await worker.done;
173
+ if (pollFault !== undefined)
174
+ throw pollFault;
175
+ if (!result.ok)
176
+ throw rehydrate(result, store);
177
+ return {
178
+ value: result.value,
179
+ journal: new Journal({ run: req.runId, entries: result.entries }),
180
+ programHash: result.programHash,
181
+ pins: result.pins,
182
+ steps: result.steps,
183
+ };
184
+ }
185
+ finally {
186
+ clearInterval(poll);
187
+ }
188
+ }
189
+ //# sourceMappingURL=engine-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-host.js","sourceRoot":"","sources":["../src/engine-host.ts"],"names":[],"mappings":"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"}
package/dist/fork.d.ts ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Fork a run from a named step: the fork cut, and the two things a fork must not re-decide.
3
+ *
4
+ * `fork(runId, fromStepKey)` is the repair verb. It does not roll the parent back — the parent is
5
+ * untouched — it starts a NEW run that owns the parent's history up to a named step and re-runs from
6
+ * there. Two decisions inside that sentence carry the whole design:
7
+ *
8
+ * **The cut is computed by a DRY WALK IN MIGRATION MODE, and the mode is load-bearing rather than a
9
+ * default.** A resume's replay short-circuits a settled scope: it takes the scope's recorded outcome
10
+ * and never walks its branches, so every entry underneath it is accounted for in one step. That is
11
+ * exactly right for a resume, which only needs to get past it, and exactly wrong here — the step a
12
+ * caller wants to cut at can be INSIDE such a scope, and a walk that never entered it would either
13
+ * miss the cut entirely or sweep entries that come after the cut point into the prefix. A fork with a
14
+ * silently-too-large prefix is the worst available outcome: it looks like a fork, drives like a fork,
15
+ * and has already replayed the work the caller forked to avoid.
16
+ *
17
+ * **The prefix inherits the parent's pins UNCHANGED, seed included.** The child gets a new run id and
18
+ * `resolvePins` defaults a seed to the run id, so re-resolving pins for the child would reseed it —
19
+ * and a reseeded prefix redecides every pure draw inside history it was supposed to be copying. A
20
+ * parent that drew `left` would give a child that draws `right`, and nothing would diverge, because a
21
+ * `pick` is pure and no entry records it. So the parent's `RunPins` are copied verbatim; the fresh
22
+ * derivation begins at the frontier, which is what "fork" means.
23
+ *
24
+ * What this file does NOT do, named rather than implied:
25
+ * - It does not respawn agents. `onFork` is specified to mint a fresh agent at the frontier, and
26
+ * `spawn` rides the durable-action machinery this host does not have, so a cut containing a
27
+ * spawn is REFUSED (L5019) rather than copied and hoped about.
28
+ * - It does not cut worktree branches. There is no worktree plane in this tree; a caller that
29
+ * asks for one is refused rather than told a branch exists.
30
+ * - It does not record LINEAGE. The child's run record cannot say it is a fork of anything: a
31
+ * run's spec has no such field, and inventing one changes the run record's shape — the kind of
32
+ * change the `migration` record was raised for a decision on rather than made here.
33
+ * `commitFork` reports `lineageRecorded: false` so the gap is something a caller reads rather
34
+ * than something a reader has to notice.
35
+ */
36
+ import type { KV } from "@nats-io/kv";
37
+ import { Journal, type JournalEntry, type JournalInit, type JournalStore, type LookupVerdict, type RunPins, type StepKey } from "@cotal-ai/lang";
38
+ /** One reason a fork was refused, carrying the code a reader repairs against — the `L` catalogue
39
+ * in `@cotal-ai/lang`. */
40
+ export interface ForkRefusal {
41
+ readonly code: string;
42
+ readonly step?: string;
43
+ readonly why: string;
44
+ }
45
+ export interface ForkPlan {
46
+ readonly parent: string;
47
+ readonly child: string;
48
+ readonly at: number;
49
+ readonly actor: string;
50
+ /** The step the child re-runs FROM. Never part of the cut: the cut is what happened before it. */
51
+ readonly fromStep: string;
52
+ /** The parent's pins, verbatim — what the child must be created under, not what it would resolve. */
53
+ readonly pins: RunPins;
54
+ /** The entries the child inherits as history, in the parent's recorded order. */
55
+ readonly cut: readonly JournalEntry[];
56
+ readonly admissible: boolean;
57
+ readonly refusals: readonly ForkRefusal[];
58
+ }
59
+ export interface ForkRequest {
60
+ readonly parent: string;
61
+ /** The child's run id. Allocated by the caller, because minting an id is not this file's decision. */
62
+ readonly child: string;
63
+ /** A journal key string, e.g. `/checkpoint:approve#0`. Not a sequence number: it survives edits. */
64
+ readonly fromStepKey: string;
65
+ /** The program the parent was running. */
66
+ readonly source: string;
67
+ readonly entries: readonly JournalEntry[];
68
+ /** Read back from the parent's run record. Never re-derived: see the header. */
69
+ readonly pins: RunPins;
70
+ readonly actor: string;
71
+ readonly now: () => number;
72
+ readonly file?: string;
73
+ /**
74
+ * The `newProgramHash` fork option. Accepted only to be REFUSED: the run record carries no
75
+ * program hash to pin — a declared but unbuilt capability this branch does not invent — so a fork
76
+ * "pinned to" one would record nothing at all and the caller would have been told it happened.
77
+ */
78
+ readonly newProgramHash?: string;
79
+ /** Worktree branches for the child. There is no worktree plane in this tree, so asking for one
80
+ * is refused. */
81
+ readonly worktreeBranches?: boolean;
82
+ }
83
+ /** The walk reached the step the caller wants to cut at. Not an error: it is where the cut ENDS. */
84
+ export declare class CutReached extends Error {
85
+ readonly step: string;
86
+ constructor(step: string);
87
+ }
88
+ /**
89
+ * A read-only journal that stops the walk when it first LOOKS UP a named step.
90
+ *
91
+ * Before delegating, never after: `Journal.lookup` marks a key consumed as its first act, so a stop
92
+ * placed after the delegation would put the cut step itself into the prefix — and the child would
93
+ * then replay the very step it was forked to re-run.
94
+ *
95
+ * Exported because the fork's correctness claim is about WHICH WALK produced the cut, and a suite
96
+ * that cannot run the wrong walk cannot show the right one is different from it.
97
+ */
98
+ export declare class CutJournal extends Journal {
99
+ private readonly cutAt;
100
+ constructor(init: JournalInit, cutAt: string);
101
+ lookup(key: StepKey, inputHash: string): LookupVerdict;
102
+ }
103
+ /**
104
+ * Compute the fork cut. Reads; never writes.
105
+ *
106
+ * The plan is the product whether or not the fork is admissible — a refused fork owes the caller the
107
+ * code and the step, because that is what makes the next attempt a repair rather than a guess.
108
+ */
109
+ export declare function planFork(req: ForkRequest): Promise<ForkPlan>;
110
+ export interface ForkCommitResult {
111
+ readonly child: string;
112
+ readonly copied: number;
113
+ /**
114
+ * Always false, and reported rather than omitted.
115
+ *
116
+ * The child's record cannot say it is a fork of anything: `RunSpecValue` has no lineage field,
117
+ * and adding one — or a `fork` record kind beside `migration` — changes the run record's shape,
118
+ * which is a decision to raise rather than one to make in this file. A caller that needs the
119
+ * lineage has it in the {@link ForkPlan} it passed; nothing durable carries it yet.
120
+ */
121
+ readonly lineageRecorded: false;
122
+ }
123
+ /**
124
+ * Copy the cut into the child, and only then create the child's record.
125
+ *
126
+ * THE ORDER IS THE DURABILITY ARGUMENT. A crash between the two writes is the case this has to be
127
+ * right for, and writing the spec first would leave a child that has a record and half a history —
128
+ * which `driveRun` would happily take over, replaying a prefix that stops in the middle of the
129
+ * parent's past and then performing effects from there. Written last, a crash leaves journal entries
130
+ * under an id with no record: `driveRun` refuses a run it cannot read a record for, and `startRun`
131
+ * refuses a run whose journal already has records. **Neither entry point will touch it**, which is
132
+ * the failure a partial fork should have.
133
+ *
134
+ * The entries are rewritten onto the child's run id as they are copied. That is not bookkeeping: a
135
+ * journal takes only its own run's entries, and the prefix is now the CHILD's history — the parent's
136
+ * copy stays exactly where it was, because fork is not rollback.
137
+ */
138
+ export declare function commitFork(kv: KV, endpoint: string, plan: ForkPlan, store: JournalStore): Promise<ForkCommitResult>;
139
+ /** A fork the plan refused, offered for commit anyway. The plan carries the per-step reason. */
140
+ export declare class ForkNotAdmissible extends Error {
141
+ readonly plan: ForkPlan;
142
+ constructor(plan: ForkPlan);
143
+ }
144
+ //# sourceMappingURL=fork.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../src/fork.ts"],"names":[],"mappings":"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"}