@astrosheep/keiyaku 2.9.11 → 2.9.12

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 (48) hide show
  1. package/README.md +62 -72
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/providers/claude-agent-sdk/adapter.js +15 -2
  4. package/build/cli/commands/contract/bind/meta.js +4 -4
  5. package/build/cli/commands/metadata.js +3 -2
  6. package/build/cli/commands/projection/kill/handler.js +4 -5
  7. package/build/cli/commands/shared.js +1 -1
  8. package/build/cli/commands/task/catalog.js +2 -0
  9. package/build/cli/commands/task/compose/handler.js +18 -0
  10. package/build/cli/commands/task/compose/meta.js +10 -0
  11. package/build/cli/flags.js +5 -1
  12. package/build/cli/index.js +11 -6
  13. package/build/cli/parse-flags.js +21 -21
  14. package/build/cli/parse-metadata.js +5 -2
  15. package/build/cli/parse-selectors.js +4 -4
  16. package/build/cli/parse.js +23 -23
  17. package/build/cli/projection-address.js +7 -7
  18. package/build/cli/render/task.js +27 -0
  19. package/build/cli/types.js +1 -1
  20. package/build/core/addressing.js +3 -3
  21. package/build/core/call/context.js +3 -3
  22. package/build/core/call/prompt.js +1 -1
  23. package/build/core/contract-view.js +1 -1
  24. package/build/core/draft.js +51 -22
  25. package/build/core/projection/generation/model.js +1 -1
  26. package/build/core/projection/generation/projection-generation-runner.js +13 -3
  27. package/build/core/projection/generation/transitions.js +10 -0
  28. package/build/core/projection/projection-history.js +1 -1
  29. package/build/core/projection/projection-kill.js +5 -4
  30. package/build/core/projection/projection-runner-lock.js +58 -229
  31. package/build/core/projection/projection-wait.js +2 -2
  32. package/build/core/projection/projection-wake.js +2 -0
  33. package/build/core/repository-ledger/claim-evidence.js +1 -1
  34. package/build/core/repository-ledger/current-state-store.js +455 -337
  35. package/build/core/repository-ledger/read-model.js +3 -3
  36. package/build/core/repository-ledger/write-transaction.js +15 -13
  37. package/build/core/sqlite-process-lifetime-lock.js +194 -0
  38. package/build/core/task/compose.js +327 -0
  39. package/build/core/task/index.js +1 -0
  40. package/build/core/task/settlement-git.js +69 -26
  41. package/build/core/task/task-git-store.js +1 -0
  42. package/build/core/task/task-store-repository.js +1 -0
  43. package/build/flow-error.js +6 -1
  44. package/build/generated/version.js +2 -2
  45. package/package.json +2 -2
  46. package/skills/keiyaku-akuma/SKILL.md +6 -1
  47. package/skills/keiyaku-task/SKILL.md +23 -1
  48. package/skills/keiyaku-workflow/SKILL.md +33 -18
@@ -1,9 +1,6 @@
1
- import * as fs from "node:fs";
2
1
  import * as path from "node:path";
