@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,22 +1,28 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { setTimeout as delay } from "node:timers/promises";
5
5
  import { DatabaseSync } from "node:sqlite";
6
6
  import { FlowError } from "../flow-error.js";
7
- import { createGit, errorContainsAnyPattern, isFullGitObjectId, MISSING_HEAD_PATTERNS, runGitProcess, wrapGitError } from "../git/core.js";
7
+ import { isCommitAncestor, readCommitCoordinate } from "../git/commits.js";
8
+ import { isFullGitObjectId, runGitProcess } from "../git/core.js";
9
+ import { resolveExactRefHeads } from "../git/refs.js";
8
10
  import { KEIYAKU_FILE } from "../keiyaku.js";
9
- import { renderCurrentContractView } from "./contract-view.js";
11
+ import { renderCanonicalCarrierBody } from "./contract-view.js";
10
12
  import { isCommissionId } from "./ids.js";
11
13
  import { activeWorkspaceHereContracts } from "./repository-ledger/inventory.js";
12
14
  import { lookupCurrentContract, readRepositoryLifecycle, } from "./repository-ledger/read-model.js";
13
15
  import { REPOSITORY_LEDGER_REF, } from "./repository-ledger/codec.js";
14
16
  import { resolveContractCarrierTarget, stableRepoRoot, } from "./worktree-path.js";
17
+ import { carrierDigest, carrierInputIdentity, contractHead, } from "./repository-ledger/identity.js";
15
18
  import { installCarrierRuntimePorts } from "./contract-carrier-runtime.js";
16
- export { convergeContractCarrier, reconcileContractCarrierProjection, withContractCarrierSession } from "./contract-carrier-runtime.js";
19
+ export { convergeContractCarrier, reconcileContractCarrierProjection, withContractCarrierSession, withObservedContractCarrierSession } from "./contract-carrier-runtime.js";
17
20
  const BUSY_SLICE_MS = 25;
