@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.
- package/dist/engine-host.d.ts +57 -0
- package/dist/engine-host.d.ts.map +1 -0
- package/dist/engine-host.js +189 -0
- package/dist/engine-host.js.map +1 -0
- 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 +335 -0
- package/dist/run-driver.js.map +1 -0
- package/package.json +31 -16
- package/README.md +0 -6
|
@@ -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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"}
|