@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
@@ -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,6 +3,7 @@ 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
+ import { parseVerificationDeclarations, renderVerificationDeclarations, } from "./verification-declaration.js";
6
7
  const KNOWN_SECTIONS = new Map([
7
8
  ["context", "context"],
8
9
  ["objective", "objective"],
@@ -82,6 +83,9 @@ function extractSingleLineDeclarations(section, heading) {
82
83
  return trimmed;
83
84
  });
84
85
  }
86
+ function verificationDeclarations(section, heading) {
87
+ return parseVerificationDeclarations(renderSectionContent(section), { heading });
88
+ }
85
89
  function collectBindDraftNodes(content) {
86
90
  // Scope is parsed from source lines below. Hide its body from the Markdown AST
87
91
  // so fence-like and heading-like payload bytes cannot acquire structure there.
@@ -183,7 +187,7 @@ export function parsePartialBindDraft(content) {
183
187
  partial.criteria = extractSingleLineDeclarations(criteria, "Criteria");
184
188
  const verification = sections.get("verification");
185
189
  if (verification)
186
- partial.verification = extractSingleLineDeclarations(verification, "Verification");
190
+ partial.verification = verificationDeclarations(verification, "## Verification");
187
191
  if (extensions.length > 0)
188
192
  partial.extensions = extensions;
189
193
  return partial;
@@ -254,7 +258,7 @@ export function renderMissingBindSectionsTemplate(missing) {
254
258
  lines.push("## Criteria", "- <declarative acceptance criterion>");
255
259
  continue;
256
260
  }
257
- lines.push("## Verification", "- <executable command or testimony declaration>");
261
+ lines.push("## Verification", "```bash", "<verification script>", "```");
258
262
  }
259
263
  return lines.join("\n");
260
264
  }
@@ -331,7 +335,7 @@ export function parseBindDraft(content) {
331
335
  design: designText,
332
336
  scope: parsedScope,
333
337
  criteria: extractSingleLineDeclarations(criteria, "Criteria"),
334
- verification: verification ? extractSingleLineDeclarations(verification, "Verification") : [],
338
+ verification: verification ? verificationDeclarations(verification, "## Verification") : [],
335
339
  extensions,
336
340
  };
337
341
  }
@@ -345,7 +349,7 @@ export function renderBindDraft(draft) {
345
349
  `## Criteria\n${draft.criteria.map((criterion) => `- ${criterion}`).join("\n")}`,
346
350
  ];
347
351
  if (draft.verification.length > 0) {
348
- sections.push(`## Verification\n${draft.verification.map((declaration) => `- ${declaration}`).join("\n")}`);
352
+ sections.push(`## Verification\n${renderVerificationDeclarations(draft.verification)}`);
349
353
  }