3
- import { DatabaseSync } from "node:sqlite";
2
+ import { initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, isSqliteProcessLifetimeLockPlatformSupported, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "../sqlite-process-lifetime-lock.js";
4
3
  export const PROJECTION_RUNNER_LOCK_DATABASE = "leash";
5
- const SQLITE_BUSY = 5;
6
- const SUPPORTED_PLATFORMS = new Set(["darwin", "linux", "win32"]);
7
4
  export class ProjectionRunnerLockUnavailableError extends Error {
8
5
  code = "PROJECTION_RUNNER_LOCK_UNAVAILABLE";
9
6
  constructor(message, options) {
@@ -22,72 +19,28 @@ export function projectionRunnerLockPath(projectionDirectory) {
22
19
  return path.join(projectionDirectory, PROJECTION_RUNNER_LOCK_DATABASE);
23
20
  }
24
21
  export function isProjectionRunnerLockPlatformSupported(platform = process.platform) {
25
- return SUPPORTED_PLATFORMS.has(platform);
22
+ return isSqliteProcessLifetimeLockPlatformSupported(platform);
26
23
  }
27
24
  function errorDetail(error) {
28
- return error instanceof Error ? error.message : String(error);
25
+ return sqliteProcessLifetimeLockErrorDetail(error);
29
26
  }
30
- function isSqliteBusy(error) {
31
- return typeof error === "object"
32
- && error !== null
33
- && "errcode" in error
34
- && error.errcode === SQLITE_BUSY;
35
- }
36
- function readJournalMode(database) {
37
- const row = database.prepare("PRAGMA journal_mode").get();
38
- return typeof row?.journal_mode === "string" ? row.journal_mode.toLowerCase() : undefined;
39
- }
40
- function assertCurrentLockSchema(database) {
41
- database.prepare("SELECT id FROM lock_anchor LIMIT 1").get();
42
- }
43
- function assertBusyTimeout(busyTimeoutMs) {
44
- if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0) {
45
- throw new ProjectionRunnerLockUnavailableError("projection runner lock busy timeout must be a non-negative safe integer");
46
- }
27
+ function isContention(error) {
28
+ return isSqliteProcessLifetimeLockContention(error)
29
+ || (error instanceof SqliteProcessLifetimeLockError && error.reason === "busy");
47
30
  }
48
31
  function openGuard(lockPath, busyTimeoutMs) {
49
- const database = new DatabaseSync(lockPath);
50
- try {
51
- database.exec(`PRAGMA busy_timeout=${busyTimeoutMs}`);
52
- database.exec("BEGIN DEFERRED");
53
- assertCurrentLockSchema(database);
54
- if (readJournalMode(database) !== "delete") {
55
- throw new ProjectionRunnerLockUnavailableError("projection runner lock database does not use rollback-journal mode");
56
- }
57
- return database;
58
- }
59
- catch (error) {
60
- closeTransaction(database);
61
- throw error;
62
- }
32
+ return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "deferred", busyTimeoutMs });
63
33
  }
64
34
  function openReservation(lockPath, busyTimeoutMs) {
65
- const database = new DatabaseSync(lockPath);
66
- try {
67
- database.exec(`PRAGMA busy_timeout=${busyTimeoutMs}`);
68
- database.exec("BEGIN IMMEDIATE");
69
- return database;
70
- }
71
- catch (error) {
72
- closeTransaction(database);
73
- throw error;
74
- }
35
+ return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "immediate", busyTimeoutMs });
75
36
  }
76
- function closeTransaction(database) {
77
- let failure;
78
- try {
79
- database.exec("ROLLBACK");
80
- }
81
- catch (error) {
82
- failure = error;
83
- }
37
+ function closeTransaction(transaction) {
84
38
  try {
85
- database.close();
39
+ transaction.close();
86
40
  }
87
41
  catch (error) {
88
- failure ??= error;
42
+ return error;
89
43
  }
90
- return failure;
91
44
  }
92
45
  function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
93
46
  let closed = false;
@@ -100,9 +53,8 @@ function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
100
53
  const released = reservation;
101
54
  reservation = undefined;
102
55
  const failure = closeTransaction(released);
103
- if (failure !== undefined) {
56
+ if (failure !== undefined)
104
57
  throw new ProjectionRunnerLockUnavailableError(`projection runner reservation release failed: ${errorDetail(failure)}`, { cause: failure });
105
- }
106
58
  },
107
59
  reclaimReservation() {
108
60
  if (closed || reservation !== undefined)
@@ -121,123 +73,61 @@ function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
121
73
  let failure;
122
74
  if (reservation !== undefined)
123
75
  failure = closeTransaction(reservation);
124
- const guardFailure = closeTransaction(guard);
125
- failure ??= guardFailure;
126
- if (failure !== undefined) {
76
+ failure ??= closeTransaction(guard);
77
+ if (failure !== undefined)
127
78
  throw new ProjectionRunnerLockUnavailableError(`projection runner lock release failed: ${errorDetail(failure)}`, { cause: failure });
128
- }
129
79
  },
130
80
  };
131
81
  }
