@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,12 +1,12 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { createGit, parseRevListOutput, wrapGitError } from "../git/core.js";
3
- import { KEIYAKU_FILE } from "../keiyaku.js";
4
- import { formatLedgerEntryCommitMessage } from "./entry.js";
5
- import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
6
- import { refreshContractView } from "./contract-view.js";
7
- import { normalizeScopePattern, scopePathEvidence, } from "./scope.js";
8
- import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
9
- import { casUpdateRefLog, createRefLogCommit } from "./ref-log.js";
3
+ import { effectiveScope } from "./lifecycle-history.js";
4
+ import { scopePathEvidence, } from "./scope.js";
5
+ import { isTerminalState } from "./status/lifecycle.js";
6
+ import { currentLifecycleState, lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
7
+ import { runLifecycle } from "./lifecycle-runner.js";
8
+ import { taskNamespaceFromScope } from "./task/coordinate.js";
9
+ export { effectiveScope } from "./lifecycle-history.js";
10
10
  function findBind(entries) {
11
11
  const bind = entries.find((entry) => entry.kind === "bind");
12
12
  if (!bind)
@@ -41,17 +41,6 @@ export function getFence(entries) {
41
41
  export async function receiptsAfterFence(cwd, entries, head = "HEAD") {
42
42
  return revList(cwd, getFence(entries), await resolveCommit(cwd, head));
43
43
  }
44
- export function effectiveScope(entries) {
45
- const scope = [...findBind(entries).data.scope];
46
- for (const entry of entries) {
47
- if (entry.kind !== "amend" || !entry.data.scopeDelta)
48
- continue;
49
- for (const pattern of entry.data.scopeDelta.add) {
50
- scope.push(pattern);
51
- }
52
- }
53
- return scope;
54
- }
55
44
  async function resolveCommit(cwd, revision) {
56
45
  const git = createGit(cwd);
57
46
  try {
@@ -100,12 +89,37 @@ async function diffstat(cwd, from, to) {
100
89
  }
101
90
  }
102
91
  async function scopeViolations(cwd, paths, scope) {
103
- const auditedPaths = paths.filter((changedPath) => changedPath !== KEIYAKU_FILE);
104
- return (await scopePathEvidence(cwd, scope, auditedPaths)).filter((evidence) => !evidence.matched);
92
+ return (await scopePathEvidence(cwd, scope, paths)).filter((evidence) => !evidence.matched);
93
+ }
94
+ function taskNamespaceViolations(paths, scope) {
95
+ const taskPaths = paths.filter((path) => path.startsWith(".keiyaku/tasks/"));
96
+ if (taskPaths.length === 0)
97
+ return [];
98
+ const namespaces = scope.flatMap((term) => taskNamespaceFromScope(term) ?? []);
99
+ if (namespaces.length !== 1) {
100
+ throw new FlowError("INTERNAL_STATE", "accepted bind must carry exactly one contract task namespace");
101
+ }
102
+ const prefix = `.keiyaku/tasks/${namespaces[0]}/`;
103
+ return taskPaths
104
+ .filter((path) => !path.startsWith(prefix))
105
+ .map((path) => ({ path, patterns: [], matched: false, outcome: "no-positive-match" }));
106
+ }
107
+ async function acceptedBindScope(cwd, contract) {
108
+ const outcome = lookupCurrentContract(await readRepositoryLifecycle(cwd), contract);
109
+ if (outcome.status !== "healthy") {
110
+ throw new FlowError("INTERNAL_STATE", `cannot resolve accepted bind namespace for contract ${contract}`);
111
+ }
112
+ const bind = outcome.model.history.find(({ entry }) => entry.kind === "bind")?.entry;
113
+ if (!bind || bind.kind !== "bind")
114
+ throw new FlowError("INTERNAL_STATE", `contract ${contract} has no accepted bind entry`);
115
+ return bind.data.scope;
105
116
  }
106
117
  async function auditScope(cwd, entries, paths) {
107
118
  const scope = effectiveScope(entries);
108
- const scopeViolationEvidence = await scopeViolations(cwd, paths, scope);
119
+ const scopeViolationEvidence = [
120
+ ...await scopeViolations(cwd, paths, scope),
121
+ ...taskNamespaceViolations(paths, findBind(entries).data.scope),
122
+ ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
109
123
  if (scopeViolationEvidence.length > 0) {
110
124
  throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
111
125
  }
@@ -115,29 +129,6 @@ async function auditScope(cwd, entries, paths) {
115
129
  scopeViolationEvidence,
116
130
  };
117
131
  }
118
- const APPEND_SCOPE_AMENDMENT = "Scope extended by lifecycle command.";
119
- export async function appendScopeTerms(input) {
120
- if (!input.patterns || input.patterns.length === 0)
121
- return null;
122
- const patterns = input.patterns.map(normalizeScopePattern);
123
- const at = new Date(input.nowMs).toISOString();
124
- const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
125
- assertOperable(input.contractId, entries);
126
- return {
127
- v: 1,
128
- kind: "amend",
129
- contract: input.contractId,
130
- at,
131
- actor: input.actor,
132
- data: { amendment: APPEND_SCOPE_AMENDMENT, scopeDelta: { add: patterns } },
133
- };
134
- });
135
- if (!result.ok) {
136
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared while appending scope`);
137
- }
138
- await refreshContractView({ ledgerCwd: input.cwd, renderCwd: input.renderCwd, contractId: input.contractId });
139
- return result.head;
140
- }
141
132
  export class ScopeAuditError extends FlowError {
142
133
  scopeViolationEvidence;
143
134
  constructor(_contractId, scope, evidence) {
@@ -154,15 +145,38 @@ export class ScopeAuditError extends FlowError {
154
145
  this.scopeViolationEvidence = evidence;
155
146
  }
156
147
  }
157
- function assertOperable(contractId, entries) {
158
- const state = deriveContractState(entries);
159
- if (state.state === "corrupt") {
160
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} is corrupt at entry ${state.at}: ${state.reason}`);
161
- }
148
+ function assertOperable(contractId, model) {
149
+ const state = currentLifecycleState(model);
162
150
  if (isTerminalState(state) || state.state === "petitioned") {
163
151
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot seal from state ${state.state}`);
164
152
  }
165
153
  }
154
+ function currentFoldedSealArc(model) {
155
+ return model.seal === null ? model.intent.arc : null;
156
+ }
157
+ /** Current-read admission capture: scope and fence come only from the folded model. */
158
+ export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = []) {
159
+ const arc = currentFoldedSealArc(model);
160
+ if (arc === null)
161
+ return null;
162
+ const oldFence = model.pins.delivery;
163
+ const newFence = await resolveCommit(cwd, head);
164
+ const [receipts, paths, stat] = await Promise.all([
165
+ revList(cwd, oldFence, newFence),
166
+ changedPaths(cwd, oldFence, newFence),
167
+ diffstat(cwd, oldFence, newFence),
168
+ ]);
169
+ const effectiveScope = [...model.effectiveScope, ...scopeAppend];
170
+ const bind = model.history.find((row) => row.entry.kind === "bind")?.entry;
171
+ const scopeViolationEvidence = [
172
+ ...await scopeViolations(cwd, paths, effectiveScope),
173
+ ...taskNamespaceViolations(paths, bind?.kind === "bind" ? bind.data.scope : []),
174
+ ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
175
+ if (scopeViolationEvidence.length > 0) {
176
+ throw new ScopeAuditError(model.contract, effectiveScope, scopeViolationEvidence);
177
+ }
178
+ return { arc, oldFence, newFence, receipts, diffstat: stat, scopeAudit: { violations: [] }, scopeViolationEvidence };
179
+ }
166
180
  /** Current seal intent: open explicit arc, else derived a0 while no explicit arc has ever existed. */
167
181
  export function currentSealArc(entries) {
168
182
  const open = findOpenArc(entries);
@@ -207,12 +221,12 @@ export async function previewSeal(cwd, entries, head = "HEAD") {
207
221
  export async function captureSeal(cwd, entries, head = "HEAD") {
208
222
  return (await previewSeal(cwd, entries, head)).capture;
209
223
  }
210
- /** Preserve original receipt hashes while auditing the rebased delivery content. */
211
- export async function captureRenewSeal(input) {
212
- const arc = currentSealArc(input.entries);
213
- if (arc === null)
224
+ /** Folded renew capture: preserve receipt hashes while auditing rebased delivery content. */
225
+ export async function captureFoldedRenewSeal(input) {
226
+ if (!input.receiptSegmentOpen)
214
227
  return null;
215
- const oldFence = getFence(input.entries);
228
+ const arc = input.intent.arc;
229
+ const oldFence = input.deliveryFence;
216
230
  const [receiptHead, contentBase, contentHead] = await Promise.all([
217
231
  resolveCommit(input.cwd, input.receiptHead),
218
232
  resolveCommit(input.cwd, input.contentBase),
@@ -223,123 +237,116 @@ export async function captureRenewSeal(input) {
223
237
  changedPaths(input.cwd, contentBase, contentHead),
224
238
  diffstat(input.cwd, contentBase, contentHead),
225
239
  ]);
240
+ const scope = [...input.effectiveScope, ...input.pendingScopeOverlay];
241
+ const bindScope = await acceptedBindScope(input.cwd, input.contract);
242
+ const scopeViolationEvidence = [
243
+ ...await scopeViolations(input.cwd, paths, scope),
244
+ ...taskNamespaceViolations(paths, bindScope),
245
+ ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
246
+ if (scopeViolationEvidence.length > 0) {
247
+ throw new ScopeAuditError(input.contract, scope, scopeViolationEvidence);
248
+ }
226
249
  return {
227
250
  arc,
228
251
  oldFence,
229
252
  newFence: receiptHead,
230
253
  receipts,
231
254
  diffstat: stat,
232
- ...await auditScope(input.cwd, input.entries, paths),
255
+ scopeAudit: { violations: [] },
256
+ scopeViolationEvidence,
233
257
  };
234
258
  }
235
259
  export async function sealCurrentArc(input) {
236
- const ledger = await readLedger(input.cwd, input.contractId);
237
- if (!ledger) {
238
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
239
- }
240
- assertOperable(input.contractId, ledger.entries);
241
- const capture = input.capture ?? await captureSeal(input.cwd, ledger.entries, "HEAD");
242
- if (!capture)
243
- return null;
244
- const at = new Date(input.nowMs).toISOString();
245
- const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
246
- assertOperable(input.contractId, entries);
247
- const currentArc = currentSealArc(entries);
248
- if (currentArc === null)
249
- return null;
250
- // a0 is legal only while no explicit arc has ever existed; otherwise treat as concurrent change.
251
- if (capture.arc === 0 && getMaxArc(entries) > 0) {
252
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} changed while sealing arc a${capture.arc}`);
253
- }
254
- if (currentArc !== capture.arc || getFence(entries) !== capture.oldFence) {
255
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} changed while sealing arc a${capture.arc}`);
256
- }
257
- const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
258
- return {
259
- v: 2,
260
- kind: "seal",
261
- contract: input.contractId,
262
- at,
263
- actor: "engine",
264
- data: { ...durableCapture, sealedBy: input.sealedBy },
265
- };
266
- });
267
- if (!result.ok) {
268
- throw new FlowError("INTERNAL_STATE", `failed to seal arc for ${input.contractId}: ${result.reason}`);
269
- }
270
- await refreshContractView({
271
- ledgerCwd: input.cwd,
272
- renderCwd: input.renderCwd,
260
+ const execution = await runLifecycle({
261
+ cwd: input.cwd,
273
262
  contractId: input.contractId,
274
- });
275
- return {
276
- ...capture,
277
- sealedBy: input.sealedBy,
278
- ledgerHead: result.head,
279
- };
280
- }
281
- function splitLedgerMessage(entry) {
282
- const message = formatLedgerEntryCommitMessage(entry);
283
- const separator = message.indexOf("\n\n");
284
- return { subject: message.slice(0, separator), body: message.slice(separator + 2).trimEnd() };
285
- }
286
- export async function appendRenewSettlement(input) {
287
- const ledger = await readLedger(input.cwd, input.contractId);
288
- if (!ledger || ledger.head !== input.expectedLedgerHead) {
289
- throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
290
- }
291
- assertOperable(input.contractId, ledger.entries);
292
- if (input.capture && (currentSealArc(ledger.entries) !== input.capture.arc || getFence(ledger.entries) !== input.capture.oldFence)) {
293
- throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
294
- }
295
- const at = new Date(input.nowMs).toISOString();
296
- let parentHead = ledger.head;
297
- let sealHead;
298
- if (input.capture) {
299
- const { scopeViolationEvidence: _evidence, ...durableCapture } = input.capture;
300
- const sealEntry = {
301
- v: 2,
302
- kind: "seal",
303
- contract: input.contractId,
304
- at,
305
- actor: "engine",
306
- data: { ...durableCapture, newFence: input.newHead, sealedBy: "renew" },
307
- };
308
- const message = splitLedgerMessage(sealEntry);
309
- sealHead = await createRefLogCommit(input.cwd, {
310
- ...message,
311
- parentHead,
312
- errorLabel: `ledger seal ${input.contractId}`,
313
- });
314
- parentHead = sealHead;
315
- }
316
- const renewEntry = {
317
- v: 1,
318
- kind: "renew",
319
- contract: input.contractId,
320
- at,
321
- actor: input.actor,
322
- data: {
323
- oldBase: input.oldBase,
324
- newBase: input.newBase,
325
- oldHead: input.oldHead,
326
- newHead: input.newHead,
263
+ mutation: {
264
+ verb: "seal",
265
+ matchesAccepted: (transaction) => {
266
+ const accepted = transaction.entries.at(-1);
267
+ if (transaction.entries.length !== 1 || accepted?.kind !== "seal" || accepted.data.sealedBy !== input.sealedBy)
268
+ return false;
269
+ if (input.capture === undefined)
270
+ return true;
271
+ if (input.capture === null)
272
+ return false;
273
+ const { scopeViolationEvidence: _evidence, ...durableCapture } = input.capture;
274
+ return JSON.stringify(accepted.data) === JSON.stringify({ ...durableCapture, sealedBy: input.sealedBy });
275
+ },
276
+ },
277
+ signal: input.signal,
278
+ admit: (session, replay) => {
279
+ assertOperable(input.contractId, session.lifecycle);
280
+ if (session.target.status !== "available")
281
+ throw new FlowError("INTERNAL_STATE", session.target.reason);
282
+ const transaction = replay.state === "verb-committed" ? replay.transaction : undefined;
283
+ const accepted = transaction?.entries.at(-1);
284
+ let existing;
285
+ if (accepted?.kind === "seal") {
286
+ existing = {
287
+ transactionObjectId: transaction.transactionObjectId,
288
+ capture: {
289
+ arc: accepted.data.arc,
290
+ oldFence: accepted.data.oldFence,
291
+ newFence: accepted.data.newFence,
292
+ receipts: [...accepted.data.receipts],
293
+ diffstat: { ...accepted.data.diffstat },
294
+ scopeAudit: { violations: [...accepted.data.scopeAudit.violations] },
295
+ scopeViolationEvidence: [],
296
+ },
297
+ };
298
+ }
299
+ return { lifecycle: session.lifecycle, targetRoot: session.target.root, ...(existing ? { existing } : {}) };
300
+ },
301
+ prepare: async (admission) => {
302
+ if (admission.business.existing)
303
+ return { capture: admission.business.existing.capture, existing: { transactionObjectId: admission.business.existing.transactionObjectId } };
304
+ return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD") };
305
+ },
306
+ revalidate: ({ session, prepared }) => {
307
+ assertOperable(input.contractId, session.lifecycle);
308
+ if (prepared.existing) {
309
+ return session.repository.topology.nodes.some((node) => node.objectId === prepared.existing.transactionObjectId)
310
+ ? undefined
311
+ : { code: "SEAL_STATE_CHANGED", message: "accepted seal changed during replay preparation" };
312
+ }
313
+ if (!prepared.capture)
314
+ return;
315
+ const currentArc = currentFoldedSealArc(session.lifecycle);
316
+ if (currentArc !== prepared.capture.arc || session.lifecycle.pins.delivery !== prepared.capture.oldFence)
317
+ return { code: "SEAL_COORDINATES_CHANGED", message: `contract ${input.contractId} changed while sealing arc a${prepared.capture.arc}` };
318
+ },
319
+ effect: async ({ context, prepared }) => {
320
+ const capture = prepared.capture;
321
+ if (!capture)
322
+ return { kind: "refused-before-effects", authoritativeHead: context.session.lifecycle.observedLedgerHead, refusal: { code: "NO_OPEN_ARC", message: "no current arc requires sealing" } };
323
+ const at = new Date(input.nowMs).toISOString();
324
+ {
325
+ const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
326
+ const entry = {
327
+ v: 2,
328
+ kind: "seal",
329
+ contract: input.contractId,
330
+ at,
331
+ actor: "engine",
332
+ data: { ...durableCapture, sealedBy: input.sealedBy },
333
+ };
334
+ const result = prepared.existing
335
+ ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
336
+ : await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.lifecycle.observedLedgerHead, acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId, entries: [entry] });
337
+ if (result.status === "indeterminate")
338
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead };
339
+ if (result.status !== "committed")
340
+ return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed while sealing` } };
341
+ return { kind: "committed", commit: result, entryKind: "seal", value: {
342
+ ...capture,
343
+ sealedBy: input.sealedBy,
344
+ ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((candidate) => candidate.kind === "seal")],
345
+ } };
346
+ }
327
347
  },
328
- };
329
- const message = splitLedgerMessage(renewEntry);
330
- const renewHead = await createRefLogCommit(input.cwd, {
331
- ...message,
332
- parentHead,
333
- errorLabel: `ledger renew ${input.contractId}`,
334
348
  });
335
- if (!await casUpdateRefLog(input.cwd, activeLedgerRef(input.contractId), renewHead, ledger.head)) {
336
- throw new FlowError("CONCURRENT_MODIFICATION", `contract ${input.contractId} changed while renewing`);
337
- }
338
- const sealed = input.capture ? {
339
- ...input.capture,
340
- newFence: input.newHead,
341
- sealedBy: "renew",
342
- ledgerHead: sealHead,
343
- } : null;
344
- return { ledgerHead: renewHead, sealed };
349
+ if (execution.kind === "noncommitted" || execution.kind === "contained")
350
+ return execution.outcome;
351
+ return execution.payload;
345
352
  }