350
354
  for (const extension of draft.extensions) {
351
355
  const content = normalizeExtensionContent(extension.content);
@@ -391,6 +395,6 @@ export function compileBindDraftFlags(input) {
391
395
  if (input.criteria !== undefined)
392
396
  sections.push(`## Criteria\n- ${input.criteria}`);
393
397
  if (input.verification !== undefined)
394
- sections.push(`## Verification\n- ${input.verification}`);
398
+ sections.push(`## Verification\n\`\`\`bash\n${input.verification}\n\`\`\``);
395
399
  return sections.length > 0 ? `${sections.join("\n\n")}\n` : "";
396
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
  }
@@ -5,7 +5,7 @@ import { resolveAcceptedLifecycleTransaction } from "./repository-ledger/accepte
5
5
  import { reconcileCommittedMutation, } from "./lifecycle-recovery.js";
6
6
  function coordinateObject(pins, target) {
7
7
  return {
8
- ledgerHead: pins.ledgerHead,
8
+ ledgerHead: pins.repositoryLedgerHead,
9
9
  targetRef: pins.target.ref,
10
10
  targetHead: pins.target.head,
11
11
  deliveryRef: pins.delivery.ref,
@@ -20,7 +20,7 @@ function sameCoordinates(admission, session) {
20
20
  }
21
21
  function validateReplayCoordinate(session, coordinate) {
22
22
  if (coordinate.state === "bind-aligned") {
23
- const currentHead = session.lifecycle.observedLedgerHead;
23
+ const currentHead = session.pins.repositoryLedgerHead;
24
24
  if (coordinate.authoritativeHead !== currentHead) {
25
25
  return {
26
26
  code: "LIFECYCLE_REPLAY_COORDINATES_CHANGED",
@@ -55,7 +55,7 @@ function validateReplayCoordinate(session, coordinate) {
55
55
  },
56
56
  current: {
57
57
  status: resolved.status,
58
- authoritativeHead: session.lifecycle.observedLedgerHead,
58
+ authoritativeHead: session.pins.repositoryLedgerHead,
59
59
  },
60
60
  };
61
61
  }
@@ -115,12 +115,12 @@ export async function runLifecycle(plan) {
115
115
  };
116
116
  }
117
117
  const attempt = await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => {
118
- observedHead = session.lifecycle.observedLedgerHead;
118
+ observedHead = session.pins.repositoryLedgerHead;
119
119
  const replayRefusal = validateReplayCoordinate(session, reconciliation.coordinate);
120
120
  if (replayRefusal) {
121
121
  return { kind: "refused", outcome: refusedLifecycleFacts({
122
122
  contractId: plan.contractId,
123
- authoritativeHead: session.lifecycle.observedLedgerHead,
123
+ authoritativeHead: session.pins.repositoryLedgerHead,
124
124
  ...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
125
125
  }) };
126
126
  }
@@ -130,13 +130,13 @@ export async function runLifecycle(plan) {
130
130
  }
131
131
  catch (error) {
132
132
  if (error instanceof FlowError && !isCancellation(error)) {
133
- return { kind: "refused", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.lifecycle.observedLedgerHead, ...flowRefusal(error) }) };
133
+ return { kind: "refused", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.pins.repositoryLedgerHead, ...flowRefusal(error) }) };
134
134
  }
135
135
  throw error;
136
136
  }
137
137
  return {
138
138
  kind: "admitted",
139
- ledgerHead: session.lifecycle.observedLedgerHead,
139
+ ledgerHead: session.pins.repositoryLedgerHead,
140
140
  lifecycle: session.lifecycle,
141
141
  repository: session.repository,
142
142
  pins: session.pins,
@@ -172,20 +172,20 @@ export async function runLifecycle(plan) {
172
172
  throw error;
173
173
  }
174
174
  try {
175
- return await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal, ...(plan.renewDeliveryProjector ? { renewDeliveryProjector: plan.renewDeliveryProjector } : {}), ...(plan.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: plan.terminalWorkspaceCleanup } : {}) }, async (session) => {
176
- observedHead = session.lifecycle.observedLedgerHead;
175
+ return await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal, ...(plan.renewDeliveryProjector ? { renewDeliveryProjector: plan.renewDeliveryProjector } : {}), ...(plan.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: plan.terminalWorkspaceCleanup } : {}), ...(plan.transactionRunner ? { transactionRunner: plan.transactionRunner } : {}) }, async (session) => {
176
+ observedHead = session.pins.repositoryLedgerHead;
177
177
  const replayRefusal = validateReplayCoordinate(session, admission.replay);
178
178
  if (replayRefusal) {
179
179
  return { kind: "noncommitted", outcome: refusedLifecycleFacts({
180
180
  contractId: plan.contractId,
181
- authoritativeHead: session.lifecycle.observedLedgerHead,
181
+ authoritativeHead: session.pins.repositoryLedgerHead,
182
182
  ...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
183
183
  }) };
184
184
  }
185
185
  if (!sameCoordinates(admission, session)) {
186
186
  return { kind: "noncommitted", outcome: refusedLifecycleFacts({
187
187
  contractId: plan.contractId,
188
- authoritativeHead: session.lifecycle.observedLedgerHead,
188
+ authoritativeHead: session.pins.repositoryLedgerHead,
189
189
  code: "LIFECYCLE_COORDINATES_CHANGED",
190
190
  message: "contract coordinates changed during lifecycle preparation",
191
191
  pinned: coordinateObject(admission.pins, admission.target),
@@ -202,7 +202,7 @@ export async function runLifecycle(plan) {
202
202
  refusal = flowRefusal(error);
203
203
  }
204
204
  if (refusal) {
205
- return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.lifecycle.observedLedgerHead, ...refusal }) };
205
+ return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.pins.repositoryLedgerHead, ...refusal }) };
206
206
  }
207
207
  const effectSession = Object.freeze({
208
208
  stableRoot: session.stableRoot,
@@ -239,7 +239,7 @@ export async function runLifecycle(plan) {
239
239
  if (error instanceof FlowError && !isCancellation(error)) {
240
240
  return { kind: "noncommitted", outcome: refusedLifecycleFacts({
241
241
  contractId: plan.contractId,
242
- authoritativeHead: session.lifecycle.observedLedgerHead,
242
+ authoritativeHead: session.pins.repositoryLedgerHead,
243
243
  ...flowRefusal(error),
244
244
  }) };
245
245
  }
@@ -274,7 +274,7 @@ export async function runLifecycle(plan) {
274
274
  kind: "noncommitted",
275
275
  outcome: precommitLifecycleFacts({
276
276
  contractId: plan.contractId,
277
- authoritativeHead: session.lifecycle.observedLedgerHead,
277
+ authoritativeHead: session.pins.repositoryLedgerHead,
278
278
  journal: session.journal,
279
279
  cancellationObserved: session.cancellationObserved(),
280
280
  }),
package/build/core/log.js CHANGED
@@ -44,7 +44,7 @@ export async function readContractLog(input) {
44
44
  at: entry.at,
45
45
  actor: entry.actor,
46
46
  })),
47
- markdown: renderCurrentContractView(model),
47
+ markdown: renderCurrentContractView(model, read.observedLedgerHead),
48
48
  ...(warning ? { amendmentWarning: warning } : {}),
49
49
  };
50
50
  }
@@ -88,14 +88,14 @@ export class EffectJournal {
88
88
  }
89
89
  }
90
90
  export function carrierLifecycleEffect(carrier) {
91
- const idempotencyKey = `carrier:${carrier.ledgerHead}`;
91
+ const idempotencyKey = `carrier:${carrier.contractHead}`;
92
92
  if (carrier.status === "failed") {
93
93
  return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_MATERIALIZATION_FAILED", message: carrier.cause } };
94
94
  }
95
95
  if (carrier.status === "target-unavailable") {
96
96
  return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_TARGET_UNAVAILABLE", message: carrier.reason } };
97
97
  }
98
- return { effect: "carrier-projection", idempotencyKey, status: "succeeded", receipt: { ledgerHead: carrier.ledgerHead, status: carrier.status } };
98
+ return { effect: "carrier-projection", idempotencyKey, status: "succeeded", receipt: { contractHead: carrier.contractHead, status: carrier.status } };
99
99
  }
100
100
  export function committedLifecycleFacts(input) {
101
101
  return {