@astrosheep/keiyaku 2.9.11 → 2.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +79 -85
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/bind/meta.js +4 -4
  17. package/build/cli/commands/contract/petition/handler.js +2 -1
  18. package/build/cli/commands/contract/petition/meta.js +2 -2
  19. package/build/cli/commands/contract/renew/handler.js +44 -3
  20. package/build/cli/commands/contract/renew/meta.js +5 -4
  21. package/build/cli/commands/metadata.js +14 -10
  22. package/build/cli/commands/projection/call/handler.js +1 -1
  23. package/build/cli/commands/projection/call/meta.js +1 -1
  24. package/build/cli/commands/projection/catalog.js +2 -2
  25. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  26. package/build/cli/commands/projection/fork/meta.js +12 -0
  27. package/build/cli/commands/projection/history/handler.js +46 -2
  28. package/build/cli/commands/projection/history/meta.js +3 -3
  29. package/build/cli/commands/projection/kill/handler.js +4 -5
  30. package/build/cli/commands/projection/kill/meta.js +2 -2
  31. package/build/cli/commands/projection/status/handler.js +29 -13
  32. package/build/cli/commands/projection/status/meta.js +14 -3
  33. package/build/cli/commands/projection/tell/handler.js +8 -4
  34. package/build/cli/commands/projection/tell/meta.js +1 -1
  35. package/build/cli/commands/projection/wait/handler.js +45 -20
  36. package/build/cli/commands/projection/wait/meta.js +1 -1
  37. package/build/cli/commands/shared.js +2 -2
  38. package/build/cli/commands/system/catalog.js +2 -0
  39. package/build/cli/commands/task/add/handler.js +28 -12
  40. package/build/cli/commands/task/add/meta.js +18 -6
  41. package/build/cli/commands/task/catalog.js +4 -0
  42. package/build/cli/commands/task/compose/handler.js +18 -0
  43. package/build/cli/commands/task/compose/meta.js +10 -0
  44. package/build/cli/commands/task/doctor/handler.js +2 -3
  45. package/build/cli/commands/task/doctor/meta.js +2 -2
  46. package/build/cli/commands/task/done/meta.js +2 -2
  47. package/build/cli/commands/task/drop/handler.js +7 -2
  48. package/build/cli/commands/task/drop/meta.js +6 -3
  49. package/build/cli/commands/task/hold/meta.js +2 -2
  50. package/build/cli/commands/task/log/meta.js +2 -2
  51. package/build/cli/commands/task/ls/meta.js +2 -2
  52. package/build/cli/commands/task/note/handler.js +6 -0
  53. package/build/cli/commands/task/note/meta.js +11 -0
  54. package/build/cli/commands/task/resume/meta.js +2 -2
  55. package/build/cli/commands/task/shared.js +29 -15
  56. package/build/cli/commands/task/show/meta.js +2 -2
  57. package/build/cli/commands/task/start/meta.js +2 -2
  58. package/build/cli/commands/task/stop/meta.js +2 -2
  59. package/build/cli/commands/task/update/meta.js +3 -2
  60. package/build/cli/commands/verification/handler.js +43 -0
  61. package/build/cli/commands/verification/meta.js +13 -0
  62. package/build/cli/completion.js +16 -13
  63. package/build/cli/flags.js +48 -12
  64. package/build/cli/help.js +29 -30
  65. package/build/cli/index.js +14 -8
  66. package/build/cli/parse-flags.js +60 -21
  67. package/build/cli/parse-metadata.js +10 -4
  68. package/build/cli/parse-selectors.js +22 -4
  69. package/build/cli/parse.js +43 -30
  70. package/build/cli/projection-address.js +7 -7
  71. package/build/cli/render/arc.js +12 -3
  72. package/build/cli/render/audit.js +2 -2
  73. package/build/cli/render/call.js +4 -4
  74. package/build/cli/render/kanshi.js +122 -85
  75. package/build/cli/render/misc.js +2 -2
  76. package/build/cli/render/petition.js +16 -1
  77. package/build/cli/render/projection-history.js +17 -4
  78. package/build/cli/render/shared.js +5 -5
  79. package/build/cli/render/status-indicator.js +47 -0
  80. package/build/cli/render/status.js +106 -76
  81. package/build/cli/render/success-response.js +20 -5
  82. package/build/cli/render/task.js +33 -5
  83. package/build/cli/render/tell.js +10 -4
  84. package/build/cli/render/tool-command-normalization.js +137 -0
  85. package/build/cli/render/tool-presentation.js +2 -1
  86. package/build/cli/render/verification.js +23 -0
  87. package/build/cli/render/wait.js +101 -33
  88. package/build/cli/types.js +2 -1
  89. package/build/config/akuma-loader.js +21 -1
  90. package/build/config/settings/knobs.js +7 -1
  91. package/build/config/settings/schema.js +10 -5
  92. package/build/core/addressing.js +3 -3
  93. package/build/core/amend.js +102 -17
  94. package/build/core/arc.js +4 -4
  95. package/build/core/audit/candidate.js +10 -18
  96. package/build/core/audit/coordinates.js +12 -17
  97. package/build/core/audit/evidence.js +6 -4
  98. package/build/core/audit/facade.js +3 -3
  99. package/build/core/audit/report.js +1 -1
  100. package/build/core/bind-reconciliation.js +42 -23
  101. package/build/core/bind.js +182 -81
  102. package/build/core/call/call.js +6 -7
  103. package/build/core/call/context.js +46 -33
  104. package/build/core/call/execution.js +21 -6
  105. package/build/core/call/prompt.js +11 -42
  106. package/build/core/contract-carrier-runtime.js +209 -105
  107. package/build/core/contract-carrier.js +156 -73
  108. package/build/core/contract-view.js +43 -6
  109. package/build/core/contract.js +2 -1
  110. package/build/core/derived-replay.js +168 -35
  111. package/build/core/draft.js +60 -27
  112. package/build/core/forfeit.js +1 -1
  113. package/build/core/lifecycle-history.js +13 -0
  114. package/build/core/lifecycle-recovery.js +32 -27
  115. package/build/core/lifecycle-runner.js +14 -14
  116. package/build/core/log.js +1 -1
  117. package/build/core/outcome-base.js +2 -2
  118. package/build/core/projection/akuma-name.js +43 -0
  119. package/build/core/projection/generation/database.js +141 -79
  120. package/build/core/projection/generation/ledger.js +91 -0
  121. package/build/core/projection/generation/model.js +19 -14
  122. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  123. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  124. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  125. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  126. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  127. package/build/core/projection/generation/projection-generation-process.js +16 -15
  128. package/build/core/projection/generation/projection-generation-runner.js +132 -76
  129. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  130. package/build/core/projection/generation/protocol.js +68 -0
  131. package/build/core/projection/generation/store.js +17 -7
  132. package/build/core/projection/generation/transitions.js +24 -14
  133. package/build/core/projection/index.js +5 -5
  134. package/build/core/projection/projection-core.js +6 -2
  135. package/build/core/projection/projection-execution-observer.js +1 -1
  136. package/build/core/projection/projection-history.js +8 -8
  137. package/build/core/projection/projection-kill.js +20 -10
  138. package/build/core/projection/projection-life-observer.js +8 -8
  139. package/build/core/projection/projection-mint.js +6 -8
  140. package/build/core/projection/projection-status-observation.js +77 -8
  141. package/build/core/projection/projection-status.js +151 -4
  142. package/build/core/projection/projection-wait.js +48 -29
  143. package/build/core/projection/projection-wake.js +28 -88
  144. package/build/core/projection/tell/launch-store.js +1 -1
  145. package/build/core/projection/tell/store.js +1 -1
  146. package/build/core/renew-build.js +204 -74
  147. package/build/core/renew-plan.js +34 -9
  148. package/build/core/renew-rewrite.js +56 -15
  149. package/build/core/renew.js +39 -35
  150. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  151. package/build/core/repository-ledger/claim-evidence.js +1 -1
  152. package/build/core/repository-ledger/codec.js +11 -5
  153. package/build/core/repository-ledger/current-state-store.js +464 -349
  154. package/build/core/repository-ledger/fold-repository.js +5 -6
  155. package/build/core/repository-ledger/identity.js +60 -0
  156. package/build/core/repository-ledger/inventory.js +3 -3
  157. package/build/core/repository-ledger/read-model.js +57 -9
  158. package/build/core/repository-ledger/write-transaction.js +24 -17
  159. package/build/core/run-control/connection-bound-close.js +41 -0
  160. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  161. package/build/core/run-control/detached-process.js +14 -0
  162. package/build/core/run-control/index.js +6 -0
  163. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  164. package/build/core/run-control/runner-lease.js +172 -0
  165. package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
  166. package/build/core/seal.js +44 -32
  167. package/build/core/settlement/claim-delivery.js +22 -9
  168. package/build/core/settlement/claim.js +8 -6
  169. package/build/core/settlement/petition-claim-gates.js +9 -8
  170. package/build/core/settlement/petition-head-guard.js +5 -6
  171. package/build/core/settlement/petition-preview.js +21 -5
  172. package/build/core/settlement/petition.js +51 -17
  173. package/build/core/settlement/settlement.js +20 -15
  174. package/build/core/settlement/verdict.js +2 -2
  175. package/build/core/settlement/verification.js +492 -364
  176. package/build/core/status/board.js +184 -75
  177. package/build/core/status/drift.js +8 -10
  178. package/build/core/task/board.js +1 -0
  179. package/build/core/task/commands.js +25 -0
  180. package/build/core/task/compose.js +328 -0
  181. package/build/core/task/document.js +102 -39
  182. package/build/core/task/index.js +4 -3
  183. package/build/core/task/settlement-git.js +70 -30
  184. package/build/core/task/settlement-policy.js +27 -14
  185. package/build/core/task/source-board.js +1 -0
  186. package/build/core/task/task-bind-preparation.js +59 -9
  187. package/build/core/task/task-contract.js +2 -2
  188. package/build/core/task/task-git-store.js +1 -0
  189. package/build/core/task/task-store-repository.js +2 -0
  190. package/build/core/task/task.js +2 -2
  191. package/build/core/transcripts.js +4 -4
  192. package/build/core/verification-declaration.js +95 -0
  193. package/build/core/worktree-bootstrap.js +1 -1
  194. package/build/core/worktree-path.js +7 -11
  195. package/build/flow-error.js +15 -2
  196. package/build/generated/version.js +2 -2
  197. package/build/git/branches.js +41 -70
  198. package/build/git/commits.js +92 -21
  199. package/build/git/core.js +9 -161
  200. package/build/git/diff/preview.js +4 -4
  201. package/build/git/diff/read.js +4 -4
  202. package/build/git/diff/structured.js +5 -5
  203. package/build/git/process.js +229 -0
  204. package/build/git/refs.js +30 -5
  205. package/build/git/staging.js +39 -115
  206. package/build/git/streaming-batch.js +9 -16
  207. package/build/git/worktree.js +104 -91
  208. package/build/index.js +7 -0
  209. package/package.json +2 -2
  210. package/skills/keiyaku-akuma/SKILL.md +27 -21
  211. package/skills/keiyaku-task/SKILL.md +61 -5
  212. package/skills/keiyaku-workflow/SKILL.md +81 -26
  213. package/build/cli/commands/projection/revive/meta.js +0 -12
  214. package/build/core/projection/projection-life-protocol.js +0 -115
  215. package/build/core/projection/projection-runner-lock.js +0 -382
  216. package/build/core/projection/tell/database.js +0 -127
  217. package/build/core/settlement/verification-coordination.js +0 -305
  218. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -0,0 +1,172 @@
