@astrosheep/keiyaku 2.9.9 → 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 +2 -1
  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 +6 -1
  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 +3 -2
  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 +9 -16
  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 +3 -5
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +2 -0
  90. package/build/config/env.js +23 -0
  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 +6 -5
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +27 -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 +21 -358
  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 +114 -119
  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,6 +1,8 @@
1
1
  import { createOathGate } from "./petition-claim-gates.js";
2
- import { assertPetitionArcReady, assertPetitionFresh, assertPetitionable, loadPetitionPreflightContext, } from "./petition.js";
3
- import { petitionSealSource, previewSeal } from "../seal.js";
2
+ import { assertFoldedPetitionable, } from "./petition.js";
3
+ import { captureFoldedSeal } from "../seal.js";
4
+ import { withContractCarrierSession } from "../contract-carrier.js";
5
+ import { FlowError } from "../../flow-error.js";
4
6
  export const PETITION_PREVIEW_GATE_NAMES = [
5
7
  "contract",
6
8
  "freshness",
@@ -23,8 +25,8 @@ async function gate(name, evaluate) {
23
25
  return { name, result: "failed", detail: failureDetail(error) };
24
26
  }
25
27
  }
26
- function sealPathDetail(paths) {
27
- return `${paths.length} ${paths.length === 1 ? "file" : "files"} · ${paths.length > 0 ? paths.join(", ") : "(none)"}`;
28
+ function sealFileDetail(files) {
29
+ return `${files} ${files === 1 ? "file" : "files"}`;
28
30
  }
29
31
  const notEvaluated = (name) => ({
30
32
  name,
@@ -36,62 +38,72 @@ const notEvaluated = (name) => ({
36
38
  * target/source coordinate and therefore propagates as dry-run exit 2.
37
39
  */
38
40
  export async function previewPetition(input) {
39
- const context = await loadPetitionPreflightContext(input.cwd, input.contractId);
40
- const gates = [{ name: "contract", result: "pass" }];
41
- const freshness = await gate("freshness", async () => {
42
- await assertPetitionFresh(input.cwd, input.contractId, context.ledger.entries, context.base);
43
- });
44
- gates.push(freshness);
45
- const arc = freshness.result === "pass"
46
- ? await gate("arc", async () => {
47
- await assertPetitionArcReady(context.deliveryCwd, input.contractId, context.ledger.entries);
48
- })
49
- : notEvaluated("arc");
50
- gates.push(arc);
51
- const state = await gate("state", () => {
52
- assertPetitionable(input.contractId, context.ledger.entries);
53
- });
54
- gates.push(state);
55
- let seal;
56
- if (freshness.result === "pass" && arc.result === "pass" && state.result === "pass") {
57
- try {
58
- const sealPreview = await previewSeal(context.deliveryCwd, context.ledger.entries, "HEAD");
59
- seal = { name: "seal", result: "pass", detail: sealPathDetail(sealPreview.paths) };
41
+ return withContractCarrierSession({ cwd: input.cwd, contractId: input.contractId, mode: "observe-only", signal: input.signal }, async (session) => {
42
+ if (session.target.status !== "available")
43
+ throw new FlowError("INTERNAL_STATE", session.target.reason);
44
+ const base = session.lifecycle.pins.base;
45
+ const target = { ref: session.lifecycle.pins.target.ref, branch: session.lifecycle.pins.target.ref.slice("refs/heads/".length) };
46
+ const context = { lifecycle: session.lifecycle, deliveryCwd: session.target.root, base, target };
47
+ const gates = [{ name: "contract", result: "pass" }];
48
+ const freshness = await gate("freshness", async () => {
49
+ if (!session.pins.target.head || context.base !== session.pins.target.head) {
50
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} must renew onto ${context.target.branch} before petition`);
51
+ }
52
+ });
53
+ gates.push(freshness);
54
+ const arc = freshness.result === "pass"
55
+ ? await gate("arc", async () => {
56
+ if (context.lifecycle.phase !== "active")
57
+ throw new FlowError("NO_OPEN_ARC", "no active folded arc requires sealing");
58
+ })
59
+ : notEvaluated("arc");
60
+ gates.push(arc);
61
+ const state = await gate("state", () => {
62
+ assertFoldedPetitionable(input.contractId, context.lifecycle);
63
+ });
64
+ gates.push(state);
65
+ let seal;
66
+ if (freshness.result === "pass" && arc.result === "pass" && state.result === "pass") {
67
+ try {
68
+ const capture = await captureFoldedSeal(context.deliveryCwd, context.lifecycle, "HEAD");
69
+ seal = { name: "seal", result: "pass", detail: sealFileDetail(capture?.diffstat.files ?? 0) };
70
+ }
71
+ catch (error) {
72
+ seal = { name: "seal", result: "failed", detail: failureDetail(error) };
73
+ }
60
74
  }
61
- catch (error) {
62
- seal = { name: "seal", result: "failed", detail: failureDetail(error) };
75
+ else {
76
+ seal = notEvaluated("seal");
63
77
  }
64
- }
65
- else {
66
- seal = notEvaluated("seal");
67
- }
68
- gates.push(seal);
69
- const oathOwner = createOathGate();
70
- const oath = await gate("oath", async () => {
71
- const result = await oathOwner.evaluate({
72
- cwd: input.cwd,
73
- contractId: input.contractId,
74
- input: {
78
+ gates.push(seal);
79
+ const oathOwner = createOathGate();
80
+ const oath = await gate("oath", async () => {
81
+ const result = await oathOwner.evaluate({
75
82
  cwd: input.cwd,
76
83
  contractId: input.contractId,
77
- intent: "CLAIM",
78
- title: `Petition ${input.contractId}`,
79
- plea: `Settle contract ${input.contractId} through the petition pipeline.`,
80
- oath: input.oath,
81
- },
82
- verificationCommands: [],
83
- baseBranch: context.target.branch,
84
- reviewNumber: 0,
84
+ input: {
85
+ cwd: input.cwd,
86
+ contractId: input.contractId,
87
+ intent: "CLAIM",
88
+ title: `Petition ${input.contractId}`,
89
+ plea: `Settle contract ${input.contractId} through the petition pipeline.`,
90
+ oath: input.oath,
91
+ },
92
+ verificationPlan: [],
93
+ verificationCandidate: { head: "", tree: "" },
94
+ baseBranch: context.target.branch,
95
+ reviewNumber: 0,
96
+ });
97
+ if (!result.pass)
98
+ throw new Error(result.message);
85
99
  });
86
- if (!result.pass)
87
- throw new Error(result.message);
100
+ gates.push(oath, notEvaluated("verification"), notEvaluated("review"));
101
+ return {
102
+ contract: input.contractId,
103
+ target: context.target.ref,
104
+ arc: { source: { arc: context.lifecycle.phase === "active" ? context.lifecycle.intent.arc : null, fence: context.lifecycle.pins.delivery } },
105
+ gates,
106
+ verdict: gates.some((item) => item.result === "failed") ? "would_fail" : "would_succeed",
107
+ };
88
108
  });
89
- gates.push(oath, notEvaluated("verification"), notEvaluated("review"));
90
- return {
91
- contract: input.contractId,
92
- target: context.target.ref,
93
- arc: { source: petitionSealSource(context.ledger.entries) },
94
- gates,
95
- verdict: gates.some((item) => item.result === "failed") ? "would_fail" : "would_succeed",
96
- };
97
109
  }
@@ -0,0 +1,50 @@
1
+ export function inspectCurrentPetition(lifecycle) {
2
+ const petitionObjectId = lifecycle.petition.state === "none" ? undefined : lifecycle.petition.objectId;
3
+ const petitionIndex = petitionObjectId === undefined
4
+ ? -1
5
+ : lifecycle.history.findIndex(({ objectId }) => objectId === petitionObjectId);
6
+ if (petitionIndex < 0)
7
+ return undefined;
8
+ const petition = lifecycle.history[petitionIndex]?.entry;
9
+ if (!petition || petition.kind !== "petition")
10
+ return undefined;
11
+ const preceding = lifecycle.history[petitionIndex - 1]?.entry;
12
+ const seal = preceding?.kind === "seal" && preceding.data.sealedBy === "petition"
13
+ ? preceding
14
+ : undefined;
15
+ const verdictRow = lifecycle.verdict === null
16
+ ? undefined
17
+ : lifecycle.history.find(({ objectId }) => objectId === lifecycle.verdict?.objectId);
18
+ const verdict = verdictRow?.entry.kind === "verdict" ? verdictRow.entry : undefined;
19
+ return {
20
+ petitionIndex,
21
+ petition,
22
+ candidateHead: seal?.data.newFence ?? lifecycle.pins.delivery,
23
+ ...(seal ? { seal } : {}),
24
+ ...(verdict ? { verdict } : {}),
25
+ };
26
+ }
27
+ export function classifyCurrentPetitionWindow(lifecycle, pins) {
28
+ if (lifecycle.phase !== "awaiting-verdict" && lifecycle.phase !== "approved")
29
+ return { status: "none" };
30
+ const current = inspectCurrentPetition(lifecycle);
31
+ if (!current)
32
+ return { status: "none" };
33
+ if (current.verdict?.data.result === "approved") {
34
+ return { status: "approved-awaiting-claim", entries: current };
35
+ }
36
+ if (current.verdict) {
37
+ return { status: "invalid", reason: "petitioned window has a non-approved verdict", entries: current };
38
+ }
39
+ const base = lifecycle.pins.base;
40
+ if (!base || !pins.target.head || !pins.delivery.head) {
41
+ return { status: "invalid", reason: "current petition coordinates do not resolve", entries: current };
42
+ }
43
+ if (base !== pins.target.head) {
44
+ return { status: "unjudged", coordinates: "target-drift", entries: current };
45
+ }
46
+ if (current.candidateHead !== pins.delivery.head) {
47
+ return { status: "unjudged", coordinates: "delivery-drift", entries: current };
48
+ }
49
+ return { status: "unjudged", coordinates: "fresh", entries: current };
50
+ }
@@ -1,148 +1,269 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../../git/core.js";
3
2
  import { parseCommandMarkdown } from "../command-markdown.js";
4
- import { activeLedgerRef, appendWithRetry, readLedger } from "../ledger.js";
5
- import { allocateSettlementSeat, queueMembership, readQueueReadModel } from "./queue.js";
6
- import { appendScopeTerms, findOpenArc, getMaxArc, receiptsAfterFence, sealCurrentArc } from "../seal.js";
7
- import { deriveContractState } from "../status/lifecycle.js";
8
- import { commissionTarget, latestBase } from "../status/drift.js";
9
- import { findContractWorktreePath } from "../worktree-path.js";
3
+ import { runLifecycle } from "../lifecycle-runner.js";
4
+ import { queueMembership, readQueueReadModel } from "./queue.js";
5
+ import { captureFoldedSeal } from "../seal.js";
6
+ import { currentLifecycleState } from "../repository-ledger/read-model.js";
10
7
  import { assertCleanTrackedWorkingTree } from "../../git/worktree.js";
11
- async function resolveCommit(cwd, ref) {
12
- const git = createGit(cwd);
13
- try {
14
- return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
15
- }
16
- catch (error) {
17
- if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
18
- return null;
19
- throw wrapGitError(`rev-parse ${ref}`, error, cwd);
20
- }
21
- }
22
- export async function loadPetitionPreflightContext(cwd, contractId) {
23
- const ledger = await readLedger(cwd, contractId);
24
- if (!ledger) {
25
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} not found or not active`);
26
- }
27
- const base = latestBase(ledger.entries);
28
- if (!base)
29
- throw new FlowError("INTERNAL_STATE", "ledger must contain bind entry");
30
- const target = commissionTarget(ledger.entries);
31
- if (!target) {
32
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
33
- }
34
- return {
35
- ledger,
36
- deliveryCwd: (await findContractWorktreePath(cwd, contractId)) ?? cwd,
37
- base,
38
- target,
39
- };
40
- }
41
- export async function assertPetitionFresh(cwd, contractId, entries, base) {
42
- const target = commissionTarget(entries);
43
- if (!target) {
44
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} missing commission target`);
45
- }
46
- const head = await resolveCommit(cwd, target.ref);
47
- if (!head) {
48
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} commission target ${target.ref} does not resolve`);
49
- }
50
- if (base !== head) {
51
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} must renew onto ${target.branch} before petition`, { facts: { kind: "needs_renew", contractId } });
52
- }
53
- }
54
- export async function assertPetitionArcReady(deliveryCwd, contractId, entries) {
55
- if (getMaxArc(entries) <= 0 || findOpenArc(entries))
56
- return;
57
- const loose = await receiptsAfterFence(deliveryCwd, entries);
58
- if (loose.length > 0) {
59
- throw new FlowError("NO_OPEN_ARC", `${loose.length} commits after fence have no arc to receipt them`, { facts: { kind: "loose_commits", contractId, nextVerb: "petition" } });
60
- }
61
- }
62
- export function assertPetitionable(contractId, entries) {
63
- const state = deriveContractState(entries);
64
- // bound is legal for derived a0 (sealCurrentArc appends seal 0 before petition).
8
+ import { normalizeScopePattern } from "../scope.js";
9
+ export function assertFoldedPetitionable(contractId, model) {
10
+ const state = currentLifecycleState(model);
65
11
  if (state.state !== "active" && state.state !== "bound") {
66
12
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot petition from state ${state.state}`);
67
13
  }
68
14
  }
15
+ function nextSettlementSeat(read, target) {
16
+ let floor = 0;
17
+ for (const model of read.contracts) {
18
+ for (const { entry } of model.history) {
19
+ if (entry.kind !== "petition" || !("target" in entry.data) || entry.data.target !== target)
20
+ continue;
21
+ floor = Math.max(floor, entry.data.seat);
22
+ }
23
+ }
24
+ const seat = floor + 1;
25
+ if (!Number.isSafeInteger(seat) || seat <= 0)
26
+ throw new FlowError("INTERNAL_STATE", `settlement seats exhausted for ${target}`);
27
+ return seat;
28
+ }
69
29
  export async function petitionContract(input) {
70
30
  const at = new Date(input.nowMs).toISOString();
71
- const { ledger, deliveryCwd, base, target } = await loadPetitionPreflightContext(input.cwd, input.contractId);
72
- await assertCleanTrackedWorkingTree(deliveryCwd);
73
- await assertPetitionFresh(input.cwd, input.contractId, ledger.entries, base);
74
- await assertPetitionArcReady(deliveryCwd, input.contractId, ledger.entries);
75
- assertPetitionable(input.contractId, ledger.entries);
76
- await appendScopeTerms({
31
+ const patterns = input.intent === "claim" ? (input.appendScope ?? []).map(normalizeScopePattern) : [];
32
+ const generatedAmendment = "Scope extended by lifecycle command.";
33
+ const matchesAccepted = (history) => {
34
+ const petitionIndex = history.length - 1;
35
+ const accepted = history[petitionIndex]?.entry;
36
+ if (accepted?.kind !== "petition" || accepted.data.intent !== input.intent)
37
+ return false;
38
+ if (input.intent === "claim" && (accepted.data.intent !== "claim" || accepted.data.oath !== input.oath))
39
+ return false;
40
+ let prefixIndex = petitionIndex - 1;
41
+ const preceding = history[prefixIndex]?.entry;
42
+ if (preceding?.kind === "seal" && preceding.data.sealedBy === "petition")
43
+ prefixIndex -= 1;
44
+ const acceptedAmend = history[prefixIndex]?.entry;
45
+ const hasGeneratedAmend = acceptedAmend?.kind === "amend" && acceptedAmend.data.amendment === generatedAmendment;
46
+ if (patterns.length === 0)
47
+ return !hasGeneratedAmend;
48
+ return hasGeneratedAmend
49
+ && JSON.stringify(acceptedAmend.data.scopeDelta ?? null) === JSON.stringify({ add: patterns });
50
+ };
51
+ const execution = await runLifecycle({
77
52
  cwd: input.cwd,
78
- renderCwd: deliveryCwd,
79
- contractId: input.contractId,
80
- patterns: input.intent === "claim" ? input.appendScope : undefined,
81
- actor: input.actor,
82
- nowMs: input.nowMs,
83
- });
84
- const sealed = await sealCurrentArc({
85
- cwd: deliveryCwd,
86
- renderCwd: deliveryCwd,
87
53
  contractId: input.contractId,
88
- sealedBy: "petition",
89
- nowMs: input.nowMs,
90
- });
91
- const allocated = await allocateSettlementSeat(input.cwd, {
92
- target: target.ref,
93
- actor: input.actor,
94
- at,
95
- });
96
- const ref = activeLedgerRef(input.contractId);
97
- const result = await appendWithRetry(input.cwd, ref, (_head, entries) => {
98
- const state = deriveContractState(entries);
99
- if (state.state === "petitioned")
100
- return null;
101
- assertPetitionable(input.contractId, entries);
102
- const data = input.intent === "claim"
103
- ? {
104
- intent: "claim",
105
- oath: input.oath,
106
- target: target.ref,
107
- seat: allocated.seat,
54
+ mutation: { verb: "petition", matchesAccepted: (transaction) => matchesAccepted(transaction.entries.map((entry, index) => ({ objectId: transaction.entryObjectIds[index], entry }))) },
55
+ signal: input.signal,
56
+ admit: async (session, replay) => {
57
+ if (replay.state === "verb-committed")
58
+ return { lifecycle: session.lifecycle };
59
+ if (session.target.status !== "available")
60
+ throw new FlowError("INTERNAL_STATE", session.target.reason);
61
+ const deliveryCwd = session.target.root;
62
+ const base = session.lifecycle.pins.base;
63
+ const target = session.lifecycle.pins.target.ref;
64
+ await assertCleanTrackedWorkingTree(deliveryCwd);
65
+ if (!session.pins.target.head)
66
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} commission target ${session.pins.target.ref} does not resolve`);
67
+ if (base !== session.pins.target.head) {
68
+ const branch = target.slice("refs/heads/".length);
69
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} must renew onto ${branch} before petition`, { facts: { kind: "needs_renew", contractId: input.contractId } });
108
70
  }
109
- : {
110
- intent: "forfeit",
111
- target: target.ref,
112
- seat: allocated.seat,
71
+ assertFoldedPetitionable(input.contractId, session.lifecycle);
72
+ return { lifecycle: session.lifecycle, deliveryCwd };
73
+ },
74
+ prepare: async (admission) => {
75
+ if (admission.replay.state === "verb-committed") {
76
+ const transaction = admission.replay.transaction;
77
+ const accepted = transaction.entries.at(-1);
78
+ if (!accepted || accepted.kind !== "petition")
79
+ throw new FlowError("INTERNAL_STATE", `accepted transaction ${transaction.transactionObjectId} is not a petition`);
80
+ let prefixIndex = transaction.entries.length - 2;
81
+ const acceptedSeal = transaction.entries[prefixIndex];
82
+ let capture = null;
83
+ if (acceptedSeal?.kind === "seal" && acceptedSeal.data.sealedBy === "petition") {
84
+ capture = {
85
+ arc: acceptedSeal.data.arc,
86
+ oldFence: acceptedSeal.data.oldFence,
87
+ newFence: acceptedSeal.data.newFence,
88
+ receipts: [...acceptedSeal.data.receipts],
89
+ diffstat: { ...acceptedSeal.data.diffstat },
90
+ scopeAudit: { violations: [...acceptedSeal.data.scopeAudit.violations] },
91
+ scopeViolationEvidence: [],
92
+ };
93
+ prefixIndex -= 1;
94
+ }
95
+ const acceptedAmend = transaction.entries[prefixIndex];
96
+ if (patterns.length > 0) {
97
+ if (acceptedAmend?.kind !== "amend" || acceptedAmend.data.amendment !== generatedAmendment) {
98
+ throw new FlowError("INTERNAL_STATE", `accepted petition transaction ${transaction.transactionObjectId} is missing its scope amendment`);
99
+ }
100
+ }
101
+ return {
102
+ additions: [],
103
+ capture,
104
+ seat: accepted.data.seat,
105
+ targetHead: accepted.data.intent === "claim" ? accepted.data.targetHead : admission.pins.target.head ?? "",
106
+ existing: { transactionObjectId: transaction.transactionObjectId },
107
+ };
108
+ }
109
+ const scopeEntry = patterns.length === 0 ? null : {
110
+ v: 1,
111
+ kind: "amend",
112
+ contract: input.contractId,
113
+ at,
114
+ actor: input.actor,
115
+ data: { amendment: generatedAmendment, scopeDelta: { add: patterns } },
113
116
  };
114
- const entry = {
115
- v: 1,
116
- kind: "petition",
117
- contract: input.contractId,
118
- at,
119
- actor: input.actor,
120
- data,
121
- };
122
- return entry;
123
- });
124
- if (!result.ok) {
125
- // Race/abort: allocated seat remains a legal hole; do not report success.
126
- if (result.reason === "aborted") {
127
- const current = await readLedger(input.cwd, input.contractId);
128
- if (current) {
129
- assertPetitionable(input.contractId, current.entries);
117
+ const deliveryCwd = admission.business.deliveryCwd;
118
+ if (!deliveryCwd)
119
+ throw new FlowError("INTERNAL_STATE", `petition ${input.contractId} is missing its delivery worktree`);
120
+ const lifecycle = admission.business.lifecycle;
121
+ const followsRenewWithoutInterveningFact = lifecycle.phase === "active"
122
+ && lifecycle.seal === null
123
+ && lifecycle.history.at(-1)?.entry.kind === "renew";
124
+ const capture = followsRenewWithoutInterveningFact
125
+ ? null
126
+ : await captureFoldedSeal(deliveryCwd, lifecycle, "HEAD", patterns);
127
+ let sealEntry = null;
128
+ if (capture) {
129
+ const { scopeViolationEvidence: _evidence, ...durableCapture } = capture;
130
+ sealEntry = {
131
+ v: 2,
132
+ kind: "seal",
133
+ contract: input.contractId,
134
+ at,
135
+ actor: "engine",
136
+ data: { ...durableCapture, sealedBy: "petition" },
137
+ };
130
138
  }
131
- throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: aborted`);
132
- }
133
- throw new FlowError("INTERNAL_STATE", `failed to petition contract ${input.contractId}: ${result.reason}`);
134
- }
135
- const model = await readQueueReadModel(input.cwd);
136
- const membership = queueMembership(model, input.contractId);
137
- const position = membership?.position ?? 0;
138
- if (position <= 0) {
139
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing target-local queue position after petition`);
140
- }
141
- return {
142
- contractId: input.contractId,
143
- position,
144
- ...(sealed ? { sealed } : {}),
145
- };
139
+ const additions = scopeEntry
140
+ ? sealEntry ? [scopeEntry, sealEntry] : [scopeEntry]
141
+ : sealEntry ? [sealEntry] : [];
142
+ const targetHead = admission.pins.target.head;
143
+ if (!targetHead)
144
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} commission target does not resolve`);
145
+ return {
146
+ additions,
147
+ capture,
148
+ seat: nextSettlementSeat(admission.repository, admission.pins.target.ref),
149
+ targetHead,
150
+ };
151
+ },
152
+ revalidate: async ({ session, admission, prepared }) => {
153
+ if (prepared.existing) {
154
+ return session.repository.topology.nodes.some((node) => node.objectId === prepared.existing.transactionObjectId)
155
+ ? undefined
156
+ : { code: "PETITION_STATE_CHANGED", message: "accepted petition changed during replay preparation" };
157
+ }
158
+ if (session.target.status !== "available" || session.target.root !== admission.business.deliveryCwd)
159
+ return { code: "DELIVERY_WORKTREE_CHANGED", message: "registered delivery worktree changed during petition preparation" };
160
+ await assertCleanTrackedWorkingTree(session.target.root);
161
+ if (input.reviewPersistence && session.pins.delivery.head !== input.reviewPersistence.reviewedHead) {
162
+ return {
163
+ code: "REVIEWED_DELIVERY_CHANGED",
164
+ message: "delivery HEAD changed after independent review",
165
+ pinned: { deliveryHead: input.reviewPersistence.reviewedHead },
166
+ current: { deliveryHead: session.pins.delivery.head },
167
+ };
168
+ }
169
+ const base = session.lifecycle.pins.base;
170
+ if (base !== session.pins.target.head)
171
+ return { code: "PETITION_TARGET_CHANGED", message: "petition target/base facts changed during preparation" };
172
+ assertFoldedPetitionable(input.contractId, session.lifecycle);
173
+ return undefined;
174
+ },
175
+ effect: async ({ context, prepared }) => {
176
+ let reviewHistoryPath;
177
+ if (!prepared.existing)
178
+ await input.afterSeat?.(prepared.seat);
179
+ const data = input.intent === "claim"
180
+ ? {
181
+ intent: "claim",
182
+ oath: input.oath,
183
+ target: context.session.pins.target.ref,
184
+ targetHead: prepared.targetHead,
185
+ seat: prepared.seat,
186
+ }
187
+ : {
188
+ intent: "forfeit",
189
+ target: context.session.pins.target.ref,
190
+ seat: prepared.seat,
191
+ };
192
+ const entry = {
193
+ v: 2,
194
+ kind: "petition",
195
+ contract: input.contractId,
196
+ at,
197
+ actor: input.actor,
198
+ data,
199
+ };
200
+ const additions = prepared.additions.length === 0
201
+ ? [entry]
202
+ : prepared.additions[0].kind === "seal"
203
+ ? [prepared.additions[0], entry]
204
+ : prepared.additions.length === 1
205
+ ? [prepared.additions[0], entry]
206
+ : [prepared.additions[0], prepared.additions[1], entry];
207
+ const result = prepared.existing
208
+ ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
209
+ : await context.publishAuthoritative({
210
+ verb: "petition",
211
+ admittedLedgerHead: context.session.lifecycle.observedLedgerHead,
212
+ acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
213
+ entries: additions,
214
+ });
215
+ if (result.status === "indeterminate") {
216
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead };
217
+ }
218
+ if (result.status !== "committed") {
219
+ const authoritativeHead = result.currentHead ?? context.session.lifecycle.observedLedgerHead;
220
+ return { kind: "refused-before-effects", authoritativeHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during petition` } };
221
+ }
222
+ if (input.reviewPersistence) {
223
+ await context.journal.run({
224
+ effect: "review-response-persistence",
225
+ ...(context.journal.all().some((row) => row.effect === "ledger-cas" && row.status === "succeeded")
226
+ ? { dependency: `${input.contractId}:${result.head}:ledger-cas` }
227
+ : {}),
228
+ run: async () => {
229
+ const persisted = await input.reviewPersistence.persist();
230
+ reviewHistoryPath = persisted.responsePath;
231
+ return { artifactId: persisted.artifactId, responsePath: persisted.responsePath };
232
+ },
233
+ });
234
+ }
235
+ let position = 0;
236
+ await context.journal.run({
237
+ effect: "settlement-queue-observation",
238
+ ...(context.journal.all().some((row) => row.effect === "ledger-cas" && row.status === "succeeded")
239
+ ? { dependency: `${input.contractId}:${result.head}:ledger-cas` }
240
+ : {}),
241
+ run: async () => {
242
+ const model = await readQueueReadModel(context.session.stableRoot);
243
+ position = queueMembership(model, input.contractId)?.position ?? 0;
244
+ if (position <= 0)
245
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} missing target-local queue position after petition`);
246
+ return { position };
247
+ },
248
+ });
249
+ return {
250
+ kind: "committed",
251
+ commit: result,
252
+ entryKind: "petition",
253
+ value: {
254
+ position,
255
+ seat: prepared.seat,
256
+ reviewHistoryPath,
257
+ sealed: prepared.capture ? { ...prepared.capture, sealedBy: "petition", ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((entry) => entry.kind === "seal")] } : undefined,
258
+ },
259
+ };
260
+ },
261
+ });
262
+ if (execution.kind === "noncommitted")
263
+ return execution.outcome;
264
+ if (execution.kind === "contained")
265
+ return { ...execution.outcome, contractId: input.contractId };
266
+ return { ...execution.outcome, contractId: input.contractId, ...execution.payload, carrier: execution.carrier };
146
267
  }
147
268
  export function buildPetitionClaimInput(input) {
148
269
  const parts = parseCommandMarkdown(input.stdin ?? "");