@astrosheep/keiyaku 2.9.8 → 2.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,17 +1,19 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
2
  import { readCurrentContractContext } from "../context.js";
3
- import { readLedger } from "../ledger.js";
4
- import { commissionTarget } from "../status/drift.js";
5
- import { deriveContractState } from "../status/lifecycle.js";
3
+ import { convergeContractCarrier, withContractCarrierSession } from "../contract-carrier.js";
4
+ import { createGit } from "../../git/core.js";
6
5
  import { getCurrentBranch, requireGitRepository } from "../../git/branches.js";
6
+ import { getLatestCommitHash } from "../../git/staging.js";
7
7
  import { readContractLog } from "../log.js";
8
- import { listRefLogCommits } from "../ref-log.js";
9
- import { effectiveScope } from "../seal.js";
10
8
  import { scopePathEvidence } from "../scope.js";
11
9
  import { nextReviewResponseNumber } from "../transcripts.js";
10
+ import { stableRepoRoot } from "../worktree-path.js";
11
+ import { isCompleteLifecycleOutcome } from "../outcome-base.js";
12
12
  import { claimContract } from "./claim.js";
13
+ import { assertClaimQueueHead, deriveQueueReadModel } from "./queue.js";
14
+ import { classifyCurrentPetitionWindow } from "./petition-window.js";
13
15
  import { buildClaimGatePipeline, } from "./petition-claim-gates.js";
14
- import { resolveVerificationCommands } from "./verification.js";
16
+ import { composeVerificationPlan, contractVerificationDeclarations, resolveVerificationPolicy } from "./verification.js";
15
17
  import { forfeitKeiyaku } from "./petition-forfeit.js";
16
18
  import { assertHeadUnchanged } from "./petition-head-guard.js";
17
19
  import { petitionContract } from "./petition.js";
@@ -19,10 +21,13 @@ import { verdictContract } from "./verdict.js";
19
21
  const VERDICT_DENIED_CODE = "VERDICT_DENIED";