1
+ import { initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "./sqlite-process-lifetime-lock.js";
2
+ export class RunnerLeaseUnavailableError extends Error {
3
+ code = "RUNNER_LEASE_UNAVAILABLE";
4
+ constructor(message, options) {
5
+ super(message, options);
6
+ this.name = "RunnerLeaseUnavailableError";
7
+ }
8
+ }
9
+ function errorDetail(error) {
10
+ return sqliteProcessLifetimeLockErrorDetail(error);
11
+ }
12
+ function isContention(error) {
13
+ return isSqliteProcessLifetimeLockContention(error)
14
+ || (error instanceof SqliteProcessLifetimeLockError && error.reason === "busy");
15
+ }
16
+ function openGuard(lockPath, busyTimeoutMs) {
17
+ return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "deferred", busyTimeoutMs });
18
+ }
19
+ function openReservation(lockPath, busyTimeoutMs) {
20
+ return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "immediate", busyTimeoutMs });
21
+ }
22
+ function closeTransaction(transaction) {
23
+ try {
24
+ transaction.close();
25
+ }
26
+ catch (error) {
27
+ return error;
28
+ }
29
+ }
30
+ function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
31
+ let closed = false;
32
+ let reservation = initialReservation;
33
+ return {
34
+ releaseReservationForTransfer() {
35
+ if (closed || reservation === undefined)
36
+ return;
37
+ const released = reservation;
38
+ reservation = undefined;
39
+ const failure = closeTransaction(released);
40
+ if (failure !== undefined)
41
+ throw new RunnerLeaseUnavailableError(`runner lease reservation release failed: ${errorDetail(failure)}`, { cause: failure });
42
+ },
43
+ reclaimReservation() {
44
+ if (closed || reservation !== undefined)
45
+ return;
46
+ try {
47
+ reservation = openReservation(lockPath, busyTimeoutMs);
48
+ }
49
+ catch (error) {
50
+ throw new RunnerLeaseUnavailableError(`runner lease reservation reclaim failed: ${errorDetail(error)}`, { cause: error });
51
+ }
52
+ },
53
+ close() {
54
+ if (closed)
55
+ return;
56
+ closed = true;
57
+ let failure;
58
+ if (reservation !== undefined)
59
+ failure = closeTransaction(reservation);
60
+ failure ??= closeTransaction(guard);
61
+ if (failure !== undefined)
62
+ throw new RunnerLeaseUnavailableError(`runner lease release failed: ${errorDetail(failure)}`, { cause: failure });
63
+ },
64
+ };
65
+ }
66
+ function unavailable(operation, error) {
67
+ return new RunnerLeaseUnavailableError(`${operation}: ${errorDetail(error)}`, { cause: error });
68
+ }
69
+ export function initializeRunnerLeaseDatabase(leasePath) {
70
+ try {
71
+ return initializeSqliteProcessLifetimeLockDatabase(leasePath);
72
+ }
73
+ catch (error) {
74
+ throw unavailable("runner lease database could not be initialized", error);
75
+ }
76
+ }
77
+ export function acquireRunnerLease(lockPath, busyTimeoutMs = 0) {
78
+ let guard;
79
+ try {
80
+ guard = openGuard(lockPath, busyTimeoutMs);
81
+ }
82
+ catch (error) {
83
+ throw unavailable("runner lease database could not be opened", error);
84
+ }
85
+ try {
86
+ return createHeldLock(lockPath, busyTimeoutMs, guard, openReservation(lockPath, busyTimeoutMs));
87
+ }
88
+ catch (error) {
89
+ closeTransaction(guard);
90
+ if (isContention(error))
91
+ throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
92
+ throw unavailable("runner lease acquisition failed", error);
93
+ }
94
+ }
95
+ export function acquireRunnerLeaseTransferTarget(lockPath, busyTimeoutMs = 0) {
96
+ let guard;
97
+ try {
98
+ guard = openGuard(lockPath, busyTimeoutMs);
99
+ }
100
+ catch (error) {
101
+ throw unavailable("runner lease transfer acquisition failed", error);
102
+ }
103
+ let closed = false;
104
+ return {
105
+ promote() {
106
+ if (closed)
107
+ throw new RunnerLeaseUnavailableError("runner lease transfer target is closed");
108
+ try {
109
+ const reservation = openReservation(lockPath, busyTimeoutMs);
110
+ closed = true;
111
+ return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
112
+ }
113
+ catch (error) {
114
+ throw unavailable("runner lease transfer promotion failed", error);
115
+ }
116
+ },
117
+ close() {
118
+ if (closed)
119
+ return;
120
+ closed = true;
121
+ const failure = closeTransaction(guard);
122
+ if (failure !== undefined)
123
+ throw unavailable("runner lease transfer release failed", failure);
124
+ },
125
+ };
126
+ }
127
+ export function acquireRunnerLostProof(lockPath, busyTimeoutMs = 0) {
128
+ let transaction;
129
+ try {
130
+ transaction = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs });
131
+ }
132
+ catch (error) {
133
+ if (isContention(error))
134
+ throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
135
+ throw unavailable("runner lost proof acquisition failed", error);
136
+ }
137
+ let closed = false;
138
+ return {
139
+ close() {
140
+ if (closed)
141
+ return;
142
+ closed = true;
143
+ const failure = closeTransaction(transaction);
144
+ if (failure !== undefined)
145
+ throw unavailable("runner lost proof release failed", failure);
146
+ },
147
+ };
148
+ }
149
+ /** Positive death probe without creating a missing lock database. */
150
+ export function observeRunnerLease(lockPath) {
151
+ let probe;
152
+ try {
153
+ probe = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs: 0 });
154
+ return { state: "released" };
155
+ }
156
+ catch (error) {
157
+ if (isContention(error))
158
+ return { state: "held" };
159
+ const detail = errorDetail(error);
160
+ return {
161
+ state: "unknown",
162
+ reason: detail.includes("rollback-journal") ? "unsupported-journal-mode" : "probe-failed",
163
+ detail,
164
+ };
165
+ }
166
+ finally {
167
+ try {
168
+ probe?.close();
169
+ }
170
+ catch { /* no probe cleanup result is death evidence */ }
171
+ }
172
+ }
@@ -0,0 +1,174 @@
1
+ import * as fs from "node:fs";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ const SQLITE_BUSY = 5;
4
+ export class SqliteProcessLifetimeLockError extends Error {
5
+ reason;
6
+ code = "SQLITE_PROCESS_LIFETIME_LOCK_UNAVAILABLE";
7
+ constructor(message, reason, options) {
8
+ super(message, options);
9
+ this.reason = reason;
10
+ this.name = "SqliteProcessLifetimeLockError";
11
+ }
12
+ }
13
+ export function sqliteProcessLifetimeLockErrorDetail(error) {
14
+ return error instanceof Error ? error.message : String(error);
15
+ }
16
+ export function isSqliteProcessLifetimeLockContention(error) {
17
+ const code = typeof error === "object" && error !== null && "code" in error
18
+ ? String(error.code)
19
+ : "";
20
+ const errcode = typeof error === "object" && error !== null && "errcode" in error
21
+ ? error.errcode
22
+ : undefined;
23
+ const message = sqliteProcessLifetimeLockErrorDetail(error).toLowerCase();
24
+ return errcode === SQLITE_BUSY || code === "ERR_SQLITE_BUSY" || code === "ERR_SQLITE_LOCKED"
25
+ || message.includes("database is locked") || message.includes("database is busy");
26
+ }
27
+ function assertBusyTimeout(busyTimeoutMs) {
28
+ if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0) {
29
+ throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock busy timeout must be a non-negative safe integer", "invalid");
30
+ }
31
+ }
32
+ function journalMode(database) {
33
+ const row = database.prepare("PRAGMA journal_mode").get();
34
+ return typeof row?.journal_mode === "string" ? row.journal_mode.toLowerCase() : undefined;
35
+ }
36
+ function assertSchema(database) {
37
+ database.prepare("SELECT singleton FROM lock_anchor LIMIT 1").get();
38
+ }
39
+ function closeTransaction(database) {
40
+ let failure;
41
+ try {
42
+ database.exec("ROLLBACK");
43
+ }
44
+ catch (error) {
45
+ failure = error;
46
+ }
47
+ try {
48
+ database.close();
49
+ }
50
+ catch (error) {
51
+ failure ??= error;
52
+ }
53
+ return failure;
54
+ }
55
+ function throwLockError(prefix, error) {
56
+ if (error instanceof SqliteProcessLifetimeLockError)
57
+ throw error;
58
+ if (isSqliteProcessLifetimeLockContention(error)) {
59
+ throw new SqliteProcessLifetimeLockError(`${prefix} is already held`, "busy", { cause: error });
60
+ }
61
+ throw new SqliteProcessLifetimeLockError(`${prefix} failed: ${sqliteProcessLifetimeLockErrorDetail(error)}`, "open-failed", { cause: error });
62
+ }
63
+ /**
64
+ * Creates only a rollback-journal schema anchor. The empty database persists no
65
+ * ownership facts; an open transaction is the complete physical ownership signal.
66
+ */
67
+ export function initializeSqliteProcessLifetimeLockDatabase(lockPath) {
68
+ let database;
69
+ try {
70
+ database = new DatabaseSync(lockPath);
71
+ database.exec("PRAGMA busy_timeout=0");
72
+ database.exec("PRAGMA journal_mode=DELETE");
73
+ database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (singleton INTEGER PRIMARY KEY CHECK (singleton = 1))");
74
+ assertSchema(database);
75
+ if (journalMode(database) !== "delete") {
76
+ throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database does not use rollback-journal mode", "invalid");
77
+ }
78
+ }
79
+ catch (error) {
80
+ throwLockError("SQLite process-lifetime lock database initialization", error);
81
+ }
82
+ finally {
83
+ try {
84
+ database?.close();
85
+ }
86
+ catch { /* the causal initialization error is retained */ }
87
+ }
88
+ return lockPath;
89
+ }
90
+ export function openSqliteProcessLifetimeLockTransaction(input) {
91
+ const busyTimeoutMs = input.busyTimeoutMs ?? 0;
92
+ assertBusyTimeout(busyTimeoutMs);
93
+ if (!input.create) {
94
+ try {
95
+ if (!fs.lstatSync(input.path).isFile()) {
96
+ throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database is not a regular file", "invalid");
97
+ }
98
+ }
99
+ catch (error) {
100
+ if (error instanceof SqliteProcessLifetimeLockError)
101
+ throw error;
102
+ throw new SqliteProcessLifetimeLockError(`SQLite process-lifetime lock database is not initialized: ${sqliteProcessLifetimeLockErrorDetail(error)}`, "open-failed", { cause: error });
103
+ }
104
+ }
105
+ let database;
106
+ try {
107
+ database = new DatabaseSync(input.path);
108
+ database.exec(`PRAGMA busy_timeout=${busyTimeoutMs}`);
109
+ if (input.create) {
110
+ database.exec("PRAGMA journal_mode=DELETE");
111
+ database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (singleton INTEGER PRIMARY KEY CHECK (singleton = 1))");
112
+ }
113
+ database.exec(`BEGIN ${input.mode.toUpperCase()}`);
114
+ // A DEFERRED transaction becomes a process-lifetime guard only after this read
115
+ // acquires its SHARED lock. Keep this inside every transaction mode.
116
+ assertSchema(database);
117
+ if (journalMode(database) !== "delete") {
118
+ throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock database does not use rollback-journal mode", "invalid");
119
+ }
120
+ }
121
+ catch (error) {
122
+ if (database !== undefined)
123
+ closeTransaction(database);
124
+ throwLockError("SQLite process-lifetime lock acquisition", error);
125
+ }
126
+ let closed = false;
127
+ return {
128
+ path: input.path,
129
+ database,
130
+ close() {
131
+ if (closed)
132
+ return;
133
+ closed = true;
134
+ const failure = closeTransaction(database);
135
+ if (failure !== undefined) {
136
+ throw new SqliteProcessLifetimeLockError(`SQLite process-lifetime lock release failed: ${sqliteProcessLifetimeLockErrorDetail(failure)}`, "release-failed", { cause: failure });
137
+ }
138
+ },
139
+ };
140
+ }
141
+ function waitForRetry(milliseconds, signal) {
142
+ return new Promise((resolve, reject) => {
143
+ const abort = () => {
144
+ clearTimeout(timer);
145
+ reject(signal?.reason ?? new DOMException("operation aborted", "AbortError"));
146
+ };
147
+ const timer = setTimeout(() => {
148
+ signal?.removeEventListener("abort", abort);
149
+ resolve();
150
+ }, milliseconds);
151
+ signal?.addEventListener("abort", abort, { once: true });
152
+ });
153
+ }
154
+ /** Waits for the sole BEGIN IMMEDIATE owner. Cancellation is the only time bound. */
155
+ export async function acquireSqliteProcessLifetimeLock(input) {
156
+ const retryMs = input.retryMs ?? 25;
157
+ assertBusyTimeout(retryMs);
158
+ for (;;) {
159
+ input.signal?.throwIfAborted();
160
+ try {
161
+ return openSqliteProcessLifetimeLockTransaction({
162
+ path: input.path,
163
+ mode: "immediate",
164
+ busyTimeoutMs: 0,
165
+ create: true,
166
+ });
167
+ }
168
+ catch (error) {
169
+ if (!(error instanceof SqliteProcessLifetimeLockError) || error.reason !== "busy")
170
+ throw error;
171
+ await waitForRetry(retryMs, input.signal);
172
+ }
173
+ }
174
+ }
@@ -1,4 +1,5 @@
1
1
  import { FlowError } from "../flow-error.js";
