@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.
- package/README.md +79 -85
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +4 -4
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +14 -10
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/handler.js +4 -5
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +2 -2
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/compose/handler.js +18 -0
- package/build/cli/commands/task/compose/meta.js +10 -0
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +48 -12
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +14 -8
- package/build/cli/parse-flags.js +60 -21
- package/build/cli/parse-metadata.js +10 -4
- package/build/cli/parse-selectors.js +22 -4
- package/build/cli/parse.js +43 -30
- package/build/cli/projection-address.js +7 -7
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +33 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/addressing.js +3 -3
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +46 -33
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +43 -6
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +60 -27
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +19 -14
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +132 -76
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +24 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +8 -8
- package/build/core/projection/projection-kill.js +20 -10
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +48 -29
- package/build/core/projection/projection-wake.js +28 -88
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/claim-evidence.js +1 -1
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +464 -349
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +57 -9
- package/build/core/repository-ledger/write-transaction.js +24 -17
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +328 -0
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +4 -3
- package/build/core/task/settlement-git.js +70 -30
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-git-store.js +1 -0
- package/build/core/task/task-store-repository.js +2 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +15 -2
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +2 -2
- package/skills/keiyaku-akuma/SKILL.md +27 -21
- package/skills/keiyaku-task/SKILL.md +61 -5
- package/skills/keiyaku-workflow/SKILL.md +81 -26
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -382
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
export const GENERATION_VERDICT_STATES = [
|
|
2
|
-
"completed",
|
|
3
|
-
"failed",
|
|
4
|
-
"launch-failed",
|
|
5
|
-
"lost-runner",
|
|
6
|
-
"dismissed",
|
|
7
|
-
"interrupted",
|
|
8
|
-
"killed",
|
|
9
|
-
];
|
|
10
|
-
function asLaunch(record) {
|
|
11
|
-
return record.kind === "launch"
|
|
12
|
-
? record
|
|
13
|
-
: undefined;
|
|
14
|
-
}
|
|
15
|
-
function asAdoption(record) {
|
|
16
|
-
return record.kind === "adoption"
|
|
17
|
-
? record
|
|
18
|
-
: undefined;
|
|
19
|
-
}
|
|
20
|
-
function asKillIntent(record) {
|
|
21
|
-
return record.kind === "kill-intent"
|
|
22
|
-
? record
|
|
23
|
-
: undefined;
|
|
24
|
-
}
|
|
25
|
-
function asInterruptIntent(record) {
|
|
26
|
-
return record.kind === "interrupt-intent"
|
|
27
|
-
? record
|
|
28
|
-
: undefined;
|
|
29
|
-
}
|
|
30
|
-
function asVerdict(record) {
|
|
31
|
-
return record.kind === "verdict"
|
|
32
|
-
? record
|
|
33
|
-
: undefined;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Select the current generation using ledger sequence only. The store validates
|
|
37
|
-
* dense sequence and transition legality before records reach this fold.
|
|
38
|
-
*/
|
|
39
|
-
export function selectCurrentGeneration(records) {
|
|
40
|
-
let launch;
|
|
41
|
-
for (const record of records) {
|
|
42
|
-
const candidate = asLaunch(record);
|
|
43
|
-
if (candidate)
|
|
44
|
-
launch = candidate;
|
|
45
|
-
}
|
|
46
|
-
if (!launch)
|
|
47
|
-
return null;
|
|
48
|
-
let adoption;
|
|
49
|
-
let killIntent;
|
|
50
|
-
let interruptIntent;
|
|
51
|
-
let verdict;
|
|
52
|
-
for (const record of records) {
|
|
53
|
-
if (record.seq <= launch.seq || record.executionId !== launch.executionId)
|
|
54
|
-
continue;
|
|
55
|
-
adoption ??= asAdoption(record);
|
|
56
|
-
killIntent ??= asKillIntent(record);
|
|
57
|
-
interruptIntent ??= asInterruptIntent(record);
|
|
58
|
-
verdict ??= asVerdict(record);
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
launch,
|
|
62
|
-
...(adoption ? { adoption } : {}),
|
|
63
|
-
...(killIntent ? { killIntent } : {}),
|
|
64
|
-
...(interruptIntent ? { interruptIntent } : {}),
|
|
65
|
-
...(verdict ? { verdict } : {}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function verdictState(record) {
|
|
69
|
-
const state = record?.facts.state;
|
|
70
|
-
return typeof state === "string"
|
|
71
|
-
&& GENERATION_VERDICT_STATES.includes(state)
|
|
72
|
-
? state
|
|
73
|
-
: undefined;
|
|
74
|
-
}
|
|
75
|
-
function phaseForVerdict(state) {
|
|
76
|
-
switch (state) {
|
|
77
|
-
case "completed": return "done";
|
|
78
|
-
case "dismissed": return "dismissed";
|
|
79
|
-
case "interrupted": return "done";
|
|
80
|
-
case "killed": return "killed";
|
|
81
|
-
case "lost-runner": return "lost";
|
|
82
|
-
case "failed":
|
|
83
|
-
case "launch-failed":
|
|
84
|
-
return "failed";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export function deriveProjectionLife(input) {
|
|
88
|
-
const chain = selectCurrentGeneration(input.records);
|
|
89
|
-
if (!chain)
|
|
90
|
-
return { phase: "quiescent" };
|
|
91
|
-
const executionId = chain.launch.executionId;
|
|
92
|
-
if (chain.verdict) {
|
|
93
|
-
const state = verdictState(chain.verdict);
|
|
94
|
-
return state
|
|
95
|
-
? { phase: phaseForVerdict(state), executionId, verdict: state }
|
|
96
|
-
: { phase: "unknown", executionId, diagnostic: "current generation has an invalid verdict" };
|
|
97
|
-
}
|
|
98
|
-
if (!chain.adoption) {
|
|
99
|
-
const createdAt = chain.launch.facts.createdAt;
|
|
100
|
-
const launchedAtMs = typeof createdAt === "string" ? Date.parse(createdAt) : Number.NaN;
|
|
101
|
-
if (!Number.isFinite(launchedAtMs)) {
|
|
102
|
-
return { phase: "unknown", executionId, diagnostic: "current launch has an invalid createdAt fact" };
|
|
103
|
-
}
|
|
104
|
-
return input.nowMs - launchedAtMs >= input.startupTimeoutMs
|
|
105
|
-
? { phase: "startup-timeout", executionId }
|
|
106
|
-
: { phase: "minting", executionId };
|
|
107
|
-
}
|
|
108
|
-
switch (input.runnerLock) {
|
|
109
|
-
case "held": return { phase: "active", executionId };
|
|
110
|
-
case "released": return { phase: "lost", executionId };
|
|
111
|
-
case "unknown":
|
|
112
|
-
case undefined:
|
|
113
|
-
return { phase: "unknown", executionId, diagnostic: "runner lock could not be determined" };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { DatabaseSync } from "node:sqlite";
|
|
4
|
-
export const PROJECTION_RUNNER_LOCK_DATABASE = "leash";
|
|
5
|
-
const SQLITE_BUSY = 5;
|
|
6
|
-
const SUPPORTED_PLATFORMS = new Set(["darwin", "linux", "win32"]);
|
|
7
|
-
export class ProjectionRunnerLockUnavailableError extends Error {
|
|
8
|
-
code = "PROJECTION_RUNNER_LOCK_UNAVAILABLE";
|
|
9
|
-
constructor(message, options) {
|
|
10
|
-
super(message, options);
|
|
11
|
-
this.name = "ProjectionRunnerLockUnavailableError";
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export class ProjectionRunnerLockUnsupportedError extends Error {
|
|
15
|
-
code = "PROJECTION_RUNNER_LOCK_UNSUPPORTED";
|
|
16
|
-
constructor(platform) {
|
|
17
|
-
super(`projection runner locks are unsupported on platform '${platform}'`);
|
|
18
|
-
this.name = "ProjectionRunnerLockUnsupportedError";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export function projectionRunnerLockPath(projectionDirectory) {
|
|
22
|
-
return path.join(projectionDirectory, PROJECTION_RUNNER_LOCK_DATABASE);
|
|
23
|
-
}
|
|
24
|
-
export function isProjectionRunnerLockPlatformSupported(platform = process.platform) {
|
|
25
|
-
return SUPPORTED_PLATFORMS.has(platform);
|
|
26
|
-
}
|
|
27
|
-
function errorDetail(error) {
|
|
28
|
-
return error instanceof Error ? error.message : String(error);
|
|
29
|
-
}
|
|
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
|
-
}
|
|
47
|
-
}
|
|
48
|
-
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
|
-
}
|
|
63
|
-
}
|
|
64
|
-
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
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function closeTransaction(database) {
|
|
77
|
-
let failure;
|
|
78
|
-
try {
|
|
79
|
-
database.exec("ROLLBACK");
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
failure = error;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
database.close();
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
failure ??= error;
|
|
89
|
-
}
|
|
90
|
-
return failure;
|
|
91
|
-
}
|
|
92
|
-
function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
|
|
93
|
-
let closed = false;
|
|
94
|
-
let reservation = initialReservation;
|
|
95
|
-
return {
|
|
96
|
-
path: lockPath,
|
|
97
|
-
releaseReservationForTransfer() {
|
|
98
|
-
if (closed || reservation === undefined)
|
|
99
|
-
return;
|
|
100
|
-
const released = reservation;
|
|
101
|
-
reservation = undefined;
|
|
102
|
-
const failure = closeTransaction(released);
|
|
103
|
-
if (failure !== undefined) {
|
|
104
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner reservation release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
reclaimReservation() {
|
|
108
|
-
if (closed || reservation !== undefined)
|
|
109
|
-
return;
|
|
110
|
-
try {
|
|
111
|
-
reservation = openReservation(lockPath, busyTimeoutMs);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner reservation reclaim failed: ${errorDetail(error)}`, { cause: error });
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
close() {
|
|
118
|
-
if (closed)
|
|
119
|
-
return;
|
|
120
|
-
closed = true;
|
|
121
|
-
let failure;
|
|
122
|
-
if (reservation !== undefined)
|
|
123
|
-
failure = closeTransaction(reservation);
|
|
124
|
-
const guardFailure = closeTransaction(guard);
|
|
125
|
-
failure ??= guardFailure;
|
|
126
|
-
if (failure !== undefined) {
|
|
127
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner lock release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
}
|
|
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 });
|
|
144
|
-
}
|
|
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
|
-
*/
|
|
150
|
-
export function initializeProjectionRunnerLockDatabase(projectionDirectory, platform = process.platform) {
|
|
151
|
-
if (!isProjectionRunnerLockPlatformSupported(platform)) {
|
|
152
|
-
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
|
-
try {
|
|
176
|
-
database.close();
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
initializationError ??= error;
|
|
180
|
-
}
|
|
181
|
-
if (initializationError !== undefined) {
|
|
182
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner lock database initialization failed: ${errorDetail(initializationError)}`, { cause: initializationError });
|
|
183
|
-
}
|
|
184
|
-
return lockPath;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
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.
|
|
191
|
-
*/
|
|
192
|
-
export function acquireProjectionRunnerLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
|
|
193
|
-
if (!isProjectionRunnerLockPlatformSupported(platform)) {
|
|
194
|
-
throw new ProjectionRunnerLockUnsupportedError(platform);
|
|
195
|
-
}
|
|
196
|
-
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
|
-
let guard;
|
|
209
|
-
try {
|
|
210
|
-
guard = openGuard(lockPath, busyTimeoutMs);
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner lock database could not be opened: ${errorDetail(error)}`, { cause: error });
|
|
214
|
-
}
|
|
215
|
-
let reservation;
|
|
216
|
-
try {
|
|
217
|
-
reservation = openReservation(lockPath, busyTimeoutMs);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (reservation)
|
|
221
|
-
closeTransaction(reservation);
|
|
222
|
-
closeTransaction(guard);
|
|
223
|
-
return closeAfterFailedAcquire(guard, error);
|
|
224
|
-
}
|
|
225
|
-
return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
|
|
226
|
-
}
|
|
227
|
-
export function acquireProjectionRunnerTransferTarget(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
|
|
228
|
-
if (!isProjectionRunnerLockPlatformSupported(platform)) {
|
|
229
|
-
throw new ProjectionRunnerLockUnsupportedError(platform);
|
|
230
|
-
}
|
|
231
|
-
assertBusyTimeout(busyTimeoutMs);
|
|
232
|
-
const lockPath = projectionRunnerLockPath(projectionDirectory);
|
|
233
|
-
let guard;
|
|
234
|
-
try {
|
|
235
|
-
guard = openGuard(lockPath, busyTimeoutMs);
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
if (guard)
|
|
239
|
-
closeTransaction(guard);
|
|
240
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner transfer acquisition failed: ${errorDetail(error)}`, { cause: error });
|
|
241
|
-
}
|
|
242
|
-
let closed = false;
|
|
243
|
-
return {
|
|
244
|
-
path: lockPath,
|
|
245
|
-
promote() {
|
|
246
|
-
if (closed)
|
|
247
|
-
throw new ProjectionRunnerLockUnavailableError("projection runner transfer target is closed");
|
|
248
|
-
let reservation;
|
|
249
|
-
try {
|
|
250
|
-
reservation = openReservation(lockPath, busyTimeoutMs);
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
if (reservation)
|
|
254
|
-
closeTransaction(reservation);
|
|
255
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner transfer promotion failed: ${errorDetail(error)}`, { cause: error });
|
|
256
|
-
}
|
|
257
|
-
closed = true;
|
|
258
|
-
return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
|
|
259
|
-
},
|
|
260
|
-
close() {
|
|
261
|
-
if (closed)
|
|
262
|
-
return;
|
|
263
|
-
closed = true;
|
|
264
|
-
const failure = closeTransaction(guard);
|
|
265
|
-
if (failure !== undefined) {
|
|
266
|
-
throw new ProjectionRunnerLockUnavailableError(`projection runner transfer release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
/** Acquire positive death evidence. Any active guard or transfer blocks this. */
|
|
272
|
-
export function acquireProjectionRunnerSettlementLock(projectionDirectory, platform = process.platform, busyTimeoutMs = 0) {
|
|
273
|
-
if (!isProjectionRunnerLockPlatformSupported(platform)) {
|
|
274
|
-
throw new ProjectionRunnerLockUnsupportedError(platform);
|
|
275
|
-
}
|
|
276
|
-
assertBusyTimeout(busyTimeoutMs);
|
|
277
|
-
const lockPath = projectionRunnerLockPath(projectionDirectory);
|
|
278
|
-
let database;
|
|
279
|
-
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
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch (error) {
|
|
289
|
-
try {
|
|
290
|
-
database.close();
|
|
291
|
-
}
|
|
292
|
-
catch { }
|
|
293
|
-
if (isSqliteBusy(error)) {
|
|
294
|
-
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 });
|
|
297
|
-
}
|
|
298
|
-
let closed = false;
|
|
299
|
-
return {
|
|
300
|
-
path: lockPath,
|
|
301
|
-
close() {
|
|
302
|
-
if (closed)
|
|
303
|
-
return;
|
|
304
|
-
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
|
-
}
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
}
|
|
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
|
-
*/
|
|
317
|
-
export function observeProjectionRunnerLock(projectionDirectory, platform = process.platform) {
|
|
318
|
-
if (!isProjectionRunnerLockPlatformSupported(platform)) {
|
|
319
|
-
return {
|
|
320
|
-
state: "unknown",
|
|
321
|
-
reason: "unsupported-platform",
|
|
322
|
-
detail: `projection runner locks are unsupported on platform '${platform}'`,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const lockPath = projectionRunnerLockPath(projectionDirectory);
|
|
326
|
-
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
|
-
}
|
|
359
|
-
return { state: "released" };
|
|
360
|
-
}
|
|
361
|
-
catch (error) {
|
|
362
|
-
if (isSqliteBusy(error))
|
|
363
|
-
return { state: "held" };
|
|
364
|
-
return { state: "unknown", reason: "probe-failed", detail: errorDetail(error) };
|
|
365
|
-
}
|
|
366
|
-
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
|
-
try {
|
|
376
|
-
database.close();
|
|
377
|
-
}
|
|
378
|
-
catch {
|
|
379
|
-
// Observer cleanup failures cannot be converted into death evidence.
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
@@ -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
|
-
}
|