@astrosheep/keiyaku 2.9.11 → 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 (218) hide show
  1. package/README.md +79 -85
  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 +17 -4
  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/bind/meta.js +4 -4
  17. package/build/cli/commands/contract/petition/handler.js +2 -1
  18. package/build/cli/commands/contract/petition/meta.js +2 -2
  19. package/build/cli/commands/contract/renew/handler.js +44 -3
  20. package/build/cli/commands/contract/renew/meta.js +5 -4
  21. package/build/cli/commands/metadata.js +14 -10
  22. package/build/cli/commands/projection/call/handler.js +1 -1
  23. package/build/cli/commands/projection/call/meta.js +1 -1
  24. package/build/cli/commands/projection/catalog.js +2 -2
  25. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  26. package/build/cli/commands/projection/fork/meta.js +12 -0
  27. package/build/cli/commands/projection/history/handler.js +46 -2
  28. package/build/cli/commands/projection/history/meta.js +3 -3
  29. package/build/cli/commands/projection/kill/handler.js +4 -5
  30. package/build/cli/commands/projection/kill/meta.js +2 -2
  31. package/build/cli/commands/projection/status/handler.js +29 -13
  32. package/build/cli/commands/projection/status/meta.js +14 -3
  33. package/build/cli/commands/projection/tell/handler.js +8 -4
  34. package/build/cli/commands/projection/tell/meta.js +1 -1
  35. package/build/cli/commands/projection/wait/handler.js +45 -20
  36. package/build/cli/commands/projection/wait/meta.js +1 -1
  37. package/build/cli/commands/shared.js +2 -2
  38. package/build/cli/commands/system/catalog.js +2 -0
  39. package/build/cli/commands/task/add/handler.js +28 -12
  40. package/build/cli/commands/task/add/meta.js +18 -6
  41. package/build/cli/commands/task/catalog.js +4 -0
  42. package/build/cli/commands/task/compose/handler.js +18 -0
  43. package/build/cli/commands/task/compose/meta.js +10 -0
  44. package/build/cli/commands/task/doctor/handler.js +2 -3
  45. package/build/cli/commands/task/doctor/meta.js +2 -2
  46. package/build/cli/commands/task/done/meta.js +2 -2
  47. package/build/cli/commands/task/drop/handler.js +7 -2
  48. package/build/cli/commands/task/drop/meta.js +6 -3
  49. package/build/cli/commands/task/hold/meta.js +2 -2
  50. package/build/cli/commands/task/log/meta.js +2 -2
  51. package/build/cli/commands/task/ls/meta.js +2 -2
  52. package/build/cli/commands/task/note/handler.js +6 -0
  53. package/build/cli/commands/task/note/meta.js +11 -0
  54. package/build/cli/commands/task/resume/meta.js +2 -2
  55. package/build/cli/commands/task/shared.js +29 -15
  56. package/build/cli/commands/task/show/meta.js +2 -2
  57. package/build/cli/commands/task/start/meta.js +2 -2
  58. package/build/cli/commands/task/stop/meta.js +2 -2
  59. package/build/cli/commands/task/update/meta.js +3 -2
  60. package/build/cli/commands/verification/handler.js +43 -0
  61. package/build/cli/commands/verification/meta.js +13 -0
  62. package/build/cli/completion.js +16 -13
  63. package/build/cli/flags.js +48 -12
  64. package/build/cli/help.js +29 -30
  65. package/build/cli/index.js +14 -8
  66. package/build/cli/parse-flags.js +60 -21
  67. package/build/cli/parse-metadata.js +10 -4
  68. package/build/cli/parse-selectors.js +22 -4
  69. package/build/cli/parse.js +43 -30
  70. package/build/cli/projection-address.js +7 -7
  71. package/build/cli/render/arc.js +12 -3
  72. package/build/cli/render/audit.js +2 -2
  73. package/build/cli/render/call.js +4 -4
  74. package/build/cli/render/kanshi.js +122 -85
  75. package/build/cli/render/misc.js +2 -2
  76. package/build/cli/render/petition.js +16 -1
  77. package/build/cli/render/projection-history.js +17 -4
  78. package/build/cli/render/shared.js +5 -5
  79. package/build/cli/render/status-indicator.js +47 -0
  80. package/build/cli/render/status.js +106 -76
  81. package/build/cli/render/success-response.js +20 -5
  82. package/build/cli/render/task.js +33 -5
  83. package/build/cli/render/tell.js +10 -4
  84. package/build/cli/render/tool-command-normalization.js +137 -0
  85. package/build/cli/render/tool-presentation.js +2 -1
  86. package/build/cli/render/verification.js +23 -0
  87. package/build/cli/render/wait.js +101 -33
  88. package/build/cli/types.js +2 -1
  89. package/build/config/akuma-loader.js +21 -1
  90. package/build/config/settings/knobs.js +7 -1
  91. package/build/config/settings/schema.js +10 -5
  92. package/build/core/addressing.js +3 -3
  93. package/build/core/amend.js +102 -17
  94. package/build/core/arc.js +4 -4
  95. package/build/core/audit/candidate.js +10 -18
  96. package/build/core/audit/coordinates.js +12 -17
  97. package/build/core/audit/evidence.js +6 -4
  98. package/build/core/audit/facade.js +3 -3
  99. package/build/core/audit/report.js +1 -1
  100. package/build/core/bind-reconciliation.js +42 -23
  101. package/build/core/bind.js +182 -81
  102. package/build/core/call/call.js +6 -7
  103. package/build/core/call/context.js +46 -33
  104. package/build/core/call/execution.js +21 -6
  105. package/build/core/call/prompt.js +11 -42
  106. package/build/core/contract-carrier-runtime.js +209 -105
  107. package/build/core/contract-carrier.js +156 -73
  108. package/build/core/contract-view.js +43 -6
  109. package/build/core/contract.js +2 -1
  110. package/build/core/derived-replay.js +168 -35
  111. package/build/core/draft.js +60 -27
  112. package/build/core/forfeit.js +1 -1
  113. package/build/core/lifecycle-history.js +13 -0
  114. package/build/core/lifecycle-recovery.js +32 -27
  115. package/build/core/lifecycle-runner.js +14 -14
  116. package/build/core/log.js +1 -1
  117. package/build/core/outcome-base.js +2 -2
  118. package/build/core/projection/akuma-name.js +43 -0
  119. package/build/core/projection/generation/database.js +141 -79
  120. package/build/core/projection/generation/ledger.js +91 -0
  121. package/build/core/projection/generation/model.js +19 -14
  122. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  123. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  124. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  125. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  126. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  127. package/build/core/projection/generation/projection-generation-process.js +16 -15
  128. package/build/core/projection/generation/projection-generation-runner.js +132 -76
  129. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  130. package/build/core/projection/generation/protocol.js +68 -0
  131. package/build/core/projection/generation/store.js +17 -7
  132. package/build/core/projection/generation/transitions.js +24 -14
  133. package/build/core/projection/index.js +5 -5
  134. package/build/core/projection/projection-core.js +6 -2
  135. package/build/core/projection/projection-execution-observer.js +1 -1
  136. package/build/core/projection/projection-history.js +8 -8
  137. package/build/core/projection/projection-kill.js +20 -10
  138. package/build/core/projection/projection-life-observer.js +8 -8
  139. package/build/core/projection/projection-mint.js +6 -8
  140. package/build/core/projection/projection-status-observation.js +77 -8
  141. package/build/core/projection/projection-status.js +151 -4
  142. package/build/core/projection/projection-wait.js +48 -29
  143. package/build/core/projection/projection-wake.js +28 -88
  144. package/build/core/projection/tell/launch-store.js +1 -1
  145. package/build/core/projection/tell/store.js +1 -1
  146. package/build/core/renew-build.js +204 -74
  147. package/build/core/renew-plan.js +34 -9
  148. package/build/core/renew-rewrite.js +56 -15
  149. package/build/core/renew.js +39 -35
  150. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  151. package/build/core/repository-ledger/claim-evidence.js +1 -1
  152. package/build/core/repository-ledger/codec.js +11 -5
  153. package/build/core/repository-ledger/current-state-store.js +464 -349
  154. package/build/core/repository-ledger/fold-repository.js +5 -6
  155. package/build/core/repository-ledger/identity.js +60 -0
  156. package/build/core/repository-ledger/inventory.js +3 -3
  157. package/build/core/repository-ledger/read-model.js +57 -9
  158. package/build/core/repository-ledger/write-transaction.js +24 -17
  159. package/build/core/run-control/connection-bound-close.js +41 -0
  160. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  161. package/build/core/run-control/detached-process.js +14 -0
  162. package/build/core/run-control/index.js +6 -0
  163. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  164. package/build/core/run-control/runner-lease.js +172 -0
  165. package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
  166. package/build/core/seal.js +44 -32
  167. package/build/core/settlement/claim-delivery.js +22 -9
  168. package/build/core/settlement/claim.js +8 -6
  169. package/build/core/settlement/petition-claim-gates.js +9 -8
  170. package/build/core/settlement/petition-head-guard.js +5 -6
  171. package/build/core/settlement/petition-preview.js +21 -5
  172. package/build/core/settlement/petition.js +51 -17
  173. package/build/core/settlement/settlement.js +20 -15
  174. package/build/core/settlement/verdict.js +2 -2
  175. package/build/core/settlement/verification.js +492 -364
  176. package/build/core/status/board.js +184 -75
  177. package/build/core/status/drift.js +8 -10
  178. package/build/core/task/board.js +1 -0
  179. package/build/core/task/commands.js +25 -0
  180. package/build/core/task/compose.js +328 -0
  181. package/build/core/task/document.js +102 -39
  182. package/build/core/task/index.js +4 -3
  183. package/build/core/task/settlement-git.js +70 -30
  184. package/build/core/task/settlement-policy.js +27 -14
  185. package/build/core/task/source-board.js +1 -0
  186. package/build/core/task/task-bind-preparation.js +59 -9
  187. package/build/core/task/task-contract.js +2 -2
  188. package/build/core/task/task-git-store.js +1 -0
  189. package/build/core/task/task-store-repository.js +2 -0
  190. package/build/core/task/task.js +2 -2
  191. package/build/core/transcripts.js +4 -4
  192. package/build/core/verification-declaration.js +95 -0
  193. package/build/core/worktree-bootstrap.js +1 -1
  194. package/build/core/worktree-path.js +7 -11
  195. package/build/flow-error.js +15 -2
  196. package/build/generated/version.js +2 -2
  197. package/build/git/branches.js +41 -70
  198. package/build/git/commits.js +92 -21
  199. package/build/git/core.js +9 -161
  200. package/build/git/diff/preview.js +4 -4
  201. package/build/git/diff/read.js +4 -4
  202. package/build/git/diff/structured.js +5 -5
  203. package/build/git/process.js +229 -0
  204. package/build/git/refs.js +30 -5
  205. package/build/git/staging.js +39 -115
  206. package/build/git/streaming-batch.js +9 -16
  207. package/build/git/worktree.js +104 -91
  208. package/build/index.js +7 -0
  209. package/package.json +2 -2
  210. package/skills/keiyaku-akuma/SKILL.md +27 -21
  211. package/skills/keiyaku-task/SKILL.md +61 -5
  212. package/skills/keiyaku-workflow/SKILL.md +81 -26
  213. package/build/cli/commands/projection/revive/meta.js +0 -12
  214. package/build/core/projection/projection-life-protocol.js +0 -115
  215. package/build/core/projection/projection-runner-lock.js +0 -382
  216. package/build/core/projection/tell/database.js +0 -127
  217. package/build/core/settlement/verification-coordination.js +0 -305
  218. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -216,15 +216,14 @@ function validateAfterPrerequisites(ledger, contracts, isolatedContracts, diseas
216
216
  const bindTransaction = containingTransactions.get(bindObjectId);
217
217
  const ready = state.bind.data.after.every((dependency) => {
218
218
  const prerequisite = contracts.get(dependency);
219
- const terminalTransaction = prerequisite?.terminalObjectId === null
219
+ const prerequisiteBindTransaction = prerequisite === undefined
220
220
  ? undefined
221
- : containingTransactions.get(prerequisite?.terminalObjectId ?? "");
221
+ : containingTransactions.get(prerequisite.entryObjectIds[0]);
222
222
  return prerequisite !== undefined &&
223
223
  valid.has(dependency) &&
224
- prerequisite.terminalObjectId !== null &&
225
- terminalTransaction !== undefined &&
224
+ prerequisiteBindTransaction !== undefined &&
226
225
  bindTransaction !== undefined &&
227
- isTypedLedgerAncestor(ledger, terminalTransaction, bindTransaction);
226
+ isTypedLedgerAncestor(ledger, prerequisiteBindTransaction, bindTransaction);
228
227
  });
229
228
  if (ready) {
230
229
  valid.add(contract);
@@ -240,7 +239,7 @@ function validateAfterPrerequisites(ledger, contracts, isolatedContracts, diseas
240
239
  objectId: bindObjectId,
241
240
  code: "causal-prerequisite",
242
241
  contract,
243
- detail: `after prerequisites were not terminal in typed ancestry: ${state.bind.data.after.join(", ")}`,
242
+ detail: `after prerequisite binds were not in typed ancestry: ${state.bind.data.after.join(", ")}`,
244
243
  });
245
244
  contracts.delete(contract);
246
245
  isolatedContracts.add(contract);
@@ -0,0 +1,60 @@
1
+ export function repositoryLedgerHead(value) {
2
+ return value;
3
+ }
4
+ export function lifecycleTransactionId(value) {
5
+ return value;
6
+ }
7
+ export function contractHead(value) {
8
+ return value;
9
+ }
10
+ export function carrierDigest(value) {
11
+ if (!/^[0-9a-f]{64}$/.test(value)) {
12
+ throw new Error("carrier digest must be a lowercase SHA-256 hex value");
13
+ }
14
+ return value;
15
+ }
16
+ export function carrierInputIdentity(input) {
17
+ return {
18
+ surface: "carrier",
19
+ contractId: input.contractId,
20
+ contractHead: input.contractHead,
21
+ bodyDigest: input.bodyDigest,
22
+ };
23
+ }
24
+ export function carrierRetirementInputIdentity(input) {
25
+ return { surface: "carrier-retirement", ...input };
26
+ }
27
+ export function worktreeInputIdentity(input) {
28
+ return {
29
+ surface: "worktree",
30
+ contractId: input.contractId,
31
+ contractHead: input.contractHead,
32
+ deliveryHead: input.deliveryHead,
33
+ };
34
+ }
35
+ export function contractViewInputIdentity(input) {
36
+ return {
37
+ surface: "contract-view",
38
+ contractId: input.contractId,
39
+ contractHead: input.contractHead,
40
+ };
41
+ }
42
+ export function narrowIgnoreInputIdentity(input) {
43
+ return { surface: "narrow-ignore", ...input };
44
+ }
45
+ export function renewDeliveryInputIdentity(input) {
46
+ return { surface: "renew-delivery", ...input };
47
+ }
48
+ export function taskBoardInputIdentity(input) {
49
+ return { surface: "task-board", ...input };
50
+ }
51
+ export function deliveryRefInputIdentity(input) {
52
+ return { surface: "delivery-ref", ...input };
53
+ }
54
+ export function targetWorktreeInputIdentity(input) {
55
+ return { surface: "target-worktree", ...input };
56
+ }
57
+ /** Structural equality keeps surface comparisons independent of process-local receipts. */
58
+ export function sameInputIdentity(left, right) {
59
+ return left !== null && JSON.stringify(left) === JSON.stringify(right);
60
+ }
@@ -295,12 +295,12 @@ export function activeWorkspaceHereContracts(read) {
295
295
  }
296
296
  /** Runtime ignore projection for one worktree from a single typed repository fold. */
297
297
  export function inventoryIgnorePatternsForWorktree(read, targetLifecycle) {
298
- if (read.observedLedgerHead !== targetLifecycle.observedLedgerHead) {
299
- throw new Error("narrow-ignore inventory requires one repository-ledger observation");
300
- }
301
298
  const acceptedTarget = read.contracts.find(({ contract }) => contract === targetLifecycle.contract);
302
299
  if (!acceptedTarget)
303
300
  throw new Error(`contract ${targetLifecycle.contract} is absent from narrow-ignore inventory`);
301
+ if (acceptedTarget.contractHead !== targetLifecycle.contractHead) {
302
+ throw new Error("narrow-ignore inventory requires the current contract semantic head");
303
+ }
304
304
  const target = foldedWorktreeCoordinate(acceptedTarget);
305
305
  const required = target.workspace === "here"
306
306
  ? activeWorkspaceHereContracts(read).length > 0
@@ -1,9 +1,11 @@
1
1
  import { openTaskAdmissionEvidenceReader, readTaskAdmissionEvidenceAtCommit, } from "../task/settlement-git.js";
2
2
  import { repositoryDeliveryRef, } from "./codec.js";
3
+ import { bindEffectiveScope } from "./fold.js";
3
4
  import { observeRepositoryLedgerHead, readAcceptedRepositoryFold } from "./accepted-fold-read.js";
4
- import { advanceRepositoryCurrentState, discardRepositoryCurrentState, publishRepositoryCurrentState, readPinnedCurrentContract, readPinnedRepositoryLifecycle, readRepositoryCurrentStateNodeIds, readRepositoryCurrentStatePin } from "./current-state-store.js";
5
+ import { advanceRepositoryCurrentState, publishRepositoryCurrentState, readPinnedCurrentContract, readPinnedRepositoryLifecycle, readRepositoryCurrentStateNodeIds, readRepositoryCurrentStatePin, readRepositoryCurrentStatePredecessor } from "./current-state-store.js";
5
6
  import { IncrementalLedgerValidationError, readIncrementalRepositorySegment } from "./incremental-read.js";
6
7
  import { RepositoryObjectError } from "./git-object-store.js";
8
+ import { contractHead, repositoryLedgerHead } from "./identity.js";
7
9
  /** Public lifecycle state derived only from the accepted repository-ledger fold. */
8
10
  export function currentLifecycleState(model) {
9
11
  switch (model.phase) {
@@ -22,6 +24,50 @@ export function currentLifecycleState(model) {
22
24
  return { state: "forfeited" };
23
25
  }
24
26
  }
27
+ /** Purely advance an admitted repository snapshot with its just-committed bind. */
28
+ export function advanceRepositorySnapshotWithBind(input) {
29
+ const { repository, ledgerHead, entryObjectId, entry } = input;
30
+ const bootstrap = entry.data.workspace === "worktree" ? entry.data.bootstrap : undefined;
31
+ const model = deepFreeze({
32
+ contractHead: contractHead(entryObjectId),
33
+ contract: entry.contract,
34
+ phase: "active",
35
+ intent: { arc: 0, openedBy: entry.actor, title: entry.data.title, objective: entry.data.objective, brief: null },
36
+ receiptSegments: [],
37
+ seal: null,
38
+ bootstrapReplay: bootstrap
39
+ ? { state: "pending", plan: { command: [...bootstrap.command], timeoutMs: bootstrap.timeoutMs } }
40
+ : { state: "not-planned" },
41
+ petition: { state: "none" },
42
+ verdict: null,
43
+ pins: { base: entry.data.base, delivery: entry.data.base, target: { ref: entry.data.target, approvedHead: null } },
44
+ effectiveScope: bindEffectiveScope(entry),
45
+ causal: { after: [...(entry.data.after ?? [])], needs: null },
46
+ resources: {
47
+ deliveryRef: repositoryDeliveryRef(entry.contract),
48
+ worktree: entry.data.workspace === "worktree" ? { place: entry.data.place, state: "held" } : null,
49
+ tasks: [...(entry.data.taskIds ?? [])].map((taskId) => ({ taskId, state: "held" })),
50
+ },
51
+ history: [{ objectId: entryObjectId, entry: structuredClone(entry) }],
52
+ });
53
+ return deepFreeze({
54
+ ...structuredClone(repository),
55
+ observedLedgerHead: ledgerHead,
56
+ contracts: [...repository.contracts, model].sort((left, right) => left.contract.localeCompare(right.contract)),
57
+ topology: {
58
+ head: ledgerHead,
59
+ nodes: [...repository.topology.nodes, {
60
+ objectId: ledgerHead,
61
+ type: "transaction",
62
+ ledgerParents: repository.observedLedgerHead ? [repository.observedLedgerHead] : [],
63
+ verb: "bind",
64
+ contract: entry.contract,
65
+ entryObjectIds: [entryObjectId],
66
+ finalEntryObjectId: entryObjectId,
67
+ }],
68
+ },
69
+ });
70
+ }
25
71
  function deepFreeze(value) {
26
72
  if (value === null || typeof value !== "object" || Object.isFrozen(value))
27
73
  return value;
@@ -199,14 +245,14 @@ function bootstrapReplay(state) {
199
245
  }
200
246
  throw new Error(`failed bootstrap for ${state.contract} has no folded replay disposition`);
201
247
  }
202
- function lifecycleModel(observedLedgerHead, state, nodes, taskAdmissionEvidence) {
248
+ function lifecycleModel(_observedLedgerHead, state, nodes, taskAdmissionEvidence) {
203
249
  const history = acceptedHistory(state, nodes);
204
250
  const verdict = findLastRow(history, "verdict");
205
251
  const released = state.terminalObjectId !== null;
206
252
  const taskReleased = state.terminal?.kind === "forfeit";
207
253
  const selectedTasks = [...(state.bind.data.taskIds ?? [])];
208
254
  return deepFreeze({
209
- observedLedgerHead,
255
+ contractHead: contractHead(state.entryObjectIds.at(-1)),
210
256
  contract: state.contract,
211
257
  phase: state.phase,
212
258
  intent: currentIntent(state, history),
@@ -359,22 +405,25 @@ async function recoverRepositoryLifecycle(cwd, options = {}) {
359
405
  });
360
406
  }
361
407
  export async function readRepositoryLifecycle(cwd, options = {}) {
362
- const observe = options.observeHead ?? observeRepositoryLedgerHead;
408
+ const observe = options.observedHead !== undefined
409
+ ? async () => options.observedHead
410
+ : options.observeHead ?? observeRepositoryLedgerHead;
363
411
  const observedHead = await observe(cwd);
364
412
  const injected = options.observeHead !== undefined || options.readTaskAdmissionEvidence !== undefined;
365
413
  if (!injected) {
366
414
  const pinned = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
367
415
  if (pinned)
368
416
  return deepFreeze(pinned);
369
- const priorHead = await readRepositoryCurrentStatePin(cwd, options.signal);
417
+ const candidatePriorHead = observedHead === null ? null : await readRepositoryCurrentStatePredecessor(cwd, observedHead, options.signal);
418
+ const priorHead = candidatePriorHead === null ? null : await readRepositoryCurrentStatePin(cwd, candidatePriorHead, options.signal);
370
419
  if (priorHead && observedHead && priorHead !== observedHead) {
371
420
  try {
372
421
  const acceptedObjectIds = await readRepositoryCurrentStateNodeIds(cwd, priorHead, options.signal);
373
422
  const segment = await readIncrementalRepositorySegment(cwd, priorHead, observedHead, options.signal, acceptedObjectIds ?? undefined);
374
423
  if (segment && await advanceRepositoryCurrentState(cwd, priorHead, observedHead, segment, {
375
424
  signal: options.signal,
376
- confirmHead: () => observeRepositoryLedgerHead(cwd),
377
- materialize: (state, history) => materializeIncrementalContract(cwd, observedHead, state, history),
425
+ confirmHead: options.observedHead !== undefined ? async () => observedHead : () => observeRepositoryLedgerHead(cwd),
426
+ materialize: (state, history) => materializeIncrementalContract(cwd, repositoryLedgerHead(observedHead), state, history),
378
427
  })) {
379
428
  const advanced = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
380
429
  if (advanced)
@@ -391,7 +440,6 @@ export async function readRepositoryLifecycle(cwd, options = {}) {
391
440
  || (error instanceof RepositoryObjectError && error.reason !== "git-failure");
392
441
  if (!untrustedDelta)
393
442
  throw error;
394
- await discardRepositoryCurrentState(cwd, options.signal);
395
443
  }
396
444
  }
397
445
  }
@@ -404,7 +452,7 @@ export async function readRepositoryLifecycle(cwd, options = {}) {
404
452
  if (!injected) {
405
453
  const published = await publishRepositoryCurrentState(cwd, recovered, {
406
454
  signal: options.signal,
407
- confirmHead: () => observeRepositoryLedgerHead(cwd),
455
+ confirmHead: options.observedHead !== undefined ? async () => observedHead : () => observeRepositoryLedgerHead(cwd),
408
456
  fold: recoveredFold,
409
457
  });
410
458
  if (!published)
@@ -1,7 +1,7 @@
1
1
  import { runGitProcess } from "../../git/core.js";
2
2
  import { commitRefTransaction, } from "../../git/refs.js";
3
3
  import { REPOSITORY_LEDGER_REF, evidenceObjectId, formatLifecycleEntry, formatLifecycleTransaction, repositoryDeliveryRef, } from "./codec.js";
4
- import { assertHostObjectId, assertReadableCommit, deriveHostGitCoordinates, } from "./git-object-store.js";
4
+ import { assertHostObjectId, assertReadableCommit, assertReadableCommitClosure, deriveHostGitCoordinates, } from "./git-object-store.js";
5
5
  import { assertHonestClaimEvidence } from "./claim-evidence.js";
6
6
  import { transactionMembershipError } from "./transaction-membership.js";
7
7
  function replayFailure(effect, error) {
@@ -76,26 +76,26 @@ async function assertAuthoritativeCommitTipClosure(cwd, coordinates, operations)
76
76
  if (operation.kind !== "update" || operation.ref === REPOSITORY_LEDGER_REF || checked.has(operation.newOid))
77
77
  continue;
78
78
  assertHostObjectId(coordinates, operation.newOid);
79
- await assertReadableCommit(cwd, coordinates, operation.newOid);
79
+ await assertReadableCommitClosure(cwd, operation.newOid);
80
80
  checked.add(operation.newOid);
81
81
  }
82
82
  }
83
- export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead) {
84
- const coordinates = await deriveHostGitCoordinates(cwd);
83
+ export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead, coordinates) {
84
+ const resolvedCoordinates = coordinates ?? await deriveHostGitCoordinates(cwd);
85
85
  if (expectedLedgerHead !== null)
86
- assertHostObjectId(coordinates, expectedLedgerHead);
86
+ assertHostObjectId(resolvedCoordinates, expectedLedgerHead);
87
87
  const evidence = evidenceObjectId(entry);
88
88
  if (evidence !== null) {
89
- assertHostObjectId(coordinates, evidence);
90
- await assertReadableCommit(cwd, coordinates, evidence);
89
+ assertHostObjectId(resolvedCoordinates, evidence);
90
+ await assertReadableCommit(cwd, resolvedCoordinates, evidence);
91
91
  }
92
92
  const parents = [
93
93
  ...(expectedLedgerHead === null ? [] : [expectedLedgerHead]),
94
94
  ...(evidence === null ? [] : [evidence]),
95
95
  ];
96
- const args = ["commit-tree", coordinates.emptyTree, ...parents.flatMap((parent) => ["-p", parent])];
96
+ const args = ["commit-tree", resolvedCoordinates.emptyTree, ...parents.flatMap((parent) => ["-p", parent])];
97
97
  const result = await runGitProcess(cwd, args, {
98
- input: formatLifecycleEntry(entry, coordinates.objectFormat),
98
+ input: formatLifecycleEntry(entry, resolvedCoordinates.objectFormat),
99
99
  env: {
100
100
  GIT_AUTHOR_NAME: "Keiyaku",
101
101
  GIT_AUTHOR_EMAIL: "keiyaku@localhost",
@@ -110,16 +110,17 @@ export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead) {
110
110
  throw new Error(`cannot build ${entry.kind} ledger object: ${detail}`, { cause: result.error });
111
111
  }
112
112
  const objectId = result.stdout.trim();
113
- assertHostObjectId(coordinates, objectId);
114
- return { entry, objectId, expectedLedgerHead, parents, coordinates };
113
+ assertHostObjectId(resolvedCoordinates, objectId);
114
+ return { entry, objectId, expectedLedgerHead, parents, coordinates: resolvedCoordinates };
115
115
  }
116
- export async function buildLifecycleTail(cwd, entries, expectedLedgerHead) {
116
+ export async function buildLifecycleTail(cwd, entries, expectedLedgerHead, coordinates) {
117
117
  if (entries.length === 0)
118
118
  throw new Error("a lifecycle tail requires at least one entry");
119
+ const resolvedCoordinates = coordinates ?? await deriveHostGitCoordinates(cwd);
119
120
  const built = [];
120
121
  let parent = expectedLedgerHead;
121
122
  for (const entry of entries) {
122
- const next = await buildLifecycleEntry(cwd, entry, parent);
123
+ const next = await buildLifecycleEntry(cwd, entry, parent, resolvedCoordinates);
123
124
  built.push(next);
124
125
  parent = next.objectId;
125
126
  }
@@ -199,7 +200,8 @@ async function buildTransactionTail(input) {
199
200
  throw new Error(`bind transaction repeats contract ${contract}`);
200
201
  if (input.verb !== "bind" && causalParent === null)
201
202
  throw new Error(`${input.verb} transaction has no accepted lifecycle predecessor for ${contract}`);
202
- const tail = await buildLifecycleTail(input.cwd, input.entries, causalParent);
203
+ const coordinates = await deriveHostGitCoordinates(input.cwd);
204
+ const tail = await buildLifecycleTail(input.cwd, input.entries, causalParent, coordinates);
203
205
  const transaction = await buildLifecycleTransactionNode({
204
206
  cwd: input.cwd,
205
207
  verb: input.verb,
@@ -246,6 +248,7 @@ async function publishLifecycleWrite(cwd, builtTransaction, final, ownedOperatio
246
248
  completion: replay.status,
247
249
  ledgerHead: builtTransaction.objectId,
248
250
  entry: final.entry,
251
+ entryObjectId: final.objectId,
249
252
  authoritativeOperations,
250
253
  authorityProof: transaction.proof,
251
254
  authorityObservations: transaction.observations,
@@ -278,6 +281,9 @@ export async function publishBindTransaction(input) {
278
281
  { kind: "update", ref: input.delivery.ref, newOid: input.delivery.head, expectedOldOid: null },
279
282
  ];
280
283
  if (input.taskBoard) {
284
+ if (input.taskBoard.ref !== input.entry.data.target) {
285
+ throw new Error("task-selected bind task-board ref must equal the entry target");
286
+ }
281
287
  operations.push({
282
288
  kind: "update",
283
289
  ref: input.taskBoard.ref,
@@ -311,11 +317,12 @@ export async function publishAuthoritativeTransaction(cwd, transaction, options
311
317
  const entry = final.entry;
312
318
  const operations = [];
313
319
  if (transaction.verb === "renew" && entry.kind === "renew") {
314
- assertAuthority("delivery" in transaction, "renew transaction must pin its delivery ref");
320
+ assertAuthority("delivery" in transaction && "target" in transaction, "renew transaction must pin its target and delivery refs");
321
+ assertAuthority(transaction.target.expectedOid === entry.data.newBase, "renew target CAS must equal newBase");
315
322
  assertAuthority(transaction.delivery.ref === repositoryDeliveryRef(entry.contract), "renew delivery ref does not name its contract");
316
323
  assertAuthority(transaction.delivery.expectedOid === entry.data.oldHead, "renew delivery CAS must equal oldHead");
317
324
  assertAuthority(entry.data.oldHead !== entry.data.newHead, "renew must replace the delivery head");
318
- operations.push({ kind: "update", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid, newOid: entry.data.newHead });
325
+ operations.push({ kind: "verify", ref: transaction.target.ref, expectedOldOid: transaction.target.expectedOid }, { kind: "update", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid, newOid: entry.data.newHead });
319
326
  }
320
327
  else if (transaction.verb === "forfeit") {
321
328
  assertAuthority(entry.kind === "forfeit", "forfeit transaction must end in a forfeit entry");
@@ -331,7 +338,7 @@ export async function publishAuthoritativeTransaction(cwd, transaction, options
331
338
  assertAuthority(transaction.target.expectedOid === entry.data.targetHead, "claim target CAS must equal targetHead");
332
339
  assertAuthority(transaction.delivery.ref === repositoryDeliveryRef(entry.contract), "claim delivery ref does not name its contract");
333
340
  assertAuthority(transaction.delivery.expectedOid === entry.data.deliveryHead, "claim delivery CAS must equal deliveryHead");
334
- await assertHonestClaimEvidence(cwd, entry.data);
341
+ await assertHonestClaimEvidence(cwd, entry.data, { coordinates: built.transaction.coordinates });
335
342
  operations.push({ kind: "update", ref: transaction.target.ref, expectedOldOid: transaction.target.expectedOid, newOid: entry.data.mergeCommit }, { kind: "delete", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid });
336
343
  }
337
344
  const write = await publishLifecycleWrite(cwd, built.transaction, final, operations, options.replayPlan, options.transactionRunner);
@@ -0,0 +1,41 @@
1
+ export function beginConnectionBoundRunnerClose(database, lease) {
2
+ database.exec("BEGIN IMMEDIATE");
3
+ let open = true;
4
+ let leaseOwnership = "retained";
5
+ const rollback = () => {
6
+ open = false;
7
+ database.exec("ROLLBACK");
8
+ };
9
+ return {
10
+ get leaseOwnership() { return leaseOwnership; },
11
+ commit() {
12
+ if (!open)
13
+ throw new Error("connection-bound runner close is not open");
14
+ try {
15
+ lease.close();
16
+ }
17
+ catch (error) {
18
+ leaseOwnership = "unknown";
19
+ rollback();
20
+ throw error;
21
+ }
22
+ leaseOwnership = "consumed";
23
+ try {
24
+ database.exec("COMMIT");
25
+ open = false;
26
+ }
27
+ catch (error) {
28
+ rollback();
29
+ throw error;
30
+ }
31
+ },
32
+ rollback() {
33
+ if (leaseOwnership !== "retained") {
34
+ throw new Error(`connection-bound runner lease ownership is ${leaseOwnership}`);
35
+ }
36
+ if (!open)
37
+ throw new Error("connection-bound runner close is no longer open");
38
+ rollback();
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,208 @@
1
+ import { launchDetachedProcess } from "./detached-process.js";
2
+ import { terminateProcessGroup } from "./process-tree.js";
3
+ import { acquireRunnerLeaseTransferTarget, observeRunnerLease, } from "./runner-lease.js";
4
+ export const RUN_CONTROL_BOOTSTRAP_ENV = "KEIYAKU_RUN_CONTROL_BOOTSTRAP";
5
+ function detail(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
8
+ function send(child, message) {
9
+ return new Promise((resolve, reject) => {
10
+ if (!child.send || !child.connected)
11
+ return reject(new Error("bootstrap channel is unavailable"));
12
+ child.send(message, (error) => error ? reject(error) : resolve());
13
+ });
14
+ }
15
+ function waitForMessage(child, coordinate, expected, deadline, now, setTimer, clearTimer) {
16
+ return new Promise((resolve, reject) => {
17
+ const cleanup = () => {
18
+ child.off("message", onMessage);
19
+ child.off("error", onError);
20
+ child.off("exit", onExit);
21
+ clearTimer(timer);
22
+ };
23
+ const onMessage = (value) => {
24
+ const message = value;
25
+ if (message.coordinate !== coordinate || message.kind !== expected)
26
+ return;
27
+ cleanup();
28
+ resolve();
29
+ };
30
+ const onError = (error) => { cleanup(); reject(error); };
31
+ const onExit = () => onError(new Error("child exited during bootstrap"));
32
+ const remaining = deadline - now();
33
+ const timer = setTimer(() => onError(new Error(`bootstrap deadline passed before ${expected}`)), Math.max(0, remaining));
34
+ child.on("message", onMessage);
35
+ child.on("error", onError);
36
+ child.on("exit", onExit);
37
+ });
38
+ }
39
+ async function stop(child, prefix, terminate) {
40
+ try {
41
+ await terminate(child);
42
+ return { status: "failed", stage: "transfer", detail: prefix };
43
+ }
44
+ catch (error) {
45
+ return { status: "failed", stage: "cleanup", detail: `${prefix}; termination could not be verified: ${detail(error)}` };
46
+ }
47
+ }
48
+ export async function openDetachedLeaseBootstrap(input) {
49
+ const terminate = input.terminate ?? terminateProcessGroup;
50
+ const now = input.now ?? Date.now;
51
+ let child;
52
+ try {
53
+ child = launchDetachedProcess(input.launch, input.spawn);
54
+ }
55
+ catch (error) {
56
+ try {
57
+ input.parentLease.close();
58
+ return { status: "failed", stage: "spawn", detail: detail(error) };
59
+ }
60
+ catch (closeError) {
61
+ return { status: "failed", stage: "cleanup", detail: `${detail(error)}; parent lease release failed: ${detail(closeError)}` };
62
+ }
63
+ }
64
+ const ready = waitForMessage(child, input.coordinate, "transfer-ready", input.deadline, now, setTimeout, clearTimeout);
65
+ const acquired = waitForMessage(child, input.coordinate, "ownership-acquired", input.deadline, now, setTimeout, clearTimeout);
66
+ void acquired.catch(() => { });
67
+ try {
68
+ await ready;
69
+ }
70
+ catch (error) {
71
+ let failure = detail(error);
72
+ try {
73
+ input.parentLease.close();
74
+ }
75
+ catch (closeError) {
76
+ failure += `; parent lease release failed: ${detail(closeError)}`;
77
+ }
78
+ return stop(child, failure, terminate);
79
+ }
80
+ let decision;
81
+ let result;
82
+ return {
83
+ status: "ready",
84
+ bootstrap: {
85
+ child,
86
+ async authorize() {
87
+ if (decision === "reject")
88
+ return { status: "failed", stage: "transfer", detail: "bootstrap was already rejected" };
89
+ if (decision)
90
+ return result;
91
+ decision = "authorize";
92
+ result = (async () => {
93
+ let transferFailure;
94
+ let transferMayBeOwnedByChild = false;
95
+ try {
96
+ input.parentLease.releaseReservationForTransfer();
97
+ await send(child, { kind: "promote", coordinate: input.coordinate });
98
+ transferMayBeOwnedByChild = true;
99
+ try {
100
+ input.parentLease.close();
101
+ }
102
+ catch (error) {
103
+ transferFailure = error;
104
+ }
105
+ try {
106
+ await acquired;
107
+ }
108
+ catch (error) {
109
+ transferFailure ??= error;
110
+ }
111
+ if (transferFailure && observeRunnerLease(input.leasePath).state !== "held")
112
+ throw transferFailure;
113
+ await send(child, { kind: "authorize", coordinate: input.coordinate });
114
+ return { status: "authorized", child };
115
+ }
116
+ catch (error) {
117
+ try {
118
+ input.parentLease.close();
119
+ }
120
+ catch { /* cleanup detail is secondary to transfer failure */ }
121
+ if (transferMayBeOwnedByChild && observeRunnerLease(input.leasePath).state === "held") {
122
+ try {
123
+ await send(child, { kind: "authorize", coordinate: input.coordinate });
124
+ }
125
+ catch { /* held lease already proves authority */ }
126
+ return { status: "authorized", child };
127
+ }
128
+ return stop(child, detail(error), terminate);
129
+ }
130
+ })();
131
+ return result;
132
+ },
133
+ async reject() {
134
+ if (decision === "authorize")
135
+ return { status: "failed", stage: "transfer", detail: "bootstrap was already authorized" };
136
+ if (decision)
137
+ return result;
138
+ decision = "reject";
139
+ result = (async () => {
140
+ try {
141
+ await send(child, { kind: "reject", coordinate: input.coordinate });
142
+ }
143
+ catch { /* termination proves rejection */ }
144
+ let failure = "launch rejected";
145
+ try {
146
+ input.parentLease.close();
147
+ }
148
+ catch (error) {
149
+ failure += `; parent lease release failed: ${detail(error)}`;
150
+ }
151
+ const stopped = await stop(child, failure, terminate);
152
+ return stopped.stage === "cleanup" ? stopped : { status: "rejected" };
153
+ })();
154
+ return result;
155
+ },
156
+ },
157
+ };
158
+ }
159
+ function sendUpstream(message) {
160
+ return new Promise((resolve, reject) => {
161
+ if (!process.send || !process.connected)
162
+ return reject(new Error("bootstrap channel is unavailable"));
163
+ process.send(message, (error) => error ? reject(error) : resolve());
164
+ });
165
+ }
166
+ function receive(coordinate, expected) {
167
+ return new Promise((resolve, reject) => {
168
+ const cleanup = () => {
169
+ process.off("message", onMessage);
170
+ process.off("disconnect", onDisconnect);
171
+ };
172
+ const onDisconnect = () => { cleanup(); reject(new Error("bootstrap channel disconnected")); };
173
+ const onMessage = (value) => {
174
+ const message = value;
175
+ if (message.coordinate !== coordinate || (message.kind !== expected && message.kind !== "reject"))
176
+ return;
177
+ cleanup();
178
+ resolve(message);
179
+ };
180
+ process.on("message", onMessage);
181
+ process.on("disconnect", onDisconnect);
182
+ });
183
+ }
184
+ export async function receiveDetachedLease(input) {
185
+ if (process.env[RUN_CONTROL_BOOTSTRAP_ENV] !== "1")
186
+ throw new Error("process lacks bootstrap authority");
187
+ let target;
188
+ let lease;
189
+ try {
190
+ target = acquireRunnerLeaseTransferTarget(input.leasePath);
191
+ await sendUpstream({ kind: "transfer-ready", coordinate: input.coordinate });
192
+ if ((await receive(input.coordinate, "promote")).kind === "reject")
193
+ return { status: "rejected" };
194
+ lease = target.promote();
195
+ target = undefined;
196
+ await sendUpstream({ kind: "ownership-acquired", coordinate: input.coordinate });
197
+ if ((await receive(input.coordinate, "authorize")).kind === "reject")
198
+ return { status: "rejected" };
199
+ process.disconnect?.();
200
+ const owned = lease;
201
+ lease = undefined;
202
+ return { status: "authorized", lease: owned };
203
+ }
204
+ finally {
205
+ lease?.close();
206
+ target?.close();
207
+ }
208
+ }
@@ -0,0 +1,14 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ /** Launches a process in its own POSIX process group when the platform supports it. */
3
+ export function launchDetachedProcess(input, spawnProcess = nodeSpawn) {
4
+ const child = spawnProcess(input.command, input.args, {
5
+ ...input.options,
6
+ detached: process.platform !== "win32",
7
+ });
8
+ if (child.pid === undefined) {
9
+ throw new Error("detached process did not expose a process id");
10
+ }
11
+ if (input.unref)
12
+ child.unref();
13
+ return child;
14
+ }
@@ -0,0 +1,6 @@
1
+ export { PROCESS_GROUP_KILL_GRACE_MS, ProcessTreeTerminationError, ownedProcessIdentity, ownedProcessIsLive, terminateOwnedProcess, terminateProcessGroup, } from "./process-tree.js";
2
+ export { launchDetachedProcess, } from "./detached-process.js";
3
+ export { openDetachedLeaseBootstrap, receiveDetachedLease, RUN_CONTROL_BOOTSTRAP_ENV, } from "./detached-lease-bootstrap.js";
4
+ export { initializeRunnerLeaseDatabase, acquireRunnerLease, acquireRunnerLeaseTransferTarget, acquireRunnerLostProof, observeRunnerLease, RunnerLeaseUnavailableError, } from "./runner-lease.js";
5
+ export { beginConnectionBoundRunnerClose, } from "./connection-bound-close.js";
6
+ export { acquireSqliteProcessLifetimeLock, initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "./sqlite-process-lifetime-lock.js";
@@ -88,7 +88,7 @@ async function terminateIdentity(identity, options) {
88
88
  throw new Error(`owned process group ${identity.pid} remained after SIGKILL`);
89
89
  }
90
90
  }
91
- /** Terminate an orphaned runner identity retained by repository coordination. */
91
+ /** Terminate an orphaned process identity retained by caller-owned coordination. */
92
92
  export async function terminateOwnedProcess(identity, options = {}) {
93
93
  try {
94
94
  await terminateIdentity(identity, {