@cotal-ai/cli 0.50.1 → 0.52.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 (179) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +32 -1
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/agents.d.ts +41 -2
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +85 -19
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/clean.d.ts.map +1 -1
  9. package/dist/commands/clean.js +5 -1
  10. package/dist/commands/clean.js.map +1 -1
  11. package/dist/commands/console.d.ts.map +1 -1
  12. package/dist/commands/console.js +9 -1
  13. package/dist/commands/console.js.map +1 -1
  14. package/dist/commands/doctor.d.ts.map +1 -1
  15. package/dist/commands/doctor.js +13 -8
  16. package/dist/commands/doctor.js.map +1 -1
  17. package/dist/commands/down.d.ts.map +1 -1
  18. package/dist/commands/down.js +151 -7
  19. package/dist/commands/down.js.map +1 -1
  20. package/dist/commands/endpoints.d.ts.map +1 -1
  21. package/dist/commands/endpoints.js +2 -1
  22. package/dist/commands/endpoints.js.map +1 -1
  23. package/dist/commands/join.d.ts.map +1 -1
  24. package/dist/commands/join.js +25 -0
  25. package/dist/commands/join.js.map +1 -1
  26. package/dist/commands/meshes-add.d.ts +2 -54
  27. package/dist/commands/meshes-add.d.ts.map +1 -1
  28. package/dist/commands/meshes-add.js +20 -105
  29. package/dist/commands/meshes-add.js.map +1 -1
  30. package/dist/commands/meshes.d.ts.map +1 -1
  31. package/dist/commands/meshes.js +5 -2
  32. package/dist/commands/meshes.js.map +1 -1
  33. package/dist/commands/service.d.ts +17 -0
  34. package/dist/commands/service.d.ts.map +1 -0
  35. package/dist/commands/service.js +541 -0
  36. package/dist/commands/service.js.map +1 -0
  37. package/dist/commands/setup.d.ts.map +1 -1
  38. package/dist/commands/setup.js +9 -8
  39. package/dist/commands/setup.js.map +1 -1
  40. package/dist/commands/spawn.d.ts +44 -2
  41. package/dist/commands/spawn.d.ts.map +1 -1
  42. package/dist/commands/spawn.js +310 -47
  43. package/dist/commands/spawn.js.map +1 -1
  44. package/dist/commands/status.d.ts.map +1 -1
  45. package/dist/commands/status.js +133 -39
  46. package/dist/commands/status.js.map +1 -1
  47. package/dist/commands/sync.d.ts +50 -0
  48. package/dist/commands/sync.d.ts.map +1 -0
  49. package/dist/commands/sync.js +284 -0
  50. package/dist/commands/sync.js.map +1 -0
  51. package/dist/commands/up.d.ts +7 -0
  52. package/dist/commands/up.d.ts.map +1 -1
  53. package/dist/commands/up.js +174 -23
  54. package/dist/commands/up.js.map +1 -1
  55. package/dist/commands/update.d.ts +12 -0
  56. package/dist/commands/update.d.ts.map +1 -1
  57. package/dist/commands/update.js +83 -9
  58. package/dist/commands/update.js.map +1 -1
  59. package/dist/commands/use.js +1 -1
  60. package/dist/commands/use.js.map +1 -1
  61. package/dist/console/app.d.ts +14 -6
  62. package/dist/console/app.d.ts.map +1 -1
  63. package/dist/console/app.js +308 -40
  64. package/dist/console/app.js.map +1 -1
  65. package/dist/console/commands.d.ts +29 -1
  66. package/dist/console/commands.d.ts.map +1 -1
  67. package/dist/console/commands.js +109 -13
  68. package/dist/console/commands.js.map +1 -1
  69. package/dist/console/control.d.ts +141 -0
  70. package/dist/console/control.d.ts.map +1 -0
  71. package/dist/console/control.js +189 -0
  72. package/dist/console/control.js.map +1 -0
  73. package/dist/console/mesh.d.ts +1 -1
  74. package/dist/console/mesh.d.ts.map +1 -1
  75. package/dist/console/root.d.ts +13 -1
  76. package/dist/console/root.d.ts.map +1 -1
  77. package/dist/console/root.js +25 -5
  78. package/dist/console/root.js.map +1 -1
  79. package/dist/console/ui/CommandPalette.d.ts +2 -1
  80. package/dist/console/ui/CommandPalette.d.ts.map +1 -1
  81. package/dist/console/ui/CommandPalette.js +3 -2
  82. package/dist/console/ui/CommandPalette.js.map +1 -1
  83. package/dist/console/ui/Confirm.d.ts +13 -4
  84. package/dist/console/ui/Confirm.d.ts.map +1 -1
  85. package/dist/console/ui/Confirm.js +13 -5
  86. package/dist/console/ui/Confirm.js.map +1 -1
  87. package/dist/console/ui/Detail.d.ts +8 -1
  88. package/dist/console/ui/Detail.d.ts.map +1 -1
  89. package/dist/console/ui/Detail.js +11 -4
  90. package/dist/console/ui/Detail.js.map +1 -1
  91. package/dist/console/ui/Help.d.ts.map +1 -1
  92. package/dist/console/ui/Help.js +3 -2
  93. package/dist/console/ui/Help.js.map +1 -1
  94. package/dist/console/ui/Roster.d.ts +9 -1
  95. package/dist/console/ui/Roster.d.ts.map +1 -1
  96. package/dist/console/ui/Roster.js +16 -5
  97. package/dist/console/ui/Roster.js.map +1 -1
  98. package/dist/console/ui/Sparkline.d.ts +13 -0
  99. package/dist/console/ui/Sparkline.d.ts.map +1 -0
  100. package/dist/console/ui/Sparkline.js +21 -0
  101. package/dist/console/ui/Sparkline.js.map +1 -0
  102. package/dist/console/ui/StatusBar.d.ts +4 -1
  103. package/dist/console/ui/StatusBar.d.ts.map +1 -1
  104. package/dist/console/ui/StatusBar.js +5 -3
  105. package/dist/console/ui/StatusBar.js.map +1 -1
  106. package/dist/console/ui/Tabs.d.ts +4 -2
  107. package/dist/console/ui/Tabs.d.ts.map +1 -1
  108. package/dist/console/ui/Tabs.js +5 -3
  109. package/dist/console/ui/Tabs.js.map +1 -1
  110. package/dist/console/ui/topo/Map.d.ts.map +1 -1
  111. package/dist/console/ui/topo/Map.js +34 -2
  112. package/dist/console/ui/topo/Map.js.map +1 -1
  113. package/dist/console/ui/topo/Matrix.d.ts.map +1 -1
  114. package/dist/console/ui/topo/Matrix.js +12 -3
  115. package/dist/console/ui/topo/Matrix.js.map +1 -1
  116. package/dist/console/ui/topo/Sequence.js +1 -1
  117. package/dist/console/ui/topo/Sequence.js.map +1 -1
  118. package/dist/console/ui/topo/Topo.d.ts +10 -2
  119. package/dist/console/ui/topo/Topo.d.ts.map +1 -1
  120. package/dist/console/ui/topo/Topo.js +6 -4
  121. package/dist/console/ui/topo/Topo.js.map +1 -1
  122. package/dist/console/ui/topo/model.d.ts +50 -2
  123. package/dist/console/ui/topo/model.d.ts.map +1 -1
  124. package/dist/console/ui/topo/model.js +69 -6
  125. package/dist/console/ui/topo/model.js.map +1 -1
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +27 -0
  128. package/dist/index.js.map +1 -1
  129. package/dist/lib/attach-client.d.ts.map +1 -1
  130. package/dist/lib/attach-client.js +7 -1
  131. package/dist/lib/attach-client.js.map +1 -1
  132. package/dist/lib/delivery-proc.d.ts +10 -2
  133. package/dist/lib/delivery-proc.d.ts.map +1 -1
  134. package/dist/lib/delivery-proc.js +7 -1
  135. package/dist/lib/delivery-proc.js.map +1 -1
  136. package/dist/lib/manifest/apply.d.ts.map +1 -1
  137. package/dist/lib/manifest/apply.js +2 -0
  138. package/dist/lib/manifest/apply.js.map +1 -1
  139. package/dist/lib/manifest/model.d.ts +2 -0
  140. package/dist/lib/manifest/model.d.ts.map +1 -1
  141. package/dist/lib/manifest/prepare.d.ts +2 -0
  142. package/dist/lib/manifest/prepare.d.ts.map +1 -1
  143. package/dist/lib/manifest/prepare.js +1 -0
  144. package/dist/lib/manifest/prepare.js.map +1 -1
  145. package/dist/lib/manifest/resolve.js +1 -0
  146. package/dist/lib/manifest/resolve.js.map +1 -1
  147. package/dist/lib/manifest/schema.d.ts +2 -0
  148. package/dist/lib/manifest/schema.d.ts.map +1 -1
  149. package/dist/lib/manifest/schema.js +2 -0
  150. package/dist/lib/manifest/schema.js.map +1 -1
  151. package/dist/lib/restore.d.ts +3 -0
  152. package/dist/lib/restore.d.ts.map +1 -1
  153. package/dist/lib/restore.js +17 -0
  154. package/dist/lib/restore.js.map +1 -1
  155. package/dist/lib/seat-admission.d.ts +51 -0
  156. package/dist/lib/seat-admission.d.ts.map +1 -0
  157. package/dist/lib/seat-admission.js +81 -0
  158. package/dist/lib/seat-admission.js.map +1 -0
  159. package/dist/lib/seat-capture.d.ts +47 -0
  160. package/dist/lib/seat-capture.d.ts.map +1 -0
  161. package/dist/lib/seat-capture.js +195 -0
  162. package/dist/lib/seat-capture.js.map +1 -0
  163. package/dist/lib/seat-restore.d.ts +39 -0
  164. package/dist/lib/seat-restore.d.ts.map +1 -0
  165. package/dist/lib/seat-restore.js +249 -0
  166. package/dist/lib/seat-restore.js.map +1 -0
  167. package/dist/lib/seat-resume.d.ts +24 -0
  168. package/dist/lib/seat-resume.d.ts.map +1 -0
  169. package/dist/lib/seat-resume.js +127 -0
  170. package/dist/lib/seat-resume.js.map +1 -0
  171. package/dist/view/mesh-view.d.ts +41 -1
  172. package/dist/view/mesh-view.d.ts.map +1 -1
  173. package/dist/view/mesh-view.js +107 -8
  174. package/dist/view/mesh-view.js.map +1 -1
  175. package/package.json +14 -7
  176. package/dist/lib/join-target.d.ts +0 -173
  177. package/dist/lib/join-target.d.ts.map +0 -1
  178. package/dist/lib/join-target.js +0 -335
  179. package/dist/lib/join-target.js.map +0 -1
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Step 7 and 8 of the preservation cut: capture one seat's repository state and harness session
3
+ * store, then seal a checkpoint over the captured bytes.
4
+ *
5
+ * Ordering is the whole point and it is not negotiable. Capture runs only after
6
+ * `Manager.awaitHandleExit` has proven every child gone (step 6). A capture before that races the
7
+ * harness by construction: the transcript is still being written and the working tree is still
8
+ * being edited. Nothing in this file may be called from a path that has not proven the stop.
9
+ *
10
+ * The git work shells out to the operator's own git rather than reimplementing bundle and diff
11
+ * formats, so a self-hoster can verify every artifact by hand with the same commands.
12
+ */
13
+ import { execFileSync } from "node:child_process";
14
+ import { mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { dirname, join, relative, resolve, sep } from "node:path";
17
+ import { sessionContinuityClass } from "@cotal-ai/core";
18
+ import { createSeatCheckpointWriter, } from "@cotal-ai/workspace";
19
+ /** The selection rule the untracked set is produced under, recorded in the checkpoint so a
20
+ * destination that cannot reproduce it refuses.
21
+ *
22
+ * Two clauses, and the second is not an optimization. `--exclude-standard` honors `.gitignore`,
23
+ * so an ignored file the seat actually needs is outside the checkpoint and has to be named
24
+ * separately by the operator. The `.cotal/` exclusion is a SECRECY boundary: when a seat's cwd is
25
+ * also the mesh root, which is what an operator gets by running `cotal up` and `cotal spawn` in
26
+ * one directory, the control directory is untracked and the unfiltered selection pulls
27
+ * `auth/broker.json`, the space account, the manager instance identity's private seed and the
28
+ * seat's own `.creds` into the artifact. A checkpoint carries credential REFERENCES, never
29
+ * values, so the control directory never travels; the destination resolves that material itself. */
30
+ export const UNTRACKED_SELECTION = "git ls-files --others --exclude-standard -z, excluding .cotal/";
31
+ /** The control directory a checkpoint must never carry, relative to the seat's cwd. */
32
+ const CONTROL_DIR = ".cotal/";
33
+ /** Default freshness horizon: 24 hours. A property of the deployment, carried in every record so a
34
+ * destination and an operator cannot disagree about what was promised. */
35
+ export const DEFAULT_RECENCY_HORIZON_MS = 24 * 60 * 60 * 1000;
36
+ function git(cwd, args) {
37
+ return execFileSync("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024 });
38
+ }
39
+ /**
40
+ * Where one captured session file lands on the destination.
41
+ *
42
+ * Anchored, never absolute: the destination has its own workspace root, its own account home and
43
+ * its own seat working tree, and the source host's spelling for any of them is a path the
44
+ * destination may not have or may not own. The anchors are tried innermost first, so a file inside
45
+ * the seat's tree travels with the tree rather than with the root it happens to sit under.
46
+ * A file under none of them has no destination-relative spelling and the cut refuses, rather than
47
+ * recording an absolute path a restore would write blind.
48
+ */
49
+ function restoreDestination(path, what, cwd, workspaceRoot) {
50
+ const target = resolve(path);
51
+ const anchors = [
52
+ ["cwd", cwd],
53
+ ["workspace-root", workspaceRoot],
54
+ ["home", homedir()],
55
+ ];
56
+ for (const [anchor, base] of anchors) {
57
+ if (!base)
58
+ continue;
59
+ const rel = relative(resolve(base), target);
60
+ if (rel && !rel.startsWith("..") && !rel.startsWith(sep) && rel !== ".")
61
+ return { anchor, path: rel.split(sep).join("/") };
62
+ }
63
+ throw new Error(`seat checkpoint: ${what} ${target} is under neither the seat's working tree, the workspace root, nor this account's home, so no destination-relative path can be recorded for it`);
64
+ }
65
+ /** Every regular file under one operator-named store path, relative to that path. A store is a
66
+ * directory of opaque harness bytes; nothing here parses one. */
67
+ function storeFiles(path) {
68
+ const root = resolve(path);
69
+ const found = [];
70
+ const walk = (dir) => {
71
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
72
+ const full = join(dir, entry.name);
73
+ if (entry.isDirectory())
74
+ walk(full);
75
+ else if (entry.isFile())
76
+ found.push(full);
77
+ else
78
+ throw new Error(`seat checkpoint: session store entry ${full} is neither a regular file nor a directory`);
79
+ }
80
+ };
81
+ if (!statSync(root).isDirectory())
82
+ throw new Error(`seat checkpoint: session store ${root} is not a directory`);
83
+ walk(root);
84
+ return found;
85
+ }
86
+ /**
87
+ * Capture and seal. Returns the sealed record. Throws, leaving no checkpoint directory behind,
88
+ * when anything cannot be captured: a partial checkpoint that looks complete is worse than none.
89
+ */
90
+ export function captureSeatCheckpoint(destination, entry, request) {
91
+ const { cwd } = request;
92
+ const base = git(cwd, ["rev-parse", "HEAD"]).toString("utf8").trim();
93
+ if (!/^[0-9a-f]{40,64}$/.test(base))
94
+ throw new Error(`seat checkpoint: ${cwd} did not report a full base object id (got ${JSON.stringify(base)})`);
95
+ // git writes the bundle and the diffs here first, so the checkpoint writer only ever copies a
96
+ // finished file and digests what it copied. The parent is the checkpoint destination's own
97
+ // parent, which the caller may not have created yet on a first cut.
98
+ const parent = dirname(resolve(destination));
99
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
100
+ const staging = mkdtempSync(join(parent, "cotal-seat-capture-"));
101
+ const writer = createSeatCheckpointWriter(destination);
102
+ try {
103
+ const bundlePath = join(staging, "repo.bundle");
104
+ git(cwd, ["bundle", "create", bundlePath, "--all"]);
105
+ // Two diffs, not one. A single diff against the base records the difference between that
106
+ // commit and the worktree without recording which parts were STAGED, so a restore from it
107
+ // reports every change as unstaged. Base-to-index and index-to-worktree, applied in that
108
+ // order, reproduce the source's `git status --porcelain`, worktree bytes and staged content
109
+ // together.
110
+ const indexDiffPath = join(staging, "repo.index.diff");
111
+ writeFileSync(indexDiffPath, git(cwd, ["diff", "--binary", "--cached"]));
112
+ const worktreeDiffPath = join(staging, "repo.worktree.diff");
113
+ writeFileSync(worktreeDiffPath, git(cwd, ["diff", "--binary"]));
114
+ // The untracked set has to travel as BYTES. Digesting a `git status` listing would record that
115
+ // the files exist without recording what is in them.
116
+ const untrackedPath = join(staging, "repo.untracked.tar");
117
+ const names = git(cwd, ["ls-files", "--others", "--exclude-standard", "-z"])
118
+ .toString("utf8").split("\0").filter(Boolean)
119
+ // Drop the control directory and everything under it: see UNTRACKED_SELECTION.
120
+ .filter((name) => name !== CONTROL_DIR.slice(0, -1) && !name.startsWith(CONTROL_DIR));
121
+ const namesFile = join(staging, "untracked.list");
122
+ writeFileSync(namesFile, names.length ? `${names.join("\0")}\0` : "");
123
+ execFileSync("tar", ["--null", "--files-from", namesFile, "-cf", untrackedPath], { cwd, maxBuffer: 1024 * 1024 * 1024 });
124
+ // The status a destination compares its promoted tree against, read under the same selection
125
+ // rule the untracked set was produced under. `git status --porcelain` reports the control
126
+ // directory the capture deliberately excludes, so a raw reading would describe a tree these
127
+ // bytes cannot reproduce and every restore would refuse.
128
+ const status = git(cwd, ["status", "--porcelain"]).toString("utf8")
129
+ .split("\n").filter(Boolean)
130
+ .filter((line) => !(line.startsWith("?? ") && (line.slice(3) === CONTROL_DIR || line.slice(3).startsWith(CONTROL_DIR))))
131
+ .map((line) => `${line}\n`).join("");
132
+ const bundle = writer.captureFile("repo.bundle", "bundle", bundlePath);
133
+ const indexDiff = writer.captureFile("repo.index.diff", "index-diff", indexDiffPath);
134
+ const worktreeDiff = writer.captureFile("repo.worktree.diff", "worktree-diff", worktreeDiffPath);
135
+ const untracked = writer.captureFile("repo.untracked.tar", "untracked", untrackedPath);
136
+ const untrackedSet = new Set(names);
137
+ // A store file the untracked archive already carries would be captured twice and restored
138
+ // twice, and the second placement would land on a file the extraction just wrote. Refuse at
139
+ // the cut rather than produce an artifact whose own restore cannot complete.
140
+ const destinationFor = (path, what) => {
141
+ const destination = restoreDestination(path, what, cwd, request.workspaceRoot);
142
+ if (destination.anchor === "cwd" && untrackedSet.has(destination.path))
143
+ throw new Error(`seat checkpoint: ${what} ${resolve(path)} is already carried by the untracked capture of ${cwd}; name a store outside the seat's working tree`);
144
+ return destination;
145
+ };
146
+ const pointer = request.sessionStatePath
147
+ ? writer.captureFile("session-pointer.json", "session-pointer", request.sessionStatePath, destinationFor(request.sessionStatePath, "the connector session pointer"))
148
+ : undefined;
149
+ // Each operator-named store path is a directory of opaque harness bytes, captured file by file
150
+ // so every one travels by size and digest like everything else in the record.
151
+ const store = [];
152
+ for (const path of request.sessionStorePaths ?? []) {
153
+ for (const file of storeFiles(path)) {
154
+ store.push(writer.captureFile(`session-store.${store.length}`, "session-store", file, destinationFor(file, "a session store file")));
155
+ }
156
+ }
157
+ // What the connector DECLARES, capped by what this cut actually carried. A class is a promise
158
+ // a destination is entitled to act on, and `exact` or `fork` promises a session the destination
159
+ // can reopen, which takes BOTH halves: the pointer names the session and the store holds the
160
+ // transcript it reopens. A pointer alone names a session whose bytes are not in the artifact,
161
+ // so it is capped exactly like carrying nothing. Capping here keeps the promise answerable to
162
+ // the artifact rather than to the declaration.
163
+ const declared = sessionContinuityClass(request.connector ?? {});
164
+ const carriesSession = pointer !== undefined && store.length > 0;
165
+ const continuity = !carriesSession && (declared === "exact" || declared === "fork")
166
+ ? (request.connector?.supportsFreshStart ? "fresh" : "drain-only")
167
+ : declared;
168
+ const session = {
169
+ continuity,
170
+ ...(request.sessionId ? { sessionId: request.sessionId } : {}),
171
+ ...(pointer ? { pointer } : {}),
172
+ store,
173
+ };
174
+ return writer.seal({
175
+ entry,
176
+ space: request.space,
177
+ name: request.name,
178
+ lifecycleUid: request.lifecycleUid,
179
+ generation: request.generation,
180
+ capturedAt: new Date().toISOString(),
181
+ recencyHorizonMs: request.recencyHorizonMs ?? DEFAULT_RECENCY_HORIZON_MS,
182
+ profile: request.profile,
183
+ repository: { base, untrackedSelection: UNTRACKED_SELECTION, status, bundle, indexDiff, worktreeDiff, untracked },
184
+ session,
185
+ });
186
+ }
187
+ catch (error) {
188
+ writer.cleanup();
189
+ throw error;
190
+ }
191
+ finally {
192
+ rmSync(staging, { recursive: true, force: true });
193
+ }
194
+ }
195
+ //# sourceMappingURL=seat-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-capture.js","sourceRoot":"","sources":["../../src/lib/seat-capture.ts"],"names":[],"mappings":"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"}
@@ -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"}