@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.
- package/dist/command.d.ts.map +1 -1
- package/dist/command.js +32 -1
- package/dist/command.js.map +1 -1
- package/dist/commands/agents.d.ts +41 -2
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +85 -19
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +5 -1
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +9 -1
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +13 -8
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +151 -7
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/endpoints.d.ts.map +1 -1
- package/dist/commands/endpoints.js +2 -1
- package/dist/commands/endpoints.js.map +1 -1
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +25 -0
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/meshes-add.d.ts +2 -54
- package/dist/commands/meshes-add.d.ts.map +1 -1
- package/dist/commands/meshes-add.js +20 -105
- package/dist/commands/meshes-add.js.map +1 -1
- package/dist/commands/meshes.d.ts.map +1 -1
- package/dist/commands/meshes.js +5 -2
- package/dist/commands/meshes.js.map +1 -1
- package/dist/commands/service.d.ts +17 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +541 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +9 -8
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spawn.d.ts +44 -2
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +310 -47
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +133 -39
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +50 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +284 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/up.d.ts +7 -0
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +174 -23
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/update.d.ts +12 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +83 -9
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/use.js +1 -1
- package/dist/commands/use.js.map +1 -1
- package/dist/console/app.d.ts +14 -6
- package/dist/console/app.d.ts.map +1 -1
- package/dist/console/app.js +308 -40
- package/dist/console/app.js.map +1 -1
- package/dist/console/commands.d.ts +29 -1
- package/dist/console/commands.d.ts.map +1 -1
- package/dist/console/commands.js +109 -13
- package/dist/console/commands.js.map +1 -1
- package/dist/console/control.d.ts +141 -0
- package/dist/console/control.d.ts.map +1 -0
- package/dist/console/control.js +189 -0
- package/dist/console/control.js.map +1 -0
- package/dist/console/mesh.d.ts +1 -1
- package/dist/console/mesh.d.ts.map +1 -1
- package/dist/console/root.d.ts +13 -1
- package/dist/console/root.d.ts.map +1 -1
- package/dist/console/root.js +25 -5
- package/dist/console/root.js.map +1 -1
- package/dist/console/ui/CommandPalette.d.ts +2 -1
- package/dist/console/ui/CommandPalette.d.ts.map +1 -1
- package/dist/console/ui/CommandPalette.js +3 -2
- package/dist/console/ui/CommandPalette.js.map +1 -1
- package/dist/console/ui/Confirm.d.ts +13 -4
- package/dist/console/ui/Confirm.d.ts.map +1 -1
- package/dist/console/ui/Confirm.js +13 -5
- package/dist/console/ui/Confirm.js.map +1 -1
- package/dist/console/ui/Detail.d.ts +8 -1
- package/dist/console/ui/Detail.d.ts.map +1 -1
- package/dist/console/ui/Detail.js +11 -4
- package/dist/console/ui/Detail.js.map +1 -1
- package/dist/console/ui/Help.d.ts.map +1 -1
- package/dist/console/ui/Help.js +3 -2
- package/dist/console/ui/Help.js.map +1 -1
- package/dist/console/ui/Roster.d.ts +9 -1
- package/dist/console/ui/Roster.d.ts.map +1 -1
- package/dist/console/ui/Roster.js +16 -5
- package/dist/console/ui/Roster.js.map +1 -1
- package/dist/console/ui/Sparkline.d.ts +13 -0
- package/dist/console/ui/Sparkline.d.ts.map +1 -0
- package/dist/console/ui/Sparkline.js +21 -0
- package/dist/console/ui/Sparkline.js.map +1 -0
- package/dist/console/ui/StatusBar.d.ts +4 -1
- package/dist/console/ui/StatusBar.d.ts.map +1 -1
- package/dist/console/ui/StatusBar.js +5 -3
- package/dist/console/ui/StatusBar.js.map +1 -1
- package/dist/console/ui/Tabs.d.ts +4 -2
- package/dist/console/ui/Tabs.d.ts.map +1 -1
- package/dist/console/ui/Tabs.js +5 -3
- package/dist/console/ui/Tabs.js.map +1 -1
- package/dist/console/ui/topo/Map.d.ts.map +1 -1
- package/dist/console/ui/topo/Map.js +34 -2
- package/dist/console/ui/topo/Map.js.map +1 -1
- package/dist/console/ui/topo/Matrix.d.ts.map +1 -1
- package/dist/console/ui/topo/Matrix.js +12 -3
- package/dist/console/ui/topo/Matrix.js.map +1 -1
- package/dist/console/ui/topo/Sequence.js +1 -1
- package/dist/console/ui/topo/Sequence.js.map +1 -1
- package/dist/console/ui/topo/Topo.d.ts +10 -2
- package/dist/console/ui/topo/Topo.d.ts.map +1 -1
- package/dist/console/ui/topo/Topo.js +6 -4
- package/dist/console/ui/topo/Topo.js.map +1 -1
- package/dist/console/ui/topo/model.d.ts +50 -2
- package/dist/console/ui/topo/model.d.ts.map +1 -1
- package/dist/console/ui/topo/model.js +69 -6
- package/dist/console/ui/topo/model.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/attach-client.d.ts.map +1 -1
- package/dist/lib/attach-client.js +7 -1
- package/dist/lib/attach-client.js.map +1 -1
- package/dist/lib/delivery-proc.d.ts +10 -2
- package/dist/lib/delivery-proc.d.ts.map +1 -1
- package/dist/lib/delivery-proc.js +7 -1
- package/dist/lib/delivery-proc.js.map +1 -1
- package/dist/lib/manifest/apply.d.ts.map +1 -1
- package/dist/lib/manifest/apply.js +2 -0
- package/dist/lib/manifest/apply.js.map +1 -1
- package/dist/lib/manifest/model.d.ts +2 -0
- package/dist/lib/manifest/model.d.ts.map +1 -1
- package/dist/lib/manifest/prepare.d.ts +2 -0
- package/dist/lib/manifest/prepare.d.ts.map +1 -1
- package/dist/lib/manifest/prepare.js +1 -0
- package/dist/lib/manifest/prepare.js.map +1 -1
- package/dist/lib/manifest/resolve.js +1 -0
- package/dist/lib/manifest/resolve.js.map +1 -1
- package/dist/lib/manifest/schema.d.ts +2 -0
- package/dist/lib/manifest/schema.d.ts.map +1 -1
- package/dist/lib/manifest/schema.js +2 -0
- package/dist/lib/manifest/schema.js.map +1 -1
- package/dist/lib/restore.d.ts +3 -0
- package/dist/lib/restore.d.ts.map +1 -1
- package/dist/lib/restore.js +17 -0
- package/dist/lib/restore.js.map +1 -1
- package/dist/lib/seat-admission.d.ts +51 -0
- package/dist/lib/seat-admission.d.ts.map +1 -0
- package/dist/lib/seat-admission.js +81 -0
- package/dist/lib/seat-admission.js.map +1 -0
- package/dist/lib/seat-capture.d.ts +47 -0
- package/dist/lib/seat-capture.d.ts.map +1 -0
- package/dist/lib/seat-capture.js +195 -0
- package/dist/lib/seat-capture.js.map +1 -0
- package/dist/lib/seat-restore.d.ts +39 -0
- package/dist/lib/seat-restore.d.ts.map +1 -0
- package/dist/lib/seat-restore.js +249 -0
- package/dist/lib/seat-restore.js.map +1 -0
- package/dist/lib/seat-resume.d.ts +24 -0
- package/dist/lib/seat-resume.d.ts.map +1 -0
- package/dist/lib/seat-resume.js +127 -0
- package/dist/lib/seat-resume.js.map +1 -0
- package/dist/view/mesh-view.d.ts +41 -1
- package/dist/view/mesh-view.d.ts.map +1 -1
- package/dist/view/mesh-view.js +107 -8
- package/dist/view/mesh-view.js.map +1 -1
- package/package.json +14 -7
- package/dist/lib/join-target.d.ts +0 -173
- package/dist/lib/join-target.d.ts.map +0 -1
- package/dist/lib/join-target.js +0 -335
- 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"}
|