@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
@@ -4,7 +4,8 @@ import * as path from "node:path";
4
4
  import { FlowError, asMessage } from "../../flow-error.js";
5
5
  import { createGit, wrapGitError } from "../../git/core.js";
6
6
  import { readStructuredDiff } from "../../git/diff/structured.js";
7
- import { rewriteRenewBranch } from "../renew.js";
7
+ import { abortDetachedRenewChain, buildDetachedRenewChain, renewScratchName } from "../renew-rewrite.js";
8
+ import { isBootstrapSuccess, runWorktreeBootstrap } from "../worktree-bootstrap.js";
8
9
  async function resolveObject(cwd, revision, kind) {
9
10
  try {
10
11
  return (await createGit(cwd).raw(["rev-parse", "--verify", `${revision}^{${kind}}`])).trim();
@@ -24,14 +25,25 @@ export async function createAuditCandidate(input) {
24
25
  await candidateGit.raw(["checkout", "--quiet", "-B", `keiyaku/${input.contractId}`, input.frozen.coordinates.delivery.head]);
25
26
  if (input.frozen.base === input.frozen.coordinates.target.head)
26
27
  return { cwd, rehearsal: { status: "fresh" }, candidate: { base: input.frozen.coordinates.target.head, head: input.frozen.coordinates.delivery.head, tree: input.frozen.coordinates.delivery.tree, rewritten: false } };
27
- try {
28
- await rewriteRenewBranch({ cwd, contractId: input.contractId, oldHead: input.frozen.coordinates.delivery.head, recoveryRef: "refs/keiyaku/audit/disposable", newBase: input.frozen.coordinates.target.head, oldBase: input.frozen.base });
29
- }
30
- catch (error) {
31
- if (error instanceof FlowError && error.code === "CLOSE_MERGE_CONFLICT" && error.facts?.kind === "renew_conflict")
32
- return { cwd, rehearsal: { status: "conflict", paths: [...error.facts.conflictPaths], pathCapture: error.facts.conflictPathCapture.status === "captured" ? "captured" : "failed", ...(error.facts.conflictPathCapture.status === "failed" ? { diagnostic: error.facts.conflictPathCapture.diagnostic } : {}) }, candidate: null };
33
- return { cwd, rehearsal: { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) } }, candidate: null };
28
+ const scratchName = renewScratchName(`audit-${input.contractId}-${input.frozen.coordinates.delivery.head}`);
29
+ const deliveryCommits = (await candidateGit.raw(["rev-list", "--reverse", "--ancestry-path", `${input.frozen.base}..${input.frozen.coordinates.delivery.head}`])).split(/\r?\n/).filter(Boolean);
30
+ const rewrite = await buildDetachedRenewChain({
31
+ cwd,
32
+ scratchName,
33
+ observedLedgerHead: input.frozen.coordinates.ledger.head,
34
+ deliveryRef: input.frozen.coordinates.delivery.ref,
35
+ targetRef: input.frozen.coordinates.target.ref,
36
+ effectiveScope: input.frozen.scope,
37
+ oldBase: input.frozen.base,
38
+ oldHead: input.frozen.coordinates.delivery.head,
39
+ newBase: input.frozen.coordinates.target.head,
40
+ deliveryCommits,
41
+ });
42
+ if (rewrite.status === "conflict") {
43
+ await abortDetachedRenewChain({ cwd, scratchName });
44
+ return { cwd, rehearsal: { status: "conflict", paths: [...rewrite.conflictPaths], pathCapture: "captured" }, candidate: null };
34
45
  }
46
+ await candidateGit.raw(["reset", "--hard", rewrite.newHead]);
35
47
  const [head, tree] = await Promise.all([resolveObject(cwd, "HEAD", "commit"), resolveObject(cwd, "HEAD", "tree")]);
36
48
  return { cwd, rehearsal: { status: "clean", oldBase: input.frozen.base, newBase: input.frozen.coordinates.target.head, oldHead: input.frozen.coordinates.delivery.head }, candidate: { base: input.frozen.coordinates.target.head, head, tree, rewritten: true } };
37
49
  }
@@ -44,5 +56,94 @@ export async function readAuditCandidateDiff(candidateResult) {
44
56
  const candidate = candidateResult.candidate;
45
57
  if (!candidate)
46
58
  return null;
47
- return readStructuredDiff(candidateResult.cwd, candidate.base, candidate.head, { excludePaths: [".keiyaku/KEIYAKU.md"] });
59
+ return readStructuredDiff(candidateResult.cwd, candidate.base, candidate.head);
60
+ }
61
+ async function readCandidateIntegrity(cwd) {
62
+ const git = createGit(cwd);
63
+ const [head, tree, status] = await Promise.all([
64
+ resolveObject(cwd, "HEAD", "commit"),
65
+ resolveObject(cwd, "HEAD", "tree"),
66
+ git.raw(["status", "--porcelain=v1", "-z", "--untracked-files=no"]),
67
+ ]);
68
+ return { head, tree, trackedChanges: status.split("\0").filter(Boolean) };
69
+ }
70
+ function preparationPlan(plan) {
71
+ return { command: [...plan.command], timeoutMs: plan.timeoutMs };
72
+ }
73
+ function preparationOutput(observation) {
74
+ return {
75
+ ...(observation.stdoutTail !== undefined ? { stdoutTail: observation.stdoutTail } : {}),
76
+ ...(observation.stdoutTruncated ? { stdoutTruncated: true } : {}),
77
+ ...(observation.stderrTail !== undefined ? { stderrTail: observation.stderrTail } : {}),
78
+ ...(observation.stderrTruncated ? { stderrTruncated: true } : {}),
79
+ };
80
+ }
81
+ function bootstrapResultDescription(result) {
82
+ if (result.kind === "exit")
83
+ return `exit ${result.code}`;
84
+ if (result.kind === "signal")
85
+ return `signal ${result.signal}`;
86
+ if (result.kind === "timeout")
87
+ return "timeout";
88
+ return `spawn: ${result.message}`;
89
+ }
90
+ function observationFailure(plan, candidate, observation) {
91
+ return {
92
+ status: "failed",
93
+ plan: preparationPlan(plan),
94
+ candidate: { head: candidate.head, tree: candidate.tree },
95
+ result: observation.result,
96
+ durationMs: observation.durationMs,
97
+ ...preparationOutput(observation),
98
+ cause: { code: "CANDIDATE_BOOTSTRAP_FAILED", message: `candidate bootstrap failed with ${bootstrapResultDescription(observation.result)}` },
99
+ };
100
+ }
101
+ export async function prepareAuditCandidate(input, dependencies = {}) {
102
+ const candidate = input.candidateResult.candidate;
103
+ if (!candidate)
104
+ return { status: "not-required", reason: "candidate_unavailable" };
105
+ if (!input.verificationRequired)
106
+ return { status: "not-required", reason: "verification_not_configured" };
107
+ if (!input.plan)
108
+ return { status: "not-configured", candidate: { head: candidate.head, tree: candidate.tree } };
109
+ const runBootstrap = dependencies.runBootstrap ?? runWorktreeBootstrap;
110
+ const observation = await runBootstrap({ cwd: input.candidateResult.cwd, plan: input.plan, captureOutput: true });
111
+ if (!isBootstrapSuccess(observation))
112
+ return observationFailure(input.plan, candidate, observation);
113
+ let integrity;
114
+ try {
115
+ integrity = await (dependencies.readIntegrity ?? readCandidateIntegrity)(input.candidateResult.cwd);
116
+ }
117
+ catch (error) {
118
+ return {
119
+ status: "failed",
120
+ plan: preparationPlan(input.plan),
121
+ candidate: { head: candidate.head, tree: candidate.tree },
122
+ result: observation.result,
123
+ durationMs: observation.durationMs,
124
+ ...preparationOutput(observation),
125
+ cause: { code: "CANDIDATE_INTEGRITY_UNKNOWN", message: asMessage(error) },
126
+ };
127
+ }
128
+ if (integrity.head !== candidate.head || integrity.tree !== candidate.tree || integrity.trackedChanges.length > 0) {
129
+ return {
130
+ status: "failed",
131
+ plan: preparationPlan(input.plan),
132
+ candidate: { head: candidate.head, tree: candidate.tree },
133
+ result: observation.result,
134
+ durationMs: observation.durationMs,
135
+ ...preparationOutput(observation),
136
+ cause: { code: "CANDIDATE_INTEGRITY_CHANGED", message: "candidate HEAD, tree, or tracked worktree changed during preparation" },
137
+ integrity,
138
+ };
139
+ }
140
+ return {
141
+ status: "succeeded",
142
+ plan: preparationPlan(input.plan),
143
+ candidate: { head: candidate.head, tree: candidate.tree },
144
+ result: { kind: "exit", code: 0 },
145
+ durationMs: observation.durationMs,
146
+ ...preparationOutput(observation),
147
+ integrity,
148
+ };
48
149
  }
@@ -3,17 +3,12 @@ import * as path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
  import { FlowError, asMessage } from "../../flow-error.js";
5
5
  import { createGit, wrapGitError } from "../../git/core.js";
6
- import { readLedger } from "../ledger.js";
6
+ import { effectiveCriteria } from "../amendment-criteria.js";
7
7
  import { hasStrandableProjectionGeneration } from "../projection/index.js";
8
- import { readRenewRecoverySession } from "../renew-session.js";
9
- import { effectiveScope, findOpenArc, getFence, getMaxArc, receiptsAfterFence } from "../seal.js";
10
- import { queueMembership, readQueueReadModel } from "../settlement/queue.js";
11
- import { markdownSourceSections } from "../scope.js";
12
- import { normalizeSectionTitle } from "../markdown/titles.js";
13
- import { commissionTarget, latestBase } from "../status/drift.js";
14
- import { deriveContractState, isTerminalState } from "../status/lifecycle.js";
15
- import { resolveContractDeliveryCwd } from "../worktree-path.js";
16
- import { KEIYAKU_FILE } from "../../keiyaku.js";
8
+ import { admitRepositoryLifecycleContract, } from "../repository-ledger/admission.js";
9
+ import { lookupCurrentContract, readRepositoryLifecycle, } from "../repository-ledger/read-model.js";
10
+ import { deriveQueueReadModel, queueMembership } from "../settlement/queue.js";
11
+ import { findRegisteredContractWorktreePathFromModel } from "../worktree-path.js";
17
12
  async function resolveObject(cwd, revision, kind) {
18
13
  const git = createGit(cwd);
19
14
  try {
@@ -23,45 +18,62 @@ async function resolveObject(cwd, revision, kind) {
23
18
  throw wrapGitError(`rev-parse ${revision}^{${kind}}`, error, cwd);
24
19
  }
25
20
  }
26
- export function appendedScopeTerms(entries) {
27
- return entries.flatMap((entry) => entry.kind === "amend" && entry.data.scopeDelta ? entry.data.scopeDelta.add : []);
21
+ export function appendedScopeTerms(model) {
22
+ return model.history.flatMap(({ entry }) => entry.kind === "amend" && entry.data.scopeDelta ? entry.data.scopeDelta.add : []);
28
23
  }
29
- export function effectiveCriteria(entries) {
30
- const bind = entries.find((entry) => entry.kind === "bind");
31
- if (!bind)
32
- return [];
33
- const criteria = [...bind.data.checks];
34
- for (const entry of entries) {
35
- if (entry.kind !== "amend")
36
- continue;
37
- for (const section of markdownSourceSections(entry.data.amendment)) {
38
- if (section.level !== 2 || normalizeSectionTitle(section.title) !== "checks")
39
- continue;
40
- for (const line of entry.data.amendment.slice(section.contentStart, section.end).split(/\r?\n/)) {
41
- const match = line.match(/^\s*-\s+(.+)$/);
42
- if (match)
43
- criteria.push(match[1].trim());
44
- }
45
- }
46
- }
47
- return criteria;
24
+ function auditCriteria(model) {
25
+ const bind = model.history.find(({ entry }) => entry.kind === "bind")?.entry;
26
+ if (!bind || bind.kind !== "bind")
27
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} is missing folded bind terms`);
28
+ const amendments = model.history.flatMap(({ entry }) => entry.kind === "amend" ? [entry.data.amendment] : []);
29
+ return effectiveCriteria(bind.data.criteria, amendments);
30
+ }
31
+ function repositoryLifecycleDiseaseError(outcome) {
32
+ return new FlowError("INTERNAL_STATE", `contract ${outcome.contract} is unavailable because repository lifecycle evidence is ${outcome.status}`, {
33
+ facts: {
34
+ kind: "repository_lifecycle_disease",
35
+ contractId: outcome.contract,
36
+ status: outcome.status,
37
+ diagnostics: outcome.diagnostics.map((diagnostic) => ({
38
+ objectId: diagnostic.objectId,
39
+ code: diagnostic.code,
40
+ detail: diagnostic.detail,
41
+ ...(diagnostic.contract === undefined ? {} : { contract: diagnostic.contract }),
42
+ })),
43
+ },
44
+ });
48
45
  }
49
46
  async function pinLedgerCoordinates(input) {
50
- const ledger = await readLedger(input.root, input.contractId);
51
- if (!ledger)
52
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} not found or not active`);
53
- const bind = ledger.entries.find((entry) => entry.kind === "bind");
54
- const target = commissionTarget(ledger.entries);
55
- const base = latestBase(ledger.entries);
56
- if (!bind || !target || !base)
57
- throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} is missing pinned audit coordinates`);
47
+ const repository = await readRepositoryLifecycle(input.root);
48
+ const admission = admitRepositoryLifecycleContract(repository, input.contractId);
49
+ let model;
50
+ switch (admission.status) {
51
+ case "healthy":
52
+ model = admission.model;
53
+ break;
54
+ case "diseased":
55
+ case "isolated":
56
+ case "indeterminate":
57
+ throw repositoryLifecycleDiseaseError(admission);
58
+ case "absent":
59
+ throw new FlowError("CONTRACT_NOT_FOUND", `contract ${admission.contract} not found`);
60
+ }
61
+ if (repository.observedLedgerHead === null)
62
+ throw new FlowError("INTERNAL_STATE", "repository lifecycle ledger is absent");
63
+ const bind = model.history.find(({ entry }) => entry.kind === "bind")?.entry;
64
+ if (!bind || bind.kind !== "bind")
65
+ throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} is missing folded bind terms`);
58
66
  const [deliveryHead, deliveryTree, targetHead, targetTree] = await Promise.all([
59
- resolveObject(input.root, input.deliveryRef, "commit"), resolveObject(input.root, input.deliveryRef, "tree"),
60
- resolveObject(input.root, target.ref, "commit"), resolveObject(input.root, target.ref, "tree"),
67
+ resolveObject(input.root, model.resources.deliveryRef, "commit"), resolveObject(input.root, model.resources.deliveryRef, "tree"),
68
+ resolveObject(input.root, model.pins.target.ref, "commit"), resolveObject(input.root, model.pins.target.ref, "tree"),
61
69
  ]);
70
+ if (model.pins.target.approvedHead !== null && model.petition.state === "approved" && model.pins.target.approvedHead !== targetHead) {
71
+ throw new FlowError("INTERNAL_STATE", `target ref ${model.pins.target.ref} moved from approved petition pin`);
72
+ }
62
73
  return {
63
- criteria: effectiveCriteria(ledger.entries), scope: effectiveScope(ledger.entries), appendScope: appendedScopeTerms(ledger.entries), base, entries: ledger.entries,
64
- coordinates: { ledger: { head: ledger.head }, delivery: { ref: input.deliveryRef, head: deliveryHead, tree: deliveryTree }, target: { ref: target.ref, head: targetHead, tree: targetTree } },
74
+ criteria: auditCriteria(model), scope: [...model.effectiveScope], appendScope: appendedScopeTerms(model), base: model.pins.base,
75
+ lifecycle: model, repository,
76
+ coordinates: { ledger: { head: repository.observedLedgerHead }, delivery: { ref: model.resources.deliveryRef, head: deliveryHead, tree: deliveryTree }, target: { ref: model.pins.target.ref, head: targetHead, tree: targetTree } },
65
77
  };
66
78
  }
