@parcha/pi-durable-disk 0.1.0-beta.14 → 0.1.0-beta.16

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,236 @@
1
+ import type { Delegation } from "disk";
2
+ import { PdaError } from "./errors.js";
3
+ import { type RunRecord } from "./status.js";
4
+ import { type EnsureOptions, type HostBox, type HostDriver, type SupervisorControl } from "./supervise.js";
5
+ export type PlaceKill = "process" | "box";
6
+ /** The run's paths in the place: its id, its directory, and the `work` and `store` under it. */
7
+ export type PlacePaths = {
8
+ id: string;
9
+ root: string;
10
+ work: string;
11
+ store: string;
12
+ };
13
+ /**
14
+ * How the run stood when a runner ended: the instance's own report, or `run.json` as it left it. `status` is the run's
15
+ * word: `complete` when it delivered, else `failed`. `models`, on any status: the role-to-model ids the run's live parts
16
+ * chose (`lead`, `eyes`, ...), when the app reported a plain object of strings; absent otherwise, never `{}` in its place.
17
+ */
18
+ export type PlaceOutcome = {
19
+ status: string;
20
+ generation: number | null;
21
+ code?: string | null;
22
+ message?: string;
23
+ record?: unknown;
24
+ storeSha256?: string;
25
+ models?: Record<string, string>;
26
+ };
27
+ /** How a runner ended. `exit`: the exit code or signal (local), or the host's status (`stopped`, `failed`, `gone`) or `SIGKILL` when the place killed it (archil). */
28
+ export type PlaceExit = {
29
+ exit: number | string | null;
30
+ signal: string | null;
31
+ outcome?: PlaceOutcome;
32
+ stderr: string;
33
+ };
34
+ /** One instance of the run in a place. `epoch`: 1 on a plain directory; on a disk, the generation the instance opens. */
35
+ export type PlaceRunner = {
36
+ epoch: number;
37
+ pid?: number;
38
+ exited: Promise<PlaceExit>;
39
+ };
40
+ /** What a runner is handed: its number in the run, its environment, and for a place with a token, the attempt's mount token. */
41
+ export type RunnerOrder = {
42
+ gen: number;
43
+ env: Record<string, string>;
44
+ stdin?: string;
45
+ };
46
+ /** What the place reads of the run, the way it reads it: `run.json`, and on a disk the run's delegations. */
47
+ /**
48
+ * What the place reads of the run, the way it reads it: `run.json`; on a disk, the run's delegations; and the boxes the
49
+ * place made for its runners, each as a receipt (`HostBox`: provider, kind, from, to, list price per second and where
50
+ * that price was read), when its host driver makes boxes. A box the place released stays listed with its end.
51
+ */
52
+ export type PlaceStatus = {
53
+ record: RunRecord | null;
54
+ delegations?: Pick<Delegation, "clientId" | "inodeId" | "path" | "isOrphaned" | "isPending">[];
55
+ boxes?: HostBox[];
56
+ };
57
+ /** What a recovery did. A takeover on a disk lists what it revoked, and `ms` is the whole of it (revoke and start). */
58
+ export type PlaceRecovery = {
59
+ runner: PlaceRunner;
60
+ restored?: {
61
+ checkpoint: string;
62
+ head: number;
63
+ };
64
+ replayed?: {
65
+ from: number;
66
+ to: number;
67
+ };
68
+ revoked?: Pick<Delegation, "clientId" | "inodeId" | "path" | "isOrphaned">[];
69
+ ms?: number;
70
+ };
71
+ /**
72
+ * What a runner announces to its supervisor: a crossing it took, or a request to be killed at one (it parks there until
73
+ * the kill). `signals-failed` is the place's own last word when it could not read the requests: it ends the iterator,
74
+ * which never rejects. `code` is the failing read's error code when it has one (CONTROL_API_FAILED for a read that timed
75
+ * out), else SIGNALS_FAILED.
76
+ */
77
+ export type PlaceSignal = {
78
+ kind: "crossing" | "kill-request";
79
+ crossing: {
80
+ point: string;
81
+ n: number;
82
+ pid?: number;
83
+ info?: Record<string, unknown>;
84
+ };
85
+ /** The attempt's nonce the runner wrote into its request, lifted out of `crossing`: the supervisor honors a kill request only with the nonce it minted for that attempt. */
86
+ nonce?: string;
87
+ } | {
88
+ kind: "signals-failed";
89
+ code: string;
90
+ message: string;
91
+ };
92
+ /**
93
+ * What the supervisor hands a place factory: the run's paths as it computed them, how to take the boxes the run made,
94
+ * and where attempt n's runner writes its kill request (a file of the supervisor's machine).
95
+ */
96
+ export type PlaceContext = {
97
+ paths: PlacePaths;
98
+ self?: string;
99
+ destroyBoxes?: () => void | Promise<void>;
100
+ requestFile?: (attempt: number) => string;
101
+ };
102
+ /**
103
+ * Where an attempt's runner records (its RECORD_DIR, in the place) and how the place brings that recording home. For a
104
+ * place whose runners cannot write the supervisor's recording directory.
105
+ */
106
+ export interface PlaceRecordHome {
107
+ /** The directory, in the place, attempt `attempt`'s runner records into. */
108
+ dir(attempt: number): string;
109
+ /**
110
+ * Brings home into `into` what the runners recorded: blobs first, then the new whole lines of each `.spool/*.jsonl`.
111
+ * Idempotent; `final` is the call after the runner ended, when nothing is still landing.
112
+ */
113
+ bringHome(into: string, options?: {
114
+ final?: boolean;
115
+ }): Promise<void>;
116
+ /** How far the place's clock is from the supervisor's, when it knows. */
117
+ readonly clockSkewMs?: number;
118
+ }
119
+ export interface Place {
120
+ /** `local` or `archil`, for the recording's lines. */
121
+ readonly name: string;
122
+ readonly paths: PlacePaths;
123
+ /** A place whose instance opens a claim mints each attempt's mount token: the supervisor passes it as the runner's stdin, never in argv or env. */
124
+ token?(attempt: number): string | undefined | Promise<string | undefined>;
125
+ /** What this place can lose. */
126
+ readonly kills: ReadonlyArray<PlaceKill>;
127
+ start(order: RunnerOrder): Promise<PlaceRunner>;
128
+ /**
129
+ * Where attempt `attempt`'s runner writes its kill request, as a path in the place (the supervisor puts it in the
130
+ * runner's order). A place with `signals` has it, and `signals(attempt)` reads what is written there.
131
+ */
132
+ requestFile?(attempt: number): string;
133
+ /**
134
+ * The kill requests (and crossings) attempt `attempt`'s runner announces. The supervisor stops reading with `return()`
135
+ * once the runner has exited, and `return()` ends a pending `next()`.
136
+ */
137
+ signals?(attempt: number): AsyncIterable<PlaceSignal>;
138
+ /** Takes the runner away abruptly, as a crash would. Safe on a runner that is already gone. */
139
+ kill(runner: PlaceRunner, kind: PlaceKill): Promise<void>;
140
+ status?(): Promise<PlaceStatus>;
141
+ /** Kills `last` if it still runs, then starts the next runner. */
142
+ recover(last: PlaceRunner, kind: PlaceKill, order: RunnerOrder): Promise<PlaceRecovery>;
143
+ checkpoint?(): Promise<{
144
+ taken: boolean;
145
+ heldBy?: string;
146
+ }>;
147
+ move?(to: Place): Promise<Record<string, unknown>>;
148
+ /** Lets the run go: stops what still runs (its commands too) and removes what the place made for it (tokens), never the run's directory. */
149
+ release(): Promise<void>;
150
+ /** A place whose runners record where the supervisor cannot read says where, and brings the recording home. */
151
+ readonly recordHome?: PlaceRecordHome;
152
+ }
153
+ export type PlaceErrorCode = "INVALID_ARGUMENT" | "HELD" | "START_TIMED_OUT" | "STATUS_FAILED" | "KILL_UNSUPPORTED" | "RELEASE_FAILED" | "BOX_GONE" | "CARRY_FAILED" | "BLOB_MISMATCH" | "DIRS_REFUSED";
154
+ export declare class PlaceError extends PdaError {
155
+ constructor(code: PlaceErrorCode, message: string, options?: {
156
+ cause?: unknown;
157
+ });
158
+ }
159
+ export interface LocalPlaceOptions {
160
+ /** The app module `run --app` loads. */
161
+ app: string;
162
+ /** The run's directory. Default: the context's `paths.root`. */
163
+ root?: string;
164
+ /** The instance program; `run` and its flags are appended. Default: this Node and the package's CLI. */
165
+ command?: string[];
166
+ /** Extra flags for `run` (heartbeat periods, a park threshold). */
167
+ runArgs?: string[];
168
+ }
169
+ /**
170
+ * The run on a directory of this machine: each runner is `pi-durable-disk run --dir <root> --id <id> --app <app>`, a
171
+ * child in its own process group, with `{ PATH, ...order.env }` and nothing else of this process's environment. No disk,
172
+ * no key and no token. A process kill is SIGKILL to the group, and the next runner resumes the run from the directory; a
173
+ * box kill also takes the boxes the run made (`destroyBoxes`), so `kills` has `box` only when the context gives that.
174
+ * A runner's outcome is the instance's `{"outcome":...}` line on stdout, or, without one, `run.json` as it left it.
175
+ */
176
+ export declare function localPlace(options: LocalPlaceOptions): (ctx: PlaceContext) => Place;
177
+ export interface ArchilPlaceOptions {
178
+ disk: string;
179
+ region: string;
180
+ /**
181
+ * The disk's control API (`archilControl`). The place holds it; no instance ever does. With `listObjects` (as
182
+ * `archilControl` has), the place has `recordHome`.
183
+ */
184
+ control: SupervisorControl & Partial<RecordControl>;
185
+ /**
186
+ * The driver that runs the instance (`localHost`, `dockerHost`, `daytonaHost`), set up with the app the instance runs.
187
+ * Its `kill` and `kills` are the place's.
188
+ */
189
+ host: HostDriver;
190
+ /** The run's id on the disk. Default: the context's `paths.id`. */
191
+ id?: string;
192
+ /** Where the driver mounts runs, for `paths`. Default: the driver's `mountRoot`, else /mnt/archil. */
193
+ mountRoot?: string;
194
+ /** More for each start's `ensureRunning`: the run directory's owner (`create`), token prefix and TTL, timeouts. */
195
+ ensure?: Omit<EnsureOptions, "control" | "demand" | "mountToken">;
196
+ /** How often a runner's host status is read. Default 1 s. */
197
+ pollMs?: number;
198
+ /** How long a start waits on a mount another start has in flight. Default 60 s. */
199
+ startTimeoutMs?: number;
200
+ /** How often `signals` reads the attempt's kill request over S3. Default 500 ms (the runner waits at its crossing). */
201
+ signalPollMs?: number;
202
+ /**
203
+ * How long those reads may fail (anything but a missing object) without a break before the iterator yields one
204
+ * `signals-failed` and ends. Default 60 s.
205
+ */
206
+ signalFailMs?: number;
207
+ }
208
+ /** The listing `recordHome` reads the run's recordings with: S3 ListObjects, each object's size when the API gives it. */
209
+ export interface RecordControl {
210
+ listObjects(prefix: string, options?: {
211
+ recursive?: boolean;
212
+ }): Promise<{
213
+ objects: {
214
+ key: string;
215
+ size?: number;
216
+ }[];
217
+ }>;
218
+ }
219
+ /**
220
+ * Every byte of `bytes` to `fd`: a write may take fewer bytes than it was given, so it goes on from where the last one
221
+ * stopped. A write that takes none fails, as one that throws (a full disk) does.
222
+ */
223
+ export declare function writeAll(fd: number, bytes: Uint8Array, write?: (fd: number, bytes: Uint8Array, offset: number, length: number) => number): void;
224
+ /**
225
+ * The run on an Archil disk, its instance run by a host driver. `token(attempt)` mints the attempt's mount token through
226
+ * the place's control, and the runner it is handed to mounts with it. A start is `ensureRunning` (it creates the run's
227
+ * directory with `ensure.create` and writes the start mark) with that token and the order's environment. A kill is the
228
+ * driver's `kill`: `process` or `box`, whichever the driver takes. A recovery is the takeover: the dead holder's
229
+ * delegations revoked (its lease counts as expired, since the place killed it) and the next instance started, which
230
+ * `revoked` and `ms` report. A runner writes its kill request in the run's directory (`requestFile`), and `signals` reads
231
+ * it over S3. A runner ends when the driver no longer reports it running, or when the place killed it; its
232
+ * outcome is `run.json` read over S3, with `code`, `message`, `record` and `storeSha256` from the detail of the last
233
+ * status the app set. `release` stops every instance it started and removes every token it minted; the run's directory
234
+ * stays (`deleteRunTree`).
235
+ */
236
+ export declare function archilPlace(options: ArchilPlaceOptions): (ctx: PlaceContext) => Place;