2
+ import { requireCommitCoordinate } from "../git/commits.js";
2
3
  import { createGit, parseRevListOutput, wrapGitError } from "../git/core.js";
3
4
  import { effectiveScope } from "./lifecycle-history.js";
4
5
  import { scopePathEvidence, } from "./scope.js";
@@ -42,13 +43,7 @@ export async function receiptsAfterFence(cwd, entries, head = "HEAD") {
42
43
  return revList(cwd, getFence(entries), await resolveCommit(cwd, head));
43
44
  }
44
45
  async function resolveCommit(cwd, revision) {
45
- const git = createGit(cwd);
46
- try {
47
- return (await git.raw(["rev-parse", "--verify", `${revision}^{commit}`])).trim();
48
- }
49
- catch (error) {
50
- throw wrapGitError(`rev-parse ${revision}`, error, cwd);
51
- }
46
+ return (await requireCommitCoordinate(cwd, revision)).commit;
52
47
  }
53
48
  async function revList(cwd, from, to) {
54
49
  const git = createGit(cwd);
@@ -120,15 +115,18 @@ async function auditScope(cwd, entries, paths) {
120
115
  ...await scopeViolations(cwd, paths, scope),
121
116
  ...taskNamespaceViolations(paths, findBind(entries).data.scope),
122
117
  ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
123
- if (scopeViolationEvidence.length > 0) {
124
- throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
125
- }
126
118
  const violations = scopeViolationEvidence.map((violation) => violation.path);
127
119
  return {
128
120
  scopeAudit: { violations },
129
121
  scopeViolationEvidence,
130
122
  };
131
123
  }
124
+ function admitScope(contract, scope, capture, policy) {
125
+ if (policy === "refuse" && capture.scopeViolationEvidence.length > 0) {
126
+ throw new ScopeAuditError(contract, scope, capture.scopeViolationEvidence);
127
+ }
128
+ return capture;
129
+ }
132
130
  export class ScopeAuditError extends FlowError {
133
131
  scopeViolationEvidence;
134
132
  constructor(_contractId, scope, evidence) {
@@ -155,7 +153,7 @@ function currentFoldedSealArc(model) {
155
153
  return model.seal === null ? model.intent.arc : null;
156
154
  }
157
155
  /** Current-read admission capture: scope and fence come only from the folded model. */
158
- export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = []) {
156
+ export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = [], scopePolicy) {
159
157
  const arc = currentFoldedSealArc(model);
160
158
  if (arc === null)
161
159
  return null;
@@ -168,14 +166,18 @@ export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend =
168
166
  ]);
169
167
  const effectiveScope = [...model.effectiveScope, ...scopeAppend];
170
168
  const bind = model.history.find((row) => row.entry.kind === "bind")?.entry;
171
- const scopeViolationEvidence = [
172
- ...await scopeViolations(cwd, paths, effectiveScope),
173
- ...taskNamespaceViolations(paths, bind?.kind === "bind" ? bind.data.scope : []),
174
- ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
175
- if (scopeViolationEvidence.length > 0) {
176
- throw new ScopeAuditError(model.contract, effectiveScope, scopeViolationEvidence);
169
+ const ordinaryViolations = await scopeViolations(cwd, paths, effectiveScope);
170
+ const namespaceViolations = taskNamespaceViolations(paths, bind?.kind === "bind" ? bind.data.scope : []);
171
+ if (namespaceViolations.length > 0) {
172
+ throw new ScopeAuditError(model.contract, effectiveScope, namespaceViolations);
177
173
  }
178
- return { arc, oldFence, newFence, receipts, diffstat: stat, scopeAudit: { violations: [] }, scopeViolationEvidence };
174
+ const scopeViolationEvidence = ordinaryViolations
175
+ .filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
176
+ const scopeAudit = admitScope(model.contract, effectiveScope, {
177
+ scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
178
+ scopeViolationEvidence,
179
+ }, scopePolicy);
180
+ return { arc, oldFence, newFence, receipts, diffstat: stat, ...scopeAudit };
179
181
  }
180
182
  /** Current seal intent: open explicit arc, else derived a0 while no explicit arc has ever existed. */
181
183
  export function currentSealArc(entries) {
@@ -219,7 +221,14 @@ export async function previewSeal(cwd, entries, head = "HEAD") {
219
221
  };
220
222
  }
221
223
  export async function captureSeal(cwd, entries, head = "HEAD") {
222
- return (await previewSeal(cwd, entries, head)).capture;
224
+ const preview = await previewSeal(cwd, entries, head);
225
+ if (!preview.capture)
226
+ return null;
227
+ admitScope("direct seal", effectiveScope(entries), {
228
+ scopeAudit: preview.capture.scopeAudit,
229
+ scopeViolationEvidence: preview.capture.scopeViolationEvidence,
230
+ }, "refuse");
231
+ return preview.capture;
223
232
  }
224
233
  /** Folded renew capture: preserve receipt hashes while auditing rebased delivery content. */
225
234
  export async function captureFoldedRenewSeal(input) {
@@ -239,21 +248,24 @@ export async function captureFoldedRenewSeal(input) {
239
248
  ]);
240
249
  const scope = [...input.effectiveScope, ...input.pendingScopeOverlay];
241
250
  const bindScope = await acceptedBindScope(input.cwd, input.contract);
242
- const scopeViolationEvidence = [
243
- ...await scopeViolations(input.cwd, paths, scope),
244
- ...taskNamespaceViolations(paths, bindScope),
245
- ].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
246
- if (scopeViolationEvidence.length > 0) {
247
- throw new ScopeAuditError(input.contract, scope, scopeViolationEvidence);
251
+ const ordinaryViolations = await scopeViolations(input.cwd, paths, scope);
252
+ const namespaceViolations = taskNamespaceViolations(paths, bindScope);
253
+ if (namespaceViolations.length > 0) {
254
+ throw new ScopeAuditError(input.contract, scope, namespaceViolations);
248
255
  }
256
+ const scopeViolationEvidence = ordinaryViolations
257
+ .filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
258
+ const scopeAudit = admitScope(input.contract, scope, {
259
+ scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
260
+ scopeViolationEvidence,
261
+ }, input.scopePolicy);
249
262
  return {
250
263
  arc,
251
264
  oldFence,
252
265
  newFence: receiptHead,
253
266
  receipts,
254
267
  diffstat: stat,
255
- scopeAudit: { violations: [] },
256
- scopeViolationEvidence,
268
+ ...scopeAudit,
257
269
  };
258
270
  }
259
271
  export async function sealCurrentArc(input) {
@@ -301,7 +313,7 @@ export async function sealCurrentArc(input) {
301
313
  prepare: async (admission) => {
302
314
  if (admission.business.existing)
303
315
  return { capture: admission.business.existing.capture, existing: { transactionObjectId: admission.business.existing.transactionObjectId } };
304
- return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD") };
316
+ return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD", [], "refuse") };
305
317
  },
306
318
  revalidate: ({ session, prepared }) => {
307
319
  assertOperable(input.contractId, session.lifecycle);
@@ -319,7 +331,7 @@ export async function sealCurrentArc(input) {
319
331
  effect: async ({ context, prepared }) => {
320
332
  const capture = prepared.capture;
321
333
  if (!capture)
322
- return { kind: "refused-before-effects", authoritativeHead: context.session.lifecycle.observedLedgerHead, refusal: { code: "NO_OPEN_ARC", message: "no current arc requires sealing" } };
334
+ return { kind: "refused-before-effects", authoritativeHead: context.session.pins.repositoryLedgerHead, refusal: { code: "NO_OPEN_ARC", message: "no current arc requires sealing" } };
323
335
  const at = new Date(input.nowMs).toISOString();
324
336
  {
325
337
  const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
@@ -333,11 +345,11 @@ export async function sealCurrentArc(input) {
333
345
  };
334
346
  const result = prepared.existing
335
347
  ? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
336
- : await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.lifecycle.observedLedgerHead, acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId, entries: [entry] });
348
+ : await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.pins.repositoryLedgerHead, acceptedLifecycleTail: context.session.lifecycle.contractHead, entries: [entry] });
337
349
  if (result.status === "indeterminate")
338
- return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead };
350
+ return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead };
339
351
  if (result.status !== "committed")
340
- return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.lifecycle.observedLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed while sealing` } };
352
+ return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed while sealing` } };
341
353
  return { kind: "committed", commit: result, entryKind: "seal", value: {
342
354
  ...capture,
343
355
  sealedBy: input.sealedBy,
@@ -1,6 +1,6 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, runGitProcess } from "../../git/core.js";
3
- import { shortCommitHash } from "../../git/commits.js";
2
+ import { createGit, runGitProcess } from "../../git/core.js";
3
+ import { readCommitCoordinate, shortCommitHash } from "../../git/commits.js";
4
4
  import { assertHonestClaimEvidence, buildHonestClaimMerge, } from "../repository-ledger/claim-evidence.js";
5
5
  async function successfulGit(cwd, args, input) {
6
6
  const result = await runGitProcess(cwd, args, input === undefined ? undefined : { input });
@@ -22,16 +22,29 @@ export async function buildClaimLanding(input) {
22
22
  }
23
23
  }
24
24
  export async function resolveCommit(cwd, rev) {
25
- return await successfulGit(cwd, ["rev-parse", "--verify", `${rev}^{commit}`]);
25
+ const read = await readCommitCoordinate(cwd, rev);
26
+ if (read.status === "ok")
27
+ return read.coordinate.commit;
28
+ if (read.status === "failed") {
29
+ const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
30
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
31
+ }
32
+ if (read.status === "malformed") {
33
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
34
+ }
35
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: revision does not resolve to a commit`);
26
36
  }
27
37
  export async function resolveCommitOrNull(cwd, rev) {
28
- const result = await runGitProcess(cwd, ["rev-parse", "--verify", `${rev}^{commit}`]);
29
- if (result.status === 0 && !result.error)
30
- return result.stdout.trim();
31
- if (errorContainsAnyPattern(result.stderr, MISSING_HEAD_PATTERNS))
38
+ const read = await readCommitCoordinate(cwd, rev);
39
+ if (read.status === "ok")
40
+ return read.coordinate.commit;
41
+ if (read.status === "missing" || read.status === "non-commit")
32
42
  return null;
33
- const detail = result.stderr.trim() || result.error?.message || `exit ${String(result.status)}`;
34
- throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: result.error });
43
+ if (read.status === "malformed") {
44
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${read.detail}`);
45
+ }
46
+ const detail = read.result.stderr.trim() || read.error.message || `exit ${String(read.result.status)}`;
47
+ throw new FlowError("INTERNAL_STATE", `git rev-parse ${rev} failed: ${detail}`, { cause: read.error });
35
48
  }
