@cotal-ai/cli 0.50.0 → 0.51.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.
Files changed (88) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +24 -1
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +18 -1
  6. package/dist/commands/agents.js.map +1 -1
  7. package/dist/commands/down.d.ts.map +1 -1
  8. package/dist/commands/down.js +114 -5
  9. package/dist/commands/down.js.map +1 -1
  10. package/dist/commands/endpoints.d.ts.map +1 -1
  11. package/dist/commands/endpoints.js +2 -1
  12. package/dist/commands/endpoints.js.map +1 -1
  13. package/dist/commands/join.d.ts.map +1 -1
  14. package/dist/commands/join.js +25 -0
  15. package/dist/commands/join.js.map +1 -1
  16. package/dist/commands/meshes-add.d.ts +2 -54
  17. package/dist/commands/meshes-add.d.ts.map +1 -1
  18. package/dist/commands/meshes-add.js +20 -105
  19. package/dist/commands/meshes-add.js.map +1 -1
  20. package/dist/commands/meshes.d.ts.map +1 -1
  21. package/dist/commands/meshes.js +5 -2
  22. package/dist/commands/meshes.js.map +1 -1
  23. package/dist/commands/service.d.ts +17 -0
  24. package/dist/commands/service.d.ts.map +1 -0
  25. package/dist/commands/service.js +541 -0
  26. package/dist/commands/service.js.map +1 -0
  27. package/dist/commands/spawn.d.ts +44 -2
  28. package/dist/commands/spawn.d.ts.map +1 -1
  29. package/dist/commands/spawn.js +292 -45
  30. package/dist/commands/spawn.js.map +1 -1
  31. package/dist/commands/status.d.ts.map +1 -1
  32. package/dist/commands/status.js +11 -4
  33. package/dist/commands/status.js.map +1 -1
  34. package/dist/commands/sync.d.ts +50 -0
  35. package/dist/commands/sync.d.ts.map +1 -0
  36. package/dist/commands/sync.js +284 -0
  37. package/dist/commands/sync.js.map +1 -0
  38. package/dist/commands/up.d.ts.map +1 -1
  39. package/dist/commands/up.js +71 -13
  40. package/dist/commands/up.js.map +1 -1
  41. package/dist/commands/update.d.ts.map +1 -1
  42. package/dist/commands/update.js +26 -5
  43. package/dist/commands/update.js.map +1 -1
  44. package/dist/commands/use.js +1 -1
  45. package/dist/commands/use.js.map +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +27 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/manifest/apply.d.ts.map +1 -1
  50. package/dist/lib/manifest/apply.js +2 -0
  51. package/dist/lib/manifest/apply.js.map +1 -1
  52. package/dist/lib/manifest/model.d.ts +2 -0
  53. package/dist/lib/manifest/model.d.ts.map +1 -1
  54. package/dist/lib/manifest/prepare.d.ts +2 -0
  55. package/dist/lib/manifest/prepare.d.ts.map +1 -1
  56. package/dist/lib/manifest/prepare.js +1 -0
  57. package/dist/lib/manifest/prepare.js.map +1 -1
  58. package/dist/lib/manifest/resolve.js +1 -0
  59. package/dist/lib/manifest/resolve.js.map +1 -1
  60. package/dist/lib/manifest/schema.d.ts +2 -0
  61. package/dist/lib/manifest/schema.d.ts.map +1 -1
  62. package/dist/lib/manifest/schema.js +2 -0
  63. package/dist/lib/manifest/schema.js.map +1 -1
  64. package/dist/lib/restore.d.ts +3 -0
  65. package/dist/lib/restore.d.ts.map +1 -1
  66. package/dist/lib/restore.js +17 -0
  67. package/dist/lib/restore.js.map +1 -1
  68. package/dist/lib/seat-admission.d.ts +51 -0
  69. package/dist/lib/seat-admission.d.ts.map +1 -0
  70. package/dist/lib/seat-admission.js +81 -0
  71. package/dist/lib/seat-admission.js.map +1 -0
  72. package/dist/lib/seat-capture.d.ts +47 -0
  73. package/dist/lib/seat-capture.d.ts.map +1 -0
  74. package/dist/lib/seat-capture.js +195 -0
  75. package/dist/lib/seat-capture.js.map +1 -0
  76. package/dist/lib/seat-restore.d.ts +39 -0
  77. package/dist/lib/seat-restore.d.ts.map +1 -0
  78. package/dist/lib/seat-restore.js +249 -0
  79. package/dist/lib/seat-restore.js.map +1 -0
  80. package/dist/lib/seat-resume.d.ts +24 -0
  81. package/dist/lib/seat-resume.d.ts.map +1 -0
  82. package/dist/lib/seat-resume.js +127 -0
  83. package/dist/lib/seat-resume.js.map +1 -0
  84. package/package.json +4 -4
  85. package/dist/lib/join-target.d.ts +0 -173
  86. package/dist/lib/join-target.d.ts.map +0 -1
  87. package/dist/lib/join-target.js +0 -335
  88. package/dist/lib/join-target.js.map +0 -1
