@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,13 +1,14 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
3
- import { activeLedgerRef, appendWithRetry } from "./ledger.js";
4
- import { refreshContractView } from "./contract-view.js";
5
- import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
3
+ import { runLifecycle } from "./lifecycle-runner.js";
4
+ import { currentLifecycleState } from "./repository-ledger/read-model.js";
5
+ import { isTerminalState } from "./status/lifecycle.js";
6
6
  import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
7
7
  import { resolveDefaultBranch } from "./status/drift.js";
8
8
  import { markdownSourceSections, normalizeScopePattern, ScopePatternError, validateScopeFragmentLines } from "./scope.js";
9
9
  import { normalizeSectionTitle } from "./markdown/titles.js";
10
- import { resolveContractDeliveryCwd } from "./worktree-path.js";
10
+ import { isCompleteLifecycleOutcome } from "./outcome-base.js";
11
+ import { getConfig } from "../config/env.js";
11
12
  function amendScopeHeading(name) {
12
13
  return `## Scope ${name[0].toUpperCase()}${name.slice(1)}`;
13
14
  }
@@ -79,11 +80,8 @@ function requireAmendment(value) {
79
80
  throw new FlowError("EMPTY_PARAM", "amendment cannot be empty");
80
81
  return value;
81
82
  }
82
- function assertContractOperable(contractId, entries) {
83
- const state = deriveContractState(entries);
84
- if (state.state === "corrupt") {
85
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} is corrupt at entry ${state.at}: ${state.reason}`);
86
- }
83
+ function assertContractOperable(contractId, model) {
84
+ const state = currentLifecycleState(model);
87
85
  if (isTerminalState(state)) {
88
86
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} is terminal (${state.state})`);
89
87
  }
