@astrosheep/keiyaku 4.1.0 → 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/README.md +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
- 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 +37 -81
- package/build/src/akuma/akuma.js +39 -447
- package/build/src/akuma/allowed.d.ts +1 -0
- package/build/src/akuma/allowed.js +6 -0
- package/build/src/akuma/archetype.js +8 -5
- 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 +45 -4
- package/build/src/akuma/body.js +145 -35
- package/build/src/akuma/coordinate.js +17 -7
- package/build/src/akuma/heart/facts.js +1 -1
- package/build/src/akuma/heart/index.d.ts +1 -1
- package/build/src/akuma/heart/index.js +2 -4
- package/build/src/akuma/heart/request-authority.js +4 -4
- package/build/src/akuma/heart/request-rows.js +27 -16
- package/build/src/akuma/heart/rows.js +79 -41
- package/build/src/akuma/heart/soul.js +1 -1
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +76 -11
- package/build/src/akuma/heart/tells.js +35 -18
- package/build/src/akuma/heart/timeline.js +19 -11
- package/build/src/akuma/identity.d.ts +1 -0
- package/build/src/akuma/identity.js +1 -0
- package/build/src/akuma/index.d.ts +3 -3
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/nuke.js +106 -14
- 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 -16
- package/build/src/akuma/projection.js +77 -154
- package/build/src/akuma/provider-recipe.js +11 -5
- package/build/src/akuma/provider.js +105 -62
- package/build/src/akuma/providers/acp/config.js +5 -6
- package/build/src/akuma/providers/acp/core.js +17 -10
- package/build/src/akuma/providers/acp/events.js +38 -29
- package/build/src/akuma/providers/acp/index.js +13 -9
- package/build/src/akuma/providers/claude/events.js +8 -12
- package/build/src/akuma/providers/claude/index.d.ts +1 -0
- package/build/src/akuma/providers/claude/index.js +55 -26
- package/build/src/akuma/providers/claude/input.js +6 -2
- package/build/src/akuma/providers/codex-app-server/events.js +61 -36
- package/build/src/akuma/providers/codex-app-server/index.js +25 -13
- package/build/src/akuma/providers/grok-build/index.js +22 -25
- package/build/src/akuma/providers/index.js +2 -18
- package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
- package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
- package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
- package/build/src/akuma/providers/pi/events.js +26 -19
- package/build/src/akuma/providers/pi/index.js +58 -40
- package/build/src/akuma/publication.js +9 -9
- 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 +19 -122
- package/build/src/akuma/request-wire.d.ts +2 -2
- package/build/src/akuma/request-wire.js +75 -102
- package/build/src/akuma/requests.js +2 -2
- package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
- package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
- package/build/src/akuma-body.js +6 -5
- package/build/src/body/amend.d.ts +4 -1
- package/build/src/body/amend.js +14 -6
- package/build/src/body/decode.js +2 -4
- package/build/src/body/envelope.js +2 -4
- package/build/src/body/region.d.ts +1 -2
- package/build/src/body/region.js +2 -70
- package/build/src/body/render.js +17 -12
- package/build/src/body/shape.js +1 -7
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +8 -1
- package/build/src/cli/commands/akuma-invoke.js +18 -6
- package/build/src/cli/commands/akuma.d.ts +0 -6
- package/build/src/cli/commands/akuma.js +11 -24
- package/build/src/cli/commands/amend.d.ts +2 -2
- package/build/src/cli/commands/amend.js +2 -4
- package/build/src/cli/commands/bind.d.ts +4 -4
- package/build/src/cli/commands/bind.js +16 -6
- 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 +109 -10
- package/build/src/cli/commands/contract.js +339 -16
- package/build/src/cli/commands/install.d.ts +1 -1
- package/build/src/cli/commands/install.js +23 -15
- package/build/src/cli/commands/task-invoke.js +187 -67
- package/build/src/cli/commands/task-query.js +48 -24
- package/build/src/cli/commands/task.js +150 -38
- package/build/src/cli/coordinates.js +2 -4
- package/build/src/cli/invoke.js +92 -208
- package/build/src/cli/main.js +4 -2
- package/build/src/cli/parse.d.ts +3 -90
- package/build/src/cli/parse.js +27 -233
- 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-tool-command.js +6 -7
- package/build/src/cli/render/akuma-tool.js +10 -8
- package/build/src/cli/render/akuma.d.ts +1 -1
- package/build/src/cli/render/akuma.js +61 -353
- package/build/src/cli/render/audit.js +5 -15
- package/build/src/cli/render/board.d.ts +1 -0
- package/build/src/cli/render/board.js +17 -0
- package/build/src/cli/render/catalog.js +81 -8
- 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-observation.d.ts +10 -0
- package/build/src/cli/render/contract-observation.js +58 -0
- package/build/src/cli/render/contract.d.ts +1 -2
- package/build/src/cli/render/contract.js +41 -142
- package/build/src/cli/render/kanshi.js +239 -215
- package/build/src/cli/render/receipt.js +2 -9
- package/build/src/cli/render/refusal.js +10 -15
- package/build/src/cli/render/region.js +22 -6
- package/build/src/cli/render/settings.js +4 -4
- package/build/src/cli/render/task.js +34 -14
- package/build/src/cli/render/terminal.js +16 -5
- package/build/src/cli/result.d.ts +3 -3
- package/build/src/cli/runtime.d.ts +6 -1
- package/build/src/cli/runtime.js +84 -63
- package/build/src/cli/selectors.js +2 -4
- package/build/src/cli/usage.js +6 -5
- package/build/src/contract-worktree.d.ts +1 -23
- package/build/src/contract-worktree.js +79 -155
- package/build/src/coordination/durable-file.js +7 -3
- package/build/src/coordination/sqlite-transaction-lock.js +29 -12
- package/build/src/core/facts/codec.js +7 -3
- package/build/src/core/facts/types.d.ts +1 -1
- package/build/src/core/subject.js +16 -9
- package/build/src/core/verbs/abandon.d.ts +1 -1
- package/build/src/core/verbs/abandon.js +1 -1
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +7 -7
- package/build/src/core/verbs/attestation.js +4 -1
- package/build/src/core/verbs/bind.d.ts +1 -1
- package/build/src/core/verbs/bind.js +1 -1
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +10 -8
- package/build/src/dispatch/index.js +8 -6
- package/build/src/duration.js +5 -1
- package/build/src/git/admission.js +5 -3
- package/build/src/git/hooks.d.ts +8 -0
- package/build/src/git/hooks.js +63 -14
- package/build/src/git/identity.js +1 -4
- package/build/src/git/integration.d.ts +2 -3
- package/build/src/git/integration.js +32 -39
- package/build/src/git/nuke.d.ts +1 -1
- package/build/src/git/nuke.js +110 -57
- package/build/src/git/observe.d.ts +13 -1
- package/build/src/git/observe.js +51 -44
- package/build/src/git/process.js +4 -2
- package/build/src/git/read-observation.js +10 -8
- package/build/src/git/reconcile.d.ts +22 -2
- package/build/src/git/reconcile.js +38 -274
- package/build/src/git/repository.d.ts +2 -0
- package/build/src/git/repository.js +13 -22
- package/build/src/git/scratch.d.ts +8 -5
- package/build/src/git/scratch.js +29 -2
- package/build/src/git/target-placement.d.ts +9 -10
- package/build/src/git/target-placement.js +59 -62
- package/build/src/git/tender.js +13 -46
- package/build/src/git/terminal-reconcile.d.ts +21 -0
- package/build/src/git/terminal-reconcile.js +278 -0
- package/build/src/git/terminal-seal.js +15 -12
- package/build/src/git/tree.js +5 -2
- package/build/src/git/workspace.d.ts +24 -2
- package/build/src/git/workspace.js +120 -16
- package/build/src/identity/coordinates.js +3 -1
- package/build/src/identity/selector.js +6 -3
- package/build/src/kanshi/fleet.d.ts +6 -0
- package/build/src/kanshi/fleet.js +27 -0
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +60 -48
- package/build/src/kanshi/report.d.ts +11 -23
- package/build/src/kanshi/select.js +25 -24
- package/build/src/library/address.d.ts +2 -1
- package/build/src/library/address.js +7 -8
- package/build/src/library/akuma-creation.js +20 -12
- package/build/src/library/audit.d.ts +0 -1
- package/build/src/library/audit.js +1 -2
- package/build/src/library/bind.d.ts +53 -5
- package/build/src/library/bind.js +98 -22
- package/build/src/library/catalog.d.ts +1 -0
- package/build/src/library/catalog.js +6 -3
- package/build/src/library/configuration.js +3 -1
- package/build/src/library/continuation.d.ts +15 -4
- package/build/src/library/continuation.js +24 -16
- 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 +19 -154
- package/build/src/library/contract.js +46 -261
- package/build/src/library/fleet.d.ts +1 -1
- package/build/src/library/fleet.js +19 -11
- package/build/src/library/input.d.ts +2 -0
- package/build/src/library/input.js +14 -0
- package/build/src/library/keiyaku.d.ts +8 -7
- package/build/src/library/keiyaku.js +3 -3
- package/build/src/library/nuke.js +14 -5
- package/build/src/library/reconcile.d.ts +9 -4
- package/build/src/library/reconcile.js +30 -24
- package/build/src/library/refusal.d.ts +13 -5
- package/build/src/library/refusal.js +7 -0
- package/build/src/library/region.d.ts +8 -7
- package/build/src/library/region.js +31 -11
- package/build/src/library/repo.js +2 -2
- package/build/src/markdown/lex.js +9 -2
- package/build/src/markdown/parse.js +11 -7
- package/build/src/markdown/query.js +4 -2
- package/build/src/protocol/amend.js +3 -1
- package/build/src/protocol/attempt.d.ts +7 -3
- package/build/src/protocol/attempt.js +9 -5
- package/build/src/protocol/audit.d.ts +1 -1
- package/build/src/protocol/audit.js +4 -14
- package/build/src/protocol/bind.d.ts +19 -6
- package/build/src/protocol/bind.js +28 -13
- package/build/src/protocol/completion.d.ts +0 -1
- package/build/src/protocol/completion.js +1 -3
- package/build/src/protocol/deliver.d.ts +1 -3
- package/build/src/protocol/deliver.js +15 -39
- package/build/src/protocol/intent.d.ts +1 -0
- package/build/src/protocol/intent.js +9 -7
- package/build/src/protocol/operations.d.ts +3 -7
- package/build/src/protocol/operations.js +5 -3
- package/build/src/protocol/placement.d.ts +0 -1
- package/build/src/protocol/placement.js +6 -6
- package/build/src/protocol/read/observation.d.ts +11 -0
- package/build/src/protocol/read/observation.js +37 -0
- package/build/src/protocol/read/status.d.ts +23 -12
- package/build/src/protocol/read/status.js +90 -72
- package/build/src/protocol/reconcile.d.ts +1 -0
- package/build/src/protocol/reconcile.js +2 -3
- package/build/src/protocol/reintegrate.js +1 -1
- package/build/src/protocol/review.d.ts +0 -1
- package/build/src/protocol/review.js +15 -22
- package/build/src/protocol/run.d.ts +1 -0
- package/build/src/protocol/run.js +4 -5
- package/build/src/runtime/proc/launch.d.ts +33 -0
- package/build/src/runtime/proc/launch.js +98 -0
- package/build/src/runtime/proc/line-rpc.js +9 -3
- package/build/src/runtime/proc/run.d.ts +12 -6
- package/build/src/runtime/proc/run.js +78 -21
- package/build/src/runtime/proc/stdio.js +7 -10
- package/build/src/settings.js +6 -2
- package/build/src/settlement/holder.js +13 -10
- package/build/src/settlement/settle.js +39 -6
- package/build/src/task/board.js +12 -6
- package/build/src/task/compose.js +85 -22
- package/build/src/task/context.js +21 -8
- package/build/src/task/document.js +46 -10
- package/build/src/task/identity.js +5 -2
- package/build/src/task/index.d.ts +0 -1
- package/build/src/task/index.js +119 -34
- package/build/src/task/input.js +29 -7
- package/build/src/task/mutation.js +61 -19
- package/build/src/task/operations.d.ts +6 -1
- package/build/src/task/operations.js +83 -29
- package/build/src/task/query.js +55 -26
- package/build/src/task/store.d.ts +3 -1
- package/build/src/task/store.js +81 -17
- package/build/src/verification/execution.js +14 -11
- package/build/src/workspace-place.js +19 -7
- package/build/src/world.js +16 -6
- package/package.json +8 -3
package/build/src/git/observe.js
CHANGED
|
@@ -2,7 +2,7 @@ import { decodeJournal } from "../core/facts/codec.js";
|
|
|
2
2
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
3
|
import { foldJournal } from "../core/facts/fold.js";
|
|
4
4
|
import { contractJournalPath, contractJournalPaths, mintContractHead, mintSnapshotId, } from "./identity.js";
|
|
5
|
-
import { isKeiyakuOwnedRef
|
|
5
|
+
import { isKeiyakuOwnedRef } from "./repository.js";
|
|
6
6
|
import { GitPlumbingError, runGit } from "./process.js";
|
|
7
7
|
import { readGitTreeSelection, withGitTargetedReadObservation, } from "./read-observation.js";
|
|
8
8
|
export async function observeDeliveryTargetAt(observation, state) {
|
|
@@ -14,9 +14,7 @@ export async function observeDeliveryTargetAt(observation, state) {
|
|
|
14
14
|
const integration = state.currentIntegration;
|
|
15
15
|
if (integration === null)
|
|
16
16
|
return { head, drift: false };
|
|
17
|
-
const expected = state.terminal?.kind === "claimed"
|
|
18
|
-
? integration.snapshot
|
|
19
|
-
: integration.predecessor;
|
|
17
|
+
const expected = state.terminal?.kind === "claimed" ? integration.snapshot : integration.predecessor;
|
|
20
18
|
return { head, drift: head !== expected };
|
|
21
19
|
}
|
|
22
20
|
const TARGET_COORDINATES_FORMAT = "%(refname)%00%(objectname)%00";
|
|
@@ -43,12 +41,9 @@ export async function normalizeTargetBranch(repository, input) {
|
|
|
43
41
|
/** Read one worktree's attached branch, or null for detached HEAD. */
|
|
44
42
|
export async function currentBranch(repository, path) {
|
|
45
43
|
try {
|
|
46
|
-
const ref = (await runGit(repository, [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"--quiet",
|
|
50
|
-
"HEAD",
|
|
51
|
-
])).toString("utf8").trim();
|
|
44
|
+
const ref = (await runGit(repository, [...(path === undefined ? [] : ["-C", path]), "symbolic-ref", "--quiet", "HEAD"]))
|
|
45
|
+
.toString("utf8")
|
|
46
|
+
.trim();
|
|
52
47
|
return ref.startsWith("refs/heads/") ? ref : null;
|
|
53
48
|
}
|
|
54
49
|
catch (error) {
|
|
@@ -66,39 +61,54 @@ function structuredFields(output, fieldCount) {
|
|
|
66
61
|
malformedBindCoordinatesOutput();
|
|
67
62
|
return fields.slice(0, -1);
|
|
68
63
|
}
|
|
64
|
+
async function observeHeadCoordinates(repository) {
|
|
65
|
+
try {
|
|
66
|
+
return {
|
|
67
|
+
start: mintSnapshotId((await runGit(repository, ["rev-parse", "--verify", "HEAD"])).toString("utf8").trim()),
|
|
68
|
+
branch: await currentBranch(repository),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof GitPlumbingError &&
|
|
73
|
+
error.status === 128 &&
|
|
74
|
+
error.stderr.toString("utf8").includes("Needed a single revision")) {
|
|
75
|
+
return { kind: "unborn-head", branch: await currentBranch(repository) };
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
69
80
|
/** Observe bind's tender start and optional reward target without creating persistent state. */
|
|
70
|
-
export async function observeBindCoordinates(repository,
|
|
71
|
-
if (
|
|
81
|
+
export async function observeBindCoordinates(repository, selection = { kind: "targetless" }) {
|
|
82
|
+
if (selection.kind === "explicit") {
|
|
83
|
+
const requestedTarget = selection.target;
|
|
84
|
+
if (isKeiyakuOwnedRef(requestedTarget)) {
|
|
85
|
+
throw new Error(`bind target names a Keiyaku-owned ref: ${requestedTarget}`);
|
|
86
|
+
}
|
|
87
|
+
const output = await runGit(repository, [
|
|
88
|
+
"for-each-ref",
|
|
89
|
+
`--format=${TARGET_COORDINATES_FORMAT}`,
|
|
90
|
+
"--",
|
|
91
|
+
requestedTarget,
|
|
92
|
+
]);
|
|
93
|
+
if (output.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
const [target, start] = structuredFields(output, 2);
|
|
96
|
+
if (target !== requestedTarget || start === undefined)
|
|
97
|
+
malformedBindCoordinatesOutput();
|
|
72
98
|
try {
|
|
73
|
-
return {
|
|
74
|
-
start: mintSnapshotId((await runGit(repository, ["rev-parse", "--verify", "HEAD"])).toString("utf8").trim()),
|
|
75
|
-
branch: await currentBranch(repository),
|
|
76
|
-
};
|
|
99
|
+
return { target, start: mintSnapshotId(start), branch: await currentBranch(repository) };
|
|
77
100
|
}
|
|
78
101
|
catch {
|
|
79
102
|
malformedBindCoordinatesOutput();
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`--format=${TARGET_COORDINATES_FORMAT}`,
|
|
88
|
-
"--",
|
|
89
|
-
requestedTarget,
|
|
90
|
-
]);
|
|
91
|
-
if (output.length === 0)
|
|
92
|
-
return null;
|
|
93
|
-
const [target, start] = structuredFields(output, 2);
|
|
94
|
-
if (target !== requestedTarget || start === undefined)
|
|
95
|
-
malformedBindCoordinatesOutput();
|
|
96
|
-
try {
|
|
97
|
-
return { target, start: mintSnapshotId(start), branch: await currentBranch(repository) };
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
malformedBindCoordinatesOutput();
|
|
105
|
+
const observed = await observeHeadCoordinates(repository);
|
|
106
|
+
if ("kind" in observed)
|
|
107
|
+
return observed;
|
|
108
|
+
if (selection.kind === "current-branch" && observed.branch !== null) {
|
|
109
|
+
return { start: observed.start, target: observed.branch, branch: observed.branch };
|
|
101
110
|
}
|
|
111
|
+
return observed;
|
|
102
112
|
}
|
|
103
113
|
function decodeGitJournal(path, git, bytes, expectedId) {
|
|
104
114
|
const journal = git.paths.get(path);
|
|
@@ -108,9 +118,9 @@ function decodeGitJournal(path, git, bytes, expectedId) {
|
|
|
108
118
|
throw new AuthorityCorruptionError(`journal path is not a blob: ${path}`);
|
|
109
119
|
const entries = decodeJournal(bytes.toString("utf8"));
|
|
110
120
|
const first = entries[0];
|
|
111
|
-
if (first === undefined
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
if (first === undefined ||
|
|
122
|
+
(expectedId !== undefined && first.contract !== expectedId) ||
|
|
123
|
+
entries.some((entry) => entry.contract !== (expectedId ?? first.contract))) {
|
|
114
124
|
if (expectedId !== undefined) {
|
|
115
125
|
throw new AuthorityCorruptionError(`journal content does not canonically identify ${expectedId}: ${path}`);
|
|
116
126
|
}
|
|
@@ -136,8 +146,7 @@ function observeDecodedJournal(id, decoded, path) {
|
|
|
136
146
|
};
|
|
137
147
|
}
|
|
138
148
|
function observeFromGit(git, id, blobs) {
|
|
139
|
-
const matches = contractJournalPaths(id)
|
|
140
|
-
.flatMap((path) => git.paths.has(path) ? [path] : []);
|
|
149
|
+
const matches = contractJournalPaths(id).flatMap((path) => (git.paths.has(path) ? [path] : []));
|
|
141
150
|
if (matches.length > 1)
|
|
142
151
|
throw new AuthorityCorruptionError(`duplicate contract journal identity: ${id}`);
|
|
143
152
|
const path = matches[0];
|
|
@@ -175,8 +184,7 @@ function enumerateContractObservations(git, blobs, journalClass) {
|
|
|
175
184
|
}
|
|
176
185
|
export async function observeActiveContractWorld(observation) {
|
|
177
186
|
const git = observation.snapshot;
|
|
178
|
-
const journals = [...git.paths]
|
|
179
|
-
.filter(([path, entry]) => path.startsWith("contracts/active/") && path.endsWith(".jsonl") && entry.type === "blob");
|
|
187
|
+
const journals = [...git.paths].filter(([path, entry]) => path.startsWith("contracts/active/") && path.endsWith(".jsonl") && entry.type === "blob");
|
|
180
188
|
const results = await observation.readBlobs(journals.map(([, entry]) => entry.oid));
|
|
181
189
|
const blobs = new Map();
|
|
182
190
|
for (const [path, entry] of journals) {
|
|
@@ -198,8 +206,7 @@ export async function observeActiveContractWorld(observation) {
|
|
|
198
206
|
}
|
|
199
207
|
export async function observeContractWorld(observation, requested = []) {
|
|
200
208
|
const git = observation.snapshot;
|
|
201
|
-
const journals = [...git.paths]
|
|
202
|
-
.filter(([path, entry]) => /^contracts\/(?:active|terminal)\//u.test(path) && path.endsWith(".jsonl") && entry.type === "blob");
|
|
209
|
+
const journals = [...git.paths].filter(([path, entry]) => /^contracts\/(?:active|terminal)\//u.test(path) && path.endsWith(".jsonl") && entry.type === "blob");
|
|
203
210
|
const results = await observation.readBlobs(journals.map(([, entry]) => entry.oid));
|
|
204
211
|
const blobs = new Map();
|
|
205
212
|
for (const [path, entry] of journals) {
|
package/build/src/git/process.js
CHANGED
|
@@ -19,7 +19,7 @@ function commandError(command, error) {
|
|
|
19
19
|
const candidate = error;
|
|
20
20
|
const stderr = candidate.stderr === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stderr);
|
|
21
21
|
const stdout = candidate.stdout === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stdout);
|
|
22
|
-
const detail = stderr.length === 0 ? candidate.message ?? "git command failed" : stderr.toString("utf8");
|
|
22
|
+
const detail = stderr.length === 0 ? (candidate.message ?? "git command failed") : stderr.toString("utf8");
|
|
23
23
|
return new GitPlumbingError({
|
|
24
24
|
stdout,
|
|
25
25
|
stderr,
|
|
@@ -47,7 +47,9 @@ async function executeGit(repository, args, input, environment) {
|
|
|
47
47
|
stderr.push(retained);
|
|
48
48
|
stderrBytes += retained.length;
|
|
49
49
|
});
|
|
50
|
-
child.stdin.on("error", (error) => {
|
|
50
|
+
child.stdin.on("error", (error) => {
|
|
51
|
+
inputError = error;
|
|
52
|
+
});
|
|
51
53
|
child.stdin.end(input);
|
|
52
54
|
const terminal = await new Promise((resolveTerminal) => {
|
|
53
55
|
child.once("error", (error) => resolveTerminal({ code: null, error }));
|
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
3
|
import { gitObjectId } from "./identity.js";
|
|
4
4
|
import { parseTreeObject, validPath } from "./tree.js";
|
|
5
|
-
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef
|
|
5
|
+
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef } from "./repository.js";
|
|
6
6
|
import { GitPlumbingError } from "./process.js";
|
|
7
7
|
const gitReadObservationBrand = Symbol("GitReadObservation");
|
|
8
8
|
const gitDecodeChannelBrand = Symbol("GitDecodeChannel");
|
|
@@ -52,8 +52,12 @@ function batchObjectReader(repository) {
|
|
|
52
52
|
let spawnError = null;
|
|
53
53
|
let failure = null;
|
|
54
54
|
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
55
|
-
child.on("error", (error) => {
|
|
56
|
-
|
|
55
|
+
child.on("error", (error) => {
|
|
56
|
+
spawnError = error;
|
|
57
|
+
});
|
|
58
|
+
child.stdin.on("error", (error) => {
|
|
59
|
+
spawnError ??= error;
|
|
60
|
+
});
|
|
57
61
|
const plumbingError = (message, status) => new GitPlumbingError({
|
|
58
62
|
stderr: Buffer.concat(stderr),
|
|
59
63
|
status,
|
|
@@ -64,7 +68,7 @@ function batchObjectReader(repository) {
|
|
|
64
68
|
if (spawnError !== null)
|
|
65
69
|
throw spawnError;
|
|
66
70
|
await new Promise((resolve, reject) => {
|
|
67
|
-
child.stdin.write(value, (error) => error === null || error === undefined ? resolve() : reject(error));
|
|
71
|
+
child.stdin.write(value, (error) => (error === null || error === undefined ? resolve() : reject(error)));
|
|
68
72
|
});
|
|
69
73
|
};
|
|
70
74
|
const read = async (oid) => {
|
|
@@ -252,7 +256,7 @@ async function readFormat(observation) {
|
|
|
252
256
|
export async function withGitDecodeChannel(repository, consume) {
|
|
253
257
|
const transport = { reader: null };
|
|
254
258
|
let active = true;
|
|
255
|
-
const objectReader = () => transport.reader ??= batchObjectReader(repository);
|
|
259
|
+
const objectReader = () => (transport.reader ??= batchObjectReader(repository));
|
|
256
260
|
const channel = {
|
|
257
261
|
[gitDecodeChannelBrand]: true,
|
|
258
262
|
readObjects: (oids) => {
|
|
@@ -329,9 +333,7 @@ async function observeEpoch(repository, channel, selection, consume) {
|
|
|
329
333
|
if (commitObject === undefined)
|
|
330
334
|
throw new Error(`missing state commit result: ${commit}`);
|
|
331
335
|
const tree = await commitTree(commitObject, commit);
|
|
332
|
-
const observed = selection === null
|
|
333
|
-
? await completeTree(channel, tree)
|
|
334
|
-
: await readGitTreeSelection(channel, tree, selection);
|
|
336
|
+
const observed = selection === null ? await completeTree(channel, tree) : await readGitTreeSelection(channel, tree, selection);
|
|
335
337
|
snapshot = { commit, tree, paths: observed.paths };
|
|
336
338
|
treeDirectories = observed.directories;
|
|
337
339
|
}
|
|
@@ -4,11 +4,17 @@ 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 {
|
|
7
|
+
import type { UnsealedBytes } from "./terminal-seal.js";
|
|
8
8
|
export type Effect = Readonly<{
|
|
9
9
|
kind: "worktree";
|
|
10
10
|
path: string;
|
|
11
11
|
action: "created" | "removed" | "unchanged";
|
|
12
|
+
}> | Readonly<{
|
|
13
|
+
kind: "worktree";
|
|
14
|
+
path: string;
|
|
15
|
+
action: "followed";
|
|
16
|
+
before: SnapshotId;
|
|
17
|
+
after: SnapshotId;
|
|
12
18
|
}> | Readonly<{
|
|
13
19
|
kind: "recovery-snapshot";
|
|
14
20
|
action: "created";
|
|
@@ -34,12 +40,19 @@ type WorktreeRetained = Readonly<{
|
|
|
34
40
|
kind: "worktree-retained";
|
|
35
41
|
path: string;
|
|
36
42
|
}>;
|
|
43
|
+
type WorktreeFollowRetained = Readonly<{
|
|
44
|
+
kind: "worktree-follow-retained";
|
|
45
|
+
path: string;
|
|
46
|
+
tender: SnapshotId;
|
|
47
|
+
head: SnapshotId;
|
|
48
|
+
reason: "head-moved" | "head-attached" | "operation-in-progress" | "unsupported-parent-shape";
|
|
49
|
+
}>;
|
|
37
50
|
export type ReconcileFailure = Readonly<{
|
|
38
51
|
kind: "reconcile-failed";
|
|
39
52
|
stage: "observation" | "effect";
|
|
40
53
|
diagnostic: string;
|
|
41
54
|
}>;
|
|
42
|
-
export type ReconcileLag = WorktreeRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
|
|
55
|
+
export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
|
|
43
56
|
export type ReconcileResult = Readonly<{
|
|
44
57
|
effects: readonly Effect[];
|
|
45
58
|
lag: readonly ReconcileLag[];
|
|
@@ -53,6 +66,13 @@ export type GitReconcileObservation = Readonly<{
|
|
|
53
66
|
state: ContractState | null;
|
|
54
67
|
result: ReconcileResult;
|
|
55
68
|
}>;
|
|
69
|
+
export type WorktreeTopology = Readonly<{
|
|
70
|
+
paths: Set<string>;
|
|
71
|
+
}>;
|
|
72
|
+
export type ReconcileAccumulation = Readonly<{
|
|
73
|
+
effects: Effect[];
|
|
74
|
+
lag: ReconcileLag[];
|
|
75
|
+
}>;
|
|
56
76
|
export declare function reconcile(input: ReconcileInput): Promise<GitReconcileObservation>;
|
|
57
77
|
export declare function reconcileObservationFailure(error: unknown): ReconcileResult;
|
|
58
78
|
export declare function reconcileEffectFailure(error: unknown, prior?: ReconcileResult): ReconcileResult;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { access, mkdir } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
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
|
|
11
|
-
import { worktreePath } from "./workspace.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { runCreateHooks } from "./hooks.js";
|
|
11
|
+
import { followManagedWorktree, worktreePath } from "./workspace.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,49 +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
28
|
async function acquireWorktreeTopology(repository) {
|
|
66
29
|
return { paths: new Set(await registeredWorktreePaths(repository)) };
|
|
67
30
|
}
|
|
68
|
-
function fromPrimaryWorktree(repository) {
|
|
69
|
-
return { ...repository, effectiveCwd: repository.primaryWorktree };
|
|
70
|
-
}
|
|
71
31
|
function diagnostic(error) {
|
|
72
32
|
return error instanceof Error ? error.message : String(error);
|
|
73
33
|
}
|
|
@@ -85,7 +45,7 @@ function reconcileLockPath(repository, contract) {
|
|
|
85
45
|
}
|
|
86
46
|
async function worktree(repository, topology, path, desired) {
|
|
87
47
|
const registered = topology.paths.has(path);
|
|
88
|
-
if (registered && await pathExists(path))
|
|
48
|
+
if (registered && (await pathExists(path)))
|
|
89
49
|
return { kind: "worktree", path, action: "unchanged" };
|
|
90
50
|
if (registered) {
|
|
91
51
|
await runGit(repository, ["worktree", "remove", path]);
|
|
@@ -98,70 +58,12 @@ async function worktree(repository, topology, path, desired) {
|
|
|
98
58
|
topology.paths.add(path);
|
|
99
59
|
return { kind: "worktree", path, action: "created" };
|
|
100
60
|
}
|
|
101
|
-
async function removeWorktree(repository, topology, path, force = false) {
|
|
102
|
-
const registered = topology.paths.has(path);
|
|
103
|
-
if (!registered) {
|
|
104
|
-
return {
|
|
105
|
-
effect: { kind: "worktree", path, action: "unchanged" },
|
|
106
|
-
retained: await pathExists(path),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
if (!await pathExists(path)) {
|
|
110
|
-
await runGit(repository, ["worktree", "remove", path]);
|
|
111
|
-
topology.paths.delete(path);
|
|
112
|
-
return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
await runGit(repository, ["worktree", "remove", ...(force ? ["--force"] : []), path]);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return { effect: { kind: "worktree", path, action: "unchanged" }, retained: true };
|
|
119
|
-
}
|
|
120
|
-
topology.paths.delete(path);
|
|
121
|
-
return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
|
|
122
|
-
}
|
|
123
61
|
async function removeCollectableScratch(repository, topology, effects, lag) {
|
|
124
|
-
for (const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (removal.retained)
|
|
129
|
-
lag.push({ kind: "worktree-retained", path: scratch.path });
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
scratch.release();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async function refRows(repository) {
|
|
137
|
-
const output = (await runGit(repository, [
|
|
138
|
-
"for-each-ref",
|
|
139
|
-
"--format=%(refname)%00%(objectname)",
|
|
140
|
-
])).toString("utf8");
|
|
141
|
-
if (output.length === 0)
|
|
142
|
-
return [];
|
|
143
|
-
return output.trimEnd().split("\n").map((row) => {
|
|
144
|
-
const [ref, oid, extra] = row.split("\0");
|
|
145
|
-
if (ref === undefined || oid === undefined || extra !== undefined)
|
|
146
|
-
throw new Error("Git ref row is malformed");
|
|
147
|
-
return { ref, oid };
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
async function snapshotCustodian(repository, snapshot) {
|
|
151
|
-
for (const row of await refRows(repository)) {
|
|
152
|
-
if (isKeiyakuOwnedRef(row.ref))
|
|
153
|
-
continue;
|
|
154
|
-
try {
|
|
155
|
-
await runGit(repository, ["merge-base", "--is-ancestor", gitObjectIdForSnapshot(snapshot), row.oid]);
|
|
156
|
-
return { ref: row.ref, oid: mintSnapshotId(row.oid) };
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
if (error instanceof GitPlumbingError && error.status === 1)
|
|
160
|
-
continue;
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
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 });
|
|
163
66
|
}
|
|
164
|
-
return null;
|
|
165
67
|
}
|
|
166
68
|
async function reconcileTargetCheckouts(repository, state) {
|
|
167
69
|
if (state.terminal?.kind !== "claimed" || state.coordinates.target === undefined || state.delivery === null) {
|
|
@@ -201,171 +103,34 @@ async function reconcileTargetCheckouts(repository, state) {
|
|
|
201
103
|
result = failed("effect", releaseFailure, result.effects, result.lag);
|
|
202
104
|
return result;
|
|
203
105
|
}
|
|
204
|
-
async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag }) {
|
|
205
|
-
const pin = candidatePinRefFor(state.id);
|
|
206
|
-
if (state.terminal) {
|
|
207
|
-
if (state.delivery === null)
|
|
208
|
-
effects.push(await removeRef(repository, pin));
|
|
209
|
-
else {
|
|
210
|
-
const integration = state.currentIntegration?.snapshot;
|
|
211
|
-
if (integration === undefined)
|
|
212
|
-
return complete();
|
|
213
|
-
const custodian = await snapshotCustodian(repository, integration);
|
|
214
|
-
effects.push(await (custodian === null
|
|
215
|
-
? await updateRef(repository, pin, integration)
|
|
216
|
-
: await removeRefWithCustody(repository, pin, custodian.ref, custodian.oid)));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else if (state.bound) {
|
|
220
|
-
effects.push(await (state.delivery === null
|
|
221
|
-
? await removeRef(repository, pin)
|
|
222
|
-
: await updateRef(repository, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)));
|
|
223
|
-
}
|
|
224
|
-
return complete(effects, lag);
|
|
225
|
-
}
|
|
226
|
-
function retainTerminalWorktree(path, retainedLag, { effects, lag }) {
|
|
227
|
-
effects.push({ kind: "worktree", path, action: "unchanged" });
|
|
228
|
-
lag.push(retainedLag);
|
|
229
|
-
return complete(effects, lag);
|
|
230
|
-
}
|
|
231
|
-
async function terminalSealExpectations(channel, state) {
|
|
232
|
-
const snapshots = terminalSealSnapshots(state);
|
|
233
|
-
const objects = await channel.readObjects(snapshots.map(gitObjectIdForSnapshot));
|
|
234
|
-
return decodeTerminalSealExpectations(state, objects);
|
|
235
|
-
}
|
|
236
|
-
async function removeSealedTerminalWorktree({ repository, topology, path, state, expected, hooks, retryHooks, acc }) {
|
|
237
|
-
if (topology.paths.has(path) && await pathExists(path)) {
|
|
238
|
-
const terminal = state.terminal;
|
|
239
|
-
if (terminal === null)
|
|
240
|
-
throw new Error(`terminal worktree cleanup received active Contract ${state.id}`);
|
|
241
|
-
const canRecover = terminal.kind === "abandoned";
|
|
242
|
-
let recovery = null;
|
|
243
|
-
const recordRecovery = async (workspace) => {
|
|
244
|
-
const snapshot = mintSnapshotId(await writeCommit({
|
|
245
|
-
repository,
|
|
246
|
-
tree: workspace.tree,
|
|
247
|
-
parent: gitObjectIdForSnapshot(recovery?.snapshot ?? workspace.head),
|
|
248
|
-
message: `${state.id}: ephemeral abandoned-workspace recovery\n\nKeiyaku-Contract: ${state.id}`,
|
|
249
|
-
actor: "Keiyaku Recovery",
|
|
250
|
-
at: terminal.at,
|
|
251
|
-
}));
|
|
252
|
-
const effect = {
|
|
253
|
-
kind: "recovery-snapshot",
|
|
254
|
-
action: "created",
|
|
255
|
-
snapshot,
|
|
256
|
-
retention: "ephemeral",
|
|
257
|
-
};
|
|
258
|
-
const existing = acc.effects.findIndex((item) => item.kind === "recovery-snapshot");
|
|
259
|
-
if (existing < 0)
|
|
260
|
-
acc.effects.push(effect);
|
|
261
|
-
else
|
|
262
|
-
acc.effects[existing] = effect;
|
|
263
|
-
return { snapshot, workspace };
|
|
264
|
-
};
|
|
265
|
-
const before = await observeTerminalWorkspace(repository, path, expected);
|
|
266
|
-
const beforeWorkspace = before.workspace;
|
|
267
|
-
const beforeHooks = before.unsealed;
|
|
268
|
-
if (beforeHooks !== null) {
|
|
269
|
-
if (!canRecover || beforeWorkspace.submodules.length > 0) {
|
|
270
|
-
return retainTerminalWorktree(path, beforeHooks, acc);
|
|
271
|
-
}
|
|
272
|
-
recovery = await recordRecovery(beforeWorkspace);
|
|
273
|
-
}
|
|
274
|
-
const hookLag = await runDestroyHooks(path, await worktreeGitDirectory(repository, path), hooks, retryHooks);
|
|
275
|
-
if (hookLag !== null)
|
|
276
|
-
return retainTerminalWorktree(path, hookLag, acc);
|
|
277
|
-
const after = await observeTerminalWorkspace(repository, path, expected);
|
|
278
|
-
const afterWorkspace = after.workspace;
|
|
279
|
-
const afterHooks = after.unsealed;
|
|
280
|
-
if (afterHooks !== null) {
|
|
281
|
-
if (!canRecover || afterWorkspace.submodules.length > 0) {
|
|
282
|
-
return retainTerminalWorktree(path, afterHooks, acc);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (canRecover
|
|
286
|
-
&& (recovery === null
|
|
287
|
-
|| recovery.workspace.head !== afterWorkspace.head
|
|
288
|
-
|| recovery.workspace.tree !== afterWorkspace.tree)
|
|
289
|
-
&& (recovery !== null || beforeWorkspace.head !== afterWorkspace.head || beforeWorkspace.tree !== afterWorkspace.tree)) {
|
|
290
|
-
recovery = await recordRecovery(afterWorkspace);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const removal = await removeWorktree(repository, topology, path, true);
|
|
294
|
-
if (removal.retained)
|
|
295
|
-
return retainTerminalWorktree(path, { kind: "worktree-retained", path }, acc);
|
|
296
|
-
acc.effects.push(removal.effect);
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
async function targetCustodyForClaimedIntegration(repository, state, integration) {
|
|
300
|
-
return state.terminal?.kind === "claimed" && state.coordinates.target !== undefined
|
|
301
|
-
&& await readRef(repository, state.coordinates.target) === integration
|
|
302
|
-
? { ref: state.coordinates.target, oid: integration }
|
|
303
|
-
: null;
|
|
304
|
-
}
|
|
305
|
-
function sealedTree(expected, snapshot) {
|
|
306
|
-
const tree = expected.treeBySnapshot.get(snapshot);
|
|
307
|
-
if (tree === undefined)
|
|
308
|
-
throw new Error(`terminal seal tree was not resolved: ${snapshot}`);
|
|
309
|
-
return tree;
|
|
310
|
-
}
|
|
311
|
-
async function releaseTerminalCustody({ repository, state, expected, ref, pin, acc: { effects } }) {
|
|
312
|
-
if (state.delivery === null) {
|
|
313
|
-
const custodian = await snapshotCustodian(repository, state.coordinates.start);
|
|
314
|
-
if (custodian !== null)
|
|
315
|
-
effects.push(await removeRefWithCustody(repository, ref, custodian.ref, custodian.oid));
|
|
316
|
-
effects.push(await removeRef(repository, pin));
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const tender = state.delivery.data.tenderSnapshot;
|
|
320
|
-
const integration = state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot;
|
|
321
|
-
const target = await targetCustodyForClaimedIntegration(repository, state, integration);
|
|
322
|
-
if (sealedTree(expected, tender) === sealedTree(expected, integration) && target !== null) {
|
|
323
|
-
effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
|
|
324
|
-
effects.push(await removeRefWithCustody(repository, pin, target.ref, target.oid));
|
|
325
|
-
}
|
|
326
|
-
else if (tender === integration) {
|
|
327
|
-
effects.push(await removeRefWithCustody(repository, pin, ref, tender));
|
|
328
|
-
}
|
|
329
|
-
else if (target !== null) {
|
|
330
|
-
effects.push(await removeRefWithCustody(repository, pin, target.ref, target.oid));
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
async function reconcileTerminalManagedWorktree({ repository, channel, hooks, retryHooks, retainTerminalWorktree, place }, state, topology, acc) {
|
|
334
|
-
const primary = fromPrimaryWorktree(repository);
|
|
335
|
-
const ref = deliveryRefFor(state.id);
|
|
336
|
-
const pin = candidatePinRefFor(state.id);
|
|
337
|
-
const path = place === undefined ? undefined : worktreePath(repository, place);
|
|
338
|
-
if (path === undefined)
|
|
339
|
-
return complete(acc.effects, acc.lag);
|
|
340
|
-
const expected = await terminalSealExpectations(channel, state);
|
|
341
|
-
acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
|
|
342
|
-
if (state.delivery !== null) {
|
|
343
|
-
acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
|
|
344
|
-
}
|
|
345
|
-
if (retainTerminalWorktree === true)
|
|
346
|
-
return complete(acc.effects, acc.lag);
|
|
347
|
-
const retained = await removeSealedTerminalWorktree({
|
|
348
|
-
repository: primary,
|
|
349
|
-
topology,
|
|
350
|
-
path,
|
|
351
|
-
state,
|
|
352
|
-
expected,
|
|
353
|
-
hooks,
|
|
354
|
-
retryHooks,
|
|
355
|
-
acc,
|
|
356
|
-
});
|
|
357
|
-
if (retained !== null)
|
|
358
|
-
return retained;
|
|
359
|
-
await releaseTerminalCustody({ repository: primary, state, expected, ref, pin, acc });
|
|
360
|
-
return complete(acc.effects, acc.lag);
|
|
361
|
-
}
|
|
362
106
|
async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, place }, state, topology, { effects, lag }) {
|
|
363
107
|
if (place === undefined)
|
|
364
108
|
return complete(effects, [...lag, missingPlaceLag(repository)]);
|
|
365
109
|
const path = worktreePath(repository, place);
|
|
366
110
|
const desired = state.delivery?.data.tenderSnapshot ?? state.coordinates.start;
|
|
367
111
|
effects.push(await updateRef(repository, deliveryRefFor(state.id), desired));
|
|
368
|
-
|
|
112
|
+
const projection = await worktree(repository, topology, path, desired);
|
|
113
|
+
if (projection.kind === "worktree" && projection.action === "unchanged" && state.delivery !== null) {
|
|
114
|
+
const follow = await followManagedWorktree(repository, path, state.delivery.data.tenderSnapshot);
|
|
115
|
+
if (follow.kind === "followed") {
|
|
116
|
+
effects.push({ kind: "worktree", path, action: "followed", before: follow.before, after: follow.after });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
effects.push(projection);
|
|
120
|
+
if (follow.kind === "retained") {
|
|
121
|
+
lag.push({
|
|
122
|
+
kind: "worktree-follow-retained",
|
|
123
|
+
path,
|
|
124
|
+
tender: state.delivery.data.tenderSnapshot,
|
|
125
|
+
head: follow.head,
|
|
126
|
+
reason: follow.reason,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
effects.push(projection);
|
|
133
|
+
}
|
|
369
134
|
const hookLag = await runCreateHooks(path, await worktreeGitDirectory(repository, path), hooks, retryHooks);
|
|
370
135
|
if (hookLag !== null)
|
|
371
136
|
lag.push(hookLag);
|
|
@@ -385,10 +150,9 @@ async function reconcileWithTopology(input, state, topology) {
|
|
|
385
150
|
const targetCheckouts = await reconcileTargetCheckouts(repository, state);
|
|
386
151
|
effects.push(...targetCheckouts.effects);
|
|
387
152
|
lag.push(...targetCheckouts.lag);
|
|
388
|
-
if (state.
|
|
389
|
-
return await
|
|
390
|
-
|
|
391
|
-
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
|
+
}
|
|
392
156
|
return await reconcileActiveManagedWorktree(input, state, topology, { effects, lag });
|
|
393
157
|
}
|
|
394
158
|
catch (error) {
|
|
@@ -4,6 +4,8 @@ export type { TreeChange } from "./tree.js";
|
|
|
4
4
|
export declare const GIT_REF = "refs/heads/keiyaku-state";
|
|
5
5
|
export declare const DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
|
|
6
6
|
export declare const CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
|
|
7
|
+
export declare const MIGRATION_DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
|
|
8
|
+
export declare const MIGRATION_CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
|
|
7
9
|
export declare const GIT_FORMAT_PATH = "meta/format.json";
|
|
8
10
|
export declare const GIT_FORMAT_BYTES = "{\"version\":4}\n";
|
|
9
11
|
export declare function isKeiyakuOwnedRef(ref: string): boolean;
|