132
- function closeAfterFailedAcquire(database, error) {
133
- try {
134
- database.close();
135
- }
136
- catch {
137
- // The acquisition error remains the causal failure. Closing a connection
138
- // that never became authoritative cannot turn it into a held lock.
139
- }
140
- if (isSqliteBusy(error)) {
141
- throw new ProjectionRunnerLockUnavailableError("projection runner lock is already held", { cause: error });
142
- }
143
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock acquisition failed: ${errorDetail(error)}`, { cause: error });
82
+ function unavailable(operation, error) {
83
+ return new ProjectionRunnerLockUnavailableError(`${operation}: ${errorDetail(error)}`, { cause: error });
144
84
  }
145
- /**
146
- * Mint-time initialization for the fixed lock database. This creates storage,
147
- * forces rollback-journal mode, verifies the anchor schema, and closes without
148
- * retaining any process-life authority.
149
- */
85
+ /** Mint-time schema initialization retains no process-life authority. */
150
86
  export function initializeProjectionRunnerLockDatabase(projectionDirectory, platform = process.platform) {
151
- if (!isProjectionRunnerLockPlatformSupported(platform)) {
87
+ if (!isProjectionRunnerLockPlatformSupported(platform))
152
88
  throw new ProjectionRunnerLockUnsupportedError(platform);
153
- }
154
- const lockPath = projectionRunnerLockPath(projectionDirectory);
155
- let database;
156
- try {
157
- database = new DatabaseSync(lockPath);
158
- }
159
- catch (error) {
160
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock database could not be initialized: ${errorDetail(error)}`, { cause: error });
161
- }
162
- let initializationError;
163
- try {
164
- database.exec("PRAGMA busy_timeout=0");
165
- database.exec("PRAGMA journal_mode=DELETE");
166
- database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (id INTEGER PRIMARY KEY CHECK (id = 1))");
167
- if (readJournalMode(database) !== "delete") {
168
- throw new ProjectionRunnerLockUnavailableError("projection runner lock database does not use rollback-journal mode");
169
- }
170
- assertCurrentLockSchema(database);
171
- }
172
- catch (error) {
173
- initializationError = error;
174
- }
175
89
  try {
176
- database.close();
90
+ return initializeSqliteProcessLifetimeLockDatabase(projectionRunnerLockPath(projectionDirectory), platform);
177
91
  }
178
92
  catch (error) {
179
- initializationError ??= error;
180
- }
181
- if (initializationError !== undefined) {
182
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock database initialization failed: ${errorDetail(initializationError)}`, { cause: initializationError });
93
+ throw unavailable("projection runner lock database could not be initialized", error);
183
94
  }
184
- return lockPath;
185
95
  }
186
96
  /**
187
- * Hold one rollback-mode SHARED guard plus the sole RESERVED claim until
188
- * close() or process death. An EXCLUSIVE probe cannot pass either component.
189
- * During handoff the child overlaps the guard before the RESERVED claim moves,
190
- * so kernel exclusion remains continuous without transferring a connection.
97
+ * Projection policy: hold a SHARED guard plus a RESERVED claim. The transfer
98
+ * target overlaps its guard before the reservation moves, preserving exclusion.
191
99
  */
192
100
  export function acquireProjectionRunnerLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
193
- if (!isProjectionRunnerLockPlatformSupported(platform)) {
101
+ if (!isProjectionRunnerLockPlatformSupported(platform))
194
102
  throw new ProjectionRunnerLockUnsupportedError(platform);
195
- }
196
103
  const lockPath = projectionRunnerLockPath(projectionDirectory);
197
- try {
198
- if (!fs.lstatSync(lockPath).isFile()) {
199
- throw new ProjectionRunnerLockUnavailableError("projection runner lock database is not a regular file");
200
- }
201
- }
202
- catch (error) {
203
- if (error instanceof ProjectionRunnerLockUnavailableError)
204
- throw error;
205
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock database is not initialized: ${errorDetail(error)}`, { cause: error });
206
- }
207
- assertBusyTimeout(busyTimeoutMs);
208
104
  let guard;
209
105
  try {
210
106
  guard = openGuard(lockPath, busyTimeoutMs);
211
107
  }
212
108
  catch (error) {
213
- throw new ProjectionRunnerLockUnavailableError(`projection runner lock database could not be opened: ${errorDetail(error)}`, { cause: error });
109
+ throw unavailable("projection runner lock database could not be opened", error);
214
110
  }
215
- let reservation;
216
111
  try {
217
- reservation = openReservation(lockPath, busyTimeoutMs);
112
+ return createHeldLock(lockPath, busyTimeoutMs, guard, openReservation(lockPath, busyTimeoutMs));
218
113
  }
219
114
  catch (error) {
220
- if (reservation)
221
- closeTransaction(reservation);
222
115
  closeTransaction(guard);
223
- return closeAfterFailedAcquire(guard, error);
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);
224
119
  }
225
- return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
226
120
  }