18
- const CARRIER_CONTRACT = /^id: (\S+)$/m;
19
- const CARRIER_PROVENANCE = /^ledger: (\S+)$/m;
21
+ const CARRIER_HEADER = "<!-- keiyaku-carrier";
22
+ const CARRIER_HEADER_END = "-->";
23
+ const CARRIER_ID_LINE = "contract-id";
24
+ const CARRIER_HEAD_LINE = "contract-head";
25
+ const CARRIER_DIGEST_LINE = "carrier-digest";
20
26
  export function acceptedBind(lifecycle) {
21
27
  for (const { entry } of lifecycle.history) {
22
28
  if (entry.kind === "bind")
@@ -38,10 +44,10 @@ async function projectRenewDeliveryRoot(root, oldHead, newHead) {
38
44
  throw new FlowError("INTERNAL_STATE", update.stderr.trim() || update.error?.message || `read-tree exited ${String(update.status)}`, { cause: update.error });
39
45
  }
40
46
  const [head, status] = await Promise.all([
41
- runGitProcess(root, ["rev-parse", "--verify", "HEAD^{commit}"]),
47
+ readCommitCoordinate(root, "HEAD"),
42
48
  runGitProcess(root, ["status", "--porcelain=v1", "--untracked-files=no"]),
43
49
  ]);
44
- if (head.status !== 0 || head.error || head.stdout.trim() !== newHead || status.status !== 0 || status.error || status.stdout !== "") {
50
+ if (head.status !== "ok" || head.coordinate.commit !== newHead || status.status !== 0 || status.error || status.stdout !== "") {
45
51
  throw new FlowError("INTERNAL_STATE", "delivery worktree did not align to the committed renew head");
46
52
  }
47
53
  }
@@ -59,9 +65,15 @@ export async function reconcileFoldedDeliveryWorktree(input) {
59
65
  }
60
66
  const indexTree = index.stdout.trim();
61
67
  const newTree = await resolveCommitTree(input.targetRoot, renew.entry.data.newHead);
62
- if (indexTree === newTree)
63
- return "already-present";
64
68
  const oldTree = await resolveCommitTree(input.targetRoot, renew.entry.data.oldHead);
69
+ if (indexTree === newTree && worktree.status === 0 && !worktree.error)
70
+ return "already-present";
71
+ if (indexTree !== oldTree
72
+ && worktree.status === 0
73
+ && !worktree.error
74
+ && await isCommitAncestor(input.targetRoot, renew.entry.data.newHead, "HEAD")) {
75
+ return "already-present";
76
+ }
65
77
  if (indexTree !== oldTree || worktree.status !== 0 || worktree.error) {
66
78
  throw new FlowError("INTERNAL_STATE", `delivery worktree cannot be safely reconstructed for accepted renew ${renew.objectId}`);
67
79
  }
@@ -69,11 +81,14 @@ export async function reconcileFoldedDeliveryWorktree(input) {
69
81
  return "created";
70
82
  }
71
83
  async function resolveCommitTree(cwd, commit) {
72
- const result = await runGitProcess(cwd, ["rev-parse", "--verify", `${commit}^{tree}`]);
73
- if (result.status !== 0 || result.error) {
74
- throw new FlowError("INTERNAL_STATE", result.stderr.trim() || result.error?.message || `cannot resolve tree for ${commit}`, { cause: result.error });
84
+ const read = await readCommitCoordinate(cwd, commit);
85
+ if (read.status !== "ok") {
86
+ if (read.status === "failed") {
87
+ throw new FlowError("INTERNAL_STATE", read.result.stderr.trim() || read.error.message || `cannot resolve tree for ${commit}`, { cause: read.error });
88
+ }
89
+ throw new FlowError("INTERNAL_STATE", `cannot resolve tree for ${commit}`);
75
90
  }
76
- return result.stdout.trim();
91
+ return read.coordinate.tree;
77
92
  }
78
93
  export function observedLedgerHead(observations) {
79
94
  const observation = observations.find((item) => item.ref === REPOSITORY_LEDGER_REF)?.observed;
@@ -90,14 +105,19 @@ export function checkCancellation(signal) {
90
105
  if (signal?.aborted)
91
106
  throwCancelled();
92
107
  }
93
- export async function acquireGate(stableRoot, signal) {
108
+ export function carrierPublicationGatePath(stableRoot, contractId, targetPath) {
109
+ const key = createHash("sha256").update(`${contractId}\0${targetPath}`).digest("hex");
110
+ return path.join(stableRoot, ".keiyaku", "carrier-gates", `${key}.sqlite`);
111
+ }
112
+ export async function acquireGate(stableRoot, contractId, targetPath, signal) {
94
113
  checkCancellation(signal);
95
- const directory = path.join(stableRoot, ".keiyaku");
114
+ const databasePath = carrierPublicationGatePath(stableRoot, contractId, targetPath);
115
+ const directory = path.dirname(databasePath);
96
116
  await fs.mkdir(directory, { recursive: true });
97
117
  checkCancellation(signal);
98
118
  let database;
99
119
  try {
100
- database = new DatabaseSync(path.join(directory, "contract-carrier.sqlite"));
120
+ database = new DatabaseSync(databasePath);
101
121
  database.exec(`PRAGMA busy_timeout = ${BUSY_SLICE_MS}`);
102
122
  }
103
123
  catch (error) {
@@ -127,9 +147,9 @@ export function releaseGate(database) {
127
147
  }
128
148
  }
129
149
  /** Serialize cross-process committed replay decisions without exposing gate coordinates. */
130
- export async function withContractReplayGate(cwd, signal, action) {
150
+ export async function withContractReplayGate(cwd, contractId, targetPath, signal, action) {
131
151
  const stableRoot = await stableRepoRoot(cwd);
132
- const gate = await acquireGate(stableRoot, signal);
152
+ const gate = await acquireGate(stableRoot, contractId, targetPath, signal);
133
153
  try {
134
154
  return await action(stableRoot);
135
155
  }
@@ -137,35 +157,102 @@ export async function withContractReplayGate(cwd, signal, action) {
137
157
  releaseGate(gate);
138
158
  }
139
159
  }
140
- export async function readCarrierIdentity(target) {
160
+ function canonicalBody(lifecycle) {
161
+ return Buffer.from(renderCanonicalCarrierBody(lifecycle), "utf8");
162
+ }
163
+ export function expectedCarrierIdentity(lifecycle) {
164
+ return carrierInputIdentity({
165
+ contractId: lifecycle.contract,
166
+ contractHead: lifecycle.contractHead,
167
+ bodyDigest: carrierDigest(createHash("sha256").update(canonicalBody(lifecycle)).digest("hex")),
168
+ });
169
+ }
170
+ export function renderCarrier(lifecycle) {
171
+ const identity = expectedCarrierIdentity(lifecycle);
172
+ return [
173
+ CARRIER_HEADER,
174
+ `${CARRIER_ID_LINE}: ${identity.contractId}`,
175
+ `${CARRIER_HEAD_LINE}: ${identity.contractHead}`,
176
+ `${CARRIER_DIGEST_LINE}: ${identity.bodyDigest}`,
177
+ CARRIER_HEADER_END,
178
+ "",
179
+ canonicalBody(lifecycle).toString("utf8"),
180
+ ].join("\n");
181
+ }
182
+ function parsedCarrierFromBytes(source) {
183
+ if (source.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])))
184
+ return null;
185
+ const text = source.toString("utf8");
186
+ if (!Buffer.from(text, "utf8").equals(source))
187
+ return null;
188
+ const lines = text.split("\n");
189
+ if (lines.length < 7 || lines[0] !== CARRIER_HEADER || lines[4] !== CARRIER_HEADER_END || lines[5] !== "")
190
+ return null;
191
+ const field = (line, name) => line.startsWith(`${name}: `) ? line.slice(name.length + 2) : null;
192
+ const contractId = field(lines[1], CARRIER_ID_LINE);
193
+ const head = field(lines[2], CARRIER_HEAD_LINE);
194
+ const digest = field(lines[3], CARRIER_DIGEST_LINE);
195
+ if (contractId === null || head === null || digest === null || !isCommissionId(contractId) || !isFullGitObjectId(head))
196
+ return null;
141
197
  try {
142
- return parseCarrierIdentity(await fs.readFile(target, "utf8"));
198
+ return {
199
+ identity: carrierInputIdentity({ contractId, contractHead: contractHead(head), bodyDigest: carrierDigest(digest) }),
200
+ body: Buffer.from(lines.slice(6).join("\n"), "utf8"),
201
+ };
143
202
  }
144
203
  catch {
145
204
  return null;
146
205
  }
147
206
  }
207
+ /** Strict current-version parser; it intentionally accepts no legacy carrier format. */
148
208
  export function parseCarrierIdentity(source) {
149
- const contractId = source.match(CARRIER_CONTRACT)?.[1] ?? "";
150
- const ledgerHead = source.match(CARRIER_PROVENANCE)?.[1] ?? "";
151
- return isCommissionId(contractId) && isFullGitObjectId(ledgerHead) ? { contractId, ledgerHead } : null;
209
+ const parsed = parsedCarrierFromBytes(Buffer.from(source, "utf8"));
210
+ if (!parsed)
211
+ return null;
212
+ const digest = carrierDigest(createHash("sha256").update(parsed.body).digest("hex"));
213
+ return digest === parsed.identity.bodyDigest ? parsed.identity : null;
214
+ }
215
+ export async function verifyCarrier(input) {
216
+ const expected = expectedCarrierIdentity(input.lifecycle);
217
+ let source;
218
+ try {
219
+ source = await fs.readFile(input.target);
220
+ }
221
+ catch (error) {
222
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
223
+ return { status: "missing", expected, target: input.target };
224
+ return { status: "invalid-header", expected, target: input.target };
225
+ }
226
+ const parsed = parsedCarrierFromBytes(source);
227
+ if (!parsed)
228
+ return { status: "invalid-header", expected, target: input.target };
229
+ const actualBodyDigest = carrierDigest(createHash("sha256").update(parsed.body).digest("hex"));
230
+ if (!parsed.body.equals(canonicalBody(input.lifecycle)) || actualBodyDigest !== parsed.identity.bodyDigest) {
231
+ return { status: "modified", expected, actual: parsed.identity, target: input.target };
232
+ }
233
+ if (parsed.identity.contractId !== expected.contractId
234
+ || parsed.identity.contractHead !== expected.contractHead
235
+ || parsed.identity.bodyDigest !== expected.bodyDigest) {
236
+ return { status: "identity-mismatch", expected, actual: parsed.identity, target: input.target };
237
+ }
238
+ return { status: "current", expected, actual: parsed.identity, target: input.target };
152
239
  }
153
- export function carrierMatches(identity, contractId, ledgerHead) {
154
- return identity?.contractId === contractId && identity.ledgerHead === ledgerHead;
240
+ export function carrierMatches(verification) {
241
+ return verification.status === "current";
155
242
  }
156
243
  export async function retireTerminalHereCarrier(lifecycle, target) {
157
244
  if (target.status !== "available" || target.workspace !== "here")
158
245
  return "already-present";
159
- let source;
160
246
  try {
161
- source = await fs.readFile(target.path, "utf8");
247
+ await fs.access(target.path);
162
248
  }
163
249
  catch (error) {
164
250
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
165
251
  return "already-present";
166
252
  throw error;
167
253
  }
168
- if (parseCarrierIdentity(source)?.contractId !== lifecycle.contract)
254
+ const verification = await verifyCarrier({ target: target.path, lifecycle });
255
+ if (!carrierMatches(verification))
169
256
  return "already-present";
170
257
  try {
171
258
  await fs.unlink(target.path);
@@ -179,45 +266,51 @@ export async function retireTerminalHereCarrier(lifecycle, target) {
179
266
  }
180
267
  export async function publishCarrier(input) {
181
268
  if (input.target.status === "unavailable") {
182
- return { status: "target-unavailable", ledgerHead: input.lifecycle.observedLedgerHead, reason: input.target.reason };
269
+ return { status: "target-unavailable", contractHead: input.lifecycle.contractHead, reason: input.target.reason };
183
270
  }
184
- if (input.target.workspace === "here") {
185
- const occupants = activeWorkspaceHereContracts(input.repository);
186
- if (occupants.length !== 1 || occupants[0] !== input.contractId) {
187
- return {
188
- status: "failed",
189
- ledgerHead: input.lifecycle.observedLedgerHead,
190
- target: input.target.path,
191
- cause: `workspace-here carrier occupancy does not admit ${input.contractId}`,
192
- };
271
+ const target = input.target;
272
+ const hasExpectedTarget = async () => {
273
+ const observed = await observe(input.stableRoot, input.contractId);
274
+ if (observed.lifecycle.contractHead !== input.lifecycle.contractHead)
275
+ return false;
276
+ if (observed.target.status !== "available" || observed.target.path !== target.path || observed.target.workspace !== target.workspace)
277
+ return false;
278
+ if (observed.target.workspace === "here") {
279
+ const occupants = activeWorkspaceHereContracts(observed.repository);
280
+ return occupants.length === 1 && occupants[0] === input.contractId;
193
281
  }
282
+ return true;
283
+ };
284
+ if (target.workspace === "here" && !await hasExpectedTarget()) {
285
+ return {
286
+ status: "failed",
287
+ contractHead: input.lifecycle.contractHead,
288
+ target: target.path,
289
+ cause: `workspace-here carrier occupancy does not admit ${input.contractId}`,
290
+ };
194
291
  }
195
- if (carrierMatches(await readCarrierIdentity(input.target.path), input.contractId, input.lifecycle.observedLedgerHead)) {
196
- return { status: "current", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path };
292
+ if (carrierMatches(await verifyCarrier({ target: target.path, lifecycle: input.lifecycle }))) {
293
+ return { status: "current", contractHead: input.lifecycle.contractHead, target: target.path };
197
294
  }
198
- const directory = path.dirname(input.target.path);
295
+ const directory = path.dirname(target.path);
199
296
  const temporary = path.join(directory, `.${path.basename(KEIYAKU_FILE)}.${process.pid}.${randomUUID()}.tmp`);
200
297
  try {
201
298
  await fs.mkdir(directory, { recursive: true });
202
- await fs.writeFile(temporary, renderCurrentContractView(input.lifecycle), { encoding: "utf8", flag: "wx" });
203
- if ((await resolveCommitOrNull(input.stableRoot, REPOSITORY_LEDGER_REF)) !== input.lifecycle.observedLedgerHead) {
299
+ await fs.writeFile(temporary, renderCarrier(input.lifecycle), { encoding: "utf8", flag: "wx" });
300
+ if (!await hasExpectedTarget()) {
204
301
  await fs.unlink(temporary);
205
- return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: "authoritative ledger head changed before carrier replacement" };
302
+ return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: "contract carrier target changed before replacement" };
206
303
  }
207
- await (input.contractViewWriter?.replace ?? fs.rename)(temporary, input.target.path);
208
- const [activeHead, carrierIdentity] = await Promise.all([
209
- resolveCommitOrNull(input.stableRoot, REPOSITORY_LEDGER_REF),
210
- readCarrierIdentity(input.target.path),
211
- ]);
212
- if (activeHead !== input.lifecycle.observedLedgerHead ||
213
- !carrierMatches(carrierIdentity, input.contractId, input.lifecycle.observedLedgerHead)) {
214
- return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: "carrier post-check did not prove the published contract identity and ledger head" };
304
+ await (input.contractViewWriter?.replace ?? fs.rename)(temporary, target.path);
305
+ const verification = await verifyCarrier({ target: target.path, lifecycle: input.lifecycle });
306
+ if (!await hasExpectedTarget() || !carrierMatches(verification)) {
307
+ return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: "carrier post-check did not prove the published contract identity" };
215
308
  }
216
- return { status: "materialized", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path };
309
+ return { status: "materialized", contractHead: input.lifecycle.contractHead, target: target.path };
217
310
  }
218
311
  catch (error) {
219
312
  await fs.unlink(temporary).catch(() => undefined);
220
- return { status: "failed", ledgerHead: input.lifecycle.observedLedgerHead, target: input.target.path, cause: diagnostic(error) };
313
+ return { status: "failed", contractHead: input.lifecycle.contractHead, target: target.path, cause: diagnostic(error) };
221
314
  }
222
315
  }
223
316
  export async function observe(stableRoot, contractId) {
@@ -237,34 +330,24 @@ export async function observe(stableRoot, contractId) {
237
330
  const target = await resolveContractCarrierTarget(stableRoot, contractId, bind);
238
331
  const targetRef = lifecycle.pins.target.ref;
239
332
  const deliveryRef = lifecycle.resources.deliveryRef;
240
- const [targetHead, deliveryHead] = await Promise.all([
241
- resolveCommitOrNull(stableRoot, targetRef),
242
- resolveCommitOrNull(stableRoot, deliveryRef),
243
- ]);
244
- return { repository, lifecycle, target, pins: { ledgerHead: lifecycle.observedLedgerHead, target: { ref: targetRef, head: targetHead }, delivery: { ref: deliveryRef, head: deliveryHead } } };
245
- }
246
- export async function resolveCommitOrNull(cwd, ref) {
247
- try {
248
- return (await createGit(cwd).raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
249
- }
250
- catch (error) {
251
- if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
252
- return null;
253
- throw wrapGitError(`rev-parse ${ref}`, error, cwd);
254
- }
333
+ const heads = await resolveExactRefHeads(stableRoot, [targetRef, deliveryRef]);
334
+ const targetHead = heads.get(targetRef) ?? null;
335
+ const deliveryHead = heads.get(deliveryRef) ?? null;
336
+ if (repository.observedLedgerHead === null)
337
+ throw new FlowError("INTERNAL_STATE", "accepted contract has no repository ledger observation");
338
+ return { repository, lifecycle, target, pins: { repositoryLedgerHead: repository.observedLedgerHead, target: { ref: targetRef, head: targetHead }, delivery: { ref: deliveryRef, head: deliveryHead } } };
255
339
  }
256
340
  installCarrierRuntimePorts({
257
341
  acceptedBind,
258
342
  acquireGate,
259
343
  carrierMatches,
260
344
  checkCancellation,
345
+ expectedCarrierIdentity,
261
346
  observe,
262
347
  observedLedgerHead,
263
- projectRenewDeliveryWorktree,
264
348
  publishCarrier,
265
- readCarrierIdentity,
349
+ verifyCarrier,
266
350
  reconcileFoldedDeliveryWorktree,
267
351
  releaseGate,
268
- resolveCommitOrNull,
269
352
  retireTerminalHereCarrier,
270
353
  });
@@ -1,6 +1,7 @@
1
1
  import { shortCommitHash } from "../git/commits.js";
2
- import { effectiveCriteria } from "./lifecycle-history.js";
2
+ import { effectiveCriteria, effectiveVerification } from "./lifecycle-history.js";
3
3
  import { renderScopeFragment } from "./scope.js";
4
+ import { renderVerificationDeclarations } from "./verification-declaration.js";
4
5
  import { currentLifecycleState, } from "./repository-ledger/read-model.js";
5
6
  export { currentLifecycleState } from "./repository-ledger/read-model.js";
6
7
  const VERB_COLUMN_WIDTH = 11;
@@ -227,6 +228,7 @@ export function renderContractView(input) {
227
228
  "",
228
229
  ].join("\n");
229
230
  }
231
+ const verification = effectiveVerification(input.entries);
230
232
  return [
231
233
  `# ${bind.data.title}`,
232
234
  "",
@@ -255,8 +257,8 @@ export function renderContractView(input) {
255
257
  "## Criteria",
256
258
  ...effectiveCriteria(input.entries).map((criterion) => `- ${criterion}`),
257
259
  "",
258
- ...(bind.data.verification.length > 0
259
- ? ["## Verification", ...bind.data.verification.map((declaration) => `- ${declaration}`), ""]
260
+ ...(verification.length > 0
261
+ ? ["## Verification", renderVerificationDeclarations(verification), ""]
260
262
  : []),
261
263
  ...(bind.data.extensions.length > 0
262
264
  ? [
@@ -270,12 +272,47 @@ export function renderContractView(input) {
270
272
  "",
271
273
  ].join("\n");
272
274
  }
273
- export function renderCurrentContractView(model) {
275
+ /** Exact UTF-8/LF carrier body; metadata and diagnostic renderings stay outside it. */
276
+ export function renderCanonicalCarrierBody(model) {
277
+ const entries = model.history.map(({ entry }) => entry);
278
+ const bind = entries.find((entry) => entry.kind === "bind");
279
+ if (!bind)
280
+ throw new Error(`contract ${model.contract} has no bind body to render`);
281
+ const verification = effectiveVerification(entries);
282
+ return [
283
+ `# ${bind.data.title}`,
284
+ "",
285
+ "## Context",
286
+ bind.data.context,
287
+ "",
288
+ "## Objective",
289
+ bind.data.objective,
290
+ "",
291
+ "## Design & Approach",
292
+ bind.data.design,
293
+ "",
294
+ "## Scope",
295
+ ...renderScopeFragment(model.effectiveScope).split("\n"),
296
+ "",
297
+ "## Criteria",
298
+ ...effectiveCriteria(entries).map((criterion) => `- ${criterion}`),
299
+ "",
300
+ ...(verification.length > 0
301
+ ? ["## Verification", renderVerificationDeclarations(verification), ""]
302
+ : []),
303
+ ...(bind.data.extensions.length > 0
304
+ ? bind.data.extensions.flatMap((extension) => extension.content.length > 0
305
+ ? [`## ${extension.title}`, extension.content, ""]
306
+ : [`## ${extension.title}`, ""])
307
+ : []),
308
+ ].join("\n").replace(/\n*$/, "\n");
309
+ }
310
+ export function renderCurrentContractView(model, observedLedgerHead) {
274
311
  const entries = model.history.map(({ entry }) => entry);
275
312
  return renderContractView({
276
313
  id: model.contract,
277
314
  entries,
278
- ledgerHead: model.observedLedgerHead,
315
+ ledgerHead: observedLedgerHead ?? undefined,
279
316
  state: currentLifecycleState(model),
280
317
  effectiveScope: model.effectiveScope,
281
318
  });
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { verificationDeclarationSchema } from "./verification-declaration.js";
2
3
  export const contractStateSchema = z.enum(["bound", "active", "petitioned", "claimed", "forfeited"]);
3
4
  export const contractRecordSchema = z
4
5
  .object({
@@ -9,7 +10,7 @@ export const contractRecordSchema = z
9
10
  design: z.string().trim().min(1),
10
11
  scope: z.array(z.string().trim().min(1)).min(1),
11
12
  criteria: z.array(z.string().trim().min(1)).min(1),
12
- verification: z.array(z.string().trim().min(1)).min(1),
13
+ verification: z.array(verificationDeclarationSchema),
13
14
  base: z.string().trim().min(1),
14
15
  place: z.string().trim().min(1).nullable().optional(),
15
16
  after: z.array(z.string().trim().min(1)),