@astrosheep/keiyaku 4.1.1 → 4.1.2
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/build/src/akuma/akuma-errors.d.ts +6 -0
- package/build/src/akuma/akuma-errors.js +9 -0
- package/build/src/akuma/akuma-handle.d.ts +39 -0
- package/build/src/akuma/akuma-handle.js +263 -0
- package/build/src/akuma/akuma-observe.d.ts +19 -0
- package/build/src/akuma/akuma-observe.js +74 -0
- package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
- package/build/src/akuma/akuma-product-symbols.js +1 -0
- package/build/src/akuma/akuma-product.d.ts +18 -0
- package/build/src/akuma/akuma-product.js +154 -0
- package/build/src/akuma/akuma.d.ts +23 -70
- package/build/src/akuma/akuma.js +17 -433
- package/build/src/akuma/body-supervisor.d.ts +26 -0
- package/build/src/akuma/body-supervisor.js +89 -0
- package/build/src/akuma/body.d.ts +1 -1
- package/build/src/akuma/body.js +3 -2
- package/build/src/akuma/projection-read.d.ts +17 -0
- package/build/src/akuma/projection-read.js +135 -0
- package/build/src/akuma/projection.d.ts +1 -18
- package/build/src/akuma/projection.js +21 -155
- package/build/src/akuma/request-execution.d.ts +6 -0
- package/build/src/akuma/request-execution.js +86 -0
- package/build/src/akuma/request-serve.d.ts +12 -1
- package/build/src/akuma/request-serve.js +2 -102
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +4 -99
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +1 -11
- package/build/src/cli/commands/contract-invoke.d.ts +25 -0
- package/build/src/cli/commands/contract-invoke.js +193 -0
- package/build/src/cli/commands/contract.d.ts +12 -0
- package/build/src/cli/commands/contract.js +229 -0
- package/build/src/cli/invoke.js +24 -184
- package/build/src/cli/parse.d.ts +1 -1
- package/build/src/cli/parse.js +8 -222
- package/build/src/cli/render/akuma-activity.d.ts +38 -0
- package/build/src/cli/render/akuma-activity.js +362 -0
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +14 -371
- package/build/src/cli/render/contract-history.d.ts +2 -0
- package/build/src/cli/render/contract-history.js +83 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +1 -83
- package/build/src/git/reconcile.d.ts +8 -1
- package/build/src/git/reconcile.js +13 -301
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/library/contract-bind.d.ts +26 -0
- package/build/src/library/contract-bind.js +106 -0
- package/build/src/library/contract-operations.d.ts +77 -0
- package/build/src/library/contract-operations.js +130 -0
- package/build/src/library/contract-types.d.ts +95 -0
- package/build/src/library/contract-types.js +1 -0
- package/build/src/library/contract.d.ts +11 -136
- package/build/src/library/contract.js +9 -222
- package/package.json +1 -1
|
@@ -2,15 +2,15 @@ import { access, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
5
|
-
import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE,
|
|
6
|
-
import {
|
|
7
|
-
import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot
|
|
5
|
+
import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, registeredWorktreePaths, worktreeGitDirectory, } from "./repository.js";
|
|
6
|
+
import { runGit } from "./process.js";
|
|
7
|
+
import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot } from "./identity.js";
|
|
8
8
|
import { observeContractAt } from "./observe.js";
|
|
9
9
|
import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-placement.js";
|
|
10
|
-
import { runCreateHooks
|
|
10
|
+
import { runCreateHooks } from "./hooks.js";
|
|
11
11
|
import { followManagedWorktree, worktreePath } from "./workspace.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { removeCollectableScratchWorktrees } from "./scratch.js";
|
|
13
|
+
import { reconcileTerminalManagedWorktree, removeRef, updateRef } from "./terminal-reconcile.js";
|
|
14
14
|
const pathExists = (path) => access(path).then(() => true, () => false);
|
|
15
15
|
function deliveryRefFor(contract) {
|
|
16
16
|
return `${DELIVERY_REF_NAMESPACE}/${contractPhysicalName(contract)}`;
|
|
@@ -25,52 +25,9 @@ function missingPlaceLag(repository) {
|
|
|
25
25
|
diagnostic: `managed Contract is unappointed: ${join(repository.commonDirectory, "keiyaku", "places.json")}`,
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
async function updateRef(repository, ref, desired) {
|
|
29
|
-
const before = await readRef(repository, ref);
|
|
30
|
-
if (before === desired)
|
|
31
|
-
return { kind: "ref", name: ref, action: "unchanged", before, after: desired };
|
|
32
|
-
await runGit(repository, [
|
|
33
|
-
"update-ref",
|
|
34
|
-
"--no-deref",
|
|
35
|
-
ref,
|
|
36
|
-
gitObjectIdForSnapshot(desired),
|
|
37
|
-
before ?? "0".repeat(desired.length),
|
|
38
|
-
]);
|
|
39
|
-
return { kind: "ref", name: ref, action: before === null ? "created" : "updated", before, after: desired };
|
|
40
|
-
}
|
|
41
|
-
async function removeRef(repository, ref) {
|
|
42
|
-
const before = await readRef(repository, ref);
|
|
43
|
-
if (before === null)
|
|
44
|
-
return { kind: "ref", name: ref, action: "unchanged", before: null, after: null };
|
|
45
|
-
await runGit(repository, ["update-ref", "--no-deref", "-d", ref, before]);
|
|
46
|
-
return { kind: "ref", name: ref, action: "removed", before, after: null };
|
|
47
|
-
}
|
|
48
|
-
async function removeRefWithCustody(repository, ref, custodian, expectedCustodian) {
|
|
49
|
-
const before = await readRef(repository, ref);
|
|
50
|
-
if (before === null)
|
|
51
|
-
return { kind: "ref", name: ref, action: "unchanged", before: null, after: null };
|
|
52
|
-
if ((await readRef(repository, custodian)) !== expectedCustodian) {
|
|
53
|
-
return { kind: "ref", name: ref, action: "unchanged", before, after: before };
|
|
54
|
-
}
|
|
55
|
-
await runGit(repository, ["update-ref", "--stdin", "--no-deref"], [
|
|
56
|
-
"start",
|
|
57
|
-
`verify ${custodian} ${gitObjectIdForSnapshot(expectedCustodian)}`,
|
|
58
|
-
`delete ${ref} ${before}`,
|
|
59
|
-
"prepare",
|
|
60
|
-
"commit",
|
|
61
|
-
"",
|
|
62
|
-
].join("\n"));
|
|
63
|
-
return { kind: "ref", name: ref, action: "removed", before, after: null };
|
|
64
|
-
}
|
|
65
|
-
function retainedRefEffect(ref, snapshot) {
|
|
66
|
-
return { kind: "ref", name: ref, action: "unchanged", before: snapshot, after: snapshot };
|
|
67
|
-
}
|
|
68
28
|
async function acquireWorktreeTopology(repository) {
|
|
69
29
|
return { paths: new Set(await registeredWorktreePaths(repository)) };
|
|
70
30
|
}
|
|
71
|
-
function fromPrimaryWorktree(repository) {
|
|
72
|
-
return { ...repository, effectiveCwd: repository.primaryWorktree };
|
|
73
|
-
}
|
|
74
31
|
function diagnostic(error) {
|
|
75
32
|
return error instanceof Error ? error.message : String(error);
|
|
76
33
|
}
|
|
@@ -101,75 +58,13 @@ async function worktree(repository, topology, path, desired) {
|
|
|
101
58
|
topology.paths.add(path);
|
|
102
59
|
return { kind: "worktree", path, action: "created" };
|
|
103
60
|
}
|
|
104
|
-
async function removeWorktree(repository, topology, path, force = false) {
|
|
105
|
-
const registered = topology.paths.has(path);
|
|
106
|
-
if (!registered) {
|
|
107
|
-
return {
|
|
108
|
-
effect: { kind: "worktree", path, action: "unchanged" },
|
|
109
|
-
retained: await pathExists(path),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (!(await pathExists(path))) {
|
|
113
|
-
await runGit(repository, ["worktree", "remove", path]);
|
|
114
|
-
topology.paths.delete(path);
|
|
115
|
-
return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
await runGit(repository, ["worktree", "remove", ...(force ? ["--force"] : []), path]);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return { effect: { kind: "worktree", path, action: "unchanged" }, retained: true };
|
|
122
|
-
}
|
|
123
|
-
topology.paths.delete(path);
|
|
124
|
-
return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
|
|
125
|
-
}
|
|
126
61
|
async function removeCollectableScratch(repository, topology, effects, lag) {
|
|
127
|
-
for (const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (removal.retained)
|
|
132
|
-
lag.push({ kind: "worktree-retained", path: scratch.path });
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
scratch.release();
|
|
136
|
-
}
|
|
62
|
+
for (const removal of await removeCollectableScratchWorktrees(repository, topology.paths)) {
|
|
63
|
+
effects.push({ kind: "worktree", path: removal.path, action: removal.action });
|
|
64
|
+
if (removal.retained)
|
|
65
|
+
lag.push({ kind: "worktree-retained", path: removal.path });
|
|
137
66
|
}
|
|
138
67
|
}
|
|
139
|
-
async function refRows(repository) {
|
|
140
|
-
const output = (await runGit(repository, ["for-each-ref", "--format=%(refname)%00%(objectname)"])).toString("utf8");
|
|
141
|
-
if (output.length === 0)
|
|
142
|
-
return [];
|
|
143
|
-
return output
|
|
144
|
-
.trimEnd()
|
|
145
|
-
.split("\n")
|
|
146
|
-
.map((row) => {
|
|
147
|
-
const [ref, oid, extra] = row.split("\0");
|
|
148
|
-
if (ref === undefined || oid === undefined || extra !== undefined)
|
|
149
|
-
throw new Error("Git ref row is malformed");
|
|
150
|
-
return { ref, oid };
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
async function commitIsAncestor(repository, ancestor, descendant) {
|
|
154
|
-
try {
|
|
155
|
-
await runGit(repository, ["merge-base", "--is-ancestor", gitObjectIdForSnapshot(ancestor), descendant]);
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
if (error instanceof GitPlumbingError && error.status === 1)
|
|
160
|
-
return false;
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
async function snapshotCustodian(repository, snapshot) {
|
|
165
|
-
for (const row of await refRows(repository)) {
|
|
166
|
-
if (isKeiyakuOwnedRef(row.ref))
|
|
167
|
-
continue;
|
|
168
|
-
if (await commitIsAncestor(repository, snapshot, row.oid))
|
|
169
|
-
return { ref: row.ref, oid: mintSnapshotId(row.oid) };
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
68
|
async function reconcileTargetCheckouts(repository, state) {
|
|
174
69
|
if (state.terminal?.kind !== "claimed" || state.coordinates.target === undefined || state.delivery === null) {
|
|
175
70
|
return complete();
|
|
@@ -208,190 +103,6 @@ async function reconcileTargetCheckouts(repository, state) {
|
|
|
208
103
|
result = failed("effect", releaseFailure, result.effects, result.lag);
|
|
209
104
|
return result;
|
|
210
105
|
}
|
|
211
|
-
function retainTerminalWorktree(path, retainedLag, { effects, lag }) {
|
|
212
|
-
effects.push({ kind: "worktree", path, action: "unchanged" });
|
|
213
|
-
lag.push(retainedLag);
|
|
214
|
-
return complete(effects, lag);
|
|
215
|
-
}
|
|
216
|
-
async function terminalSealExpectations(channel, state) {
|
|
217
|
-
const snapshots = terminalSealSnapshots(state);
|
|
218
|
-
const objects = await channel.readObjects(snapshots.map(gitObjectIdForSnapshot));
|
|
219
|
-
return decodeTerminalSealExpectations(state, objects);
|
|
220
|
-
}
|
|
221
|
-
async function removeSealedTerminalWorktree({ repository, topology, path, state, expected, hooks, retryHooks, acc, }) {
|
|
222
|
-
if (topology.paths.has(path) && (await pathExists(path))) {
|
|
223
|
-
const terminal = state.terminal;
|
|
224
|
-
if (terminal === null)
|
|
225
|
-
throw new Error(`terminal worktree cleanup received active Contract ${state.id}`);
|
|
226
|
-
const canRecover = terminal.kind === "abandoned";
|
|
227
|
-
let recovery = null;
|
|
228
|
-
const recordRecovery = async (workspace) => {
|
|
229
|
-
const snapshot = mintSnapshotId(await writeCommit({
|
|
230
|
-
repository,
|
|
231
|
-
tree: workspace.tree,
|
|
232
|
-
parent: gitObjectIdForSnapshot(recovery?.snapshot ?? workspace.head),
|
|
233
|
-
message: `${state.id}: ephemeral abandoned-workspace recovery\n\nKeiyaku-Contract: ${state.id}`,
|
|
234
|
-
actor: "Keiyaku Recovery",
|
|
235
|
-
at: terminal.at,
|
|
236
|
-
}));
|
|
237
|
-
const effect = {
|
|
238
|
-
kind: "recovery-snapshot",
|
|
239
|
-
action: "created",
|
|
240
|
-
snapshot,
|
|
241
|
-
retention: "ephemeral",
|
|
242
|
-
};
|
|
243
|
-
const existing = acc.effects.findIndex((item) => item.kind === "recovery-snapshot");
|
|
244
|
-
if (existing < 0)
|
|
245
|
-
acc.effects.push(effect);
|
|
246
|
-
else
|
|
247
|
-
acc.effects[existing] = effect;
|
|
248
|
-
return { snapshot, workspace };
|
|
249
|
-
};
|
|
250
|
-
const before = await observeTerminalWorkspace(repository, path, expected);
|
|
251
|
-
const beforeWorkspace = before.workspace;
|
|
252
|
-
const beforeHooks = before.unsealed;
|
|
253
|
-
if (beforeHooks !== null) {
|
|
254
|
-
if (!canRecover || beforeWorkspace.submodules.length > 0) {
|
|
255
|
-
return retainTerminalWorktree(path, beforeHooks, acc);
|
|
256
|
-
}
|
|
257
|
-
recovery = await recordRecovery(beforeWorkspace);
|
|
258
|
-
}
|
|
259
|
-
const hookLag = await runDestroyHooks(path, await worktreeGitDirectory(repository, path), hooks, retryHooks);
|
|
260
|
-
if (hookLag !== null)
|
|
261
|
-
return retainTerminalWorktree(path, hookLag, acc);
|
|
262
|
-
const after = await observeTerminalWorkspace(repository, path, expected);
|
|
263
|
-
const afterWorkspace = after.workspace;
|
|
264
|
-
const afterHooks = after.unsealed;
|
|
265
|
-
if (afterHooks !== null) {
|
|
266
|
-
if (!canRecover || afterWorkspace.submodules.length > 0) {
|
|
267
|
-
return retainTerminalWorktree(path, afterHooks, acc);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (canRecover &&
|
|
271
|
-
(recovery === null ||
|
|
272
|
-
recovery.workspace.head !== afterWorkspace.head ||
|
|
273
|
-
recovery.workspace.tree !== afterWorkspace.tree) &&
|
|
274
|
-
(recovery !== null ||
|
|
275
|
-
beforeWorkspace.head !== afterWorkspace.head ||
|
|
276
|
-
beforeWorkspace.tree !== afterWorkspace.tree)) {
|
|
277
|
-
recovery = await recordRecovery(afterWorkspace);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const removal = await removeWorktree(repository, topology, path, true);
|
|
281
|
-
if (removal.retained)
|
|
282
|
-
return retainTerminalWorktree(path, { kind: "worktree-retained", path }, acc);
|
|
283
|
-
acc.effects.push(removal.effect);
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
async function targetCustodyForClaimedIntegration(repository, state, integration) {
|
|
287
|
-
if (state.terminal?.kind !== "claimed" || state.coordinates.target === undefined)
|
|
288
|
-
return null;
|
|
289
|
-
const target = await readRef(repository, state.coordinates.target);
|
|
290
|
-
if (target === null)
|
|
291
|
-
return null;
|
|
292
|
-
const oid = mintSnapshotId(target);
|
|
293
|
-
return (await commitIsAncestor(repository, integration, oid)) ? { ref: state.coordinates.target, oid } : null;
|
|
294
|
-
}
|
|
295
|
-
function sealedTree(expected, snapshot) {
|
|
296
|
-
const tree = expected.treeBySnapshot.get(snapshot);
|
|
297
|
-
if (tree === undefined)
|
|
298
|
-
throw new Error(`terminal seal tree was not resolved: ${snapshot}`);
|
|
299
|
-
return tree;
|
|
300
|
-
}
|
|
301
|
-
async function releaseTerminalCustody({ repository, state, resolveSeal, ref, pin, acc: { effects }, }) {
|
|
302
|
-
const [deliveryRef, candidatePin] = await Promise.all([readRef(repository, ref), readRef(repository, pin)]);
|
|
303
|
-
if (deliveryRef === null && candidatePin === null)
|
|
304
|
-
return;
|
|
305
|
-
if (state.delivery === null) {
|
|
306
|
-
const custodian = await snapshotCustodian(repository, state.coordinates.start);
|
|
307
|
-
if (deliveryRef !== null) {
|
|
308
|
-
effects.push(custodian === null
|
|
309
|
-
? retainedRefEffect(ref, deliveryRef)
|
|
310
|
-
: await removeRefWithCustody(repository, ref, custodian.ref, custodian.oid));
|
|
311
|
-
}
|
|
312
|
-
if (candidatePin !== null)
|
|
313
|
-
effects.push(await removeRef(repository, pin));
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const tender = state.delivery.data.tenderSnapshot;
|
|
317
|
-
const integration = state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot;
|
|
318
|
-
const target = await targetCustodyForClaimedIntegration(repository, state, integration);
|
|
319
|
-
if (deliveryRef !== null && target !== null) {
|
|
320
|
-
if (tender === integration) {
|
|
321
|
-
effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
const expected = await resolveSeal();
|
|
325
|
-
if (sealedTree(expected, tender) === sealedTree(expected, integration)) {
|
|
326
|
-
effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
effects.push(retainedRefEffect(ref, deliveryRef));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
else if (deliveryRef !== null) {
|
|
334
|
-
effects.push(retainedRefEffect(ref, deliveryRef));
|
|
335
|
-
}
|
|
336
|
-
if (candidatePin !== null) {
|
|
337
|
-
if (target !== null) {
|
|
338
|
-
effects.push(await removeRefWithCustody(repository, pin, target.ref, target.oid));
|
|
339
|
-
}
|
|
340
|
-
else if (tender === integration && deliveryRef !== null) {
|
|
341
|
-
effects.push(await removeRefWithCustody(repository, pin, ref, tender));
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
effects.push(retainedRefEffect(pin, candidatePin));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
async function reconcileTerminalManagedWorktree({ repository, channel, hooks, retryHooks, retainTerminalWorktree, place }, state, topology, acc) {
|
|
349
|
-
const primary = fromPrimaryWorktree(repository);
|
|
350
|
-
const ref = deliveryRefFor(state.id);
|
|
351
|
-
const pin = candidatePinRefFor(state.id);
|
|
352
|
-
const path = place === undefined ? undefined : worktreePath(repository, place);
|
|
353
|
-
if (path === undefined) {
|
|
354
|
-
if (retainTerminalWorktree === true)
|
|
355
|
-
return complete(acc.effects, acc.lag);
|
|
356
|
-
await releaseTerminalCustody({
|
|
357
|
-
repository: primary,
|
|
358
|
-
state,
|
|
359
|
-
resolveSeal: async () => await terminalSealExpectations(channel, state),
|
|
360
|
-
ref,
|
|
361
|
-
pin,
|
|
362
|
-
acc,
|
|
363
|
-
});
|
|
364
|
-
return complete(acc.effects, acc.lag);
|
|
365
|
-
}
|
|
366
|
-
const expected = await terminalSealExpectations(channel, state);
|
|
367
|
-
acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
|
|
368
|
-
if (state.delivery !== null) {
|
|
369
|
-
acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
|
|
370
|
-
}
|
|
371
|
-
if (retainTerminalWorktree === true)
|
|
372
|
-
return complete(acc.effects, acc.lag);
|
|
373
|
-
const retained = await removeSealedTerminalWorktree({
|
|
374
|
-
repository: primary,
|
|
375
|
-
topology,
|
|
376
|
-
path,
|
|
377
|
-
state,
|
|
378
|
-
expected,
|
|
379
|
-
hooks,
|
|
380
|
-
retryHooks,
|
|
381
|
-
acc,
|
|
382
|
-
});
|
|
383
|
-
if (retained !== null)
|
|
384
|
-
return retained;
|
|
385
|
-
await releaseTerminalCustody({
|
|
386
|
-
repository: primary,
|
|
387
|
-
state,
|
|
388
|
-
resolveSeal: async () => expected,
|
|
389
|
-
ref,
|
|
390
|
-
pin,
|
|
391
|
-
acc,
|
|
392
|
-
});
|
|
393
|
-
return complete(acc.effects, acc.lag);
|
|
394
|
-
}
|
|
395
106
|
async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, place }, state, topology, { effects, lag }) {
|
|
396
107
|
if (place === undefined)
|
|
397
108
|
return complete(effects, [...lag, missingPlaceLag(repository)]);
|
|
@@ -439,8 +150,9 @@ async function reconcileWithTopology(input, state, topology) {
|
|
|
439
150
|
const targetCheckouts = await reconcileTargetCheckouts(repository, state);
|
|
440
151
|
effects.push(...targetCheckouts.effects);
|
|
441
152
|
lag.push(...targetCheckouts.lag);
|
|
442
|
-
if (state.terminal)
|
|
443
|
-
return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag });
|
|
153
|
+
if (state.terminal) {
|
|
154
|
+
return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag }, { ref: deliveryRefFor(state.id), pin: candidatePinRefFor(state.id) });
|
|
155
|
+
}
|
|
444
156
|
return await reconcileActiveManagedWorktree(input, state, topology, { effects, lag });
|
|
445
157
|
}
|
|
446
158
|
catch (error) {
|
|
@@ -4,14 +4,17 @@ export type MaterializedScratchCandidate = Readonly<{
|
|
|
4
4
|
cwd: string;
|
|
5
5
|
dispose: () => Promise<WorktreeLeak | null>;
|
|
6
6
|
}>;
|
|
7
|
-
export type CollectableScratchWorktree = Readonly<{
|
|
8
|
-
path: string;
|
|
9
|
-
release(): void;
|
|
10
|
-
}>;
|
|
11
7
|
export type WorktreeLeak = Readonly<{
|
|
12
8
|
path: string;
|
|
13
9
|
diagnostic: string;
|
|
14
10
|
}>;
|
|
15
|
-
|
|
11
|
+
type CollectableScratchRemoval = Readonly<{
|
|
12
|
+
path: string;
|
|
13
|
+
action: "removed" | "unchanged";
|
|
14
|
+
retained: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
/** Remove only abandoned Verification scratch worktrees whose ownership locks are held. */
|
|
17
|
+
export declare function removeCollectableScratchWorktrees(repository: GitRepository, paths: Set<string>): Promise<readonly CollectableScratchRemoval[]>;
|
|
16
18
|
/** Materialize a disposable candidate worktree and own only its removal. */
|
|
17
19
|
export declare function materializeScratchCandidate(repository: GitRepository, candidate: SnapshotId): Promise<MaterializedScratchCandidate>;
|
|
20
|
+
export {};
|
package/build/src/git/scratch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { access, realpath } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import { tryAcquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
@@ -17,7 +17,7 @@ function ownershipLockPath(path) {
|
|
|
17
17
|
const match = SCRATCH_PATTERN.exec(basename(path));
|
|
18
18
|
return match === null ? null : join(SCRATCH_ROOT, `.${SCRATCH_PREFIX}${match[1]}.owner.sqlite`);
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
async function collectableScratchWorktrees(paths) {
|
|
21
21
|
const collectable = [];
|
|
22
22
|
for (const path of paths) {
|
|
23
23
|
const lockPath = ownershipLockPath(path);
|
|
@@ -29,6 +29,33 @@ export async function collectableScratchWorktrees(paths) {
|
|
|
29
29
|
}
|
|
30
30
|
return collectable;
|
|
31
31
|
}
|
|
32
|
+
const pathExists = (path) => access(path).then(() => true, () => false);
|
|
33
|
+
/** Remove only abandoned Verification scratch worktrees whose ownership locks are held. */
|
|
34
|
+
export async function removeCollectableScratchWorktrees(repository, paths) {
|
|
35
|
+
const removals = [];
|
|
36
|
+
for (const scratch of await collectableScratchWorktrees(paths)) {
|
|
37
|
+
try {
|
|
38
|
+
if (!(await pathExists(scratch.path))) {
|
|
39
|
+
await runGit(repository, ["worktree", "remove", scratch.path]);
|
|
40
|
+
paths.delete(scratch.path);
|
|
41
|
+
removals.push({ path: scratch.path, action: "removed", retained: false });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await runGit(repository, ["worktree", "remove", "--force", scratch.path]);
|
|
46
|
+
paths.delete(scratch.path);
|
|
47
|
+
removals.push({ path: scratch.path, action: "removed", retained: false });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
removals.push({ path: scratch.path, action: "unchanged", retained: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
scratch.release();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return removals;
|
|
58
|
+
}
|
|
32
59
|
/** Materialize a disposable candidate worktree and own only its removal. */
|
|
33
60
|
export async function materializeScratchCandidate(repository, candidate) {
|
|
34
61
|
const cwd = scratchPath();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContractState, SnapshotId } from "../core/facts/types.js";
|
|
2
|
+
import type { GitDecodeChannel } from "./read-observation.js";
|
|
3
|
+
import { type WorktreeHooks } from "./hooks.js";
|
|
4
|
+
import { type GitRepository } from "./process.js";
|
|
5
|
+
import type { Effect, ReconcileAccumulation, ReconcileResult, WorktreeTopology } from "./reconcile.js";
|
|
6
|
+
type TerminalReconcileInput = Readonly<{
|
|
7
|
+
repository: GitRepository;
|
|
8
|
+
channel: GitDecodeChannel;
|
|
9
|
+
hooks: WorktreeHooks;
|
|
10
|
+
retryHooks: boolean;
|
|
11
|
+
retainTerminalWorktree?: boolean;
|
|
12
|
+
place?: string;
|
|
13
|
+
}>;
|
|
14
|
+
type TerminalCustodyRefs = Readonly<{
|
|
15
|
+
ref: string;
|
|
16
|
+
pin: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function updateRef(repository: GitRepository, ref: string, desired: SnapshotId): Promise<Effect>;
|
|
19
|
+
export declare function removeRef(repository: GitRepository, ref: string): Promise<Effect>;
|
|
20
|
+
export declare function reconcileTerminalManagedWorktree({ repository, channel, hooks, retryHooks, retainTerminalWorktree, place }: TerminalReconcileInput, state: ContractState, topology: WorktreeTopology, acc: ReconcileAccumulation, { ref, pin }: TerminalCustodyRefs): Promise<ReconcileResult>;
|
|
21
|
+
export {};
|