@astrosheep/keiyaku 2.9.8 → 2.9.10
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 +50 -182
- 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 +10 -7
- 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 +242 -79
- 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 +65 -29
- package/build/cli/render/projection-history.js +64 -0
- package/build/cli/render/shared.js +23 -9
- package/build/cli/render/status.js +13 -35
- package/build/cli/render/task.js +51 -26
- package/build/cli/render/terminal-failure.js +3 -1
- package/build/cli/render/wait.js +45 -69
- package/build/cli/subagent-guard.js +1 -1
- package/build/cli/types.js +1 -1
- package/build/config/env-keys.js +3 -1
- package/build/config/env.js +24 -1
- 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 +13 -6
- package/build/core/projection/index.js +5 -4
- package/build/core/projection/projection-activity.js +29 -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 +35 -337
- 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 +125 -127
- 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,390 @@
|
|
|
1
|
+
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { lookupCurrentContract, readRepositoryLifecycle } from "./repository-ledger/read-model.js";
|
|
4
|
+
import { withContractCarrierSession } from "./contract-carrier.js";
|
|
5
|
+
import { stableRepoRoot, contractWorktreePathFromBind, findRegisteredContractWorktreePath } from "./worktree-path.js";
|
|
6
|
+
import { defaultCreateWorktree } from "./bind-workspace.js";
|
|
7
|
+
import { isCommissionId } from "./ids.js";
|
|
8
|
+
import { REPOSITORY_LEDGER_REF, repositoryDeliveryRef } from "./repository-ledger/codec.js";
|
|
9
|
+
import { resolveLocalBranchHead } from "../git/refs.js";
|
|
10
|
+
import { runWorktreeBootstrap, bootstrapEntryDataFromObservation, isBootstrapSuccess } from "./worktree-bootstrap.js";
|
|
11
|
+
import { committedLifecycleFacts, containedCommittedLifecycleFacts, EffectJournal } from "./outcome-base.js";
|
|
12
|
+
export async function publishBindBootstrap(session, entry, expectedLedgerHead) {
|
|
13
|
+
return await session.publishAuthoritative({ verb: "bootstrap", admittedLedgerHead: expectedLedgerHead, acceptedLifecycleTail: session.lifecycle.history.at(-1).objectId, entries: [entry] });
|
|
14
|
+
}
|
|
15
|
+
export async function publishBindFailedForfeit(session, entry, delivery, expectedLedgerHead) {
|
|
16
|
+
return await session.publishAuthoritative({
|
|
17
|
+
verb: "forfeit",
|
|
18
|
+
admittedLedgerHead: expectedLedgerHead,
|
|
19
|
+
acceptedLifecycleTail: session.lifecycle.history.at(-1).objectId,
|
|
20
|
+
entries: [entry],
|
|
21
|
+
delivery: {
|
|
22
|
+
ref: repositoryDeliveryRef(entry.contract),
|
|
23
|
+
expectedOid: delivery.status === "present" ? delivery.head : null,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function copyAcceptedBindEntry(entry) {
|
|
28
|
+
const common = {
|
|
29
|
+
title: entry.data.title,
|
|
30
|
+
context: entry.data.context,
|
|
31
|
+
objective: entry.data.objective,
|
|
32
|
+
design: entry.data.design,
|
|
33
|
+
scope: [...entry.data.scope],
|
|
34
|
+
criteria: [...entry.data.criteria],
|
|
35
|
+
verification: [...entry.data.verification],
|
|
36
|
+
extensions: entry.data.extensions.map((extension) => ({ title: extension.title, content: extension.content })),
|
|
37
|
+
base: entry.data.base,
|
|
38
|
+
target: entry.data.target,
|
|
39
|
+
...(entry.data.after ? { after: [...entry.data.after] } : {}),
|
|
40
|
+
...(entry.data.taskIds ? { taskIds: [...entry.data.taskIds] } : {}),
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
v: 2,
|
|
44
|
+
kind: "bind",
|
|
45
|
+
contract: entry.contract,
|
|
46
|
+
at: entry.at,
|
|
47
|
+
actor: entry.actor,
|
|
48
|
+
data: entry.data.workspace === "worktree"
|
|
49
|
+
? {
|
|
50
|
+
...common,
|
|
51
|
+
workspace: "worktree",
|
|
52
|
+
place: entry.data.place,
|
|
53
|
+
...(entry.data.bootstrap ? { bootstrap: { command: [...entry.data.bootstrap.command], timeoutMs: entry.data.bootstrap.timeoutMs } } : {}),
|
|
54
|
+
}
|
|
55
|
+
: { ...common, workspace: "here" },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function copyAcceptedBootstrapEntry(entry) {
|
|
59
|
+
return {
|
|
60
|
+
v: 1,
|
|
61
|
+
kind: "bootstrap",
|
|
62
|
+
contract: entry.contract,
|
|
63
|
+
at: entry.at,
|
|
64
|
+
actor: entry.actor,
|
|
65
|
+
data: {
|
|
66
|
+
result: { ...entry.data.result },
|
|
67
|
+
durationMs: entry.data.durationMs,
|
|
68
|
+
...(entry.data.stderrTail === undefined ? {} : { stderrTail: entry.data.stderrTail }),
|
|
69
|
+
...(entry.data.stderrTruncated === undefined ? {} : { stderrTruncated: entry.data.stderrTruncated }),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function bootstrapProgressLine(command) {
|
|
74
|
+
return `bootstrap » ${command.join(" ")}`;
|
|
75
|
+
}
|
|
76
|
+
function workspaceBindView(entry) {
|
|
77
|
+
return entry;
|
|
78
|
+
}
|
|
79
|
+
export async function revalidateBindAuthority(input) {
|
|
80
|
+
const [target, delivery] = await Promise.all([
|
|
81
|
+
resolveLocalBranchHead(input.cwd, input.target),
|
|
82
|
+
resolveLocalBranchHead(input.cwd, input.deliveryRef),
|
|
83
|
+
]);
|
|
84
|
+
if (target?.head !== input.targetHead) {
|
|
85
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `bind target changed from ${input.targetHead}`);
|
|
86
|
+
}
|
|
87
|
+
if (delivery !== null) {
|
|
88
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `bind delivery ref already exists: ${input.deliveryRef}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function committedOrThrow(result, operation) {
|
|
92
|
+
if (result.family === "committed")
|
|
93
|
+
return result;
|
|
94
|
+
const detail = `${result.code}: ${result.cause}`;
|
|
95
|
+
if (result.family === "indeterminate") {
|
|
96
|
+
throw new FlowError("INTERNAL_STATE", `${operation}_INDETERMINATE: ${detail}`);
|
|
97
|
+
}
|
|
98
|
+
throw new FlowError("CONCURRENT_MODIFICATION", `${operation}_REFUSED: ${detail}`);
|
|
99
|
+
}
|
|
100
|
+
function effectCause(row) {
|
|
101
|
+
return row.status === "succeeded" ? undefined : row.cause;
|
|
102
|
+
}
|
|
103
|
+
function bindResult(input) {
|
|
104
|
+
const ledgerHead = input.heads.bindFailedForfeit ?? input.heads.bootstrap ?? input.heads.bind;
|
|
105
|
+
const reconciliationKey = `${input.coordinates.id}:${input.heads.bind}`;
|
|
106
|
+
if (input.cause) {
|
|
107
|
+
return {
|
|
108
|
+
ledgerHead,
|
|
109
|
+
heads: input.heads,
|
|
110
|
+
reconciliationKey,
|
|
111
|
+
...containedCommittedLifecycleFacts({
|
|
112
|
+
head: ledgerHead,
|
|
113
|
+
entryKind: input.heads.bindFailedForfeit ? "forfeit" : input.heads.bootstrap ? "bootstrap" : "bind",
|
|
114
|
+
contractId: input.coordinates.id,
|
|
115
|
+
carrier: input.carrier,
|
|
116
|
+
cause: input.cause,
|
|
117
|
+
prerequisites: input.journal.rows("prerequisite"),
|
|
118
|
+
downstream: input.journal.rows("downstream"),
|
|
119
|
+
}),
|
|
120
|
+
replay: {
|
|
121
|
+
idempotencyKey: reconciliationKey,
|
|
122
|
+
action: "reconcile",
|
|
123
|
+
reason: "one or more committed-head effects remain",
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const lifecycle = committedLifecycleFacts({
|
|
128
|
+
head: ledgerHead,
|
|
129
|
+
entryKind: input.heads.bootstrap ? "bootstrap" : "bind",
|
|
130
|
+
contractId: input.coordinates.id,
|
|
131
|
+
carrier: input.carrier,
|
|
132
|
+
prerequisites: input.journal.rows("prerequisite"),
|
|
133
|
+
downstream: input.journal.rows("downstream"),
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
...input.coordinates,
|
|
137
|
+
ledgerHead,
|
|
138
|
+
heads: input.heads,
|
|
139
|
+
reconciliationKey,
|
|
140
|
+
surfaces: bindSurfaceReceipts(input.journal),
|
|
141
|
+
...lifecycle,
|
|
142
|
+
replay: { ...lifecycle.replay, idempotencyKey: reconciliationKey },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function bindSurfaceReceipts(journal) {
|
|
146
|
+
const mapping = new Map([
|
|
147
|
+
["task-board-projection", "task-view"],
|
|
148
|
+
["registry-projection", "registry"],
|
|
149
|
+
["worktree-projection", "worktree"],
|
|
150
|
+
["bootstrap-execution", "bootstrap"],
|
|
151
|
+
["carrier-projection", "carrier"],
|
|
152
|
+
]);
|
|
153
|
+
const receipts = [];
|
|
154
|
+
for (const row of journal.rows("downstream")) {
|
|
155
|
+
const surface = mapping.get(row.effect);
|
|
156
|
+
if (!surface || row.status !== "succeeded")
|
|
157
|
+
continue;
|
|
158
|
+
const raw = row.receipt?.status;
|
|
159
|
+
const status = raw === "current" || raw === "already-present" || raw === "not-applicable"
|
|
160
|
+
? "already-present"
|
|
161
|
+
: "created";
|
|
162
|
+
receipts.push({ surface, status });
|
|
163
|
+
}
|
|
164
|
+
return receipts;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Sole replay-only entry for a committed bind. It never allocates identity or
|
|
168
|
+
* reads draft input; every coordinate is reconstructed from the accepted bind.
|
|
169
|
+
*/
|
|
170
|
+
export async function reconcileCommittedBind(input, admittedScopeOverlaps) {
|
|
171
|
+
if (input.signal?.aborted) {
|
|
172
|
+
throw new FlowError("CARRIER_GATE_CANCELLED", "contract replay cancelled before derived effects");
|
|
173
|
+
}
|
|
174
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
175
|
+
const result = await withContractCarrierSession({
|
|
176
|
+
cwd: input.cwd,
|
|
177
|
+
contractId: input.contractId,
|
|
178
|
+
mode: "mutation",
|
|
179
|
+
signal: input.signal,
|
|
180
|
+
...(input.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: input.terminalWorkspaceCleanup } : {}),
|
|
181
|
+
}, async (session) => reconcileBindContractLocked({ ...input, cwd: session.stableRoot }, session, admittedScopeOverlaps));
|
|
182
|
+
if ("continueAfterFailedBootstrap" in result)
|
|
183
|
+
continue;
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
throw new FlowError("INTERNAL_STATE", `bind-failed finalization for ${input.contractId} did not settle after bootstrap re-admission`);
|
|
187
|
+
}
|
|
188
|
+
async function reconcileBindContractLocked(input, replaySession, admittedScopeOverlaps) {
|
|
189
|
+
const stableRoot = await stableRepoRoot(input.cwd);
|
|
190
|
+
if (!isCommissionId(input.contractId)) {
|
|
191
|
+
throw new FlowError("INVALID_TARGET", `bind replay requires a full canonical contract id; got ${input.contractId}`);
|
|
192
|
+
}
|
|
193
|
+
const initialRead = await readRepositoryLifecycle(stableRoot);
|
|
194
|
+
const outcome = lookupCurrentContract(initialRead, input.contractId);
|
|
195
|
+
if (outcome.status === "absent")
|
|
196
|
+
throw new FlowError("CONTRACT_NOT_FOUND", `contract ${input.contractId} not found`);
|
|
197
|
+
if (outcome.status !== "healthy") {
|
|
198
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} is unavailable (${outcome.status})`);
|
|
199
|
+
}
|
|
200
|
+
const bindRow = outcome.model.history.find((row) => row.entry.kind === "bind");
|
|
201
|
+
if (!bindRow || bindRow.entry.kind !== "bind")
|
|
202
|
+
throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} has no accepted bind`);
|
|
203
|
+
const bootstrapRow = outcome.model.history.find((row) => row.entry.kind === "bootstrap");
|
|
204
|
+
const forfeitRow = outcome.model.history.find((row) => row.entry.kind === "forfeit");
|
|
205
|
+
const replayingBindFailedTerminal = outcome.model.phase === "forfeited"
|
|
206
|
+
&& bootstrapRow?.entry.kind === "bootstrap"
|
|
207
|
+
&& !isBootstrapSuccess(bootstrapRow.entry.data)
|
|
208
|
+
&& forfeitRow?.entry.kind === "forfeit"
|
|
209
|
+
&& forfeitRow.entry.data.reason === "bind-failed";
|
|
210
|
+
if (["claimed", "forfeited"].includes(outcome.model.phase) && !replayingBindFailedTerminal) {
|
|
211
|
+
throw new FlowError("CONTRACT_TERMINAL", `contract ${input.contractId} is terminal (${outcome.model.phase})`);
|
|
212
|
+
}
|
|
213
|
+
const entry = copyAcceptedBindEntry(bindRow.entry);
|
|
214
|
+
const workspace = entry.data.workspace;
|
|
215
|
+
const branch = `keiyaku/${entry.contract}`;
|
|
216
|
+
const worktreePath = await contractWorktreePathFromBind(stableRoot, entry.contract, workspaceBindView(entry));
|
|
217
|
+
const targetRoot = workspace === "here" ? stableRoot : worktreePath;
|
|
218
|
+
const deps = input.dependencies ?? {};
|
|
219
|
+
const heads = {
|
|
220
|
+
bind: bindRow.objectId,
|
|
221
|
+
bootstrap: bootstrapRow?.objectId ?? null,
|
|
222
|
+
bindFailedForfeit: replayingBindFailedTerminal ? forfeitRow.objectId : null,
|
|
223
|
+
};
|
|
224
|
+
const journal = new EffectJournal(`bind:${entry.contract}:${bindRow.objectId}`);
|
|
225
|
+
journal.record({
|
|
226
|
+
effect: "ledger-cas",
|
|
227
|
+
idempotencyKey: `bind:${entry.contract}:${bindRow.objectId}:ledger-cas`,
|
|
228
|
+
status: "succeeded",
|
|
229
|
+
receipt: { bindHead: bindRow.objectId, observed: true },
|
|
230
|
+
});
|
|
231
|
+
let containedCause;
|
|
232
|
+
const rememberFailure = (row) => { containedCause ??= effectCause(row); };
|
|
233
|
+
const scopeOverlaps = [...admittedScopeOverlaps];
|
|
234
|
+
const bootstrapPlan = entry.data.workspace === "worktree" ? entry.data.bootstrap : undefined;
|
|
235
|
+
if (!replayingBindFailedTerminal && bootstrapPlan) {
|
|
236
|
+
const worktreeReplay = await journal.run({
|
|
237
|
+
effect: "bootstrap-worktree-prerequisite",
|
|
238
|
+
run: async () => {
|
|
239
|
+
const registered = await findRegisteredContractWorktreePath(stableRoot, entry.contract);
|
|
240
|
+
if (registered === worktreePath)
|
|
241
|
+
return { status: "already-present", path: worktreePath };
|
|
242
|
+
try {
|
|
243
|
+
await access(worktreePath);
|
|
244
|
+
throw Object.assign(new Error(`worktree path ${worktreePath} is occupied by an unowned filesystem entry`), {
|
|
245
|
+
code: "BIND_WORKTREE_PATH_OCCUPIED",
|
|
246
|
+
facts: { path: worktreePath, occupant: "unowned-filesystem-entry" },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
await (input.createWorktree ?? defaultCreateWorktree)({ cwd: stableRoot, id: entry.contract, branch, base: entry.data.base, worktreePath });
|
|
254
|
+
return { status: "created", path: worktreePath };
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
rememberFailure(worktreeReplay);
|
|
258
|
+
}
|
|
259
|
+
let bootstrapData = bootstrapRow?.entry.kind === "bootstrap"
|
|
260
|
+
? copyAcceptedBootstrapEntry(bootstrapRow.entry)
|
|
261
|
+
: undefined;
|
|
262
|
+
let publishedFailedBootstrapThroughSession = false;
|
|
263
|
+
if (bootstrapPlan) {
|
|
264
|
+
const locked = lookupCurrentContract(await readRepositoryLifecycle(stableRoot), entry.contract);
|
|
265
|
+
if (locked.status !== "healthy")
|
|
266
|
+
throw new FlowError("INTERNAL_STATE", `contract ${entry.contract} became unavailable during bootstrap replay`);
|
|
267
|
+
const existingBootstrap = locked.model.history.find((row) => row.entry.kind === "bootstrap")?.entry;
|
|
268
|
+
bootstrapData = existingBootstrap?.kind === "bootstrap"
|
|
269
|
+
? copyAcceptedBootstrapEntry(existingBootstrap)
|
|
270
|
+
: undefined;
|
|
271
|
+
if (bootstrapData) {
|
|
272
|
+
const row = await journal.run({ effect: "bootstrap-execution", run: async () => ({ status: "already-present" }) });
|
|
273
|
+
rememberFailure(row);
|
|
274
|
+
const publication = await journal.run({
|
|
275
|
+
effect: "bootstrap-ledger-cas",
|
|
276
|
+
dependency: "bootstrap-execution",
|
|
277
|
+
run: async () => ({ head: heads.bootstrap, status: "already-present" }),
|
|
278
|
+
});
|
|
279
|
+
rememberFailure(publication);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const execution = await journal.run({
|
|
283
|
+
effect: "bootstrap-execution",
|
|
284
|
+
dependency: "bootstrap-worktree-prerequisite",
|
|
285
|
+
run: async () => {
|
|
286
|
+
input.onProgress?.(bootstrapProgressLine(bootstrapPlan.command));
|
|
287
|
+
const observation = await (input.bootstrapRunner ?? runWorktreeBootstrap)({ cwd: worktreePath, plan: bootstrapPlan }, input.bootstrapDependencies);
|
|
288
|
+
const data = bootstrapEntryDataFromObservation(observation);
|
|
289
|
+
bootstrapData = { v: 1, kind: "bootstrap", contract: entry.contract, at: new Date(input.nowMs).toISOString(), actor: input.actor, data };
|
|
290
|
+
return { status: "created", result: data.result.kind };
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
rememberFailure(execution);
|
|
294
|
+
const publication = await journal.run({
|
|
295
|
+
effect: "bootstrap-ledger-cas",
|
|
296
|
+
dependency: "bootstrap-execution",
|
|
297
|
+
run: async () => {
|
|
298
|
+
const current = await readRepositoryLifecycle(stableRoot);
|
|
299
|
+
const written = deps.publishBootstrap
|
|
300
|
+
? await deps.publishBootstrap(bootstrapData, current.observedLedgerHead)
|
|
301
|
+
: await publishBindBootstrap(replaySession, bootstrapData, current.observedLedgerHead);
|
|
302
|
+
if (written.status !== "committed")
|
|
303
|
+
throw new FlowError("CONCURRENT_MODIFICATION", "BIND_BOOTSTRAP_TRANSACTION_REFUSED");
|
|
304
|
+
publishedFailedBootstrapThroughSession = !deps.publishBootstrap && !isBootstrapSuccess(bootstrapData.data);
|
|
305
|
+
heads.bootstrap = written.head;
|
|
306
|
+
return { head: written.head };
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
rememberFailure(publication);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const bootstrapEntry = bootstrapData?.kind === "bootstrap" ? bootstrapData : undefined;
|
|
313
|
+
if (publishedFailedBootstrapThroughSession)
|
|
314
|
+
return { continueAfterFailedBootstrap: true };
|
|
315
|
+
if (bootstrapEntry && !isBootstrapSuccess(bootstrapEntry.data)) {
|
|
316
|
+
const forfeitPublication = await journal.run({
|
|
317
|
+
effect: "bind-failed-forfeit-cas",
|
|
318
|
+
dependency: heads.bootstrap ? "bootstrap-ledger-cas" : undefined,
|
|
319
|
+
run: async () => {
|
|
320
|
+
const current = await readRepositoryLifecycle(stableRoot);
|
|
321
|
+
const currentContract = lookupCurrentContract(current, entry.contract);
|
|
322
|
+
if (currentContract.status === "healthy" && currentContract.model.phase === "forfeited") {
|
|
323
|
+
const existing = currentContract.model.history.find((row) => row.entry.kind === "forfeit");
|
|
324
|
+
if (!existing)
|
|
325
|
+
throw new FlowError("INTERNAL_STATE", `forfeited contract ${entry.contract} has no accepted terminal entry`);
|
|
326
|
+
heads.bindFailedForfeit = existing.objectId;
|
|
327
|
+
return { head: existing.objectId, status: "already-present" };
|
|
328
|
+
}
|
|
329
|
+
const delivery = await resolveLocalBranchHead(stableRoot, repositoryDeliveryRef(entry.contract));
|
|
330
|
+
if (!delivery) {
|
|
331
|
+
throw new FlowError("INTERNAL_STATE", `contract ${entry.contract} delivery ref is absent before bind-failed forfeit`);
|
|
332
|
+
}
|
|
333
|
+
const forfeitEntry = {
|
|
334
|
+
v: 2,
|
|
335
|
+
kind: "forfeit",
|
|
336
|
+
contract: entry.contract,
|
|
337
|
+
at: new Date(input.nowMs).toISOString(),
|
|
338
|
+
actor: input.actor,
|
|
339
|
+
data: { reason: "bind-failed", actor: input.actor, finalHead: delivery.head },
|
|
340
|
+
};
|
|
341
|
+
const deliveryState = { status: "present", head: delivery.head };
|
|
342
|
+
const written = deps.publishBindFailedForfeit
|
|
343
|
+
? await deps.publishBindFailedForfeit(forfeitEntry, deliveryState, current.observedLedgerHead)
|
|
344
|
+
: await publishBindFailedForfeit(replaySession, forfeitEntry, deliveryState, current.observedLedgerHead);
|
|
345
|
+
if (written.status !== "committed")
|
|
346
|
+
throw new FlowError("CONCURRENT_MODIFICATION", "BIND_FAILED_FORFEIT_TRANSACTION_REFUSED");
|
|
347
|
+
heads.bindFailedForfeit = written.head;
|
|
348
|
+
return { head: written.head, status: "created" };
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
rememberFailure(forfeitPublication);
|
|
352
|
+
const terminalCarrier = await replaySession.replayDerivedState();
|
|
353
|
+
for (const row of replaySession.journal.all()) {
|
|
354
|
+
journal.record(row);
|
|
355
|
+
rememberFailure(row);
|
|
356
|
+
}
|
|
357
|
+
return bindResult({
|
|
358
|
+
coordinates: {
|
|
359
|
+
id: entry.contract, ref: REPOSITORY_LEDGER_REF, target: entry.data.target,
|
|
360
|
+
place: workspace === "worktree" ? entry.data.place : null,
|
|
361
|
+
...(workspace === "worktree" ? { branch } : {}), worktreePath: targetRoot,
|
|
362
|
+
workspace, scopeOverlaps,
|
|
363
|
+
},
|
|
364
|
+
heads,
|
|
365
|
+
journal,
|
|
366
|
+
carrier: terminalCarrier,
|
|
367
|
+
cause: { code: "BIND_BOOTSTRAP_FAILED", message: `worktree bootstrap failed with ${bootstrapEntry.data.result.kind}` },
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const carrier = await replaySession.replayDerivedState();
|
|
371
|
+
for (const row of replaySession.journal.all()) {
|
|
372
|
+
journal.record(row);
|
|
373
|
+
rememberFailure(row);
|
|
374
|
+
}
|
|
375
|
+
return bindResult({
|
|
376
|
+
coordinates: {
|
|
377
|
+
id: entry.contract, ref: REPOSITORY_LEDGER_REF, target: entry.data.target,
|
|
378
|
+
place: workspace === "worktree" ? entry.data.place : null,
|
|
379
|
+
...(workspace === "worktree" ? { branch } : {}), worktreePath: targetRoot,
|
|
380
|
+
workspace, scopeOverlaps,
|
|
381
|
+
},
|
|
382
|
+
heads,
|
|
383
|
+
journal,
|
|
384
|
+
carrier,
|
|
385
|
+
...(containedCause ? { cause: containedCause } : {}),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
export async function reconcileBindContract(input) {
|
|
389
|
+
return await reconcileCommittedBind(input, []);
|
|
390
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
4
2
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
5
|
-
import { listContractIds, readLedger } from "./ledger.js";
|
|
6
3
|
import { CONTRACT_PLACES } from "./places.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { contractWorktreePathFromBind, findBindEntry } from "./worktree-path.js";
|
|
4
|
+
import { lookupCurrentContract, readRepositoryLifecycle } from "./repository-ledger/read-model.js";
|
|
5
|
+
import { contractWorktreePathFromBind } from "./worktree-path.js";
|
|
10
6
|
export async function createContractBranch(cwd, branch, base) {
|
|
11
7
|
const git = createGit(cwd);
|
|
12
8
|
try {
|
|
@@ -80,21 +76,14 @@ export async function cleanupCreatedBindResources(cwd, resources) {
|
|
|
80
76
|
}
|
|
81
77
|
return failures;
|
|
82
78
|
}
|
|
83
|
-
|
|
84
|
-
const keiyakuPath = path.join(input.targetRoot, KEIYAKU_FILE);
|
|
85
|
-
await fs.mkdir(path.dirname(keiyakuPath), { recursive: true });
|
|
86
|
-
await fs.writeFile(keiyakuPath, renderContractView({ id: input.id, entries: input.entries }), "utf8");
|
|
87
|
-
return keiyakuPath;
|
|
88
|
-
}
|
|
89
|
-
async function occupiedWorktreePlaces(cwd) {
|
|
79
|
+
function occupiedWorktreePlaces(read) {
|
|
90
80
|
const occupied = new Set();
|
|
91
|
-
for (const
|
|
92
|
-
|
|
93
|
-
if (!ledger || isTerminalState(deriveContractState(ledger.entries)))
|
|
81
|
+
for (const model of read.contracts) {
|
|
82
|
+
if (lookupCurrentContract(read, model.contract).status !== "healthy")
|
|
94
83
|
continue;
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
occupied.add(place);
|
|
84
|
+
const worktree = model.resources.worktree;
|
|
85
|
+
if (worktree?.state === "held")
|
|
86
|
+
occupied.add(worktree.place);
|
|
98
87
|
}
|
|
99
88
|
return occupied;
|
|
100
89
|
}
|
|
@@ -113,7 +102,10 @@ function nextGenerationPlace(base, occupied) {
|
|
|
113
102
|
}
|
|
114
103
|
}
|
|
115
104
|
export async function allocateWorktreePlace(cwd, randomBytes) {
|
|
116
|
-
|
|
105
|
+
return allocateWorktreePlaceFromRead(await readRepositoryLifecycle(cwd), randomBytes);
|
|
106
|
+
}
|
|
107
|
+
export function allocateWorktreePlaceFromRead(read, randomBytes) {
|
|
108
|
+
const occupied = occupiedWorktreePlaces(read);
|
|
117
109
|
const candidates = CONTRACT_PLACES.filter((place) => !occupied.has(place));
|
|
118
110
|
if (candidates.length > 0) {
|
|
119
111
|
return candidates[randomIndex(randomBytes, candidates.length)];
|