@@ -100,53 +98,131 @@ function normalizeScopeDelta(scopeDelta) {
100
98
  export async function amendContract(input) {
101
99
  const amendment = requireAmendment(input.amendment);
102
100
  const scopeDelta = normalizeScopeDelta(input.scopeDelta);
103
- const renderCwd = input.renderCwd ?? input.cwd;
104
101
  const at = new Date(input.nowMs).toISOString();
105
- const ref = activeLedgerRef(input.contractId);
106
- let amendmentNumber = 0;
107
- const result = await appendWithRetry(input.cwd, ref, (_currentHead, entries) => {
108
- if (entries.length === 0)
109
- return null;
110
- assertContractOperable(input.contractId, entries);
111
- amendmentNumber = entries.filter((entry) => entry.kind === "amend").length + 1;
112
- const entry = {
113
- v: 1,
114
- kind: "amend",
115
- contract: input.contractId,
116
- at,
117
- actor: input.actor,
118
- data: { amendment, ...(scopeDelta ? { scopeDelta } : {}) },
119
- };
120
- return entry;
102
+ const execution = await runLifecycle({
103
+ cwd: input.cwd,
104
+ contractId: input.contractId,
105
+ mutation: {
106
+ verb: "amend",
107
+ // Amendment content is not a retry key; the containing Lnew is the sole mutation identity.
108
+ matchesAccepted: () => false,
109
+ },
110
+ signal: input.signal,
111
+ admit: (session, replay) => {
112
+ assertContractOperable(input.contractId, session.lifecycle);
113
+ if (session.target.status !== "available")
114
+ throw new FlowError("INTERNAL_STATE", session.target.reason);
115
+ const transaction = replay.state === "verb-committed" ? replay.transaction : undefined;
116
+ const accepted = transaction?.entries.at(-1);
117
+ if (accepted && accepted.kind !== "amend")
118
+ throw new FlowError("INTERNAL_STATE", `accepted transaction ${transaction.transactionObjectId} is not an amend`);
119
+ const existing = accepted?.kind === "amend" ? { transactionObjectId: transaction.transactionObjectId } : undefined;
120
+ const amendmentCount = session.lifecycle.history.filter(({ entry }) => entry.kind === "amend").length;
121
+ return {
122
+ amendmentNumber: amendmentCount + (existing ? 0 : 1),
123
+ targetRoot: session.target.root,
124
+ ...(existing ? { existing } : {}),
125
+ };
126
+ },
127
+ prepare: (admission) => {
128
+ if (admission.business.existing) {
129
+ return {
130
+ amendmentNumber: admission.business.amendmentNumber,
131
+ existing: admission.business.existing,
132
+ entry: null,
133
+ };
134
+ }
135
+ return {
136
+ amendmentNumber: admission.business.amendmentNumber,
137
+ entry: {
138
+ v: 1,
139
+ kind: "amend",
140
+ contract: input.contractId,
141
+ at,
142
+ actor: input.actor,
143
+ data: { amendment, ...(scopeDelta ? { scopeDelta } : {}) },
144
+ },
145
+ };
146
+ },
147
+ revalidate: ({ session, admission, prepared }) => {
148
+ assertContractOperable(input.contractId, session.lifecycle);
149
+ if (session.target.status !== "available") {
150
+ return { code: "CARRIER_TARGET_UNAVAILABLE", message: session.target.reason };
151
+ }
152
+ if (prepared.existing) {
153
+ return session.repository.topology.nodes.some((node) => node.objectId === prepared.existing.transactionObjectId)
154
+ ? undefined
155
+ : { code: "AMEND_STATE_CHANGED", message: "accepted amend changed during replay preparation" };
156
+ }
157
+ const amendmentNumber = session.lifecycle.history.filter(({ entry }) => entry.kind === "amend").length + 1;
158
+ return amendmentNumber === admission.business.amendmentNumber
159
+ ? undefined
160
+ : { code: "AMENDMENT_NUMBER_CHANGED", message: "amendment sequence changed during preparation" };
161
+ },
162
+ effect: async ({ context, admission, prepared }) => {
163
+ const result = prepared.existing
164
+ ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
165
+ : await context.publishAuthoritative({ verb: "amend", admittedLedgerHead: admission.ledgerHead, acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId, entries: [prepared.entry] });
166
+ if (result.status === "indeterminate") {
167
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? admission.ledgerHead };
168
+ }
169
+ if (result.status !== "committed") {
170
+ return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during amend` } };
171
+ }
172
+ return {
173
+ kind: "committed",
174
+ commit: result,
175
+ entryKind: "amend",
176
+ value: {
177
+ amendmentNumber: prepared.amendmentNumber,
178
+ amendmentWarning: input.amendmentWarningThreshold === undefined
179
+ ? undefined
180
+ : prepared.amendmentNumber > input.amendmentWarningThreshold
181
+ ? { amendmentCount: prepared.amendmentNumber, threshold: input.amendmentWarningThreshold }
182
+ : undefined,
183
+ targetRoot: admission.business.targetRoot,
184
+ },
185
+ };
186
+ },
121
187
  });
122
- if (!result.ok) {
123
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared during amend: ${result.reason}`);
124
- }
125
- if (amendmentNumber === 0) {
126
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared during amend`);
127
- }
128
- await refreshContractView({ ledgerCwd: input.cwd, renderCwd, contractId: input.contractId });
129
- return { contractId: input.contractId, amendmentNumber, ledgerHead: result.head };
188
+ if (execution.kind === "noncommitted")
189
+ return execution.outcome;
190
+ if (execution.kind === "contained")
191
+ return { ...execution.outcome, contractId: input.contractId };
192
+ return {
193
+ ...execution.outcome,
194
+ contractId: input.contractId,
195
+ amendmentNumber: execution.payload.amendmentNumber,
196
+ ...(execution.payload.amendmentWarning ? { amendmentWarning: execution.payload.amendmentWarning } : {}),
197
+ ledgerHead: execution.outcome.committed.head,
198
+ carrier: execution.carrier,
199
+ targetRoot: execution.payload.targetRoot,
200
+ };
130
201
  }
131
202
  export async function amendKeiyaku(input) {
132
203
  await requireGitRepository(input.cwd, "amend");
133
204
  const address = input.address ?? await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
134
205
  return await withResolvedAddress(address, async () => {
135
206
  const contractId = address.binding.commissionId;
136
- const renderCwd = await resolveContractDeliveryCwd(input.cwd, contractId);
137
207
  const amendment = requireAmendment(input.amendment);
138
- const currentBranch = await getCurrentBranch(renderCwd);
139
- const baseBranch = (await resolveDefaultBranch(input.cwd))?.branch ?? currentBranch;
208
+ const amendmentWarningThreshold = getConfig().amendmentWarning.threshold;
140
209
  const amended = await amendContract({
141
210
  cwd: input.cwd,
142
- renderCwd,
143
211
  contractId,
144
212
  actor: "keiyaku",
145
213
  nowMs: Date.now(),
146
214
  amendment,
215
+ amendmentWarningThreshold,
147
216
  scopeDelta: input.scopeDelta,
217
+ signal: input.signal,
148
218
  });
219
+ if (!isCompleteLifecycleOutcome(amended))
220
+ return amended;
221
+ const { targetRoot, ...amendedFacts } = amended;
222
+ const currentBranch = await getCurrentBranch(targetRoot);
223
+ const baseBranch = (await resolveDefaultBranch(input.cwd))?.branch ?? currentBranch;
149
224
  return {
225
+ ...amendedFacts,
150
226
  status: "success",
151
227
  amendmentNumber: amended.amendmentNumber,
152
228
  currentBranch,
@@ -154,6 +230,7 @@ export async function amendKeiyaku(input) {
154
230
  ledgerCommit: amended.ledgerHead,
155
231
  summary: `Appended Amendment ${amended.amendmentNumber} to contract ledger ${contractId}.`,
156
232
  address,
233
+ carrier: amended.carrier,
157
234
  };
158
235
  });
159
236
  }
@@ -0,0 +1,17 @@
1
+ import { parseToAST } from "./markdown/parser.js";
2
+ import { extractListItems } from "./markdown/render.js";
3
+ import { normalizeSectionTitle } from "./markdown/titles.js";
4
+ /** Criteria declared by one lawful amendment, without imposing bind-document grammar. */
5
+ export function amendmentCriteria(amendment) {
6
+ const criteria = [];
7
+ for (const node of parseToAST(amendment).children) {
8
+ if (node.type !== "section" || node.level !== 2 || normalizeSectionTitle(node.title) !== "criteria")
9
+ continue;
10
+ criteria.push(...extractListItems(node).map((item) => item.trim()).filter(Boolean));
11
+ }
12
+ return criteria;
13
+ }
14
+ /** Bind declarations first, then amendment declarations in accepted lifecycle order. */
15
+ export function effectiveCriteria(bindCriteria, amendments) {
16
+ return [...bindCriteria, ...amendments.flatMap(amendmentCriteria)];
17
+ }
@@ -0,0 +1,10 @@
1
+ import { currentLifecycleState } from "./repository-ledger/read-model.js";
2
+ export function deriveAmendmentWarning(model, threshold) {
3
+ if (model.history[0]?.entry.kind !== "bind")
4
+ return undefined;
5
+ const state = currentLifecycleState(model);
6
+ if (state.state === "claimed" || state.state === "forfeited")
7
+ return undefined;
8
+ const amendmentCount = model.history.filter(({ entry }) => entry.kind === "amend").length;
9
+ return amendmentCount > threshold ? { amendmentCount, threshold } : undefined;
10
+ }
package/build/core/arc.js CHANGED
@@ -1,19 +1,18 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
1
  import { requireText, FlowError } from "../flow-error.js";
4
2
  import { getCurrentBranch, requireGitRepository } from "../git/branches.js";
5
3
  import { assertCleanTrackedWorkingTree } from "../git/worktree.js";
6
- import { KEIYAKU_FILE, ACTOR_IDENTITY } from "../keiyaku.js";
4
+ import { ACTOR_IDENTITY } from "../keiyaku.js";
7
5
  import { normalizeInlineTitle } from "./markdown/titles.js";
8
6
  import { parseCommandMarkdown, requireMarkdownTextSection, requireMarkdownTitle, } from "./command-markdown.js";
7
+ import { isCompleteLifecycleOutcome } from "./outcome-base.js";
9
8
  import { resolveContractAddress, withResolvedAddress } from "./addressing.js";
10
- import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
11
- import { refreshContractView } from "./contract-view.js";
12
- import { appendScopeTerms, findOpenArc, getMaxArc, sealCurrentArc } from "./seal.js";
13
- import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
9
+ import { runLifecycle } from "./lifecycle-runner.js";
10
+ import { captureFoldedSeal, findOpenArc } from "./seal.js";
11
+ import { normalizeScopePattern } from "./scope.js";
12
+ import { currentLifecycleState } from "./repository-ledger/read-model.js";
13
+ import { isTerminalState } from "./status/lifecycle.js";
14
14
  import { resolveDefaultBranch } from "./status/drift.js";
15
15
  import { persistResponseHistory } from "./transcripts.js";
16
- import { resolveContractDeliveryCwd } from "./worktree-path.js";
17
16
  export function buildArcViews(entries) {
18
17
  const arcs = new Map();
19
18
  for (const entry of entries) {
@@ -40,13 +39,10 @@ export function findOpenArcView(entries) {
40
39
  return null;
41
40
  return buildArcViews(entries).find((arc) => arc.arc === open.data.arc) ?? null;
42
41
  }
43
- function assertArcOpenable(contractId, entries) {
44
- const state = deriveContractState(entries);
42
+ function assertArcOpenable(contractId, model) {
43
+ const state = currentLifecycleState(model);
45
44
  if (state.state === "bound" || state.state === "active")
46
45
  return;
47
- if (state.state === "corrupt") {
48
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot open arc from corrupt state at entry ${state.at}`);
49
- }
50
46
  if (isTerminalState(state) || state.state === "petitioned") {
51
47
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot open arc from state ${state.state}`);
52
48
  }
@@ -55,50 +51,166 @@ export async function openArc(input) {
55
51
  const title = requireText("title", input.title);
56
52
  const objective = requireText("objective", input.objective);
57
53
  const brief = requireText("brief", input.brief);
58
- const renderCwd = input.renderCwd ?? input.cwd;
59
- const ledger = await readLedger(input.cwd, input.contractId);
60
- if (!ledger) {
61
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
62
- }
63
- assertArcOpenable(input.contractId, ledger.entries);
64
- await appendScopeTerms({
54
+ const patterns = input.appendScope?.map(normalizeScopePattern) ?? [];
55
+ const at = new Date(input.nowMs).toISOString();
56
+ const execution = await runLifecycle({
65
57
  cwd: input.cwd,
66
- renderCwd,
67
58
  contractId: input.contractId,
68
- patterns: input.appendScope,
69
- actor: input.actor,
70
- nowMs: input.nowMs,
59
+ mutation: {
60
+ verb: "arc",
61
+ matchesAccepted: (transaction) => {
62
+ const accepted = transaction.entries.at(-1);
63
+ const acceptedAmend = transaction.entries.find((entry) => entry.kind === "amend");
64
+ const acceptedPatterns = acceptedAmend?.kind === "amend" ? acceptedAmend.data.scopeDelta?.add ?? [] : [];
65
+ return accepted?.kind === "arc"
66
+ && accepted.data.title === title
67
+ && accepted.data.objective === objective
68
+ && accepted.data.brief === brief
69
+ && JSON.stringify(acceptedPatterns) === JSON.stringify(patterns);
70
+ },
71
+ },
72
+ signal: input.signal,
73
+ admit: (session, replay) => {
74
+ assertArcOpenable(input.contractId, session.lifecycle);
75
+ if (session.target.status !== "available")
76
+ throw new FlowError("INTERNAL_STATE", session.target.reason);
77
+ const accepted = replay.state === "verb-committed" ? replay.transaction.entries.at(-1) : undefined;
78
+ let existing;
79
+ if (accepted?.kind === "arc" && replay.state === "verb-committed") {
80
+ const seal = replay.transaction.entries.find((entry) => entry.kind === "seal");
81
+ existing = {
82
+ transactionObjectId: replay.transaction.transactionObjectId,
83
+ arcNumber: accepted.data.arc,
84
+ capture: seal?.kind === "seal" ? {
85
+ arc: seal.data.arc,
86
+ oldFence: seal.data.oldFence,
87
+ newFence: seal.data.newFence,
88
+ receipts: [...seal.data.receipts],
89
+ diffstat: { ...seal.data.diffstat },
90
+ scopeAudit: { violations: [...seal.data.scopeAudit.violations] },
91
+ scopeViolationEvidence: [],
92
+ } : null,
93
+ };
94
+ }
95
+ return { lifecycle: session.lifecycle, targetRoot: session.target.root, ...(existing ? { existing } : {}) };
96
+ },
97
+ prepare: async (admission) => {
98
+ if (admission.business.existing) {
99
+ return {
100
+ additions: [],
101
+ arcNumber: admission.business.existing.arcNumber,
102
+ capture: admission.business.existing.capture,
103
+ existing: admission.business.existing,
104
+ };
105
+ }
106
+ const scopeEntry = patterns.length === 0 ? null : {
107
+ v: 1,
108
+ kind: "amend",
109
+ contract: input.contractId,
110
+ at,
111
+ actor: input.actor,
112
+ data: { amendment: "Scope extended by lifecycle command.", scopeDelta: { add: patterns } },
113
+ };
114
+ const capture = await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD", patterns);
115
+ const arcNumber = admission.business.lifecycle.intent.arc + 1;
116
+ let sealEntry = null;
117
+ if (capture) {
118
+ const { scopeViolationEvidence: _evidence, ...durableCapture } = capture;
119
+ sealEntry = {
120
+ v: 2,
121
+ kind: "seal",
122
+ contract: input.contractId,
123
+ at,
124
+ actor: "engine",
125
+ data: { ...durableCapture, sealedBy: "arc" },
126
+ };
127
+ }
128
+ const arcEntry = {
129
+ v: 1,
130
+ kind: "arc",
131
+ contract: input.contractId,
132
+ at,
133
+ actor: input.actor,
134
+ data: { arc: arcNumber, title, objective, brief },
135
+ };
136
+ const additions = scopeEntry
137
+ ? sealEntry ? [scopeEntry, sealEntry, arcEntry] : [scopeEntry, arcEntry]
138
+ : sealEntry ? [sealEntry, arcEntry] : [arcEntry];
139
+ return { additions, arcNumber, capture };
140
+ },
141
+ revalidate: async ({ session, prepared }) => {
142
+ assertArcOpenable(input.contractId, session.lifecycle);
143
+ if (session.target.status !== "available")
144
+ return { code: "CARRIER_TARGET_UNAVAILABLE", message: session.target.reason };
145
+ if (prepared.existing) {
146
+ return session.repository.topology.nodes.some((node) => node.objectId === prepared.existing.transactionObjectId)
147
+ ? undefined
148
+ : { code: "ARC_STATE_CHANGED", message: "accepted arc changed during replay preparation" };
149
+ }
150
+ await assertCleanTrackedWorkingTree(session.target.root);
151
+ return session.lifecycle.intent.arc + 1 === prepared.arcNumber
152
+ ? undefined
153
+ : { code: "ARC_NUMBER_CHANGED", message: "arc sequence changed during preparation" };
154
+ },
155
+ effect: async ({ context, admission, prepared }) => {
156
+ const result = prepared.existing
157
+ ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
158
+ : await context.publishAuthoritative({
159
+ verb: "arc",
160
+ admittedLedgerHead: admission.ledgerHead,
161
+ acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
162
+ entries: prepared.additions,
163
+ });
164
+ if (result.status === "indeterminate") {
165
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? admission.ledgerHead };
166
+ }
167
+ if (result.status !== "committed" || prepared.arcNumber === 0) {
168
+ return { kind: "refused-before-effects", authoritativeHead: result.status === "committed" ? result.head : result.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during arc` } };
169
+ }
170
+ const committedCapture = prepared.capture;
171
+ const sealed = committedCapture && {
172
+ ...committedCapture,
173
+ sealedBy: "arc",
174
+ ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((entry) => entry.kind === "seal")],
175
+ };
176
+ let response;
177
+ if (input.persistResponse) {
178
+ await context.journal.run({
179
+ effect: "response-history-persistence",
180
+ run: async () => {
181
+ response = await input.persistResponse({ ledgerHead: result.head, sealed });
182
+ return response;
183
+ },
184
+ });
185
+ }
186
+ return {
187
+ kind: "committed",
188
+ commit: result,
189
+ entryKind: "arc",
190
+ value: {
191
+ arc: prepared.arcNumber,
192
+ sealed,
193
+ targetRoot: admission.business.targetRoot,
194
+ ...(response ?? {}),
195
+ },
196
+ };
197
+ },
71
198
  });