227
121
  export function acquireProjectionRunnerTransferTarget(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
228
- if (!isProjectionRunnerLockPlatformSupported(platform)) {
122
+ if (!isProjectionRunnerLockPlatformSupported(platform))
229
123
  throw new ProjectionRunnerLockUnsupportedError(platform);
230
- }
231
- assertBusyTimeout(busyTimeoutMs);
232
124
  const lockPath = projectionRunnerLockPath(projectionDirectory);
233
125
  let guard;
234
126
  try {
235
127
  guard = openGuard(lockPath, busyTimeoutMs);
236
128
  }
237
129
  catch (error) {
238
- if (guard)
239
- closeTransaction(guard);
240
- throw new ProjectionRunnerLockUnavailableError(`projection runner transfer acquisition failed: ${errorDetail(error)}`, { cause: error });
130
+ throw unavailable("projection runner transfer acquisition failed", error);
241
131
  }
242
132
  let closed = false;
243
133
  return {
@@ -245,55 +135,38 @@ export function acquireProjectionRunnerTransferTarget(projectionDirectory, platf
245
135
  promote() {
246
136
  if (closed)
247
137
  throw new ProjectionRunnerLockUnavailableError("projection runner transfer target is closed");
248
- let reservation;
249
138
  try {
250
- reservation = openReservation(lockPath, busyTimeoutMs);
139
+ const reservation = openReservation(lockPath, busyTimeoutMs);
140
+ closed = true;
141
+ return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
251
142
  }
252
143
  catch (error) {
253
- if (reservation)
254
- closeTransaction(reservation);
255
- throw new ProjectionRunnerLockUnavailableError(`projection runner transfer promotion failed: ${errorDetail(error)}`, { cause: error });
144
+ throw unavailable("projection runner transfer promotion failed", error);
256
145
  }
257
- closed = true;
258
- return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
259
146
  },
260
147
  close() {
261
148
  if (closed)
262
149
  return;
263
150
  closed = true;
264
151
  const failure = closeTransaction(guard);
265
- if (failure !== undefined) {
266
- throw new ProjectionRunnerLockUnavailableError(`projection runner transfer release failed: ${errorDetail(failure)}`, { cause: failure });
267
- }
152
+ if (failure !== undefined)
153
+ throw unavailable("projection runner transfer release failed", failure);
268
154
  },
269
155
  };
270
156
  }
271
- /** Acquire positive death evidence. Any active guard or transfer blocks this. */
157
+ /** Acquire positive death evidence. An active guard or transfer blocks this. */
272
158
  export function acquireProjectionRunnerSettlementLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
273
- if (!isProjectionRunnerLockPlatformSupported(platform)) {
159
+ if (!isProjectionRunnerLockPlatformSupported(platform))
274
160
  throw new ProjectionRunnerLockUnsupportedError(platform);
275
- }
276
- assertBusyTimeout(busyTimeoutMs);
277
161
  const lockPath = projectionRunnerLockPath(projectionDirectory);
278
- let database;
162
+ let transaction;
279
163
  try {
280
- database = new DatabaseSync(lockPath);
281
- database.exec(`PRAGMA busy_timeout=${busyTimeoutMs}`);
282
- database.exec("BEGIN EXCLUSIVE");
283
- assertCurrentLockSchema(database);
284
- if (readJournalMode(database) !== "delete") {
285
- throw new ProjectionRunnerLockUnavailableError("projection runner lock database does not use rollback-journal mode");
286
- }
164
+ transaction = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs });
287
165
  }
288
166
  catch (error) {
289
- try {
290
- database.close();
291
- }
292
- catch { }
293
- if (isSqliteBusy(error)) {
167
+ if (isContention(error))
294
168
  throw new ProjectionRunnerLockUnavailableError("projection runner lock is already held", { cause: error });
295
- }
296
- throw new ProjectionRunnerLockUnavailableError(`projection runner settlement lock acquisition failed: ${errorDetail(error)}`, { cause: error });
169
+ throw unavailable("projection runner settlement lock acquisition failed", error);
297
170
  }
298
171
  let closed = false;
299
172
  return {
@@ -302,81 +175,37 @@ export function acquireProjectionRunnerSettlementLock(projectionDirectory, platf
302
175
  if (closed)
303
176
  return;
304
177
  closed = true;
305
- const failure = closeTransaction(database);
306
- if (failure !== undefined) {
307
- throw new ProjectionRunnerLockUnavailableError(`projection runner settlement lock release failed: ${errorDetail(failure)}`, { cause: failure });
308
- }
178
+ const failure = closeTransaction(transaction);
179
+ if (failure !== undefined)
180
+ throw unavailable("projection runner settlement lock release failed", failure);
309
181
  },
310
182
  };
311
183
  }