36
49
  /** Prove parent order and tree identity independently of mutable refs. */
37
50
  export async function proveClaimLanding(cwd, landing) {
@@ -179,7 +179,7 @@ export async function claimContract(input) {
179
179
  : await context.publishAuthoritative({
180
180
  verb: "claim",
181
181
  admittedLedgerHead: admission.ledgerHead,
182
- acceptedLifecycleTail: context.session.lifecycle.history.at(-1).objectId,
182
+ acceptedLifecycleTail: context.session.lifecycle.contractHead,
183
183
  entries: [{
184
184
  v: 2,
185
185
  kind: "claim",
@@ -221,11 +221,13 @@ export async function claimContract(input) {
221
221
  },
222
222
  });
223
223
  await context.session.completeReplay("retire");
224
- const taskRow = context.journal.all().find((row) => row.effect === "task-board-projection" && row.status === "succeeded");
225
- const taskFacts = taskRow?.status === "succeeded" ? taskRow.receipt?.facts : undefined;
226
- const warnings = isJsonObject(taskFacts) && Array.isArray(taskFacts.warnings)
227
- ? taskFacts.warnings.filter((warning) => typeof warning === "string")
228
- : [];
224
+ const warnings = ["task-board-projection", "target-worktree-projection"].flatMap((effect) => {
225
+ const row = context.journal.all().find((candidate) => candidate.effect === effect && candidate.status === "succeeded");
226
+ const facts = row?.status === "succeeded" ? row.receipt?.facts : undefined;
227
+ return isJsonObject(facts) && Array.isArray(facts.warnings)
228
+ ? facts.warnings.filter((warning) => typeof warning === "string")
229
+ : [];
230
+ });
229
231
  const cleanupRow = context.journal.all().find((row) => row.effect === "worktree-projection" && row.status === "succeeded");
230
232
  const cleanupFacts = cleanupRow?.status === "succeeded" ? cleanupRow.receipt?.facts : undefined;
231
233
  const deliveryRefRow = context.journal.all().find((row) => row.effect === "delivery-ref-projection" && row.status === "succeeded");
@@ -8,7 +8,7 @@ import { getDiffStats } from "../../git/diff/preview.js";
8
8
  import { readDiff } from "../../git/diff/read.js";
9
9
  import { getCurrentBranch } from "../../git/branches.js";
10
10
  import { getLatestCommitHash } from "../../git/staging.js";
11
- import { createGit } from "../../git/core.js";
11
+ import { requireCommitCoordinate } from "../../git/commits.js";
12
12
  import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../../keiyaku.js";
13
13
  import { FlowError } from "../../flow-error.js";
14
14
  import { persistResponseHistory } from "../transcripts.js";
@@ -100,13 +100,14 @@ export function createOathGate() {
100
100
  export function createCommandVerificationGate() {
101
101
  return {
102
102
  name: "command",
103
- async evaluate({ cwd, coordinationRoot, verificationPlan, verificationCandidate, input }) {
104
- const repositoryRoot = coordinationRoot ?? cwd;
103
+ async evaluate({ cwd, repositoryRoot, verificationPlan, verificationCandidate, input }) {
105
104
  for (const verification of verificationPlan) {
106
105
  logInfo(`[CLAIM] Running ${verification.source}: ${verification.label}`, { cwd, section: "script", progressOnly: true });
107
106
  }
108
- const evidence = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, coordinationKey: JSON.stringify({ repository: repositoryRoot, candidate: verificationCandidate, plan: verificationPlan }), coordinationRoot: repositoryRoot });
109
- const runnerFailure = evidence.find((result) => result.execution.status === "runner_failed");
107
+ const invocation = await runVerificationPlan({ cwd, candidate: verificationCandidate, plan: verificationPlan, signal: input.signal, root: repositoryRoot ?? cwd });
108
+ // Petition owns the run: abort cancels and awaitTerminal observes cleanup.
109
+ const evidence = await invocation.awaitTerminal();
110
+ const runnerFailure = evidence.results.find((result) => result.execution.status === "runner_failed");
110
111
  if (runnerFailure?.execution.status === "runner_failed") {
111
112
  return {
112
113
  pass: false,
@@ -120,7 +121,7 @@ export function createCommandVerificationGate() {
120
121
  },
121
122
  };
122
123
  }
123
- const commandFailure = evidence.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
124
+ const commandFailure = evidence.results.find((result) => result.execution.status === "executed" && result.execution.code !== 0);
124
125
  if (commandFailure?.execution.status === "executed") {
125
126
  return {
126
127
  pass: false,
@@ -134,7 +135,7 @@ export function createCommandVerificationGate() {
134
135
  },
135
136
  };
136
137
  }
137
- return { pass: true, verification: [...evidence] };
138
+ return { pass: true, verification: [...evidence.results] };
138
139
  },
139
140
  };
140
141
  }
@@ -193,7 +194,7 @@ export function createAgentReviewGate() {
193
194
  authority: { kind: "repository" },
194
195
  binding: { kind: "commission", commissionId: contractId },
195
196
  });
196
- const reviewHeadBeforeWrite = (await createGit(cwd).revparse(["--verify", "HEAD^{commit}"])).trim();
197
+ const reviewHeadBeforeWrite = (await requireCommitCoordinate(cwd, "HEAD")).commit;
197
198
  const raw = subagentResult.finalMessage.trim();
198
199
  const passed = raw.startsWith(REVIEW_PASS_TOKEN);
199
200
  const observations = passed ? raw.slice(REVIEW_PASS_TOKEN.length).trim() || undefined : undefined;
@@ -1,13 +1,12 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- import { createGit } from "../../git/core.js";
2
+ import { requireCommitCoordinate } from "../../git/commits.js";
3
3
  const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
4
4
  export async function assertHeadUnchanged(cwd, expectedHead, operation) {
5
- const git = createGit(cwd);
6
5
  const [expectedCommit, currentHead] = await Promise.all([
7
- git.raw(["rev-parse", "--verify", `${expectedHead}^{commit}`]),
8
- git.raw(["rev-parse", "--verify", "HEAD^{commit}"]),
6
+ requireCommitCoordinate(cwd, expectedHead),
7
+ requireCommitCoordinate(cwd, "HEAD"),
9
8
  ]);
10
- if (currentHead.trim() === expectedCommit.trim())
9
+ if (currentHead.commit === expectedCommit.commit)
11
10
  return;
12
- throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.trim()} while the ${operation} was running`);
11
+ throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead.commit} while the ${operation} was running`);
13
12
  }