72
- // Seal capture needs delivery HEAD; ledger append may use hub or delivery (shared refs).
73
- const sealed = await sealCurrentArc({
74
- cwd: renderCwd,
75
- renderCwd,
199
+ if (execution.kind === "noncommitted")
200
+ return execution.outcome;
201
+ if (execution.kind === "contained")
202
+ return { ...execution.outcome, contractId: input.contractId };
203
+ return {
204
+ ...execution.outcome,
76
205
  contractId: input.contractId,
77
- sealedBy: "arc",
78
- nowMs: input.nowMs,
79
- });
80
- const at = new Date(input.nowMs).toISOString();
81
- let arcNumber = 0;
82
- const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), (_head, entries) => {
83
- assertArcOpenable(input.contractId, entries);
84
- if (findOpenArc(entries)) {
85
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} still has an open arc after sealing`);
86
- }
87
- arcNumber = getMaxArc(entries) + 1;
88
- return {
89
- v: 1,
90
- kind: "arc",
91
- contract: input.contractId,
92
- at,
93
- actor: input.actor,
94
- data: { arc: arcNumber, title, objective, brief },
95
- };
96
- });
97
- if (!result.ok || arcNumber === 0) {
98
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} active ledger disappeared during arc`);
99
- }
100
- await refreshContractView({ ledgerCwd: input.cwd, renderCwd, contractId: input.contractId });
101
- return { contractId: input.contractId, arc: arcNumber, ledgerHead: result.head, sealed };
206
+ arc: execution.payload.arc,
207
+ ledgerHead: execution.outcome.committed.head,
208
+ sealed: execution.payload.sealed,
209
+ carrier: execution.carrier,
210
+ targetRoot: execution.payload.targetRoot,
211
+ ...(execution.payload.responsePath ? { responsePath: execution.payload.responsePath } : {}),
212
+ ...(execution.payload.artifactId ? { artifactId: execution.payload.artifactId } : {}),
213
+ };
102
214
  }
