@astrosheep/keiyaku 2.9.12 → 2.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +22 -18
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -1,211 +0,0 @@
1
- import * as path from "node:path";
2
- import { initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, isSqliteProcessLifetimeLockPlatformSupported, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "../sqlite-process-lifetime-lock.js";
3
- export const PROJECTION_RUNNER_LOCK_DATABASE = "leash";
4
- export class ProjectionRunnerLockUnavailableError extends Error {
5
- code = "PROJECTION_RUNNER_LOCK_UNAVAILABLE";
6
- constructor(message, options) {
7
- super(message, options);
8
- this.name = "ProjectionRunnerLockUnavailableError";
9
- }
10
- }
11
- export class ProjectionRunnerLockUnsupportedError extends Error {
12
- code = "PROJECTION_RUNNER_LOCK_UNSUPPORTED";
13
- constructor(platform) {
14
- super(`projection runner locks are unsupported on platform '${platform}'`);
15
- this.name = "ProjectionRunnerLockUnsupportedError";
16
- }
17
- }
18
- export function projectionRunnerLockPath(projectionDirectory) {
19
- return path.join(projectionDirectory, PROJECTION_RUNNER_LOCK_DATABASE);
20
- }
21
- export function isProjectionRunnerLockPlatformSupported(platform = process.platform) {
22
- return isSqliteProcessLifetimeLockPlatformSupported(platform);
23
- }
24
- function errorDetail(error) {
25
- return sqliteProcessLifetimeLockErrorDetail(error);
26
- }
27
- function isContention(error) {
28
- return isSqliteProcessLifetimeLockContention(error)
29
- || (error instanceof SqliteProcessLifetimeLockError && error.reason === "busy");
30
- }
31
- function openGuard(lockPath, busyTimeoutMs) {
32
- return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "deferred", busyTimeoutMs });
33
- }
34
- function openReservation(lockPath, busyTimeoutMs) {
35
- return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "immediate", busyTimeoutMs });
36
- }
37
- function closeTransaction(transaction) {
38
- try {
39
- transaction.close();
40
- }
41
- catch (error) {
42
- return error;
43
- }
44
- }
45
- function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
46
- let closed = false;
47
- let reservation = initialReservation;
48
- return {
49
- path: lockPath,
50
- releaseReservationForTransfer() {
51
- if (closed || reservation === undefined)
52
- return;
53
- const released = reservation;
54
- reservation = undefined;
55
- const failure = closeTransaction(released);
56
- if (failure !== undefined)
57
- throw new ProjectionRunnerLockUnavailableError(`projection runner reservation release failed: ${errorDetail(failure)}`, { cause: failure });
58
- },
59
- reclaimReservation() {
60
- if (closed || reservation !== undefined)
61
- return;
62
- try {
63
- reservation = openReservation(lockPath, busyTimeoutMs);
64
- }
65
- catch (error) {
66
- throw new ProjectionRunnerLockUnavailableError(`projection runner reservation reclaim failed: ${errorDetail(error)}`, { cause: error });
67
- }
68
- },
69
- close() {
70
- if (closed)
71
- return;
72
- closed = true;
73
- let failure;
74
- if (reservation !== undefined)
75
- failure = closeTransaction(reservation);
76
- failure ??= closeTransaction(guard);
77
- if (failure !== undefined)
78
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock release failed: ${errorDetail(failure)}`, { cause: failure });
79
- },
80
- };
81
- }
82
- function unavailable(operation, error) {
83
- return new ProjectionRunnerLockUnavailableError(`${operation}: ${errorDetail(error)}`, { cause: error });
84
- }
85
- /** Mint-time schema initialization retains no process-life authority. */
86
- export function initializeProjectionRunnerLockDatabase(projectionDirectory, platform = process.platform) {
87
- if (!isProjectionRunnerLockPlatformSupported(platform))
88
- throw new ProjectionRunnerLockUnsupportedError(platform);
89
- try {
90
- return initializeSqliteProcessLifetimeLockDatabase(projectionRunnerLockPath(projectionDirectory), platform);
91
- }
92
- catch (error) {
93
- throw unavailable("projection runner lock database could not be initialized", error);
94
- }
95
- }
96
- /**
97
- * Projection policy: hold a SHARED guard plus a RESERVED claim. The transfer
98
- * target overlaps its guard before the reservation moves, preserving exclusion.
99
- */
100
- export function acquireProjectionRunnerLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
101
- if (!isProjectionRunnerLockPlatformSupported(platform))
102
- throw new ProjectionRunnerLockUnsupportedError(platform);
103
- const lockPath = projectionRunnerLockPath(projectionDirectory);
104
- let guard;
105
- try {
106
- guard = openGuard(lockPath, busyTimeoutMs);
107
- }
108
- catch (error) {
109
- throw unavailable("projection runner lock database could not be opened", error);
110
- }
111
- try {
112
- return createHeldLock(lockPath, busyTimeoutMs, guard, openReservation(lockPath, busyTimeoutMs));
113
- }
114
- catch (error) {
115
- closeTransaction(guard);
116
- if (isContention(error))
117
- throw new ProjectionRunnerLockUnavailableError("projection runner lock is already held", { cause: error });
118
- throw unavailable("projection runner lock acquisition failed", error);
119
- }
120
- }
121
- export function acquireProjectionRunnerTransferTarget(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
122
- if (!isProjectionRunnerLockPlatformSupported(platform))
123
- throw new ProjectionRunnerLockUnsupportedError(platform);
124
- const lockPath = projectionRunnerLockPath(projectionDirectory);
125
- let guard;
126
- try {
127
- guard = openGuard(lockPath, busyTimeoutMs);
128
- }
129
- catch (error) {
130
- throw unavailable("projection runner transfer acquisition failed", error);
131
- }
132
- let closed = false;
133
- return {
134
- path: lockPath,
135
- promote() {
136
- if (closed)
137
- throw new ProjectionRunnerLockUnavailableError("projection runner transfer target is closed");
138
- try {
139
- const reservation = openReservation(lockPath, busyTimeoutMs);
140
- closed = true;
141
- return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
142
- }
143
- catch (error) {
144
- throw unavailable("projection runner transfer promotion failed", error);
145
- }
146
- },
147
- close() {
148
- if (closed)
149
- return;
150
- closed = true;
151
- const failure = closeTransaction(guard);
152
- if (failure !== undefined)
153
- throw unavailable("projection runner transfer release failed", failure);
154
- },
155
- };
156
- }
157
- /** Acquire positive death evidence. An active guard or transfer blocks this. */
158
- export function acquireProjectionRunnerSettlementLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
159
- if (!isProjectionRunnerLockPlatformSupported(platform))
160
- throw new ProjectionRunnerLockUnsupportedError(platform);
161
- const lockPath = projectionRunnerLockPath(projectionDirectory);
162
- let transaction;
163
- try {
164
- transaction = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs });
165
- }
166
- catch (error) {
167
- if (isContention(error))
168
- throw new ProjectionRunnerLockUnavailableError("projection runner lock is already held", { cause: error });
169
- throw unavailable("projection runner settlement lock acquisition failed", error);
170
- }
171
- let closed = false;
172
- return {
173
- path: lockPath,
174
- close() {
175
- if (closed)
176
- return;
177
- closed = true;
178
- const failure = closeTransaction(transaction);
179
- if (failure !== undefined)
180
- throw unavailable("projection runner settlement lock release failed", failure);
181
- },
182
- };
183
- }
184
- /** Positive death probe without creating a missing lock database. */
185
- export function observeProjectionRunnerLock(projectionDirectory, platform = process.platform) {
186
- if (!isProjectionRunnerLockPlatformSupported(platform)) {
187
- return { state: "unknown", reason: "unsupported-platform", detail: `projection runner locks are unsupported on platform '${platform}'` };
188
- }
189
- const lockPath = projectionRunnerLockPath(projectionDirectory);
190
- let probe;
191
- try {
192
- probe = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs: 0 });
193
- return { state: "released" };
194
- }
195
- catch (error) {
196
- if (isContention(error))
197
- return { state: "held" };
198
- const detail = errorDetail(error);
199
- return {
200
- state: "unknown",
201
- reason: detail.includes("rollback-journal") ? "unsupported-journal-mode" : "probe-failed",
202
- detail,
203
- };
204
- }
205
- finally {
206
- try {
207
- probe?.close();
208
- }
209
- catch { /* no probe cleanup result is death evidence */ }
210
- }
211
- }
@@ -1,127 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { DatabaseSync } from "node:sqlite";
4
- import { ProjectionStateError } from "../../atomic-publish.js";
5
- import { PROJECTION_GENERATION_STORE_FILE, } from "../generation/store.js";
6
- import { configureHeartBusyTimeout } from "../heart.js";
7
- function databasePath(projectionDirectory) {
8
- return path.join(projectionDirectory, PROJECTION_GENERATION_STORE_FILE);
9
- }
10
- function openDatabase(projectionDirectory, readOnly) {
11
- const target = databasePath(projectionDirectory);
12
- if (!fs.lstatSync(target).isFile()) {
13
- throw new ProjectionStateError("projection heart must be a regular file");
14
- }
15
- const database = new DatabaseSync(target, {
16
- readOnly,
17
- enableForeignKeyConstraints: true,
18
- enableDoubleQuotedStringLiterals: false,
19
- allowExtension: false,
20
- });
21
- configureHeartBusyTimeout(database);
22
- if (!readOnly) {
23
- database.exec("PRAGMA foreign_keys = ON");
24
- database.exec("PRAGMA trusted_schema = OFF");
25
- }
26
- return database;
27
- }
28
- function rollbackQuietly(database) {
29
- try {
30
- database.exec("ROLLBACK");
31
- }
32
- catch {
33
- // Preserve the transition failure.
34
- }
35
- }
36
- export function withRead(projectionDirectory, read) {
37
- const database = openDatabase(projectionDirectory, true);
38
- try {
39
- return read(database);
40
- }
41
- finally {
42
- database.close();
43
- }
44
- }
45
- export function withReadSnapshot(projectionDirectory, read) {
46
- const database = openDatabase(projectionDirectory, true);
47
- try {
48
- database.exec("BEGIN");
49
- try {
50
- const result = read(database);
51
- database.exec("COMMIT");
52
- return result;
53
- }
54
- catch (error) {
55
- rollbackQuietly(database);
56
- throw error;
57
- }
58
- }
59
- finally {
60
- database.close();
61
- }
62
- }
63
- export function withWrite(projectionDirectory, write) {
64
- const database = openDatabase(projectionDirectory, false);
65
- try {
66
- database.exec("BEGIN IMMEDIATE");
67
- try {
68
- const result = write(database);
69
- database.exec("COMMIT");
70
- return result;
71
- }
72
- catch (error) {
73
- rollbackQuietly(database);
74
- throw error;
75
- }
76
- }
77
- finally {
78
- database.close();
79
- }
80
- }
81
- export function openProjectionTellObserver(projectionDirectory) {
82
- const database = openDatabase(projectionDirectory, true);
83
- let closed = false;
84
- let observedDataVersion;
85
- const assertOpen = () => {
86
- if (closed)
87
- throw new ProjectionStateError("projection tell observer is closed");
88
- };
89
- const readDataVersion = () => {
90
- assertOpen();
91
- const row = database.prepare("PRAGMA data_version").get();
92
- const raw = row?.data_version;
93
- const value = typeof raw === "bigint" ? Number(raw) : raw;
94
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
95
- throw new ProjectionStateError("PRAGMA data_version returned an invalid value");
96
- }
97
- return value;
98
- };
99
- return {
100
- hasDataVersionChanged() {
101
- const current = readDataVersion();
102
- if (observedDataVersion === undefined) {
103
- observedDataVersion = current;
104
- return true;
105
- }
106
- if (current === observedDataVersion)
107
- return false;
108
- observedDataVersion = current;
109
- return true;
110
- },
111
- hasInbox() {
112
- assertOpen();
113
- return database.prepare(`
114
- SELECT 1
115
- FROM projection_tells
116
- WHERE state = 'inbox'
117
- LIMIT 1
118
- `).get() !== undefined;
119
- },
120
- close() {
121
- if (closed)
122
- return;
123
- closed = true;
124
- database.close();
125
- },
126
- };
127
- }
@@ -1,305 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import { DatabaseSync } from "node:sqlite";
5
- import { z } from "zod";
6
- const COORDINATION_SCHEMA_VERSION = 1;
7
- const MAX_RECEIPT_BYTES = 1_048_576;
8
- const DATABASE_BUSY_TIMEOUT_MS = 5_000;
9
- export const VERIFICATION_COMPLETED_RECEIPT_LIMIT = 8;
10
- const outputTailSchema = z.object({
11
- tail: z.string(),
12
- truncated: z.boolean(),
13
- omittedBytes: z.number().int().nonnegative(),
14
- }).strict();
15
- const executionSchema = z.discriminatedUnion("status", [
16
- z.object({
17
- status: z.literal("executed"),
18
- code: z.number().int(),
19
- durationMs: z.number().nonnegative(),
20
- stdout: outputTailSchema,
21
- stderr: outputTailSchema,
22
- }).strict(),
23
- z.object({
24
- status: z.literal("runner_failed"),
25
- durationMs: z.number().nonnegative(),
26
- stdout: outputTailSchema,
27
- stderr: outputTailSchema,
28
- cause: z.object({
29
- code: z.enum(["SPAWN_FAILED", "PROCESS_TERMINATED", "TIMED_OUT", "CANCELLED", "TREE_TERMINATION_FAILED"]),
30
- message: z.string(),
31
- }).strict(),
32
- }).strict(),
33
- ]);
34
- const resultSchema = z.object({
35
- source: z.enum(["contract", "repository-gate"]),
36
- label: z.string(),
37
- declaration: z.string(),
38
- command: z.string(),
39
- args: z.array(z.string()),
40
- shell: z.boolean(),
41
- timeoutMs: z.number().int().positive(),
42
- candidate: z.object({ head: z.string(), tree: z.string() }).strict(),
43
- executor: z.string(),
44
- execution: executionSchema,
45
- }).strict();
46
- const receiptSchema = z.object({
47
- version: z.literal(COORDINATION_SCHEMA_VERSION),
48
- results: z.array(resultSchema),
49
- }).strict();
50
- function processIsLive(pid) {
51
- if (pid <= 0)
52
- return false;
53
- try {
54
- process.kill(pid, 0);
55
- return true;
56
- }
57
- catch (error) {
58
- return !(error && typeof error === "object" && "code" in error && error.code === "ESRCH");
59
- }
60
- }
61
- function rollbackQuietly(database) {
62
- try {
63
- database.exec("ROLLBACK");
64
- }
65
- catch { }
66
- }
67
- function pragmaNumber(database, name) {
68
- const row = database.prepare(`PRAGMA ${name}`).get();
69
- const value = row ? Object.values(row)[0] : undefined;
70
- if (typeof value !== "number" && typeof value !== "bigint") {
71
- throw new Error(`verification coordination PRAGMA ${name} is invalid`);
72
- }
73
- return Number(value);
74
- }
75
- function initializeOrValidate(database) {
76
- database.exec("BEGIN EXCLUSIVE");
77
- try {
78
- const version = pragmaNumber(database, "user_version");
79
- const objects = database.prepare(`
80
- SELECT name, type FROM sqlite_schema
81
- WHERE name NOT LIKE 'sqlite_%' ORDER BY name
82
- `).all();
83
- if (version === 0 && objects.length === 0) {
84
- database.exec(`
85
- CREATE TABLE verification_coordination_schema (
86
- singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
87
- version INTEGER NOT NULL CHECK (version = ${COORDINATION_SCHEMA_VERSION})
88
- ) STRICT;
89
- CREATE TABLE verification_executions (
90
- fingerprint TEXT PRIMARY KEY CHECK (length(fingerprint) = 64),
91
- owner_token TEXT NOT NULL CHECK (length(owner_token) > 0),
92
- owner_pid INTEGER NOT NULL CHECK (owner_pid >= 0),
93
- started_at_ms INTEGER NOT NULL CHECK (started_at_ms >= 0),
94
- supervisor_pid INTEGER CHECK (supervisor_pid IS NULL OR supervisor_pid > 0),
95
- supervisor_platform TEXT CHECK (supervisor_platform IS NULL OR supervisor_platform IN ('posix', 'win32')),
96
- completed_at_ms INTEGER CHECK (completed_at_ms IS NULL OR completed_at_ms >= 0),
97
- receipt_json TEXT CHECK (receipt_json IS NULL OR json_valid(receipt_json)),
98
- CHECK ((supervisor_pid IS NULL) = (supervisor_platform IS NULL)),
99
- CHECK ((receipt_json IS NULL) = (completed_at_ms IS NULL)),
100
- CHECK (receipt_json IS NULL OR (owner_pid = 0 AND supervisor_pid IS NULL))
101
- ) STRICT;
102
- `);
103
- database.prepare("INSERT INTO verification_coordination_schema (singleton, version) VALUES (1, ?)")
104
- .run(COORDINATION_SCHEMA_VERSION);
105
- database.exec(`PRAGMA user_version = ${COORDINATION_SCHEMA_VERSION}`);
106
- }
107
- else if (version !== COORDINATION_SCHEMA_VERSION) {
108
- throw new Error(`incompatible verification coordination schema version ${version}; expected ${COORDINATION_SCHEMA_VERSION}`);
109
- }
110
- const signature = database.prepare(`
111
- SELECT name, type FROM sqlite_schema
112
- WHERE name NOT LIKE 'sqlite_%' ORDER BY name
113
- `).all().map((object) => {
114
- const row = object;
115
- return `${row.type}:${row.name}`;
116
- }).join(",");
117
- if (signature !== "table:verification_coordination_schema,table:verification_executions") {
118
- throw new Error(`verification coordination schema contains unexpected objects: ${signature || "none"}`);
119
- }
120
- const columns = database.prepare("PRAGMA table_info(verification_executions)").all();
121
- const columnSignature = columns
122
- .map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
123
- .join(",");
124
- if (columnSignature !== "fingerprint:TEXT:1:1,owner_token:TEXT:1:0,owner_pid:INTEGER:1:0,started_at_ms:INTEGER:1:0,supervisor_pid:INTEGER:0:0,supervisor_platform:TEXT:0:0,completed_at_ms:INTEGER:0:0,receipt_json:TEXT:0:0") {
125
- throw new Error("verification coordination execution schema is malformed");
126
- }
127
- const metadata = database.prepare("SELECT singleton, version FROM verification_coordination_schema").all();
128
- if (metadata.length !== 1 || Number(metadata[0].singleton) !== 1 || Number(metadata[0].version) !== COORDINATION_SCHEMA_VERSION) {
129
- throw new Error("verification coordination schema metadata is malformed");
130
- }
131
- const quickCheck = database.prepare("PRAGMA quick_check").get();
132
- if (!quickCheck || Object.values(quickCheck)[0] !== "ok") {
133
- throw new Error("verification coordination database failed quick_check");
134
- }
135
- database.exec("COMMIT");
136
- }
137
- catch (error) {
138
- rollbackQuietly(database);
139
- throw error;
140
- }
141
- }
142
- function parseReceipt(raw) {
143
- if (Buffer.byteLength(raw) > MAX_RECEIPT_BYTES)
144
- throw new Error("verification receipt exceeds its size bound");
145
- return receiptSchema.parse(JSON.parse(raw)).results;
146
- }
147
- function serializeReceipt(results) {
148
- const raw = JSON.stringify(receiptSchema.parse({ version: COORDINATION_SCHEMA_VERSION, results }));
149
- if (Buffer.byteLength(raw) > MAX_RECEIPT_BYTES)
150
- throw new Error("verification receipt exceeds its size bound");
151
- return raw;
152
- }
153
- export class VerificationCoordination {
154
- #databasePath;
155
- constructor(repositoryRoot) {
156
- const directory = path.join(repositoryRoot, ".keiyaku", "runtime");
157
- fs.mkdirSync(directory, { recursive: true });
158
- this.#databasePath = path.join(directory, "verification.sqlite");
159
- this.#withDatabase(() => undefined);
160
- }
161
- fingerprint(key) {
162
- return createHash("sha256").update(key).digest("hex");
163
- }
164
- claim(fingerprint, staleAfterMs, nowMs = Date.now()) {
165
- return this.#withTransaction((database) => {
166
- const row = database.prepare(`
167
- SELECT owner_token, owner_pid, started_at_ms, supervisor_pid, supervisor_platform, completed_at_ms, receipt_json
168
- FROM verification_executions WHERE fingerprint = ?
169
- `).get(fingerprint);
170
- if (row?.receipt_json !== null && row?.receipt_json !== undefined) {
171
- return { status: "receipt", results: parseReceipt(row.receipt_json) };
172
- }
173
- const token = randomUUID();
174
- if (!row) {
175
- database.prepare(`
176
- INSERT INTO verification_executions
177
- (fingerprint, owner_token, owner_pid, started_at_ms, supervisor_pid, supervisor_platform, completed_at_ms, receipt_json)
178
- VALUES (?, ?, ?, ?, NULL, NULL, NULL, NULL)
179
- `).run(fingerprint, token, process.pid, nowMs);
180
- return { status: "owner", token };
181
- }
182
- const ownerPid = Number(row.owner_pid);
183
- const stale = nowMs - Number(row.started_at_ms) > staleAfterMs;
184
- if (processIsLive(ownerPid) && !stale)
185
- return { status: "wait" };
186
- database.prepare(`
187
- UPDATE verification_executions
188
- SET owner_token = ?, owner_pid = ?, started_at_ms = ?
189
- WHERE fingerprint = ?
190
- `).run(token, process.pid, nowMs, fingerprint);
191
- const platform = row.supervisor_platform;
192
- const supervisorPid = row.supervisor_pid === null ? undefined : Number(row.supervisor_pid);
193
- return {
194
- status: "owner",
195
- token,
196
- ...(supervisorPid && (platform === "posix" || platform === "win32")
197
- ? { priorSupervisor: { pid: supervisorPid, platform } }
198
- : {}),
199
- };
200
- });
201
- }
202
- recordSupervisor(fingerprint, token, identity) {
203
- this.#ownedUpdate(fingerprint, token, (database) => {
204
- database.prepare(`
205
- UPDATE verification_executions SET supervisor_pid = ?, supervisor_platform = ?
206
- WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
207
- `).run(identity.pid, identity.platform, fingerprint, token);
208
- });
209
- }
210
- clearSupervisor(fingerprint, token) {
211
- this.#ownedUpdate(fingerprint, token, (database) => {
212
- database.prepare(`
213
- UPDATE verification_executions SET supervisor_pid = NULL, supervisor_platform = NULL
214
- WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
215
- `).run(fingerprint, token);
216
- });
217
- }
218
- complete(fingerprint, token, results, completedAtMs = Date.now()) {
219
- const recoveryRequired = results.some((result) => result.execution.status === "runner_failed"
220
- && result.execution.cause.code === "TREE_TERMINATION_FAILED");
221
- return this.#withTransaction((database) => {
222
- if (recoveryRequired) {
223
- const changed = database.prepare(`
224
- UPDATE verification_executions
225
- SET owner_pid = 0, started_at_ms = 0
226
- WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
227
- `).run(fingerprint, token).changes;
228
- return Number(changed) === 1
229
- ? { status: "recovery-required" }
230
- : { status: "ownership-lost" };
231
- }
232
- const receipt = serializeReceipt(results);
233
- const changed = database.prepare(`
234
- UPDATE verification_executions
235
- SET owner_pid = 0, supervisor_pid = NULL, supervisor_platform = NULL, completed_at_ms = ?, receipt_json = ?
236
- WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
237
- `).run(completedAtMs, receipt, fingerprint, token).changes;
238
- if (Number(changed) !== 1)
239
- return { status: "ownership-lost" };
240
- const countRow = database.prepare(`
241
- SELECT count(*) AS count FROM verification_executions WHERE receipt_json IS NOT NULL
242
- `).get();
243
- const excess = Number(countRow.count) - VERIFICATION_COMPLETED_RECEIPT_LIMIT;
244
- if (excess > 0) {
245
- database.prepare(`
246
- DELETE FROM verification_executions
247
- WHERE fingerprint IN (
248
- SELECT fingerprint FROM verification_executions
249
- WHERE receipt_json IS NOT NULL
250
- ORDER BY completed_at_ms ASC, fingerprint ASC
251
- LIMIT ?
252
- )
253
- `).run(excess);
254
- }
255
- return { status: "published" };
256
- });
257
- }
258
- releaseForTakeover(fingerprint, token) {
259
- this.#withTransaction((database) => {
260
- database.prepare(`
261
- UPDATE verification_executions SET owner_pid = 0, started_at_ms = 0
262
- WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
263
- `).run(fingerprint, token);
264
- });
265
- }
266
- #ownedUpdate(fingerprint, token, update) {
267
- this.#withTransaction((database) => {
268
- update(database);
269
- const row = database.prepare(`
270
- SELECT owner_token FROM verification_executions
271
- WHERE fingerprint = ? AND receipt_json IS NULL
272
- `).get(fingerprint);
273
- if (row?.owner_token !== token)
274
- throw new Error("verification coordination ownership changed");
275
- });
276
- }
277
- #withTransaction(operation) {
278
- return this.#withDatabase((database) => {
279
- database.exec("BEGIN IMMEDIATE");
280
- try {
281
- const result = operation(database);
282
- database.exec("COMMIT");
283
- return result;
284
- }
285
- catch (error) {
286
- rollbackQuietly(database);
287
- throw error;
288
- }
289
- });
290
- }
291
- #withDatabase(operation) {
292
- const database = new DatabaseSync(this.#databasePath, {
293
- enableDoubleQuotedStringLiterals: false,
294
- allowExtension: false,
295
- });
296
- try {
297
- database.exec(`PRAGMA busy_timeout = ${DATABASE_BUSY_TIMEOUT_MS}`);
298
- initializeOrValidate(database);
299
- return operation(database);
300
- }
301
- finally {
302
- database.close();
303
- }
304
- }
305
- }