@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.
Files changed (58) hide show
  1. package/build/src/akuma/akuma-errors.d.ts +6 -0
  2. package/build/src/akuma/akuma-errors.js +9 -0
  3. package/build/src/akuma/akuma-handle.d.ts +39 -0
  4. package/build/src/akuma/akuma-handle.js +263 -0
  5. package/build/src/akuma/akuma-observe.d.ts +19 -0
  6. package/build/src/akuma/akuma-observe.js +74 -0
  7. package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
  8. package/build/src/akuma/akuma-product-symbols.js +1 -0
  9. package/build/src/akuma/akuma-product.d.ts +18 -0
  10. package/build/src/akuma/akuma-product.js +154 -0
  11. package/build/src/akuma/akuma.d.ts +23 -70
  12. package/build/src/akuma/akuma.js +17 -433
  13. package/build/src/akuma/body-supervisor.d.ts +26 -0
  14. package/build/src/akuma/body-supervisor.js +89 -0
  15. package/build/src/akuma/body.d.ts +1 -1
  16. package/build/src/akuma/body.js +3 -2
  17. package/build/src/akuma/projection-read.d.ts +17 -0
  18. package/build/src/akuma/projection-read.js +135 -0
  19. package/build/src/akuma/projection.d.ts +1 -18
  20. package/build/src/akuma/projection.js +21 -155
  21. package/build/src/akuma/request-execution.d.ts +6 -0
  22. package/build/src/akuma/request-execution.js +86 -0
  23. package/build/src/akuma/request-serve.d.ts +12 -1
  24. package/build/src/akuma/request-serve.js +2 -102
  25. package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
  26. package/build/src/akuma/{body-turn.js → turn-drive.js} +4 -99
  27. package/build/src/cli/commands/akuma.d.ts +0 -6
  28. package/build/src/cli/commands/akuma.js +1 -11
  29. package/build/src/cli/commands/contract-invoke.d.ts +25 -0
  30. package/build/src/cli/commands/contract-invoke.js +193 -0
  31. package/build/src/cli/commands/contract.d.ts +12 -0
  32. package/build/src/cli/commands/contract.js +229 -0
  33. package/build/src/cli/invoke.js +24 -184
  34. package/build/src/cli/parse.d.ts +1 -1
  35. package/build/src/cli/parse.js +8 -222
  36. package/build/src/cli/render/akuma-activity.d.ts +38 -0
  37. package/build/src/cli/render/akuma-activity.js +362 -0
  38. package/build/src/cli/render/akuma.d.ts +1 -1
  39. package/build/src/cli/render/akuma.js +14 -371
  40. package/build/src/cli/render/contract-history.d.ts +2 -0
  41. package/build/src/cli/render/contract-history.js +83 -0
  42. package/build/src/cli/render/contract.d.ts +1 -2
  43. package/build/src/cli/render/contract.js +1 -83
  44. package/build/src/git/reconcile.d.ts +8 -1
  45. package/build/src/git/reconcile.js +13 -301
  46. package/build/src/git/scratch.d.ts +8 -5
  47. package/build/src/git/scratch.js +29 -2
  48. package/build/src/git/terminal-reconcile.d.ts +21 -0
  49. package/build/src/git/terminal-reconcile.js +278 -0
  50. package/build/src/library/contract-bind.d.ts +26 -0
  51. package/build/src/library/contract-bind.js +106 -0
  52. package/build/src/library/contract-operations.d.ts +77 -0
  53. package/build/src/library/contract-operations.js +130 -0
  54. package/build/src/library/contract-types.d.ts +95 -0
  55. package/build/src/library/contract-types.js +1 -0
  56. package/build/src/library/contract.d.ts +11 -136
  57. package/build/src/library/contract.js +9 -222
  58. 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, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, writeCommit, worktreeGitDirectory, } from "./repository.js";
6
- import { GitPlumbingError, runGit } from "./process.js";
7
- import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
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, runDestroyHooks } from "./hooks.js";
10
+ import { runCreateHooks } from "./hooks.js";
11
11
  import { followManagedWorktree, worktreePath } from "./workspace.js";
12
- import { collectableScratchWorktrees } from "./scratch.js";
13
- import { terminalSealExpectations as decodeTerminalSealExpectations, terminalSealSnapshots, observeTerminalWorkspace, } from "./terminal-seal.js";
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 scratch of await collectableScratchWorktrees(topology.paths)) {
128
- try {
129
- const removal = await removeWorktree(repository, topology, scratch.path, true);
130
- effects.push(removal.effect);
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
- export declare function collectableScratchWorktrees(paths: Iterable<string>): Promise<readonly CollectableScratchWorktree[]>;
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 {};
@@ -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
- export async function collectableScratchWorktrees(paths) {
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 {};