103
215
  export function buildArcInput(content, cwd, appendScope = [], contractId, contractAddressSource) {
104
216
  const parts = parseCommandMarkdown(content);
@@ -122,21 +234,15 @@ export async function arcKeiyaku(input) {
122
234
  const address = input.address ?? await resolveContractAddress(input.cwd, input.contractId, input.contractAddressSource);
123
235
  return await withResolvedAddress(address, async () => {
124
236
  const contractId = address.binding.commissionId;
125
- const renderCwd = await resolveContractDeliveryCwd(input.cwd, contractId);
126
- await assertCleanTrackedWorkingTree(renderCwd);
127
- try {
128
- await fs.access(path.join(renderCwd, KEIYAKU_FILE));
129
- }
130
- catch {
131
- throw new FlowError("MISSING_PROTOCOL_FILES", `missing protocol file for current keiyaku (${KEIYAKU_FILE})`);
132
- }
133
237
  const title = normalizeInlineTitle(requireText("title", input.title));
134
238
  const objective = requireText("objective", input.objective);
135
239
  const brief = requireText("brief", input.brief);
136
240
  const nowMs = Date.now();
241
+ const summaryFor = (sealed, arc) => sealed
242
+ ? `a${sealed.arc} sealed; a${arc} open: ${title}.`
243
+ : `a${arc} open: ${title}.`;
137
244
  const opened = await openArc({
138
245
  cwd: input.cwd,
139
- renderCwd,
140
246
  contractId,
141
247
  actor: ACTOR_IDENTITY,
142
248
  nowMs,
@@ -144,33 +250,27 @@ export async function arcKeiyaku(input) {
144
250
  objective,
145
251
  brief,
146
252
  appendScope: input.appendScope,
147
- });
148
- const { currentBranch, baseBranch } = await readBranchLabels(renderCwd);
149
- const summary = opened.sealed
150
- ? `a${opened.sealed.arc} sealed; a${opened.arc} open: ${title}.`
151
- : `a${opened.arc} open: ${title}.`;
152
- let responsePath;
153
- let artifactId;
154
- try {
155
- const persisted = await persistResponseHistory({
253
+ signal: input.signal,
254
+ persistResponse: async ({ ledgerHead, sealed }) => persistResponseHistory({
156
255
  cwd: input.cwd,
157
256
  tool: "arc",
158
257
  provenance: { authority: { kind: "repository" }, binding: address.binding, evidenceCwd: input.cwd },
159
258
  title,
160
259
  objective,
161
260
  brief,
162
- ledgerCommit: opened.ledgerHead,
163
- summary,
261
+ ledgerCommit: ledgerHead,
262
+ summary: summaryFor(sealed, (sealed?.arc ?? 0) + 1),
164
263
  emptyDiffLabel: "working tree untouched",
165
- ...(opened.sealed ? { sealed: opened.sealed } : {}),
166
- });
167
- responsePath = persisted.responsePath;
168
- artifactId = persisted.artifactId;
169
- }
170
- catch {
171
- responsePath = undefined;
172
- }
264
+ ...(sealed ? { sealed } : {}),
265
+ }),
266
+ });
267
+ if (!isCompleteLifecycleOutcome(opened))
268
+ return opened;
269
+ const { targetRoot, ...openedFacts } = opened;
270
+ const { currentBranch, baseBranch } = await readBranchLabels(targetRoot);
271
+ const summary = summaryFor(opened.sealed, opened.arc);
173
272
  return {
273
+ ...openedFacts,
174
274
  status: "success",
175
275
  arc: opened.arc,
176
276
  currentBranch,
@@ -184,9 +284,10 @@ export async function arcKeiyaku(input) {
184
284
  brief,
185
285
  ledgerCommit: opened.ledgerHead,
186
286
  sealed: opened.sealed ?? undefined,
187
- responsePath,
188
- artifactId,
287
+ responsePath: opened.responsePath,
288
+ artifactId: opened.artifactId,
189
289
  address,
290
+ carrier: opened.carrier,
190
291
  };
191
292
  });
192
293
  }