@astrosheep/keiyaku 4.5.11 → 4.5.13
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/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +135 -377
- package/build/integrations/pi/keiyaku.ts +11 -116
- package/build/src/akuma/akuma-errors.d.ts +1 -0
- package/build/src/akuma/akuma-errors.js +1 -0
- package/build/src/akuma/akuma-handle.d.ts +1 -0
- package/build/src/akuma/akuma-handle.js +6 -7
- package/build/src/akuma/akuma-product.d.ts +6 -8
- package/build/src/akuma/akuma-product.js +141 -57
- package/build/src/akuma/akuma.d.ts +5 -7
- package/build/src/akuma/akuma.js +1 -22
- package/build/src/akuma/archetype.d.ts +6 -5
- package/build/src/akuma/archetype.js +137 -38
- package/build/src/akuma/body.d.ts +5 -5
- package/build/src/akuma/body.js +223 -63
- package/build/src/akuma/call-input.d.ts +4 -0
- package/build/src/akuma/call-input.js +21 -0
- package/build/src/akuma/call-request.d.ts +5 -8
- package/build/src/akuma/call-request.js +28 -73
- package/build/src/akuma/fleet-execution.d.ts +26 -0
- package/build/src/akuma/fleet-execution.js +109 -0
- package/build/src/{library/fleet-result.d.ts → akuma/fleet-observation.d.ts} +13 -57
- package/build/src/{library/fleet-result.js → akuma/fleet-observation.js} +4 -25
- package/build/src/akuma/fleet-request.d.ts +122 -0
- package/build/src/akuma/fleet-request.js +168 -0
- package/build/src/akuma/heart/facts.d.ts +23 -0
- package/build/src/akuma/heart/facts.js +7 -0
- package/build/src/akuma/heart/index.d.ts +43 -4
- package/build/src/akuma/heart/index.js +107 -7
- package/build/src/akuma/heart/request-authority.d.ts +2 -0
- package/build/src/akuma/heart/request-authority.js +28 -3
- package/build/src/akuma/heart/request-rows.d.ts +2 -0
- package/build/src/akuma/heart/request-rows.js +24 -5
- package/build/src/akuma/heart/rows.d.ts +4 -1
- package/build/src/akuma/heart/rows.js +46 -19
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +28 -6
- package/build/src/akuma/heart/storage.d.ts +1 -1
- package/build/src/akuma/heart/tells.d.ts +29 -0
- package/build/src/akuma/heart/tells.js +136 -5
- package/build/src/akuma/heart/timeline.js +9 -3
- package/build/src/akuma/projection-read.js +36 -3
- package/build/src/akuma/projection.js +3 -4
- package/build/src/akuma/provider.d.ts +4 -0
- package/build/src/akuma/provider.js +56 -3
- package/build/src/akuma/providers/claude/index.js +3 -0
- package/build/src/akuma/providers/codex-app-server/index.js +14 -12
- package/build/src/akuma/providers/pi/index.js +7 -1
- package/build/src/akuma/publication.js +65 -49
- package/build/src/akuma/request-lifecycle.d.ts +5 -4
- package/build/src/akuma/request-lifecycle.js +29 -16
- package/build/src/akuma/request-rendezvous.d.ts +15 -12
- package/build/src/akuma/request-rendezvous.js +80 -51
- package/build/src/akuma/request-serve.d.ts +1 -2
- package/build/src/akuma/request-serve.js +94 -39
- package/build/src/akuma/request-wire.d.ts +57 -26
- package/build/src/akuma/request-wire.js +64 -25
- package/build/src/akuma/turn-drive.d.ts +5 -4
- package/build/src/akuma/turn-drive.js +27 -11
- package/build/src/akuma-body.d.ts +4 -6
- package/build/src/akuma-body.js +19 -17
- package/build/src/bounded-list.d.ts +8 -0
- package/build/src/bounded-list.js +14 -0
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +1 -0
- package/build/src/cli/commands/akuma-invoke.js +1 -25
- package/build/src/cli/commands/contract-help.d.ts +5 -2
- package/build/src/cli/commands/contract-help.js +62 -12
- package/build/src/cli/commands/contract-invoke.d.ts +5 -0
- package/build/src/cli/commands/contract-invoke.js +26 -1
- package/build/src/cli/commands/contract.js +50 -18
- package/build/src/cli/commands/task-invoke.js +10 -2
- package/build/src/cli/commands/task-query.js +3 -4
- package/build/src/cli/coordinates.d.ts +13 -0
- package/build/src/cli/coordinates.js +38 -24
- package/build/src/cli/draft.d.ts +1 -1
- package/build/src/cli/invoke.js +14 -1
- package/build/src/cli/render/akuma-activity.d.ts +0 -1
- package/build/src/cli/render/akuma-activity.js +5 -38
- package/build/src/cli/render/akuma.js +4 -1
- package/build/src/cli/render/audit.js +4 -1
- package/build/src/cli/render/catalog.js +13 -39
- package/build/src/cli/render/contract.js +4 -14
- package/build/src/cli/render/kanshi-akuma.js +3 -8
- package/build/src/cli/render/kanshi.js +8 -34
- package/build/src/cli/render/nuke.js +7 -3
- package/build/src/cli/render/receipt.d.ts +1 -0
- package/build/src/cli/render/receipt.js +8 -0
- package/build/src/cli/render/refusal.js +4 -3
- package/build/src/cli/render/task.js +4 -37
- package/build/src/cli/render/terminal.d.ts +2 -6
- package/build/src/cli/render/terminal.js +4 -9
- package/build/src/cli/result.d.ts +2 -1
- package/build/src/contract-guidance.js +13 -3
- package/build/src/contract-worktree.d.ts +1 -0
- package/build/src/contract-worktree.js +26 -2
- package/build/src/core/facts/codec.d.ts +2 -1
- package/build/src/core/facts/codec.js +3 -0
- package/build/src/core/facts/gate.d.ts +2 -1
- package/build/src/core/facts/gate.js +63 -0
- package/build/src/core/verbs/abandon.d.ts +2 -1
- package/build/src/core/verbs/abandon.js +16 -0
- package/build/src/core/verbs/amend.d.ts +2 -1
- package/build/src/core/verbs/amend.js +20 -0
- package/build/src/core/verbs/arc.d.ts +2 -1
- package/build/src/core/verbs/arc.js +16 -0
- package/build/src/core/verbs/attestation.d.ts +2 -1
- package/build/src/core/verbs/attestation.js +16 -0
- package/build/src/core/verbs/bind.d.ts +2 -1
- package/build/src/core/verbs/bind.js +16 -0
- package/build/src/core/verbs/deliver.d.ts +2 -1
- package/build/src/core/verbs/deliver.js +16 -0
- package/build/src/core/verbs/placement.d.ts +2 -1
- package/build/src/core/verbs/placement.js +58 -1
- package/build/src/dispatch/association.d.ts +13 -0
- package/build/src/dispatch/association.js +20 -0
- package/build/src/dispatch/index.d.ts +6 -0
- package/build/src/dispatch/index.js +27 -3
- package/build/src/git/hooks.d.ts +9 -1
- package/build/src/git/hooks.js +79 -1
- package/build/src/git/nuke.d.ts +2 -1
- package/build/src/git/nuke.js +6 -11
- package/build/src/git/private-state-seat.d.ts +26 -1
- package/build/src/git/private-state-seat.js +78 -13
- package/build/src/git/process.d.ts +2 -0
- package/build/src/git/reconcile.d.ts +1 -2
- package/build/src/git/reconcile.js +0 -6
- package/build/src/git/repository.d.ts +0 -2
- package/build/src/git/repository.js +27 -25
- package/build/src/git/result-codec.d.ts +29 -0
- package/build/src/git/result-codec.js +306 -0
- package/build/src/git/target-placement.js +2 -8
- package/build/src/git/workspace.d.ts +2 -0
- package/build/src/git/workspace.js +4 -0
- package/build/src/index.d.ts +1 -0
- package/build/src/kanshi/fleet.js +1 -1
- package/build/src/kanshi/read.js +55 -28
- package/build/src/kanshi/report.d.ts +4 -3
- package/build/src/kanshi/select.js +14 -1
- package/build/src/library/address.d.ts +1 -0
- package/build/src/library/address.js +4 -7
- package/build/src/library/akuma-creation.d.ts +5 -3
- package/build/src/library/akuma-creation.js +90 -33
- package/build/src/library/audit.js +2 -1
- package/build/src/library/catalog.d.ts +10 -2
- package/build/src/library/catalog.js +65 -25
- package/build/src/library/composition.d.ts +1 -0
- package/build/src/library/composition.js +1 -0
- package/build/src/library/configuration.d.ts +4 -21
- package/build/src/library/configuration.js +2 -158
- package/build/src/library/continuation.d.ts +20 -1
- package/build/src/library/continuation.js +36 -0
- package/build/src/library/contract-bind.d.ts +1 -0
- package/build/src/library/contract-bind.js +16 -5
- package/build/src/library/contract-forwarding.d.ts +2 -2
- package/build/src/library/contract-forwarding.js +18 -4
- package/build/src/library/contract-handle.js +4 -3
- package/build/src/library/contract-operations.d.ts +5 -9
- package/build/src/library/contract-operations.js +34 -47
- package/build/src/library/contract.d.ts +11 -2
- package/build/src/library/contract.js +21 -1
- package/build/src/library/delivery.d.ts +4 -1
- package/build/src/library/delivery.js +47 -0
- package/build/src/library/fleet.d.ts +4 -136
- package/build/src/library/fleet.js +31 -315
- package/build/src/library/keiyaku.d.ts +7 -3
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +431 -3
- package/build/src/library/mutation.js +192 -19
- package/build/src/library/nuke.d.ts +7 -1
- package/build/src/library/nuke.js +18 -4
- package/build/src/library/reconcile.d.ts +14 -0
- package/build/src/library/reconcile.js +22 -8
- package/build/src/library/refusal.d.ts +9 -1
- package/build/src/library/refusal.js +76 -0
- package/build/src/library/region.d.ts +0 -8
- package/build/src/library/region.js +1 -29
- package/build/src/library/result-codec.d.ts +2 -0
- package/build/src/library/result-codec.js +12 -0
- package/build/src/markdown/lex.js +9 -2
- package/build/src/markdown/types.d.ts +2 -1
- package/build/src/markdown/types.js +3 -1
- package/build/src/plugin/akuma-signals.d.ts +12 -0
- package/build/src/plugin/akuma-signals.js +14 -0
- package/build/src/plugin/public.d.ts +51 -0
- package/build/src/plugin/public.js +1 -0
- package/build/src/plugin/runtime.d.ts +14 -0
- package/build/src/plugin/runtime.js +357 -0
- package/build/src/protocol/amend.js +3 -3
- package/build/src/protocol/attempt.d.ts +2 -1
- package/build/src/protocol/attempt.js +1 -1
- package/build/src/protocol/audit.d.ts +1 -1
- package/build/src/protocol/audit.js +1 -1
- package/build/src/protocol/bind.d.ts +3 -1
- package/build/src/protocol/bind.js +26 -0
- package/build/src/protocol/completion.js +0 -1
- package/build/src/protocol/deliver.d.ts +1 -1
- package/build/src/protocol/deliver.js +136 -57
- package/build/src/protocol/intent.d.ts +0 -1
- package/build/src/protocol/intent.js +0 -1
- package/build/src/protocol/operations.d.ts +9 -2
- package/build/src/protocol/operations.js +16 -1
- package/build/src/protocol/outcome.d.ts +3 -0
- package/build/src/protocol/outcome.js +17 -1
- package/build/src/protocol/placement.js +10 -3
- package/build/src/protocol/read/status.d.ts +8 -0
- package/build/src/protocol/read/status.js +49 -0
- package/build/src/protocol/reintegrate.js +10 -3
- package/build/src/protocol/result-codec.d.ts +22 -0
- package/build/src/protocol/result-codec.js +442 -0
- package/build/src/protocol/review.d.ts +1 -1
- package/build/src/protocol/review.js +81 -46
- package/build/src/protocol/run.d.ts +22 -2
- package/build/src/protocol/run.js +119 -29
- package/build/src/runtime/proc/lifecycle.js +1 -1
- package/build/src/runtime/proc/line-rpc.d.ts +7 -1
- package/build/src/runtime/proc/line-rpc.js +49 -9
- package/build/src/runtime/proc/run.d.ts +0 -1
- package/build/src/runtime/proc/run.js +1 -11
- package/build/src/runtime/proc/termination.d.ts +1 -0
- package/build/src/runtime/proc/termination.js +63 -18
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settings.d.ts +10 -0
- package/build/src/settings.js +88 -2
- package/build/src/settlement/result-codec.d.ts +2 -0
- package/build/src/settlement/result-codec.js +56 -0
- package/build/src/settlement/settle.d.ts +5 -12
- package/build/src/settlement/settle.js +60 -55
- package/build/src/task/board.d.ts +3 -0
- package/build/src/task/board.js +22 -4
- package/build/src/task/catalog.d.ts +13 -2
- package/build/src/task/catalog.js +59 -5
- package/build/src/task/compose.d.ts +4 -2
- package/build/src/task/compose.js +56 -30
- package/build/src/task/created-observation.d.ts +39 -0
- package/build/src/task/created-observation.js +26 -0
- package/build/src/task/identity.d.ts +6 -0
- package/build/src/task/identity.js +48 -7
- package/build/src/task/index.d.ts +4 -3
- package/build/src/task/index.js +19 -16
- package/build/src/task/input.d.ts +3 -1
- package/build/src/task/input.js +7 -4
- package/build/src/task/lifecycle-operations.d.ts +6 -0
- package/build/src/task/lifecycle-operations.js +116 -0
- package/build/src/task/mutation-result.d.ts +762 -20
- package/build/src/task/mutation-result.js +34 -9
- package/build/src/task/mutation.d.ts +4 -9
- package/build/src/task/mutation.js +17 -52
- package/build/src/task/operation-types.d.ts +114 -0
- package/build/src/task/operation-types.js +1 -0
- package/build/src/task/operations.d.ts +6 -113
- package/build/src/task/operations.js +75 -117
- package/build/src/task/query.d.ts +8 -11
- package/build/src/task/query.js +7 -14
- package/build/src/task/store.d.ts +11 -1
- package/build/src/task/store.js +169 -55
- package/build/src/task/view.d.ts +4 -0
- package/build/src/task/view.js +7 -0
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/declaration.js +18 -1
- package/build/src/verification/execution.d.ts +0 -1
- package/build/src/verification/execution.js +5 -5
- package/build/src/world.d.ts +0 -2
- package/build/src/world.js +3 -7
- package/package.json +17 -5
- package/build/src/cli/square-edge.d.ts +0 -7
- package/build/src/cli/square-edge.js +0 -159
- package/build/src/git/ref-migration.d.ts +0 -22
- package/build/src/git/ref-migration.js +0 -55
- package/build/src/library/contract-forwarding-reconciliation-result.d.ts +0 -102
- package/build/src/library/contract-forwarding-reconciliation-result.js +0 -92
- package/build/src/library/contract-forwarding-result.d.ts +0 -4270
- package/build/src/library/contract-forwarding-result.js +0 -521
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import { join } from "node:path";
|
|
2
|
-
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
3
|
+
import { acquireSqliteTransactionLock, SqliteTransactionLockError } from "../coordination/sqlite-transaction-lock.js";
|
|
3
4
|
const PRIVATE_STATE_SEAT = "private-state.sqlite";
|
|
5
|
+
const PRIVATE_STATE_SEAT_ACQUIRE_TIMEOUT_MS = 5_000;
|
|
4
6
|
const privateStateSeat = Symbol("private-state-seat");
|
|
5
7
|
const confirmedPublications = new WeakSet();
|
|
8
|
+
const heldPrivateStateSeats = new AsyncLocalStorage();
|
|
9
|
+
/** Timed-out wait for the private-state publication seat. The holder and lock are unchanged. */
|
|
10
|
+
export class GitPrivateStateSeatContentionError extends Error {
|
|
11
|
+
reason = "timeout";
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = "GitPrivateStateSeatContentionError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
/** Record a known or exact-read-back-proven private-root publication. */
|
|
7
18
|
export function confirmPrivateStatePublication(seat) {
|
|
8
19
|
confirmedPublications.add(seat);
|
|
@@ -10,30 +21,84 @@ export function confirmPrivateStatePublication(seat) {
|
|
|
10
21
|
export function privateStatePublicationSeatPath(repository) {
|
|
11
22
|
return join(repository.commonDirectory, "keiyaku", "locks", PRIVATE_STATE_SEAT);
|
|
12
23
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
export function privateStateSeatCloseLag(error) {
|
|
25
|
+
return {
|
|
26
|
+
kind: "private-state-seat-close-failed",
|
|
27
|
+
diagnostic: error instanceof Error ? error.message : String(error),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function concatenatePrivateStateSeatClose(current, next) {
|
|
31
|
+
const lags = [...(current ?? []), ...(next ?? [])];
|
|
32
|
+
return lags.length === 0 ? undefined : lags;
|
|
33
|
+
}
|
|
34
|
+
export function appendPrivateStateSeatClose(current, closeLag) {
|
|
35
|
+
return current === undefined ? [closeLag] : [...current, closeLag];
|
|
36
|
+
}
|
|
37
|
+
/** Unwrap a seat outcome that has no typed lag surface; confirmed close lag throws. */
|
|
38
|
+
export function requireClosedPrivateStateSeat(outcome) {
|
|
39
|
+
if (outcome.closeLag !== undefined)
|
|
40
|
+
throw new Error(outcome.closeLag.diagnostic);
|
|
41
|
+
return outcome.value;
|
|
42
|
+
}
|
|
43
|
+
/** Unwrap a seat outcome through an owner-typed merge of confirmed close lag. */
|
|
44
|
+
export function mergePrivateStateSeatClose(outcome, merge) {
|
|
45
|
+
return outcome.closeLag === undefined ? outcome.value : merge(outcome.value, outcome.closeLag);
|
|
46
|
+
}
|
|
47
|
+
export function isPrivateStateSeatContention(error) {
|
|
48
|
+
return error instanceof GitPrivateStateSeatContentionError;
|
|
49
|
+
}
|
|
50
|
+
function acquireTimeoutMs(timeoutMs) {
|
|
51
|
+
return timeoutMs ?? PRIVATE_STATE_SEAT_ACQUIRE_TIMEOUT_MS;
|
|
52
|
+
}
|
|
53
|
+
async function acquirePrivateStateSeatLock(repository, path, timeoutMs) {
|
|
54
|
+
try {
|
|
55
|
+
return await acquireSqliteTransactionLock({
|
|
56
|
+
path,
|
|
57
|
+
mode: "immediate",
|
|
58
|
+
timeoutMs: acquireTimeoutMs(timeoutMs),
|
|
59
|
+
...(repository.signal === undefined ? {} : { signal: repository.signal }),
|
|
60
|
+
...(repository.onPrivateStateSeatContention === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { onContended: repository.onPrivateStateSeatContention }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof SqliteTransactionLockError && error.reason === "timeout") {
|
|
67
|
+
throw new GitPrivateStateSeatContentionError(error.message, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function runPrivateStateSeatAction(repository, held, action) {
|
|
23
73
|
const seat = { [privateStateSeat]: true };
|
|
24
74
|
let completed = false;
|
|
75
|
+
let value;
|
|
25
76
|
try {
|
|
26
|
-
|
|
77
|
+
value = await action(seat);
|
|
27
78
|
completed = true;
|
|
28
|
-
return
|
|
79
|
+
return { value };
|
|
29
80
|
}
|
|
30
81
|
finally {
|
|
31
82
|
try {
|
|
32
83
|
held.close();
|
|
84
|
+
repository.onPrivateStateSeatClose?.();
|
|
33
85
|
}
|
|
34
86
|
catch (error) {
|
|
35
87
|
if (!(completed && confirmedPublications.has(seat)))
|
|
36
88
|
throw error;
|
|
89
|
+
return { value: value, closeLag: privateStateSeatCloseLag(error) };
|
|
37
90
|
}
|
|
38
91
|
}
|
|
39
92
|
}
|
|
93
|
+
/** Serialize cooperating writers of the one private Git state root. */
|
|
94
|
+
export async function withPrivateStatePublicationSeat(repository, action, options) {
|
|
95
|
+
const path = privateStatePublicationSeatPath(repository);
|
|
96
|
+
const heldPaths = heldPrivateStateSeats.getStore();
|
|
97
|
+
if (heldPaths?.has(path) === true) {
|
|
98
|
+
throw new Error(`private-state publication seat reentered: ${path}`);
|
|
99
|
+
}
|
|
100
|
+
const held = await acquirePrivateStateSeatLock(repository, path, options?.timeoutMs);
|
|
101
|
+
const nested = new Set(heldPaths);
|
|
102
|
+
nested.add(path);
|
|
103
|
+
return await heldPrivateStateSeats.run(nested, () => runPrivateStateSeatAction(repository, held, action));
|
|
104
|
+
}
|
|
@@ -13,6 +13,8 @@ export type GitRepository = Readonly<{
|
|
|
13
13
|
readonly signal?: AbortSignal;
|
|
14
14
|
/** Test-only observation that this capability is waiting for the private-state seat. */
|
|
15
15
|
readonly onPrivateStateSeatContention?: () => void;
|
|
16
|
+
/** Test-only hook after the private-state seat close attempt. */
|
|
17
|
+
readonly onPrivateStateSeatClose?: () => void;
|
|
16
18
|
}>;
|
|
17
19
|
/** Give one caller's Git capability view its cancellation boundary. */
|
|
18
20
|
export declare function withGitAbortSignal(repository: GitRepository, signal: AbortSignal | undefined): GitRepository;
|
|
@@ -4,7 +4,6 @@ import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.
|
|
|
4
4
|
import type { GitDecodeChannel } from "./read-observation.js";
|
|
5
5
|
import { type TargetCheckoutEffect, type TargetCheckoutLag } from "./target-placement.js";
|
|
6
6
|
import { type WorktreeHookLag, type WorktreeHooks } from "./hooks.js";
|
|
7
|
-
import { type RefMigrationConflict } from "./ref-migration.js";
|
|
8
7
|
import type { UnsealedBytes } from "./terminal-seal.js";
|
|
9
8
|
export type Effect = Readonly<{
|
|
10
9
|
kind: "worktree";
|
|
@@ -54,7 +53,7 @@ export type ReconcileFailure = Readonly<{
|
|
|
54
53
|
stage: "observation" | "effect";
|
|
55
54
|
diagnostic: string;
|
|
56
55
|
}>;
|
|
57
|
-
export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag |
|
|
56
|
+
export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
|
|
58
57
|
export type ReconcileResult = Readonly<{
|
|
59
58
|
effects: readonly Effect[];
|
|
60
59
|
lag: readonly ReconcileLag[];
|
|
@@ -10,7 +10,6 @@ import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-p
|
|
|
10
10
|
import { runCreateHooks } from "./hooks.js";
|
|
11
11
|
import { followDependentManagedWorktree, retireConflictHandoff, worktreePath } from "./workspace.js";
|
|
12
12
|
import { removeCollectableScratchWorktrees } from "./scratch.js";
|
|
13
|
-
import { migrateContractCustodyRefs } from "./ref-migration.js";
|
|
14
13
|
import { reconcileTerminalManagedWorktree, removeRef, updateRef } from "./terminal-reconcile.js";
|
|
15
14
|
const pathExists = (path) => access(path).then(() => true, () => false);
|
|
16
15
|
function deliveryRefFor(contract) {
|
|
@@ -174,11 +173,6 @@ async function reconcileWithTopology(input, state, topology) {
|
|
|
174
173
|
await removeCollectableScratch(repository, topology, effects, lag);
|
|
175
174
|
if (!state)
|
|
176
175
|
return complete(effects, lag);
|
|
177
|
-
const migration = await migrateContractCustodyRefs(repository, state.id);
|
|
178
|
-
effects.push(...migration.effects);
|
|
179
|
-
lag.push(...migration.lag);
|
|
180
|
-
if (migration.lag.length > 0)
|
|
181
|
-
return complete(effects, lag);
|
|
182
176
|
const targetCheckouts = await reconcileTargetCheckouts(repository, state);
|
|
183
177
|
effects.push(...targetCheckouts.effects);
|
|
184
178
|
lag.push(...targetCheckouts.lag);
|
|
@@ -5,8 +5,6 @@ export declare const GIT_REF = "refs/heads/keiyaku-state";
|
|
|
5
5
|
export declare const DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
|
|
6
6
|
export declare const CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
|
|
7
7
|
export declare const HANDOFF_RECEIPT_REF_NAMESPACE = "refs/keiyaku/handoff";
|
|
8
|
-
export declare const LEGACY_DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
|
|
9
|
-
export declare const LEGACY_CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
|
|
10
8
|
export declare const GIT_FORMAT_PATH = "meta/format.json";
|
|
11
9
|
export declare const GIT_FORMAT_BYTES = "{\"version\":4}\n";
|
|
12
10
|
export declare const STATE_COMMIT_SUBJECT = "keiyaku authority - do not delete or rewrite";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
1
|
import { isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
3
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
4
4
|
import { gitObjectId } from "./identity.js";
|
|
5
5
|
import { GitPlumbingError, runGit, runGitWithEnvironment } from "./process.js";
|
|
@@ -8,8 +8,6 @@ export const GIT_REF = "refs/heads/keiyaku-state";
|
|
|
8
8
|
export const DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
|
|
9
9
|
export const CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
|
|
10
10
|
export const HANDOFF_RECEIPT_REF_NAMESPACE = "refs/keiyaku/handoff";
|
|
11
|
-
export const LEGACY_DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
|
|
12
|
-
export const LEGACY_CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
|
|
13
11
|
export const GIT_FORMAT_PATH = "meta/format.json";
|
|
14
12
|
const CURRENT_FORMAT_VERSION = 4;
|
|
15
13
|
export const GIT_FORMAT_BYTES = `{"version":${CURRENT_FORMAT_VERSION}}\n`;
|
|
@@ -21,11 +19,7 @@ export function isKeiyakuOwnedRef(ref) {
|
|
|
21
19
|
ref === CANDIDATE_PIN_REF_NAMESPACE ||
|
|
22
20
|
ref.startsWith(`${CANDIDATE_PIN_REF_NAMESPACE}/`) ||
|
|
23
21
|
ref === HANDOFF_RECEIPT_REF_NAMESPACE ||
|
|
24
|
-
ref.startsWith(`${HANDOFF_RECEIPT_REF_NAMESPACE}/`)
|
|
25
|
-
ref === LEGACY_DELIVERY_REF_NAMESPACE ||
|
|
26
|
-
ref.startsWith(`${LEGACY_DELIVERY_REF_NAMESPACE}/`) ||
|
|
27
|
-
ref === LEGACY_CANDIDATE_PIN_REF_NAMESPACE ||
|
|
28
|
-
ref.startsWith(`${LEGACY_CANDIDATE_PIN_REF_NAMESPACE}/`));
|
|
22
|
+
ref.startsWith(`${HANDOFF_RECEIPT_REF_NAMESPACE}/`));
|
|
29
23
|
}
|
|
30
24
|
export class NoGitWorldError extends Error {
|
|
31
25
|
path;
|
|
@@ -53,7 +47,11 @@ function worktreesFromPorcelain(output) {
|
|
|
53
47
|
const branch = branchField === undefined ? null : branchField.slice("branch ".length);
|
|
54
48
|
if (branch !== null && !branch.startsWith("refs/"))
|
|
55
49
|
throw new Error("Git worktree porcelain output has an invalid branch");
|
|
56
|
-
worktrees.push({
|
|
50
|
+
worktrees.push({
|
|
51
|
+
path: resolve(path),
|
|
52
|
+
branch,
|
|
53
|
+
prunable: record.some((field) => field.startsWith("prunable ")),
|
|
54
|
+
});
|
|
57
55
|
record = [];
|
|
58
56
|
};
|
|
59
57
|
for (const field of fields) {
|
|
@@ -69,7 +67,17 @@ function worktreesFromPorcelain(output) {
|
|
|
69
67
|
return worktrees;
|
|
70
68
|
}
|
|
71
69
|
export async function registeredWorktrees(repository) {
|
|
72
|
-
return worktreesFromPorcelain(await runGit(repository, ["worktree", "list", "--porcelain", "-z"]))
|
|
70
|
+
return await Promise.all(worktreesFromPorcelain(await runGit(repository, ["worktree", "list", "--porcelain", "-z"])).map(async (worktree) => {
|
|
71
|
+
const { prunable, ...registered } = worktree;
|
|
72
|
+
try {
|
|
73
|
+
return { ...registered, path: await realpath(registered.path) };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (prunable || error.code === "ENOENT")
|
|
77
|
+
return registered;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}));
|
|
73
81
|
}
|
|
74
82
|
export async function registeredWorktreePaths(repository) {
|
|
75
83
|
return (await registeredWorktrees(repository)).map((worktree) => worktree.path);
|
|
@@ -106,7 +114,7 @@ async function absoluteGitPath(repository, args, label) {
|
|
|
106
114
|
const value = (await runGit(repository, ["rev-parse", "--path-format=absolute", ...args])).toString("utf8").trim();
|
|
107
115
|
if (value.length === 0)
|
|
108
116
|
throw new Error(`${label} is empty`);
|
|
109
|
-
return resolve(isAbsolute(value) ? value : resolve(repository.effectiveCwd, value));
|
|
117
|
+
return await realpath(resolve(isAbsolute(value) ? value : resolve(repository.effectiveCwd, value)));
|
|
110
118
|
}
|
|
111
119
|
export function commonGitDirectory(repository) {
|
|
112
120
|
return repository.commonDirectory;
|
|
@@ -329,10 +337,7 @@ async function writePreparedTrees(repository, prepared) {
|
|
|
329
337
|
}
|
|
330
338
|
}
|
|
331
339
|
}
|
|
332
|
-
|
|
333
|
-
export async function updateGitTree(repository, baseTree, changes) {
|
|
334
|
-
const update = treeUpdate(changes);
|
|
335
|
-
const bases = await baseTreeEntries(repository, baseTree, [...changes.keys()], update.nodePaths);
|
|
340
|
+
async function writeTreeUpdate(repository, baseTree, changes, update, bases) {
|
|
336
341
|
const representativeOid = baseTree ?? [...changes.values()].find((change) => change !== null)?.oid;
|
|
337
342
|
if (representativeOid === undefined)
|
|
338
343
|
throw new Error("cannot write an empty Git tree without an object format");
|
|
@@ -340,19 +345,16 @@ export async function updateGitTree(repository, baseTree, changes) {
|
|
|
340
345
|
await writePreparedTrees(repository, prepared.trees);
|
|
341
346
|
return prepared.root;
|
|
342
347
|
}
|
|
348
|
+
/** Copy only the changed paths' tree ancestors, preserving untouched subtrees by object ID. */
|
|
349
|
+
export async function updateGitTree(repository, baseTree, changes) {
|
|
350
|
+
const update = treeUpdate(changes);
|
|
351
|
+
const bases = await baseTreeEntries(repository, baseTree, [...changes.keys()], update.nodePaths);
|
|
352
|
+
return writeTreeUpdate(repository, baseTree, changes, update, bases);
|
|
353
|
+
}
|
|
343
354
|
/** Write a tree update from directory entries frozen with the admitted base tree. */
|
|
344
355
|
export async function updateGitTreeFromFrozenDirectories(repository, baseTree, directories, changes) {
|
|
345
356
|
const update = treeUpdate(changes);
|
|
346
|
-
|
|
347
|
-
if (representativeOid === undefined)
|
|
348
|
-
throw new Error("cannot write an empty Git tree without an object format");
|
|
349
|
-
const prepared = prepareTreeUpdate({
|
|
350
|
-
update,
|
|
351
|
-
bases: directories,
|
|
352
|
-
oidBytes: representativeOid.length / 2,
|
|
353
|
-
});
|
|
354
|
-
await writePreparedTrees(repository, prepared.trees);
|
|
355
|
-
return prepared.root;
|
|
357
|
+
return writeTreeUpdate(repository, baseTree, changes, update, directories);
|
|
356
358
|
}
|
|
357
359
|
async function writeCommitObject(input) {
|
|
358
360
|
const { repository, tree, parents, message, actor, email, at } = input;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ContractId } from "../core/facts/types.js";
|
|
2
|
+
import type { PublicationFailed } from "./admission.js";
|
|
3
|
+
import type { HookFailure } from "./hooks.js";
|
|
4
|
+
import type { IntegrationPreparationRefusal } from "./integration.js";
|
|
5
|
+
import type { PrivateStateSeatCloseLag } from "./private-state-seat.js";
|
|
6
|
+
import type { ReconcileLag } from "./reconcile.js";
|
|
7
|
+
import type { WorktreeLeak } from "./scratch.js";
|
|
8
|
+
import type { AuditTargetAnswer, CheckoutNotFollowableRefusal } from "./target-placement.js";
|
|
9
|
+
import type { DirtyWorkspaceRefusal, WorkspaceDirtyDelta, WorktreeMissingRefusal } from "./tender.js";
|
|
10
|
+
export declare function decodeHookFailure(value: unknown): HookFailure;
|
|
11
|
+
export declare function decodeWorktreeLeak(value: unknown): WorktreeLeak;
|
|
12
|
+
export declare function decodePrivateStateSeatCloseLag(value: unknown): PrivateStateSeatCloseLag;
|
|
13
|
+
export declare function decodePublicationFailed(value: unknown): PublicationFailed;
|
|
14
|
+
export declare function decodeWorktreeWorkspace(value: unknown): Readonly<{
|
|
15
|
+
kind: "worktree";
|
|
16
|
+
path: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function decodeWorkspaceDirtyDelta(value: unknown): WorkspaceDirtyDelta;
|
|
19
|
+
export declare function decodeWorktreeMissingRefusal(value: unknown): WorktreeMissingRefusal;
|
|
20
|
+
export declare function decodeUnmergedPathsRefusal(value: unknown): Readonly<{
|
|
21
|
+
kind: "unmerged-paths";
|
|
22
|
+
contractId: ContractId;
|
|
23
|
+
paths: readonly string[];
|
|
24
|
+
}>;
|
|
25
|
+
export declare function decodeDirtyWorkspaceRefusal(value: unknown): DirtyWorkspaceRefusal;
|
|
26
|
+
export declare function decodeIntegrationPreparationRefusal(value: unknown): IntegrationPreparationRefusal;
|
|
27
|
+
export declare function decodeCheckoutNotFollowableRefusal(value: unknown): CheckoutNotFollowableRefusal;
|
|
28
|
+
export declare function decodeAuditTargetAnswer(value: unknown): AuditTargetAnswer;
|
|
29
|
+
export declare function decodeGitReconcileLag(value: unknown): ReconcileLag;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { contractId, snapshotId } from "../core/facts/types.js";
|
|
2
|
+
import { mintSnapshotId } from "./identity.js";
|
|
3
|
+
function fail(message = "malformed git result") {
|
|
4
|
+
throw new Error(message);
|
|
5
|
+
}
|
|
6
|
+
function record(value, required, optional = []) {
|
|
7
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
8
|
+
fail();
|
|
9
|
+
const object = value;
|
|
10
|
+
const allowed = new Set([...required, ...optional]);
|
|
11
|
+
for (const key of Object.keys(object))
|
|
12
|
+
if (!allowed.has(key))
|
|
13
|
+
fail();
|
|
14
|
+
for (const key of required)
|
|
15
|
+
if (!(key in object))
|
|
16
|
+
fail();
|
|
17
|
+
return object;
|
|
18
|
+
}
|
|
19
|
+
function nonblank(value) {
|
|
20
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
21
|
+
fail();
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function integer(value) {
|
|
25
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
26
|
+
fail();
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function nonnegativeInteger(value) {
|
|
30
|
+
const n = integer(value);
|
|
31
|
+
if (n < 0)
|
|
32
|
+
fail();
|
|
33
|
+
return n;
|
|
34
|
+
}
|
|
35
|
+
function boolean(value) {
|
|
36
|
+
if (typeof value !== "boolean")
|
|
37
|
+
fail();
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function strings(value) {
|
|
41
|
+
if (!Array.isArray(value))
|
|
42
|
+
fail();
|
|
43
|
+
return value.map(nonblank);
|
|
44
|
+
}
|
|
45
|
+
function decodeContractId(value) {
|
|
46
|
+
try {
|
|
47
|
+
return contractId(nonblank(value));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
fail();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function decodeSnapshotId(value) {
|
|
54
|
+
try {
|
|
55
|
+
return snapshotId(nonblank(value));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
fail();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function decodeGitSnapshotId(value) {
|
|
62
|
+
try {
|
|
63
|
+
return mintSnapshotId(nonblank(value));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
fail();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function decodeHookFailure(value) {
|
|
70
|
+
const object = record(value, ["kind"], ["code", "stdout", "stderr", "truncated", "diagnostic"]);
|
|
71
|
+
if (object.kind === "timeout" || object.kind === "unknown-exit") {
|
|
72
|
+
if ("code" in object || "stdout" in object || "stderr" in object || "truncated" in object || "diagnostic" in object)
|
|
73
|
+
fail();
|
|
74
|
+
return { kind: object.kind };
|
|
75
|
+
}
|
|
76
|
+
if (object.kind === "spawn-error") {
|
|
77
|
+
if ("code" in object || "stdout" in object || "stderr" in object || "truncated" in object)
|
|
78
|
+
fail();
|
|
79
|
+
return { kind: "spawn-error", diagnostic: nonblank(object.diagnostic) };
|
|
80
|
+
}
|
|
81
|
+
if (object.kind !== "exit")
|
|
82
|
+
fail();
|
|
83
|
+
if ("diagnostic" in object)
|
|
84
|
+
fail();
|
|
85
|
+
return {
|
|
86
|
+
kind: "exit",
|
|
87
|
+
code: integer(object.code),
|
|
88
|
+
stdout: typeof object.stdout === "string" ? object.stdout : fail(),
|
|
89
|
+
stderr: typeof object.stderr === "string" ? object.stderr : fail(),
|
|
90
|
+
truncated: boolean(object.truncated),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function decodeWorktreeLeak(value) {
|
|
94
|
+
const object = record(value, ["path", "diagnostic"]);
|
|
95
|
+
if (typeof object.path !== "string" || typeof object.diagnostic !== "string")
|
|
96
|
+
fail();
|
|
97
|
+
return { path: object.path, diagnostic: object.diagnostic };
|
|
98
|
+
}
|
|
99
|
+
export function decodePrivateStateSeatCloseLag(value) {
|
|
100
|
+
const object = record(value, ["kind", "diagnostic"]);
|
|
101
|
+
if (object.kind !== "private-state-seat-close-failed")
|
|
102
|
+
fail();
|
|
103
|
+
return { kind: "private-state-seat-close-failed", diagnostic: nonblank(object.diagnostic) };
|
|
104
|
+
}
|
|
105
|
+
export function decodePublicationFailed(value) {
|
|
106
|
+
const object = record(value, ["kind", "diagnostic"]);
|
|
107
|
+
if (object.kind !== "publication-failed")
|
|
108
|
+
fail();
|
|
109
|
+
if (typeof object.diagnostic !== "string")
|
|
110
|
+
fail();
|
|
111
|
+
return { kind: "publication-failed", diagnostic: object.diagnostic };
|
|
112
|
+
}
|
|
113
|
+
export function decodeWorktreeWorkspace(value) {
|
|
114
|
+
const object = record(value, ["kind", "path"]);
|
|
115
|
+
if (object.kind !== "worktree")
|
|
116
|
+
fail();
|
|
117
|
+
return { kind: "worktree", path: nonblank(object.path) };
|
|
118
|
+
}
|
|
119
|
+
export function decodeWorkspaceDirtyDelta(value) {
|
|
120
|
+
const object = record(value, ["staged", "unstaged", "untracked", "shortStat"]);
|
|
121
|
+
const shortStat = record(object.shortStat, ["filesChanged", "insertions", "deletions"]);
|
|
122
|
+
return {
|
|
123
|
+
staged: Array.isArray(object.staged)
|
|
124
|
+
? object.staged.map((path) => (typeof path === "string" ? path : fail()))
|
|
125
|
+
: fail(),
|
|
126
|
+
unstaged: Array.isArray(object.unstaged)
|
|
127
|
+
? object.unstaged.map((path) => (typeof path === "string" ? path : fail()))
|
|
128
|
+
: fail(),
|
|
129
|
+
untracked: Array.isArray(object.untracked)
|
|
130
|
+
? object.untracked.map((path) => (typeof path === "string" ? path : fail()))
|
|
131
|
+
: fail(),
|
|
132
|
+
shortStat: {
|
|
133
|
+
filesChanged: integer(shortStat.filesChanged),
|
|
134
|
+
insertions: integer(shortStat.insertions),
|
|
135
|
+
deletions: integer(shortStat.deletions),
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function decodeWorktreeMissingRefusal(value) {
|
|
140
|
+
const object = record(value, ["kind", "contractId"]);
|
|
141
|
+
if (object.kind !== "worktree-missing")
|
|
142
|
+
fail();
|
|
143
|
+
return { kind: "worktree-missing", contractId: decodeContractId(object.contractId) };
|
|
144
|
+
}
|
|
145
|
+
export function decodeUnmergedPathsRefusal(value) {
|
|
146
|
+
const object = record(value, ["kind", "contractId", "paths"]);
|
|
147
|
+
if (object.kind !== "unmerged-paths")
|
|
148
|
+
fail();
|
|
149
|
+
return { kind: "unmerged-paths", contractId: decodeContractId(object.contractId), paths: strings(object.paths) };
|
|
150
|
+
}
|
|
151
|
+
export function decodeDirtyWorkspaceRefusal(value) {
|
|
152
|
+
const object = record(value, ["kind", "contractId", "staged", "unstaged", "untracked", "submodules", "shortStat"]);
|
|
153
|
+
if (object.kind !== "dirty-workspace")
|
|
154
|
+
fail();
|
|
155
|
+
const shortStat = record(object.shortStat, ["filesChanged", "insertions", "deletions"]);
|
|
156
|
+
return {
|
|
157
|
+
kind: "dirty-workspace",
|
|
158
|
+
contractId: decodeContractId(object.contractId),
|
|
159
|
+
staged: strings(object.staged),
|
|
160
|
+
unstaged: strings(object.unstaged),
|
|
161
|
+
untracked: strings(object.untracked),
|
|
162
|
+
submodules: strings(object.submodules),
|
|
163
|
+
shortStat: {
|
|
164
|
+
filesChanged: nonnegativeInteger(shortStat.filesChanged),
|
|
165
|
+
insertions: nonnegativeInteger(shortStat.insertions),
|
|
166
|
+
deletions: nonnegativeInteger(shortStat.deletions),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function decodeIntegrationPreparationRefusal(value) {
|
|
171
|
+
const object = record(value, ["kind", "contractId"], ["reason", "targetHead", "conflictPaths", "requiredGit"]);
|
|
172
|
+
if (object.kind === "integration-unsupported") {
|
|
173
|
+
if (object.requiredGit !== "2.38")
|
|
174
|
+
fail();
|
|
175
|
+
if ("reason" in object || "targetHead" in object || "conflictPaths" in object)
|
|
176
|
+
fail();
|
|
177
|
+
return { kind: "integration-unsupported", contractId: decodeContractId(object.contractId), requiredGit: "2.38" };
|
|
178
|
+
}
|
|
179
|
+
if (object.kind !== "integration-failed")
|
|
180
|
+
fail();
|
|
181
|
+
if ("requiredGit" in object)
|
|
182
|
+
fail();
|
|
183
|
+
if (object.reason !== "not-based-on-target" &&
|
|
184
|
+
object.reason !== "unrelated-histories" &&
|
|
185
|
+
object.reason !== "conflict")
|
|
186
|
+
fail();
|
|
187
|
+
return {
|
|
188
|
+
kind: "integration-failed",
|
|
189
|
+
contractId: decodeContractId(object.contractId),
|
|
190
|
+
reason: object.reason,
|
|
191
|
+
targetHead: decodeSnapshotId(object.targetHead),
|
|
192
|
+
...(object.conflictPaths === undefined ? {} : { conflictPaths: strings(object.conflictPaths) }),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function decodeCheckoutNotFollowableRefusal(value) {
|
|
196
|
+
const object = record(value, ["kind", "contractId", "target", "path", "reason", "paths"]);
|
|
197
|
+
if (object.kind !== "checkout-not-followable")
|
|
198
|
+
fail();
|
|
199
|
+
if (object.reason !== "staged" && object.reason !== "conflict" && object.reason !== "untracked")
|
|
200
|
+
fail();
|
|
201
|
+
return {
|
|
202
|
+
kind: "checkout-not-followable",
|
|
203
|
+
contractId: decodeContractId(object.contractId),
|
|
204
|
+
target: nonblank(object.target),
|
|
205
|
+
path: nonblank(object.path),
|
|
206
|
+
reason: object.reason,
|
|
207
|
+
paths: strings(object.paths),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function decodeAuditTargetAnswer(value) {
|
|
211
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
212
|
+
fail();
|
|
213
|
+
const kind = value.kind;
|
|
214
|
+
if (kind === "placeable") {
|
|
215
|
+
const object = record(value, ["kind", "ref", "head"]);
|
|
216
|
+
return { kind: "placeable", ref: nonblank(object.ref), head: decodeSnapshotId(object.head) };
|
|
217
|
+
}
|
|
218
|
+
if (kind === "moved") {
|
|
219
|
+
const object = record(value, ["kind", "ref", "expected", "observed"]);
|
|
220
|
+
return {
|
|
221
|
+
kind: "moved",
|
|
222
|
+
ref: nonblank(object.ref),
|
|
223
|
+
expected: decodeSnapshotId(object.expected),
|
|
224
|
+
observed: object.observed === null ? null : decodeSnapshotId(object.observed),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (kind === "refused") {
|
|
228
|
+
const object = record(value, ["kind", "refusal"]);
|
|
229
|
+
return { kind: "refused", refusal: decodeCheckoutNotFollowableRefusal(object.refusal) };
|
|
230
|
+
}
|
|
231
|
+
const object = record(value, ["kind", "diagnostic"]);
|
|
232
|
+
if (object.kind !== "failed")
|
|
233
|
+
fail();
|
|
234
|
+
return { kind: "failed", diagnostic: nonblank(object.diagnostic) };
|
|
235
|
+
}
|
|
236
|
+
export function decodeGitReconcileLag(value) {
|
|
237
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
238
|
+
fail();
|
|
239
|
+
const kind = value.kind;
|
|
240
|
+
if (kind === "worktree-retained") {
|
|
241
|
+
const object = record(value, ["kind", "path"]);
|
|
242
|
+
return { kind: "worktree-retained", path: nonblank(object.path) };
|
|
243
|
+
}
|
|
244
|
+
if (kind === "worktree-follow-retained") {
|
|
245
|
+
const object = record(value, ["kind", "path", "tender", "head", "reason"], ["paths"]);
|
|
246
|
+
if (object.reason !== "head-moved" &&
|
|
247
|
+
object.reason !== "head-attached" &&
|
|
248
|
+
object.reason !== "operation-in-progress" &&
|
|
249
|
+
object.reason !== "unsupported-parent-shape")
|
|
250
|
+
fail();
|
|
251
|
+
const lag = {
|
|
252
|
+
kind: "worktree-follow-retained",
|
|
253
|
+
path: nonblank(object.path),
|
|
254
|
+
tender: decodeGitSnapshotId(object.tender),
|
|
255
|
+
head: decodeGitSnapshotId(object.head),
|
|
256
|
+
reason: object.reason,
|
|
257
|
+
...(object.paths === undefined ? {} : { paths: strings(object.paths) }),
|
|
258
|
+
};
|
|
259
|
+
return lag;
|
|
260
|
+
}
|
|
261
|
+
if (kind === "unsealed-bytes") {
|
|
262
|
+
const object = record(value, ["kind", "path", "paths"], ["head"]);
|
|
263
|
+
const lag = {
|
|
264
|
+
kind: "unsealed-bytes",
|
|
265
|
+
path: nonblank(object.path),
|
|
266
|
+
paths: strings(object.paths),
|
|
267
|
+
...(object.head === undefined ? {} : { head: decodeGitSnapshotId(object.head) }),
|
|
268
|
+
};
|
|
269
|
+
return lag;
|
|
270
|
+
}
|
|
271
|
+
if (kind === "target-checkout-retained") {
|
|
272
|
+
const object = record(value, ["kind", "path", "target", "diagnostic"]);
|
|
273
|
+
const lag = {
|
|
274
|
+
kind: "target-checkout-retained",
|
|
275
|
+
path: nonblank(object.path),
|
|
276
|
+
target: nonblank(object.target),
|
|
277
|
+
diagnostic: nonblank(object.diagnostic),
|
|
278
|
+
};
|
|
279
|
+
return lag;
|
|
280
|
+
}
|
|
281
|
+
if (kind === "reconcile-failed") {
|
|
282
|
+
const object = record(value, ["kind", "stage", "diagnostic"]);
|
|
283
|
+
if (object.stage !== "observation" && object.stage !== "effect")
|
|
284
|
+
fail();
|
|
285
|
+
const lag = {
|
|
286
|
+
kind: "reconcile-failed",
|
|
287
|
+
stage: object.stage,
|
|
288
|
+
diagnostic: nonblank(object.diagnostic),
|
|
289
|
+
};
|
|
290
|
+
return lag;
|
|
291
|
+
}
|
|
292
|
+
const object = record(value, ["kind", "phase", "path", "command", "name", "failure"]);
|
|
293
|
+
if (object.kind !== "worktree-hook-failed")
|
|
294
|
+
fail();
|
|
295
|
+
if (object.phase !== "create" && object.phase !== "destroy")
|
|
296
|
+
fail();
|
|
297
|
+
const lag = {
|
|
298
|
+
kind: "worktree-hook-failed",
|
|
299
|
+
phase: object.phase,
|
|
300
|
+
path: nonblank(object.path),
|
|
301
|
+
command: nonnegativeInteger(object.command),
|
|
302
|
+
name: nonblank(object.name),
|
|
303
|
+
failure: decodeHookFailure(object.failure),
|
|
304
|
+
};
|
|
305
|
+
return lag;
|
|
306
|
+
}
|
|
@@ -2,7 +2,7 @@ import { lstat } from "node:fs/promises";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { gitObjectId, gitObjectIdForSnapshot, gitRefLocator, mintSnapshotId } from "./identity.js";
|
|
5
|
-
import { commonGitDirectory, readRef, registeredWorktrees } from "./repository.js";
|
|
5
|
+
import { commonGitDirectory, decodeGitNameOnly, readRef, registeredWorktrees } from "./repository.js";
|
|
6
6
|
import { consumeGitStdout, GitPlumbingError, runGit } from "./process.js";
|
|
7
7
|
import { captureWorkspaceTree } from "./workspace.js";
|
|
8
8
|
export async function observeTargetHead(repository, target) {
|
|
@@ -12,14 +12,8 @@ export async function observeTargetHead(repository, target) {
|
|
|
12
12
|
function diagnostic(error) {
|
|
13
13
|
return error instanceof Error ? error.message : String(error);
|
|
14
14
|
}
|
|
15
|
-
function nulPaths(bytes) {
|
|
16
|
-
const fields = bytes.toString("utf8").split("\0");
|
|
17
|
-
if (fields.at(-1) !== "")
|
|
18
|
-
throw new Error("Git path output is not NUL terminated");
|
|
19
|
-
return [...new Set(fields.slice(0, -1))].sort();
|
|
20
|
-
}
|
|
21
15
|
async function gitPaths(repository, path, args) {
|
|
22
|
-
return
|
|
16
|
+
return [...new Set(decodeGitNameOnly(await runGit(repository, ["-C", path, ...args])))].sort();
|
|
23
17
|
}
|
|
24
18
|
function literalPath(path) {
|
|
25
19
|
return `:(literal)${path}`;
|
|
@@ -15,6 +15,8 @@ export type WorkspaceTree = Readonly<{
|
|
|
15
15
|
changes: WorkspaceChanges;
|
|
16
16
|
}>;
|
|
17
17
|
export declare function worktreePath(repository: GitRepository, place: string): string;
|
|
18
|
+
/** Check out a captured candidate tree in the appointed detached worktree. */
|
|
19
|
+
export declare function checkoutDetachedSnapshot(repository: GitRepository, workspace: string, snapshot: SnapshotId): Promise<void>;
|
|
18
20
|
export type DependentWorktreeFollow = Readonly<{
|
|
19
21
|
kind: "followed";
|
|
20
22
|
before: SnapshotId;
|
|
@@ -8,6 +8,10 @@ const WORKTREE_DIRECTORY = [".keiyaku", "wt"];
|
|
|
8
8
|
export function worktreePath(repository, place) {
|
|
9
9
|
return resolve(repository.primaryWorktree, ...WORKTREE_DIRECTORY, place);
|
|
10
10
|
}
|
|
11
|
+
/** Check out a captured candidate tree in the appointed detached worktree. */
|
|
12
|
+
export async function checkoutDetachedSnapshot(repository, workspace, snapshot) {
|
|
13
|
+
await runGit(repository, ["-C", workspace, "checkout", "--quiet", "--force", "--detach", gitObjectId(snapshot)]);
|
|
14
|
+
}
|
|
11
15
|
async function workspaceRevision(repository, workspace, revision) {
|
|
12
16
|
try {
|
|
13
17
|
const value = (await runGit(repository, ["-C", workspace, "rev-parse", "--verify", "--quiet", revision]))
|