@@ -0,0 +1,39 @@
1
+ import { type SeatCheckpoint } from "@cotal-ai/workspace";
2
+ /** One admitted seat this restore is about to place, with the destination paths it lands on. */
3
+ export interface SeatRestoreRequest {
4
+ readonly name: string;
5
+ /** The directory the checkpoint was read from, re-verified here over the files as they are now. */
6
+ readonly directory: string;
7
+ readonly checkpoint: SeatCheckpoint;
8
+ /** The working tree the manager will launch this seat in, from the retained inventory. */
9
+ readonly cwd: string;
10
+ /** The session id the retained inventory says this seat reopens, when it has one. A pointer that
11
+ * names a different session is not this seat's pointer. */
12
+ readonly inventorySessionId?: string;
13
+ }
14
+ export interface SeatRestoreOptions {
15
+ /** The destination workspace root, the anchor a `workspace-root` destination resolves against. */
16
+ readonly root: string;
17
+ readonly seats: readonly SeatRestoreRequest[];
18
+ /** Reports each promoted seat, so the command can print what it placed. */
19
+ readonly onPromoted?: (seat: {
20
+ name: string;
21
+ cwd: string;
22
+ superseded?: string;
23
+ }) => void;
24
+ }
25
+ /** Every refusal below. The message names the seat, the step, and what git or tar actually said. */
26
+ export declare class SeatRestoreError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ /**
30
+ * Restore every admitted seat, or leave every live `cwd` as it was.
31
+ *
32
+ * Two phases. Every seat is staged first, so a refusal on the last seat costs nothing on the first.
33
+ * Only once every staging directory is complete does any promotion happen, and a failure during the
34
+ * promotion phase puts back what this call moved: the renames it made are undone in reverse and the
35
+ * session files it exclusively created are removed, so the destination is where it started and the
36
+ * whole set can be retried once the refusal is understood.
37
+ */
38
+ export declare function restoreSeatCheckpoints(options: SeatRestoreOptions): void;
39
+ //# sourceMappingURL=seat-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-restore.d.ts","sourceRoot":"","sources":["../../src/lib/seat-restore.ts"],"names":[],"mappings":"AAoCA,OAAO,EAEL,KAAK,cAAc,EAGpB,MAAM,qBAAqB,CAAC;AAE7B,gGAAgG;AAChG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,kGAAkG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC9C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1F;AAED,oGAAoG;AACpG,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AA4JD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA+BxE"}
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Restore: the destination consumes an admitted seat checkpoint before the seat is alive.
3
+ *
4
+ * This is section 6 step 7 of `docs/design/session-recovery.md`, which that record gives as a shell
5
+ * sequence and calls manual only because no command carried it. Every rule stated there is a rule
6
+ * here, and the reasons are the same ones measured there:
7
+ *
8
+ * - The sequence stages. A clone lands in `<cwd>.incoming`, every verification and both applies run
9
+ * inside it, and the two renames that promote it are the only steps that touch the path the seat
10
+ * will use. A failed restore leaves the live `cwd` as it was.
11
+ * - An existing `<cwd>.incoming` refuses. A staging directory from a failed run is the only record
12
+ * of what failed, so nothing here removes one: an operator inspects it and removes it by hand.
13
+ * The shell version's `set -e` exists because a bare test reported and continued, and the final
14
+ * `mv` then promoted a leftover directory that was never restored. Here every step throws.
15
+ * - `git clone` rather than a fetch into an existing repository, because the refspec form that
16
+ * would populate it fails against the branch HEAD is on, and clone avoids the question.
17
+ * - The recorded base is verified in the clone. A bundle that does not contain it does not apply to
18
+ * the tree this checkpoint describes.
19
+ * - Two applies, `--index` first and the worktree diff second, both `--binary --allow-empty`. The
20
+ * order is what puts staged content back in the index rather than only in the worktree, and
21
+ * `--allow-empty` is why the cleanest possible seat is still restorable.
22
+ * - A pre-existing `cwd` is moved aside under a timestamped name rather than deleted, so a wrong
23
+ * checkpoint costs a rename instead of a tree.
24
+ *
25
+ * Two things this adds over the manual sequence. The session files travel with the tree, to the
26
+ * paths the destination's own connector will read. And the promoted tree is re-read with
27
+ * `git status --porcelain` and compared to what the checkpoint recorded, so a restore that applied
28
+ * without error and still produced a different index is a refusal rather than a silent difference.
29
+ *
30
+ * git and tar run as child processes through `execFileSync`, never a shell string.
31
+ */
32
+ import { execFileSync } from "node:child_process";
33
+ import { createHash } from "node:crypto";
34
+ import { closeSync, constants, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeSync } from "node:fs";
35
+ import { homedir } from "node:os";
36
+ import { dirname, join, resolve } from "node:path";
37
+ import { assertSeatCheckpointIntegrity, } from "@cotal-ai/workspace";
38
+ /** Every refusal below. The message names the seat, the step, and what git or tar actually said. */
39
+ export class SeatRestoreError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = "SeatRestoreError";
43
+ }
44
+ }
45
+ function run(command, args, cwd, seat, step) {
46
+ try {
47
+ // Both streams are captured. git writes progress and advice to stderr even when it succeeds,
48
+ // and the default here would forward that to the operator's terminal as if the resume had
49
+ // failed; on a failure the same bytes are what the refusal has to name.
50
+ return execFileSync(command, args, {
51
+ cwd,
52
+ encoding: "utf8",
53
+ stdio: ["ignore", "pipe", "pipe"],
54
+ maxBuffer: 1024 * 1024 * 1024,
55
+ });
56
+ }
57
+ catch (error) {
58
+ const failure = error;
59
+ const output = `${failure.stderr ?? ""}${failure.stdout ?? ""}`.trim() || failure.message || "no output";
60
+ throw new SeatRestoreError(`seat ${seat}: ${step} failed (${command}): ${output}`);
61
+ }
62
+ }
63
+ /**
64
+ * Where one captured session file lands on THIS host.
65
+ *
66
+ * The anchors are the destination's own: its workspace root, the account home it runs under, and
67
+ * the seat's working tree. The record carries a relative path and the reader already refused an
68
+ * absolute one or a `..` segment, so the result cannot leave the anchor it names.
69
+ */
70
+ function destinationPath(destination, root, cwd) {
71
+ const anchor = destination.anchor === "workspace-root" ? root : destination.anchor === "home" ? homedir() : cwd;
72
+ return join(anchor, destination.path);
73
+ }
74
+ /**
75
+ * Write one captured session file where the connector will read it.
76
+ *
77
+ * Durable, private and exclusive. A file already at that path is judged by CONTENT, which is the
78
+ * same rule the rest of the checkpoint is judged by: a destination whose file already hashes to the
79
+ * recorded digest holds the bytes this step exists to place, and that is the ordinary same-host
80
+ * resume, where nothing removed the transcript the seat was writing. Different bytes under the path
81
+ * the connector is about to read are a different session, and that is refused rather than
82
+ * clobbered: the harness transcript is the one thing here Cotal cannot reconstruct.
83
+ */
84
+ function placeSessionFile(seat, file, root, created) {
85
+ const path = destinationPath(file.restore, root, seat.cwd);
86
+ const bytes = readFileSync(join(seat.directory, file.path));
87
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
88
+ let fd;
89
+ try {
90
+ fd = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
91
+ }
92
+ catch (error) {
93
+ if (error.code === "EEXIST") {
94
+ const present = createHash("sha256").update(readFileSync(path)).digest("hex");
95
+ if (present === file.sha256)
96
+ return;
97
+ throw new SeatRestoreError(`seat ${seat.name}: ${path} already holds different bytes than the checkpoint recorded (${present} against ${file.sha256}); inspect it and move it aside before resuming`);
98
+ }
99
+ throw error;
100
+ }
101
+ created.push(path);
102
+ try {
103
+ writeSync(fd, bytes);
104
+ fsyncSync(fd);
105
+ }
106
+ finally {
107
+ closeSync(fd);
108
+ }
109
+ }
110
+ /** The sha256-verified pointer's own session id, refused when it is not the one this seat reopens. */
111
+ function assertPointerSession(seat, pointer) {
112
+ const recorded = seat.checkpoint.session.sessionId ?? seat.inventorySessionId;
113
+ if (!recorded)
114
+ return;
115
+ let parsed;
116
+ try {
117
+ parsed = JSON.parse(readFileSync(join(seat.directory, pointer.path), "utf8"));
118
+ }
119
+ catch (error) {
120
+ throw new SeatRestoreError(`seat ${seat.name}: captured session pointer is not readable JSON (${error.message})`);
121
+ }
122
+ if (typeof parsed.sessionId !== "string" || parsed.sessionId !== recorded)
123
+ throw new SeatRestoreError(`seat ${seat.name}: captured session pointer names session ${JSON.stringify(parsed.sessionId)}, this resume reopens ${JSON.stringify(recorded)}`);
124
+ if (seat.inventorySessionId && seat.checkpoint.session.sessionId && seat.inventorySessionId !== seat.checkpoint.session.sessionId)
125
+ throw new SeatRestoreError(`seat ${seat.name}: checkpoint records session ${seat.checkpoint.session.sessionId}, the retained inventory says ${seat.inventorySessionId}`);
126
+ }
127
+ /** Stage one seat: everything up to and including the extraction, inside `<cwd>.incoming` alone. */
128
+ function stageSeat(seat, staging) {
129
+ // THE ONE TREE A PROMOTION MAY NOT REPLACE.
130
+ //
131
+ // A checkpoint deliberately excludes the control directory (see `UNTRACKED_SELECTION`): it holds
132
+ // the broker trust material, the space account, the seat's own credentials and the maintenance
133
+ // journal this very resume is reading, and a checkpoint carries credential references rather than
134
+ // values. Promotion replaces the whole `cwd`, so when the seat's `cwd` IS a workspace root, which
135
+ // is the ordinary layout an operator gets by running `cotal up` and `cotal spawn` in one
136
+ // directory, promoting a clone that cannot contain `.cotal/` moves the live control directory
137
+ // aside and leaves the destination with none.
138
+ //
139
+ // Moving the control directory across the promotion is not the answer: its contents are live
140
+ // while this command runs. So this refuses, and names the remedy rather than degrading.
141
+ const control = join(resolve(seat.cwd), ".cotal");
142
+ if (existsSync(control))
143
+ throw new SeatRestoreError(`seat ${seat.name}: ${resolve(seat.cwd)} holds a control directory at ${control}, and a checkpoint never carries one, so promoting the restored tree over it would move this destination's live trust material and maintenance state aside; give the seat a working tree that is not a workspace root, then resume`);
144
+ // The digests again, over the files as they are NOW. Gate 1 ran before custody moved; a
145
+ // checkpoint that changed between the gate and the bytes being applied is not the admitted one.
146
+ try {
147
+ assertSeatCheckpointIntegrity(seat.directory, seat.checkpoint);
148
+ }
149
+ catch (error) {
150
+ throw new SeatRestoreError(`seat ${seat.name}: re-verifying the checkpoint before restore failed: ${error.message}`);
151
+ }
152
+ // Before the clone, because this decides nothing about the tree and therefore should cost
153
+ // nothing: a pointer that names another session leaves no staging directory to inspect for a
154
+ // step that never ran.
155
+ if (seat.checkpoint.session.pointer)
156
+ assertPointerSession(seat, seat.checkpoint.session.pointer);
157
+ // A leftover staging directory is evidence of a failed run, never a tree to promote, and nothing
158
+ // here removes it.
159
+ if (existsSync(staging))
160
+ throw new SeatRestoreError(`seat ${seat.name}: ${staging} already exists; a staging directory from a failed restore is evidence - inspect it and remove it by hand, then resume`);
161
+ const parent = dirname(staging);
162
+ mkdirSync(parent, { recursive: true });
163
+ const repository = seat.checkpoint.repository;
164
+ run("git", ["clone", "--quiet", join(seat.directory, repository.bundle.path), staging], parent, seat.name, "cloning the bundle");
165
+ run("git", ["rev-parse", "--verify", `${repository.base}^{commit}`], staging, seat.name, `verifying the recorded base ${repository.base}`);
166
+ run("git", ["checkout", "--quiet", "--detach", repository.base], staging, seat.name, "checking out the recorded base");
167
+ run("git", ["apply", "--binary", "--allow-empty", "--index", join(seat.directory, repository.indexDiff.path)], staging, seat.name, "applying the index diff");
168
+ run("git", ["apply", "--binary", "--allow-empty", join(seat.directory, repository.worktreeDiff.path)], staging, seat.name, "applying the worktree diff");
169
+ run("tar", ["-xf", join(seat.directory, repository.untracked.path)], staging, seat.name, "extracting the untracked archive");
170
+ }
171
+ /**
172
+ * Move an existing `cwd` aside under a timestamped name, and return the name it went to.
173
+ *
174
+ * The name has to be one this call can prove it owns. A whole-second timestamp is not that: two
175
+ * promotions of the same seat inside one second compute the same path, and the second `rename`
176
+ * lands on the non-empty directory the first one just created. A refusal naming a rename instead
177
+ * of the real cause is not acceptable for the one step that exists to keep a superseded tree.
178
+ *
179
+ * So the rename IS the claim. Nothing creates the timestamped name ahead of the move: an
180
+ * exclusive `mkdir` first and a rename onto that empty directory is the replace POSIX allows, but
181
+ * Windows refuses to rename onto any existing directory (EPERM), so a claim made that way can never
182
+ * be redeemed there. Renaming onto a name that already holds a tree fails on every platform
183
+ * (ENOTEMPTY or EEXIST on POSIX, EPERM on Windows), and that failure is read as "taken" and retried
184
+ * with a counter. A rename that fails for any other reason is the error it is.
185
+ */
186
+ function supersede(cwd, seat) {
187
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
188
+ for (let attempt = 0; attempt < 100; attempt++) {
189
+ const candidate = `${cwd}.superseded.${stamp}${attempt ? `.${attempt}` : ""}`;
190
+ try {
191
+ renameSync(cwd, candidate);
192
+ return candidate;
193
+ }
194
+ catch (error) {
195
+ const code = error.code;
196
+ if ((code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM") && existsSync(candidate))
197
+ continue;
198
+ throw error;
199
+ }
200
+ }
201
+ throw new SeatRestoreError(`seat ${seat}: could not claim a free superseded name beside ${cwd}; ${cwd}.superseded.${stamp}[.0-99] are all taken, so an earlier tree would be overwritten`);
202
+ }
203
+ /**
204
+ * Restore every admitted seat, or leave every live `cwd` as it was.
205
+ *
206
+ * Two phases. Every seat is staged first, so a refusal on the last seat costs nothing on the first.
207
+ * Only once every staging directory is complete does any promotion happen, and a failure during the
208
+ * promotion phase puts back what this call moved: the renames it made are undone in reverse and the
209
+ * session files it exclusively created are removed, so the destination is where it started and the
210
+ * whole set can be retried once the refusal is understood.
211
+ */
212
+ export function restoreSeatCheckpoints(options) {
213
+ const staged = options.seats.map((seat) => ({ seat, staging: `${resolve(seat.cwd)}.incoming` }));
214
+ for (const { seat, staging } of staged)
215
+ stageSeat(seat, staging);
216
+ const promoted = [];
217
+ const created = [];
218
+ try {
219
+ for (const { seat, staging } of staged) {
220
+ const cwd = resolve(seat.cwd);
221
+ const superseded = existsSync(cwd) ? supersede(cwd, seat.name) : undefined;
222
+ renameSync(staging, cwd);
223
+ promoted.push({ cwd, staging, ...(superseded ? { superseded } : {}) });
224
+ // Before the status is read, for the same reason the capture read it with the store in place:
225
+ // a store file inside the tree is part of the state the checkpoint recorded.
226
+ if (seat.checkpoint.session.pointer)
227
+ placeSessionFile(seat, seat.checkpoint.session.pointer, options.root, created);
228
+ for (const file of seat.checkpoint.session.store)
229
+ placeSessionFile(seat, file, options.root, created);
230
+ // The bytes landed and git accepted them; this is the only check that sees a restore which
231
+ // applied cleanly and still produced a different index.
232
+ const status = run("git", ["status", "--porcelain"], cwd, seat.name, "re-reading the restored working tree status");
233
+ if (status !== seat.checkpoint.repository.status)
234
+ throw new SeatRestoreError(`seat ${seat.name}: the restored tree reports a different status than the checkpoint recorded\n recorded: ${JSON.stringify(seat.checkpoint.repository.status)}\n restored: ${JSON.stringify(status)}`);
235
+ options.onPromoted?.({ name: seat.name, cwd, ...(superseded ? { superseded } : {}) });
236
+ }
237
+ }
238
+ catch (error) {
239
+ for (const path of created.reverse())
240
+ rmSync(path, { force: true });
241
+ for (const entry of promoted.reverse()) {
242
+ renameSync(entry.cwd, entry.staging);
243
+ if (entry.superseded)
244
+ renameSync(entry.superseded, entry.cwd);
245
+ }
246
+ throw error;
247
+ }
248
+ }
249
+ //# sourceMappingURL=seat-restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-restore.js","sourceRoot":"","sources":["../../src/lib/seat-restore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** One stale checkpoint the operator deliberately admitted, for the resume journal. */
2
+ export interface StaleCheckpointConsent {
3
+ readonly name: string;
4
+ readonly capturedAt: string;
5
+ readonly ageMs: number;
6
+ readonly horizonMs: number;
7
+ }
8
+ /**
9
+ * Run section 2.2's gates over every seat checkpoint the cut wrote, restore the captured bytes,
10
+ * then take custody of each seat.
11
+ *
12
+ * Gate 2 is told which lifecycle uids are live. On these paths that set is empty and the reason is
13
+ * structural rather than an omission: a preserved resume is only reached from a `ready` journal,
14
+ * which `down --preserve-state` publishes after it has proven every recorded process stopped and
15
+ * the exact recorded endpoint unreachable. There is no roster to consult because there is no
16
+ * broker. A destination that resumes against a live space supplies the roster instead.
17
+ *
18
+ * The restore runs inside the admission, after every gate and before any generation is claimed, so
19
+ * a refusal costs nothing: no tree has been promoted, no claim has been made, nothing has started.
20
+ */
21
+ export declare function admitAndRestoreSeatCheckpoints(root: string, space: string, attemptId: string, values: {
22
+ "accept-stale-checkpoint"?: boolean;
23
+ }, inventory: unknown): StaleCheckpointConsent[];
24
+ //# sourceMappingURL=seat-resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-resume.d.ts","sourceRoot":"","sources":["../../src/lib/seat-resume.ts"],"names":[],"mappings":"AAyBA,uFAAuF;AACvF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,yBAAyB,CAAC,EAAE,OAAO,CAAA;CAAE,EAC/C,SAAS,EAAE,OAAO,GACjB,sBAAsB,EAAE,CAuF1B"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Admission plus restore for one preserved cut, as both destination paths run it.
3
+ *
4
+ * `cotal up` from a `ready` journal and `cotal up --restore <dir>` reach a preserved resume by
5
+ * different routes and hand the same retained inventory to the same manager operation. They
6
+ * therefore owe the seats the same thing: section 2.2's gates, the restore of section 6 step 7, and
7
+ * the writer generation, in that order, before a manager launches. Keeping one implementation is
8
+ * what stops the two routes from drifting into one gated path and one ungated one.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { readFileSync } from "node:fs";
12
+ import { seatCheckpointDir } from "@cotal-ai/workspace";
13
+ import { admitSeatCheckpoints } from "./seat-admission.js";
14
+ import { restoreSeatCheckpoints } from "./seat-restore.js";
15
+ import { c } from "../ui.js";
16
+ /** This host's current revision for a seat's launch config, or undefined when the file is gone.
17
+ * A missing profile is not a mismatch: the manager's own relaunch check reports that separately,
18
+ * and inventing a digest here would turn an absent file into a false profile refusal. */
19
+ function digestFileOrUndefined(path) {
20
+ try {
21
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /**
28
+ * Run section 2.2's gates over every seat checkpoint the cut wrote, restore the captured bytes,
29
+ * then take custody of each seat.
30
+ *
31
+ * Gate 2 is told which lifecycle uids are live. On these paths that set is empty and the reason is
32
+ * structural rather than an omission: a preserved resume is only reached from a `ready` journal,
33
+ * which `down --preserve-state` publishes after it has proven every recorded process stopped and
34
+ * the exact recorded endpoint unreachable. There is no roster to consult because there is no
35
+ * broker. A destination that resumes against a live space supplies the roster instead.
36
+ *
37
+ * The restore runs inside the admission, after every gate and before any generation is claimed, so
38
+ * a refusal costs nothing: no tree has been promoted, no claim has been made, nothing has started.
39
+ */
40
+ export function admitAndRestoreSeatCheckpoints(root, space, attemptId, values, inventory) {
41
+ // The checkpoints of the CUT this resume is consuming, named by that cut's attempt id. A resume
42
+ // must not admit an older cut's artifacts: they describe a tree and a transcript this inventory
43
+ // is not about to restore.
44
+ const checkpointDir = seatCheckpointDir(root, attemptId);
45
+ // The seats this resume is about to hand the manager. Admission is reconciled against this set,
46
+ // because "no checkpoint" and "no seat" look identical to a reader of the checkpoint directory
47
+ // alone, and only one of them is safe.
48
+ const retainedAgents = (inventory.agents ?? []);
49
+ const retained = new Map(retainedAgents.map((agent) => [agent.name ?? "", agent]));
50
+ const admitted = admitSeatCheckpoints({
51
+ root,
52
+ checkpointDir,
53
+ space,
54
+ // This host's current revision for each seat's launch source, digested from the same file the
55
+ // manager re-digests before it relaunches. Passing it is what makes gate 2's profile half a
56
+ // decision: without it the gate has nothing to compare and the override decides nothing.
57
+ currentProfileConfigSha256: (name) => {
58
+ const source = retained.get(name)?.launch?.source;
59
+ return source ? digestFileOrUndefined(source.configPath) : undefined;
60
+ },
61
+ liveLifecycleUids: new Set(),
62
+ // Reconcile inside the admission, after the gates and BEFORE any generation is claimed. A
63
+ // retained seat with no checkpoint would otherwise resume with no gate run and no custody
64
+ // claimed, and checking after the claims had landed would leave the earlier seats' exclusive
65
+ // creates behind, so the retry over a repaired checkpoint set could never make them again.
66
+ requireCovered: (admittedNames) => {
67
+ const covered = new Set(admittedNames);
68
+ const uncovered = retainedAgents.map((agent) => agent.name ?? "").filter((name) => !covered.has(name));
69
+ if (uncovered.length)
70
+ throw new Error(`preserved resume is refused: retained seat(s) ${uncovered.map((name) => JSON.stringify(name)).join(", ")} have no admitted checkpoint under ${checkpointDir}; a seat cannot resume without passing the admission gates and claiming its writer generation`);
71
+ },
72
+ ...(values["accept-stale-checkpoint"] ? { acceptStale: true } : {}),
73
+ // The restore, between the gates and custody. Every admitted seat's captured bytes land in a
74
+ // staging directory beside its cwd and are promoted only once every seat has staged; a refusal
75
+ // here leaves every live cwd as it was and claims no generation, like a gate failure.
76
+ beforeCustody: (staged) => {
77
+ // THE INCARNATION RECONCILIATION, AND IT RUNS BEFORE THE RESTORE TOUCHES ANYTHING.
78
+ //
79
+ // This compares the checkpoint's recovered `lifecycleUid` against the one the inventory this
80
+ // resume is about to hand the manager carries, and it used to run after the whole admission
81
+ // returned. By then the restore had already moved each live `cwd` aside and promoted another
82
+ // seat's tree over it, and the generation had been claimed under the checkpoint's uid, so the
83
+ // refusal arrived after the two irreversible steps it exists to prevent. A mismatch means the
84
+ // checkpoint describes a different incarnation than the seat being resumed, which is the last
85
+ // moment where refusing is still free.
86
+ for (const seat of staged) {
87
+ const expected = retained.get(seat.checkpoint.name)?.identity?.lifecycleUid ?? "";
88
+ if (expected && expected !== seat.checkpoint.lifecycleUid)
89
+ throw new Error(`seat ${seat.checkpoint.name}: checkpoint records lifecycle ${seat.checkpoint.lifecycleUid}, the retained inventory says ${expected}`);
90
+ }
91
+ restoreSeatCheckpoints({
92
+ root,
93
+ seats: staged.map((seat) => {
94
+ const entry = retained.get(seat.checkpoint.name);
95
+ const cwd = entry?.launch?.cwd;
96
+ if (!cwd)
97
+ throw new Error(`preserved resume is refused: retained seat ${JSON.stringify(seat.checkpoint.name)} records no launch cwd, so its checkpoint has no working tree to restore into`);
98
+ const sessionId = entry?.launch?.sessionId;
99
+ return {
100
+ name: seat.checkpoint.name,
101
+ directory: seat.directory,
102
+ checkpoint: seat.checkpoint,
103
+ cwd,
104
+ ...(sessionId ? { inventorySessionId: sessionId } : {}),
105
+ };
106
+ }),
107
+ onPromoted: (seat) => {
108
+ console.log(c.dim(` restored working tree: ${seat.name} -> ${seat.cwd}${seat.superseded ? ` (previous tree kept at ${seat.superseded})` : ""}`));
109
+ },
110
+ });
111
+ },
112
+ });
113
+ for (const seat of admitted) {
114
+ console.log(c.dim(` admitted checkpoint: ${seat.checkpoint.name} (${seat.checkpoint.session.continuity}) at generation ${seat.generation}`));
115
+ if (seat.staleOverrideAgeMs !== undefined)
116
+ console.log(c.yellow(` stale checkpoint admitted by --accept-stale-checkpoint: ${seat.checkpoint.name} is ${seat.staleOverrideAgeMs}ms past capture, horizon ${seat.checkpoint.recencyHorizonMs}ms`));
117
+ }
118
+ return admitted
119
+ .filter((seat) => seat.staleOverrideAgeMs !== undefined)
120
+ .map((seat) => ({
121
+ name: seat.checkpoint.name,
122
+ capturedAt: seat.checkpoint.capturedAt,
123
+ ageMs: seat.staleOverrideAgeMs,
124
+ horizonMs: seat.checkpoint.recencyHorizonMs,
125
+ }));
126
+ }
127
+ //# sourceMappingURL=seat-resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-resume.js","sourceRoot":"","sources":["../../src/lib/seat-resume.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/cli",
3
3
  "description": "Cotal mesh CLI: up, join, watch, console, spawn, mint, channels, history, ext.",
4
- "version": "0.50.0",
4
+ "version": "0.51.0",
5
5
  "homepage": "https://cotal.ai",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "yaml": "^2.9.0",
30
30
  "zod": "^4.4.3",
31
31
  "ws": "^8.21.0",
32
- "@cotal-ai/core": "0.50.0",
33
- "@cotal-ai/workspace": "0.50.0"
32
+ "@cotal-ai/core": "0.51.0",
33
+ "@cotal-ai/workspace": "0.51.0"
34
34
  },
35
35
  "optionalDependencies": {
36
36
  "@eplightning/nats-server-darwin-arm64": "^2.14.0",
@@ -44,7 +44,7 @@
44
44
  "@types/react": "^19.0.0",
45
45
  "@types/ws": "^8.18.1",
46
46
  "@cotal-ai/smoke-kit": "0.0.0",
47
- "@cotal-ai/auth": "0.50.0"
47
+ "@cotal-ai/auth": "0.51.0"
48
48
  },
49
49
  "files": [
50
50
  "dist",