@astrosheep/keiyaku 4.0.4 → 4.0.6

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 (233) 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 +15 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +24 -4
  8. package/build/src/akuma/akuma.d.ts +2 -1
  9. package/build/src/akuma/akuma.js +27 -9
  10. package/build/src/akuma/allowed.d.ts +1 -1
  11. package/build/src/akuma/allowed.js +3 -10
  12. package/build/src/akuma/archetype.d.ts +3 -1
  13. package/build/src/akuma/archetype.js +11 -6
  14. package/build/src/akuma/body-turn.d.ts +64 -0
  15. package/build/src/akuma/body-turn.js +449 -0
  16. package/build/src/akuma/body.d.ts +4 -5
  17. package/build/src/akuma/body.js +9 -414
  18. package/build/src/akuma/heart/facts.d.ts +37 -9
  19. package/build/src/akuma/heart/facts.js +3 -1
  20. package/build/src/akuma/heart/index.d.ts +4 -13
  21. package/build/src/akuma/heart/index.js +14 -135
  22. package/build/src/akuma/heart/request-authority.d.ts +12 -0
  23. package/build/src/akuma/heart/request-authority.js +119 -0
  24. package/build/src/akuma/heart/request-rows.d.ts +16 -0
  25. package/build/src/akuma/heart/request-rows.js +88 -0
  26. package/build/src/akuma/heart/rows.d.ts +1 -28
  27. package/build/src/akuma/heart/rows.js +4 -138
  28. package/build/src/akuma/heart/schema.d.ts +1 -1
  29. package/build/src/akuma/heart/schema.js +11 -3
  30. package/build/src/akuma/heart/soul.js +0 -36
  31. package/build/src/akuma/heart/timeline.d.ts +1 -0
  32. package/build/src/akuma/heart/timeline.js +19 -0
  33. package/build/src/akuma/index.d.ts +1 -1
  34. package/build/src/akuma/nuke.d.ts +2 -0
  35. package/build/src/akuma/nuke.js +152 -0
  36. package/build/src/akuma/provider-recipe.js +1 -11
  37. package/build/src/akuma/provider.d.ts +4 -7
  38. package/build/src/akuma/providers/acp/config.d.ts +8 -0
  39. package/build/src/akuma/providers/acp/config.js +37 -0
  40. package/build/src/akuma/providers/acp/core.js +12 -2
  41. package/build/src/akuma/providers/acp/index.d.ts +1 -8
  42. package/build/src/akuma/providers/acp/index.js +2 -38
  43. package/build/src/akuma/providers/claude/index.d.ts +0 -4
  44. package/build/src/akuma/providers/claude/index.js +12 -3
  45. package/build/src/akuma/providers/codex-app-server/index.d.ts +2 -27
  46. package/build/src/akuma/providers/codex-app-server/index.js +5 -2
  47. package/build/src/akuma/providers/grok-build/index.js +1 -2
  48. package/build/src/akuma/providers/index.d.ts +3 -2
  49. package/build/src/akuma/providers/index.js +36 -36
  50. package/build/src/akuma/providers/opencode-sdk/index.d.ts +2 -28
  51. package/build/src/akuma/providers/opencode-sdk/index.js +14 -4
  52. package/build/src/akuma/providers/opencode-sdk/session.d.ts +2 -2
  53. package/build/src/akuma/providers/opencode-sdk/session.js +2 -2
  54. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  55. package/build/src/akuma/providers/pi/index.js +29 -6
  56. package/build/src/akuma/request-serve.d.ts +88 -0
  57. package/build/src/akuma/request-serve.js +484 -0
  58. package/build/src/akuma/request-wire.d.ts +125 -0
  59. package/build/src/akuma/request-wire.js +383 -0
  60. package/build/src/akuma/requests.d.ts +28 -94
  61. package/build/src/akuma/requests.js +52 -562
  62. package/build/src/akuma-body.d.ts +7 -0
  63. package/build/src/akuma-body.js +69 -2
  64. package/build/src/alias/index.d.ts +1 -0
  65. package/build/src/alias/index.js +34 -1
  66. package/build/src/body/arc.js +4 -29
  67. package/build/src/body/decode.js +5 -39
  68. package/build/src/body/envelope.d.ts +7 -0
  69. package/build/src/body/envelope.js +37 -0
  70. package/build/src/cli/accepted.js +21 -9
  71. package/build/src/cli/commands/akuma-invoke.js +1 -0
  72. package/build/src/cli/commands/akuma.d.ts +1 -0
  73. package/build/src/cli/commands/akuma.js +13 -10
  74. package/build/src/cli/commands/contract.d.ts +28 -17
  75. package/build/src/cli/commands/contract.js +23 -17
  76. package/build/src/cli/commands/install.d.ts +2 -2
  77. package/build/src/cli/commands/install.js +6 -6
  78. package/build/src/cli/commands/nuke.d.ts +10 -0
  79. package/build/src/cli/commands/nuke.js +20 -0
  80. package/build/src/cli/commands/task-invoke.d.ts +15 -0
  81. package/build/src/cli/commands/task-invoke.js +144 -24
  82. package/build/src/cli/commands/task-query.d.ts +2 -2
  83. package/build/src/cli/commands/task-query.js +1 -2
  84. package/build/src/cli/commands/task.js +22 -22
  85. package/build/src/cli/coordinates.d.ts +6 -2
  86. package/build/src/cli/coordinates.js +12 -56
  87. package/build/src/cli/invoke.d.ts +5 -4
  88. package/build/src/cli/invoke.js +112 -58
  89. package/build/src/cli/main.d.ts +0 -2
  90. package/build/src/cli/main.js +4 -140
  91. package/build/src/cli/parse.d.ts +17 -4
  92. package/build/src/cli/parse.js +93 -15
  93. package/build/src/cli/render/akuma.js +111 -53
  94. package/build/src/cli/render/audit.js +2 -6
  95. package/build/src/cli/render/contract.js +61 -20
  96. package/build/src/cli/render/kanshi.js +15 -5
  97. package/build/src/cli/render/nuke.d.ts +3 -0
  98. package/build/src/cli/render/nuke.js +8 -0
  99. package/build/src/cli/render/receipt.js +28 -1
  100. package/build/src/cli/render/refusal.d.ts +2 -1
  101. package/build/src/cli/render/refusal.js +31 -0
  102. package/build/src/cli/render/terminal.d.ts +0 -2
  103. package/build/src/cli/render/terminal.js +0 -29
  104. package/build/src/cli/render/text.js +6 -1
  105. package/build/src/cli/result.d.ts +36 -9
  106. package/build/src/cli/runtime.d.ts +2 -0
  107. package/build/src/cli/runtime.js +140 -0
  108. package/build/src/cli/usage.d.ts +0 -2
  109. package/build/src/cli/usage.js +0 -4
  110. package/build/src/contract-worktree.d.ts +29 -14
  111. package/build/src/contract-worktree.js +131 -70
  112. package/build/src/core/facts/codec.js +9 -0
  113. package/build/src/core/facts/fold.js +13 -1
  114. package/build/src/core/facts/types.d.ts +8 -1
  115. package/build/src/core/subject.js +4 -3
  116. package/build/src/core/verbs/placement.d.ts +9 -1
  117. package/build/src/core/verbs/placement.js +24 -6
  118. package/build/src/core/verbs/reintegrate.d.ts +13 -0
  119. package/build/src/core/verbs/reintegrate.js +24 -0
  120. package/build/src/dispatch/index.d.ts +1 -1
  121. package/build/src/git/admission.d.ts +2 -1
  122. package/build/src/git/hooks.d.ts +2 -0
  123. package/build/src/git/hooks.js +34 -1
  124. package/build/src/git/integration.d.ts +9 -2
  125. package/build/src/git/integration.js +92 -1
  126. package/build/src/git/nuke.d.ts +2 -0
  127. package/build/src/git/nuke.js +97 -0
  128. package/build/src/git/observe.d.ts +2 -1
  129. package/build/src/git/observe.js +6 -5
  130. package/build/src/git/process.d.ts +28 -0
  131. package/build/src/git/process.js +94 -0
  132. package/build/src/git/read-observation.d.ts +2 -1
  133. package/build/src/git/read-observation.js +3 -2
  134. package/build/src/git/reconcile.d.ts +4 -1
  135. package/build/src/git/reconcile.js +42 -8
  136. package/build/src/git/repository.d.ts +2 -29
  137. package/build/src/git/repository.js +7 -118
  138. package/build/src/git/scratch.d.ts +1 -1
  139. package/build/src/git/scratch.js +1 -1
  140. package/build/src/git/target-placement.d.ts +3 -2
  141. package/build/src/git/target-placement.js +15 -14
  142. package/build/src/git/tender.d.ts +2 -1
  143. package/build/src/git/tender.js +4 -3
  144. package/build/src/git/terminal-seal.d.ts +2 -2
  145. package/build/src/git/terminal-seal.js +3 -6
  146. package/build/src/git/workspace.d.ts +4 -1
  147. package/build/src/git/workspace.js +1 -1
  148. package/build/src/index.d.ts +1 -2
  149. package/build/src/index.js +1 -1
  150. package/build/src/kanshi/read.js +5 -1
  151. package/build/src/library/address.js +7 -1
  152. package/build/src/library/akuma-creation.d.ts +1 -0
  153. package/build/src/library/akuma-creation.js +8 -1
  154. package/build/src/library/audit.d.ts +3 -1
  155. package/build/src/library/audit.js +2 -1
  156. package/build/src/library/bind.d.ts +2 -1
  157. package/build/src/library/bind.js +1 -1
  158. package/build/src/library/configuration.d.ts +1 -1
  159. package/build/src/library/configuration.js +52 -18
  160. package/build/src/library/contract.d.ts +59 -4
  161. package/build/src/library/contract.js +195 -34
  162. package/build/src/library/delivery.d.ts +4 -2
  163. package/build/src/library/fleet.d.ts +27 -7
  164. package/build/src/library/fleet.js +79 -39
  165. package/build/src/library/input.d.ts +0 -1
  166. package/build/src/library/input.js +0 -5
  167. package/build/src/library/keiyaku.d.ts +6 -2
  168. package/build/src/library/keiyaku.js +2 -0
  169. package/build/src/library/mutation.d.ts +3 -1
  170. package/build/src/library/mutation.js +3 -0
  171. package/build/src/library/nuke.d.ts +14 -0
  172. package/build/src/library/nuke.js +54 -0
  173. package/build/src/library/reconcile.d.ts +2 -1
  174. package/build/src/library/reconcile.js +8 -3
  175. package/build/src/library/refusal.d.ts +10 -1
  176. package/build/src/library/region.d.ts +1 -1
  177. package/build/src/library/repo.d.ts +4 -1
  178. package/build/src/library/repo.js +10 -4
  179. package/build/src/protocol/abandon.d.ts +9 -0
  180. package/build/src/protocol/abandon.js +15 -0
  181. package/build/src/protocol/amend.d.ts +16 -0
  182. package/build/src/protocol/amend.js +77 -0
  183. package/build/src/protocol/arc.d.ts +6 -0
  184. package/build/src/protocol/arc.js +12 -0
  185. package/build/src/protocol/attempt.d.ts +2 -1
  186. package/build/src/protocol/audit.d.ts +53 -0
  187. package/build/src/protocol/audit.js +147 -0
  188. package/build/src/protocol/bind.d.ts +1 -1
  189. package/build/src/protocol/completion.d.ts +42 -0
  190. package/build/src/protocol/completion.js +183 -0
  191. package/build/src/protocol/deliver.d.ts +49 -0
  192. package/build/src/protocol/deliver.js +261 -0
  193. package/build/src/protocol/intent.d.ts +2 -1
  194. package/build/src/protocol/intent.js +8 -2
  195. package/build/src/protocol/operations.d.ts +93 -202
  196. package/build/src/protocol/operations.js +23 -642
  197. package/build/src/protocol/placement.d.ts +12 -13
  198. package/build/src/protocol/placement.js +8 -19
  199. package/build/src/protocol/read/status.d.ts +17 -5
  200. package/build/src/protocol/read/status.js +57 -13
  201. package/build/src/protocol/reconcile.d.ts +35 -0
  202. package/build/src/protocol/reconcile.js +41 -0
  203. package/build/src/protocol/reintegrate.d.ts +31 -0
  204. package/build/src/protocol/reintegrate.js +74 -0
  205. package/build/src/protocol/review.d.ts +34 -0
  206. package/build/src/protocol/review.js +156 -0
  207. package/build/src/protocol/run.d.ts +1 -1
  208. package/build/src/runtime/proc/run.js +9 -5
  209. package/build/src/settlement/fence.d.ts +3 -1
  210. package/build/src/settlement/fence.js +24 -0
  211. package/build/src/settlement/holder.d.ts +1 -1
  212. package/build/src/settlement/settle.d.ts +1 -1
  213. package/build/src/settlement/settle.js +1 -2
  214. package/build/src/task/board.d.ts +0 -1
  215. package/build/src/task/board.js +0 -3
  216. package/build/src/task/compose.d.ts +2 -2
  217. package/build/src/task/compose.js +13 -11
  218. package/build/src/task/context.d.ts +14 -2
  219. package/build/src/task/context.js +30 -7
  220. package/build/src/task/index.d.ts +7 -6
  221. package/build/src/task/index.js +17 -145
  222. package/build/src/task/input.d.ts +18 -0
  223. package/build/src/task/input.js +189 -0
  224. package/build/src/task/mutation.d.ts +44 -0
  225. package/build/src/task/mutation.js +93 -0
  226. package/build/src/task/operations.d.ts +14 -6
  227. package/build/src/task/operations.js +57 -57
  228. package/build/src/task/store.d.ts +1 -0
  229. package/build/src/task/store.js +91 -3
  230. package/build/src/verification/execution.d.ts +1 -1
  231. package/build/src/workspace-place.d.ts +3 -4
  232. package/build/src/workspace-place.js +26 -15
  233. package/package.json +3 -3
