@astrosheep/keiyaku 2.9.9 → 2.9.11
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 +93 -174
- package/build/.tsbuildinfo +1 -1
- package/build/agents/harness/control-types.js +18 -1
- package/build/agents/harness/index.js +2 -0
- package/build/agents/harness/outcome-error.js +81 -0
- package/build/agents/harness/projection.js +2 -1
- package/build/agents/harness/router.js +5 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
- package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
- package/build/agents/providers/claude-agent-sdk/session.js +19 -5
- package/build/agents/providers/codex-app-server/adapter.js +11 -6
- package/build/agents/providers/codex-app-server/events.js +67 -6
- package/build/agents/providers/codex-app-server/registration.js +11 -1
- package/build/agents/providers/opencode-sdk/adapter.js +8 -1
- package/build/agents/providers/opencode-sdk/events.js +24 -2
- package/build/agents/providers/opencode-sdk/registration.js +11 -1
- package/build/agents/providers/pi/checkpoint.js +9 -1
- package/build/agents/providers/pi/events.js +2 -0
- package/build/agents/providers/pi/registration.js +14 -1
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/akuma/meta.js +3 -3
- package/build/cli/commands/akuma/catalog.js +2 -2
- package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
- package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +5 -4
- package/build/cli/commands/contract/arc/handler.js +5 -4
- package/build/cli/commands/contract/audit/handler.js +2 -2
- package/build/cli/commands/contract/bind/handler.js +12 -3
- package/build/cli/commands/contract/bind/meta.js +45 -15
- package/build/cli/commands/contract/forfeit/handler.js +9 -5
- package/build/cli/commands/contract/log/handler.js +2 -2
- package/build/cli/commands/contract/petition/handler.js +34 -7
- package/build/cli/commands/contract/renew/handler.js +7 -7
- package/build/cli/commands/contract/renew/meta.js +3 -3
- package/build/cli/commands/metadata.js +10 -6
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -0
- package/build/cli/commands/projection/history/handler.js +21 -0
- package/build/cli/commands/projection/history/meta.js +9 -0
- package/build/cli/commands/projection/shared.js +3 -4
- package/build/cli/commands/projection/status/handler.js +3 -3
- package/build/cli/commands/projection/wait/handler.js +20 -11
- package/build/cli/commands/task/add/handler.js +14 -5
- package/build/cli/commands/task/add/meta.js +2 -2
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/done/handler.js +1 -4
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/hold/handler.js +3 -0
- package/build/cli/commands/task/hold/meta.js +8 -0
- package/build/cli/commands/task/log/handler.js +1 -1
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/resume/handler.js +3 -0
- package/build/cli/commands/task/resume/meta.js +8 -0
- package/build/cli/commands/task/shared.js +39 -14
- package/build/cli/commands/task/show/handler.js +1 -1
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/handler.js +1 -4
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +11 -3
- package/build/cli/commands/task/update/meta.js +3 -3
- package/build/cli/completion.js +3 -2
- package/build/cli/flags.js +51 -14
- package/build/cli/help.js +4 -1
- package/build/cli/index.js +40 -25
- package/build/cli/parse-flags.js +40 -7
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +2 -3
- package/build/cli/parse.js +40 -6
- package/build/cli/projection-address.js +12 -10
- package/build/cli/render/amendment-warning.js +8 -0
- package/build/cli/render/arc.js +6 -2
- package/build/cli/render/audit.js +400 -81
- package/build/cli/render/kanshi.js +6 -1
- package/build/cli/render/lifecycle.js +56 -0
- package/build/cli/render/misc.js +24 -28
- package/build/cli/render/petition.js +5 -0
- package/build/cli/render/projection-activity.js +3 -2
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +9 -16
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +3 -5
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +2 -0
- package/build/config/env.js +23 -0
- package/build/config/settings/schema.js +3 -1
- package/build/core/address-carrier.js +89 -0
- package/build/core/addressing.js +255 -196
- package/build/core/amend.js +115 -38
- package/build/core/amendment-criteria.js +17 -0
- package/build/core/amendment-warning.js +10 -0
- package/build/core/arc.js +184 -83
- package/build/core/audit/candidate.js +110 -9
- package/build/core/audit/coordinates.js +120 -65
- package/build/core/audit/evidence.js +19 -23
- package/build/core/audit/facade.js +34 -11
- package/build/core/audit/report.js +5 -5
- package/build/core/bind-reconciliation.js +390 -0
- package/build/core/bind-workspace.js +12 -20
- package/build/core/bind.js +242 -273
- package/build/core/call/call.js +1 -0
- package/build/core/call/context.js +46 -71
- package/build/core/call/prompt.js +6 -3
- package/build/core/context.js +49 -35
- package/build/core/contract-carrier-runtime.js +408 -0
- package/build/core/contract-carrier.js +270 -0
- package/build/core/contract-view.js +33 -27
- package/build/core/contract.js +5 -1
- package/build/core/derived-replay.js +207 -0
- package/build/core/draft.js +125 -57
- package/build/core/forfeit.js +113 -56
- package/build/core/identifier-slug.js +63 -13
- package/build/core/ids.js +4 -4
- package/build/core/lifecycle-history.js +35 -0
- package/build/core/lifecycle-recovery.js +117 -0
- package/build/core/lifecycle-runner.js +377 -0
- package/build/core/log.js +37 -11
- package/build/core/outcome-base.js +181 -0
- package/build/core/process-group.js +139 -0
- package/build/core/projection/generation/database.js +82 -7
- package/build/core/projection/generation/model.js +9 -36
- package/build/core/projection/generation/projection-generation-continuation.js +10 -22
- package/build/core/projection/generation/projection-generation-execution.js +6 -5
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +27 -8
- package/build/core/projection/projection-alias.js +23 -10
- package/build/core/projection/projection-core.js +21 -7
- package/build/core/projection/projection-execution-observer.js +91 -39
- package/build/core/projection/projection-history.js +474 -0
- package/build/core/projection/projection-runner-lock.js +12 -0
- package/build/core/projection/projection-status-observation.js +365 -0
- package/build/core/projection/projection-status.js +21 -358
- package/build/core/projection/projection-terminal-failure.js +13 -8
- package/build/core/projection/projection-wait.js +7 -2
- package/build/core/projection/tell/launch-store.js +244 -0
- package/build/core/projection/tell/model.js +3 -3
- package/build/core/projection/tell/store.js +70 -118
- package/build/core/registry.js +22 -87
- package/build/core/renew-build.js +424 -0
- package/build/core/renew-plan.js +239 -0
- package/build/core/renew-rewrite.js +77 -0
- package/build/core/renew.js +295 -520
- package/build/core/repository-ledger/accepted-fold-read.js +19 -0
- package/build/core/repository-ledger/accepted-tail.js +116 -0
- package/build/core/repository-ledger/admission.js +38 -0
- package/build/core/repository-ledger/authoritative-transaction.js +1 -0
- package/build/core/repository-ledger/claim-evidence.js +147 -0
- package/build/core/repository-ledger/codec.js +297 -0
- package/build/core/repository-ledger/current-state-store.js +853 -0
- package/build/core/repository-ledger/fold-repository.js +375 -0
- package/build/core/repository-ledger/fold.js +248 -0
- package/build/core/repository-ledger/git-object-store.js +243 -0
- package/build/core/repository-ledger/history-read.js +15 -0
- package/build/core/repository-ledger/incremental-read.js +153 -0
- package/build/core/repository-ledger/index.js +15 -0
- package/build/core/repository-ledger/inventory-plan.js +75 -0
- package/build/core/repository-ledger/inventory.js +310 -0
- package/build/core/repository-ledger/publication-admission.js +125 -0
- package/build/core/repository-ledger/publication.js +353 -0
- package/build/core/repository-ledger/read-model.js +469 -0
- package/build/core/repository-ledger/transaction-membership.js +78 -0
- package/build/core/repository-ledger/traversal.js +404 -0
- package/build/core/repository-ledger/write-transaction.js +339 -0
- package/build/core/seal.js +168 -161
- package/build/core/settlement/claim-delivery.js +42 -215
- package/build/core/settlement/claim.js +235 -153
- package/build/core/settlement/index.js +4 -4
- package/build/core/settlement/petition-claim-gates.js +52 -33
- package/build/core/settlement/petition-forfeit.js +12 -5
- package/build/core/settlement/petition-head-guard.js +8 -5
- package/build/core/settlement/petition-preview.js +68 -56
- package/build/core/settlement/petition-window.js +50 -0
- package/build/core/settlement/petition.js +252 -131
- package/build/core/settlement/queue-read-model.js +72 -95
- package/build/core/settlement/queue-seat-allocation.js +35 -126
- package/build/core/settlement/queue.js +1 -1
- package/build/core/settlement/settlement.js +172 -80
- package/build/core/settlement/verdict.js +112 -66
- package/build/core/settlement/verification-coordination.js +305 -0
- package/build/core/settlement/verification-supervisor.js +275 -0
- package/build/core/settlement/verification.js +398 -31
- package/build/core/status/board.js +114 -119
- package/build/core/status/lifecycle.js +7 -80
- package/build/core/structured-query.js +23 -0
- package/build/core/target-ref.js +1 -1
- package/build/core/task/board.js +59 -24
- package/build/core/task/commands.js +181 -33
- package/build/core/task/coordinate.js +56 -0
- package/build/core/task/document.js +25 -9
- package/build/core/task/index.js +7 -4
- package/build/core/task/query.js +17 -27
- package/build/core/task/settlement-git.js +164 -179
- package/build/core/task/settlement-policy.js +15 -11
- package/build/core/task/source-board.js +31 -13
- package/build/core/task/task-bind-preparation.js +193 -0
- package/build/core/task/task-contract.js +102 -193
- package/build/core/task/task-file-transaction.js +171 -0
- package/build/core/task/task-git-runtime.js +76 -14
- package/build/core/task/task-git-store.js +68 -169
- package/build/core/task/task-store-repository.js +13 -6
- package/build/core/task/task-worktree.js +6 -33
- package/build/core/task/task.js +1 -1
- package/build/core/task/tree.js +3 -3
- package/build/core/task/validation.js +4 -3
- package/build/core/transcripts.js +93 -1
- package/build/core/worktree-bootstrap.js +44 -36
- package/build/core/worktree-path.js +210 -77
- package/build/flow-error.js +7 -7
- package/build/generated/version.js +2 -2
- package/build/git/core.js +76 -13
- package/build/git/refs.js +168 -46
- package/build/git/streaming-batch.js +73 -13
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +3 -3
- package/skills/keiyaku-task/SKILL.md +4 -2
- package/skills/keiyaku-workflow/SKILL.md +88 -203
- package/build/core/entry.js +0 -305
- package/build/core/ledger-batch.js +0 -194
- package/build/core/ledger.js +0 -84
- package/build/core/projection/leash.js +0 -87
- package/build/core/ref-log.js +0 -119
- package/build/core/renew-session.js +0 -128
- package/build/core/status/ledger-batch.js +0 -1
- package/build/core/status/reconciliation.js +0 -137
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { runGitProcess } from "../../git/core.js";
|
|
3
|
+
import { assertGitObservation, requireGitRepository } from "../../git/branches.js";
|
|
4
|
+
import { inspectCurrentBranch, createTaskBoardCommit, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskSourcesAtCommit, taskWritesFromSources, } from "./settlement-git.js";
|
|
5
|
+
import { taskCoordinateFromSourcePath } from "./coordinate.js";
|
|
6
|
+
import { advancedTaskUpdatedAt } from "./model.js";
|
|
7
|
+
import { requireHealthySettlementBoard, selectReadyTasksForBind } from "./settlement-policy.js";
|
|
8
|
+
import { buildTaskSourceBoard } from "./source-board.js";
|
|
9
|
+
import { prepareTaskDocumentReplacements, taskBoardFromSourceBoard } from "./task-store-repository.js";
|
|
10
|
+
import { withLockHeldTaskBoard, } from "./task-git-store.js";
|
|
11
|
+
function linkedTaskDocument(task, contractId, at) {
|
|
12
|
+
return {
|
|
13
|
+
id: task.id,
|
|
14
|
+
namespace: task.namespace,
|
|
15
|
+
coordinate: task.coordinate,
|
|
16
|
+
title: task.title,
|
|
17
|
+
state: "open",
|
|
18
|
+
pri: task.pri,
|
|
19
|
+
needs: [...task.needs],
|
|
20
|
+
parent: task.parent,
|
|
21
|
+
from: [...task.from],
|
|
22
|
+
supersedes: [...task.supersedes],
|
|
23
|
+
relatesTo: [...task.relatesTo],
|
|
24
|
+
createdAt: task.createdAt,
|
|
25
|
+
updatedAt: advancedTaskUpdatedAt(task, at),
|
|
26
|
+
creator: task.creator,
|
|
27
|
+
contractId,
|
|
28
|
+
body: task.body,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function selectedEvidence(board, selected) {
|
|
32
|
+
return selected.map((task) => ({
|
|
33
|
+
taskId: task.coordinate,
|
|
34
|
+
needs: task.needs.map((taskId) => {
|
|
35
|
+
const state = board.byId.get(taskId)?.state;
|
|
36
|
+
if (state !== "done" && state !== "drop") {
|
|
37
|
+
throw new FlowError("INTERNAL_STATE", `ready task ${task.coordinate} has unsatisfied need ${taskId}`);
|
|
38
|
+
}
|
|
39
|
+
return { taskId, state };
|
|
40
|
+
}),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
async function prepareLockedTaskBind(transaction, input, deps) {
|
|
44
|
+
if (transaction.location.mode !== "git") {
|
|
45
|
+
if (transaction.location.capability === "unavailable") {
|
|
46
|
+
assertGitObservation({ capability: "unavailable" }, "task bind");
|
|
47
|
+
}
|
|
48
|
+
throw new FlowError("NOT_GIT_REPO", "task bind requires Git task storage");
|
|
49
|
+
}
|
|
50
|
+
const board = requireHealthySettlementBoard(taskBoardFromSourceBoard(buildTaskSourceBoard(transaction.sources)));
|
|
51
|
+
const selected = selectReadyTasksForBind(board, input.taskIds);
|
|
52
|
+
if (selected.length === 0) {
|
|
53
|
+
throw new FlowError("INVALID_TARGET", "task bind requires at least one selected task");
|
|
54
|
+
}
|
|
55
|
+
const attempt = await input.prepare(selected);
|
|
56
|
+
const evidence = selectedEvidence(board, selected);
|
|
57
|
+
const linked = prepareTaskDocumentReplacements(transaction.sources, selected.map((task) => linkedTaskDocument(task, attempt.contractId, attempt.at)));
|
|
58
|
+
const linkedIds = new Set(selected.map((task) => task.coordinate));
|
|
59
|
+
const linkedSources = linked.sources.filter((source) => linkedIds.has(taskCoordinateFromSourcePath(source.path).coordinate));
|
|
60
|
+
const originalSources = transaction.sources.filter((source) => linkedIds.has(taskCoordinateFromSourcePath(source.path).coordinate));
|
|
61
|
+
if (linkedSources.length !== selected.length) {
|
|
62
|
+
throw new FlowError("INTERNAL_STATE", "task bind preparation did not resolve the exact selected task pathset");
|
|
63
|
+
}
|
|
64
|
+
if (originalSources.length !== selected.length) {
|
|
65
|
+
throw new FlowError("INTERNAL_STATE", "task bind preparation did not retain the exact original task pathset");
|
|
66
|
+
}
|
|
67
|
+
const branch = await inspectCurrentBranch(transaction.location.root);
|
|
68
|
+
const newHead = await (deps.createTaskBoardCommit ?? createTaskBoardCommit)({
|
|
69
|
+
cwd: transaction.location.root,
|
|
70
|
+
gitDir: transaction.location.gitDir,
|
|
71
|
+
root: transaction.location.root,
|
|
72
|
+
parent: branch.head,
|
|
73
|
+
sources: linkedSources,
|
|
74
|
+
subject: selected.length === 1
|
|
75
|
+
? `Bind task ${selected[0].coordinate} to contract ${attempt.contractId}`
|
|
76
|
+
: `Bind ${selected.length} tasks to contract ${attempt.contractId}`,
|
|
77
|
+
});
|
|
78
|
+
const taskBoard = { ref: branch.ref, expectedHead: branch.head, head: newHead };
|
|
79
|
+
return {
|
|
80
|
+
boardRef: taskBoard.ref,
|
|
81
|
+
expectedHead: taskBoard.expectedHead,
|
|
82
|
+
newHead,
|
|
83
|
+
entryBase: newHead,
|
|
84
|
+
taskIds: selected.map((task) => task.coordinate),
|
|
85
|
+
selected: evidence,
|
|
86
|
+
taskBoard,
|
|
87
|
+
replay: {
|
|
88
|
+
paths: selected.map((task) => `.keiyaku/tasks/${task.coordinate}.md`).sort(),
|
|
89
|
+
linkedSources,
|
|
90
|
+
originalSources,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Hold the repository task lock across build and the caller-owned ref transaction. */
|
|
95
|
+
export async function withPreparedTaskBind(input, deps = {}) {
|
|
96
|
+
await requireGitRepository(input.cwd, "task bind");
|
|
97
|
+
return await withLockHeldTaskBoard(input.cwd, async (transaction) => {
|
|
98
|
+
const preparation = await prepareLockedTaskBind(transaction, input, deps);
|
|
99
|
+
const result = await input.transact(preparation, transaction);
|
|
100
|
+
return { preparation, result };
|
|
101
|
+
}, { signal: input.signal });
|
|
102
|
+
}
|
|
103
|
+
/** Complete the prepared task projection while its authority transaction still owns the task lock. */
|
|
104
|
+
export async function replayPreparedTaskBindLocked(transaction, prepared) {
|
|
105
|
+
if (transaction.location.mode !== "git")
|
|
106
|
+
throw new FlowError("NOT_GIT_REPO", "task bind replay requires Git task storage");
|
|
107
|
+
const currentHead = await refHead(transaction.location.root, prepared.boardRef);
|
|
108
|
+
const sources = await taskSourcesAtCommit(transaction.location.root, transaction.location.root, currentHead, prepared.replay.paths);
|
|
109
|
+
if (!sources)
|
|
110
|
+
throw new FlowError("INTERNAL_STATE", "committed task bind paths are missing from board authority");
|
|
111
|
+
await transaction.replace(taskWritesFromSources(sources));
|
|
112
|
+
await reconcileTaskPathsetIndex(transaction.location.root, currentHead, prepared.replay.paths);
|
|
113
|
+
}
|
|
114
|
+
export async function replayPreparedTaskBind(cwd, prepared) {
|
|
115
|
+
await withLockHeldTaskBoard(cwd, async (transaction) => {
|
|
116
|
+
await replayPreparedTaskBindLocked(transaction, prepared);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function projectionMatchesCommit(cwd, commit, paths) {
|
|
120
|
+
const cached = await runGitProcess(cwd, ["diff", "--quiet", "--cached", commit, "--", ...paths]);
|
|
121
|
+
if (cached.error || (cached.status !== 0 && cached.status !== 1))
|
|
122
|
+
return false;
|
|
123
|
+
if (cached.status !== 0)
|
|
124
|
+
return false;
|
|
125
|
+
const worktree = await runGitProcess(cwd, ["diff", "--quiet", "--", ...paths]);
|
|
126
|
+
return !worktree.error && worktree.status === 0;
|
|
127
|
+
}
|
|
128
|
+
/** Rebuild the selected task working-tree projection from the authoritative bind base. */
|
|
129
|
+
export async function replayCommittedTaskBind(cwd, commit, taskIds) {
|
|
130
|
+
const paths = taskIds.map((taskId) => `.keiyaku/tasks/${taskId}.md`).sort();
|
|
131
|
+
if (await projectionMatchesCommit(cwd, commit, paths))
|
|
132
|
+
return "already-present";
|
|
133
|
+
return await withLockHeldTaskBoard(cwd, async (transaction) => {
|
|
134
|
+
if (transaction.location.mode !== "git")
|
|
135
|
+
throw new FlowError("NOT_GIT_REPO", "task bind replay requires Git task storage");
|
|
136
|
+
const sources = await taskSourcesAtCommit(transaction.location.root, transaction.location.root, commit, paths);
|
|
137
|
+
if (!sources)
|
|
138
|
+
throw new FlowError("INTERNAL_STATE", "committed task bind paths are missing from board authority");
|
|
139
|
+
const current = new Map(transaction.sources.map((source) => [source.path, source.text]));
|
|
140
|
+
const alreadyPresent = sources.every((source) => current.get(source.path) === source.text);
|
|
141
|
+
if (!alreadyPresent)
|
|
142
|
+
await transaction.replace(taskWritesFromSources(sources));
|
|
143
|
+
await reconcileTaskPathsetIndex(transaction.location.root, commit, paths);
|
|
144
|
+
return alreadyPresent ? "already-present" : "created";
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** Reopen task projections from the authoritative parent of a task-selected bind base. */
|
|
148
|
+
export async function replayCommittedTaskBindForfeit(cwd, commit, boardRef, taskIds) {
|
|
149
|
+
return await withLockHeldTaskBoard(cwd, async (transaction) => {
|
|
150
|
+
if (transaction.location.mode !== "git")
|
|
151
|
+
throw new FlowError("NOT_GIT_REPO", "task forfeit replay requires Git task storage");
|
|
152
|
+
const parentResult = await runGitProcess(transaction.location.root, ["rev-parse", "--verify", `${commit}^`]);
|
|
153
|
+
if (parentResult.status !== 0 || parentResult.error) {
|
|
154
|
+
throw new FlowError("INTERNAL_STATE", `committed task bind base ${commit} has no readable parent`, { cause: parentResult.error });
|
|
155
|
+
}
|
|
156
|
+
const parent = parentResult.stdout.trim();
|
|
157
|
+
const paths = taskIds.map((taskId) => `.keiyaku/tasks/${taskId}.md`).sort();
|
|
158
|
+
const originalSources = await taskSourcesAtCommit(transaction.location.root, transaction.location.root, parent, paths);
|
|
159
|
+
if (!originalSources)
|
|
160
|
+
throw new FlowError("INTERNAL_STATE", "committed task bind parent is missing selected task paths");
|
|
161
|
+
const current = new Map(transaction.sources.map((source) => [source.path, source.text]));
|
|
162
|
+
const alreadyPresent = originalSources.every((source) => current.get(source.path) === source.text);
|
|
163
|
+
if (alreadyPresent)
|
|
164
|
+
return "already-present";
|
|
165
|
+
await transaction.replace(taskWritesFromSources(originalSources));
|
|
166
|
+
await publishForwardTaskBoardCommit({
|
|
167
|
+
cwd: transaction.location.root,
|
|
168
|
+
ref: boardRef,
|
|
169
|
+
gitDir: transaction.location.gitDir,
|
|
170
|
+
root: transaction.location.root,
|
|
171
|
+
sources: originalSources,
|
|
172
|
+
paths,
|
|
173
|
+
subject: `Reopen ${taskIds.length === 1 ? `task ${taskIds[0]}` : `${taskIds.length} tasks`} after bind failure`,
|
|
174
|
+
}, {});
|
|
175
|
+
return "created";
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export async function replayPreparedTaskBindForfeit(cwd, prepared) {
|
|
179
|
+
await withLockHeldTaskBoard(cwd, async (transaction) => {
|
|
180
|
+
if (transaction.location.mode !== "git")
|
|
181
|
+
throw new FlowError("NOT_GIT_REPO", "task forfeit replay requires Git task storage");
|
|
182
|
+
await transaction.replace(taskWritesFromSources(prepared.replay.originalSources));
|
|
183
|
+
await publishForwardTaskBoardCommit({
|
|
184
|
+
cwd: transaction.location.root,
|
|
185
|
+
ref: prepared.boardRef,
|
|
186
|
+
gitDir: transaction.location.gitDir,
|
|
187
|
+
root: transaction.location.root,
|
|
188
|
+
sources: prepared.replay.originalSources,
|
|
189
|
+
paths: prepared.replay.paths,
|
|
190
|
+
subject: `Reopen ${prepared.taskIds.length === 1 ? `task ${prepared.taskIds[0]}` : `${prepared.taskIds.length} tasks`} after bind failure`,
|
|
191
|
+
}, {});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
import { assertGitObservation, requireGitRepository } from "../../git/branches.js";
|
|
3
|
-
import { withLockHeldTaskBoard } from "./task-git-store.js";
|
|
3
|
+
import { assertTaskBoardGeneration, withLockHeldTaskBoard, } from "./task-git-store.js";
|
|
4
4
|
import { prepareTaskDocumentReplacements, taskBoardFromSourceBoard, taskRepositoryFromLockedBoard } from "./task-store-repository.js";
|
|
5
|
-
import { serializeTaskDocument } from "./document.js";
|
|
6
5
|
import { buildTaskSourceBoard } from "./source-board.js";
|
|
7
|
-
import {
|
|
6
|
+
import { formatTaskCoordinate } from "./coordinate.js";
|
|
7
|
+
import { createTaskBoardCommit, publishTaskMutationCommit, inspectCurrentBranch, publishForwardTaskBoardCommit, reconcileTaskPathsetIndex, refHead, taskContractAtCommit, taskSourcesAtCommit, taskWritesFromSources, trackedTaskPaths, updateBranch, } from "./settlement-git.js";
|
|
8
8
|
import { contractPointerDocument, doneTaskReplacements, reopenedTaskDocument, requireHealthySettlementBoard, requireSettledTask, selectReadyTasksForBind, tasksForContract, } from "./settlement-policy.js";
|
|
9
9
|
import { stableRepoRoot } from "../worktree-path.js";
|
|
10
10
|
/**
|
|
@@ -38,7 +38,7 @@ function ownedTaskPaths(board, taskIds) {
|
|
|
38
38
|
for (const taskId of taskIds) {
|
|
39
39
|
let current = board.byId.get(taskId);
|
|
40
40
|
while (current) {
|
|
41
|
-
paths.add(taskPath(current.
|
|
41
|
+
paths.add(taskPath(current.coordinate));
|
|
42
42
|
current = current.parent === null ? undefined : board.byId.get(current.parent);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -83,18 +83,18 @@ function repositoryForSettlement(transaction, sources) {
|
|
|
83
83
|
async function clearAttemptedPointers(input) {
|
|
84
84
|
return input.repository.mutate((board) => {
|
|
85
85
|
const replacements = input.tasks.flatMap((task) => {
|
|
86
|
-
const current = requireSettledTask(board, task.
|
|
86
|
+
const current = requireSettledTask(board, task.coordinate);
|
|
87
87
|
return current.contractId === input.contractId
|
|
88
88
|
? [contractPointerDocument(current, undefined, input.updatedAt)]
|
|
89
89
|
: [];
|
|
90
90
|
});
|
|
91
|
-
return { replacements, select: () => replacements.map((replacement) => replacement.id) };
|
|
91
|
+
return { replacements, select: () => replacements.map((replacement) => replacement.coordinate ?? replacement.id) };
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
async function replaceTaskDocuments(repository, replacements) {
|
|
95
95
|
return repository.mutate(() => ({
|
|
96
96
|
replacements,
|
|
97
|
-
select: (resulting) => replacements.map((replacement) => requireSettledTask(resulting, replacement.id)),
|
|
97
|
+
select: (resulting) => replacements.map((replacement) => requireSettledTask(resulting, replacement.coordinate ?? replacement.id)),
|
|
98
98
|
}));
|
|
99
99
|
}
|
|
100
100
|
async function restorePreAdmissionTaskMutations(input) {
|
|
@@ -149,9 +149,9 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
149
149
|
if (taskBound && selectedTasks.length > 0) {
|
|
150
150
|
await replaceTaskDocuments(repository, selectedTasks.map((task) => contractPointerDocument(task, contractId, updatedAt)));
|
|
151
151
|
}
|
|
152
|
-
const taskIds = taskBound ? selectedTasks.map((t) => t.
|
|
152
|
+
const taskIds = taskBound ? selectedTasks.map((t) => t.coordinate) : undefined;
|
|
153
153
|
const selectedPaths = ownedTaskPaths(await repository.read(), taskIds);
|
|
154
|
-
const mutationPaths = taskBound ? selectedTasks.map((t) => taskPath(t.
|
|
154
|
+
const mutationPaths = taskBound ? selectedTasks.map((t) => taskPath(t.coordinate)) : [];
|
|
155
155
|
const selectCurrentSettlement = async () => selectSettlementSources({
|
|
156
156
|
root: transaction.location.root,
|
|
157
157
|
sources: repository.sources(),
|
|
@@ -166,7 +166,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
166
166
|
try {
|
|
167
167
|
const taskLabel = taskBound
|
|
168
168
|
? selectedTasks.length === 1
|
|
169
|
-
? `task ${selectedTasks[0].
|
|
169
|
+
? `task ${selectedTasks[0].coordinate}`
|
|
170
170
|
: `${selectedTasks.length} tasks`
|
|
171
171
|
: "";
|
|
172
172
|
firstCommit = await makeBoardCommit({
|
|
@@ -182,7 +182,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
182
182
|
}
|
|
183
183
|
catch (error) {
|
|
184
184
|
if (taskBound) {
|
|
185
|
-
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.
|
|
185
|
+
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.coordinate), previousSources });
|
|
186
186
|
}
|
|
187
187
|
throw error;
|
|
188
188
|
}
|
|
@@ -192,7 +192,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
192
192
|
catch (error) {
|
|
193
193
|
if (taskBound) {
|
|
194
194
|
const concurrentHead = await refHead(hubCwd, branch.ref);
|
|
195
|
-
const concurrentMatches = await Promise.all(selectedTasks.map(async (task) => (await taskContractAtCommit(hubCwd, concurrentHead, task.
|
|
195
|
+
const concurrentMatches = await Promise.all(selectedTasks.map(async (task) => (await taskContractAtCommit(hubCwd, concurrentHead, task.coordinate)) === contractId));
|
|
196
196
|
if (concurrentMatches.every(Boolean)) {
|
|
197
197
|
await rollForwardClearAttemptedPointer({
|
|
198
198
|
repository, selected: selectedTasks, contractId, hubCwd,
|
|
@@ -202,7 +202,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
204
|
else {
|
|
205
|
-
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.
|
|
205
|
+
await restorePreAdmissionTaskMutations({ transaction, taskIds: selectedTasks.map((task) => task.coordinate), previousSources });
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
throw error;
|
|
@@ -228,7 +228,7 @@ export async function settleTaskBoardForBind(input, deps = {}) {
|
|
|
228
228
|
boardCommit: firstCommit,
|
|
229
229
|
contractId,
|
|
230
230
|
ledger,
|
|
231
|
-
...(taskBound && selectedTasks.length > 0 ? { tasks: selectedTasks.map((t) => requireSettledTask(boardAfter, t.
|
|
231
|
+
...(taskBound && selectedTasks.length > 0 ? { tasks: selectedTasks.map((t) => requireSettledTask(boardAfter, t.coordinate)) } : {}),
|
|
232
232
|
evidence: selection.evidence,
|
|
233
233
|
};
|
|
234
234
|
}
|
|
@@ -256,207 +256,116 @@ export async function reconcileTaskForfeit(input) {
|
|
|
256
256
|
}
|
|
257
257
|
throw new FlowError("NOT_GIT_REPO", "task forfeit requires Git task storage");
|
|
258
258
|
}
|
|
259
|
+
assertTaskBoardGeneration(transaction, input.expectedGeneration);
|
|
259
260
|
const repository = taskRepositoryFromLockedBoard(transaction);
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
const branch = await inspectCurrentBranch(transaction.location.root);
|
|
262
|
+
const trackedPaths = await trackedTaskPaths(transaction.location.root, branch.head);
|
|
263
|
+
const branchSources = await taskSourcesAtCommit(transaction.location.root, transaction.location.root, branch.head, trackedPaths);
|
|
264
|
+
if (!branchSources)
|
|
265
|
+
throw new FlowError("INTERNAL_STATE", `task branch ${branch.head} lost tracked task sources`);
|
|
266
|
+
const branchBoard = requireHealthySettlementBoard(taskBoardFromSourceBoard(buildTaskSourceBoard(branchSources)));
|
|
267
|
+
const matched = tasksForContract(branchBoard, input.contractId);
|
|
268
|
+
const worktreeMatched = tasksForContract(await repository.read(), input.contractId);
|
|
269
|
+
if (matched.length === 0 && worktreeMatched.length === 0) {
|
|
270
|
+
input.observeEffect?.({ effect: "task-source-reconciliation", status: "succeeded", taskCount: 0, detail: "current" });
|
|
271
|
+
input.observeEffect?.({ effect: "task-reconciliation", status: "succeeded", commit: branch.head, detail: "current" });
|
|
272
|
+
input.observeEffect?.({ effect: "task-index-reconciliation", status: "succeeded", commit: branch.head });
|
|
273
|
+
return { ledger: await input.publishLedger() };
|
|
274
|
+
}
|
|
264
275
|
const reopenedDocuments = matched.map((task) => reopenedTaskDocument(task, updatedAt));
|
|
265
|
-
const
|
|
266
|
-
const
|
|
276
|
+
const prepared = prepareTaskDocumentReplacements(branchSources, reopenedDocuments);
|
|
277
|
+
const projectionIds = matched.length > 0 ? matched.map((task) => task.coordinate) : worktreeMatched.map((task) => task.coordinate);
|
|
278
|
+
const branchSourcesByPath = new Map(branchSources.map((source) => [sourcePath(transaction.location.root, source), source]));
|
|
279
|
+
const writes = matched.length > 0
|
|
280
|
+
? prepared.writes
|
|
281
|
+
: taskWritesFromSources(projectionIds.map((id) => {
|
|
282
|
+
const source = branchSourcesByPath.get(taskPath(id));
|
|
283
|
+
if (!source)
|
|
284
|
+
throw new FlowError("INTERNAL_STATE", `task ${id} is missing from authoritative task branch ${branch.head}`);
|
|
285
|
+
return source;
|
|
286
|
+
}));
|
|
287
|
+
const reopened = projectionIds.map((id) => requireSettledTask(prepared.board, id));
|
|
288
|
+
const taskLabel = projectionIds.length === 1 ? `task ${projectionIds[0]}` : `${projectionIds.length} tasks`;
|
|
289
|
+
let indexSucceeded = false;
|
|
267
290
|
await publishTaskMutationCommit({
|
|
268
291
|
cwd: transaction.location.root,
|
|
269
292
|
gitDir: transaction.location.gitDir,
|
|
270
|
-
writes
|
|
293
|
+
writes,
|
|
271
294
|
subject: `Reopen ${taskLabel} after forfeit of ${input.contractId}`,
|
|
295
|
+
observeEffect: (effect) => {
|
|
296
|
+
input.observeEffect?.(effect);
|
|
297
|
+
if (effect.effect === "task-index-reconciliation" && effect.status === "succeeded")
|
|
298
|
+
indexSucceeded = true;
|
|
299
|
+
},
|
|
272
300
|
});
|
|
301
|
+
if (!indexSucceeded)
|
|
302
|
+
throw new FlowError("INTERNAL_STATE", "task index reconciliation failed before forfeit ledger publication");
|
|
303
|
+
try {
|
|
304
|
+
await transaction.replace(writes);
|
|
305
|
+
input.observeEffect?.({ effect: "task-source-reconciliation", status: "succeeded", taskCount: reopened.length, detail: "materialized" });
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
input.observeEffect?.({ effect: "task-source-reconciliation", status: "failed", error });
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
const ledger = await input.publishLedger();
|
|
273
312
|
return { ledger, tasks: reopened };
|
|
274
313
|
});
|
|
275
314
|
}
|
|
276
|
-
/**
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
* one complete snapshot of the common hub board held under its repository lock.
|
|
280
|
-
*/
|
|
281
|
-
export async function settleTaskBoardForLanding(input, deps = {}) {
|
|
282
|
-
const updatedAt = new Date().toISOString();
|
|
283
|
-
await requireGitRepository(input.cwd, "task landing");
|
|
315
|
+
/** Replay the claim's folded task selection after target+ledger authority commits. */
|
|
316
|
+
export async function replayClaimTasks(input) {
|
|
317
|
+
await requireGitRepository(input.cwd, "task claim replay");
|
|
284
318
|
const hubRoot = await stableRepoRoot(input.cwd);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
let acceptedMovement;
|
|
290
|
-
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
291
|
-
const result = await withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
292
|
-
if (transaction.location.mode !== "git") {
|
|
293
|
-
if (transaction.location.capability === "unavailable") {
|
|
294
|
-
assertGitObservation({ capability: "unavailable" }, "task landing");
|
|
295
|
-
}
|
|
296
|
-
throw new FlowError("NOT_GIT_REPO", "task landing requires Git task storage");
|
|
319
|
+
return await withLockHeldTaskBoard(hubRoot, async (transaction) => {
|
|
320
|
+
if (transaction.location.mode !== "git") {
|
|
321
|
+
if (transaction.location.capability === "unavailable") {
|
|
322
|
+
assertGitObservation({ capability: "unavailable" }, "task claim replay");
|
|
297
323
|
}
|
|
298
|
-
|
|
299
|
-
|
|
324
|
+
throw new FlowError("NOT_GIT_REPO", "task claim replay requires Git task storage");
|
|
325
|
+
}
|
|
326
|
+
const hubBranch = await inspectCurrentBranch(transaction.location.root);
|
|
327
|
+
const materializeHub = hubBranch.ref === input.targetRef;
|
|
328
|
+
const selectedPaths = input.taskIds.map(taskPath);
|
|
329
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
300
330
|
const targetHead = await refHead(input.cwd, input.targetRef);
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
ownedPaths: selectedPaths,
|
|
315
|
-
settledPaths: ownership.taskIds.map(taskPath),
|
|
316
|
-
});
|
|
317
|
-
const deliveryAlreadyAccepted = ownership.taskIds.length === 0
|
|
318
|
-
&& await containsCommit(input.cwd, input.deliveryHead, targetHead);
|
|
319
|
-
if (acceptedLanding !== undefined || deliveryAlreadyAccepted) {
|
|
320
|
-
const mergeCommit = acceptedLanding ?? targetHead;
|
|
321
|
-
const movement = acceptedMovement?.mergeCommit === targetHead ? acceptedMovement : undefined;
|
|
322
|
-
const authoritativeSources = selectedPaths.length > 0
|
|
323
|
-
? await taskSourcesAtCommit(input.cwd, transaction.location.root, targetHead, selectedPaths)
|
|
324
|
-
: [];
|
|
325
|
-
if (!authoritativeSources) {
|
|
326
|
-
throw new FlowError("INTERNAL_STATE", `accepted landing ${mergeCommit} lost an owned task path`);
|
|
331
|
+
const paths = await trackedTaskPaths(input.cwd, targetHead);
|
|
332
|
+
const sources = await taskSourcesAtCommit(input.cwd, transaction.location.root, targetHead, paths);
|
|
333
|
+
if (!sources)
|
|
334
|
+
throw new FlowError("INTERNAL_STATE", `claim target ${targetHead} lost tracked task sources`);
|
|
335
|
+
const board = requireHealthySettlementBoard(taskBoardFromSourceBoard(buildTaskSourceBoard(sources)));
|
|
336
|
+
const replacements = doneTaskReplacements(board, input.contractId, input.taskIds, input.updatedAt);
|
|
337
|
+
if (replacements.length === 0) {
|
|
338
|
+
const selected = new Set(selectedPaths);
|
|
339
|
+
const writes = taskWritesFromSources(sources.filter((source) => selected.has(sourcePath(transaction.location.root, source))));
|
|
340
|
+
if (materializeHub) {
|
|
341
|
+
if (writes.length > 0)
|
|
342
|
+
await transaction.replace(writes);
|
|
343
|
+
await reconcileTaskPathsetIndex(transaction.location.root, targetHead, selectedPaths);
|
|
327
344
|
}
|
|
328
|
-
|
|
329
|
-
root: transaction.location.root,
|
|
330
|
-
sources: authoritativeSources,
|
|
331
|
-
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
332
|
-
ownedPaths: selectedPaths,
|
|
333
|
-
mutationPaths: selectedPaths,
|
|
334
|
-
});
|
|
335
|
-
const currentText = new Map(transaction.sources.map((source) => [sourcePath(transaction.location.root, source), source.text]));
|
|
336
|
-
const writes = authoritativeSources.filter((source) => currentText.get(sourcePath(transaction.location.root, source)) !== source.text);
|
|
337
|
-
if (writes.length > 0)
|
|
338
|
-
await transaction.replace(taskWritesFromSources(writes));
|
|
339
|
-
try {
|
|
340
|
-
await reconcileIndex(transaction.location.root, targetHead, selectedPaths);
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
lastPostCasError = error;
|
|
344
|
-
return { kind: "retry" };
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
await input.publishLedger?.(mergeCommit);
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
lastPostCasError = error;
|
|
351
|
-
return { kind: "retry" };
|
|
352
|
-
}
|
|
353
|
-
const reconcileWarnings = movement
|
|
354
|
-
? await input.afterCas?.(mergeCommit, movement.oldHead)
|
|
355
|
-
: undefined;
|
|
356
|
-
return {
|
|
357
|
-
kind: movement ? "accepted" : "idempotent",
|
|
358
|
-
mergeCommit,
|
|
359
|
-
...(movement ? { oldHead: movement.oldHead } : {}),
|
|
360
|
-
...(reconcileWarnings && reconcileWarnings.length > 0 ? { warnings: reconcileWarnings } : {}),
|
|
361
|
-
evidence: selection.evidence,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
// No durable receipt exists, so this remains a first-publication attempt:
|
|
365
|
-
// validate mutable hub authority before composing or publishing anything.
|
|
366
|
-
const repository = repositoryForSettlement(transaction, transaction.sources);
|
|
367
|
-
const board = requireHealthySettlementBoard(await repository.read());
|
|
368
|
-
const replacements = doneTaskReplacements(board, input.contractId, ownership.taskIds, updatedAt);
|
|
369
|
-
const prepared = replacements.length > 0
|
|
370
|
-
? prepareTaskDocumentReplacements(transaction.sources, replacements)
|
|
371
|
-
: { sources: transaction.sources, board };
|
|
372
|
-
const finalSources = prepared.sources;
|
|
373
|
-
requireHealthySettlementBoard(prepared.board);
|
|
374
|
-
const selection = selectSettlementSources({
|
|
375
|
-
root: transaction.location.root,
|
|
376
|
-
sources: finalSources,
|
|
377
|
-
trackedPaths: await trackedTaskPaths(input.cwd, targetHead),
|
|
378
|
-
ownedPaths: selectedPaths,
|
|
379
|
-
// Bind-base ownership, unlike incidental hub bytes, authorizes a path
|
|
380
|
-
// even when a concurrent target commit removed it.
|
|
381
|
-
mutationPaths: selectedPaths,
|
|
382
|
-
});
|
|
383
|
-
const selected = new Set(selection.evidence.selectedPaths);
|
|
384
|
-
const missingOwnedPaths = selectedPaths.filter((ownedPath) => !selected.has(ownedPath));
|
|
385
|
-
if (missingOwnedPaths.length > 0) {
|
|
386
|
-
throw new FlowError("INTERNAL_STATE", `hub board lost bind-base-owned task paths: ${missingOwnedPaths.join(", ")}`);
|
|
345
|
+
return { taskCount: input.taskIds.length, commit: targetHead, worktree: materializeHub ? "materialized" : "ref-only" };
|
|
387
346
|
}
|
|
388
|
-
const
|
|
347
|
+
const prepared = prepareTaskDocumentReplacements(sources, replacements);
|
|
348
|
+
const commit = await createTaskBoardCommit({
|
|
389
349
|
cwd: input.cwd,
|
|
390
350
|
gitDir: transaction.location.gitDir,
|
|
391
351
|
root: transaction.location.root,
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
sources: selection.sources,
|
|
352
|
+
parent: targetHead,
|
|
353
|
+
sources: prepared.sources,
|
|
354
|
+
subject: `Complete ${replacements.length === 1 ? `task ${replacements[0].coordinate ?? formatTaskCoordinate(replacements[0].namespace ?? null, replacements[0].id)}` : `${replacements.length} tasks`} after claim of ${input.contractId}`,
|
|
396
355
|
});
|
|
397
|
-
const mergeCommit = await createLandingCommit({
|
|
398
|
-
cwd: input.cwd,
|
|
399
|
-
target: targetHead,
|
|
400
|
-
delivery: input.deliveryHead,
|
|
401
|
-
tree: candidateTree,
|
|
402
|
-
contractId: input.contractId,
|
|
403
|
-
targetBranch: input.targetBranch,
|
|
404
|
-
});
|
|
405
|
-
await input.beforeCas?.(mergeCommit, targetHead);
|
|
406
|
-
let movement;
|
|
407
|
-
try {
|
|
408
|
-
await updateTargetRef(input.cwd, input.targetRef, mergeCommit, targetHead);
|
|
409
|
-
movement = { mergeCommit, oldHead: targetHead };
|
|
410
|
-
acceptedMovement = movement;
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
return { kind: "retry" };
|
|
414
|
-
}
|
|
415
|
-
const warnings = [];
|
|
416
|
-
if (replacements.length > 0) {
|
|
417
|
-
await transaction.replace(taskWritesFromSources(selection.sources));
|
|
418
|
-
}
|
|
419
|
-
try {
|
|
420
|
-
await reconcileIndex(transaction.location.root, mergeCommit, selectedPaths);
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
lastPostCasError = error;
|
|
424
|
-
return { kind: "retry" };
|
|
425
|
-
}
|
|
426
356
|
try {
|
|
427
|
-
await input.
|
|
357
|
+
await updateBranch({ cwd: input.cwd, ref: input.targetRef, next: commit, expected: targetHead }, {});
|
|
358
|
+
if (materializeHub) {
|
|
359
|
+
await transaction.replace(prepared.writes);
|
|
360
|
+
await reconcileTaskPathsetIndex(transaction.location.root, commit, selectedPaths);
|
|
361
|
+
}
|
|
362
|
+
return { taskCount: input.taskIds.length, commit, worktree: materializeHub ? "materialized" : "ref-only" };
|
|
428
363
|
}
|
|
429
364
|
catch (error) {
|
|
430
|
-
|
|
431
|
-
|
|
365
|
+
if (attempt === 3)
|
|
366
|
+
throw error;
|
|
432
367
|
}
|
|
433
|
-
const reconcileWarnings = await input.afterCas?.(mergeCommit, movement.oldHead);
|
|
434
|
-
warnings.push(...(reconcileWarnings ?? []));
|
|
435
|
-
return {
|
|
436
|
-
kind: "accepted",
|
|
437
|
-
mergeCommit,
|
|
438
|
-
oldHead: movement.oldHead,
|
|
439
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
440
|
-
evidence: selection.evidence,
|
|
441
|
-
};
|
|
442
|
-
});
|
|
443
|
-
if (result.kind === "retry")
|
|
444
|
-
continue;
|
|
445
|
-
if (result.kind === "idempotent") {
|
|
446
|
-
return {
|
|
447
|
-
mergeCommit: result.mergeCommit,
|
|
448
|
-
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
449
|
-
...(result.evidence ? { evidence: result.evidence } : {}),
|
|
450
|
-
};
|
|
451
368
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
oldHead: result.oldHead,
|
|
455
|
-
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
456
|
-
...(result.evidence ? { evidence: result.evidence } : {}),
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
if (lastPostCasError !== undefined)
|
|
460
|
-
throw lastPostCasError;
|
|
461
|
-
throw new FlowError("CONCURRENT_MODIFICATION", `target branch kept moving while claiming ${input.contractId}; last observed ${lastTargetHead}`);
|
|
369
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `claim target ${input.targetRef} kept moving during task replay`);
|
|
370
|
+
});
|
|
462
371
|
}
|