@astrosheep/keiyaku 2.9.12 → 2.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +22 -18
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -1,6 +1,6 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, runGitProcess } from "../../git/core.js";
3
- import { shortCommitHash } from "../../git/commits.js";
2
+ import { createGit, runGitProcess } from "../../git/core.js";
3
+ import { readCommitCoordinate, shortCommitHash } from "../../git/commits.js";
4
4
  import { assertHonestClaimEvidence, buildHonestClaimMerge, } from "../repository-ledger/claim-evidence.js";
5
5
  async function successfulGit(cwd, args, input) {
6
6
  const result = await runGitProcess(cwd, args, input === undefined ? undefined : { input });
@@ -22,16 +22,29 @@ export async function buildClaimLanding(input) {
22
22
  }
23
23
  }
24
24
  export async function resolveCommit(cwd, rev) {
25
- return await successfulGit(cwd, ["rev-parse", "--verify", `${rev}^{commit}`]);
25
+ const read = await readCommitCoordinate(cwd, rev);
26
+ if (read.status === "ok")
27
+ return read.coordinate.commit;
28
+ if (read.status === "failed") {
29
+ const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
30
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
31
+ }
32
+ if (read.status === "malformed") {
33
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
34
+ }
35
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: revision does not resolve to a commit`);
26
36
  }
27
37
  export async function resolveCommitOrNull(cwd, rev) {
28
- const result = await runGitProcess(cwd, ["rev-parse", "--verify", `${rev}^{commit}`]);
29
- if (result.status === 0 && !result.error)
30
- return result.stdout.trim();
31
- if (errorContainsAnyPattern(result.stderr, MISSING_HEAD_PATTERNS))
38
+ const read = await readCommitCoordinate(cwd, rev);
39
+ if (read.status === "ok")
40
+ return read.coordinate.commit;
41
+ if (read.status === "missing" || read.status === "non-commit")
32
42
  return null;
33
- const detail = result.stderr.trim() || result.error?.message || `exit ${String(result.status)}`;
34
- throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: result.error });
43
+ if (read.status === "malformed") {
44
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
45
+ }
46
+ const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
47
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
35
48
  }
36
49
  /** Prove parent order and tree identity independently of mutable refs. */
37
50
  export async function proveClaimLanding(cwd, landing) {
@@ -179,7 +179,7 @@ export async function claimContract(input) {
179
179
  : await context.publishAuthoritative({
180
180
  verb: "claim",
181
181
  admittedLedgerHead: admission.ledgerHead,
182
- acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
182
+ acceptedLifecycleTail: context.session.lifecycle.contractHead,
183
183
  entries: [{
184
184
  v: 2,
185
185
  kind: "claim",
@@ -221,11 +221,13 @@ export async function claimContract(input) {
221
221
  },
222
222
  });
223
223
  await context.session.completeReplay("retire");
224
- const taskRow = context.journal.all().find((row) => row.effect === "task-board-projection" && row.status === "succeeded");
225
- const taskFacts = taskRow?.status === "succeeded" ? taskRow.receipt?.facts : undefined;
226
- const warnings = isJsonObject(taskFacts) && Array.isArray(taskFacts.warnings)
227
- ? taskFacts.warnings.filter((warning) => typeof warning === "string")
228
- : [];
224
+ const warnings = ["task-board-projection", "target-worktree-projection"].flatMap((effect) => {
225
+ const row = context.journal.all().find((candidate) => candidate.effect === effect && candidate.status === "succeeded");
226
+ const facts = row?.status === "succeeded" ? row.receipt?.facts : undefined;
227
+ return isJsonObject(facts) && Array.isArray(facts.warnings)
228
+ ? facts.warnings.filter((warning) => typeof warning === "string")
229
+ : [];
230
+ });
229
231
  const cleanupRow = context.journal.all().find((row) => row.effect === "worktree-projection" && row.status === "succeeded");
230
232
  const cleanupFacts = cleanupRow?.status === "succeeded" ? cleanupRow.receipt?.facts : undefined;
231
233
  const deliveryRefRow = context.journal.all().find((row) => row.effect === "delivery-ref-projection" && row.status === "succeeded");
@@ -8,7 +8,7 @@ import { getDiffStats } from "../../git/diff/preview.js";
8
8
  import { readDiff } from "../../git/diff/read.js";
9
9
  import { getCurrentBranch } from "../../git/branches.js";
10
10
  import { getLatestCommitHash } from "../../git/staging.js";
11
- import { createGit } from "../../git/core.js";
11
+ import { requireCommitCoordinate } from "../../git/commits.js";
12
12
  import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../../keiyaku.js";
13
13
  import { FlowError } from "../../flow-error.js";
14
14
  import { persistResponseHistory } from "../transcripts.js";
@@ -100,13 +100,14 @@ export function createOathGate() {
100
100
  export function createCommandVerificationGate() {
101
101
  return {
102
102
  name: "command",
103
- async evaluate({ cwd, coordinationRoot, verificationPlan, verificationCandidate, input }) {
104
- const repositoryRoot = coordinationRoot ?? cwd;
103
+ async evaluate({ cwd, repositoryRoot, verificationPlan, verificationCandidate, input }) {
105
104
  for (const verification of verificationPlan) {
106
105
  logInfo(`[CLAIM] Running ${verification.source}: ${verification.label}`, { cwd, section: "script", progressOnly: true });
107
106
  }
108
- const evidence = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, coordinationKey: JSON.stringify({ repository: repositoryRoot, candidate: verificationCandidate, plan: verificationPlan }), coordinationRoot: repositoryRoot });
109
- const runnerFailure = evidence.find((result) => result.execution.status === "runner_failed");
107
+ const invocation = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, root: repositoryRoot ?? cwd });
108
+ // Petition owns the run: abort cancels and awaitTerminal observes cleanup.
109
+ const evidence = await invocation.awaitTerminal();
110
+ const runnerFailure = evidence.results.find((result) => result.execution.status === "runner_failed");
110
111
  if (runnerFailure?.execution.status === "runner_failed") {
111
112
  return {
112
113
  pass: false,
@@ -120,7 +121,7 @@ export function createCommandVerificationGate() {
120
121
  },
121
122
  };
122
123
  }
123
- const commandFailure = evidence.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
124
+ const commandFailure = evidence.results.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
124
125
  if (commandFailure?.execution.status === "executed") {
125
126
  return {
126
127
  pass: false,
@@ -134,7 +135,7 @@ export function createCommandVerificationGate() {
134
135
  },
135
136
  };
136
137
  }
137
- return { pass: true, verification: [...evidence] };
138
+ return { pass: true, verification: [...evidence.results] };
138
139
  },
139
140
  };
140
141
  }
@@ -193,7 +194,7 @@ export function createAgentReviewGate() {
193
194
  authority: { kind: "repository" },
194
195
  binding: { kind: "commission", commissionId: contractId },
195
196
  });
196
- const reviewHeadBeforeWrite = (await createGit(cwd).revparse(["--verify", "HEAD^{commit}"])).trim();
197
+ const reviewHeadBeforeWrite = (await requireCommitCoordinate(cwd, "HEAD")).commit;
197
198
  const raw = subagentResult.finalMessage.trim();
198
199
  const passed = raw.startsWith(REVIEW_PASS_TOKEN);
199
200
  const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
@@ -1,13 +1,12 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { createGit } from "../../git/core.js";
2
+ import { requireCommitCoordinate } from "../../git/commits.js";
3
3
  const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
4
4
  export async function assertHeadUnchanged(cwd, expectedHead, operation) {
5
- const git = createGit(cwd);
6
5
  const [expectedCommit, currentHead] = await Promise.all([
7
- git.raw(["rev-parse", "--verify", `${expectedHead}^{commit}`]),
8
- git.raw(["rev-parse", "--verify", "HEAD^{commit}"]),
6
+ requireCommitCoordinate(cwd, expectedHead),
7
+ requireCommitCoordinate(cwd, "HEAD"),
9
8
  ]);
10
- if (currentHead.trim() === expectedCommit.trim())
9
+ if (currentHead.commit === expectedCommit.commit)
11
10
  return;
12
- throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.trim()} while the ${operation} was running`);
11
+ throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.commit} while the ${operation} was running`);
13
12
  }
@@ -1,8 +1,9 @@
1
1
  import { createOathGate } from "./petition-claim-gates.js";
2
- import { assertFoldedPetitionable, } from "./petition.js";
2
+ import { assertFoldedPetitionable, precedingRenewSealResult, } from "./petition.js";
3
3
  import { captureFoldedSeal } from "../seal.js";
4
4
  import { withContractCarrierSession } from "../contract-carrier.js";
5
5
  import { FlowError } from "../../flow-error.js";
6
+ import { normalizeScopePattern } from "../scope.js";
6
7
  export const PETITION_PREVIEW_GATE_NAMES = [
7
8
  "contract",
8
9
  "freshness",
@@ -44,6 +45,7 @@ export async function previewPetition(input) {
44
45
  const base = session.lifecycle.pins.base;
45
46
  const target = { ref: session.lifecycle.pins.target.ref, branch: session.lifecycle.pins.target.ref.slice("refs/heads/".length) };
46
47
  const context = { lifecycle: session.lifecycle, deliveryCwd: session.target.root, base, target };
48
+ const appendScope = (input.appendScope ?? []).map(normalizeScopePattern);
47
49
  const gates = [{ name: "contract", result: "pass" }];
48
50
  const freshness = await gate("freshness", async () => {
49
51
  if (!session.pins.target.head || context.base !== session.pins.target.head) {
@@ -58,15 +60,28 @@ export async function previewPetition(input) {
58
60
  })
59
61
  : notEvaluated("arc");
60
62
  gates.push(arc);
61
- const state = await gate("state", () => {
62
- assertFoldedPetitionable(input.contractId, context.lifecycle);
63
+ const state = await gate("state", async () => {
64
+ await assertFoldedPetitionable(input.contractId, context.lifecycle, session.repository, session.stableRoot);
63
65
  });
64
66
  gates.push(state);
65
67
  let seal;
68
+ let scopeWarning = null;
66
69
  if (freshness.result === "pass" && arc.result === "pass" && state.result === "pass") {
67
70
  try {
68
- const capture = await captureFoldedSeal(context.deliveryCwd, context.lifecycle, "HEAD");
69
- seal = { name: "seal", result: "pass", detail: sealFileDetail(capture?.diffstat.files ?? 0) };
71
+ const precedingRenewSeal = appendScope.length === 0
72
+ ? await precedingRenewSealResult(context.deliveryCwd, context.lifecycle)
73
+ : null;
74
+ const capture = precedingRenewSeal
75
+ ? null
76
+ : await captureFoldedSeal(context.deliveryCwd, context.lifecycle, "HEAD", appendScope, "warn");
77
+ const scope = precedingRenewSeal ?? capture;
78
+ if (scope?.scopeAudit.violations.length) {
79
+ scopeWarning = {
80
+ violations: [...scope.scopeAudit.violations],
81
+ evidence: [...scope.scopeViolationEvidence],
82
+ };
83
+ }
84
+ seal = { name: "seal", result: "pass", detail: sealFileDetail(scope?.diffstat.files ?? 0) };
70
85
  }
71
86
  catch (error) {
72
87
  seal = { name: "seal", result: "failed", detail: failureDetail(error) };
@@ -102,6 +117,7 @@ export async function previewPetition(input) {
102
117
  contract: input.contractId,
103
118
  target: context.target.ref,
104
119
  arc: { source: { arc: context.lifecycle.phase === "active" ? context.lifecycle.intent.arc : null, fence: context.lifecycle.pins.delivery } },
120
+ scopeWarning,
105
121
  gates,
106
122
  verdict: gates.some((item) => item.result === "failed") ? "would_fail" : "would_succeed",
107
123
  };
@@ -3,14 +3,44 @@ import { parseCommandMarkdown } from "../command-markdown.js";
3
3
  import { runLifecycle } from "../lifecycle-runner.js";
4
4
  import { queueMembership, readQueueReadModel } from "./queue.js";
5
5
  import { captureFoldedSeal } from "../seal.js";
6
- import { currentLifecycleState } from "../repository-ledger/read-model.js";
6
+ import { currentLifecycleState, lookupCurrentContract } from "../repository-ledger/read-model.js";
7
7
  import { assertCleanTrackedWorkingTree } from "../../git/worktree.js";
8
- import { normalizeScopePattern } from "../scope.js";
9
- export function assertFoldedPetitionable(contractId, model) {
8
+ import { normalizeScopePattern, scopePathEvidence } from "../scope.js";
9
+ import { readRemainingTaskNeedsForSettlement } from "../task/settlement-policy.js";
10
+ export async function assertFoldedPetitionable(contractId, model, repository, stableRoot) {
10
11
  const state = currentLifecycleState(model);
11
12
  if (state.state !== "active" && state.state !== "bound") {
12
13
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot petition from state ${state.state}`);
13
14
  }
