@astrosheep/keiyaku 4.0.3 → 4.0.5

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 (238) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +4 -4
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +15 -1
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +34 -13
  9. package/build/src/akuma/akuma.d.ts +34 -12
  10. package/build/src/akuma/akuma.js +118 -63
  11. package/build/src/akuma/allowed.d.ts +7 -0
  12. package/build/src/akuma/allowed.js +33 -0
  13. package/build/src/akuma/archetype.d.ts +7 -4
  14. package/build/src/akuma/archetype.js +15 -16
  15. package/build/src/akuma/body.d.ts +6 -1
  16. package/build/src/akuma/body.js +36 -27
  17. package/build/src/akuma/heart/facts.d.ts +82 -14
  18. package/build/src/akuma/heart/index.d.ts +5 -18
  19. package/build/src/akuma/heart/index.js +14 -84
  20. package/build/src/akuma/heart/request-authority.d.ts +12 -0
  21. package/build/src/akuma/heart/request-authority.js +119 -0
  22. package/build/src/akuma/heart/request-rows.d.ts +16 -0
  23. package/build/src/akuma/heart/request-rows.js +88 -0
  24. package/build/src/akuma/heart/rows.d.ts +1 -26
  25. package/build/src/akuma/heart/rows.js +0 -50
  26. package/build/src/akuma/heart/schema.d.ts +1 -1
  27. package/build/src/akuma/heart/schema.js +22 -10
  28. package/build/src/akuma/heart/soul.js +2 -36
  29. package/build/src/akuma/heart/storage.d.ts +1 -0
  30. package/build/src/akuma/heart/storage.js +15 -0
  31. package/build/src/akuma/heart/timeline.d.ts +1 -5
  32. package/build/src/akuma/heart/timeline.js +8 -17
  33. package/build/src/akuma/index.d.ts +5 -2
  34. package/build/src/akuma/index.js +1 -0
  35. package/build/src/akuma/projection.d.ts +17 -3
  36. package/build/src/akuma/projection.js +40 -3
  37. package/build/src/akuma/provider-recipe.js +1 -11
  38. package/build/src/akuma/provider.d.ts +2 -7
  39. package/build/src/akuma/provider.js +2 -1
  40. package/build/src/akuma/providers/acp/core.d.ts +3 -1
  41. package/build/src/akuma/providers/acp/core.js +10 -6
  42. package/build/src/akuma/providers/acp/events.d.ts +11 -3
  43. package/build/src/akuma/providers/acp/events.js +78 -4
  44. package/build/src/akuma/providers/acp/index.js +0 -1
  45. package/build/src/akuma/providers/claude/events.js +62 -3
  46. package/build/src/akuma/providers/claude/index.d.ts +0 -4
  47. package/build/src/akuma/providers/claude/index.js +7 -3
  48. package/build/src/akuma/providers/codex-app-server/events.js +1 -13
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +2 -27
  50. package/build/src/akuma/providers/codex-app-server/index.js +0 -2
  51. package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
  52. package/build/src/akuma/providers/grok-build/index.js +72 -3
  53. package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
  54. package/build/src/akuma/providers/opencode-sdk/index.d.ts +2 -28
  55. package/build/src/akuma/providers/opencode-sdk/index.js +8 -4
  56. package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
  57. package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
  58. package/build/src/akuma/providers/pi/events.js +17 -3
  59. package/build/src/akuma/providers/pi/index.js +11 -4
  60. package/build/src/akuma/providers/unified-patch.d.ts +4 -0
  61. package/build/src/akuma/providers/unified-patch.js +14 -0
  62. package/build/src/akuma/publication.d.ts +1 -1
  63. package/build/src/akuma/publication.js +1 -1
  64. package/build/src/akuma/request-serve.d.ts +87 -0
  65. package/build/src/akuma/request-serve.js +484 -0
  66. package/build/src/akuma/request-wire.d.ts +124 -0
  67. package/build/src/akuma/request-wire.js +380 -0
  68. package/build/src/akuma/requests.d.ts +41 -41
  69. package/build/src/akuma/requests.js +75 -374
  70. package/build/src/akuma-body.d.ts +8 -0
  71. package/build/src/akuma-body.js +84 -0
  72. package/build/src/body/arc.js +4 -29
  73. package/build/src/body/decode.js +5 -39
  74. package/build/src/body/envelope.d.ts +7 -0
  75. package/build/src/body/envelope.js +37 -0
  76. package/build/src/cli/accepted.d.ts +4 -1
  77. package/build/src/cli/accepted.js +39 -9
  78. package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
  79. package/build/src/cli/commands/akuma-invoke.js +18 -10
  80. package/build/src/cli/commands/akuma.d.ts +11 -2
  81. package/build/src/cli/commands/akuma.js +67 -28
  82. package/build/src/cli/commands/amend.d.ts +1 -1
  83. package/build/src/cli/commands/amend.js +1 -1
  84. package/build/src/cli/commands/contract.d.ts +8 -8
  85. package/build/src/cli/commands/contract.js +3 -3
  86. package/build/src/cli/commands/install.js +2 -2
  87. package/build/src/cli/commands/task-invoke.js +98 -9
  88. package/build/src/cli/commands/task-query.d.ts +1 -1
  89. package/build/src/cli/commands/task-query.js +11 -5
  90. package/build/src/cli/coordinates.d.ts +2 -0
  91. package/build/src/cli/coordinates.js +12 -5
  92. package/build/src/cli/invoke.d.ts +1 -0
  93. package/build/src/cli/invoke.js +116 -75
  94. package/build/src/cli/main.d.ts +2 -0
  95. package/build/src/cli/main.js +29 -4
  96. package/build/src/cli/parse.d.ts +3 -2
  97. package/build/src/cli/parse.js +25 -8
  98. package/build/src/cli/render/akuma.d.ts +2 -2
  99. package/build/src/cli/render/akuma.js +138 -24
  100. package/build/src/cli/render/audit.js +18 -8
  101. package/build/src/cli/render/contract.d.ts +2 -0
  102. package/build/src/cli/render/contract.js +243 -36
  103. package/build/src/cli/render/kanshi.js +64 -29
  104. package/build/src/cli/render/receipt.d.ts +2 -1
  105. package/build/src/cli/render/receipt.js +37 -2
  106. package/build/src/cli/render/terminal.d.ts +0 -2
  107. package/build/src/cli/render/terminal.js +0 -29
  108. package/build/src/cli/render/text.js +3 -1
  109. package/build/src/cli/result.d.ts +52 -9
  110. package/build/src/cli/selectors.d.ts +1 -0
  111. package/build/src/cli/selectors.js +9 -6
  112. package/build/src/contract-worktree.d.ts +1 -2
  113. package/build/src/contract-worktree.js +7 -9
  114. package/build/src/core/facts/codec.js +9 -0
  115. package/build/src/core/facts/fold.js +13 -1
  116. package/build/src/core/facts/types.d.ts +8 -1
  117. package/build/src/core/subject.js +4 -3
  118. package/build/src/core/verbs/placement.d.ts +9 -1
  119. package/build/src/core/verbs/placement.js +24 -6
  120. package/build/src/core/verbs/reintegrate.d.ts +13 -0
  121. package/build/src/core/verbs/reintegrate.js +24 -0
  122. package/build/src/dispatch/index.d.ts +1 -1
  123. package/build/src/dispatch/index.js +50 -39
  124. package/build/src/git/admission.d.ts +2 -1
  125. package/build/src/git/hooks.d.ts +0 -4
  126. package/build/src/git/hooks.js +0 -36
  127. package/build/src/git/integration.d.ts +7 -8
  128. package/build/src/git/integration.js +62 -9
  129. package/build/src/git/observe.d.ts +2 -1
  130. package/build/src/git/observe.js +6 -5
  131. package/build/src/git/process.d.ts +26 -0
  132. package/build/src/git/process.js +94 -0
  133. package/build/src/git/read-observation.d.ts +2 -1
  134. package/build/src/git/read-observation.js +3 -2
  135. package/build/src/git/reconcile.d.ts +8 -2
  136. package/build/src/git/reconcile.js +63 -14
  137. package/build/src/git/repository.d.ts +2 -29
  138. package/build/src/git/repository.js +4 -118
  139. package/build/src/git/scratch.d.ts +1 -1
  140. package/build/src/git/scratch.js +1 -1
  141. package/build/src/git/target-placement.d.ts +1 -1
  142. package/build/src/git/target-placement.js +9 -7
  143. package/build/src/git/tender.d.ts +20 -5
  144. package/build/src/git/tender.js +45 -13
  145. package/build/src/git/terminal-seal.d.ts +16 -2
  146. package/build/src/git/terminal-seal.js +15 -5
  147. package/build/src/git/workspace.d.ts +2 -1
  148. package/build/src/git/workspace.js +13 -5
  149. package/build/src/index.d.ts +1 -2
  150. package/build/src/index.js +1 -1
  151. package/build/src/kanshi/index.d.ts +1 -1
  152. package/build/src/kanshi/index.js +1 -1
  153. package/build/src/kanshi/read.d.ts +9 -1
  154. package/build/src/kanshi/read.js +91 -37
  155. package/build/src/kanshi/report.d.ts +1 -0
  156. package/build/src/library/address.d.ts +5 -14
  157. package/build/src/library/address.js +27 -23
  158. package/build/src/library/akuma-creation.d.ts +4 -2
  159. package/build/src/library/akuma-creation.js +43 -26
  160. package/build/src/library/audit.d.ts +2 -1
  161. package/build/src/library/audit.js +1 -1
  162. package/build/src/library/bind.d.ts +2 -1
  163. package/build/src/library/bind.js +4 -4
  164. package/build/src/library/catalog.d.ts +1 -2
  165. package/build/src/library/catalog.js +11 -6
  166. package/build/src/library/configuration.d.ts +5 -1
  167. package/build/src/library/configuration.js +71 -18
  168. package/build/src/library/contract.d.ts +70 -3
  169. package/build/src/library/contract.js +213 -27
  170. package/build/src/library/delivery.d.ts +1 -1
  171. package/build/src/library/fleet.d.ts +70 -12
  172. package/build/src/library/fleet.js +236 -56
  173. package/build/src/library/input.d.ts +0 -1
  174. package/build/src/library/input.js +1 -5
  175. package/build/src/library/keiyaku.d.ts +4 -4
  176. package/build/src/library/keiyaku.js +1 -2
  177. package/build/src/library/mutation.d.ts +2 -1
  178. package/build/src/library/reconcile.d.ts +2 -1
  179. package/build/src/library/reconcile.js +3 -1
  180. package/build/src/library/region.d.ts +1 -1
  181. package/build/src/library/repo.d.ts +3 -1
  182. package/build/src/library/repo.js +8 -4
  183. package/build/src/protocol/abandon.d.ts +9 -0
  184. package/build/src/protocol/abandon.js +15 -0
  185. package/build/src/protocol/amend.d.ts +16 -0
  186. package/build/src/protocol/amend.js +77 -0
  187. package/build/src/protocol/arc.d.ts +6 -0
  188. package/build/src/protocol/arc.js +12 -0
  189. package/build/src/protocol/attempt.d.ts +2 -1
  190. package/build/src/protocol/audit.d.ts +53 -0
  191. package/build/src/protocol/audit.js +140 -0
  192. package/build/src/protocol/bind.d.ts +2 -3
  193. package/build/src/protocol/bind.js +2 -16
  194. package/build/src/protocol/completion.d.ts +32 -0
  195. package/build/src/protocol/completion.js +130 -0
  196. package/build/src/protocol/deliver.d.ts +37 -0
  197. package/build/src/protocol/deliver.js +160 -0
  198. package/build/src/protocol/intent.d.ts +1 -1
  199. package/build/src/protocol/intent.js +1 -1
  200. package/build/src/protocol/operations.d.ts +65 -198
  201. package/build/src/protocol/operations.js +21 -634
  202. package/build/src/protocol/placement.d.ts +4 -13
  203. package/build/src/protocol/placement.js +3 -15
  204. package/build/src/protocol/read/status.d.ts +4 -2
  205. package/build/src/protocol/read/status.js +27 -15
  206. package/build/src/protocol/reconcile.d.ts +35 -0
  207. package/build/src/protocol/reconcile.js +41 -0
  208. package/build/src/protocol/reintegrate.d.ts +31 -0
  209. package/build/src/protocol/reintegrate.js +74 -0
  210. package/build/src/protocol/review.d.ts +33 -0
  211. package/build/src/protocol/review.js +148 -0
  212. package/build/src/protocol/run.d.ts +1 -1
  213. package/build/src/runtime/proc/run.d.ts +1 -0
  214. package/build/src/runtime/proc/run.js +9 -2
  215. package/build/src/settlement/fence.d.ts +1 -1
  216. package/build/src/settlement/holder.d.ts +1 -1
  217. package/build/src/settlement/settle.d.ts +2 -1
  218. package/build/src/settlement/settle.js +4 -1
  219. package/build/src/task/board.d.ts +18 -4
  220. package/build/src/task/board.js +73 -29
  221. package/build/src/task/compose.d.ts +1 -1
  222. package/build/src/task/compose.js +11 -4
  223. package/build/src/task/index.d.ts +4 -4
  224. package/build/src/task/index.js +8 -131
  225. package/build/src/task/input.d.ts +18 -0
  226. package/build/src/task/input.js +189 -0
  227. package/build/src/task/mutation.d.ts +39 -0
  228. package/build/src/task/mutation.js +85 -0
  229. package/build/src/task/operations.d.ts +4 -19
  230. package/build/src/task/operations.js +64 -18
  231. package/build/src/task/query.d.ts +9 -2
  232. package/build/src/task/query.js +32 -64
  233. package/build/src/task/store.js +0 -2
  234. package/build/src/verification/execution.d.ts +1 -1
  235. package/build/src/verification/execution.js +2 -1
  236. package/build/src/workspace-place.d.ts +1 -4
  237. package/build/src/workspace-place.js +0 -15
  238. package/package.json +5 -3
