@astrosheep/keiyaku 2.9.11 → 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 (218) hide show
  1. package/README.md +79 -85
  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 +17 -4
  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/bind/meta.js +4 -4
  17. package/build/cli/commands/contract/petition/handler.js +2 -1
  18. package/build/cli/commands/contract/petition/meta.js +2 -2
  19. package/build/cli/commands/contract/renew/handler.js +44 -3
  20. package/build/cli/commands/contract/renew/meta.js +5 -4
  21. package/build/cli/commands/metadata.js +14 -10
  22. package/build/cli/commands/projection/call/handler.js +1 -1
  23. package/build/cli/commands/projection/call/meta.js +1 -1
  24. package/build/cli/commands/projection/catalog.js +2 -2
  25. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  26. package/build/cli/commands/projection/fork/meta.js +12 -0
  27. package/build/cli/commands/projection/history/handler.js +46 -2
  28. package/build/cli/commands/projection/history/meta.js +3 -3
  29. package/build/cli/commands/projection/kill/handler.js +4 -5
  30. package/build/cli/commands/projection/kill/meta.js +2 -2
  31. package/build/cli/commands/projection/status/handler.js +29 -13
  32. package/build/cli/commands/projection/status/meta.js +14 -3
  33. package/build/cli/commands/projection/tell/handler.js +8 -4
  34. package/build/cli/commands/projection/tell/meta.js +1 -1
  35. package/build/cli/commands/projection/wait/handler.js +45 -20
  36. package/build/cli/commands/projection/wait/meta.js +1 -1
  37. package/build/cli/commands/shared.js +2 -2
  38. package/build/cli/commands/system/catalog.js +2 -0
  39. package/build/cli/commands/task/add/handler.js +28 -12
  40. package/build/cli/commands/task/add/meta.js +18 -6
  41. package/build/cli/commands/task/catalog.js +4 -0
  42. package/build/cli/commands/task/compose/handler.js +18 -0
  43. package/build/cli/commands/task/compose/meta.js +10 -0
  44. package/build/cli/commands/task/doctor/handler.js +2 -3
  45. package/build/cli/commands/task/doctor/meta.js +2 -2
  46. package/build/cli/commands/task/done/meta.js +2 -2
  47. package/build/cli/commands/task/drop/handler.js +7 -2
  48. package/build/cli/commands/task/drop/meta.js +6 -3
  49. package/build/cli/commands/task/hold/meta.js +2 -2
  50. package/build/cli/commands/task/log/meta.js +2 -2
  51. package/build/cli/commands/task/ls/meta.js +2 -2
  52. package/build/cli/commands/task/note/handler.js +6 -0
  53. package/build/cli/commands/task/note/meta.js +11 -0
  54. package/build/cli/commands/task/resume/meta.js +2 -2
  55. package/build/cli/commands/task/shared.js +29 -15
  56. package/build/cli/commands/task/show/meta.js +2 -2
  57. package/build/cli/commands/task/start/meta.js +2 -2
  58. package/build/cli/commands/task/stop/meta.js +2 -2
  59. package/build/cli/commands/task/update/meta.js +3 -2
  60. package/build/cli/commands/verification/handler.js +43 -0
  61. package/build/cli/commands/verification/meta.js +13 -0
  62. package/build/cli/completion.js +16 -13
  63. package/build/cli/flags.js +48 -12
  64. package/build/cli/help.js +29 -30
  65. package/build/cli/index.js +14 -8
  66. package/build/cli/parse-flags.js +60 -21
  67. package/build/cli/parse-metadata.js +10 -4
  68. package/build/cli/parse-selectors.js +22 -4
  69. package/build/cli/parse.js +43 -30
  70. package/build/cli/projection-address.js +7 -7
  71. package/build/cli/render/arc.js +12 -3
  72. package/build/cli/render/audit.js +2 -2
  73. package/build/cli/render/call.js +4 -4
  74. package/build/cli/render/kanshi.js +122 -85
  75. package/build/cli/render/misc.js +2 -2
  76. package/build/cli/render/petition.js +16 -1
  77. package/build/cli/render/projection-history.js +17 -4
  78. package/build/cli/render/shared.js +5 -5
  79. package/build/cli/render/status-indicator.js +47 -0
  80. package/build/cli/render/status.js +106 -76
  81. package/build/cli/render/success-response.js +20 -5
  82. package/build/cli/render/task.js +33 -5
  83. package/build/cli/render/tell.js +10 -4
  84. package/build/cli/render/tool-command-normalization.js +137 -0
  85. package/build/cli/render/tool-presentation.js +2 -1
  86. package/build/cli/render/verification.js +23 -0
  87. package/build/cli/render/wait.js +101 -33
  88. package/build/cli/types.js +2 -1
  89. package/build/config/akuma-loader.js +21 -1
  90. package/build/config/settings/knobs.js +7 -1
  91. package/build/config/settings/schema.js +10 -5
  92. package/build/core/addressing.js +3 -3
  93. package/build/core/amend.js +102 -17
  94. package/build/core/arc.js +4 -4
  95. package/build/core/audit/candidate.js +10 -18
  96. package/build/core/audit/coordinates.js +12 -17
  97. package/build/core/audit/evidence.js +6 -4
  98. package/build/core/audit/facade.js +3 -3
  99. package/build/core/audit/report.js +1 -1
  100. package/build/core/bind-reconciliation.js +42 -23
  101. package/build/core/bind.js +182 -81
  102. package/build/core/call/call.js +6 -7
  103. package/build/core/call/context.js +46 -33
  104. package/build/core/call/execution.js +21 -6
  105. package/build/core/call/prompt.js +11 -42
  106. package/build/core/contract-carrier-runtime.js +209 -105
  107. package/build/core/contract-carrier.js +156 -73
  108. package/build/core/contract-view.js +43 -6
  109. package/build/core/contract.js +2 -1
  110. package/build/core/derived-replay.js +168 -35
  111. package/build/core/draft.js +60 -27
  112. package/build/core/forfeit.js +1 -1
  113. package/build/core/lifecycle-history.js +13 -0
  114. package/build/core/lifecycle-recovery.js +32 -27
  115. package/build/core/lifecycle-runner.js +14 -14
  116. package/build/core/log.js +1 -1
  117. package/build/core/outcome-base.js +2 -2
  118. package/build/core/projection/akuma-name.js +43 -0
  119. package/build/core/projection/generation/database.js +141 -79
  120. package/build/core/projection/generation/ledger.js +91 -0
  121. package/build/core/projection/generation/model.js +19 -14
  122. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  123. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  124. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  125. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  126. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  127. package/build/core/projection/generation/projection-generation-process.js +16 -15
  128. package/build/core/projection/generation/projection-generation-runner.js +132 -76
  129. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  130. package/build/core/projection/generation/protocol.js +68 -0
  131. package/build/core/projection/generation/store.js +17 -7
  132. package/build/core/projection/generation/transitions.js +24 -14
  133. package/build/core/projection/index.js +5 -5
  134. package/build/core/projection/projection-core.js +6 -2
  135. package/build/core/projection/projection-execution-observer.js +1 -1
  136. package/build/core/projection/projection-history.js +8 -8
  137. package/build/core/projection/projection-kill.js +20 -10
  138. package/build/core/projection/projection-life-observer.js +8 -8
  139. package/build/core/projection/projection-mint.js +6 -8
  140. package/build/core/projection/projection-status-observation.js +77 -8
  141. package/build/core/projection/projection-status.js +151 -4
  142. package/build/core/projection/projection-wait.js +48 -29
  143. package/build/core/projection/projection-wake.js +28 -88
  144. package/build/core/projection/tell/launch-store.js +1 -1
  145. package/build/core/projection/tell/store.js +1 -1
  146. package/build/core/renew-build.js +204 -74
  147. package/build/core/renew-plan.js +34 -9
  148. package/build/core/renew-rewrite.js +56 -15
  149. package/build/core/renew.js +39 -35
  150. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  151. package/build/core/repository-ledger/claim-evidence.js +1 -1
  152. package/build/core/repository-ledger/codec.js +11 -5
  153. package/build/core/repository-ledger/current-state-store.js +464 -349
  154. package/build/core/repository-ledger/fold-repository.js +5 -6
  155. package/build/core/repository-ledger/identity.js +60 -0
  156. package/build/core/repository-ledger/inventory.js +3 -3
  157. package/build/core/repository-ledger/read-model.js +57 -9
  158. package/build/core/repository-ledger/write-transaction.js +24 -17
  159. package/build/core/run-control/connection-bound-close.js +41 -0
  160. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  161. package/build/core/run-control/detached-process.js +14 -0
  162. package/build/core/run-control/index.js +6 -0
  163. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  164. package/build/core/run-control/runner-lease.js +172 -0
  165. package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
  166. package/build/core/seal.js +44 -32
  167. package/build/core/settlement/claim-delivery.js +22 -9
  168. package/build/core/settlement/claim.js +8 -6
  169. package/build/core/settlement/petition-claim-gates.js +9 -8
  170. package/build/core/settlement/petition-head-guard.js +5 -6
  171. package/build/core/settlement/petition-preview.js +21 -5
  172. package/build/core/settlement/petition.js +51 -17
  173. package/build/core/settlement/settlement.js +20 -15
  174. package/build/core/settlement/verdict.js +2 -2
  175. package/build/core/settlement/verification.js +492 -364
  176. package/build/core/status/board.js +184 -75
  177. package/build/core/status/drift.js +8 -10
  178. package/build/core/task/board.js +1 -0
  179. package/build/core/task/commands.js +25 -0
  180. package/build/core/task/compose.js +328 -0
  181. package/build/core/task/document.js +102 -39
  182. package/build/core/task/index.js +4 -3
  183. package/build/core/task/settlement-git.js +70 -30
  184. package/build/core/task/settlement-policy.js +27 -14
  185. package/build/core/task/source-board.js +1 -0
  186. package/build/core/task/task-bind-preparation.js +59 -9
  187. package/build/core/task/task-contract.js +2 -2
  188. package/build/core/task/task-git-store.js +1 -0
  189. package/build/core/task/task-store-repository.js +2 -0
  190. package/build/core/task/task.js +2 -2
  191. package/build/core/transcripts.js +4 -4
  192. package/build/core/verification-declaration.js +95 -0
  193. package/build/core/worktree-bootstrap.js +1 -1
  194. package/build/core/worktree-path.js +7 -11
  195. package/build/flow-error.js +15 -2
  196. package/build/generated/version.js +2 -2
  197. package/build/git/branches.js +41 -70
  198. package/build/git/commits.js +92 -21
  199. package/build/git/core.js +9 -161
  200. package/build/git/diff/preview.js +4 -4
  201. package/build/git/diff/read.js +4 -4
  202. package/build/git/diff/structured.js +5 -5
  203. package/build/git/process.js +229 -0
  204. package/build/git/refs.js +30 -5
  205. package/build/git/staging.js +39 -115
  206. package/build/git/streaming-batch.js +9 -16
  207. package/build/git/worktree.js +104 -91
  208. package/build/index.js +7 -0
  209. package/package.json +2 -2
  210. package/skills/keiyaku-akuma/SKILL.md +27 -21
  211. package/skills/keiyaku-task/SKILL.md +61 -5
  212. package/skills/keiyaku-workflow/SKILL.md +81 -26
  213. package/build/cli/commands/projection/revive/meta.js +0 -12
  214. package/build/core/projection/projection-life-protocol.js +0 -115
  215. package/build/core/projection/projection-runner-lock.js +0 -382
  216. package/build/core/projection/tell/database.js +0 -127
  217. package/build/core/settlement/verification-coordination.js +0 -305
  218. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -5,6 +5,9 @@ import { FlowError } from "../flow-error.js";