15
+ const unavailable = model.causal.after.filter((dependency) => {
16
+ const prerequisite = lookupCurrentContract(repository, dependency);
17
+ return prerequisite.status !== "healthy" ||
18
+ (prerequisite.model.phase !== "claimed" && prerequisite.model.phase !== "forfeited");
19
+ });
20
+ if (unavailable.length > 0) {
21
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot petition before after dependencies settle: ${unavailable.join(", ")}`);
22
+ }
23
+ const pendingNeeds = (await readRemainingTaskNeedsForSettlement(stableRoot, model.causal.needs))
24
+ .filter((need) => !need.satisfied);
25
+ if (pendingNeeds.length > 0) {
26
+ throw new FlowError("INTERNAL_STATE", `contract ${contractId} cannot petition before task needs settle: ${pendingNeeds.map((need) => need.id).join(", ")}`);
27
+ }
28
+ }
29
+ /** Rehydrate the seal consumed by an immediately preceding renew without auditing a new segment. */
30
+ export async function precedingRenewSealResult(cwd, lifecycle, renewIndex = lifecycle.history.length - 1) {
31
+ const renew = lifecycle.history[renewIndex]?.entry;
32
+ const seal = lifecycle.history[renewIndex - 1];
33
+ if (renew?.kind !== "renew" || seal?.entry.kind !== "seal" || seal.entry.data.sealedBy !== "renew")
34
+ return null;
35
+ return {
36
+ ...seal.entry.data,
37
+ receipts: [...seal.entry.data.receipts],
38
+ diffstat: { ...seal.entry.data.diffstat },
39
+ scopeAudit: { violations: [...seal.entry.data.scopeAudit.violations] },
40
+ scopeViolationEvidence: await scopePathEvidence(cwd, lifecycle.effectiveScope, seal.entry.data.scopeAudit.violations),
41
+ sealedBy: "renew",
42
+ ledgerHead: seal.objectId,
43
+ };
14
44
  }
15
45
  function nextSettlementSeat(read, target) {
16
46
  let floor = 0;
@@ -68,7 +98,7 @@ export async function petitionContract(input) {
68
98
  const branch = target.slice("refs/heads/".length);
69
99
  throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} must renew onto ${branch} before petition`, { facts: { kind: "needs_renew", contractId: input.contractId } });