@@ -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 {
@@ -55,6 +56,58 @@ export async function materializeIntegrationSnapshot(repository, tree, parent, m
55
56
  })).toString("utf8").trim();
56
57
  return mintSnapshotId(commit);
57
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,
108
+ })).toString("utf8").trim();
109
+ return mintSnapshotId(commit);
110
+ }
58
111
  async function isAncestor(repository, ancestor, descendant) {
59
112
  const result = await runAllowingNonzero(repository, [
60
113
  "merge-base",
@@ -139,6 +192,44 @@ async function mergedTree(repository, contractId, base, targetHead, tenderTree)
139
192
  message: "git merge-tree --write-tree failed",
140
193
  });
141
194
  }
195
+ /** True when the appointed workspace already has Git MERGE_HEAD. */
196
+ export async function workspaceMergeStatePresent(repository, workspace) {
197
+ const result = await runAllowingNonzero(repository, [
198
+ "-C",
199
+ workspace,
200
+ "rev-parse",
201
+ "-q",
202
+ "--verify",
203
+ "MERGE_HEAD",
204
+ ]);
205
+ if (result.status === 0)
206
+ return true;
207
+ if (result.status === 1)
208
+ return false;
209
+ throw new GitPlumbingError({
210
+ stderr: result.stderr,
211
+ status: result.status,
212
+ message: "git rev-parse --verify MERGE_HEAD failed",
213
+ });
214
+ }
215
+ /** Project the already-judged conflict as `git merge --no-commit <targetHead>`. */
216
+ export async function materializeJudgedConflict(repository, workspace, targetHead) {
217
+ const result = await runAllowingNonzero(repository, [
218
+ "-C",
219
+ workspace,
220
+ "merge",
221
+ "--no-commit",
222
+ gitObjectIdForSnapshot(targetHead),
223
+ ]);
224
+ if (result.status === 0 || result.status === 1)
225
+ return;
226
+ throw new GitPlumbingError({
227
+ stdout: result.stdout,
228
+ stderr: result.stderr,
229
+ status: result.status,
230
+ message: "git merge --no-commit failed",
231
+ });
232
+ }
142
233
  export async function planIntegration(repository, input, tender, requireBranchesToBeUpToDate) {
143
234
  const target = input.coordinates.target;
144
235
  if (target === undefined) {
@@ -0,0 +1,2 @@
1
+ import type { WorldRoot } from "../world.js";
2
+ export declare function nukeGit(world: WorldRoot): Promise<void>;
@@ -0,0 +1,97 @@
1
+ import { access } from "node:fs/promises";
2
+ import { nukeContractWorktreeLock, nukeHereAppointments } from "../contract-worktree.js";
3
+ import { nukeSettlementFences } from "../settlement/fence.js";
4
+ import { nukeEmptyPlaceAuthority, readPlaceRegister, releaseManagedWorktrees, } from "../workspace-place.js";
5
+ import { nukeWorktreeHookResidue } from "./hooks.js";
6
+ import { nukeReconcileLocks } from "./reconcile.js";
7
+ import { runGit } from "./process.js";
8
+ import { NoGitWorldError, registeredWorktrees, repositoryAt, worktreeGitDirectory } from "./repository.js";
9
+ import { worktreePath } from "./workspace.js";
10
+ async function managedCustody(repository) {
11
+ const register = await readPlaceRegister(repository);
12
+ const topology = new Map((await registeredWorktrees(repository)).map((entry) => [entry.path, entry]));
13
+ const entries = [];
14
+ for (const appointment of register.appointments) {
15
+ const path = worktreePath(repository, appointment.place);
16
+ const registered = topology.get(path);
17
+ if (registered === undefined) {
18
+ try {
19
+ await access(path);
20
+ }
21
+ catch {
22
+ entries.push({ contract: appointment.contract, path });
23
+ continue;
24
+ }
25
+ throw new Error(`managed Place path has foreign custody: ${path}`);
26
+ }
27
+ if (registered.branch !== null)
28
+ throw new Error(`managed Place path has a branch: ${path}`);
29
+ entries.push({ contract: appointment.contract, path });
30
+ }
31
+ const refs = (await runGit(repository, [
32
+ "for-each-ref", "--format=%(refname)", "refs/heads/keiyaku-delivery", "refs/heads/keiyaku-candidate",
33
+ ])).toString("utf8").split("\n").filter((ref) => ref.length > 0);
34
+ const state = await runGit(repository, ["show-ref", "--verify", "--quiet", "refs/heads/keiyaku-state"])
35
+ .then(() => true, () => false);
36
+ return { entries, refs, state };
37
+ }
38
+ async function removeManagedWorktree(repository, entry) {
39
+ const topology = await registeredWorktrees(repository);
40
+ const registered = topology.find((candidate) => candidate.path === entry.path);
41
+ if (registered === undefined) {
42
+ try {
43
+ await access(entry.path);
44
+ }
45
+ catch {
46
+ return;
47
+ }
48
+ throw new Error(`managed Place path has foreign custody: ${entry.path}`);
49
+ }
50
+ if (registered.branch !== null)
51
+ throw new Error(`managed Place path has a branch: ${entry.path}`);
52
+ await nukeWorktreeHookResidue(await worktreeGitDirectory(repository, entry.path));
53
+ await runGit(repository, ["worktree", "remove", "--force", entry.path]);
54
+ if ((await registeredWorktrees(repository)).some((candidate) => candidate.path === entry.path)) {
55
+ throw new Error(`managed Place worktree remains registered: ${entry.path}`);
56
+ }
57
+ try {
58
+ await access(entry.path);
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ throw new Error(`managed Place worktree remains present: ${entry.path}`);
64
+ }
65
+ async function removeOwnedRefs(repository, refs) {
66
+ for (const ref of refs) {
67
+ const oid = (await runGit(repository, ["rev-parse", "--verify", "--quiet", ref])).toString("utf8").trim();
68
+ if (oid.length > 0)
69
+ await runGit(repository, ["update-ref", "--no-deref", "-d", ref, oid]);
70
+ }
71
+ }
72
+ export async function nukeGit(world) {
73
+ let repository;
74
+ try {
75
+ repository = await repositoryAt(world);
76
+ }
77
+ catch (error) {
78
+ if (error instanceof NoGitWorldError)
79
+ return;
80
+ throw error;
81
+ }
82
+ const custody = await managedCustody(repository);
83
+ for (const entry of custody.entries) {
84
+ await removeManagedWorktree(repository, entry);
85
+ await releaseManagedWorktrees(repository, [entry.contract]);
86
+ }
87
+ await nukeHereAppointments(repository);
88
+ await nukeEmptyPlaceAuthority(repository);
89
+ await nukeContractWorktreeLock(repository);
90
+ await nukeReconcileLocks(repository);
91
+ await nukeSettlementFences(repository);
92
+ await removeOwnedRefs(repository, custody.refs);
93
+ if (custody.state) {
94
+ const oid = (await runGit(repository, ["rev-parse", "--verify", "refs/heads/keiyaku-state"])).toString("utf8").trim();
95
+ await runGit(repository, ["update-ref", "--no-deref", "-d", "refs/heads/keiyaku-state", oid]);
96
+ }
97
+ }
@@ -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,28 @@
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 worktree root containing the invocation's effective working directory. */
7
+ readonly invocationWorktree: string;
8
+ /** The canonical primary worktree root for this repository. */
9
+ readonly primaryWorktree: string;
10
+ /** The canonical common Git directory pinned when this capability is created. */
11
+ readonly commonDirectory: string;
12
+ }>;
13
+ export declare class GitPlumbingError extends Error {
14
+ readonly stdout: Buffer;
15
+ readonly stderr: Buffer;
16
+ readonly status: number | null;
17
+ readonly pid: number | null;
18
+ constructor(input: Readonly<{
19
+ stdout?: string | Uint8Array;
20
+ stderr: string | Uint8Array;
21
+ status: number | null;
22
+ message: string;
23
+ pid?: number | null;
24
+ }>);
25
+ }
26
+ export declare function runGit(repository: GitRepository, args: readonly string[], input?: string | Uint8Array): Promise<Buffer>;
27
+ export declare function consumeGitStdout(repository: GitRepository, args: readonly string[], consume: (chunk: Buffer) => void): Promise<void>;
28
+ 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,4 +1,5 @@
1
- import { type GitOid, type GitRepository } from "./repository.js";
1
+ import { type GitOid } from "./repository.js";
2
+ import { type GitRepository } from "./process.js";
2
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";
@@ -63,4 +64,6 @@ type ReconcileBatchOptions = Readonly<{
63
64
  }>;
64
65
  /** Reconcile each discovered Contract through its own serialized, fresh observation. */
65
66
  export declare function reconcileBatch(repository: GitRepository, channel: GitDecodeChannel, contracts: Iterable<ContractId>, options: ReconcileBatchOptions): Promise<readonly ReconcileBatchItem[]>;
67
+ /** Remove only validated Contract reconciliation lock residue. */
68
+ export declare function nukeReconcileLocks(repository: GitRepository): Promise<void>;
66
69
  export {};
@@ -1,8 +1,9 @@
1
- import { access, mkdir } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
1
+ import { access, mkdir, readdir } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
3
  import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
4
4
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
5
- import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, GitPlumbingError, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, runGit, writeCommit, worktreeGitDirectory, } from "./repository.js";
5
+ import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, writeCommit, worktreeGitDirectory, } from "./repository.js";
6
+ import { GitPlumbingError, runGit } from "./process.js";
6
7
  import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot, mintSnapshotId, } from "./identity.js";