5
5
  import { runGitProcess } from "../git/core.js";
6
6
  import { KEIYAKU_FILE } from "../keiyaku.js";
7
7
  import { inventoryIgnorePatternsForWorktree } from "./repository-ledger/inventory.js";
8
+ import { isJsonObject } from "./outcome-base.js";
9
+ import { carrierRetirementInputIdentity, deliveryRefInputIdentity, narrowIgnoreInputIdentity, renewDeliveryInputIdentity, sameInputIdentity, taskBoardInputIdentity, targetWorktreeInputIdentity, worktreeInputIdentity, } from "./repository-ledger/identity.js";
10
+ import { resolveAcceptedLifecycleTransaction, } from "./repository-ledger/accepted-tail.js";
8
11
  import { readTaskBoardGeneration, reconcileTaskForfeit, replayClaimTasks, replayCommittedTaskBind, } from "./task/index.js";
9
12
  function terminalEntry(model) {
10
13
  return [...model.history].reverse().find(({ entry }) => (entry.kind === "claim" || entry.kind === "forfeit"));
@@ -13,10 +16,101 @@ function receipt(model, surface, status, facts) {
13
16
  return {
14
17
  surface,
15
18
  status,
16
- authoritativeHead: model.observedLedgerHead,
19
+ authoritativeHead: model.contractHead,
17
20
  ...(facts ? { facts } : {}),
18
21
  };
19
22
  }
23
+ function currentTransaction(repository, model) {
24
+ const node = repository.topology.nodes.find((candidate) => (candidate.type === "transaction"
25
+ && candidate.contract === model.contract
26
+ && candidate.finalEntryObjectId === model.contractHead));
27
+ if (!node || node.type !== "transaction") {
28
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} has no accepted transaction for ${model.contractHead}`);
29
+ }
30
+ const accepted = resolveAcceptedLifecycleTransaction({
31
+ read: repository,
32
+ transactionObjectId: node.objectId,
33
+ expectedContract: model.contract,
34
+ });
35
+ if (accepted.status !== "accepted") {
36
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} transaction ${node.objectId} is not replayable (${accepted.reason})`);
37
+ }
38
+ return accepted;
39
+ }
40
+ function obligation(transactionObjectId, surface, expected) {
41
+ return {
42
+ transactionObjectId,
43
+ surface,
44
+ expected,
45
+ idempotencyKey: `${transactionObjectId}:${surface}`,
46
+ };
47
+ }
48
+ /**
49
+ * Purely derive every required surface for the accepted transaction that
50
+ * advanced this folded contract head. No journal, carrier bytes, or filesystem
51
+ * observation participates in this calculation.
52
+ */
53
+ export function deriveReplayObligations(input) {
54
+ const { repository, lifecycle: model, target, expectedCarrierIdentity } = input;
55
+ const transaction = input.transaction ?? currentTransaction(repository, model);
56
+ const base = { contractId: model.contract, contractHead: model.contractHead };
57
+ const terminal = terminalEntry(model);
58
+ const required = [];
59
+ const taskIds = model.resources.tasks.map(({ taskId }) => taskId);
60
+ if (taskIds.length > 0) {
61
+ required.push(obligation(transaction.transactionObjectId, "task-board", taskBoardInputIdentity({
62
+ ...base, taskIds, phase: model.phase,
63
+ })));
64
+ }
65
+ if (terminal) {
66
+ required.push(obligation(transaction.transactionObjectId, "carrier-retirement", carrierRetirementInputIdentity({
67
+ ...base,
68
+ target: target.status === "available" ? target.path : `${target.workspace}:${target.reason}`,
69
+ })), obligation(transaction.transactionObjectId, "worktree", worktreeInputIdentity({
70
+ ...base,
71
+ deliveryHead: model.pins.delivery,
72
+ })), obligation(transaction.transactionObjectId, "delivery-ref", deliveryRefInputIdentity({
73
+ ...base, deliveryRef: model.resources.deliveryRef,
74
+ })));
75
+ if (terminal.entry.kind === "claim" && terminal.entry.data && typeof terminal.entry.data.target === "string") {
76
+ required.push(obligation(transaction.transactionObjectId, "target-worktree", targetWorktreeInputIdentity({
77
+ ...base,
78
+ target: terminal.entry.data.target,
79
+ targetHead: terminal.entry.data.targetHead,
80
+ mergeCommit: terminal.entry.data.mergeCommit,
81
+ })));
82
+ }
83
+ if (target.status === "available") {
84
+ required.push(obligation(transaction.transactionObjectId, "narrow-ignore", narrowIgnoreInputIdentity({
85
+ ...base,
86
+ inventory: JSON.stringify(inventoryIgnorePatternsForWorktree(repository, model)),
87
+ occupancy: `${target.workspace}:${target.root}`,
88
+ })));
89
+ }
90
+ return required;
91
+ }
92
+ const deliveryHead = model.pins.delivery;
93
+ if (deliveryHead !== null) {
94
+ required.push(obligation(transaction.transactionObjectId, "worktree", worktreeInputIdentity({ ...base, deliveryHead })));
95
+ }
96
+ const patterns = inventoryIgnorePatternsForWorktree(repository, model);
97
+ required.push(obligation(transaction.transactionObjectId, "narrow-ignore", narrowIgnoreInputIdentity({
98
+ ...base,
99
+ inventory: JSON.stringify(patterns),
100
+ occupancy: `${target.workspace}:${target.status === "available" ? target.root : target.reason}`,
101
+ })), obligation(transaction.transactionObjectId, "carrier", expectedCarrierIdentity));
102
+ if (transaction.verb === "renew") {
103
+ const renew = transaction.entries.find((entry) => entry.kind === "renew");
104
+ if (renew?.kind === "renew") {
105
+ required.push(obligation(transaction.transactionObjectId, "renew-delivery", renewDeliveryInputIdentity({
106
+ ...base,
107
+ oldHead: renew.data.oldHead,
108
+ newHead: renew.data.newHead,
109
+ })));
110
+ }
111
+ }
112
+ return required;
113
+ }
20
114
  async function gitExit(cwd, args) {
21
115
  const result = await runGitProcess(cwd, args);
22
116
  if (result.error || (result.status !== 0 && result.status !== 1)) {
@@ -83,14 +177,9 @@ async function replayTaskBoard(cwd, model, adapters) {
83
177
  taskIds,
84
178
  updatedAt: terminal.entry.at,
85
179
  });
86
- const warnings = await adapters.replayClaimTarget({
87
- target: terminal.entry.data.target,
88
- targetHead: terminal.entry.data.targetHead,
89
- mergeCommit: replay.commit,
90
- });
91
180
  return {
92
181
  status: taskIds.length === 0 ? "already-present" : replay.worktree === "ref-only" ? "ref-only" : "created",
93
- facts: { taskCount: replay.taskCount, commit: replay.commit, worktree: replay.worktree, warnings: [...warnings] },
182
+ facts: { taskCount: replay.taskCount, commit: replay.commit, worktree: replay.worktree },
94
183
  };
95
184
  }