70
100
  }
71
- assertFoldedPetitionable(input.contractId, session.lifecycle);
101
+ await assertFoldedPetitionable(input.contractId, session.lifecycle, session.repository, session.stableRoot);
72
102
  return { lifecycle: session.lifecycle, deliveryCwd };
73
103
  },
74
104
  prepare: async (admission) => {
@@ -88,7 +118,7 @@ export async function petitionContract(input) {
88
118
  receipts: [...acceptedSeal.data.receipts],
89
119
  diffstat: { ...acceptedSeal.data.diffstat },
90
120
  scopeAudit: { violations: [...acceptedSeal.data.scopeAudit.violations] },
91
- scopeViolationEvidence: [],
121
+ scopeViolationEvidence: await scopePathEvidence(input.cwd, admission.business.lifecycle.effectiveScope, acceptedSeal.data.scopeAudit.violations),
92
122
  };
93
123
  prefixIndex -= 1;
94
124
  }
@@ -101,13 +131,14 @@ export async function petitionContract(input) {
101
131
  return {
102
132
  additions: [],
103
133
  capture,
134
+ precedingRenewSeal: await precedingRenewSealResult(input.cwd, admission.business.lifecycle, admission.business.lifecycle.history.findIndex(({ objectId }) => objectId === transaction.entryObjectIds.at(-1)) - 1),
104
135
  seat: accepted.data.seat,
105
136
  targetHead: accepted.data.intent === "claim" ? accepted.data.targetHead : admission.pins.target.head ?? "",
106
137
  existing: { transactionObjectId: transaction.transactionObjectId },
107
138
  };
108
139
  }