7
8
  import { observeContractAt } from "./observe.js";
8
9
  import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-placement.js";
@@ -206,7 +207,9 @@ async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag
206
207
  if (state.delivery === null)
207
208
  effects.push(await removeRef(repository, pin));
208
209
  else {
209
- const integration = state.delivery.data.integration.snapshot;
210
+ const integration = state.currentIntegration?.snapshot;
211
+ if (integration === undefined)
212
+ return complete();
210
213
  const custodian = await snapshotCustodian(repository, integration);
211
214
  effects.push(await (custodian === null
212
215
  ? await updateRef(repository, pin, integration)
@@ -216,7 +219,7 @@ async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag
216
219
  else if (state.bound) {
217
220
  effects.push(await (state.delivery === null
218
221
  ? await removeRef(repository, pin)
219
- : await updateRef(repository, pin, state.delivery.data.integration.snapshot)));
222
+ : await updateRef(repository, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)));
220
223
  }
221
224
  return complete(effects, lag);
222
225
  }
@@ -314,7 +317,7 @@ async function releaseTerminalCustody({ repository, state, expected, ref, pin, a
314
317
  return;
315
318
  }
316
319
  const tender = state.delivery.data.tenderSnapshot;
317
- const integration = state.delivery.data.integration.snapshot;
320
+ const integration = state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot;
318
321
  const target = await targetCustodyForClaimedIntegration(repository, state, integration);
319
322
  if (sealedTree(expected, tender) === sealedTree(expected, integration) && target !== null) {
320
323
  effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
@@ -337,7 +340,7 @@ async function reconcileTerminalManagedWorktree({ repository, channel, hooks, re
337
340
  const expected = await terminalSealExpectations(channel, state);
338
341
  acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
339
342
  if (state.delivery !== null) {
340
- acc.effects.push(await updateRef(primary, pin, state.delivery.data.integration.snapshot));
343
+ acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
341
344
  }
342
345
  if (retainTerminalWorktree === true)
343
346
  return complete(acc.effects, acc.lag);
@@ -367,7 +370,7 @@ async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, p
367
370
  if (hookLag !== null)
368
371
  lag.push(hookLag);
369
372
  effects.push(await (state.delivery
370
- ? await updateRef(repository, candidatePinRefFor(state.id), state.delivery.data.integration.snapshot)
373
+ ? await updateRef(repository, candidatePinRefFor(state.id), state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)
371
374
  : await removeRef(repository, candidatePinRefFor(state.id))));
372
375
  return complete(effects, lag);
373
376
  }
@@ -463,3 +466,34 @@ export async function reconcileBatch(repository, channel, contracts, options) {
463
466
  }
464
467
  return items;
465
468
  }
469
+ /** Remove only validated Contract reconciliation lock residue. */
470
+ export async function nukeReconcileLocks(repository) {
471
+ const root = join(commonGitDirectory(repository), "keiyaku", "locks", "reconcile");
472
+ let first;
473
+ try {
474
+ first = await readdir(root, { withFileTypes: true });
475
+ }
476
+ catch (error) {
477
+ if (error.code === "ENOENT")
478
+ return;
479
+ throw error;
480
+ }
481
+ let paths = [];
482
+ for (const directory of first) {
483
+ if (!/^[0-9a-f]{2}$/u.test(directory.name) || !directory.isDirectory() || directory.isSymbolicLink()) {
484
+ throw new Error(`reconciliation lock custody is invalid: ${resolve(root, directory.name)}`);
485
+ }
486
+ const nested = resolve(root, directory.name);
487
+ const files = await readdir(nested, { withFileTypes: true });
488
+ for (const file of files) {
489
+ if (!/^[0-9a-f]{62}\.sqlite$/u.test(file.name) || !file.isFile() || file.isSymbolicLink()) {
490
+ throw new Error(`reconciliation lock custody is invalid: ${resolve(nested, file.name)}`);
491
+ }
492
+ paths.push(resolve(nested, file.name));
493
+ }
494
+ }
495
+ for (const path of paths) {
496
+ const held = await acquireSqliteTransactionLock({ path, mode: "immediate" });
497
+ held.close();
498
+ }
499
+ }
@@ -1,3 +1,4 @@
1
+ import { type GitRepository } from "./process.js";
1
2
  import { type TreeChange, type TreeEntry } from "./tree.js";
2
3
  export type { TreeChange } from "./tree.js";
3
4
  export declare const GIT_REF = "refs/heads/keiyaku-state";
@@ -19,14 +20,6 @@ export type GitRefAssertion = Readonly<{
19
20
  readonly ref: string;
20
21
  readonly oid: GitOid;
21
22
  }>;
22
- export type GitRepository = Readonly<{
23
- /** The invocation's effective working directory, including a caller -C worktree. */
24
- readonly effectiveCwd: string;
25
- /** The canonical primary worktree root for this repository. */
26
- readonly primaryWorktree: string;
27
- /** The canonical common Git directory pinned when this capability is created. */
28
- readonly commonDirectory: string;
29
- }>;
30
23
  export type GitSnapshot = Readonly<{
31
24
  readonly commit: GitOid | null;
32
25
  readonly tree: GitOid | null;
@@ -36,41 +29,21 @@ export declare class NoGitWorldError extends Error {
36
29
  readonly path: string;
37
30
  constructor(path: string);
38
31
  }
39
- export declare class GitPlumbingError extends Error {
40
- readonly stdout: Buffer;
41
- readonly stderr: Buffer;
42
- readonly status: number | null;
43
- readonly pid: number | null;
44
- constructor(input: Readonly<{
45
- stdout?: string | Uint8Array;
46
- stderr: string | Uint8Array;
47
- status: number | null;
48
- message: string;
49
- pid?: number | null;
50
- }>);
51
- }
52
32
  export type RegisteredWorktree = Readonly<{
53
33
  path: string;
54
34
  branch: string | null;
55
35
  }>;
56
36
  export declare function registeredWorktrees(repository: GitRepository): Promise<readonly RegisteredWorktree[]>;
57
37
  export declare function registeredWorktreePaths(repository: GitRepository): Promise<readonly string[]>;
58
- export declare function repositoryAt(cwd: string): Promise<GitRepository>;
38
+ export declare function repositoryAt(cwd: string, gitPath?: string): Promise<GitRepository>;
59
39
  export declare function commonGitDirectory(repository: GitRepository): string;
60
40
  export declare function worktreeGitDirectory(repository: GitRepository, worktree: string): Promise<string>;
61
41
  export declare function worktreeRoot(repository: GitRepository): Promise<string>;
62
- export declare function runGit(repository: GitRepository, args: readonly string[], input?: string | Uint8Array): Promise<Buffer>;
63
- export declare function consumeGitStdout(repository: GitRepository, args: readonly string[], consume: (chunk: Buffer) => void): Promise<void>;
64
- export declare function runGitWithEnvironment(repository: GitRepository, args: readonly string[], input: string | Uint8Array | undefined, environment: NodeJS.ProcessEnv): Promise<Buffer>;
65
42
  export declare function readRef(repository: GitRepository, ref: string): Promise<GitOid | null>;
66
43
  export declare function readTreeEntries(repository: GitRepository, tree: GitOid): Promise<Map<string, TreeEntry>>;
67
44
  export declare function readGit(repository: GitRepository): Promise<GitSnapshot>;
68
45
  /** Read only the requested private Git paths from one immutable Git tree. */
69
46
  export declare function readGitPaths(repository: GitRepository, requestedPaths: readonly string[]): Promise<GitSnapshot>;
70
- /** Add paths from the same immutable Git tree without rereading its ref. */
71
- export declare function extendGitPaths(repository: GitRepository, snapshot: GitSnapshot, requestedPaths: readonly string[]): Promise<GitSnapshot>;
72
- /** Expand a targeted immutable snapshot to its complete already-pinned tree. */
73
- export declare function expandGitSnapshot(repository: GitRepository, snapshot: GitSnapshot): Promise<GitSnapshot>;
74
47
  export declare function writeBlob(repository: GitRepository, bytes: string | Uint8Array): Promise<GitOid>;
75
48
  export declare function readBlob(repository: GitRepository, oid: GitOid): Promise<Buffer>;
76
49
  /** Copy only the changed paths' tree ancestors, preserving untouched subtrees by object ID. */