20
22
  async function resolveClaimGateConfig(cwd) {
21
23
  const merged = {
22
- verificationCommands: [],
24
+ repositoryGates: [],
25
+ verificationTimeoutMs: 300_000,
23
26
  };
24
27
  try {
25
- merged.verificationCommands = await resolveVerificationCommands(cwd);
28
+ const policy = await resolveVerificationPolicy(cwd);
29
+ merged.repositoryGates = policy.commands;
30
+ merged.verificationTimeoutMs = policy.timeoutMs;
26
31
  return merged;
27
32
  }
28
33
  catch (error) {
@@ -35,21 +40,34 @@ async function resolveClaimGateConfig(cwd) {
35
40
  throw error;
36
41
  }
37
42
  }
38
- async function loadCloseContext(cwd, requestedContractId) {
39
- await requireGitRepository(cwd, "petition");
40
- if (requestedContractId) {
41
- const ledger = await readLedger(cwd, requestedContractId);
42
- if (!ledger) {
43
- throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} not found or not active`);
43
+ async function pinnedCloseCandidate(cwd, contractId, signal) {
44
+ return withContractCarrierSession({ cwd, contractId, mode: "observe-only", signal }, async (session) => {
45
+ if (session.target.status !== "available" || !session.pins.delivery.head) {
46
+ throw new FlowError("INTERNAL_STATE", session.target.status === "available" ? `contract ${contractId} delivery ref does not resolve` : session.target.reason);
44
47
  }
45
- const target = commissionTarget(ledger.entries);
46
- if (!target) {
47
- throw new FlowError("INTERNAL_STATE", `contract ${requestedContractId} active ledger is missing commission target`);
48
+ const git = createGit(session.target.root);
49
+ const [head, tree] = await Promise.all([
50
+ git.raw(["rev-parse", "--verify", "HEAD^{commit}"]),
51
+ git.raw(["rev-parse", "--verify", "HEAD^{tree}"]),
52
+ ]);
53
+ if (head.trim() !== session.pins.delivery.head) {
54
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} delivery head changed while petition was being prepared`);
48
55
  }
56
+ return { deliveryCwd: session.target.root, candidate: { head: head.trim(), tree: tree.trim() }, contractVerification: contractVerificationDeclarations(session.lifecycle) };
57
+ });
58
+ }
59
+ async function loadCloseContext(cwd, requestedContractId, signal) {
60
+ await requireGitRepository(cwd, "petition");
61
+ if (requestedContractId) {
62
+ const lifecycle = (await convergeContractCarrier({ cwd, contractId: requestedContractId, signal, mode: "observe-only" })).lifecycle;
63
+ const pinned = await pinnedCloseCandidate(cwd, requestedContractId, signal);
49
64
  return {
50
65
  cwd,
66
+ deliveryCwd: pinned.deliveryCwd,
67
+ verificationCandidate: pinned.candidate,
68
+ contractVerification: pinned.contractVerification,
51
69
  keiyakuBranch: `keiyaku/${requestedContractId}`,
52
- baseBranch: target.branch,
70
+ baseBranch: lifecycle.pins.target.ref.slice("refs/heads/".length),
53
71
  contractId: requestedContractId,
54
72
  };
55
73
  }
@@ -64,8 +82,12 @@ async function loadCloseContext(cwd, requestedContractId) {
64
82
  throw new FlowError("NOT_ACTIVE_KEIYAKU_BRANCH", "Current workspace is not addressing an active Keiyaku contract.");
65
83
  }
66
84
  const keiyakuBranch = await getCurrentBranch(cwd);
85
+ const pinned = await pinnedCloseCandidate(cwd, contractContext.contractId, signal);
67
86
  return {
68
87
  cwd,
88
+ deliveryCwd: pinned.deliveryCwd,
89
+ verificationCandidate: pinned.candidate,
90
+ contractVerification: pinned.contractVerification,
69
91
  keiyakuBranch,
70
92
  baseBranch: contractContext.baseBranch,
71
93
  contractId: contractContext.contractId,
@@ -84,30 +106,47 @@ async function throwClaimGateFailure(failure, _input, context, _dependencies) {
84
106
  });
85
107
  }
86
108
  async function runClaimGatePipelineOrThrow(input, context, dependencies) {
87
- const { verificationCommands } = await dependencies.resolveClaimGateConfig(context.cwd);
109
+ const deliveryCwd = context.deliveryCwd ?? context.cwd;
110
+ const verificationCandidate = context.verificationCandidate ?? {
111
+ head: await getLatestCommitHash(deliveryCwd),
112
+ tree: (await createGit(deliveryCwd).raw(["rev-parse", "--verify", "HEAD^{tree}"])).trim(),
113
+ };
114
+ const gateConfig = await dependencies.resolveClaimGateConfig(context.cwd);
115
+ const repositoryGates = gateConfig.repositoryGates ?? gateConfig.verificationCommands ?? [];
116
+ const verificationPlan = composeVerificationPlan({ contract: context.contractVerification ?? [], repositoryGates, timeoutMs: gateConfig.verificationTimeoutMs });
117
+ const coordinationRoot = await stableRepoRoot(context.cwd);
88
118
  let review;
89
119
  let reviewHeadBeforeWrite;
90
120
  let reviewerActor;
121
+ let reviewPersistence;
122
+ let verification;
91
123
  for (const gate of buildClaimGatePipeline()) {
92
124
  const result = await gate.evaluate({
93
- cwd: context.cwd,
125
+ cwd: deliveryCwd,
126
+ coordinationRoot,
94
127
  contractId: context.contractId,
95
128
  input,
96
- verificationCommands,
129
+ verificationPlan,
130
+ verificationCandidate,
97
131
  baseBranch: dependencies.baseBranch,
98
132
  reviewNumber: dependencies.reviewNumber,
99
133
  });
134
+ if (!result.pass) {
135
+ await throwClaimGateFailure(result, input, context, dependencies);
136
+ }
100
137
  if (result.review) {
101
138
  review = result.review;
102
139
  reviewHeadBeforeWrite = result.reviewHeadBeforeWrite;
103
140
  }
104
141
  if (result.reviewerActor)
105
142
  reviewerActor = result.reviewerActor;
106
- if (!result.pass) {
107
- await throwClaimGateFailure(result, input, context, dependencies);
108
- }
143
+ if (result.reviewPersistence)
144
+ reviewPersistence = result.reviewPersistence;
145
+ if ("verification" in result && result.verification)
146
+ verification = result.verification;
109
147
  }
110
- return { review, reviewHeadBeforeWrite, reviewerActor };
148
+ await assertHeadUnchanged(deliveryCwd, verificationCandidate.head, "claim verification");
149
+ return { review, reviewHeadBeforeWrite, reviewerActor, reviewPersistence, verification };
111
150
  }
