@astrosheep/keiyaku 2.9.12 → 2.9.13

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 (212) hide show
  1. package/README.md +22 -18
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -1,22 +1,26 @@
1
1
  import * as crypto from "node:crypto";
2
+ import { readdir } from "node:fs/promises";
3
+ import * as path from "node:path";
2
4
  import { assertSettingsKnobUsable } from "../config/settings/disease.js";
3
5
  import { loadKeiyakuSettings } from "../config/settings/loader.js";
4
6
  import { FlowError } from "../flow-error.js";
5
- import { lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
6
- import { repositoryDeliveryRef, } from "./repository-ledger/codec.js";
7
+ import { advanceRepositorySnapshotWithBind, lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
8
+ import { REPOSITORY_LEDGER_REF, repositoryDeliveryRef, } from "./repository-ledger/codec.js";
7
9
  import { publishBindTransaction, } from "./repository-ledger/write-transaction.js";
8
10
  import { bindEffectiveScope } from "./repository-ledger/fold.js";
9
11
  import { activeWorkspaceHereContracts } from "./repository-ledger/inventory.js";
12
+ import { repositoryLedgerHead } from "./repository-ledger/identity.js";
10
13
  import { describeMissingBindSections, materializeBindDraft, mergeBindDraft, missingBindDraftSections, parseBindDraft, parsePartialBindDraft, renderBindDraft, renderMissingBindSectionsTemplate, } from "./draft.js";
11
- import { assertValidContractId, buildContractId, isCommissionId } from "./ids.js";
14
+ import { buildContractId, isCommissionId } from "./ids.js";
12
15
  import { admitScopeRegistration, } from "./registry.js";
13
- import { resolveDefaultBranch } from "./status/drift.js";
14
16
  import { normalizeTargetRef } from "./target-ref.js";
15
- import { previewTaskBoardForBind, readTaskNamespaces, replayPreparedTaskBindLocked, withPreparedTaskBind, } from "./task/index.js";
17
+ import { previewTaskBoardForBind, withPreparedTaskBindSnapshot, } from "./task/index.js";
16
18
  import { stableRepoRoot } from "./worktree-path.js";
17
19
  import { allocateWorktreePlaceFromRead, } from "./bind-workspace.js";
18
- import { committedOrThrow, reconcileCommittedBind, revalidateBindAuthority } from "./bind-reconciliation.js";
20
+ import { committedOrThrow, reconcileBindContract, reconcileCommittedBindFromSnapshot, requiresBindRecovery } from "./bind-reconciliation.js";
19
21
  import { allocateTaskNamespace, taskNamespaceFromScope, taskNamespaceScope } from "./task/coordinate.js";
22
+ import { resolveExactRefHeads } from "../git/refs.js";
23
+ import { runGitProcess } from "../git/core.js";
20
24
  export { reconcileBindContract } from "./bind-reconciliation.js";
21
25
  function randomBytesDefault() {
22
26
  return [...crypto.randomBytes(10)];
@@ -29,6 +33,27 @@ function resolvedBootstrapPlan(settings) {
29
33
  timeoutMs: settings.timeoutMs,
30
34
  };
31
35
  }
36
+ async function readPersistedTaskNamespaces(stableRoot) {
37
+ try {
38
+ const entries = await readdir(path.join(stableRoot, ".keiyaku", "tasks"), { withFileTypes: true });
39
+ return new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
40
+ }
41
+ catch (error) {
42
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
43
+ return new Set();
44
+ throw error;
45
+ }
46
+ }
47
+ async function observeBindRefs(stableRoot, defaultBranchOverride) {
48
+ const branches = defaultBranchOverride ? [defaultBranchOverride] : ["main", "master"];
49
+ const refs = [REPOSITORY_LEDGER_REF, ...branches.map((branch) => normalizeTargetRef(branch))];
50
+ const heads = await resolveExactRefHeads(stableRoot, refs);
51
+ const selected = branches.find((branch) => heads.get(normalizeTargetRef(branch)) !== null);
52
+ return {
53
+ ledgerHead: heads.get(REPOSITORY_LEDGER_REF) ?? null,
54
+ branch: selected ? { branch: selected, head: heads.get(normalizeTargetRef(selected)) } : null,
55
+ };
56
+ }
32
57
  function bindDraftWithTaskNamespace(draft, read, persistedNamespaces) {
33
58
  const declared = draft.scope.flatMap((term) => {
34
59
  const namespace = taskNamespaceFromScope(term);
@@ -66,7 +91,7 @@ function makeBindEntry(input) {
66
91
  ...(input.taskIds ? { taskIds: [...input.taskIds] } : {}),
67
92
  };
68
93
  return {
69
- v: 2,
94
+ v: 3,
70
95
  kind: "bind",
71
96
  contract: input.id,
72
97
  at: input.at,
@@ -108,24 +133,22 @@ export function composeTaskBindDraft(input) {
108
133
  return materializeBindDraft(merged);
109
134
  }
110
135
  function validateBindAfter(read, after) {
111
- const unique = new Set();
112
136
  for (const contractId of after) {
113
- if (unique.has(contractId)) {
114
- throw new FlowError("INVALID_TARGET", `contract ${contractId} selected more than once by --after`);
115
- }
116
- unique.add(contractId);
117
137
  if (!isCommissionId(contractId)) {
118
138
  throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a valid contract id`);
119
139
  }
120
140
  const outcome = lookupCurrentContract(read, contractId);
121
141
  if (outcome.status !== "healthy") {
122
- throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a healthy terminal contract`);
123
- }
124
- if (!["claimed", "forfeited"].includes(outcome.model.phase)) {
125
- throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not terminal`);
142
+ throw new FlowError("INVALID_TARGET", `after contract ${contractId} is not a healthy existing contract`);
126
143
  }
127
144
  }
128
145
  }
146
+ function canonicalBindAfter(explicit, selected = []) {
147
+ return [...new Set([
148
+ ...explicit,
149
+ ...selected.flatMap((task) => task.needs.flatMap((need) => need.contractId === undefined ? [] : [need.contractId])),
150
+ ])];
151
+ }
129
152
  function assertContractAbsent(read, contractId) {
130
153
  const outcome = lookupCurrentContract(read, contractId);
131
154
  if (outcome.status === "absent")
@@ -149,10 +172,9 @@ function admitWorkspaceOccupancy(read, workspace) {
149
172
  * facts and rendered the canonical draft.
150
173
  */
151
174
  async function admitBindCandidate(input, draft, read) {
152
- draft = bindDraftWithTaskNamespace(draft, read, await readTaskNamespaces(input.cwd));
175
+ draft = bindDraftWithTaskNamespace(draft, read, await readPersistedTaskNamespaces(input.cwd));
153
176
  admitWorkspaceOccupancy(read, input.workspace);
154
177
  const id = buildContractId({ title: draft.title, nowMs: input.nowMs, randomBytes: input.randomBytes });
155
- await assertValidContractId(input.cwd, id);
156
178
  assertContractAbsent(read, id);
157
179
  validateBindAfter(read, input.after);
158
180
  const provisionalEntry = makeBindEntry({
@@ -191,18 +213,20 @@ export async function previewBindContract(input) {
191
213
  const isTaskBound = input.taskIds !== undefined && input.taskIds.length > 0;
192
214
  const ordinaryDraft = !isTaskBound ? parseBindDraft(input.draft) : undefined;
193
215
  const workspace = input.workspace ?? "here";
194
- const defaultBranch = await resolveDefaultBranch(input.cwd);
216
+ const loadedSettings = await loadKeiyakuSettings(input.cwd);
217
+ assertSettingsKnobUsable(loadedSettings, "defaultBranch");
218
+ const observed = await observeBindRefs(input.cwd, loadedSettings.knobs?.defaultBranch);
219
+ const defaultBranch = observed.branch;
195
220
  if (!defaultBranch) {
196
221
  throw new FlowError("INTERNAL_STATE", "cannot bind: no default branch");
197
222
  }
198
223
  const target = normalizeTargetRef(defaultBranch.branch);
199
- const loadedSettings = await loadKeiyakuSettings(input.cwd);
200
224
  let bootstrapPlan;
201
225
  if (workspace === "worktree") {
202
226
  assertSettingsKnobUsable(loadedSettings, "worktreeBootstrap");
203
227
  bootstrapPlan = resolvedBootstrapPlan(loadedSettings.knobs?.worktreeBootstrap);
204
228
  }
205
- const read = await readRepositoryLifecycle(input.cwd);
229
+ const read = await readRepositoryLifecycle(input.cwd, { observedHead: observed.ledgerHead, signal: input.signal });
206
230
  const randomBytes = input.randomBytes?.(0) ?? randomBytesDefault();
207
231
  const place = workspace === "worktree" ? allocateWorktreePlaceFromRead(read, randomBytes) : undefined;
208
232
  const taskAdmission = isTaskBound
@@ -233,18 +257,57 @@ export async function bindContract(input) {
233
257
  const workspace = input.workspace ?? "here";
234
258
  const at = new Date(input.nowMs).toISOString();
235
259
  const after = input.after ?? [];
236
- const defaultBranch = await resolveDefaultBranch(stableRoot);
237
- if (!defaultBranch)
238
- throw new FlowError("INTERNAL_STATE", "cannot bind: no default branch");
239
- const target = normalizeTargetRef(defaultBranch.branch);
240
260
  const publishBind = input.dependencies?.publishBind ?? publishBindTransaction;
241
- const persistedTaskNamespaces = await readTaskNamespaces(stableRoot);
261
+ const persistedTaskNamespaces = await readPersistedTaskNamespaces(stableRoot);
242
262
  const loadedSettings = await loadKeiyakuSettings(stableRoot);
263
+ assertSettingsKnobUsable(loadedSettings, "defaultBranch");
243
264
  let bootstrapPlan;
244
265
  if (workspace === "worktree") {
245
266
  assertSettingsKnobUsable(loadedSettings, "worktreeBootstrap");
246
267
  bootstrapPlan = resolvedBootstrapPlan(loadedSettings.knobs?.worktreeBootstrap);
247
268
  }
269
+ const observeEntry = async () => {
270
+ const attemptedReplay = new Map();
271
+ for (;;) {
272
+ const observed = await observeBindRefs(stableRoot, loadedSettings.knobs?.defaultBranch);
273
+ if (!observed.branch) {
274
+ if (loadedSettings.knobs?.defaultBranch)
275
+ throw new FlowError("INVALID_SETTINGS", `configured defaultBranch '${loadedSettings.knobs.defaultBranch}' does not resolve`);
276
+ throw new FlowError("INTERNAL_STATE", "cannot bind: no default branch");
277
+ }
278
+ const read = await readRepositoryLifecycle(stableRoot, { observedHead: observed.ledgerHead, signal: input.signal });
279
+ const outstanding = read.contracts.filter(requiresBindRecovery);
280
+ if (outstanding.length === 0)
281
+ return { branch: observed.branch, read };
282
+ for (const lifecycle of outstanding) {
283
+ const priorAttempt = attemptedReplay.get(lifecycle.contract);
284
+ if (priorAttempt) {
285
+ if (priorAttempt.completion.status === "contained") {
286
+ const cause = priorAttempt.completion.cause;
287
+ const code = cause.code === "BIND_WORKTREE_PATH_OCCUPIED"
288
+ || cause.code === "CARRIER_MATERIALIZATION_FAILED"
289
+ || cause.code === "CARRIER_TARGET_UNAVAILABLE"
290
+ ? cause.code
291
+ : "INTERNAL_STATE";
292
+ throw new FlowError(code, cause.message);
293
+ }
294
+ throw new FlowError("INTERNAL_STATE", `bind entry replay for ${lifecycle.contract} completed without advancing its pending lifecycle state`);
295
+ }
296
+ attemptedReplay.set(lifecycle.contract, await reconcileBindContract({
297
+ cwd: stableRoot,
298
+ contractId: lifecycle.contract,
299
+ actor: input.actor,
300
+ nowMs: input.nowMs,
301
+ ...(input.signal ? { signal: input.signal } : {}),
302
+ ...(input.createWorktree ? { createWorktree: input.createWorktree } : {}),
303
+ ...(input.onProgress ? { onProgress: input.onProgress } : {}),
304
+ ...(input.bootstrapRunner ? { bootstrapRunner: input.bootstrapRunner } : {}),
305
+ ...(input.bootstrapDependencies ? { bootstrapDependencies: input.bootstrapDependencies } : {}),
306
+ dependencies: input.dependencies,
307
+ }));
308
+ }
309
+ }
310
+ };
248
311
  let selectedDraft;
249
312
  let selectedPlace;
250
313
  let selectedId;
@@ -262,7 +325,6 @@ export async function bindContract(input) {
262
325
  collided.push(id);
263
326
  continue;
264
327
  }
265
- await assertValidContractId(stableRoot, id);
266
328
  selectedDraft = bindDraftWithTaskNamespace(draft, read, persistedTaskNamespaces);
267
329
  selectedPlace = place;
268
330
  selectedId = id;
@@ -273,84 +335,100 @@ export async function bindContract(input) {
273
335
  let entry;
274
336
  let admittedScopeOverlaps = [];
275
337
  let preparedTask;
338
+ let preparedTaskProjectionSucceeded = false;
339
+ let committedHead;
340
+ let committedEntryObjectId;
341
+ let admittedRead;
276
342
  if (isTaskBound) {
277
- let lockedRead;
278
- const settled = await withPreparedTaskBind({
279
- cwd: stableRoot,
280
- taskIds: input.taskIds,
281
- signal: input.signal,
282
- prepare: async (tasks) => {
283
- lockedRead = await readRepositoryLifecycle(stableRoot);
284
- validateBindAfter(lockedRead, after);
285
- return await selectCandidate(composeTaskBindDraft({ task: tasks[0], draft: input.draft }), lockedRead);
286
- },
287
- transact: async (prepared, taskTransaction) => {
288
- if (prepared.boardRef !== target) {
289
- throw new FlowError("CONCURRENT_MODIFICATION", "locked task-board ref does not match the selected bind target");
290
- }
291
- let attemptRead = lockedRead;
292
- let lastRefusal;
293
- for (let attempt = 0; attempt < 4; attempt += 1) {
294
- admitWorkspaceOccupancy(attemptRead, workspace);
295
- validateBindAfter(attemptRead, after);
296
- assertContractAbsent(attemptRead, selectedId);
343
+ let settled;
344
+ for (let attempt = 0; attempt < 4; attempt += 1) {
345
+ let lockedRead;
346
+ let target;
347
+ const attempted = await withPreparedTaskBindSnapshot({
348
+ location: { mode: "git", root: stableRoot, surfaceRoot: stableRoot, gitDir: path.join(stableRoot, ".git") },
349
+ observeBoard: async () => {
350
+ const observed = await observeEntry();
351
+ target = normalizeTargetRef(observed.branch.branch);
352
+ lockedRead = observed.read;
353
+ admittedRead = lockedRead;
354
+ return { ref: target, head: observed.branch.head };
355
+ },
356
+ taskIds: input.taskIds,
357
+ signal: input.signal,
358
+ prepare: async (tasks) => {
359
+ return await selectCandidate(composeTaskBindDraft({ task: tasks[0], draft: input.draft }), lockedRead);
360
+ },
361
+ transact: async (prepared, taskTransaction) => {
362
+ if (prepared.boardRef !== target) {
363
+ throw new FlowError("CONCURRENT_MODIFICATION", "locked task-board ref does not match the selected bind target");
364
+ }
365
+ admitWorkspaceOccupancy(lockedRead, workspace);
366
+ const canonicalAfter = canonicalBindAfter(after, prepared.selected);
367
+ validateBindAfter(lockedRead, canonicalAfter);
368
+ assertContractAbsent(lockedRead, selectedId);
297
369
  selectedPlace = workspace === "worktree"
298
- ? allocateWorktreePlaceFromRead(attemptRead, input.randomBytes?.(attempt) ?? randomBytesDefault())
370
+ ? allocateWorktreePlaceFromRead(lockedRead, input.randomBytes?.(attempt) ?? randomBytesDefault())
299
371
  : undefined;
300
372
  entry = makeBindEntry({
301
373
  id: selectedId, at, actor: input.actor, draft: selectedDraft, base: prepared.entryBase,
302
- target, after, workspace, place: selectedPlace, bootstrap: bootstrapPlan, taskIds: prepared.taskIds,
374
+ target, after: canonicalAfter, workspace, place: selectedPlace, bootstrap: bootstrapPlan, taskIds: prepared.taskIds,
303
375
  });
304
376
  admittedScopeOverlaps = await admitScopeRegistration(stableRoot, {
305
377
  contractId: entry.contract,
306
378
  scope: bindEffectiveScope(entry),
307
379
  tree: entry.data.base,
308
380
  ...(input.exclusive === undefined ? {} : { exclusive: input.exclusive }),
309
- }, attemptRead);
310
- await revalidateBindAuthority({
311
- cwd: stableRoot,
312
- target,
313
- targetHead: prepared.expectedHead,
314
- deliveryRef: repositoryDeliveryRef(entry.contract),
315
- });
381
+ }, lockedRead);
316
382
  const written = await publishBind({
317
383
  cwd: stableRoot,
318
384
  entry,
319
- expectedLedgerHead: attemptRead.observedLedgerHead,
385
+ expectedLedgerHead: lockedRead.observedLedgerHead,
320
386
  delivery: { ref: repositoryDeliveryRef(entry.contract), head: prepared.entryBase },
321
387
  taskBoard: prepared.taskBoard,
322
388
  });
323
389
  if (written.family === "committed") {
324
390
  try {
325
- await replayPreparedTaskBindLocked(taskTransaction, prepared);
391
+ await taskTransaction.replace(prepared.taskIds.map((taskId) => {
392
+ const source = prepared.replay.linkedSources.find((candidate) => candidate.path.endsWith(`/${taskId}.md`) || candidate.path.endsWith(`\\${taskId}.md`));
393
+ if (!source)
394
+ throw new FlowError("INTERNAL_STATE", `prepared task bind has no source for ${taskId}`);
395
+ return { id: taskId, text: source.text };
396
+ }));
397
+ const staged = await runGitProcess(stableRoot, ["add", "--", ...prepared.replay.paths]);
398
+ if (staged.status !== 0 || staged.error)
399
+ throw staged.error ?? new Error(staged.stderr);
400
+ preparedTaskProjectionSucceeded = true;
326
401
  }
327
402
  catch {
328
403
  // Authority is already committed. The ordinary derived replay
329
404
  // below owns truthful retry/contained classification.
330
405
  }
331
- return written;
332
406
  }
333
407
  if (written.family === "indeterminate")
334
408
  committedOrThrow(written, "BIND_TRANSACTION");
335
- lastRefusal = written;
336
- attemptRead = await readRepositoryLifecycle(stableRoot);
337
- }
338
- return lastRefusal;
339
- },
340
- });
409
+ return written;
410
+ },
411
+ });
412
+ if (attempted.result.family === "committed") {
413
+ settled = attempted;
414
+ break;
415
+ }
416
+ }
417
+ if (!settled)
418
+ throw new FlowError("CONCURRENT_MODIFICATION", "BIND_TRANSACTION_REFUSED: repository authority changed during four bind attempts");
341
419
  preparedTask = settled.preparation;
342
- committedOrThrow(settled.result, "BIND_TRANSACTION");
420
+ const committed = committedOrThrow(settled.result, "BIND_TRANSACTION");
421
+ committedHead = committed.ledgerHead;
422
+ committedEntryObjectId = committed.entryObjectId;
343
423
  }
344
424
  else {
345
425
  let committed;
346
426
  for (let attempt = 0; attempt < 4; attempt += 1) {
347
- const [read, branch] = await Promise.all([
348
- readRepositoryLifecycle(stableRoot),
349
- resolveDefaultBranch(stableRoot),
350
- ]);
351
- if (!branch || normalizeTargetRef(branch.branch) !== target) {
352
- throw new FlowError("CONCURRENT_MODIFICATION", "bind target changed during admission");
353
- }
427
+ const observed = await observeEntry();
428
+ const branch = observed.branch;
429
+ const target = normalizeTargetRef(branch.branch);
430
+ const read = observed.read;
431
+ admittedRead = read;
354
432
  validateBindAfter(read, after);
355
433
  await selectCandidate(ordinaryDraft, read);
356
434
  entry = makeBindEntry({
@@ -363,12 +441,6 @@ export async function bindContract(input) {
363
441
  tree: entry.data.base,
364
442
  ...(input.exclusive === undefined ? {} : { exclusive: input.exclusive }),
365
443
  }, read);
366
- await revalidateBindAuthority({
367
- cwd: stableRoot,
368
- target,
369
- targetHead: branch.head,
370
- deliveryRef: repositoryDeliveryRef(entry.contract),
371
- });
372
444
  const bindWrite = await publishBind({
373
445
  cwd: stableRoot,
374
446
  entry,
@@ -377,6 +449,8 @@ export async function bindContract(input) {
377
449
  });
378
450
  if (bindWrite.family === "committed") {
379
451
  committed = bindWrite;
452
+ committedHead = bindWrite.ledgerHead;
453
+ committedEntryObjectId = bindWrite.entryObjectId;
380
454
  break;
381
455
  }
382
456
  if (bindWrite.family === "indeterminate")
@@ -386,7 +460,33 @@ export async function bindContract(input) {
386
460
  throw new FlowError("CONCURRENT_MODIFICATION", "BIND_TRANSACTION_REFUSED: repository authority changed during four bind attempts");
387
461
  }
388
462
  }
389
- return await reconcileCommittedBind({
463
+ const repository = advanceRepositorySnapshotWithBind({
464
+ repository: admittedRead,
465
+ ledgerHead: repositoryLedgerHead(committedHead),
466
+ entryObjectId: committedEntryObjectId,
467
+ entry: entry,
468
+ });
469
+ const committedContract = lookupCurrentContract(repository, entry.contract);
470
+ if (committedContract.status !== "healthy") {
471
+ throw new FlowError("INTERNAL_STATE", `committed bind ${entry.contract} is unavailable (${committedContract.status})`);
472
+ }
473
+ const carrierTarget = entry.data.workspace === "here"
474
+ ? { status: "available", root: stableRoot, path: `${stableRoot}/.keiyaku/KEIYAKU.md`, workspace: "here" }
475
+ : { status: "unavailable", workspace: "worktree", reason: `contract ${entry.contract} has no verified registered engine worktree` };
476
+ const snapshot = {
477
+ stableRoot,
478
+ observation: {
479
+ repository,
480
+ lifecycle: committedContract.model,
481
+ target: carrierTarget,
482
+ pins: {
483
+ repositoryLedgerHead: repositoryLedgerHead(committedHead),
484
+ target: { ref: entry.data.target, head: entry.data.base },
485
+ delivery: { ref: repositoryDeliveryRef(entry.contract), head: entry.data.base },
486
+ },
487
+ },
488
+ };
489
+ return await reconcileCommittedBindFromSnapshot({
390
490
  cwd: stableRoot,
391
491
  contractId: entry.contract,
392
492
  actor: input.actor,
@@ -397,6 +497,7 @@ export async function bindContract(input) {
397
497
  ...(input.bootstrapRunner ? { bootstrapRunner: input.bootstrapRunner } : {}),
398
498
  ...(input.bootstrapDependencies ? { bootstrapDependencies: input.bootstrapDependencies } : {}),
399
499
  ...(preparedTask ? { preparedTask } : {}),
500
+ ...(preparedTaskProjectionSucceeded ? { preparedTaskProjectionSucceeded } : {}),
400
501
  dependencies: input.dependencies,
401
- }, admittedScopeOverlaps);
502
+ }, admittedScopeOverlaps, snapshot);
402
503
  }
@@ -8,7 +8,7 @@ import { ACTOR_IDENTITY } from "../../keiyaku.js";
8
8
  import { appendDebugBlock } from "../../telemetry/debug-log.js";
9
9
  import { logInfo } from "../../telemetry/logger.js";
10
10
  import { withResolvedAddress } from "../addressing.js";
11
- import { assertProjectionAlias, PROJECTION_ADOPTION_TIMEOUT_MS, observeProjectionLife, waitForProjection, } from "../projection/index.js";
11
+ import { assertProjectionAlias, observeProjectionLife, waitForProjection, } from "../projection/index.js";
12
12
  import { normalizeFilesystemCoordinate } from "../../fs/path-coordinate.js";
13
13
  import { requireResumableArtifact } from "../transcripts.js";
14
14
  import { stableRepoRoot } from "../worktree-path.js";
@@ -56,7 +56,7 @@ export async function runCall(input) {
56
56
  const artifactHome = await fs.realpath(reviveArtifact.storageRoot);
57
57
  const addressed = normalizeFilesystemCoordinate(input.repo ?? input.cwd);
58
58
  const observation = await observeGitRepository(addressed);
59
- const coordinate = assertGitObservation(observation, "revive");
59
+ const coordinate = assertGitObservation(observation, "fork");
60
60
  if (coordinate === "repository") {
61
61
  const addressedRoot = await stableRepoRoot(addressed);
62
62
  if (addressedRoot !== artifactHome) {
@@ -74,7 +74,7 @@ export async function runCall(input) {
74
74
  const title = requireText("title", input.title);
75
75
  const task = requireText("task", input.task);
76
76
  if (input.incognito && reviveArtifact) {
77
- throw new FlowError("EMPTY_PARAM", "Parameter 'incognito' cannot be used with 'reviveArtifact' — incognito calls cannot be revived or persist conversation history.");
77
+ throw new FlowError("EMPTY_PARAM", "Parameter 'incognito' cannot be used with a fork source artifact — incognito calls cannot be forked or persist conversation history.");
78
78
  }
79
79
  const keiyakuContext = await readCallKeiyakuContext(cwd, {
80
80
  bare: input.bare,
@@ -92,8 +92,8 @@ export async function runCall(input) {
92
92
  task,
93
93
  context: input.context,
94
94
  referenceFiles,
95
- currentIntent: keiyakuContext.currentIntent,
96
95
  commissioned: keiyakuContext.address.binding.kind === "commission",
96
+ worktree: keiyakuContext.effectiveCwd,
97
97
  });
98
98
  const launchSnapshot = reviveArtifact
99
99
  ? input.effort !== undefined
@@ -134,6 +134,7 @@ export async function runCall(input) {
134
134
  ? { kind: "repository" }
135
135
  : { kind: "user" },
136
136
  binding: keiyakuContext.address.binding,
137
+ carrier: keiyakuContext.carrier,
137
138
  title,
138
139
  context: input.context,
139
140
  ...(input.mode === "call" && input.alias !== undefined ? { alias: assertProjectionAlias(input.alias) } : {}),
@@ -144,7 +145,6 @@ export async function runCall(input) {
144
145
  id: launched.projection.id,
145
146
  phase: observeProjectionLife(launched.projection.dir, {
146
147
  nowMs: Date.now(),
147
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
148
148
  }).phase,
149
149
  ...(launched.projection.alias ? { alias: launched.projection.alias } : {}),
150
150
  ...(launched.projection.previousAliasTarget ? { previousAliasTarget: launched.projection.previousAliasTarget } : {}),
@@ -206,7 +206,6 @@ export async function runCall(input) {
206
206
  id: execution.projection.id,
207
207
  phase: observeProjectionLife(execution.projection.dir, {
208
208
  nowMs: Date.now(),
209
- startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
210
209
  }).phase,
211
210
  };
212
211
  }
@@ -222,7 +221,7 @@ export async function runCall(input) {
222
221
  launchSnapshot: subagentOutcome.launchSnapshot,
223
222
  ...(responsePath ? { responsePath } : {}),
224
223
  ...(artifactId ? { artifactId } : {}),
225
- ...(reviveArtifact ? { revivedArtifactId: reviveArtifact.artifactId } : {}),
224
+ ...(reviveArtifact ? { sourceArtifactId: reviveArtifact.artifactId } : {}),
226
225
  projectionOwnsResponsePersistence: execution.projection !== undefined,
227
226
  meta: {
228
227
  ...(subagentOutcome.warnings.length > 0 ? { warnings: subagentOutcome.warnings.map(renderSubagentWarning) } : {}),
@@ -1,11 +1,39 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
2
3
  import { FlowError } from "../../flow-error.js";
3
4
  import { assertGitObservation, observeGitRepository } from "../../git/branches.js";
4
5
  import { AddressResolutionError, buildResolutionAttempt, buildUncommissionedAddress, buildUncommissionedRepositoryAddress, resolveCallWorktreeAddressFromRead, resolveExplicitContractAddressFromRead, withResolvedAddress, } from "../addressing.js";
5
6
  import { normalizeFilesystemCoordinate } from "../../fs/path-coordinate.js";
6
7
  import { readRepositoryLifecycle, } from "../repository-ledger/read-model.js";
7
8
  import { isTerminalState } from "../status/lifecycle.js";
9
+ import { verifyCarrier } from "../contract-carrier.js";
8
10
  import { findRegisteredContractWorktreePathFromModel, stableRepoRoot, } from "../worktree-path.js";
11
+ import { KEIYAKU_FILE } from "../../keiyaku.js";
12
+ function throwCarrierDispatchRefusal(contractId, verification) {
13
+ let status;
14
+ if (verification.status === "missing") {
15
+ status = "missing";
16
+ }
17
+ else if (verification.status === "invalid-header") {
18
+ status = "incomplete-replay";
19
+ }
20
+ else if (verification.status === "identity-mismatch") {
21
+ status = verification.actual.contractId !== verification.expected.contractId ? "wrong-contract" : "lagging";
22
+ }
23
+ else {
24
+ status = "modified";
25
+ }
26
+ const recoveryCommand = `keiyaku bind --contract ${contractId}`;
27
+ throw new FlowError("CARRIER_MATERIALIZATION_FAILED", `contract ${contractId} carrier is ${status}; recover with ${recoveryCommand}`, {
28
+ facts: {
29
+ kind: "carrier_dispatch_refusal",
30
+ contractId,
31
+ status,
32
+ carrierPath: verification.target,
33
+ recoveryCommand,
34
+ },
35
+ });
36
+ }
9
37
  export async function readCallKeiyakuContext(executionCwd, input, ports = {}) {
10
38
  executionCwd = normalizeFilesystemCoordinate(executionCwd);
11
39
  input.repoCwd = input.repoCwd === undefined ? undefined : normalizeFilesystemCoordinate(input.repoCwd);
@@ -148,49 +176,34 @@ export async function readCallKeiyakuContext(executionCwd, input, ports = {}) {
148
176
  if (isTerminalState(address.lifecycle)) {
149
177
  throw new FlowError("EMPTY_PARAM", `commission ${contractId} is terminal and cannot launch an Akuma`);
150
178
  }
151
- const bind = model.history[0]?.entry;
152
- if (!bind || bind.kind !== "bind") {
153
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} active ledger is missing bind entry`);
154
- }
155
179
  const effectiveCwd = model.resources.worktree
156
180
  ? await findRegisteredContractWorktreePathFromModel(ledgerCwd, model)
157
181
  : await fs.realpath(ledgerCwd);
158
182
  if (!effectiveCwd) {
159
183
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} has no verified registered engine worktree`);
160
184
  }
185
+ const carrier = await verifyCarrier({
186
+ target: path.join(effectiveCwd, KEIYAKU_FILE),
187
+ lifecycle: model,
188
+ });
189
+ if (carrier.status !== "current")
190
+ throwCarrierDispatchRefusal(contractId, carrier);
161
191
  const executionAddress = { ...address, workdir: effectiveCwd };
162
- const openArc = model.phase === "active" && model.history.some(({ entry }) => entry.kind === "arc")
163
- ? model.intent
164
- : null;
165
- const currentIntent = openArc
166
- ? {
167
- source: "arc",
168
- arc: openArc.arc,
169
- title: openArc.title,
170
- objective: openArc.objective,
171
- brief: openArc.brief ?? "",
172
- }
173
- : model.history.some(({ entry }) => entry.kind === "arc")
174
- ? undefined
175
- : {
176
- source: "bind",
177
- arc: 0,
178
- title: bind.data.title,
179
- context: bind.data.context,
180
- objective: bind.data.objective,
181
- design: bind.data.design,
182
- scope: [...model.effectiveScope],
183
- criteria: [...bind.data.criteria],
184
- verification: [...bind.data.verification],
185
- extensions: bind.data.extensions.map((extension) => ({ ...extension })),
186
- };
192
+ const { contractId: verifiedContractId, contractHead: verifiedContractHead, bodyDigest } = carrier.actual;
187
193
  return {
188
194
  activeKeiyaku: true,
189
195
  effectiveCwd,
190
196
  historyCwd: ledgerCwd,
191
197
  profileCwd: effectiveCwd,
192
198
  projectionCwd: ledgerCwd,
193
- currentIntent,
199
+ carrier: {
200
+ path: ".keiyaku/KEIYAKU.md",
201
+ identity: {
202
+ contractId: verifiedContractId,
203
+ contractHead: verifiedContractHead,
204
+ bodyDigest,
205
+ },
206
+ },
194
207
  address: executionAddress,
195
208
  };
196
209
  });