@astrosheep/keiyaku 2.9.9 → 2.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +93 -174
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +2 -1
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +6 -1
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +3 -2
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +9 -16
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +3 -5
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +2 -0
  90. package/build/config/env.js +23 -0
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +6 -5
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +27 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +21 -358
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +114 -119
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,194 +0,0 @@
1
- import { wrapGitError, runGitProcess } from "../git/core.js";
2
- import { GitStreamingBatchFramingError, GitStreamingBatchResponseEndedError, openGitStreamingBatch, } from "../git/streaming-batch.js";
3
- import { parseLedgerEntryCommitMessage } from "./entry.js";
4
- import { ACTIVE_LEDGER_REF_PREFIX, activeLedgerRef, } from "./ledger.js";
5
- const MAX_LEDGER_COMMIT_BYTES = 1024 * 1024;
6
- async function runLedgerGit(cwd, args) {
7
- const result = await runGitProcess(cwd, args);
8
- if (result.error || result.status !== 0) {
9
- throw wrapGitError(args.join(" "), {
10
- message: result.error?.message
11
- ?? `git exited with status ${result.status ?? "unknown"}`,
12
- stderr: result.stderr,
13
- stdout: result.stdout,
14
- }, cwd);
15
- }
16
- return Buffer.from(result.stdout, "utf8");
17
- }
18
- async function queryBatchObject(batch, expression) {
19
- const record = await batch.request(expression, {
20
- maxContentBytes: MAX_LEDGER_COMMIT_BYTES,
21
- });
22
- if (record.kind === "missing")
23
- return null;
24
- if (record.kind === "ambiguous") {
25
- throw new Error(`[git cat-file ${expression}] object is ambiguous`);
26
- }
27
- return {
28
- oid: record.record.oid,
29
- type: record.record.type,
30
- content: record.record.content,
31
- };
32
- }
33
- function parseCommitObject(object) {
34
- if (object.type !== "commit") {
35
- throw new Error(`git cat-file expected commit ${object.oid}, received ${object.type}`);
36
- }
37
- const content = object.content.toString("utf8");
38
- const messageStart = content.indexOf("\n\n");
39
- if (messageStart < 0)
40
- throw new Error(`git commit ${object.oid} has no message separator`);
41
- const parents = [];
42
- for (const line of content.slice(0, messageStart).split("\n")) {
43
- if (!line.startsWith("parent "))
44
- continue;
45
- const parent = line.slice("parent ".length);
46
- if (!/^[0-9a-f]{40,64}$/.test(parent)) {
47
- throw new Error(`git commit ${object.oid} has an invalid parent header`);
48
- }
49
- parents.push(parent);
50
- }
51
- return { oid: object.oid, parents, message: content.slice(messageStart + 2) };
52
- }
53
- function parseLedgerRefs(output) {
54
- return output
55
- .toString("utf8")
56
- .split(/\r?\n/)
57
- .filter((ref) => ref.startsWith(ACTIVE_LEDGER_REF_PREFIX))
58
- .map((ref) => ({ contractId: ref.slice(ACTIVE_LEDGER_REF_PREFIX.length), ref }));
59
- }
60
- async function readLedgerFromBatch(batch, ref, commits) {
61
- const headObject = await queryBatchObject(batch, `${ref.ref}^{commit}`);
62
- if (!headObject)
63
- return null;
64
- const head = parseCommitObject(headObject);
65
- commits.set(head.oid, Promise.resolve(head));
66
- const ordered = [];
67
- const visited = new Set();
68
- const visiting = new Set();
69
- const visit = async (oid) => {
70
- if (visited.has(oid))
71
- return;
72
- if (visiting.has(oid))
73
- throw new Error(`git ledger ancestry contains a cycle at ${oid}`);
74
- visiting.add(oid);
75
- let pending = commits.get(oid);
76
- if (!pending) {
77
- pending = queryBatchObject(batch, oid).then((object) => {
78
- if (!object)
79
- throw new Error(`[git cat-file ${oid}] object is missing`);
80
- return parseCommitObject(object);
81
- });
82
- commits.set(oid, pending);
83
- }
84
- const commit = await pending;
85
- for (const parent of commit.parents)
86
- await visit(parent);
87
- visiting.delete(oid);
88
- visited.add(oid);
89
- ordered.push(commit);
90
- };
91
- await visit(head.oid);
92
- return {
93
- contractId: ref.contractId,
94
- ref: ref.ref,
95
- head: head.oid,
96
- entries: ordered.map((commit) => parseLedgerEntryCommitMessage(commit.message)),
97
- };
98
- }
99
- async function closeBatch(cwd, batch) {
100
- const close = await batch.close();
101
- if (!close.aborted && (close.timedOut || close.signal || close.code !== 0)) {
102
- throw wrapGitError("cat-file --batch", {
103
- message: close.timedOut
104
- ? "git timed out"
105
- : close.signal
106
- ? `git exited from signal ${close.signal}`
107
- : `git exited with status ${close.code ?? "unknown"}`,
108
- stderr: close.stderr.toString("utf8"),
109
- }, cwd);
110
- }
111
- }
112
- async function readLedgerRefs(cwd, refs) {
113
- if (refs.length === 0)
114
- return [];
115
- const batch = openGitStreamingBatch(cwd);
116
- const commits = new Map();
117
- const results = [];
118
- let readError;
119
- try {
120
- for (let index = 0; index < refs.length; index += 1) {
121
- const ref = refs[index];
122
- try {
123
- const ledger = await readLedgerFromBatch(batch, ref, commits);
124
- if (ledger)
125
- results.push(ledger);
126
- else
127
- results.push({ id: ref.contractId, error: new Error(`ledger ref disappeared: ${ref.ref}`) });
128
- }
129
- catch (error) {
130
- const diagnostic = error instanceof Error ? error : new Error(String(error));
131
- results.push({ id: ref.contractId, error: diagnostic });
132
- if (error instanceof GitStreamingBatchFramingError
133
- || error instanceof GitStreamingBatchResponseEndedError) {
134
- const isolationReason = error instanceof GitStreamingBatchResponseEndedError
135
- ? "response ended"
136
- : "malformed frame";
137
- for (const pending of refs.slice(index + 1)) {
138
- results.push({
139
- id: pending.contractId,
140
- error: new Error(`git cat-file batch terminated after ${isolationReason}: ${diagnostic.message}`),
141
- });
142
- }
143
- break;
144
- }
145
- }
146
- }
147
- }
148
- catch (error) {
149
- readError = error;
150
- }
151
- try {
152
- await closeBatch(cwd, batch);
153
- }
154
- catch (error) {
155
- if (!readError)
156
- readError = error;
157
- }
158
- if (readError)
159
- throw readError;
160
- return results;
161
- }
162
- /** Read every ledger with two Git processes regardless of ledger or entry count. */
163
- export async function readAllLedgerSnapshots(cwd) {
164
- const refOutput = await runLedgerGit(cwd, [
165
- "for-each-ref",
166
- "--sort=refname",
167
- "--format=%(refname)",
168
- ACTIVE_LEDGER_REF_PREFIX,
169
- ]);
170
- return readLedgerRefs(cwd, parseLedgerRefs(refOutput));
171
- }
172
- /** Read one exact ledger with one Git process and without enumerating other refs. */
173
- export async function readLedgerSnapshot(cwd, contractId) {
174
- const ref = activeLedgerRef(contractId);
175
- const batch = openGitStreamingBatch(cwd);
176
- let ledger = null;
177
- let readError;
178
- try {
179
- ledger = await readLedgerFromBatch(batch, { contractId, ref }, new Map());
180
- }
181
- catch (error) {
182
- readError = error;
183
- }
184
- try {
185
- await closeBatch(cwd, batch);
186
- }
187
- catch (error) {
188
- if (!readError)
189
- readError = error;
190
- }
191
- if (readError)
192
- throw readError;
193
- return ledger;
194
- }
@@ -1,84 +0,0 @@
1
- import { createGit, wrapGitError, } from "../git/core.js";
2
- import { formatLedgerEntryCommitMessage, parseLedgerEntryCommitMessage, } from "./entry.js";
3
- import { casUpdateRefLog, createRefLogCommit, listRefLogCommits, readRefLogCommitMessage, resolveRefLogHead, } from "./ref-log.js";
4
- export const ACTIVE_LEDGER_REF_PREFIX = "refs/keiyaku/active/";
5
- export function activeLedgerRef(contractId) {
6
- return `${ACTIVE_LEDGER_REF_PREFIX}${contractId}`;
7
- }
8
- export async function appendEntry(cwd, ref, entry, expectedHead) {
9
- const message = formatLedgerEntryCommitMessage(entry);
10
- const separator = message.indexOf("\n\n");
11
- const subject = message.slice(0, separator);
12
- const body = message.slice(separator + 2).trimEnd();
13
- const newHead = await createRefLogCommit(cwd, {
14
- subject,
15
- body,
16
- parentHead: expectedHead,
17
- errorLabel: `ledger ${entry.kind} ${entry.contract}`,
18
- });
19
- const casOk = await casUpdateRefLog(cwd, ref, newHead, expectedHead);
20
- if (casOk)
21
- return { ok: true, head: newHead };
22
- // Intentional: CAS loss re-resolves and surfaces conflict/missing-ref without throw.
23
- const currentHead = await resolveRefLogHead(cwd, ref);
24
- if (currentHead === null) {
25
- return { ok: false, reason: "missing-ref" };
26
- }
27
- return { ok: false, reason: "conflict", currentHead };
28
- }
29
- export async function appendWithRetry(cwd, ref, build, options = {}) {
30
- const maxRetries = options.maxRetries ?? 3;
31
- let currentHead = await resolveRefLogHead(cwd, ref);
32
- const startedMissing = currentHead === null;
33
- for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
34
- const currentEntries = currentHead ? (await readLedgerRef(cwd, ref, currentHead)).entries : [];
35
- const nextEntry = await build(currentHead, currentEntries);
36
- if (nextEntry === null) {
37
- return { ok: false, reason: "aborted", currentHead };
38
- }
39
- const result = await appendEntry(cwd, ref, nextEntry, currentHead);
40
- if (result.ok)
41
- return result;
42
- if (result.reason === "missing-ref") {
43
- if (!startedMissing) {
44
- return result;
45
- }
46
- currentHead = null;
47
- continue;
48
- }
49
- currentHead = result.currentHead;
50
- }
51
- if (currentHead === null) {
52
- return { ok: false, reason: "missing-ref" };
53
- }
54
- return { ok: false, reason: "conflict", currentHead };
55
- }
56
- async function readLedgerRef(cwd, ref, head) {
57
- const commits = await listRefLogCommits(cwd, head, ref);
58
- const entries = [];
59
- for (const commit of commits) {
60
- entries.push(parseLedgerEntryCommitMessage(await readRefLogCommitMessage(cwd, commit)));
61
- }
62
- return { ref, head, entries };
63
- }
64
- export async function readLedger(cwd, contractId) {
65
- const ref = activeLedgerRef(contractId);
66
- const head = await resolveRefLogHead(cwd, ref);
67
- if (!head)
68
- return null;
69
- return { contractId, ...(await readLedgerRef(cwd, ref, head)) };
70
- }
71
- export async function listContractIds(cwd) {
72
- const git = createGit(cwd);
73
- try {
74
- const output = await git.raw(["for-each-ref", "--format=%(refname)", ACTIVE_LEDGER_REF_PREFIX]);
75
- return output
76
- .split(/\r?\n/)
77
- .map((line) => line.trim())
78
- .filter((line) => line.startsWith(ACTIVE_LEDGER_REF_PREFIX))
79
- .map((ref) => ref.slice(ACTIVE_LEDGER_REF_PREFIX.length));
80
- }
81
- catch (error) {
82
- throw wrapGitError(`for-each-ref ${ACTIVE_LEDGER_REF_PREFIX}`, error, cwd);
83
- }
84
- }
@@ -1,87 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { DatabaseSync } from "node:sqlite";
4
- import { isErrnoException } from "../../errno.js";
5
- export const LEASH_FILE = "leash";
6
- export class ProjectionLeashError extends Error {
7
- code = "PROJECTION_LEASH_ERROR";
8
- constructor(message, options) {
9
- super(message, options);
10
- this.name = "ProjectionLeashError";
11
- }
12
- }
13
- export function leashPath(projectionDirectory) {
14
- return path.join(projectionDirectory, LEASH_FILE);
15
- }
16
- function errorDetail(error) {
17
- return error instanceof Error ? error.message : String(error);
18
- }
19
- function readJournalMode(database) {
20
- const row = database.prepare("PRAGMA journal_mode").get();
21
- return typeof row?.journal_mode === "string" ? row.journal_mode.toLowerCase() : undefined;
22
- }
23
- /**
24
- * Birth-time initialization for the projection leash file.
25
- * Creates a brand-new extensionless rollback-journal SQLite database with the
26
- * minimal kernel-lock anchor schema, then closes all handles before return.
27
- * Fails if the target already exists. Stores zero product facts.
28
- */
29
- export function initializeProjectionLeash(projectionDirectory) {
30
- let parent;
31
- try {
32
- parent = fs.statSync(projectionDirectory);
33
- }
34
- catch (error) {
35
- throw new ProjectionLeashError(`projection leash parent directory does not exist at ${projectionDirectory}`, { cause: error });
36
- }
37
- if (!parent.isDirectory()) {
38
- throw new ProjectionLeashError("projection leash parent must be a directory");
39
- }
40
- const target = leashPath(projectionDirectory);
41
- let reserved = false;
42
- try {
43
- const fd = fs.openSync(target, "wx");
44
- fs.closeSync(fd);
45
- reserved = true;
46
- }
47
- catch (error) {
48
- if (isErrnoException(error) && error.code === "EEXIST") {
49
- throw new ProjectionLeashError(`projection leash already exists at ${target}`, { cause: error });
50
- }
51
- throw new ProjectionLeashError(`projection leash could not be initialized: ${errorDetail(error)}`, { cause: error });
52
- }
53
- let database;
54
- try {
55
- database = new DatabaseSync(target, {
56
- enableDoubleQuotedStringLiterals: false,
57
- allowExtension: false,
58
- });
59
- database.exec("PRAGMA busy_timeout = 0");
60
- database.exec("PRAGMA journal_mode = DELETE");
61
- database.exec("CREATE TABLE lock_anchor (id INTEGER PRIMARY KEY CHECK (id = 1))");
62
- if (readJournalMode(database) !== "delete") {
63
- throw new ProjectionLeashError("projection leash does not use rollback-journal mode");
64
- }
65
- database.prepare("SELECT id FROM lock_anchor LIMIT 1").get();
66
- database.close();
67
- database = undefined;
68
- return target;
69
- }
70
- catch (error) {
71
- try {
72
- database?.close();
73
- }
74
- catch { /* preserve the causal initialization failure */ }
75
- if (reserved) {
76
- try {
77
- fs.unlinkSync(target);
78
- }
79
- catch {
80
- // Leave a partial file only if unlink itself fails.
81
- }
82
- }
83
- if (error instanceof ProjectionLeashError)
84
- throw error;
85
- throw new ProjectionLeashError(`projection leash could not be initialized: ${errorDetail(error)}`, { cause: error });
86
- }
87
- }
@@ -1,119 +0,0 @@
1
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, parseRevListOutput, wrapGitError } from "../git/core.js";
2
- const EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
3
- const MISSING_REF_SENTINEL = "0".repeat(40);
4
- /** Resolve a ref log head, or null when the ref is missing. */
5
- export async function resolveRefLogHead(cwd, ref, options = {}) {
6
- const git = createGit(cwd);
7
- const peel = options.peel ?? "commit";
8
- const target = peel === "commit" ? `${ref}^{commit}` : ref;
9
- try {
10
- return (await git.raw(["rev-parse", "--verify", target])).trim();
11
- }
12
- catch (error) {
13
- if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
14
- return null;
15
- throw wrapGitError(`rev-parse ${ref}`, error, cwd);
16
- }
17
- }
18
- /** Read the raw commit message bytes used by ref-log parsers (`show %B`). */
19
- export async function readRefLogCommitMessage(cwd, commit) {
20
- const git = createGit(cwd);
21
- try {
22
- return await git.raw(["show", "-s", "--format=%B", commit]);
23
- }
24
- catch (error) {
25
- throw wrapGitError(`show commit message ${commit}`, error, cwd);
26
- }
27
- }
28
- /**
29
- * List commits reachable from `head` oldest-first.
30
- * `errorLabel` is the ref or range name used in wrapGitError text.
31
- */
32
- export async function listRefLogCommits(cwd, head, errorLabel = head) {
33
- const git = createGit(cwd);
34
- try {
35
- return parseRevListOutput(await git.raw(["rev-list", "--reverse", head]));
36
- }
37
- catch (error) {
38
- throw wrapGitError(`rev-list ${errorLabel}`, error, cwd);
39
- }
40
- }
41
- /**
42
- * Create an empty-tree commit for a ref log entry.
43
- * Callers own subject/body byte layout so message formats stay domain-local.
44
- */
45
- export async function createRefLogCommit(cwd, input) {
46
- const git = createGit(cwd);
47
- const args = ["commit-tree", EMPTY_TREE_HASH];
48
- if (input.parentHead)
49
- args.push("-p", input.parentHead);
50
- args.push("-m", input.subject, "-m", input.body);
51
- try {
52
- return (await git.raw(args)).trim();
53
- }
54
- catch (error) {
55
- throw wrapGitError(`commit-tree ${input.errorLabel ?? input.subject}`, error, cwd);
56
- }
57
- }
58
- /**
59
- * Compare-and-swap a ref to `newHead`.
60
- * Returns false on CAS failure; callers own re-resolve / degrade policy.
61
- */
62
- export async function casUpdateRefLog(cwd, ref, newHead, expectedOld) {
63
- const git = createGit(cwd);
64
- const oldValue = expectedOld ?? MISSING_REF_SENTINEL;
65
- try {
66
- await git.raw(["update-ref", ref, newHead, oldValue]);
67
- return true;
68
- }
69
- catch {
70
- return false;
71
- }
72
- }
73
- async function readRefLogAt(cwd, ref, head, parse) {
74
- const commits = await listRefLogCommits(cwd, head, ref);
75
- const entries = [];
76
- for (const commit of commits) {
77
- entries.push(parse(await readRefLogCommitMessage(cwd, commit)));
78
- }
79
- return entries;
80
- }
81
- export async function readRefLog(cwd, ref, parse) {
82
- const head = await resolveRefLogHead(cwd, ref);
83
- if (!head)
84
- return [];
85
- return readRefLogAt(cwd, ref, head, parse);
86
- }
87
- export async function appendRefLogWithRetry(cwd, ref, subjectFor, parse, build, options = {}) {
88
- const maxRetries = options.maxRetries ?? 3;
89
- let currentHead = await resolveRefLogHead(cwd, ref);
90
- const startedMissing = currentHead === null;
91
- for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
92
- const entries = currentHead ? await readRefLogAt(cwd, ref, currentHead, parse) : [];
93
- const entry = await build(entries);
94
- if (entry === null)
95
- return { ok: false, reason: "aborted", currentHead };
96
- const newHead = await createRefLogCommit(cwd, {
97
- subject: subjectFor(entry),
98
- body: JSON.stringify(entry, null, 2),
99
- parentHead: currentHead,
100
- });
101
- const casOk = await casUpdateRefLog(cwd, ref, newHead, currentHead);
102
- if (casOk)
103
- return { ok: true, head: newHead };
104
- // Intentional degrade: re-resolve after CAS loss; missing-ref mid-flight is sticky
105
- // when the ref existed at start (do not resurrect).
106
- const resolved = await resolveRefLogHead(cwd, ref);
107
- if (resolved === null) {
108
- if (!startedMissing)
109
- return { ok: false, reason: "missing-ref" };
110
- currentHead = null;
111
- }
112
- else {
113
- currentHead = resolved;
114
- }
115
- }
116
- if (currentHead === null)
117
- return { ok: false, reason: "missing-ref" };
118
- return { ok: false, reason: "conflict", currentHead };
119
- }
@@ -1,128 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import * as fs from "node:fs/promises";
3
- import * as path from "node:path";
4
- import { FlowError } from "../flow-error.js";
5
- import { syncDirectory } from "../fs/durability.js";
6
- import { deleteRecoveryRef } from "../git/refs.js";
7
- import { KEIYAKU_FILE } from "../keiyaku.js";
8
- import { renderContractView } from "./contract-view.js";
9
- import { readLedger } from "./ledger.js";
10
- import { latestBase } from "./status/drift.js";
11
- import { findRegisteredContractWorktreePath } from "./worktree-path.js";
12
- const RENEW_SESSION_DIRECTORY = path.join(".keiyaku", "recovery");
13
- const RENEW_SESSION_FILE = "renew-session.json";
14
- function isSha(value) {
15
- return typeof value === "string" && /^[0-9a-f]{40}$/.test(value);
16
- }
17
- function parseSession(raw) {
18
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
19
- throw new FlowError("INTERNAL_STATE", "renew recovery session is malformed");
20
- }
21
- const value = raw;
22
- const keys = Object.keys(value).sort();
23
- const expected = ["commission", "newBase", "oldBase", "oldHead", "recoveryRef"];
24
- if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) {
25
- throw new FlowError("INTERNAL_STATE", "renew recovery session has unknown or missing fields");
26
- }
27
- if (typeof value.commission !== "string" || !value.commission ||
28
- !isSha(value.oldBase) || !isSha(value.oldHead) || !isSha(value.newBase) ||
29
- typeof value.recoveryRef !== "string" || !value.recoveryRef) {
30
- throw new FlowError("INTERNAL_STATE", "renew recovery session has invalid coordinates");
31
- }
32
- return value;
33
- }
34
- async function sessionPath(cwd, commission) {
35
- const worktree = await findRegisteredContractWorktreePath(cwd, commission);
36
- return worktree ? path.join(worktree, RENEW_SESSION_DIRECTORY, RENEW_SESSION_FILE) : undefined;
37
- }
38
- export async function readRenewRecoverySession(cwd, commission) {
39
- const filePath = await sessionPath(cwd, commission);
40
- if (!filePath)
41
- return null;
42
- try {
43
- return parseSession(JSON.parse(await fs.readFile(filePath, "utf8")));
44
- }
45
- catch (error) {
46
- if (error.code === "ENOENT")
47
- return null;
48
- if (error instanceof FlowError)
49
- throw error;
50
- throw new FlowError("INTERNAL_STATE", `cannot read renew recovery session for ${commission}`, { cause: error });
51
- }
52
- }
53
- export async function writeRenewRecoverySession(cwd, session) {
54
- const filePath = await sessionPath(cwd, session.commission);
55
- if (!filePath) {
56
- throw new FlowError("INTERNAL_STATE", `renew --resolve requires ${session.commission}'s registered contract worktree`);
57
- }
58
- const existing = await readRenewRecoverySession(cwd, session.commission);
59
- if (existing) {
60
- throw new FlowError("RENEW_SESSION_OPEN", `renew recovery session is already open for ${session.commission}`, {
61
- facts: { kind: "renew_session", commission: session.commission, nextAction: "continue-or-abort" },
62
- });
63
- }
64
- const directory = path.dirname(filePath);
65
- await fs.mkdir(directory, { recursive: true });
66
- const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
67
- await fs.writeFile(temporary, `${JSON.stringify(session)}\n`, { encoding: "utf8", flag: "wx" });
68
- await fs.rename(temporary, filePath);
69
- await syncDirectory(directory);
70
- return filePath;
71
- }
72
- export async function removeRenewRecoverySession(cwd, commission) {
73
- const filePath = await sessionPath(cwd, commission);
74
- if (!filePath)
75
- return;
76
- try {
77
- await fs.unlink(filePath);
78
- await syncDirectory(path.dirname(filePath));
79
- }
80
- catch (error) {
81
- if (error.code !== "ENOENT")
82
- throw error;
83
- }
84
- }
85
- function publishedRenewForSession(ledger, session) {
86
- const tail = ledger.entries.at(-1);
87
- if (session.commission !== ledger.contractId ||
88
- tail?.kind !== "renew" ||
89
- tail.data.oldBase !== session.oldBase ||
90
- tail.data.oldHead !== session.oldHead ||
91
- tail.data.newBase !== session.newBase ||
92
- latestBase(ledger.entries) !== session.newBase)
93
- return null;
94
- const previous = ledger.entries.at(-2);
95
- return {
96
- renew: tail,
97
- seal: previous?.kind === "seal" && previous.data.sealedBy === "renew" ? previous : null,
98
- };
99
- }
100
- export async function convergeRenewRecoverySession(cwd, commission) {
101
- const session = await readRenewRecoverySession(cwd, commission);
102
- if (!session)
103
- return { state: "none" };
104
- const ledger = await readLedger(cwd, commission);
105
- if (!ledger)
106
- return { state: "live", session };
107
- const published = publishedRenewForSession(ledger, session);
108
- if (!published)
109
- return { state: "live", session };
110
- const worktree = await findRegisteredContractWorktreePath(cwd, commission);
111
- if (!worktree) {
112
- throw new FlowError("INTERNAL_STATE", `registered contract worktree disappeared for spent renew session ${commission}`);
113
- }
114
- await fs.writeFile(path.join(worktree, KEIYAKU_FILE), renderContractView({ id: commission, entries: ledger.entries }), "utf8");
115
- const recoveryCleanup = await deleteRecoveryRef(cwd, session.recoveryRef, session.oldHead);
116
- await removeRenewRecoverySession(cwd, commission);
117
- return { state: "spent", session, ledger, ...published, recoveryCleanup };
118
- }
119
- export async function assertNoRenewRecoverySession(cwd, commission) {
120
- const state = await convergeRenewRecoverySession(cwd, commission);
121
- if (state.state === "none")
122
- return null;
123
- if (state.state === "spent")
124
- return state;
125
- throw new FlowError("RENEW_SESSION_OPEN", `renew recovery session is open for ${commission}`, {
126
- facts: { kind: "renew_session", commission, nextAction: "continue-or-abort" },
127
- });
128
- }
@@ -1 +0,0 @@
1
- export { readAllLedgerSnapshots as readStatusLedgers, } from "../ledger-batch.js";