@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.
- package/README.md +62 -72
- package/build/.tsbuildinfo +1 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +15 -2
- package/build/cli/commands/contract/bind/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/kill/handler.js +4 -5
- package/build/cli/commands/shared.js +1 -1
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/compose/handler.js +18 -0
- package/build/cli/commands/task/compose/meta.js +10 -0
- package/build/cli/flags.js +5 -1
- package/build/cli/index.js +11 -6
- package/build/cli/parse-flags.js +21 -21
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +4 -4
- package/build/cli/parse.js +23 -23
- package/build/cli/projection-address.js +7 -7
- package/build/cli/render/task.js +27 -0
- package/build/cli/types.js +1 -1
- package/build/core/addressing.js +3 -3
- package/build/core/call/context.js +3 -3
- package/build/core/call/prompt.js +1 -1
- package/build/core/contract-view.js +1 -1
- package/build/core/draft.js +51 -22
- package/build/core/projection/generation/model.js +1 -1
- package/build/core/projection/generation/projection-generation-runner.js +13 -3
- package/build/core/projection/generation/transitions.js +10 -0
- package/build/core/projection/projection-history.js +1 -1
- package/build/core/projection/projection-kill.js +5 -4
- package/build/core/projection/projection-runner-lock.js +58 -229
- package/build/core/projection/projection-wait.js +2 -2
- package/build/core/projection/projection-wake.js +2 -0
- package/build/core/repository-ledger/claim-evidence.js +1 -1
- package/build/core/repository-ledger/current-state-store.js +455 -337
- package/build/core/repository-ledger/read-model.js +3 -3
- package/build/core/repository-ledger/write-transaction.js +15 -13
- package/build/core/sqlite-process-lifetime-lock.js +194 -0
- package/build/core/task/compose.js +327 -0
- package/build/core/task/index.js +1 -0
- package/build/core/task/settlement-git.js +69 -26
- package/build/core/task/task-git-store.js +1 -0
- package/build/core/task/task-store-repository.js +1 -0
- package/build/flow-error.js +6 -1
- package/build/generated/version.js +2 -2
- package/package.json +2 -2
- package/skills/keiyaku-akuma/SKILL.md +6 -1
- package/skills/keiyaku-task/SKILL.md +23 -1
- 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 {
|
|
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
|
|
22
|
+
return isSqliteProcessLifetimeLockPlatformSupported(platform);
|
|
26
23
|
}
|
|
27
24
|
function errorDetail(error) {
|
|
28
|
-
return
|
|
25
|
+
return sqliteProcessLifetimeLockErrorDetail(error);
|
|
29
26
|
}
|
|
30
|
-
function
|
|
31
|
-
return
|
|
32
|
-
&& error
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
39
|
+
transaction.close();
|
|
86
40
|
}
|
|
87
41
|
catch (error) {
|
|
88
|
-
|
|
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
|
-
|
|
125
|
-
failure
|
|
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
|
|
133
|
-
|
|
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
|
-
|
|
90
|
+
return initializeSqliteProcessLifetimeLockDatabase(projectionRunnerLockPath(projectionDirectory), platform);
|
|
177
91
|
}
|
|
178
92
|
catch (error) {
|
|
179
|
-
|
|
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
|
-
*
|
|
188
|
-
*
|
|
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
|
|
109
|
+
throw unavailable("projection runner lock database could not be opened", error);
|
|
214
110
|
}
|
|
215
|
-
let reservation;
|
|
216
111
|
try {
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
162
|
+
let transaction;
|
|
279
163
|
try {
|
|
280
|
-
|
|
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
|
-
|
|
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(
|
|
306
|
-
if (failure !== undefined)
|
|
307
|
-
throw
|
|
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
|
-
|
|
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 (
|
|
196
|
+
if (isContention(error))
|
|
363
197
|
return { state: "held" };
|
|
364
|
-
|
|
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
|
-
|
|
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 && (
|
|
227
|
-
|| (typeof terminalAtMs === "number" && Number.isFinite(terminalAtMs) && terminalAtMs <
|
|
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
|
}
|