@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.
Files changed (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. 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 { renderContractView } from "./contract-view.js";
8
- import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
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
- export async function materializeContractView(input) {
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 id of await listContractIds(cwd)) {
92
- const ledger = await readLedger(cwd, id);
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 place = findBindEntry(ledger.entries)?.data.place;
96
- if (place)
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
- const occupied = await occupiedWorktreePlaces(cwd);
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)];