112
151
  function maxArc(entries) {
113
152
  return entries.filter((entry) => entry.kind === "arc").length;
@@ -117,75 +156,108 @@ function cleanupWarnings(cleanup) {
117
156
  ...(cleanup.worktreeRemoveFailure
118
157
  ? [`Could not remove contract worktree ${cleanup.worktreeRemoveFailure.path}: ${cleanup.worktreeRemoveFailure.reason}`]
119
158
  : []),
120
- ...(cleanup.branchDeleteFailure
121
- ? [`Could not delete contract branch ${cleanup.branchDeleteFailure.name}: ${cleanup.branchDeleteFailure.reason}`]
159
+ ...(cleanup.deliveryRef.status === "mismatch"
160
+ ? [`Delivery ref ${cleanup.deliveryRef.mismatch.ref} remains present at ${cleanup.deliveryRef.mismatch.observed.head}; terminal replay preserved it as a projected-surface mismatch.`]
122
161
  : []),
123
162
  ];
124
163
  }
125
- function findClaimEntry(entries) {
126
- return entries.find((entry) => entry.kind === "claim");
127
- }
128
- /** Verdict for the open petition window (after last petition, before claim). */
129
- function latestCurrentPetitionVerdict(entries) {
130
- for (let index = entries.length - 1; index >= 0; index -= 1) {
131
- const entry = entries[index];
132
- if (entry.kind === "verdict")
133
- return entry;
134
- if (entry.kind === "petition")
135
- return undefined;
136
- }
137
- return undefined;
138
- }
139
164
  /**
140
165
  * Crash-recovery head: petition → approved verdict → no claim.
141
166
  * Orchestrator resumes claim delivery only; no gates, petition, or re-judgment.
142
167
  */
143
- function isApprovedVerdictAwaitingClaim(entries) {
144
- const state = deriveContractState(entries);
145
- if (state.state !== "petitioned")
146
- return false;
147
- if (findClaimEntry(entries))
148
- return false;
149
- return latestCurrentPetitionVerdict(entries)?.data.result === "approved";
168
+ function isApprovedVerdictAwaitingClaim(lifecycle) {
169
+ return lifecycle.phase === "approved"
170
+ && lifecycle.petition.state === "approved"
171
+ && lifecycle.petition.intent === "claim";
150
172
  }
151
- async function recoverCurrentPetitionSeal(cwd, ledger) {
152
- let petitionIndex = -1;
153
- for (let index = ledger.entries.length - 1; index >= 0; index -= 1) {
154
- if (ledger.entries[index]?.kind === "petition") {
155
- petitionIndex = index;
156
- break;
157
- }
173
+ /** Resume the first missing stage of an existing petition settlement. */
174
+ export async function reconcilePetitionRetry(input) {
175
+ const observed = await withContractCarrierSession({ ...input, mode: "observe-only" }, async (session) => ({
176
+ window: classifyCurrentPetitionWindow(session.lifecycle, session.pins),
177
+ queue: deriveQueueReadModel(session.repository),
178
+ }));
179
+ if (observed.window.status === "none")
180
+ return { status: "not-needed" };
181
+ if (observed.window.status === "approved-awaiting-claim") {
182
+ return { status: "approved-awaiting-claim" };
183
+ }
184
+ if (observed.window.status === "invalid") {
185
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} petition recovery is invalid: ${observed.window.reason}`);
186
+ }
187
+ assertClaimQueueHead(observed.queue, input.contractId);
188
+ if (observed.window.coordinates === "fresh") {
189
+ return {
190
+ status: "verdict",
191
+ result: "approved",
192
+ reason: "resume",
193
+ verdict: await verdictContract({
194
+ cwd: input.cwd,
195
+ contractId: input.contractId,
196
+ actor: "keiyaku",
197
+ nowMs: Date.now(),
198
+ result: "approved",
199
+ summary: "Recovered passed petition at its pinned candidate.",
200
+ signal: input.signal,
201
+ }),
202
+ };
158
203
  }
204
+ const reason = observed.window.coordinates;
205
+ return {
206
+ status: "verdict",
207
+ result: "rejected",
208
+ reason,
209
+ verdict: await verdictContract({
210
+ cwd: input.cwd,
211
+ contractId: input.contractId,
212
+ actor: "keiyaku",
213
+ nowMs: Date.now(),
214
+ result: "rejected",
215
+ summary: reason === "target-drift"
216
+ ? "Petition target advanced before verdict."
217
+ : "Petition delivery changed before verdict.",
218
+ signal: input.signal,
219
+ }),
220
+ };
221
+ }
222
+ export async function recoverCurrentPetitionSeal(cwd, lifecycle) {
223
+ if (lifecycle.petition.state !== "approved" || lifecycle.petition.intent !== "claim")
224
+ return undefined;
225
+ const petitionObjectId = lifecycle.petition.objectId;
226
+ const petitionIndex = lifecycle.history.findIndex(({ objectId }) => objectId === petitionObjectId);
159
227
  if (petitionIndex <= 0)
160
228
  return undefined;
161
- const seal = ledger.entries[petitionIndex - 1];
162
- if (seal.kind !== "seal" || seal.data.sealedBy !== "petition")
229
+ const seal = lifecycle.history[petitionIndex - 1];
230
+ if (seal?.entry.kind !== "seal" || seal.entry.data.sealedBy !== "petition")
163
231
  return undefined;
164
- const commits = await listRefLogCommits(cwd, ledger.head, ledger.ref);
165
- const ledgerHead = commits[petitionIndex - 1];
166
- if (!ledgerHead || commits.length !== ledger.entries.length) {
167
- throw new FlowError("INTERNAL_STATE", `contract ${ledger.contractId} ledger entries do not align with ref-log commits during petition recovery`);
168
- }
169
- const scopeViolationEvidence = await scopePathEvidence(cwd, effectiveScope(ledger.entries), seal.data.scopeAudit.violations);
232
+ const scopeViolationEvidence = await scopePathEvidence(cwd, lifecycle.effectiveScope, seal.entry.data.scopeAudit.violations);
170
233
  return {
171
- ...seal.data,
234
+ ...seal.entry.data,
235
+ receipts: [...seal.entry.data.receipts],
236
+ diffstat: { ...seal.entry.data.diffstat },
237
+ scopeAudit: { violations: [...seal.entry.data.scopeAudit.violations] },
172
238
  scopeViolationEvidence,
173
- ledgerHead,
239
+ ledgerHead: seal.objectId,
174
240
  };
175
241
  }
176
242
  async function finishApprovedClaim(context, options) {
177
243
  const { cwd, baseBranch, contractId } = context;
178
244
  // Capture arc facts before claim cleanup may remove the delivery worktree cwd.
179
- const log = await readContractLog({ cwd, contractId });
245
+ const log = await readContractLog({ cwd, contractId, signal: options.signal });
180
246
  const claimed = await claimContract({
181
247
  cwd,
182
248
  contractId,
183
249
  actor: "keiyaku",
184
250
  nowMs: options.nowMs,
251
+ signal: options.signal,
185
252
  });
186
- const cleanup = claimed.cleanup ?? { absent: ["worktree", "branch"] };
253
+ if (!isCompleteLifecycleOutcome(claimed))
254
+ return claimed;
255
+ if (!claimed.cleanup)
256
+ throw new FlowError("INTERNAL_STATE", "complete claim has no terminal cleanup facts");
257
+ const cleanup = claimed.cleanup;
187
258
  const warnings = [...(claimed.warnings ?? []), ...cleanupWarnings(cleanup)];
188
259
  return {
260
+ ...claimed,
189
261
  status: "success",
190
262
  result: "merged",
191
263
  arc: maxArc(log.entries),
@@ -193,12 +265,12 @@ async function finishApprovedClaim(context, options) {
193
265
  baseBranch,
194
266
  commit: claimed.mergeCommit,
195
267
  mergedInto: baseBranch,
196
- ...(cleanup.deletedBranch ? { deletedBranch: cleanup.deletedBranch } : {}),
197
268
  cleanup,
198
269
  diff: `Merged contract ${contractId} with merge-tree/commit-tree CAS.`,
199
270
  // Prior public outcome carried sealed facts when petition sealed an open arc.
200
271
  ...(options.sealed ? { sealed: options.sealed } : {}),
201
272
  ...(warnings.length > 0 ? { meta: { warnings } } : {}),
273
+ carrier: claimed.carrier,
202
274
  };
203
275
  }
204
276
  /**
@@ -213,29 +285,35 @@ async function finishApprovedClaim(context, options) {
213
285
  */
214
286
  export async function claimKeiyaku(input, context, dependencies) {
215
287
  const { cwd, baseBranch, contractId } = context;
288
+ const retry = await reconcilePetitionRetry({
289
+ cwd,
290
+ contractId,
291
+ signal: input.signal,
292
+ });
293
+ if (retry.status === "verdict" && !isCompleteLifecycleOutcome(retry.verdict)) {
294
+ return retry.verdict;
295
+ }
216
296
  // Crash recovery: petition → approved verdict → no claim.
217
297
  // Resume claim delivery only; do not re-gate, re-petition, or re-judge.
218
- const existing = await readLedger(cwd, contractId);
219
- if (existing && isApprovedVerdictAwaitingClaim(existing.entries)) {
220
- const sealed = await recoverCurrentPetitionSeal(cwd, existing);
298
+ const existing = await convergeContractCarrier({ cwd, contractId, signal: input.signal, mode: "observe-only" });
299
+ if (isApprovedVerdictAwaitingClaim(existing.lifecycle)) {
300
+ const sealed = await recoverCurrentPetitionSeal(cwd, existing.lifecycle);
221
301
  return finishApprovedClaim(context, {
222
302
  nowMs: Date.now(),
303
+ signal: input.signal,
223
304
  ...(sealed ? { sealed } : {}),
224
305
  });
225
306
  }
226
307
  const claimResult = "dangerouslyBypassGates" in input && input.dangerouslyBypassGates
227
- ? { review: undefined, reviewHeadBeforeWrite: undefined, bypassedByArchitect: true }
308
+ ? { review: undefined, reviewHeadBeforeWrite: undefined, reviewPersistence: undefined, bypassedByArchitect: true }
228
309
  : {
229
- ...(await runClaimGatePipelineOrThrow(input, { cwd, contractId }, {
310
+ ...(await runClaimGatePipelineOrThrow(input, context, {
230
311
  ...dependencies,
231
312
  baseBranch,
232
313
  reviewNumber: await nextReviewResponseNumber(cwd),
233
314
  })),
234
315
  bypassedByArchitect: false,
235
316
  };
236
- if (claimResult.review && claimResult.reviewHeadBeforeWrite) {
237
- await assertHeadUnchanged(cwd, claimResult.reviewHeadBeforeWrite, "claim review");
238
- }
239
317
  const oath = String(input.oath ?? "").trim();
240
318
  if (!oath) {
241
319
  throw new FlowError("OATH_MISSING", "Oath missing.");
@@ -249,7 +327,14 @@ export async function claimKeiyaku(input, context, dependencies) {
249
327
  intent: "claim",
250
328
  oath,
251
329
  appendScope: input.appendScope,
330
+ reviewPersistence: claimResult.reviewPersistence,
331
+ signal: input.signal,
252
332
  });
333
+ if (!isCompleteLifecycleOutcome(petition))
334
+ return petition;
335
+ const review = claimResult.review
336
+ ? { ...claimResult.review, ...(petition.reviewHistoryPath ? { historyPath: petition.reviewHistoryPath } : {}) }
337
+ : undefined;
253
338
  // 2. Pure verdict: judgment facts only; no claim execution.
254
339
  // Capture one clock for verdict + claim so ledger timestamps match the prior
255
340
  // verdict→claim atomic pair (verdict previously forwarded its own nowMs).
@@ -260,24 +345,31 @@ export async function claimKeiyaku(input, context, dependencies) {
260
345
  actor: claimResult.reviewerActor ?? "keiyaku",
261
346
  nowMs: judgmentNowMs,
262
347
  result: "approved",
263
- summary: claimResult.review?.findings?.trim() || "礼成",
348
+ summary: review?.findings?.trim() || "礼成",
349
+ signal: input.signal,
264
350
  });
351
+ if (!isCompleteLifecycleOutcome(verdict))
352
+ return verdict;
265
353
  // 3. Execution: approved → claim delivery (same sequential append window as before).
266
354
  // Rejected verdict publication is verdictContract itself (state returns active).
267
355
  if (verdict.result !== "approved") {
268
356
  throw new FlowError("INTERNAL_STATE", `settlement expected approved verdict for ${contractId}, got ${verdict.result}`);
269
357
  }
270
- return finishApprovedClaim(context, {
358
+ const completed = await finishApprovedClaim(context, {
271
359
  nowMs: judgmentNowMs,
360
+ signal: input.signal,
272
361
  ...(petition.sealed ? { sealed: petition.sealed } : {}),
273
362
  });
363
+ if (!claimResult.verification || !isCompleteLifecycleOutcome(completed))
364
+ return completed;
365
+ return { ...completed, verification: claimResult.verification };
274
366
  }
275
367
  /** Public settlement entrypoint for CLI petition (claim or forfeit). */
276
368
  export async function petitionKeiyaku(input) {
277
- const context = await loadCloseContext(input.cwd, input.contractId);
369
+ const context = await loadCloseContext(input.cwd, input.contractId, input.signal);
278
370
  const { intent } = input;
279
371
  if (intent === "FORFEIT") {
280
- return forfeitKeiyaku(context);
372
+ return forfeitKeiyaku(context, input.signal);
281
373
  }
282
374
  if (intent === "CLAIM") {
283
375
  return claimKeiyaku(input, context, {
@@ -1,80 +1,126 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { activeLedgerRef, appendWithRetry, readLedger } from "../ledger.js";
3
- import { assertClaimQueueHead, readQueueReadModel } from "./queue.js";
4
- import { deriveContractState } from "../status/lifecycle.js";
5
- function latestVerdict(entries) {
6
- for (let index = entries.length - 1; index >= 0; index -= 1) {
7
- const entry = entries[index];
8
- if (entry.kind === "verdict")
9
- return entry;
10
- if (entry.kind === "petition")
11
- return undefined;
12
- }
13
- return undefined;
14
- }
15
- function assertPetitioned(contractId, entries) {
16
- const state = deriveContractState(entries);
2
+ import { runLifecycle } from "../lifecycle-runner.js";
3
+ import { assertClaimQueueHead } from "./queue.js";
4
+ import { deriveQueueReadModel } from "./queue-read-model.js";
5
+ import { currentLifecycleState } from "../repository-ledger/read-model.js";
6
+ function assertFoldedPetitioned(contractId, model) {
7
+ const state = currentLifecycleState(model);
17
8
  if (state.state !== "petitioned") {
18
9
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot verdict from state ${state.state}`);
19
10
  }
20
11
  }
12
+ function assertApprovedWindow(contractId, model, pins) {
13
+ if (model.petition.state !== "awaiting-verdict") {
14
+ throw new FlowError("VERDICT_DENIED", `contract ${contractId} cannot approve petition without an awaiting-verdict fold`);
15
+ }
16
+ if (!pins.target.head || !pins.delivery.head) {
17
+ throw new FlowError("VERDICT_DENIED", `contract ${contractId} cannot approve petition because current coordinates do not resolve`);
18
+ }
19
+ if (model.pins.base !== pins.target.head) {
20
+ throw new FlowError("VERDICT_DENIED", `contract ${contractId} cannot approve petition from target-drift`);
21
+ }
22
+ if (model.pins.delivery !== pins.delivery.head) {
23
+ throw new FlowError("VERDICT_DENIED", `contract ${contractId} cannot approve petition from delivery-drift`);
24
+ }
25
+ }
21
26
  /**
22
27
  * Append a verdict entry and return typed judgment facts.
23
28
  * Does not claim, clean up, or otherwise execute delivery.
24
29
  */
25
30
  export async function verdictContract(input) {
26
31
  const at = new Date(input.nowMs).toISOString();
27
- const current = await readLedger(input.cwd, input.contractId);
28
- if (!current) {
29
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
30
- }
31
- assertPetitioned(input.contractId, current.entries);
32
- // Rejected verdict is allowed for any petitioned ledger, including legacy and
33
- // target mismatch, without a queue-head gate.
34
- if (input.result === "approved") {
35
- const model = await readQueueReadModel(input.cwd);
36
- assertClaimQueueHead(model, input.contractId);
37
- }
38
- const ref = activeLedgerRef(input.contractId);
39
- const appendResult = await appendWithRetry(input.cwd, ref, async (_head, entries) => {
40
- const existingVerdict = latestVerdict(entries);
41
- if (existingVerdict)
42
- return null;
43
- assertPetitioned(input.contractId, entries);
44
- if (input.result === "approved") {
45
- const model = await readQueueReadModel(input.cwd);
46
- assertClaimQueueHead(model, input.contractId);
47
- }
48
- const entry = {
49
- v: 1,
50
- kind: "verdict",
51
- contract: input.contractId,
52
- at,
53
- actor: input.actor,
54
- data: {
55
- result: input.result,
56
- summary: input.summary,
32
+ const execution = await runLifecycle({
33
+ cwd: input.cwd,
34
+ contractId: input.contractId,
35
+ mutation: {
36
+ verb: "verdict",
37
+ matchesAccepted: (transaction) => {
38
+ const accepted = transaction.entries.at(-1);
39
+ return accepted?.kind === "verdict"
40
+ && accepted.data.result === input.result
41
+ && accepted.data.summary === input.summary;
57
42
  },
58
- };
59
- return entry;
43
+ },
44
+ signal: input.signal,
45
+ admit: async (session, replay) => {
46
+ const transaction = replay.state === "verb-committed" ? replay.transaction : undefined;
47
+ const accepted = transaction?.entries.at(-1);
48
+ if (accepted && accepted.kind !== "verdict") {
49
+ throw new FlowError("INTERNAL_STATE", `accepted transaction ${transaction.transactionObjectId} is not a verdict`);
50
+ }
51
+ if (!accepted)
52
+ assertFoldedPetitioned(input.contractId, session.lifecycle);
53
+ const queue = input.result === "approved" && !accepted ? deriveQueueReadModel(session.repository) : null;
54
+ if (queue) {
55
+ assertClaimQueueHead(queue, input.contractId);
56
+ assertApprovedWindow(input.contractId, session.lifecycle, session.pins);
57
+ }
58
+ return {
59
+ existing: accepted?.kind === "verdict"
60
+ ? { transactionObjectId: transaction.transactionObjectId, result: accepted.data.result, summary: accepted.data.summary }
61
+ : undefined,
62
+ };
63
+ },
64
+ prepare: () => ({ entry: {
65
+ v: 1,
66
+ kind: "verdict",
67
+ contract: input.contractId,
68
+ at,
69
+ actor: input.actor,
70
+ data: {
71
+ result: input.result,
72
+ summary: input.summary,
73
+ },
74
+ } }),
75
+ revalidate: async ({ session, admission }) => {
76
+ if (admission.business.existing) {
77
+ return session.repository.topology.nodes.some((node) => node.objectId === admission.business.existing.transactionObjectId)
78
+ ? undefined
79
+ : { code: "VERDICT_CHANGED", message: "current petition verdict changed during preparation" };
80
+ }
81
+ assertFoldedPetitioned(input.contractId, session.lifecycle);
82
+ if (input.result === "approved") {
83
+ const queue = deriveQueueReadModel(session.repository);
84
+ try {
85
+ assertClaimQueueHead(queue, input.contractId);
86
+ assertApprovedWindow(input.contractId, session.lifecycle, session.pins);
87
+ }
88
+ catch (error) {
89
+ return {
90
+ code: "VERDICT_COORDINATES_CHANGED",
91
+ message: error instanceof Error ? error.message : String(error),
92
+ };
93
+ }
94
+ }
95
+ return undefined;
96
+ },
97
+ effect: async ({ context, admission, prepared }) => {
98
+ if (admission.business.existing) {
99
+ return {
100
+ kind: "committed",
101
+ commit: await context.replayCommitted({ transactionObjectId: admission.business.existing.transactionObjectId }),
102
+ entryKind: "verdict",
103
+ value: { result: admission.business.existing.result, summary: admission.business.existing.summary },
104
+ };
105
+ }
106
+ const committed = await context.publishAuthoritative({ verb: "verdict", admittedLedgerHead: admission.ledgerHead, acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId, entries: [prepared.entry] });
107
+ if (committed.status === "indeterminate") {
108
+ return { kind: "precommit-effects-only", authoritativeHead: committed.currentHead ?? admission.ledgerHead };
109
+ }
110
+ if (committed.status !== "committed") {
111
+ return { kind: "refused-before-effects", authoritativeHead: committed.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during verdict` } };
112
+ }
113
+ return {
114
+ kind: "committed",
115
+ commit: committed,
116
+ entryKind: "verdict",
117
+ value: { result: input.result, summary: input.summary },
118
+ };
119
+ },
60
120
  });
61
- if (!appendResult.ok && appendResult.reason !== "aborted") {
62
- throw new FlowError("INTERNAL_STATE", `failed to append verdict for ${input.contractId}: ${appendResult.reason}`);
63
- }
64
- const settled = await readLedger(input.cwd, input.contractId);
65
- if (!settled) {
66
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} disappeared before verdict settlement`);
67
- }
68
- const verdict = latestVerdict(settled.entries);
69
- if (!verdict) {
70
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing verdict after settlement write`);
71
- }
72
- if (verdict.data.result !== input.result) {
73
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} already has ${verdict.data.result} verdict`);
74
- }
75
- return {
76
- contractId: input.contractId,
77
- result: verdict.data.result,
78
- summary: verdict.data.summary,
79
- };
121
+ if (execution.kind === "noncommitted")
122
+ return execution.outcome;
123
+ if (execution.kind === "contained")
124
+ return { ...execution.outcome, contractId: input.contractId };
125
+ return { ...execution.outcome, contractId: input.contractId, ...execution.payload, carrier: execution.carrier };
80
126
  }