312
- /**
313
- * Probe without creating the lock database. BEGIN EXCLUSIVE is the positive
314
- * death test: SQLITE_BUSY proves a SHARED guard or RESERVED owner remains;
315
- * success proves both are gone. Every other failure remains unknown.
316
- */
184
+ /** Positive death probe without creating a missing lock database. */
317
185
  export function observeProjectionRunnerLock(projectionDirectory, platform = process.platform) {
318
186
  if (!isProjectionRunnerLockPlatformSupported(platform)) {
319
- return {
320
- state: "unknown",
321
- reason: "unsupported-platform",
322
- detail: `projection runner locks are unsupported on platform '${platform}'`,
323
- };
187
+ return { state: "unknown", reason: "unsupported-platform", detail: `projection runner locks are unsupported on platform '${platform}'` };
324
188
  }
325
189
  const lockPath = projectionRunnerLockPath(projectionDirectory);
190
+ let probe;
326
191
  try {
327
- if (!fs.lstatSync(lockPath).isFile()) {
328
- return {
329
- state: "unknown",
330
- reason: "open-failed",
331
- detail: "projection runner lock database is not a regular file",
332
- };
333
- }
334
- }
335
- catch (error) {
336
- return { state: "unknown", reason: "open-failed", detail: errorDetail(error) };
337
- }
338
- let database;
339
- try {
340
- database = new DatabaseSync(lockPath);
341
- }
342
- catch (error) {
343
- return { state: "unknown", reason: "open-failed", detail: errorDetail(error) };
344
- }
345
- let transactionOpen = false;
346
- try {
347
- database.exec("PRAGMA busy_timeout=0");
348
- database.exec("BEGIN EXCLUSIVE");
349
- transactionOpen = true;
350
- assertCurrentLockSchema(database);
351
- const journalMode = readJournalMode(database);
352
- if (journalMode !== "delete") {
353
- return {
354
- state: "unknown",
355
- reason: "unsupported-journal-mode",
356
- detail: `projection runner lock database uses journal mode '${journalMode ?? "unknown"}'`,
357
- };
358
- }
192
+ probe = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs: 0 });
359
193
  return { state: "released" };
360
194
  }
361
195
  catch (error) {
362
- if (isSqliteBusy(error))
196
+ if (isContention(error))
363
197
  return { state: "held" };
364
- return { state: "unknown", reason: "probe-failed", detail: errorDetail(error) };
198
+ const detail = errorDetail(error);
199
+ return {
200
+ state: "unknown",
201
+ reason: detail.includes("rollback-journal") ? "unsupported-journal-mode" : "probe-failed",
202
+ detail,
203
+ };
365
204
  }
366
205
  finally {
367
- if (transactionOpen) {
368
- try {
369
- database.exec("ROLLBACK");
370
- }
371
- catch {
372
- // Closing below still drops this observer's own ephemeral SHARED lock.
373
- }
374
- }
375
206
  try {
376
- database.close();
377
- }
378
- catch {
379
- // Observer cleanup failures cannot be converted into death evidence.
207
+ probe?.close();
380
208
  }
209
+ catch { /* no probe cleanup result is death evidence */ }
381
210
  }
382
211
  }
@@ -223,8 +223,8 @@ export async function waitForProjection(input) {
223
223
  const verdict = current ? verdictFromCurrent(current, observedAtMs) : undefined;
224
224
  const terminal = verdict !== undefined;
225
225
  const terminalAtMs = verdict === undefined ? undefined : Date.parse(verdict.at);
226
- const terminalWithinDeadline = terminal && (input.deadlineMs === undefined
227
- || (typeof terminalAtMs === "number" && Number.isFinite(terminalAtMs) && terminalAtMs < input.deadlineMs));
226
+ const terminalWithinDeadline = terminal && (deadline === undefined
227
+ || (typeof terminalAtMs === "number" && Number.isFinite(terminalAtMs) && terminalAtMs < deadline));
228
228
  if (terminalWithinDeadline || (deadline !== undefined && observedAtMs >= deadline)) {
229
229
  const revalidated = readContinuationSnapshot(input.projectionDirectory, 10);
230
230
  if (revalidated.generationSequence !== continuation.generationSequence)
@@ -234,6 +234,7 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
234
234
  const settled = store.settleKillIfRunnerDead({
235
235
  executionId: current.launch.executionId,
236
236
  releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
237
+ terminalAt,
237
238
  });
238
239
  if (settled.status === "committed")
239
240
  store.commitWriteTransaction();
@@ -300,6 +301,7 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
300
301
  const settled = store.settleKillIfRunnerDead({
301
302
  executionId: current.launch.executionId,
302
303
  releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
304
+ terminalAt,
303
305
  });
304
306
  if (settled.status === "committed")
305
307
  store.commitWriteTransaction();
@@ -119,7 +119,7 @@ export async function assertHonestClaimEvidence(cwd, evidence, readContext) {
119
119
  let merge;
120
120
  try {
121
121
  const coordinates = readContext?.coordinates ?? await deriveHostGitCoordinates(cwd);
122
- merge = readContext === undefined
122
+ merge = readContext?.reader === undefined
123
123
  ? await readCommitObject(cwd, coordinates, evidence.mergeCommit)
124
124
  : await readContext.reader.read(evidence.mergeCommit);
125
125
  }