109
140
  const scopeEntry = patterns.length === 0 ? null : {
110
- v: 1,
141
+ v: 2,
111
142
  kind: "amend",
112
143
  contract: input.contractId,
113
144
  at,
@@ -118,12 +149,12 @@ export async function petitionContract(input) {
118
149
  if (!deliveryCwd)
119
150
  throw new FlowError("INTERNAL_STATE", `petition ${input.contractId} is missing its delivery worktree`);
120
151
  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
152
+ const precedingRenewSeal = patterns.length === 0
153
+ ? await precedingRenewSealResult(input.cwd, lifecycle)
154
+ : null;
155
+ const capture = precedingRenewSeal
125
156
  ? null
126
- : await captureFoldedSeal(deliveryCwd, lifecycle, "HEAD", patterns);
157
+ : await captureFoldedSeal(deliveryCwd, lifecycle, "HEAD", patterns, "warn");
127
158
  let sealEntry = null;
128
159
  if (capture) {
129
160
  const { scopeViolationEvidence: _evidence, ...durableCapture } = capture;
@@ -145,6 +176,7 @@ export async function petitionContract(input) {
145
176
  return {
146
177
  additions,
147
178
  capture,
179
+ precedingRenewSeal,
148
180
  seat: nextSettlementSeat(admission.repository, admission.pins.target.ref),
149
181
  targetHead,
150
182
  };
@@ -169,7 +201,7 @@ export async function petitionContract(input) {
169
201
  const base = session.lifecycle.pins.base;
170
202
  if (base !== session.pins.target.head)
171
203
  return { code: "PETITION_TARGET_CHANGED", message: "petition target/base facts changed during preparation" };
172
- assertFoldedPetitionable(input.contractId, session.lifecycle);
204
+ await assertFoldedPetitionable(input.contractId, session.lifecycle, session.repository, session.stableRoot);
173
205
  return undefined;
174
206
  },
175
207
  effect: async ({ context, prepared }) => {
@@ -208,15 +240,15 @@ export async function petitionContract(input) {
208
240
  ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
209
241
  : await context.publishAuthoritative({
210
242
  verb: "petition",
211
- admittedLedgerHead: context.session.lifecycle.observedLedgerHead,
212
- acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
243
+ admittedLedgerHead: context.session.pins.repositoryLedgerHead,
244
+ acceptedLifecycleTail: context.session.lifecycle.contractHead,
213
245
  entries: additions,
214
246
  });
215
247
  if (result.status === "indeterminate") {
216
- return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead };
248
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead };
217
249
  }
218
250
  if (result.status !== "committed") {
219
- const authoritativeHead = result.currentHead ?? context.session.lifecycle.observedLedgerHead;
251
+ const authoritativeHead = result.currentHead ?? context.session.pins.repositoryLedgerHead;
220
252
  return { kind: "refused-before-effects", authoritativeHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during petition` } };
221
253
  }
222
254
  if (input.reviewPersistence) {
@@ -254,7 +286,9 @@ export async function petitionContract(input) {
254
286
  position,
255
287
  seat: prepared.seat,
256
288
  reviewHistoryPath,
257
- sealed: prepared.capture ? { ...prepared.capture, sealedBy: "petition", ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((entry) => entry.kind === "seal")] } : undefined,
289
+ sealed: prepared.capture
290
+ ? { ...prepared.capture, sealedBy: "petition", ledgerHead: result.transaction.entryObjectIds[result.transaction.entries.findIndex((entry) => entry.kind === "seal")] }
291
+ : prepared.precedingRenewSeal ?? undefined,
258
292
  },
259
293
  };
260
294
  },
@@ -1,9 +1,8 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
2
  import { readCurrentContractContext } from "../context.js";
3
3
  import { convergeContractCarrier, withContractCarrierSession } from "../contract-carrier.js";
4
- import { createGit } from "../../git/core.js";
4
+ import { requireCommitCoordinate } from "../../git/commits.js";
5
5
  import { getCurrentBranch, requireGitRepository } from "../../git/branches.js";
6
- import { getLatestCommitHash } from "../../git/staging.js";
7
6
  import { readContractLog } from "../log.js";
8
7
  import { scopePathEvidence } from "../scope.js";
9
8
  import { nextReviewResponseNumber } from "../transcripts.js";
@@ -45,15 +44,17 @@ async function pinnedCloseCandidate(cwd, contractId, signal) {
45
44
  if (session.target.status !== "available" || !session.pins.delivery.head) {
46
45
  throw new FlowError("INTERNAL_STATE", session.target.status === "available" ? `contract ${contractId} delivery ref does not resolve` : session.target.reason);
47
46
  }
48
- const git = createGit(session.target.root);
49
- const [head, tree] = await Promise.all([
50
- git.raw(["rev-parse", "--verify", "HEAD^{commit}"]),
51
- git.raw(["rev-parse", "--verify", "HEAD^{tree}"]),
52
- ]);
53
- if (head.trim() !== session.pins.delivery.head) {
47
+ const coordinate = await requireCommitCoordinate(session.target.root, "HEAD");
48
+ if (coordinate.commit !== session.pins.delivery.head) {
54
49
  throw new FlowError("INTERNAL_STATE", `contract ${contractId} delivery head changed while petition was being prepared`);
55
50
  }
56
- return { deliveryCwd: session.target.root, candidate: { head: head.trim(), tree: tree.trim() }, contractVerification: contractVerificationDeclarations(session.lifecycle) };
51
+ const bind = session.lifecycle.history.find(({ entry }) => entry.kind === "bind")?.entry;
52
+ return {
53
+ deliveryCwd: session.target.root,
54
+ candidate: { head: coordinate.commit, tree: coordinate.tree },
55
+ contractVerification: contractVerificationDeclarations(session.lifecycle),
56
+ preparation: bind?.kind === "bind" && bind.data.workspace === "worktree" ? bind.data.bootstrap ?? null : null,
57
+ };
57
58
  });
58
59
  }
59
60
  async function loadCloseContext(cwd, requestedContractId, signal) {
@@ -66,6 +67,7 @@ async function loadCloseContext(cwd, requestedContractId, signal) {
66
67
  deliveryCwd: pinned.deliveryCwd,
67
68
  verificationCandidate: pinned.candidate,
68
69
  contractVerification: pinned.contractVerification,
70
+ verificationPreparation: pinned.preparation,
69
71
  keiyakuBranch: `keiyaku/${requestedContractId}`,
70
72
  baseBranch: lifecycle.pins.target.ref.slice("refs/heads/".length),
71
73
  contractId: requestedContractId,
@@ -88,6 +90,7 @@ async function loadCloseContext(cwd, requestedContractId, signal) {
88
90
  deliveryCwd: pinned.deliveryCwd,
89
91
  verificationCandidate: pinned.candidate,
90
92
  contractVerification: pinned.contractVerification,
93
+ verificationPreparation: pinned.preparation,
91
94
  keiyakuBranch,
92
95
  baseBranch: contractContext.baseBranch,
93
96
  contractId: contractContext.contractId,
@@ -107,14 +110,15 @@ async function throwClaimGateFailure(failure, _input, context, _dependencies) {
107
110
  }
108
111
  async function runClaimGatePipelineOrThrow(input, context, dependencies) {
109
112
  const deliveryCwd = context.deliveryCwd ?? context.cwd;
110
- const verificationCandidate = context.verificationCandidate ?? {
111
- head: await getLatestCommitHash(deliveryCwd),
112
- tree: (await createGit(deliveryCwd).raw(["rev-parse", "--verify", "HEAD^{tree}"])).trim(),
113
- };
113
+ const verificationCandidate = context.verificationCandidate ?? await (async () => {
114
+ const coordinate = await requireCommitCoordinate(deliveryCwd, "HEAD");
115
+ return { head: coordinate.commit, tree: coordinate.tree };
116
+ })();
117
+ const verificationPreparation = context.verificationPreparation ?? null;
114
118
  const gateConfig = await dependencies.resolveClaimGateConfig(context.cwd);
115
119
  const repositoryGates = gateConfig.repositoryGates ?? gateConfig.verificationCommands ?? [];
116
120
  const verificationPlan = composeVerificationPlan({ contract: context.contractVerification ?? [], repositoryGates, timeoutMs: gateConfig.verificationTimeoutMs });
117
- const coordinationRoot = await stableRepoRoot(context.cwd);
121
+ const repositoryRoot = await stableRepoRoot(context.cwd);
118
122
  let review;
119
123
  let reviewHeadBeforeWrite;
120
124
  let reviewerActor;
@@ -123,11 +127,12 @@ async function runClaimGatePipelineOrThrow(input, context, dependencies) {
123
127
  for (const gate of buildClaimGatePipeline()) {
124
128
  const result = await gate.evaluate({
125
129
  cwd: deliveryCwd,
126
- coordinationRoot,
130
+ repositoryRoot,
127
131
  contractId: context.contractId,
128
132
  input,
129
133
  verificationPlan,
130
134
  verificationCandidate,
135
+ verificationPreparation,
131
136
  baseBranch: dependencies.baseBranch,
132
137
  reviewNumber: dependencies.reviewNumber,
133
138
  });
@@ -103,12 +103,12 @@ export async function verdictContract(input) {
103
103
  value: { result: admission.business.existing.result, summary: admission.business.existing.summary },
104
104
  };
105
105
  }
106
- const committed = await context.publishAuthoritative({ verb: "verdict", admittedLedgerHead: admission.ledgerHead, acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId, entries: [prepared.entry] });
106
+ const committed = await context.publishAuthoritative({ verb: "verdict", admittedLedgerHead: admission.ledgerHead, acceptedLifecycleTail: context.session.lifecycle.contractHead, entries: [prepared.entry] });
107
107
  if (committed.status === "indeterminate") {
108
108
  return { kind: "precommit-effects-only", authoritativeHead: committed.currentHead ?? admission.ledgerHead };
109
109
  }
110
110
  if (committed.status !== "committed") {
111
- return { kind: "refused-before-effects", authoritativeHead: committed.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during verdict` } };
111
+ return { kind: "refused-before-effects", authoritativeHead: committed.currentHead ?? context.session.pins.repositoryLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed during verdict` } };
112
112
  }
113
113
  return {
114
114
  kind: "committed",