67
79
  async function deliveryResidue(cwd) {
@@ -75,11 +87,6 @@ async function deliveryResidue(cwd) {
75
87
  continue;
76
88
  const xy = field.slice(0, 2);
77
89
  const filePath = field.slice(3);
78
- if (filePath === KEIYAKU_FILE || filePath.startsWith(".keiyaku/recovery/")) {
79
- if (xy[0] === "R" || xy[0] === "C")
80
- index += 1;
81
- continue;
82
- }
83
90
  if (xy === "??")
84
91
  untracked.add(filePath);
85
92
  else {
@@ -119,44 +126,88 @@ async function deliveryCoordinate(cwd) {
119
126
  const [head, tree, residue] = await Promise.all([resolveObject(cwd, "HEAD", "commit"), resolveObject(cwd, "HEAD", "tree"), deliveryResidue(cwd)]);
120
127
  return { head, tree, residue, residueFingerprint: await residueFingerprint(cwd, residue) };
121
128
  }
129
+ async function checkedOutRef(cwd) {
130
+ try {
131
+ return (await createGit(cwd).raw(["symbolic-ref", "--quiet", "HEAD"])).trim();
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
137
+ async function receiptsAfterPinnedFence(input) {
138
+ const fence = input.model.pins.delivery;
139
+ try {
140
+ return (await createGit(input.root).raw(["rev-list", "--reverse", `${fence}..${input.deliveryHead}`]))
141
+ .split(/\r?\n/).filter(Boolean);
142
+ }
143
+ catch (error) {
144
+ throw wrapGitError(`rev-list ${fence}..${input.deliveryHead}`, error, input.root);
145
+ }
146
+ }
122
147
  async function readReadiness(input) {
123
- const bind = input.frozen.entries.find((entry) => entry.kind === "bind");
124
- const lifecycle = deriveContractState(input.frozen.entries);
125
- const [residue, looseCommits, session, queue, liveProjection, dependencies] = await Promise.all([
126
- deliveryResidue(input.deliveryCwd), receiptsAfterFence(input.root, input.frozen.entries, input.frozen.coordinates.delivery.head), readRenewRecoverySession(input.root, input.contractId), readQueueReadModel(input.root),
148
+ const model = input.frozen.lifecycle;
149
+ const [residue, looseCommits, queue, liveProjection, dependencies] = await Promise.all([
150
+ deliveryResidue(input.deliveryCwd), receiptsAfterPinnedFence({ root: input.root, model, deliveryHead: input.frozen.coordinates.delivery.head }), Promise.resolve(deriveQueueReadModel(input.frozen.repository)),
127
151
  hasStrandableProjectionGeneration({ cwd: input.root, commissionId: input.contractId, deliveryWorktree: input.deliveryCwd, nowMs: Date.now() }),
128
- Promise.all((bind.data.after ?? []).map(async (id) => { const ledger = await readLedger(input.root, id); const state = ledger ? deriveContractState(ledger.entries) : { state: "missing" }; return { id, satisfied: ledger !== null && isTerminalState(state), state: state.state }; })),
152
+ Promise.resolve(model.causal.after.map((id) => {
153
+ const outcome = lookupCurrentContract(input.frozen.repository, id);
154
+ const state = outcome.status === "healthy" ? outcome.model.phase : outcome.status;
155
+ return { id, satisfied: outcome.status === "healthy" && (state === "claimed" || state === "forfeited"), state };
156
+ })),
129
157
  ]);
130
158
  const membership = queueMembership(queue, input.contractId);
131
- return { deliveryResidue: residue, arc: { open: findOpenArc(input.frozen.entries)?.data.arc ?? (getMaxArc(input.frozen.entries) === 0 ? 0 : null), max: getMaxArc(input.frozen.entries), fence: getFence(input.frozen.entries), looseCommits }, renewSession: { open: session !== null }, rewriteGuard: { liveProjection }, dependencies, petition: { state: lifecycle.state, queue: membership ? { position: membership.position, length: membership.queueLength, head: membership.queueHead, diagnostics: membership.diagnostics.map((item) => item.kind) } : null } };
159
+ return { deliveryResidue: residue, arc: { open: model.phase === "active" ? model.intent.arc : null, max: model.intent.arc, fence: model.pins.delivery, looseCommits }, rewriteGuard: { liveProjection }, dependencies, petition: { state: model.phase, queue: membership ? { position: membership.position, length: membership.queueLength, head: membership.queueHead, diagnostics: membership.diagnostics.map((item) => item.kind) } : null } };
132
160
  }
133
161
  export async function pinAuditSnapshot(input) {
134
162
  const contractId = input.address.binding.commissionId;
135
163
  const root = input.address.repo.stableRoot;
136
- const deliveryRef = `refs/heads/keiyaku/${contractId}`;
137
- return pinLedgerCoordinates({ root, contractId, deliveryRef });
164
+ return pinLedgerCoordinates({ root, contractId });
138
165
  }
139
166
  export function initialCoordinateRace(input) {
140
167
  return { which: "delivery-worktree", pinned: input.frozen.coordinates.delivery, observed: input.observed };
141
168
  }
169
+ async function authoritativeDeliveryCwd(root, model) {
170
+ const bind = model.history.find(({ entry }) => entry.kind === "bind")?.entry;
171
+ if (!bind || bind.kind !== "bind")
172
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} is missing folded bind terms`);
173
+ if (bind.data.workspace === "here")
174
+ return fs.realpath(root);
175
+ const worktree = model.resources.worktree;
176
+ if (!worktree || worktree.state !== "held" || worktree.place !== bind.data.place) {
177
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} has no held worktree matching folded bind place ${bind.data.place}`);
178
+ }
179
+ const registered = await findRegisteredContractWorktreePathFromModel(root, model);
180
+ if (!registered) {
181
+ throw new FlowError("INTERNAL_STATE", `contract ${model.contract} has no Git-registered delivery worktree at folded bind place ${bind.data.place}`);
182
+ }
183
+ return registered;
184
+ }
142
185
  export async function pinAuditCoordinates(input) {
186
+ const frozen = await pinAuditSnapshot({ address: input.address });
187
+ return completeAuditCoordinates({ address: input.address, frozen, deliveryCwd: input.deliveryCwd });
188
+ }
189
+ /** Expensive worktree/readiness capture performed after repository admission is released. */
190
+ export async function completeAuditCoordinates(input) {
143
191
  const contractId = input.address.binding.commissionId;
144
192
  const root = input.address.repo.stableRoot;
145
- const frozen = await pinAuditSnapshot({ address: input.address });
146
- const deliveryCwd = await resolveContractDeliveryCwd(input.cwd, contractId);
193
+ const frozen = input.frozen;
194
+ const deliveryCwd = await authoritativeDeliveryCwd(root, frozen.lifecycle);
195
+ if (input.deliveryCwd && path.resolve(input.deliveryCwd) !== path.resolve(deliveryCwd)) {
196
+ throw new Error(`observed delivery worktree ${deliveryCwd} does not match expected worktree ${input.deliveryCwd}`);
197
+ }
147
198
  const coordinate = await deliveryCoordinate(deliveryCwd);
148
- if (coordinate.head !== frozen.coordinates.delivery.head || coordinate.tree !== frozen.coordinates.delivery.tree)
149
- throw new Error(`registered delivery worktree ${coordinate.head}/${coordinate.tree} does not match pinned delivery ref ${frozen.coordinates.delivery.head}/${frozen.coordinates.delivery.tree}`);
199
+ const branchRef = await checkedOutRef(deliveryCwd);
200
+ if (branchRef !== frozen.coordinates.delivery.ref || coordinate.head !== frozen.coordinates.delivery.head || coordinate.tree !== frozen.coordinates.delivery.tree)
201
+ throw new Error(`registered delivery worktree ${branchRef ?? "detached"} ${coordinate.head}/${coordinate.tree} does not match pinned delivery ref ${frozen.coordinates.delivery.ref} ${frozen.coordinates.delivery.head}/${frozen.coordinates.delivery.tree}`);
150
202
  const readiness = await readReadiness({ root, deliveryCwd, contractId, frozen });
151
203
  return { ...frozen, deliveryCwd, deliveryCoordinate: coordinate, readiness };
152
204
  }
153
205
  export async function rereadCoordinateRace(input) {
154
206
  const contractId = input.address.binding.commissionId;
155
207
  const root = input.address.repo.stableRoot;
156
- const deliveryRef = `refs/heads/keiyaku/${contractId}`;
157
208
  let after;
158
209
  try {
159
- after = await pinLedgerCoordinates({ root, contractId, deliveryRef });
210
+ after = await pinLedgerCoordinates({ root, contractId });
160
211
  }
161
212
  catch (error) {
162
213
  return { which: "ledger", pinned: input.frozen.coordinates.ledger, observed: asMessage(error) };
@@ -168,10 +219,14 @@ export async function rereadCoordinateRace(input) {
168
219
  if (after.coordinates.target.head !== input.frozen.coordinates.target.head || after.coordinates.target.tree !== input.frozen.coordinates.target.tree)
169
220
  return { which: "target", pinned: input.frozen.coordinates.target, observed: after.coordinates.target };
170
221
  try {
171
- const deliveryCwd = await resolveContractDeliveryCwd(input.cwd, contractId);
222
+ const deliveryCwd = await authoritativeDeliveryCwd(root, after.lifecycle);
223
+ if (input.deliveryCwd && path.resolve(input.deliveryCwd) !== path.resolve(deliveryCwd)) {
224
+ return { which: "delivery-worktree", pinned: { cwd: input.frozen.deliveryCwd, ...input.frozen.deliveryCoordinate }, observed: { cwd: deliveryCwd, expected: input.deliveryCwd } };
225
+ }
172
226
  const observed = await deliveryCoordinate(deliveryCwd);
173
- if (path.resolve(deliveryCwd) !== path.resolve(input.frozen.deliveryCwd) || observed.head !== input.frozen.deliveryCoordinate.head || observed.tree !== input.frozen.deliveryCoordinate.tree || observed.residueFingerprint !== input.frozen.deliveryCoordinate.residueFingerprint)
174
- return { which: "delivery-worktree", pinned: { cwd: input.frozen.deliveryCwd, ...input.frozen.deliveryCoordinate }, observed: { cwd: deliveryCwd, ...observed } };
227
+ const branchRef = await checkedOutRef(deliveryCwd);
228
+ if (branchRef !== input.frozen.coordinates.delivery.ref || path.resolve(deliveryCwd) !== path.resolve(input.frozen.deliveryCwd) || observed.head !== input.frozen.deliveryCoordinate.head || observed.tree !== input.frozen.deliveryCoordinate.tree || observed.residueFingerprint !== input.frozen.deliveryCoordinate.residueFingerprint)
229
+ return { which: "delivery-worktree", pinned: { cwd: input.frozen.deliveryCwd, ref: input.frozen.coordinates.delivery.ref, ...input.frozen.deliveryCoordinate }, observed: { cwd: deliveryCwd, ref: branchRef, ...observed } };
175
230
  }
176
231
  catch (error) {
177
232
  return { which: "delivery-worktree", pinned: { cwd: input.frozen.deliveryCwd, ...input.frozen.deliveryCoordinate }, observed: asMessage(error) };
@@ -1,6 +1,6 @@
1
1
  import { FlowError, asMessage } from "../../flow-error.js";
2
2
  import { scopePathEvidence } from "../scope.js";
3
- import { verificationExecutor, resolveVerificationCommands, runVerificationCommand } from "../settlement/verification.js";
3
+ import { composeVerificationPlan, runVerificationPlan, resolveVerificationPolicy } from "../settlement/verification.js";
4
4
  function importLine(line) {
5
5
  return /^(?:import\b|export\s+(?:\*|\{)|(?:const|let|var)\s+[^=]+\s*=\s*require\s*\(|[^=]+\brequire\s*\(|[^=]+\bimport\s*\()/.test(line.content.trim());
6
6
  }
@@ -118,32 +118,28 @@ export function presentDiff(input) {
118
118
  return { budget: { limit: budget, used }, selected: selected.sort((left, right) => left.path.localeCompare(right.path) || (left.hunk ?? -1) - (right.hunk ?? -1)), omissions };
119
119
  }
120
120
  function fileFacts(diff, scope) { return diff.files.map((file, index) => { const row = scope[index]; return { path: file.path, ...(file.oldPath ? { oldPath: file.oldPath } : {}), status: file.status, stats: { insertions: file.insertions, deletions: file.deletions, binary: file.binary }, matcher: row.matcher, explanations: row.explanations, residue: row.residue, class: row.class }; }); }
121
- export async function evaluateVerification(cwd, head) {
122
- let commands;
121
+ export async function resolveAuditVerificationPlan(input) {
122
+ let policy;
123
123
  try {
124
- commands = await resolveVerificationCommands(cwd);
124
+ policy = await resolveVerificationPolicy(input.cwd);
125
125
  }
126
126
  catch (error) {
127
- return { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) }, results: [] };
127
+ return { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) } };
128
128
  }
129
- if (commands.length === 0)
129
+ const plan = composeVerificationPlan({ contract: input.contract, repositoryGates: policy.commands, timeoutMs: policy.timeoutMs });
130
+ return { status: "resolved", plan };
131
+ }
132
+ export async function evaluateVerification(input) {
133
+ const plan = input.plan;
134
+ if (plan.length === 0)
130
135
  return { status: "not_configured", results: [] };
131
- const results = [];
132
- for (const command of commands) {
133
- const argv = [command.command, ...command.args];
134
- const known = verificationExecutor(command);
135
- if ("cause" in known) {
136
- results.push({ status: "unverifiable", label: command.label, command: command.command, argv, head, cause: known.cause });
137
- continue;
138
- }
139
- try {
140
- const execution = await runVerificationCommand(cwd, command);
141
- results.push({ status: "executed", label: command.label, executor: known.executor, body: known.body, command: command.command, argv, head, exitCode: execution.code, durationMs: execution.durationMs, stdout: execution.stdout, stderr: execution.stderr });
142
- }
143
- catch (error) {
144
- return { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) }, results: [] };
145
- }
146
- }
136
+ const executed = await runVerificationPlan({ cwd: input.cwd, candidate: { head: input.candidate.head, tree: input.candidate.tree }, plan, signal: input.signal, coordinationKey: input.coordinationKey, coordinationRoot: input.coordinationRoot });
137
+ const results = executed.map((result) => {
138
+ const common = { source: result.source, declaration: result.declaration, label: result.label, executor: result.executor, command: result.command, argv: [result.command, ...result.args], candidate: result.candidate };
139
+ if (result.execution.status === "runner_failed")
140
+ return { ...common, ...result.execution };
141
+ return { status: "executed", ...common, body: result.shell ? result.command : result.args.at(-1) ?? "", exitCode: result.execution.code, durationMs: result.execution.durationMs, stdout: result.execution.stdout, stderr: result.execution.stderr };
142
+ });
147
143
  return { status: "evaluated", results };
148
144
  }
149
145
  export async function collectEvidence(input) {
@@ -154,5 +150,5 @@ export async function collectEvidence(input) {
154
150
  return null;
155
151
  const diff = input.diff;
156
152
  const scope = await evaluateScope({ cwd: input.candidateResult.cwd, frozen: input.frozen, diff });
157
- return { diff, scope, presentation: presentDiff({ diff, scope, budget: input.diffBudgetBytes, retrievalCoordinates: { from: input.frozen.base, to: input.frozen.coordinates.delivery.head } }), files: fileFacts(diff, scope), verification: await evaluateVerification(input.candidateResult.cwd, candidate.head), candidate };
153
+ return { diff, scope, presentation: presentDiff({ diff, scope, budget: input.diffBudgetBytes, retrievalCoordinates: { from: input.frozen.base, to: input.frozen.coordinates.delivery.head } }), files: fileFacts(diff, scope), verification: input.verificationOverride ?? await evaluateVerification({ cwd: input.candidateResult.cwd, candidate, plan: input.verificationPlan, signal: input.signal, coordinationKey: input.coordinationKey, coordinationRoot: input.coordinationRoot }), candidate };
158
154
  }
@@ -1,27 +1,50 @@
1
- import { createAuditCandidate, disposeAuditCandidate, readAuditCandidateDiff } from "./candidate.js";
2
- import { initialCoordinateRace, pinAuditCoordinates, pinAuditSnapshot, rereadCoordinateRace } from "./coordinates.js";
3
- import { collectEvidence } from "./evidence.js";
1
+ import { createAuditCandidate, disposeAuditCandidate, prepareAuditCandidate, readAuditCandidateDiff } from "./candidate.js";
2
+ import { completeAuditCoordinates, initialCoordinateRace, pinAuditSnapshot, rereadCoordinateRace } from "./coordinates.js";
3
+ import { collectEvidence, resolveAuditVerificationPlan } from "./evidence.js";
4
4
  import { collectFindings, emptyRaceReport } from "./report.js";
5
5
  import { readinessFromFacts } from "./readiness.js";
6
6
  export async function auditContract(input) {
7
7
  const contractId = input.address.binding.commissionId;
8
+ const frozenSnapshot = await pinAuditSnapshot({ address: input.address });
9
+ const bind = frozenSnapshot.lifecycle.history.find(({ entry }) => entry.kind === "bind")?.entry;
10
+ const contractVerification = bind?.kind === "bind" ? bind.data.verification : [];
11
+ const admission = {
12
+ frozen: frozenSnapshot,
13
+ contractVerification,
14
+ };
8
15
  let frozen;
9
16
  try {
10
- frozen = await pinAuditCoordinates({ cwd: input.cwd, address: input.address });
17
+ frozen = await completeAuditCoordinates({ address: input.address, frozen: admission.frozen });
11
18
  }
12
19
  catch (error) {
13
- const fallback = await pinAuditSnapshot({ address: input.address }).catch(() => null);
14
- if (!fallback)
15
- throw error;
20
+ const fallback = admission.frozen;
16
21
  return emptyRaceReport({ contractId, coordinates: fallback.coordinates, terms: { criteria: fallback.criteria, scope: fallback.scope, appendScope: fallback.appendScope }, detail: error instanceof Error ? error.message : String(error), race: initialCoordinateRace({ frozen: fallback, observed: error instanceof Error ? error.message : String(error) }) });
17
22
  }
18
23
  const candidateResult = await createAuditCandidate({ root: input.address.repo.stableRoot, contractId, frozen });
19
24
  let evidence = null;
20
25
  let verification;
26
+ let candidatePreparation = { status: "not-required", reason: "candidate_unavailable" };
21
27
  try {
28
+ let verificationPlan = [];
29
+ let verificationOverride;
30
+ if (candidateResult.candidate) {
31
+ const planResolution = await resolveAuditVerificationPlan({ cwd: candidateResult.cwd, contract: admission.contractVerification });
32
+ if (planResolution.status === "unknown") {
33
+ candidatePreparation = { status: "not-required", reason: "verification_plan_unavailable" };
34
+ verificationOverride = { status: "unknown", cause: planResolution.cause, results: [] };
35
+ }
36
+ else {
37
+ verificationPlan = planResolution.plan;
38
+ const lifecycleBind = frozen.lifecycle.history.find(({ entry }) => entry.kind === "bind")?.entry;
39
+ candidatePreparation = await prepareAuditCandidate({ candidateResult, verificationRequired: verificationPlan.length > 0, plan: lifecycleBind?.kind === "bind" && lifecycleBind.data.workspace === "worktree" ? lifecycleBind.data.bootstrap : undefined });
40
+ if (candidatePreparation.status === "failed") {
41
+ verificationOverride = { status: "not_evaluated", reason: "candidate_preparation_failed", results: [] };
42
+ }
43
+ }
44
+ }
22
45
  const diff = await readAuditCandidateDiff(candidateResult);
23
- evidence = await collectEvidence({ candidateResult, frozen, diff, diffBudgetBytes: input.diffBudgetBytes ?? 32_000 });
24
- verification = evidence?.verification ?? { status: "not_evaluated", reason: candidateResult.rehearsal.status === "conflict" ? "renew_conflict" : "renew_unknown", results: [] };
46
+ evidence = await collectEvidence({ candidateResult, frozen, diff, diffBudgetBytes: input.diffBudgetBytes ?? 32_000, verificationPlan, signal: input.signal, coordinationKey: JSON.stringify({ repository: input.address.repo.stableRoot, candidate: candidateResult.candidate, preparation: bind?.kind === "bind" && bind.data.workspace === "worktree" ? bind.data.bootstrap : null, plan: verificationPlan }), coordinationRoot: input.address.repo.stableRoot, ...(verificationOverride ? { verificationOverride } : {}) });
47
+ verification = evidence?.verification ?? verificationOverride ?? { status: "not_evaluated", reason: candidateResult.rehearsal.status === "conflict" ? "renew_conflict" : "renew_unknown", results: [] };
25
48
  await input.afterEvidence?.();
26
49
  }
27
50
  finally {
@@ -37,6 +60,6 @@ export async function auditContract(input) {
37
60
  return emptyRaceReport({ contractId, coordinates: frozen.coordinates, terms: { criteria: frozen.criteria, scope: frozen.scope, appendScope: frozen.appendScope }, detail, race });
38
61
  }
39
62
  const readiness = readinessFromFacts(frozen.readiness);
40
- const findings = collectFindings({ contractId, deliveryCwd: frozen.deliveryCwd, rehearsal: candidateResult.rehearsal, scope: evidence?.scope ?? null, verification, readiness });
41
- return { contract: contractId, coordinates: frozen.coordinates, terms: { criteria: frozen.criteria, scope: frozen.scope, appendScope: frozen.appendScope }, rehearsal: candidateResult.rehearsal, candidate: evidence ? (evidence.candidate.rewritten ? { kind: "renew-rehearsal", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree, deliveryHead: frozen.coordinates.delivery.head, targetHead: frozen.coordinates.target.head } : { kind: "delivery", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree }) : null, diff: evidence?.diff ?? null, presentation: evidence?.presentation ?? null, files: evidence?.files ?? null, scope: evidence?.scope ?? null, verification, readiness, findings, counts: { files: evidence?.files.length ?? 0, selected: evidence?.presentation.selected.length ?? 0, omitted: evidence?.presentation.omissions.length ?? 0, inScope: evidence?.scope.filter((row) => row.class === "in-scope").length ?? 0, downgraded: evidence?.scope.filter((row) => row.class === "downgraded").length ?? 0, violated: evidence?.scope.filter((row) => row.class === "violated").length ?? 0, findings: findings.length } };
63
+ const findings = collectFindings({ contractId, deliveryCwd: frozen.deliveryCwd, rehearsal: candidateResult.rehearsal, scope: evidence?.scope ?? null, candidatePreparation, verification, readiness });
64
+ return { contract: contractId, coordinates: frozen.coordinates, terms: { criteria: frozen.criteria, scope: frozen.scope, appendScope: frozen.appendScope }, rehearsal: candidateResult.rehearsal, candidate: evidence ? (evidence.candidate.rewritten ? { kind: "renew-rehearsal", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree, deliveryHead: frozen.coordinates.delivery.head, targetHead: frozen.coordinates.target.head } : { kind: "delivery", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree }) : null, diff: evidence?.diff ?? null, presentation: evidence?.presentation ?? null, files: evidence?.files ?? null, scope: evidence?.scope ?? null, candidatePreparation, verification, readiness, findings, counts: { files: evidence?.files.length ?? 0, selected: evidence?.presentation.selected.length ?? 0, omitted: evidence?.presentation.omissions.length ?? 0, inScope: evidence?.scope.filter((row) => row.class === "in-scope").length ?? 0, downgraded: evidence?.scope.filter((row) => row.class === "downgraded").length ?? 0, violated: evidence?.scope.filter((row) => row.class === "violated").length ?? 0, findings: findings.length } };
42
65
  }
@@ -9,12 +9,14 @@ export function collectFindings(input) {
9
9
  findings.push({ code: "RENEW_UNKNOWN", detail: `${input.rehearsal.cause.code}: ${input.rehearsal.cause.message}`, nextCommand: `${selector} audit` });
10
10
  for (const row of input.scope?.filter((item) => item.class === "violated") ?? [])
11
11
  findings.push({ code: "SCOPE_RESIDUE", detail: `${row.path}: ${row.residue.length} unexplained changes`, nextCommand: `${selector} amend --scope-add ${JSON.stringify(row.path)}` });
12
+ if (input.candidatePreparation.status === "failed")
13
+ findings.push({ code: "CANDIDATE_PREPARATION_FAILED", detail: `${input.candidatePreparation.cause.code}: ${input.candidatePreparation.cause.message}`, nextCommand: `${selector} audit` });
12
14
  if (input.verification.status === "evaluated") {
13
15
  for (const result of input.verification.results) {
14
16
  if (result.status === "executed" && result.exitCode !== 0)
15
17
  findings.push({ code: "VERIFICATION_FAILED", detail: `${result.label} exited ${result.exitCode}`, nextCommand: result.command });
16
- if (result.status === "unverifiable")
17
- findings.push({ code: "VERIFICATION_UNVERIFIABLE", detail: `${result.label}: ${result.cause}`, nextCommand: result.command });
18
+ if (result.status === "runner_failed")
19
+ findings.push({ code: "VERIFICATION_UNKNOWN", detail: `${result.source} ${result.label}: ${result.cause.code}: ${result.cause.message}`, nextCommand: result.command });
18
20
  }
19
21
  }
20
22
  const residue = input.readiness.deliveryResidue;
@@ -22,8 +24,6 @@ export function collectFindings(input) {
22
24
  findings.push({ code: "DELIVERY_RESIDUE", detail: `registered delivery worktree has uncommitted residue at ${input.deliveryCwd}`, nextCommand: `git -C ${JSON.stringify(input.deliveryCwd)} status --short` });
23
25
  if (input.readiness.arc.max > 0 && input.readiness.arc.open === null && input.readiness.arc.looseCommits.length > 0)
24
26
  findings.push({ code: "LOOSE_COMMITS", detail: `${input.readiness.arc.looseCommits.length} loose commits after fence`, nextCommand: `printf '# Continue\\n\\n## Objective\\nReceipt loose commits.\\n\\n## Brief\\nContinue delivery.\\n' | ${selector} arc` });
25
- if (input.readiness.renewSession.open)
26
- findings.push({ code: "RENEW_SESSION_OPEN", detail: "renew recovery session is open", nextCommand: `${selector} renew --continue` });
27
27
  if (input.readiness.rewriteGuard.liveProjection)
28
28
  findings.push({ code: "LIVE_REWRITE_GUARD", detail: "a live projection owns the delivery worktree", nextCommand: "keiyaku status" });
29
29
  for (const dependency of input.readiness.dependencies.filter((item) => !item.satisfied))
@@ -34,5 +34,5 @@ export function collectFindings(input) {
34
34
  }
35
35
  export function emptyRaceReport(input) {
36
36
  const findings = [{ code: "COORDINATE_RACE", detail: input.detail, nextCommand: `keiyaku @${input.contractId} audit`, race: input.race }];
37
- return { contract: input.contractId, coordinates: input.coordinates, terms: input.terms, rehearsal: { status: "unknown", cause: { code: "COORDINATE_RACE", message: input.detail } }, candidate: null, diff: null, presentation: null, files: null, scope: null, verification: { status: "not_evaluated", reason: "coordinate_race", results: [] }, readiness: null, findings, counts: { files: 0, selected: 0, omitted: 0, inScope: 0, downgraded: 0, violated: 0, findings: 1 } };
37
+ return { contract: input.contractId, coordinates: input.coordinates, terms: input.terms, rehearsal: { status: "unknown", cause: { code: "COORDINATE_RACE", message: input.detail } }, candidate: null, diff: null, presentation: null, files: null, scope: null, candidatePreparation: { status: "not-required", reason: "coordinate_race" }, verification: { status: "not_evaluated", reason: "coordinate_race", results: [] }, readiness: null, findings, counts: { files: 0, selected: 0, omitted: 0, inScope: 0, downgraded: 0, violated: 0, findings: 1 } };
38
38
  }