@astrosheep/keiyaku 2.9.3 → 2.9.5
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/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +14 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +26 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +68 -5
- package/build/agents/harness/projection.js +179 -124
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +82 -53
- package/build/agents/providers/codex-app-server/events.js +90 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/events.js +4 -3
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +10 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +1 -1
- package/build/cli/commands/akuma/list/meta-ls.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma/view/meta.js +1 -1
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/arc/handler.js +8 -2
- package/build/cli/commands/contract/arc/meta.js +4 -4
- package/build/cli/commands/contract/audit/handler.js +16 -0
- package/build/cli/commands/contract/audit/meta.js +16 -0
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +44 -8
- package/build/cli/commands/contract/catalog.js +2 -0
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/forfeit/meta.js +1 -1
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/log/meta.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +5 -8
- package/build/cli/commands/contract/petition/meta.js +6 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +6 -5
- package/build/cli/commands/metadata.js +59 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/call/meta.js +4 -1
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/kill/meta.js +1 -1
- package/build/cli/commands/projection/revive/meta.js +4 -1
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/status/meta.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/tell/meta.js +4 -1
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +8 -3
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/install/meta.js +1 -1
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/skills/skills/meta.js +1 -1
- package/build/cli/commands/system/completion/meta.js +1 -1
- package/build/cli/commands/system/dump-env/meta.js +1 -1
- package/build/cli/commands/system/guide/meta.js +1 -1
- package/build/cli/commands/task/add/handler.js +7 -15
- package/build/cli/commands/task/add/meta.js +4 -1
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/doctor/meta.js +1 -1
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/ls/handler.js +12 -8
- package/build/cli/commands/task/ls/meta.js +3 -3
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/task/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +6 -11
- package/build/cli/commands/task/update/meta.js +4 -1
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +37 -6
- package/build/cli/help.js +7 -14
- package/build/cli/index.js +112 -154
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/output-stream-error-policy.js +19 -5
- package/build/cli/parse-flags.js +33 -7
- package/build/cli/parse-metadata.js +3 -9
- package/build/cli/parse-selectors.js +2 -1
- package/build/cli/parse.js +35 -5
- package/build/cli/projection-address.js +12 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/audit.js +121 -0
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +58 -13
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +15 -6
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +56 -23
- package/build/cli/render/success-response.js +78 -0
- package/build/cli/render/task.js +66 -57
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +63 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +68 -38
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +3 -0
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +61 -18
- package/build/core/arc.js +33 -73
- package/build/core/audit.js +650 -0
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +22 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +22 -6
- package/build/core/draft.js +139 -19
- package/build/core/entry.js +3 -4
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/lex.js +2 -1
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +41 -52
- package/build/core/projection/generation/model.js +63 -2
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
- package/build/core/projection/generation/store.js +3 -3
- package/build/core/projection/generation/transitions.js +19 -3
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
- package/build/core/projection/projection-terminal-failure.js +106 -0
- package/build/core/projection/projection-wait.js +275 -0
- package/build/core/projection/projection-wake.js +227 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +448 -102
- package/build/core/scope.js +272 -68
- package/build/core/seal.js +165 -40
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
- package/build/core/{petition.js → settlement/petition.js} +21 -12
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +288 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/settlement/verification.js +59 -0
- package/build/core/status/board.js +62 -41
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +6 -2
- package/build/core/task/board.js +41 -4
- package/build/core/task/commands.js +45 -28
- package/build/core/task/document.js +5 -0
- package/build/core/task/index.js +12 -0
- package/build/core/task/model.js +8 -1
- package/build/core/task/query.js +172 -0
- package/build/core/task/settlement-git.js +265 -74
- package/build/core/task/settlement-policy.js +67 -29
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-contract.js +444 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +7 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +83 -39
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/diff/parsers.js +23 -13
- package/build/git/diff/structured.js +127 -0
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +9 -9
- package/build/keiyaku.js +2 -36
- package/build/telemetry/logger.js +4 -2
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +5 -4
- package/skills/keiyaku-akuma/SKILL.md +25 -11
- package/skills/keiyaku-task/SKILL.md +17 -8
- package/skills/keiyaku-workflow/SKILL.md +58 -9
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
import { resolveTaskReadId } from "./board.js";
|
|
3
|
+
import { advancedTaskUpdatedAt } from "./model.js";
|
|
3
4
|
export function requireHealthySettlementBoard(board) {
|
|
4
5
|
if (board.diseases.length > 0) {
|
|
5
6
|
throw new FlowError("INTERNAL_STATE", `authoritative task board is diseased: ${board.diseases.map((disease) => disease.message).join("; ")}`);
|
|
6
7
|
}
|
|
7
8
|
return board;
|
|
8
9
|
}
|
|
9
|
-
function settlementDocument(task, contractId) {
|
|
10
|
+
function settlementDocument(task, contractId, updatedAt) {
|
|
10
11
|
return {
|
|
11
12
|
id: task.id,
|
|
12
13
|
title: task.title,
|
|
@@ -16,30 +17,28 @@ function settlementDocument(task, contractId) {
|
|
|
16
17
|
parent: task.parent,
|
|
17
18
|
from: [...task.from],
|
|
18
19
|
createdAt: task.createdAt,
|
|
20
|
+
updatedAt: advancedTaskUpdatedAt(task, updatedAt),
|
|
19
21
|
creator: task.creator,
|
|
20
22
|
...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
|
|
21
23
|
...(contractId === undefined ? {} : { contractId }),
|
|
22
24
|
body: task.body,
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
export function contractPointerDocument(task, contractId) {
|
|
26
|
-
return settlementDocument(task, contractId);
|
|
27
|
+
export function contractPointerDocument(task, contractId, updatedAt) {
|
|
28
|
+
return settlementDocument(task, contractId, updatedAt);
|
|
27
29
|
}
|
|
28
|
-
export function reopenedTaskDocument(task) {
|
|
29
|
-
const document = settlementDocument(task, undefined);
|
|
30
|
+
export function reopenedTaskDocument(task, updatedAt) {
|
|
31
|
+
const document = settlementDocument(task, undefined, updatedAt);
|
|
30
32
|
document.state = "open";
|
|
31
33
|
delete document.startedAt;
|
|
32
34
|
return document;
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
requireHealthySettlementBoard(board);
|
|
36
|
-
const id = resolveTaskReadId(board, taskId);
|
|
37
|
-
const task = board.byId.get(id);
|
|
36
|
+
function validateReadyForBind(board, task) {
|
|
38
37
|
if (task.state === "in_progress") {
|
|
39
|
-
throw new FlowError("INVALID_TARGET", `task ${id} is in_progress; stop it before bind`);
|
|
38
|
+
throw new FlowError("INVALID_TARGET", `task ${task.id} is in_progress; stop it before bind`);
|
|
40
39
|
}
|
|
41
40
|
if (task.state !== "open") {
|
|
42
|
-
throw new FlowError("INVALID_TARGET", `task ${id} is ${task.state}, not ready for bind`);
|
|
41
|
+
throw new FlowError("INVALID_TARGET", `task ${task.id} is ${task.state}, not ready for bind`);
|
|
43
42
|
}
|
|
44
43
|
const unsatisfied = task.needs
|
|
45
44
|
.filter((need) => {
|
|
@@ -48,22 +47,70 @@ export function selectReadyTaskForBind(board, taskId) {
|
|
|
48
47
|
})
|
|
49
48
|
.sort();
|
|
50
49
|
if (unsatisfied.length > 0) {
|
|
51
|
-
throw new FlowError("INVALID_TARGET", `task ${id} is blocked by: ${unsatisfied.join(", ")}`);
|
|
50
|
+
throw new FlowError("INVALID_TARGET", `task ${task.id} is blocked by: ${unsatisfied.join(", ")}`);
|
|
52
51
|
}
|
|
53
52
|
if (task.contractId !== undefined) {
|
|
54
|
-
throw new FlowError("INVALID_TARGET", `task ${id} is already bound to contract ${task.contractId}`);
|
|
53
|
+
throw new FlowError("INVALID_TARGET", `task ${task.id} is already bound to contract ${task.contractId}`);
|
|
55
54
|
}
|
|
56
|
-
return task;
|
|
57
55
|
}
|
|
58
|
-
export function
|
|
56
|
+
export function selectReadyTasksForBind(board, taskIds) {
|
|
59
57
|
requireHealthySettlementBoard(board);
|
|
60
|
-
const
|
|
58
|
+
const rootIds = taskIds.map((taskId) => resolveTaskReadId(board, taskId));
|
|
59
|
+
const duplicates = rootIds.filter((id, index) => rootIds.indexOf(id) !== index);
|
|
60
|
+
if (duplicates.length > 0) {
|
|
61
|
+
throw new FlowError("INVALID_TARGET", `task ${[...new Set(duplicates)].sort().join(", ")} selected more than once`);
|
|
62
|
+
}
|
|
63
|
+
const admitted = new Set();
|
|
64
|
+
const tasks = [];
|
|
65
|
+
for (const rootId of rootIds) {
|
|
66
|
+
const root = board.byId.get(rootId);
|
|
67
|
+
if (!admitted.has(root.id)) {
|
|
68
|
+
admitted.add(root.id);
|
|
69
|
+
tasks.push(root);
|
|
70
|
+
}
|
|
71
|
+
for (const candidate of board.tasks) {
|
|
72
|
+
if (candidate.parent === null || admitted.has(candidate.id))
|
|
73
|
+
continue;
|
|
74
|
+
if (candidate.state === "done" || candidate.state === "drop")
|
|
75
|
+
continue;
|
|
76
|
+
if (isDescendantOf(board, candidate, rootId)) {
|
|
77
|
+
admitted.add(candidate.id);
|
|
78
|
+
tasks.push(candidate);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const task of tasks)
|
|
83
|
+
validateReadyForBind(board, task);
|
|
84
|
+
return tasks;
|
|
85
|
+
}
|
|
86
|
+
function isDescendantOf(board, task, ancestorId) {
|
|
87
|
+
let current = task.parent;
|
|
88
|
+
while (current !== null) {
|
|
89
|
+
if (current === ancestorId)
|
|
90
|
+
return true;
|
|
91
|
+
current = board.byId.get(current)?.parent ?? null;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
export function tasksForContract(board, contractId) {
|
|
96
|
+
requireHealthySettlementBoard(board);
|
|
97
|
+
return board.tasks
|
|
61
98
|
.filter((task) => task.contractId === contractId)
|
|
62
99
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
}
|
|
101
|
+
export function doneTaskReplacements(board, contractId, taskIds, updatedAt) {
|
|
102
|
+
return taskIds.flatMap((taskId) => {
|
|
103
|
+
const task = requireSettledTask(board, taskId);
|
|
104
|
+
if (task.state === "done" && task.contractId === undefined)
|
|
105
|
+
return [];
|
|
106
|
+
if (task.contractId !== contractId) {
|
|
107
|
+
throw new FlowError("INTERNAL_STATE", `task ${task.id} no longer points to contract ${contractId} while settling its immutable bind-base ownership`);
|
|
108
|
+
}
|
|
109
|
+
const settled = settlementDocument(task, undefined, updatedAt);
|
|
110
|
+
settled.state = "done";
|
|
111
|
+
delete settled.startedAt;
|
|
112
|
+
return [settled];
|
|
113
|
+
});
|
|
67
114
|
}
|
|
68
115
|
export function requireSettledTask(board, id) {
|
|
69
116
|
const task = requireHealthySettlementBoard(board).byId.get(id);
|
|
@@ -71,12 +118,3 @@ export function requireSettledTask(board, id) {
|
|
|
71
118
|
throw new FlowError("INTERNAL_STATE", `task ${id} disappeared from the board`);
|
|
72
119
|
return task;
|
|
73
120
|
}
|
|
74
|
-
export function doneTaskReplacement(board, contractId) {
|
|
75
|
-
const bound = taskForContract(board, contractId);
|
|
76
|
-
if (!bound)
|
|
77
|
-
return undefined;
|
|
78
|
-
const settled = settlementDocument(bound, undefined);
|
|
79
|
-
settled.state = "done";
|
|
80
|
-
delete settled.startedAt;
|
|
81
|
-
return settled;
|
|
82
|
-
}
|
|
@@ -23,6 +23,7 @@ function taskDocument(parsed) {
|
|
|
23
23
|
parent: parsed.parent,
|
|
24
24
|
from: [...parsed.from],
|
|
25
25
|
createdAt: parsed.createdAt,
|
|
26
|
+
...(parsed.updatedAt === undefined ? {} : { updatedAt: parsed.updatedAt }),
|
|
26
27
|
creator: parsed.creator,
|
|
27
28
|
...(parsed.startedAt === undefined ? {} : { startedAt: parsed.startedAt }),
|
|
28
29
|
...(parsed.contractId === undefined ? {} : { contractId: parsed.contractId }),
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { assertGitObservation, requireGitRepository } from "../../git/branches.js";
|
|
3
|
+
import { withLockHeldTaskBoard } from "./task-git-store.js";
|
|
4
|
+
import { prepareTaskDocumentReplacements, taskBoardFromSourceBoard, taskRepositoryFromLockedBoard } from "./task-store-repository.js";
|
|
5
|
+
import { serializeTaskDocument } from "./document.js";
|
|
6
|
+
import { buildTaskSourceBoard } from "./source-board.js";
|
|
7
|
+
import { composeLandingTree, containsCommit, createLandingCommit, createTaskBoardCommit, findAcceptedLandingCommit, publishTaskMutationCommit, inspectCurrentBranch, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskContractAtCommit, taskSettlementOwnershipAtCommit, taskSourcesAtCommit, taskWritesFromSources, trackedTaskPaths, updateBranch, updateTargetRef, } from "./settlement-git.js";
|
|
8
|
+
import { contractPointerDocument, doneTaskReplacements, reopenedTaskDocument, requireHealthySettlementBoard, requireSettledTask, selectReadyTasksForBind, tasksForContract, } from "./settlement-policy.js";
|
|
9
|
+
import { stableRepoRoot } from "../worktree-path.js";
|
|
10
|
+
function sourcePath(root, source) {
|
|
11
|
+
return source.path.slice(root.length + 1).split("\\").join("/");
|
|
12
|
+
}
|
|
13
|
+
function taskPath(id) {
|
|
14
|
+
return `.keiyaku/tasks/${id}.md`;
|
|
15
|
+
}
|
|
16
|
+
function ownedTaskPaths(board, taskIds) {
|
|
17
|
+
if (!taskIds || taskIds.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const paths = new Set();
|
|
20
|
+
for (const taskId of taskIds) {
|
|
21
|
+
let current = board.byId.get(taskId);
|
|
22
|
+
while (current) {
|
|
23
|
+
paths.add(taskPath(current.id));
|
|
24
|
+
current = current.parent === null ? undefined : board.byId.get(current.parent);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [...paths].sort();
|
|
28
|
+
}
|
|
29
|
+
function selectSettlementSources(input) {
|
|
30
|
+
const tracked = new Set(input.trackedPaths);
|
|
31
|
+
const sourceByPath = new Map(input.sources.map((source) => [sourcePath(input.root, source), source]));
|
|
32
|
+
// An ancestry path is eligible only when it was already tracked. A mutation is
|
|
33
|
+
// the sole explicit adoption authority for an untracked task document.
|
|
34
|
+
const selected = new Set([
|
|
35
|
+
...input.ownedPaths.filter((taskSourcePath) => tracked.has(taskSourcePath)),
|
|
36
|
+
...input.mutationPaths,
|
|
37
|
+
].filter((taskSourcePath) => sourceByPath.has(taskSourcePath)));
|
|
38
|
+
const selectedPaths = [...selected].sort();
|
|
39
|
+
const adoptedPaths = selectedPaths.filter((taskSourcePath) => !tracked.has(taskSourcePath));
|
|
40
|
+
const observedPaths = new Set([...tracked, ...sourceByPath.keys()]);
|
|
41
|
+
return {
|
|
42
|
+
sources: selectedPaths.map((taskSourcePath) => sourceByPath.get(taskSourcePath)),
|
|
43
|
+
evidence: {
|
|
44
|
+
selectedPaths,
|
|
45
|
+
rejectedPaths: [...observedPaths].filter((taskSourcePath) => !selected.has(taskSourcePath)).sort(),
|
|
46
|
+
adoptedPaths,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function repositoryForSettlement(transaction, sources) {
|
|
51
|
+
let currentSources = sources;
|
|
52
|
+
return {
|
|
53
|
+
read: async () => taskBoardFromSourceBoard(buildTaskSourceBoard(currentSources)),
|
|
54
|
+
sources: () => currentSources,
|
|
55
|
+
mutate: async (apply) => {
|
|
56
|
+
const board = taskBoardFromSourceBoard(buildTaskSourceBoard(currentSources));
|
|
57
|
+
const mutation = await apply(board);
|
|
58
|
+
const prepared = prepareTaskDocumentReplacements(currentSources, mutation.replacements);
|
|
59
|
+
await transaction.replace(prepared.writes);
|
|
60
|
+
currentSources = prepared.sources;
|
|
61
|
+
return mutation.select(prepared.board);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function clearAttemptedPointers(input) {
|
|
66
|
+
return input.repository.mutate((board) => {
|
|
67
|
+
const replacements = input.tasks.flatMap((task) => {
|
|
68
|
+
const current = requireSettledTask(board, task.id);
|
|
69
|
+
return current.contractId === input.contractId
|
|
70
|
+
? [contractPointerDocument(current, undefined, input.updatedAt)]
|
|
71
|
+
: [];
|
|
72
|
+
});
|
|
73
|
+
return { replacements, select: () => replacements.map((replacement) => replacement.id) };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async function replaceTaskDocuments(repository, replacements) {
|
|
77
|
+
return repository.mutate(() => ({
|
|
78
|
+
replacements,
|
|
79
|
+
select: (resulting) => replacements.map((replacement) => requireSettledTask(resulting, replacement.id)),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
async function restorePreAdmissionTaskMutations(input) {
|
|
83
|
+
// Write text is ignored by restore; the id selects the path whose prior bytes return.
|
|
84
|
+
await input.transaction.restore(input.taskIds.map((id) => ({ id, text: "" })), input.previousSources);
|
|
85
|
+
}
|
|
86
|
+
async function rollForwardClearAttemptedPointer(input) {
|
|
87
|
+
const clearedIds = await clearAttemptedPointers({
|
|
88
|
+
repository: input.repository,
|
|
89
|
+
tasks: input.selected,
|
|
90
|
+
contractId: input.contractId,
|
|
91
|
+
updatedAt: input.updatedAt,
|
|
92
|
+
});
|
|
93
|
+
if (clearedIds.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
const rollbackSelection = selectSettlementSources({
|
|
96
|
+
root: input.root,
|
|
97
|
+
sources: input.repository.sources(),
|
|
98
|
+
trackedPaths: await trackedTaskPaths(input.hubCwd, input.trackedHead),
|
|
99
|
+
ownedPaths: input.selectedPaths,
|
|
100
|
+
mutationPaths: clearedIds.map(taskPath),
|
|
101
|
+
});
|
|
102
|
+
await publishForwardTaskBoardCommit({
|
|
103
|
+
cwd: input.hubCwd,
|
|
104
|
+
ref: input.branchRef,
|
|
105
|
+
gitDir: input.gitDir,
|
|
106
|
+
root: input.root,
|
|
107
|
+
sources: rollbackSelection.sources,
|
|
108
|
+
paths: rollbackSelection.evidence.selectedPaths,
|
|
109
|
+
subject: `Clear failed contract ${input.contractId} from ${clearedIds.length === 1 ? `task ${clearedIds[0]}` : `${clearedIds.length} tasks`}`,
|
|
110
|
+
}, input.deps);
|
|
111
|
+
}
|
|
112
|
+
export async function settleTaskBoardForBind(input, deps = {}) {
|
|
113
|
+
const updatedAt = new Date().toISOString();
|
|
114
|
+
await requireGitRepository(input.cwd, "task bind");
|
|
115
|
+
return withLockHeldTaskBoard(input.cwd, async (transaction) => {
|
|
116
|
+
if (transaction.location.mode !== "git") {
|
|
117
|
+
if (transaction.location.capability === "unavailable") {
|
|
118
|
+
assertGitObservation({ capability: "unavailable" }, "task bind");
|
|
119
|
+
}
|
|
120
|
+
throw new FlowError("NOT_GIT_REPO", "task bind requires Git task storage");
|
|
121
|
+
}
|
|
122
|
+
const hubCwd = transaction.location.root;
|
|
123
|
+
const branch = await inspectCurrentBranch(hubCwd);
|
|
124
|
+
const previousSources = transaction.sources;
|
|
125
|
+
const repository = repositoryForSettlement(transaction, transaction.sources);
|
|
126
|
+
const board = requireHealthySettlementBoard(await repository.read());
|
|
127
|
+
const taskBound = "taskIds" in input;
|
|
128
|
+
const selectedTasks = taskBound ? selectReadyTasksForBind(board, input.taskIds) : [];
|
|
129
|
+
const prepared = taskBound ? await input.prepare(selectedTasks) : input;
|
|
130
|
+
const contractId = prepared.contractId;
|
|
131
|
+
if (taskBound && selectedTasks.length > 0) {
|
|
132
|
+
await replaceTaskDocuments(repository, selectedTasks.map((task) => contractPointerDocument(task, contractId, updatedAt)));
|
|
133
|
+
}
|
|
134
|
+
const taskIds = taskBound ? selectedTasks.map((t) => t.id) : undefined;
|
|
135
|
+
const selectedPaths = ownedTaskPaths(await repository.read(), taskIds);
|
|
136
|
+
const mutationPaths = taskBound ? selectedTasks.map((t) => taskPath(t.id)) : [];
|
|
137
|
+
const selectCurrentSettlement = async () => selectSettlementSources({
|
|
138
|
+
root: transaction.location.root,
|
|
139
|
+
sources: repository.sources(),
|
|
140
|
+
trackedPaths: await trackedTaskPaths(hubCwd, branch.head),
|
|
141
|
+
ownedPaths: selectedPaths,
|
|
142
|
+
mutationPaths,
|
|
143
|
+
});
|
|
144
|
+
const selection = await selectCurrentSettlement();
|
|
145
|
+
const makeBoardCommit = deps.createTaskBoardCommit ?? createTaskBoardCommit;
|
|
146
|
+
const reconcileIndex = deps.reconcileTaskPathsetIndex ?? reconcileTaskPathsetIndex;
|
|
147
|
+
let firstCommit;
|
|
148
|
+
try {
|
|
149
|
+
const taskLabel = taskBound
|
|
150
|
+
? selectedTasks.length === 1
|
|
151
|
+
? `task ${selectedTasks[0].id}`
|
|
152
|
+
: `${selectedTasks.length} tasks`
|
|
153
|
+
: "";
|
|
154
|
+
firstCommit = await makeBoardCommit({
|
|
155
|
+
cwd: hubCwd,
|
|
156
|
+
gitDir: transaction.location.gitDir,
|
|
157
|
+
root: transaction.location.root,
|
|
158
|
+
parent: branch.head,
|
|
159
|
+
sources: selection.sources,
|
|
160
|
+
subject: taskBound
|
|
161
|
+
? `Bind ${taskLabel} to contract ${contractId}`
|
|
162
|
+
: `Flush task board for contract ${contractId}`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (taskBound) {
|
|
167
|
+
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.id), previousSources });
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await updateBranch({ cwd: hubCwd, ref: branch.ref, next: firstCommit, expected: branch.head }, deps);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (taskBound) {
|
|
176
|
+
const concurrentHead = await refHead(hubCwd, branch.ref);
|
|
177
|
+
const concurrentMatches = await Promise.all(selectedTasks.map(async (task) => (await taskContractAtCommit(hubCwd, concurrentHead, task.id)) === contractId));
|
|
178
|
+
if (concurrentMatches.every(Boolean)) {
|
|
179
|
+
await rollForwardClearAttemptedPointer({
|
|
180
|
+
repository, selected: selectedTasks, contractId, hubCwd,
|
|
181
|
+
branchRef: branch.ref, trackedHead: branch.head,
|
|
182
|
+
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
183
|
+
selectedPaths, deps, updatedAt,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.id), previousSources });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await reconcileIndex(hubCwd, firstCommit, selection.evidence.selectedPaths);
|
|
194
|
+
}
|
|
195
|
+
catch (indexError) {
|
|
196
|
+
if (taskBound) {
|
|
197
|
+
await rollForwardClearAttemptedPointer({
|
|
198
|
+
repository, selected: selectedTasks, contractId, hubCwd,
|
|
199
|
+
branchRef: branch.ref, trackedHead: branch.head,
|
|
200
|
+
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
201
|
+
selectedPaths, deps, updatedAt,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
throw indexError;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const ledger = await prepared.publishLedger(firstCommit);
|
|
208
|
+
const boardAfter = requireHealthySettlementBoard(await repository.read());
|
|
209
|
+
return {
|
|
210
|
+
boardCommit: firstCommit,
|
|
211
|
+
contractId,
|
|
212
|
+
ledger,
|
|
213
|
+
...(taskBound && selectedTasks.length > 0 ? { tasks: selectedTasks.map((t) => requireSettledTask(boardAfter, t.id)) } : {}),
|
|
214
|
+
evidence: selection.evidence,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (publishError) {
|
|
218
|
+
if (!taskBound)
|
|
219
|
+
throw publishError;
|
|
220
|
+
await rollForwardClearAttemptedPointer({
|
|
221
|
+
repository, selected: selectedTasks, contractId, hubCwd,
|
|
222
|
+
branchRef: branch.ref, trackedHead: branch.head,
|
|
223
|
+
gitDir: transaction.location.gitDir, root: transaction.location.root,
|
|
224
|
+
selectedPaths, deps, updatedAt,
|
|
225
|
+
});
|
|
226
|
+
throw publishError;
|
|
227
|
+
}
|
|
228
|
+
}, { signal: input.signal });
|
|
229
|
+
}
|
|
230
|
+
export async function reconcileTaskForfeit(input) {
|
|
231
|
+
const updatedAt = new Date().toISOString();
|
|
232
|
+
await requireGitRepository(input.cwd, "task forfeit");
|
|
233
|
+
const hubRoot = await stableRepoRoot(input.cwd);
|
|
234
|
+
return withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
235
|
+
if (transaction.location.mode !== "git") {
|
|
236
|
+
if (transaction.location.capability === "unavailable") {
|
|
237
|
+
assertGitObservation({ capability: "unavailable" }, "task forfeit");
|
|
238
|
+
}
|
|
239
|
+
throw new FlowError("NOT_GIT_REPO", "task forfeit requires Git task storage");
|
|
240
|
+
}
|
|
241
|
+
const repository = taskRepositoryFromLockedBoard(transaction);
|
|
242
|
+
const matched = tasksForContract(await repository.read(), input.contractId);
|
|
243
|
+
const ledger = await input.publishLedger();
|
|
244
|
+
if (matched.length === 0)
|
|
245
|
+
return { ledger };
|
|
246
|
+
const reopenedDocuments = matched.map((task) => reopenedTaskDocument(task, updatedAt));
|
|
247
|
+
const reopened = await replaceTaskDocuments(repository, reopenedDocuments);
|
|
248
|
+
const taskLabel = matched.length === 1 ? `task ${matched[0].id}` : `${matched.length} tasks`;
|
|
249
|
+
await publishTaskMutationCommit({
|
|
250
|
+
cwd: transaction.location.root,
|
|
251
|
+
gitDir: transaction.location.gitDir,
|
|
252
|
+
writes: reopenedDocuments.map((document) => ({ id: document.id, text: serializeTaskDocument(document) })),
|
|
253
|
+
subject: `Reopen ${taskLabel} after forfeit of ${input.contractId}`,
|
|
254
|
+
});
|
|
255
|
+
return { ledger, tasks: reopened };
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Claim owns the sole task-board settlement after target CAS. Contract branch
|
|
260
|
+
* task trees are fossils: the candidate is the code-only three-way merge plus
|
|
261
|
+
* one complete snapshot of the common hub board held under its repository lock.
|
|
262
|
+
*/
|
|
263
|
+
export async function settleTaskBoardForLanding(input, deps = {}) {
|
|
264
|
+
const updatedAt = new Date().toISOString();
|
|
265
|
+
await requireGitRepository(input.cwd, "task landing");
|
|
266
|
+
const hubRoot = await stableRepoRoot(input.cwd);
|
|
267
|
+
const ownership = await taskSettlementOwnershipAtCommit(input.cwd, input.baseCommit, input.contractId);
|
|
268
|
+
const reconcileIndex = deps.reconcileTaskPathsetIndex ?? reconcileTaskPathsetIndex;
|
|
269
|
+
let lastTargetHead = input.targetHead;
|
|
270
|
+
let lastPostCasError;
|
|
271
|
+
let acceptedMovement;
|
|
272
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
273
|
+
const result = await withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
274
|
+
if (transaction.location.mode !== "git") {
|
|
275
|
+
if (transaction.location.capability === "unavailable") {
|
|
276
|
+
assertGitObservation({ capability: "unavailable" }, "task landing");
|
|
277
|
+
}
|
|
278
|
+
throw new FlowError("NOT_GIT_REPO", "task landing requires Git task storage");
|
|
279
|
+
}
|
|
280
|
+
// Each attempt reads fresh target and board facts while holding the
|
|
281
|
+
// common task lock. A losing CAS exits before the next attempt.
|
|
282
|
+
const targetHead = await refHead(input.cwd, input.targetRef);
|
|
283
|
+
lastTargetHead = targetHead;
|
|
284
|
+
const selectedPaths = ownership.paths;
|
|
285
|
+
const acceptedLanding = acceptedMovement?.mergeCommit === targetHead
|
|
286
|
+
? targetHead
|
|
287
|
+
: await findAcceptedLandingCommit({
|
|
288
|
+
cwd: input.cwd,
|
|
289
|
+
gitDir: transaction.location.gitDir,
|
|
290
|
+
root: transaction.location.root,
|
|
291
|
+
base: input.baseCommit,
|
|
292
|
+
target: targetHead,
|
|
293
|
+
delivery: input.deliveryHead,
|
|
294
|
+
contractId: input.contractId,
|
|
295
|
+
targetBranch: input.targetBranch,
|
|
296
|
+
ownedPaths: selectedPaths,
|
|
297
|
+
settledPaths: ownership.taskIds.map(taskPath),
|
|
298
|
+
});
|
|
299
|
+
const deliveryAlreadyAccepted = ownership.taskIds.length === 0
|
|
300
|
+
&& await containsCommit(input.cwd, input.deliveryHead, targetHead);
|
|
301
|
+
if (acceptedLanding !== undefined || deliveryAlreadyAccepted) {
|
|
302
|
+
const mergeCommit = acceptedLanding ?? targetHead;
|
|
303
|
+
const movement = acceptedMovement?.mergeCommit === targetHead ? acceptedMovement : undefined;
|
|
304
|
+
const authoritativeSources = selectedPaths.length > 0
|
|
305
|
+
? await taskSourcesAtCommit(input.cwd, transaction.location.root, targetHead, selectedPaths)
|
|
306
|
+
: [];
|
|
307
|
+
if (!authoritativeSources) {
|
|
308
|
+
throw new FlowError("INTERNAL_STATE", `accepted landing ${mergeCommit} lost an owned task path`);
|
|
309
|
+
}
|
|
310
|
+
const selection = selectSettlementSources({
|
|
311
|
+
root: transaction.location.root,
|
|
312
|
+
sources: authoritativeSources,
|
|
313
|
+
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
314
|
+
ownedPaths: selectedPaths,
|
|
315
|
+
mutationPaths: selectedPaths,
|
|
316
|
+
});
|
|
317
|
+
const currentText = new Map(transaction.sources.map((source) => [sourcePath(transaction.location.root, source), source.text]));
|
|
318
|
+
const writes = authoritativeSources.filter((source) => currentText.get(sourcePath(transaction.location.root, source)) !== source.text);
|
|
319
|
+
if (writes.length > 0)
|
|
320
|
+
await transaction.replace(taskWritesFromSources(writes));
|
|
321
|
+
try {
|
|
322
|
+
await reconcileIndex(transaction.location.root, targetHead, selectedPaths);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
lastPostCasError = error;
|
|
326
|
+
return { kind: "retry" };
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
await input.publishLedger?.(mergeCommit);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
lastPostCasError = error;
|
|
333
|
+
return { kind: "retry" };
|
|
334
|
+
}
|
|
335
|
+
const reconcileWarnings = movement
|
|
336
|
+
? await input.afterCas?.(mergeCommit, movement.oldHead)
|
|
337
|
+
: undefined;
|
|
338
|
+
return {
|
|
339
|
+
kind: movement ? "accepted" : "idempotent",
|
|
340
|
+
mergeCommit,
|
|
341
|
+
...(movement ? { oldHead: movement.oldHead } : {}),
|
|
342
|
+
...(reconcileWarnings && reconcileWarnings.length > 0 ? { warnings: reconcileWarnings } : {}),
|
|
343
|
+
evidence: selection.evidence,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// No durable receipt exists, so this remains a first-publication attempt:
|
|
347
|
+
// validate mutable hub authority before composing or publishing anything.
|
|
348
|
+
const repository = repositoryForSettlement(transaction, transaction.sources);
|
|
349
|
+
const board = requireHealthySettlementBoard(await repository.read());
|
|
350
|
+
const replacements = doneTaskReplacements(board, input.contractId, ownership.taskIds, updatedAt);
|
|
351
|
+
const prepared = replacements.length > 0
|
|
352
|
+
? prepareTaskDocumentReplacements(transaction.sources, replacements)
|
|
353
|
+
: { sources: transaction.sources, board };
|
|
354
|
+
const finalSources = prepared.sources;
|
|
355
|
+
requireHealthySettlementBoard(prepared.board);
|
|
356
|
+
const selection = selectSettlementSources({
|
|
357
|
+
root: transaction.location.root,
|
|
358
|
+
sources: finalSources,
|
|
359
|
+
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
360
|
+
ownedPaths: selectedPaths,
|
|
361
|
+
// Bind-base ownership, unlike incidental hub bytes, authorizes a path
|
|
362
|
+
// even when a concurrent target commit removed it.
|
|
363
|
+
mutationPaths: selectedPaths,
|
|
364
|
+
});
|
|
365
|
+
const selected = new Set(selection.evidence.selectedPaths);
|
|
366
|
+
const missingOwnedPaths = selectedPaths.filter((ownedPath) => !selected.has(ownedPath));
|
|
367
|
+
if (missingOwnedPaths.length > 0) {
|
|
368
|
+
throw new FlowError("INTERNAL_STATE", `hub board lost bind-base-owned task paths: ${missingOwnedPaths.join(", ")}`);
|
|
369
|
+
}
|
|
370
|
+
const candidateTree = await composeLandingTree({
|
|
371
|
+
cwd: input.cwd,
|
|
372
|
+
gitDir: transaction.location.gitDir,
|
|
373
|
+
root: transaction.location.root,
|
|
374
|
+
base: input.baseCommit,
|
|
375
|
+
target: targetHead,
|
|
376
|
+
delivery: input.deliveryHead,
|
|
377
|
+
sources: selection.sources,
|
|
378
|
+
});
|
|
379
|
+
const mergeCommit = await createLandingCommit({
|
|
380
|
+
cwd: input.cwd,
|
|
381
|
+
target: targetHead,
|
|
382
|
+
delivery: input.deliveryHead,
|
|
383
|
+
tree: candidateTree,
|
|
384
|
+
contractId: input.contractId,
|
|
385
|
+
targetBranch: input.targetBranch,
|
|
386
|
+
});
|
|
387
|
+
await input.beforeCas?.(mergeCommit, targetHead);
|
|
388
|
+
let movement;
|
|
389
|
+
try {
|
|
390
|
+
await updateTargetRef(input.cwd, input.targetRef, mergeCommit, targetHead);
|
|
391
|
+
movement = { mergeCommit, oldHead: targetHead };
|
|
392
|
+
acceptedMovement = movement;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return { kind: "retry" };
|
|
396
|
+
}
|
|
397
|
+
const warnings = [];
|
|
398
|
+
if (replacements.length > 0) {
|
|
399
|
+
await transaction.replace(taskWritesFromSources(selection.sources));
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
await reconcileIndex(transaction.location.root, mergeCommit, selectedPaths);
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
lastPostCasError = error;
|
|
406
|
+
return { kind: "retry" };
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
await input.publishLedger?.(mergeCommit);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
lastPostCasError = error;
|
|
413
|
+
return { kind: "retry" };
|
|
414
|
+
}
|
|
415
|
+
const reconcileWarnings = await input.afterCas?.(mergeCommit, movement.oldHead);
|
|
416
|
+
warnings.push(...(reconcileWarnings ?? []));
|
|
417
|
+
return {
|
|
418
|
+
kind: "accepted",
|
|
419
|
+
mergeCommit,
|
|
420
|
+
oldHead: movement.oldHead,
|
|
421
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
422
|
+
evidence: selection.evidence,
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
if (result.kind === "retry")
|
|
426
|
+
continue;
|
|
427
|
+
if (result.kind === "idempotent") {
|
|
428
|
+
return {
|
|
429
|
+
mergeCommit: result.mergeCommit,
|
|
430
|
+
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
431
|
+
...(result.evidence ? { evidence: result.evidence } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
mergeCommit: result.mergeCommit,
|
|
436
|
+
oldHead: result.oldHead,
|
|
437
|
+
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
438
|
+
...(result.evidence ? { evidence: result.evidence } : {}),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (lastPostCasError !== undefined)
|
|
442
|
+
throw lastPostCasError;
|
|
443
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `target branch kept moving while claiming ${input.contractId}; last observed ${lastTargetHead}`);
|
|
444
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { rollbackOrphanTaskPointers } from "./task.js";
|
|
2
|
-
import { buildTaskSourceBoard } from "./
|
|
2
|
+
import { buildTaskSourceBoard } from "./source-board.js";
|
|
3
3
|
export async function inspectTaskDoctor(store, repository, facts) {
|
|
4
4
|
const orphans = facts.lockHeld ? [] : await rollbackOrphanTaskPointers(repository, facts.contractIds);
|
|
5
5
|
const sources = await store.read();
|
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runGitProcess } from "../../git/core.js";
|
|
2
2
|
const ANONYMOUS_TASK_CREATOR = "(anonymous)";
|
|
3
3
|
const TASK_ACTOR_ENV = "KEIYAKU_ACTOR";
|
|
4
4
|
const GIT_ACTOR_KEYS = ["keiyaku.actor", "user.name"];
|
|
5
|
-
function defaultRunGit(cwd, args, env) {
|
|
6
|
-
return
|
|
7
|
-
cwd,
|
|
8
|
-
encoding: "utf8",
|
|
9
|
-
env,
|
|
10
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
11
|
-
});
|
|
5
|
+
async function defaultRunGit(cwd, args, env) {
|
|
6
|
+
return await runGitProcess(cwd, args, { env: { ...env } });
|
|
12
7
|
}
|
|
13
8
|
function attribution(value) {
|
|
14
9
|
const trimmed = value?.trim();
|
|
15
10
|
return trimmed ? trimmed : undefined;
|
|
16
11
|
}
|
|
17
|
-
export function resolveTaskCreator(cwd, deps = {}) {
|
|
12
|
+
export async function resolveTaskCreator(cwd, deps = {}) {
|
|
18
13
|
const sourceEnv = deps.env ?? process.env;
|
|
19
14
|
const environmentActor = attribution(sourceEnv[TASK_ACTOR_ENV]);
|
|
20
15
|
if (environmentActor)
|
|
@@ -23,7 +18,7 @@ export function resolveTaskCreator(cwd, deps = {}) {
|
|
|
23
18
|
const runGit = deps.runGit ?? defaultRunGit;
|
|
24
19
|
for (const key of GIT_ACTOR_KEYS) {
|
|
25
20
|
try {
|
|
26
|
-
const result = runGit(cwd, ["config", "--get", key], gitEnv);
|
|
21
|
+
const result = await runGit(cwd, ["config", "--get", key], gitEnv);
|
|
27
22
|
if (!result.error && result.status === 0) {
|
|
28
23
|
const configuredActor = attribution(result.stdout);
|
|
29
24
|
if (configuredActor)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { observeGitRepository } from "
|
|
2
|
-
import { listContractIds } from "
|
|
3
|
-
import { contractBoundByEngineWorktree, stableRepoRoot } from "
|
|
1
|
+
import { observeGitRepository } from "../../git/branches.js";
|
|
2
|
+
import { listContractIds } from "../ledger.js";
|
|
3
|
+
import { contractBoundByEngineWorktree, stableRepoRoot } from "../worktree-path.js";
|
|
4
4
|
import { inspectTaskLock, openTaskStore } from "./task-git-store.js";
|
|
5
5
|
import { taskRepositoryFromStore } from "./task-store-repository.js";
|
|
6
6
|
import { withTaskWorktreePolicy } from "./task-worktree.js";
|