96
185
  if (terminal?.entry.kind === "forfeit") {
@@ -113,10 +202,10 @@ async function replayTaskBoard(cwd, model, adapters) {
113
202
  const status = await replayCommittedTaskBind(cwd, bind.data.base, taskIds);
114
203
  return { status, facts: { taskCount: taskIds.length } };
115
204
  }
116
- function recordSurface(journal, model, surface, run) {
205
+ function recordSurface(journal, replay, run) {
117
206
  return journal.run({
118
- effect: `${surface}-projection`,
119
- idempotencyKey: `${model.contract}:${model.observedLedgerHead}:${surface}`,
207
+ effect: `${replay.surface}-projection`,
208
+ idempotencyKey: replay.idempotencyKey,
120
209
  run: async () => structuredClone(await run()),
121
210
  });
122
211
  }
@@ -124,29 +213,65 @@ function recordSurface(journal, model, surface, run) {
124
213
  export async function replayCommittedDerivedState(input) {
125
214
  const { lifecycle: model, journal } = input;
126
215
  const terminal = terminalEntry(model);
216
+ const transaction = input.transaction ?? currentTransaction(input.repository, model);
217
+ const expectedCarrierIdentity = input.adapters.expectedCarrierIdentity(model);
218
+ if (expectedCarrierIdentity.contractId !== model.contract
219
+ || expectedCarrierIdentity.contractHead !== model.contractHead) {
220
+ throw new FlowError("INTERNAL_STATE", "carrier identity semantics did not derive the folded contract coordinate");
221
+ }
222
+ const obligations = new Map(deriveReplayObligations({
223
+ ...input,
224
+ transaction,
225
+ expectedCarrierIdentity,
226
+ }).map((item) => [item.surface, item]));
127
227
  let projectedTarget = input.target;
128
228
  const receipts = [];
129
- const run = async (surface, operation) => {
130
- const row = await recordSurface(journal, model, surface, operation);
229
+ const run = async (surface, operation, current) => {
230
+ const replay = obligations.get(surface);
231
+ if (!replay)
232
+ return;
233
+ const row = await recordSurface(journal, replay, async () => {
234
+ const actual = await input.adapters.inspectSurface({ obligation: replay });
235
+ if (sameInputIdentity(actual, replay.expected)) {
236
+ return current?.() ?? receipt(model, surface, "already-present", { inputIdentity: replay.expected });
237
+ }
238
+ return operation();
239
+ });
131
240
  if (row.status === "succeeded")
132
241
  receipts.push(row.receipt);
133
242
  };
134
- await run("status", async () => receipt(model, "status", "already-present", { phase: model.phase }));
135
- await run("registry", async () => receipt(model, "registry", terminal ? "retired" : "already-present", {
136
- source: "repository-ledger-fold",
137
- effectiveScope: [...model.effectiveScope],
138
- }));
139
243
  await run("task-board", async () => {
140
244
  const replay = await replayTaskBoard(input.cwd, model, input.adapters);
141
245
  return receipt(model, "task-board", replay.status, replay.facts);
142
246
  });
247
+ if (terminal?.entry.kind === "claim") {
248
+ const claim = terminal.entry;
249
+ await run("target-worktree", async () => {
250
+ const taskRow = journal.all().find((row) => row.effect === "task-board-projection" && row.status === "succeeded");
251
+ const taskFacts = taskRow?.status === "succeeded" ? taskRow.receipt?.facts : undefined;
252
+ const commit = isJsonObject(taskFacts) && typeof taskFacts.commit === "string"
253
+ ? taskFacts.commit
254
+ : claim.data.mergeCommit;
255
+ const warnings = await input.adapters.replayClaimTarget({
256
+ target: claim.data.target,
257
+ targetHead: claim.data.targetHead,
258
+ mergeCommit: commit,
259
+ });
260
+ return receipt(model, "target-worktree", warnings.length === 0 ? "created" : "mismatch", {
261
+ target: claim.data.target,
262
+ targetHead: claim.data.targetHead,
263
+ mergeCommit: commit,
264
+ warnings: [...warnings],
265
+ });
266
+ });
267
+ }
143
268
  if (terminal) {
144
269
  let carrierRetired = false;
145
- await run("carrier", async () => {
270
+ await run("carrier-retirement", async () => {
146
271
  const status = await input.adapters.retireContractView({ lifecycle: model, target: input.target });
147
- carrierRetired = true;
148
- return receipt(model, "carrier", status);
272
+ return receipt(model, "carrier-retirement", status);
149
273
  });
274
+ carrierRetired = journal.all().some((row) => (row.effect === "carrier-retirement-projection" && row.status === "succeeded"));
150
275
  await run("narrow-ignore", async () => {
151
276
  if (!carrierRetired) {
152
277
  throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "cannot retire narrow ignore before carrier retirement");
@@ -159,19 +284,23 @@ export async function replayCommittedDerivedState(input) {
159
284
  });
160
285
  await run("worktree", async () => {
161
286
  const cleanup = await input.adapters.cleanupWorkspace(input.cwd, model);
162
- if (cleanup.worktreeRemoveFailure)
163
- throw new FlowError("INTERNAL_STATE", cleanup.worktreeRemoveFailure.reason);
164
287
  return receipt(model, "worktree", cleanup.absent.includes("worktree") ? "already-present" : "retired", {
165
288
  absent: cleanup.absent,
166
289
  ...(cleanup.removedWorktreePath ? { removedWorktreePath: cleanup.removedWorktreePath } : {}),
290
+ ...(cleanup.worktreeRemoveFailure ? { worktreeRemoveFailure: cleanup.worktreeRemoveFailure } : {}),
167
291
  });
168
- });
292
+ }, () => receipt(model, "worktree", "already-present", { absent: ["worktree"] }));
169
293
  await run("delivery-ref", async () => {
170
294
  const observation = await input.adapters.observeDeliveryRef(input.cwd, model.contract);
171
295
  return observation.status === "aligned"
172
296
  ? receipt(model, "delivery-ref", "already-present", observation)
173
297
  : receipt(model, "delivery-ref", "mismatch", observation);
174
- });
298
+ }, () => receipt(model, "delivery-ref", "already-present", {
299
+ status: "aligned",
300
+ ref: model.resources.deliveryRef,
301
+ expected: "absent",
302
+ observed: "absent",
303
+ }));
175
304
  return receipts;
176
305
  }
177
306
  await run("worktree", async () => {
@@ -179,6 +308,18 @@ export async function replayCommittedDerivedState(input) {
179
308
  projectedTarget = replay.target;
180
309
  return receipt(model, "worktree", replay.status);
181
310
  });
311
+ if (obligations.has("renew-delivery")) {
312
+ await run("renew-delivery", async () => {
313
+ if (transaction.verb !== "renew")
314
+ throw new FlowError("INTERNAL_STATE", "renew delivery obligation lacks an accepted renew transaction");
315
+ const renew = transaction.entries.find((entry) => entry.kind === "renew");
316
+ if (!renew || renew.kind !== "renew")
317
+ throw new FlowError("INTERNAL_STATE", "accepted renew transaction lacks a renew entry");
318
+ if (projectedTarget.status !== "available")
319
+ throw new FlowError("CARRIER_TARGET_UNAVAILABLE", projectedTarget.reason);
320
+ return receipt(model, "renew-delivery", await input.adapters.reconcileRenewDelivery({ lifecycle: model, target: projectedTarget, transaction }), { oldHead: renew.data.oldHead, newHead: renew.data.newHead });
321
+ });
322
+ }
182
323
  await run("narrow-ignore", async () => {
183
324
  if (projectedTarget.status !== "available")
184
325
  throw new FlowError("CARRIER_TARGET_UNAVAILABLE", projectedTarget.reason);
@@ -187,21 +328,13 @@ export async function replayCommittedDerivedState(input) {
187
328
  patterns,
188
329
  });
189
330
  });
190
- let viewStatus = "already-present";
191
- let viewProjected = false;
192
- await run("contract-view", async () => {
331
+ await run("carrier", async () => {
193
332
  const incomplete = journal.all().find((row) => row.status !== "succeeded");
194
333
  if (incomplete) {
195
334
  throw new FlowError("CARRIER_MATERIALIZATION_FAILED", `cannot advance carrier checkpoint while ${incomplete.effect} is ${incomplete.status}`);
196
335
  }
197
- viewStatus = await input.adapters.materializeContractView({ lifecycle: model, target: projectedTarget });
198
- viewProjected = true;
199
- return receipt(model, "contract-view", viewStatus, { path: projectedTarget.status === "available" ? path.join(projectedTarget.root, KEIYAKU_FILE) : null });
200
- });
201
- await run("carrier", async () => {
202
- if (!viewProjected)
203
- throw new FlowError("CARRIER_MATERIALIZATION_FAILED", "contract view was not materialized");
204
- return receipt(model, "carrier", viewStatus, { checkpoint: model.observedLedgerHead });
336
+ const status = await input.adapters.materializeContractView({ lifecycle: model, target: projectedTarget });
337
+ return receipt(model, "carrier", status, { path: projectedTarget.status === "available" ? path.join(projectedTarget.root, KEIYAKU_FILE) : null });
205
338
  });
206
339
  return receipts;
207
340
  }
@@ -3,8 +3,16 @@ import { parseToAST } from "./markdown/parser.js";
3
3
  import { extractListItems, renderSectionContent } from "./markdown/render.js";
4
4
  import { normalizeSectionTitle } from "./markdown/titles.js";
5
5
  import { markdownSourceSections, renderScopeFragment, validateScopeFragmentLines } from "./scope.js";
6
- const KNOWN_SECTIONS = new Set(["context", "objective", "design", "scope", "criteria", "verification"]);
7
- const RETIRED_OR_MALFORMED_CORE_SECTIONS = new Set(["title", "name", "checks"]);
6
+ import { parseVerificationDeclarations, renderVerificationDeclarations, } from "./verification-declaration.js";
7
+ const KNOWN_SECTIONS = new Map([
8
+ ["context", "context"],
9
+ ["objective", "objective"],
10
+ ["design & approach", "design"],
11
+ ["scope", "scope"],
12
+ ["criteria", "criteria"],
13
+ ["verification", "verification"],
14
+ ]);
15
+ const RETIRED_OR_MALFORMED_CORE_SECTIONS = new Set(["title", "name", "design", "checks"]);
8
16
  const BIND_DRAFT_SECTION_ORDER = ["title", "context", "objective", "design", "scope", "criteria", "verification"];
9
17
  function invalidBindDraft(message) {
10
18
  throw new FlowError("INVALID_KEIYAKU_DRAFT", `INVALID_BIND_DRAFT: ${message}`);
@@ -75,6 +83,9 @@ function extractSingleLineDeclarations(section, heading) {
75
83
  return trimmed;
76
84
  });
77
85
  }
86
+ function verificationDeclarations(section, heading) {
87
+ return parseVerificationDeclarations(renderSectionContent(section), { heading });
88
+ }
78
89
  function collectBindDraftNodes(content) {
79
90
  // Scope is parsed from source lines below. Hide its body from the Markdown AST
80
91
  // so fence-like and heading-like payload bytes cannot acquire structure there.
@@ -105,14 +116,19 @@ function collectBindDraftNodes(content) {
105
116
  continue;
106
117
  const normalized = normalizeSectionName(node.title);
107
118
  if (RETIRED_OR_MALFORMED_CORE_SECTIONS.has(normalized)) {
108
- const replacement = normalized === "checks" ? "Criteria and Verification" : "the H1 Title";
119
+ const replacement = normalized === "checks"
120
+ ? "Criteria and Verification"
121
+ : normalized === "design"
122
+ ? "Design & Approach"
123
+ : "the H1 Title";
109
124
  invalidBindDraft(`obsolete or malformed core section '${node.title}'; use ${replacement}`);
110
125
  }
111
- if (KNOWN_SECTIONS.has(normalized)) {
112
- if (sections.has(normalized)) {
126
+ const coreSection = KNOWN_SECTIONS.get(normalized);
127
+ if (coreSection) {
128
+ if (sections.has(coreSection)) {
113
129
  invalidBindDraft(`duplicate section '${node.title}' in bind draft`);
114
130
  }
115
- sections.set(normalized, node);
131
+ sections.set(coreSection, node);
116
132
  continue;
117
133
  }
118
134
  extensions.push({
@@ -155,7 +171,9 @@ export function parsePartialBindDraft(content) {
155
171
  if (!section)
156
172
  continue;
157
173
  const text = renderSectionContent(section).trim();
158
- const heading = `${sectionName[0].toUpperCase()}${sectionName.slice(1)}`;
174
+ const heading = sectionName === "design"
175
+ ? "Design & Approach"
176
+ : `${sectionName[0].toUpperCase()}${sectionName.slice(1)}`;
159
177
  if (!text)
160
178
  invalidBindDraft(`${heading} section cannot be empty`);
161
179
  partial[sectionName] = text;
@@ -169,7 +187,7 @@ export function parsePartialBindDraft(content) {
169
187
  partial.criteria = extractSingleLineDeclarations(criteria, "Criteria");
170
188
  const verification = sections.get("verification");
171
189
  if (verification)
172
- partial.verification = extractSingleLineDeclarations(verification, "Verification");
190
+ partial.verification = verificationDeclarations(verification, "## Verification");
173
191
  if (extensions.length > 0)
174
192
  partial.extensions = extensions;
175
193
  return partial;
@@ -229,7 +247,7 @@ export function renderMissingBindSectionsTemplate(missing) {
229
247
  continue;
230
248
  }
231
249
  if (section === "design") {
232
- lines.push("## Design", "<settled owner, boundaries, flow, state, and failure behavior>");
250
+ lines.push("## Design & Approach", "<settled owner, boundaries, flow, state, and failure behavior>");
233
251
  continue;
234
252
  }
235
253
  if (section === "scope") {
@@ -240,18 +258,28 @@ export function renderMissingBindSectionsTemplate(missing) {
240
258
  lines.push("## Criteria", "- <declarative acceptance criterion>");
241
259
  continue;
242
260
  }
243
- lines.push("## Verification", "- <executable command or testimony declaration>");
261
+ lines.push("## Verification", "```bash", "<verification script>", "```");
244
262
  }
245
263
  return lines.join("\n");
246
264
  }
247
265
  export function describeMissingBindSections(missing) {
248
- if (missing.length === 0)
266
+ const labels = {
267
+ title: "Title",
268
+ context: "Context",
269
+ objective: "Objective",
270
+ design: "Design & Approach",
271
+ scope: "Scope",
272
+ criteria: "Criteria",
273
+ verification: "Verification",
274
+ };
275
+ const named = missing.map((section) => labels[section]);
276
+ if (named.length === 0)
249
277
  return "";
250
- if (missing.length === 1)
251
- return missing[0];
252
- if (missing.length === 2)
253
- return `${missing[0]} or ${missing[1]}`;
254
- return `${missing.slice(0, -1).join(", ")}, or ${missing[missing.length - 1]}`;
278
+ if (named.length === 1)
279
+ return named[0];
280
+ if (named.length === 2)
281
+ return `${named[0]} or ${named[1]}`;
282
+ return `${named.slice(0, -1).join(", ")}, or ${named[named.length - 1]}`;
255
283
  }
256
284
  export function materializeBindDraft(partial) {
257
285
  const missing = missingBindDraftSections(partial);
@@ -272,7 +300,7 @@ export function materializeBindDraft(partial) {
272
300
  export function parseBindDraft(content) {
273
301
  const { title, sections, extensions } = collectBindDraftNodes(content);
274
302
  if (!title) {
275
- invalidBindDraft("missing H1 Title in bind draft; expected '# <title>' followed by Context, Objective, Design, Scope, and Criteria; Verification is optional");
303
+ invalidBindDraft("missing H1 Title in bind draft; expected '# <title>' followed by Context, Objective, Design & Approach, Scope, and Criteria; Verification is optional");
276
304
  }
277
305
  const context = sections.get("context");
278
306
  const objective = sections.get("objective");
@@ -285,7 +313,7 @@ export function parseBindDraft(content) {
285
313
  if (!objective)
286
314
  invalidBindDraft("missing Objective section in bind draft");
287
315
  if (!design)
288
- invalidBindDraft("missing Design section in bind draft");
316
+ invalidBindDraft("missing Design & Approach section in bind draft");
289
317
  if (!scope)
290
318
  invalidBindDraft("missing Scope section in bind draft");
291
319
  const parsedScope = parseScopeSection(content);
@@ -299,7 +327,7 @@ export function parseBindDraft(content) {
299
327
  if (!objectiveText)
300
328
  invalidBindDraft("Objective section cannot be empty");
301
329
  if (!designText)
302
- invalidBindDraft("Design section cannot be empty");
330
+ invalidBindDraft("Design & Approach section cannot be empty");
303
331
  return {
304
332
  title,
305
333
  context: contextText,
@@ -307,7 +335,7 @@ export function parseBindDraft(content) {
307
335
  design: designText,
308
336
  scope: parsedScope,
309
337
  criteria: extractSingleLineDeclarations(criteria, "Criteria"),
310
- verification: verification ? extractSingleLineDeclarations(verification, "Verification") : [],
338
+ verification: verification ? verificationDeclarations(verification, "## Verification") : [],
311
339
  extensions,
312
340
  };
313
341
  }
@@ -316,12 +344,12 @@ export function renderBindDraft(draft) {
316
344
  `# ${draft.title}`,
317
345
  `## Context\n${draft.context}`,
318
346
  `## Objective\n${draft.objective}`,
319
- `## Design\n${draft.design}`,
347
+ `## Design & Approach\n${draft.design}`,
320
348
  `## Scope\n${renderScopeFragment(draft.scope)}`,
321
349
  `## Criteria\n${draft.criteria.map((criterion) => `- ${criterion}`).join("\n")}`,
322
350
  ];
323
351
  if (draft.verification.length > 0) {
324
- sections.push(`## Verification\n${draft.verification.map((declaration) => `- ${declaration}`).join("\n")}`);
352
+ sections.push(`## Verification\n${renderVerificationDeclarations(draft.verification)}`);
325
353
  }
326
354
  for (const extension of draft.extensions) {
327
355
  const content = normalizeExtensionContent(extension.content);
@@ -340,7 +368,6 @@ export function compileBindDraftFlags(input) {
340
368
  ["--context", input.context],
341
369
  ["--objective", input.objective],
342
370
  ["--design", input.design],
343
- ["--scope", input.scope],
344
371
  ["--criteria", input.criteria],
345
372
  ["--verification", input.verification],
346
373
  ]) {
@@ -348,6 +375,12 @@ export function compileBindDraftFlags(input) {
348
375
  throw new FlowError("EMPTY_PARAM", `${flag} must be a single line`);
349
376
  }
350
377
  }
378
+ const scope = typeof input.scope === "string" ? [input.scope] : input.scope;
379
+ for (const pattern of scope ?? []) {
380
+ if (pattern.includes("\n") || pattern.includes("\r")) {
381
+ throw new FlowError("EMPTY_PARAM", "--scope must be a single line");
382
+ }
383
+ }
351
384
  const sections = [];
352
385
  if (input.title !== undefined)
353
386
  sections.push(`# ${input.title}`);
@@ -356,12 +389,12 @@ export function compileBindDraftFlags(input) {
356
389
  if (input.objective !== undefined)
357
390
  sections.push(`## Objective\n${input.objective}`);
358
391
  if (input.design !== undefined)
359
- sections.push(`## Design\n${input.design}`);
360
- if (input.scope !== undefined)
361
- sections.push(`## Scope\n~~~\n${input.scope}\n~~~`);
392
+ sections.push(`## Design & Approach\n${input.design}`);
393
+ if (scope !== undefined)
394
+ sections.push(`## Scope\n~~~\n${scope.join("\n")}\n~~~`);
362
395
  if (input.criteria !== undefined)
363
396
  sections.push(`## Criteria\n- ${input.criteria}`);
364
397
  if (input.verification !== undefined)
365
- sections.push(`## Verification\n- ${input.verification}`);
398
+ sections.push(`## Verification\n\`\`\`bash\n${input.verification}\n\`\`\``);
366
399
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
367
400
  }
@@ -88,7 +88,7 @@ export async function forfeitContract(input) {
88
88
  : await context.publishAuthoritative({
89
89
  verb: "forfeit",
90
90
  admittedLedgerHead: admission.ledgerHead,
91
- acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
91
+ acceptedLifecycleTail: context.session.lifecycle.contractHead,
92
92
  entries: [prepared.entry],
93
93
  delivery: {
94
94
  ref: repositoryDeliveryRef(input.contractId),
@@ -33,3 +33,16 @@ export function effectiveCriteria(entries) {
33
33
  }
34
34
  return criteria;
35
35
  }
36
+ /** Ordered effective contract Verification declarations from admitted lifecycle history. */
37
+ export function effectiveVerification(entries) {
38
+ const bind = entries.find((entry) => entry.kind === "bind");
39
+ if (!bind)
40
+ throw new FlowError("INTERNAL_STATE", "lifecycle history must contain bind entry");
41
+ let declarations = [...bind.data.verification];
42
+ for (const entry of entries) {
43
+ if (entry.kind === "amend" && entry.data.verificationDelta) {
44
+ declarations = [...entry.data.verificationDelta.replace];
45
+ }
46
+ }
47
+ return declarations;
48
+ }
@@ -1,12 +1,16 @@
1
1
  import { withContractCarrierSession } from "./contract-carrier.js";
2
2
  import { resolveAcceptedLifecycleTransaction, } from "./repository-ledger/accepted-tail.js";
3
+ import { lifecycleTransactionId } from "./repository-ledger/identity.js";
3
4
  export async function reconcileCommittedMutation(plan) {
4
5
  const observed = await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => ({
5
6
  model: session.lifecycle,
6
7
  repository: session.repository,
7
8
  }));
8
- const authoritativeHead = observed.model.observedLedgerHead;
9
- const accepted = findLatestAcceptedTransaction(observed.repository, plan.contractId, observed.model.history.at(-1)?.objectId ?? null);
9
+ const authoritativeHead = observed.repository.observedLedgerHead;
10
+ if (authoritativeHead === null) {
11
+ throw new Error(`accepted contract ${plan.contractId} has no repository ledger observation`);
12
+ }
13
+ const accepted = findLatestAcceptedTransaction(observed.repository, plan.contractId, observed.model.contractHead);
10
14
  if (accepted.status === "refused") {
11
15
  return {
12
16
  kind: "refused",
@@ -38,6 +42,11 @@ export async function reconcileCommittedMutation(plan) {
38
42
  return replayed;
39
43
  return observeOrdinaryCoordinate(plan);
40
44
  }
45
+ // A matching retry is still a mutating verb. Its accepted authority never
46
+ // proves that derived surfaces survived process death or manual removal.
47
+ const replayed = await alignDerivedTransaction(plan, transaction);
48
+ if (replayed.kind === "refused")
49
+ return replayed;
41
50
  return {
42
51
  kind: "aligned",
43
52
  coordinate: { state: "verb-committed", invokedVerb: plan.mutation.verb, transaction, authoritativeHead },
@@ -50,23 +59,7 @@ export async function reconcileCommittedMutation(plan) {
50
59
  refusal: { code: "CONTRACT_TERMINAL", message: `contract ${plan.contractId} is terminal (${observed.model.phase})` },
51
60
  };
52
61
  }
53
- const bindObjectId = observed.model.history.find(({ entry }) => entry.kind === "bind")?.objectId;
54
- if (!bindObjectId) {
55
- return {
56
- kind: "refused",
57
- authoritativeHead,
58
- refusal: { code: "INTERNAL_STATE", message: `contract ${plan.contractId} typed lifecycle has no bind object` },
59
- };
60
- }
61
- return {
62
- kind: "aligned",
63
- coordinate: {
64
- state: "bind-aligned",
65
- invokedVerb: plan.mutation.verb,
66
- bindObjectId,
67
- authoritativeHead,
68
- },
69
- };
62
+ return bindAligned(observed.repository, observed.model, plan, authoritativeHead);
70
63
  }
71
64
  async function alignDerivedTransaction(plan, transaction) {
72
65
  return withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal }, async (session) => {
@@ -81,7 +74,7 @@ async function alignDerivedTransaction(plan, transaction) {
81
74
  catch (error) {
82
75
  return {
83
76
  kind: "refused",
84
- authoritativeHead: session.lifecycle.observedLedgerHead,
77
+ authoritativeHead: session.pins.repositoryLedgerHead,
85
78
  refusal: {
86
79
  code: "LIFECYCLE_RECONCILIATION_INCOMPLETE",
87
80
  message: error instanceof Error ? error.message : `contract ${plan.contractId} derived replay did not complete`,
@@ -91,18 +84,30 @@ async function alignDerivedTransaction(plan, transaction) {
91
84
  });
92
85
  }
93
86
  async function observeOrdinaryCoordinate(plan) {
94
- return withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => bindAligned(session.lifecycle.history, plan, session.lifecycle.observedLedgerHead));
87
+ return withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => bindAligned(session.repository, session.lifecycle, plan, session.pins.repositoryLedgerHead));
95
88
  }
96
- function bindAligned(history, plan, authoritativeHead) {
97
- const bindObjectId = history.find(({ entry }) => entry.kind === "bind")?.objectId;
98
- if (!bindObjectId) {
89
+ function bindAligned(repository, lifecycle, plan, authoritativeHead) {
90
+ const bindObjectId = lifecycle.history.find(({ entry }) => entry.kind === "bind")?.objectId;
91
+ const transaction = repository.topology.nodes.find((node) => (node.type === "transaction"
92
+ && node.contract === lifecycle.contract
93
+ && node.finalEntryObjectId === lifecycle.contractHead));
94
+ if (!bindObjectId || !transaction || transaction.type !== "transaction") {
99
95
  return {
100
96
  kind: "refused",
101
97
  authoritativeHead,
102
- refusal: { code: "INTERNAL_STATE", message: "typed lifecycle has no bind object" },
98
+ refusal: { code: "INTERNAL_STATE", message: "typed lifecycle lacks a replayable bind transaction" },
103
99
  };
104
100
  }
105
- return { kind: "aligned", coordinate: { state: "bind-aligned", invokedVerb: plan.mutation.verb, bindObjectId, authoritativeHead } };
101
+ return {
102
+ kind: "aligned",
103
+ coordinate: {
104
+ state: "bind-aligned",
105
+ invokedVerb: plan.mutation.verb,
106
+ bindObjectId,
107
+ transactionObjectId: lifecycleTransactionId(transaction.objectId),
108
+ authoritativeHead,
109
+ },
110
+ };
106
111
  }
107
112
  function findLatestAcceptedTransaction(read, contractId, finalEntryObjectId) {
108
113
  if (finalEntryObjectId === null)
@@ -112,6 +117,6 @@ function findLatestAcceptedTransaction(read, contractId, finalEntryObjectId) {
112
117
  && candidate.finalEntryObjectId === finalEntryObjectId);
113
118
  if (!node || node.type !== "transaction")
114
119
  return { status: "none" };
115
- const resolved = resolveAcceptedLifecycleTransaction({ read, transactionObjectId: node.objectId, expectedContract: contractId });
120
+ const resolved = resolveAcceptedLifecycleTransaction({ read, transactionObjectId: lifecycleTransactionId(node.objectId), expectedContract: contractId });
116
121
  return resolved.status === "accepted" ? { status: "accepted", transaction: resolved } : resolved;
117
122
  }