@@ -1,16 +1,34 @@
1
1
  import { gatesSatisfied } from "../facts/gate.js";
2
- import { activeContract, prerequisiteStatus } from "../facts/observation.js";
2
+ import { activeContract, contractState } from "../facts/observation.js";
3
+ function unmetPrerequisites(prerequisites, observation) {
4
+ const unmet = [];
5
+ for (const contractId of prerequisites) {
6
+ const state = contractState(observation, contractId);
7
+ if (state === null) {
8
+ unmet.push({ contractId, state: "missing" });
9
+ }
10
+ else if (state.terminal?.kind === "abandoned") {
11
+ unmet.push({ contractId, state: "abandoned" });
12
+ }
13
+ else if (state.terminal?.kind !== "claimed") {
14
+ unmet.push({ contractId, state: "active" });
15
+ }
16
+ }
17
+ return unmet;
18
+ }
3
19
  export function decidePlacement({ input, attempt, observation }) {
4
20
  const id = input.contractId;
5
21
  const current = activeContract(observation, id);
6
22
  if ("kind" in current)
7
23
  return { kind: "refused", refusal: current };
8
24
  const delivery = current.delivery;
9
- if (!delivery) {
25
+ const integration = current.currentIntegration;
26
+ if (!delivery || !integration) {
10
27
  return { kind: "refused", refusal: { kind: "delivery-missing", contractId: id } };
11
28
  }
12
- if (prerequisiteStatus(current.terms.after, observation) !== "claimed") {
13
- return { kind: "refused", refusal: { kind: "prerequisites-unsatisfied", contractId: id } };
29
+ const unmet = unmetPrerequisites(current.terms.after, observation);
30
+ if (unmet.length > 0) {
31
+ return { kind: "refused", refusal: { kind: "prerequisites-unsatisfied", contractId: id, unmet } };
14
32
  }
15
33
  if (!gatesSatisfied(current)) {
16
34
  return { kind: "refused", refusal: { kind: "gates-unsatisfied", contractId: id } };
@@ -31,8 +49,8 @@ export function decidePlacement({ input, attempt, observation }) {
31
49
  ...(current.coordinates.target === undefined ? {} : {
32
50
  target: {
33
51
  target: current.coordinates.target,
34
- expectedOid: delivery.data.integration.predecessor,
35
- newOid: delivery.data.integration.snapshot,
52
+ expectedOid: integration.predecessor,
53
+ newOid: integration.snapshot,
36
54
  },
37
55
  }),
38
56
  },
@@ -0,0 +1,13 @@
1
+ import type { DecideInput, OfferDecision, Preparation } from "../decide.js";
2
+ import type { ActorId, ContractId, ReintegratedData } from "../facts/types.js";
3
+ export type ReintegrateInput<Failure = never> = Readonly<{
4
+ contractId: ContractId;
5
+ actor?: ActorId;
6
+ at: string;
7
+ preparation: Preparation<ReintegratedData, Failure>;
8
+ }>;
9
+ export type ReintegrateRefusal = Readonly<{
10
+ kind: "contract-missing" | "delivery-missing" | "terminal";
11
+ contractId: ContractId;
12
+ }>;
13
+ export declare function decideReintegrate<Failure>({ input, attempt, observation, }: DecideInput<ReintegrateInput<Failure>>): OfferDecision<ReintegrateRefusal | Failure>;
@@ -0,0 +1,24 @@
1
+ import { activeContract } from "../facts/observation.js";
2
+ export function decideReintegrate({ input, attempt, observation, }) {
3
+ const state = activeContract(observation, input.contractId);
4
+ if ("kind" in state)
5
+ return { kind: "refused", refusal: state };
6
+ if (state.delivery === null) {
7
+ return { kind: "refused", refusal: { kind: "delivery-missing", contractId: input.contractId } };
8
+ }
9
+ if (input.preparation.kind === "refused")
10
+ return { kind: "refused", refusal: input.preparation.refusal };
11
+ const reintegrated = {
12
+ v: 1,
13
+ kind: "reintegrated",
14
+ contract: input.contractId,
15
+ entry: attempt.entryUlids[0],
16
+ at: input.at,
17
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
18
+ data: input.preparation.data,
19
+ };
20
+ return {
21
+ kind: "offer",
22
+ offer: { facts: [{ contractId: input.contractId, expectedHead: state.head, entries: [reintegrated] }] },
23
+ };
24
+ }
@@ -1,6 +1,6 @@
1
1
  import { type ContractId } from "../core/facts/types.js";
2
2
  import { type AkuId } from "../akuma/identity.js";
3
- import { type GitRepository } from "../git/repository.js";
3
+ import type { GitRepository } from "../git/process.js";
4
4
  import { type GitReadObservation } from "../git/read-observation.js";
5
5
  export type Dispatch = Readonly<{
6
6
  akuId: AkuId;
@@ -1,7 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
2
3
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
4
  import { contractId } from "../core/facts/types.js";
4
5
  import { parseAkuId } from "../akuma/identity.js";
6
+ import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
5
7
  import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readBlob, readGitPaths, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
6
8
  import { withGitDecodeChannel, withGitReadObservation } from "../git/read-observation.js";
7
9
  const DISPATCH_ROOT = "dispatch";
@@ -123,46 +125,55 @@ export async function publishDispatch(input) {
123
125
  const owner = contractId(input.contractId);
124
126
  const intended = { akuId, contractId: owner, dispatchedAt: new Date().toISOString() };
125
127
  const path = pathFor(akuId);
126
- for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
127
- const snapshot = await readGitPaths(input.repository, [path]);
128
- const current = await dispatchFromSnapshot(input.repository, snapshot, akuId);
129
- if (current !== null) {
130
- return current.contractId === owner
131
- ? { kind: "dispatched", dispatch: current }
132
- : { kind: "failed", failure: { kind: "conflict", current } };
133
- }
134
- const changes = new Map();
135
- if (snapshot.commit === null) {
136
- changes.set(GIT_FORMAT_PATH, { oid: await writeBlob(input.repository, GIT_FORMAT_BYTES) });
137
- }
138
- changes.set(path, { oid: await writeBlob(input.repository, bytesFor(intended)) });
139
- const tree = await updateGitTree(input.repository, snapshot.tree, changes);
140
- const commit = await writeCommit({
141
- repository: input.repository,
142
- tree,
143
- parent: snapshot.commit,
144
- message: `dispatch ${akuId}`,
145
- at: intended.dispatchedAt,
146
- });
147
- const publication = await updateRefsAtomically(input.repository, [{
148
- ref: GIT_REF,
149
- newOid: commit,
150
- expectedOid: snapshot.commit,
151
- }]);
152
- if (publication.kind === "published")
153
- return { kind: "dispatched", dispatch: intended };
154
- const observed = await observedPublication(input.repository, akuId, owner);
155
- if (observed !== null)
156
- return observed;
157
- if (publication.kind === "non-published") {
158
- const fresh = await readGitPaths(input.repository, [path]);
159
- if (fresh.commit === snapshot.commit) {
160
- return {
161
- kind: "failed",
162
- failure: { kind: "publication-failed", diagnostic: diagnostic(publication.error) },
163
- };
128
+ const held = await acquireSqliteTransactionLock({
129
+ path: join(input.repository.commonDirectory, "keiyaku", "locks", "dispatch.sqlite"),
130
+ mode: "immediate",
131
+ });
132
+ try {
133
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
134
+ const snapshot = await readGitPaths(input.repository, [path]);
135
+ const current = await dispatchFromSnapshot(input.repository, snapshot, akuId);
136
+ if (current !== null) {
137
+ return current.contractId === owner
138
+ ? { kind: "dispatched", dispatch: current }
139
+ : { kind: "failed", failure: { kind: "conflict", current } };
140
+ }
141
+ const changes = new Map();
142
+ if (snapshot.commit === null) {
143
+ changes.set(GIT_FORMAT_PATH, { oid: await writeBlob(input.repository, GIT_FORMAT_BYTES) });
144
+ }
145
+ changes.set(path, { oid: await writeBlob(input.repository, bytesFor(intended)) });
146
+ const tree = await updateGitTree(input.repository, snapshot.tree, changes);
147
+ const commit = await writeCommit({
148
+ repository: input.repository,
149
+ tree,
150
+ parent: snapshot.commit,
151
+ message: `dispatch ${akuId}`,
152
+ at: intended.dispatchedAt,
153
+ });
154
+ const publication = await updateRefsAtomically(input.repository, [{
155
+ ref: GIT_REF,
156
+ newOid: commit,
157
+ expectedOid: snapshot.commit,
158
+ }]);
159
+ if (publication.kind === "published")
160
+ return { kind: "dispatched", dispatch: intended };
161
+ const observed = await observedPublication(input.repository, akuId, owner);
162
+ if (observed !== null)
163
+ return observed;
164
+ if (publication.kind === "non-published") {
165
+ const fresh = await readGitPaths(input.repository, [path]);
166
+ if (fresh.commit === snapshot.commit) {
167
+ return {
168
+ kind: "failed",
169
+ failure: { kind: "publication-failed", diagnostic: diagnostic(publication.error) },
170
+ };
171
+ }
164
172
  }
165
173
  }
174
+ return { kind: "failed", failure: { kind: "contention" } };
175
+ }
176
+ finally {
177
+ held.close();
166
178
  }
167
- return { kind: "failed", failure: { kind: "contention" } };
168
179
  }
@@ -1,6 +1,7 @@
1
1
  import type { Offer } from "../core/facts/offer.js";
2
2
  import type { GitAdmissionSnapshot } from "./observe.js";
3
- import { type GitRepository, type GitRefAssertion } from "./repository.js";
3
+ import { type GitRefAssertion } from "./repository.js";
4
+ import type { GitRepository } from "./process.js";
4
5
  import { type ContractHead } from "../core/facts/types.js";
5
6
  type Accepted = Readonly<{
6
7
  kind: "accepted";
@@ -1,4 +1,3 @@
1
- import { type Settings } from "../settings.js";
2
1
  export type HookCommand = Readonly<{
3
2
  argv: readonly string[];
4
3
  timeoutMs: number;
@@ -29,9 +28,6 @@ export type WorktreeHookLag = Readonly<{
29
28
  failure: HookFailure;
30
29
  }>;
31
30
  type HookPhase = "create" | "destroy";
32
- export declare function worktreeHooksFrom(input: Readonly<{
33
- settings: Settings;
34
- }>): WorktreeHooks;
35
31
  export declare function hookMarkerPath(administrationDirectory: string): string;
36
32
  export type HookCommandRun = Readonly<{
37
33
  kind: "ok";
@@ -4,46 +4,10 @@ import { fileURLToPath } from "node:url";
4
4
  import { replaceFileDurably } from "../coordination/durable-file.js";
5
5
  import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
6
6
  import { runProcess, runProcessToExit } from "../runtime/proc/run.js";
7
- import { SettingsError } from "../settings.js";
8
7
  const MARKER_VERSION = 1;
9
8
  function object(value) {
10
9
  return value !== null && typeof value === "object" && !Array.isArray(value);
11
10
  }
12
- export function worktreeHooksFrom(input) {
13
- if (!object(input))
14
- throw new TypeError("worktreeHooksFrom input must be an object");
15
- const view = input.settings.namespace("worktree");
16
- if (view.kind === "failed") {
17
- throw new SettingsError(view.failures.map((failure) => `${failure.scope}: ${failure.diagnostic}`).join("; "));
18
- }
19
- for (const entry of view.entries) {
20
- if (entry.name !== "create" && entry.name !== "destroy")
21
- throw new SettingsError(`worktree has unknown entry: ${entry.name}`);
22
- }
23
- const commands = (phase) => {
24
- const selected = view.entries.find((entry) => entry.name === phase);
25
- if (selected === undefined)
26
- return Object.freeze([]);
27
- if (!Array.isArray(selected.value))
28
- throw new SettingsError(`worktree.${phase} must be an array`);
29
- return Object.freeze(selected.value.map((item, index) => {
30
- if (!object(item))
31
- throw new SettingsError(`worktree.${phase}[${index}] must be an object`);
32
- const coordinate = `worktree.${phase}[${index}]`;
33
- exactKeys(item, ["argv", "timeoutMs"], coordinate);
34
- if (!Array.isArray(item.argv) || item.argv.length === 0 || !item.argv.every((value) => typeof value === "string")) {
35
- throw new SettingsError(`${coordinate}.argv must be a nonempty string array`);
36
- }
37
- if (item.argv[0].trim().length === 0)
38
- throw new SettingsError(`${coordinate}.argv[0] must be nonblank`);
39
- if (!Number.isSafeInteger(item.timeoutMs) || item.timeoutMs < 1 || item.timeoutMs > 2_147_483_647) {
40
- throw new SettingsError(`${coordinate}.timeoutMs must be an integer from 1 through 2147483647`);
41
- }
42
- return Object.freeze({ argv: Object.freeze([...item.argv]), timeoutMs: item.timeoutMs });
43
- }));
44
- };
45
- return Object.freeze({ create: commands("create"), destroy: commands("destroy") });
46
- }
47
11
  function exactKeys(value, keys, coordinate) {
48
12
  const actual = Object.keys(value).sort();
49
13
  const expected = [...keys].sort();
@@ -1,8 +1,9 @@
1
1
  import type { Preparation } from "../core/decide.js";
2
2
  import type { ChangeId, ContractCoordinates, ContractId, SnapshotId } from "../core/facts/types.js";
3
3
  import { type GitObjectId } from "./identity.js";
4
- import { type GitRepository } from "./repository.js";
5
- import type { TenderCapture } from "./tender.js";
4
+ import { type GitRepository } from "./process.js";
5
+ import type { DeliveryCommitMetadata, TenderCapture } from "./tender.js";
6
+ type IntegrationTender = Pick<TenderCapture, "head" | "tree">;
6
7
  declare const REQUIRED_GIT: "2.38";
7
8
  export type IntegrationPreparationRefusal = Readonly<{
8
9
  kind: "integration-failed";
@@ -26,12 +27,10 @@ export type IntegrationPlan = Readonly<{
26
27
  /** Mint the one worktree-content identity shared by review and delivery. */
27
28
  export declare function worktreeChangeId(repository: GitRepository, input: IntegrationCoordinates, tender: TenderCapture): Promise<ChangeId>;
28
29
  /** Materialize the exact integration tree against its observed predecessor. */
29
- export declare function materializeIntegrationSnapshot(repository: GitRepository, tree: GitObjectId, parent: SnapshotId, input: Readonly<{
30
- contractId: ContractId;
31
- title: string;
32
- message?: string;
33
- }>): Promise<SnapshotId>;
34
- export declare function planIntegration(repository: GitRepository, input: IntegrationCoordinates, tender: TenderCapture, requireBranchesToBeUpToDate: boolean): Promise<Preparation<IntegrationPlan, Readonly<{
30
+ export declare function materializeIntegrationSnapshot(repository: GitRepository, tree: GitObjectId, parent: SnapshotId, metadata: DeliveryCommitMetadata): Promise<SnapshotId>;
31
+ export declare function persistedTender(repository: GitRepository, snapshot: SnapshotId): Promise<IntegrationTender>;
32
+ export declare function materializeReintegrationSnapshot(repository: GitRepository, tree: GitObjectId, parent: SnapshotId, template: SnapshotId): Promise<SnapshotId>;
33
+ export declare function planIntegration(repository: GitRepository, input: IntegrationCoordinates, tender: IntegrationTender, requireBranchesToBeUpToDate: boolean): Promise<Preparation<IntegrationPlan, Readonly<{
35
34
  kind: "target-missing";
36
35
  contractId: ContractId;
37
36
  }> | IntegrationPreparationRefusal>>;
@@ -1,6 +1,7 @@
1
1
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
2
2
  import { gitObjectId, gitObjectIdForSnapshot, mintChangeId, mintSnapshotId } from "./identity.js";
3
- import { decodeGitNameOnly, decodeGitNumstat, GitPlumbingError, readRef, runGit, runGitWithEnvironment, } from "./repository.js";
3
+ import { decodeGitNameOnly, decodeGitNumstat, readRef, } from "./repository.js";
4
+ import { GitPlumbingError, runGit, runGitWithEnvironment, } from "./process.js";
4
5
  const REQUIRED_GIT = "2.38";
5
6
  async function runAllowingNonzero(repository, args, input) {
6
7
  try {
@@ -44,14 +45,66 @@ export async function worktreeChangeId(repository, input, tender) {
44
45
  return mintChangeId(id === "" ? "0000000000000000000000000000000000000000" : id);
45
46
  }
46
47
  /** Materialize the exact integration tree against its observed predecessor. */
47
- export async function materializeIntegrationSnapshot(repository, tree, parent, input) {
48
- const commit = (await runGitWithEnvironment(repository, ["commit-tree", tree, "-p", gitObjectIdForSnapshot(parent)], `${input.message ?? `${input.contractId}: ${input.title}`}\n\nKeiyaku-Contract: ${input.contractId}\n`, {
49
- GIT_AUTHOR_NAME: "Keiyaku",
50
- GIT_AUTHOR_EMAIL: "keiyaku@localhost",
51
- GIT_COMMITTER_NAME: "Keiyaku",
52
- GIT_COMMITTER_EMAIL: "keiyaku@localhost",
53
- GIT_AUTHOR_DATE: "Thu, 01 Jan 1970 00:00:00 +0000",
54
- GIT_COMMITTER_DATE: "Thu, 01 Jan 1970 00:00:00 +0000",
48
+ export async function materializeIntegrationSnapshot(repository, tree, parent, metadata) {
49
+ const commit = (await runGitWithEnvironment(repository, ["commit-tree", tree, "-p", gitObjectIdForSnapshot(parent)], metadata.message, {
50
+ GIT_AUTHOR_NAME: metadata.identity.name,
51
+ GIT_AUTHOR_EMAIL: metadata.identity.email,
52
+ GIT_COMMITTER_NAME: metadata.identity.name,
53
+ GIT_COMMITTER_EMAIL: metadata.identity.email,
54
+ GIT_AUTHOR_DATE: metadata.at,
55
+ GIT_COMMITTER_DATE: metadata.at,
56
+ })).toString("utf8").trim();
57
+ return mintSnapshotId(commit);
58
+ }
59
+ export async function persistedTender(repository, snapshot) {
60
+ const tree = (await runGit(repository, ["show", "-s", "--format=%T", gitObjectIdForSnapshot(snapshot)]))
61
+ .toString("ascii")
62
+ .trim();
63
+ return { head: snapshot, tree: gitObjectId(tree, "persisted tender tree") };
64
+ }
65
+ export async function materializeReintegrationSnapshot(repository, tree, parent, template) {
66
+ const templateObject = gitObjectIdForSnapshot(template);
67
+ let raw;
68
+ try {
69
+ raw = await runGit(repository, ["cat-file", "commit", templateObject]);
70
+ }
71
+ catch (error) {
72
+ if (error instanceof GitPlumbingError) {
73
+ throw new AuthorityCorruptionError(`recorded delivery snapshot cannot be read: ${template}`);
74
+ }
75
+ throw error;
76
+ }
77
+ const separator = raw.indexOf(Buffer.from("\n\n"));
78
+ if (separator < 0)
79
+ throw new AuthorityCorruptionError(`recorded delivery snapshot has no commit message: ${template}`);
80
+ const message = raw.subarray(separator + 2);
81
+ let fields;
82
+ try {
83
+ const formatted = (await runGit(repository, [
84
+ "show",
85
+ "-s",
86
+ "--format=%an%x00%ae%x00%aI%x00%cn%x00%ce%x00%cI",
87
+ templateObject,
88
+ ])).toString("utf8").replace(/\n$/u, "");
89
+ fields = formatted.split("\0");
90
+ }
91
+ catch (error) {
92
+ if (error instanceof GitPlumbingError) {
93
+ throw new AuthorityCorruptionError(`recorded delivery snapshot metadata cannot be read: ${template}`);
94
+ }
95
+ throw error;
96
+ }
97
+ if (fields.length !== 6 || fields.some((field) => field.length === 0)) {
98
+ throw new AuthorityCorruptionError(`recorded delivery snapshot metadata is malformed: ${template}`);
99
+ }
100
+ const [authorName, authorEmail, authorDate, committerName, committerEmail, committerDate] = fields;
101
+ const commit = (await runGitWithEnvironment(repository, ["commit-tree", tree, "-p", gitObjectIdForSnapshot(parent)], message, {
102
+ GIT_AUTHOR_NAME: authorName,
103
+ GIT_AUTHOR_EMAIL: authorEmail,
104
+ GIT_AUTHOR_DATE: authorDate,
105
+ GIT_COMMITTER_NAME: committerName,
106
+ GIT_COMMITTER_EMAIL: committerEmail,
107
+ GIT_COMMITTER_DATE: committerDate,
55
108
  })).toString("utf8").trim();
56
109
  return mintSnapshotId(commit);
57
110
  }
@@ -1,6 +1,7 @@
1
1
  import { type ContractId, type ContractState, type JournalEntry, type SnapshotId } from "../core/facts/types.js";
2
2
  import type { ContractsObservation } from "../core/facts/observation.js";
3
- import { type GitSnapshot, type GitOid, type GitRepository } from "./repository.js";
3
+ import { type GitSnapshot, type GitOid } from "./repository.js";
4
+ import { type GitRepository } from "./process.js";
4
5
  import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "./read-observation.js";
5
6
  export type TargetObservation = Readonly<{
6
7
  head: SnapshotId | null;
@@ -2,7 +2,8 @@ import { decodeJournal } from "../core/facts/codec.js";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { foldJournal } from "../core/facts/fold.js";
4
4
  import { contractJournalPath, contractJournalPaths, mintContractHead, mintSnapshotId, } from "./identity.js";
5
- import { GitPlumbingError, isKeiyakuOwnedRef, runGit, } from "./repository.js";
5
+ import { isKeiyakuOwnedRef, } from "./repository.js";
6
+ import { GitPlumbingError, runGit } from "./process.js";
6
7
  import { readGitTreeSelection, withGitTargetedReadObservation, } from "./read-observation.js";
7
8
  export async function observeDeliveryTargetAt(observation, state) {
8
9
  const target = state.coordinates.target;
@@ -10,12 +11,12 @@ export async function observeDeliveryTargetAt(observation, state) {
10
11
  return null;
11
12
  const value = await observation.resolveRef(target);
12
13
  const head = value === null ? null : mintSnapshotId(value);
13
- const delivery = state.delivery?.data;
14
- if (delivery === undefined)
14
+ const integration = state.currentIntegration;
15
+ if (integration === null)
15
16
  return { head, drift: false };
16
17
  const expected = state.terminal?.kind === "claimed"
17
- ? delivery.integration.snapshot
18
- : delivery.integration.predecessor;
18
+ ? integration.snapshot
19
+ : integration.predecessor;
19
20
  return { head, drift: head !== expected };
20
21
  }
21
22
  const TARGET_COORDINATES_FORMAT = "%(refname)%00%(objectname)%00";
@@ -0,0 +1,26 @@
1
+ export type GitRepository = Readonly<{
2
+ /** The executable selected when this repository capability is created. */
3
+ readonly gitPath: string;
4
+ /** The invocation's effective working directory, including a caller -C worktree. */
5
+ readonly effectiveCwd: string;
6
+ /** The canonical primary worktree root for this repository. */
7
+ readonly primaryWorktree: string;
8
+ /** The canonical common Git directory pinned when this capability is created. */
9
+ readonly commonDirectory: string;
10
+ }>;
11
+ export declare class GitPlumbingError extends Error {
12
+ readonly stdout: Buffer;
13
+ readonly stderr: Buffer;
14
+ readonly status: number | null;
15
+ readonly pid: number | null;
16
+ constructor(input: Readonly<{
17
+ stdout?: string | Uint8Array;
18
+ stderr: string | Uint8Array;
19
+ status: number | null;
20
+ message: string;
21
+ pid?: number | null;
22
+ }>);
23
+ }
24
+ export declare function runGit(repository: GitRepository, args: readonly string[], input?: string | Uint8Array): Promise<Buffer>;
25
+ export declare function consumeGitStdout(repository: GitRepository, args: readonly string[], consume: (chunk: Buffer) => void): Promise<void>;
26
+ export declare function runGitWithEnvironment(repository: GitRepository, args: readonly string[], input: string | Uint8Array | undefined, environment: NodeJS.ProcessEnv): Promise<Buffer>;
@@ -0,0 +1,94 @@
1
+ import { spawn } from "node:child_process";
2
+ import { consumeProcessStdout } from "../runtime/proc/run.js";
3
+ export class GitPlumbingError extends Error {
4
+ stdout;
5
+ stderr;
6
+ status;
7
+ pid;
8
+ constructor(input) {
9
+ super(input.message);
10
+ this.name = "GitPlumbingError";
11
+ this.stdout = Buffer.from(input.stdout ?? "");
12
+ this.stderr = Buffer.from(input.stderr);
13
+ this.status = input.status;
14
+ this.pid = input.pid ?? null;
15
+ }
16
+ }
17
+ const GIT_STDERR_BYTES = 16 * 1024;
18
+ function commandError(command, error) {
19
+ const candidate = error;
20
+ const stderr = candidate.stderr === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stderr);
21
+ const stdout = candidate.stdout === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stdout);
22
+ const detail = stderr.length === 0 ? candidate.message ?? "git command failed" : stderr.toString("utf8");
23
+ return new GitPlumbingError({
24
+ stdout,
25
+ stderr,
26
+ status: candidate.status ?? null,
27
+ message: `${command.join(" ")}: ${detail}`,
28
+ pid: candidate.pid ?? null,
29
+ });
30
+ }
31
+ async function executeGit(repository, args, input, environment) {
32
+ const stdout = [];
33
+ const stderr = [];
34
+ let stderrBytes = 0;
35
+ let inputError;
36
+ const child = spawn(repository.gitPath, [...args], {
37
+ cwd: repository.effectiveCwd,
38
+ ...(environment === undefined ? {} : { env: { ...process.env, ...environment } }),
39
+ stdio: ["pipe", "pipe", "pipe"],
40
+ windowsHide: true,
41
+ });
42
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
43
+ child.stderr.on("data", (chunk) => {
44
+ if (stderrBytes >= GIT_STDERR_BYTES)
45
+ return;
46
+ const retained = chunk.subarray(0, GIT_STDERR_BYTES - stderrBytes);
47
+ stderr.push(retained);
48
+ stderrBytes += retained.length;
49
+ });
50
+ child.stdin.on("error", (error) => { inputError = error; });
51
+ child.stdin.end(input);
52
+ const terminal = await new Promise((resolveTerminal) => {
53
+ child.once("error", (error) => resolveTerminal({ code: null, error }));
54
+ child.once("close", (code) => resolveTerminal({ code }));
55
+ });
56
+ const output = Buffer.concat(stdout);
57
+ if (terminal.code === 0 && inputError === undefined)
58
+ return output;
59
+ throw commandError(args, {
60
+ message: terminal.error?.message ?? inputError?.message ?? `git exited with status ${terminal.code ?? "unknown"}`,
61
+ stdout: output,
62
+ stderr: Buffer.concat(stderr),
63
+ status: terminal.code,
64
+ pid: child.pid,
65
+ });
66
+ }
67
+ export async function runGit(repository, args, input) {
68
+ return await executeGit(repository, args, input);
69
+ }
70
+ export async function consumeGitStdout(repository, args, consume) {
71
+ const result = await consumeProcessStdout({
72
+ argv: [repository.gitPath, ...args],
73
+ cwd: repository.effectiveCwd,
74
+ }, consume);
75
+ const { outcome } = result;
76
+ if (outcome.kind === "terminal" && outcome.code === 0)
77
+ return;
78
+ const status = outcome.kind === "terminal" ? outcome.code : null;
79
+ const stderr = outcome.kind === "terminal" ? outcome.stderr : "";
80
+ const message = outcome.kind === "terminal"
81
+ ? `git exited with status ${outcome.code}`
82
+ : outcome.kind === "spawn-error" || outcome.kind === "stream-error"
83
+ ? outcome.diagnostic
84
+ : `git process ended with ${outcome.kind}`;
85
+ throw commandError(args, {
86
+ message,
87
+ stderr,
88
+ status,
89
+ pid: result.pid ?? undefined,
90
+ });
91
+ }
92
+ export async function runGitWithEnvironment(repository, args, input, environment) {
93
+ return await executeGit(repository, args, input, environment);
94
+ }
@@ -1,5 +1,6 @@
1
1
  import { type TreeEntry } from "./tree.js";
2
- import { type GitOid, type GitRepository, type GitSnapshot } from "./repository.js";
2
+ import { type GitOid, type GitSnapshot } from "./repository.js";
3
+ import { type GitRepository } from "./process.js";
3
4
  declare const gitReadObservationBrand: unique symbol;
4
5
  declare const gitDecodeChannelBrand: unique symbol;
5
6
  export type GitBlobResult = Readonly<{
@@ -2,7 +2,8 @@ import { spawn } from "node:child_process";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { gitObjectId } from "./identity.js";
4
4
  import { parseTreeObject, validPath } from "./tree.js";
5
- import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, GitPlumbingError, readRef, } from "./repository.js";
5
+ import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef, } from "./repository.js";
6
+ import { GitPlumbingError } from "./process.js";
6
7
  const gitReadObservationBrand = Symbol("GitReadObservation");
7
8
  const gitDecodeChannelBrand = Symbol("GitDecodeChannel");
8
9
  function streamCursor(stream) {
@@ -36,7 +37,7 @@ function streamCursor(stream) {
36
37
  return { line, exact };
37
38
  }
38
39
  function batchObjectReader(repository) {
39
- const child = spawn("git", ["cat-file", "--batch"], {
40
+ const child = spawn(repository.gitPath, ["cat-file", "--batch"], {
40
41
  cwd: repository.effectiveCwd,
41
42
  stdio: ["pipe", "pipe", "pipe"],
42
43
  windowsHide: true,
@@ -1,5 +1,6 @@
1
- import { type GitOid, type GitRepository } from "./repository.js";
2
- import type { ContractId, ContractState } from "../core/facts/types.js";
1
+ import { type GitOid } from "./repository.js";
2
+ import { type GitRepository } from "./process.js";
3
+ import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.js";
3
4
  import type { GitDecodeChannel } from "./read-observation.js";
4
5
  import { type TargetCheckoutEffect, type TargetCheckoutLag } from "./target-placement.js";
5
6
  import { type WorktreeHookLag, type WorktreeHooks } from "./hooks.js";
@@ -8,6 +9,11 @@ export type Effect = Readonly<{
8
9
  kind: "worktree";
9
10
  path: string;
10
11
  action: "created" | "removed" | "unchanged";
12
+ }> | Readonly<{
13
+ kind: "recovery-snapshot";
14
+ action: "created";
15
+ snapshot: SnapshotId;
16
+ retention: "ephemeral";
11
17
  }> | TargetCheckoutEffect | Readonly<{
12
18
  kind: "ref";
13
19
  name: string;