@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,305 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
const COORDINATION_SCHEMA_VERSION = 1;
|
|
7
|
-
const MAX_RECEIPT_BYTES = 1_048_576;
|
|
8
|
-
const DATABASE_BUSY_TIMEOUT_MS = 5_000;
|
|
9
|
-
export const VERIFICATION_COMPLETED_RECEIPT_LIMIT = 8;
|
|
10
|
-
const outputTailSchema = z.object({
|
|
11
|
-
tail: z.string(),
|
|
12
|
-
truncated: z.boolean(),
|
|
13
|
-
omittedBytes: z.number().int().nonnegative(),
|
|
14
|
-
}).strict();
|
|
15
|
-
const executionSchema = z.discriminatedUnion("status", [
|
|
16
|
-
z.object({
|
|
17
|
-
status: z.literal("executed"),
|
|
18
|
-
code: z.number().int(),
|
|
19
|
-
durationMs: z.number().nonnegative(),
|
|
20
|
-
stdout: outputTailSchema,
|
|
21
|
-
stderr: outputTailSchema,
|
|
22
|
-
}).strict(),
|
|
23
|
-
z.object({
|
|
24
|
-
status: z.literal("runner_failed"),
|
|
25
|
-
durationMs: z.number().nonnegative(),
|
|
26
|
-
stdout: outputTailSchema,
|
|
27
|
-
stderr: outputTailSchema,
|
|
28
|
-
cause: z.object({
|
|
29
|
-
code: z.enum(["SPAWN_FAILED", "PROCESS_TERMINATED", "TIMED_OUT", "CANCELLED", "TREE_TERMINATION_FAILED"]),
|
|
30
|
-
message: z.string(),
|
|
31
|
-
}).strict(),
|
|
32
|
-
}).strict(),
|
|
33
|
-
]);
|
|
34
|
-
const resultSchema = z.object({
|
|
35
|
-
source: z.enum(["contract", "repository-gate"]),
|
|
36
|
-
label: z.string(),
|
|
37
|
-
declaration: z.string(),
|
|
38
|
-
command: z.string(),
|
|
39
|
-
args: z.array(z.string()),
|
|
40
|
-
shell: z.boolean(),
|
|
41
|
-
timeoutMs: z.number().int().positive(),
|
|
42
|
-
candidate: z.object({ head: z.string(), tree: z.string() }).strict(),
|
|
43
|
-
executor: z.string(),
|
|
44
|
-
execution: executionSchema,
|
|
45
|
-
}).strict();
|
|
46
|
-
const receiptSchema = z.object({
|
|
47
|
-
version: z.literal(COORDINATION_SCHEMA_VERSION),
|
|
48
|
-
results: z.array(resultSchema),
|
|
49
|
-
}).strict();
|
|
50
|
-
function processIsLive(pid) {
|
|
51
|
-
if (pid <= 0)
|
|
52
|
-
return false;
|
|
53
|
-
try {
|
|
54
|
-
process.kill(pid, 0);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return !(error && typeof error === "object" && "code" in error && error.code === "ESRCH");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function rollbackQuietly(database) {
|
|
62
|
-
try {
|
|
63
|
-
database.exec("ROLLBACK");
|
|
64
|
-
}
|
|
65
|
-
catch { }
|
|
66
|
-
}
|
|
67
|
-
function pragmaNumber(database, name) {
|
|
68
|
-
const row = database.prepare(`PRAGMA ${name}`).get();
|
|
69
|
-
const value = row ? Object.values(row)[0] : undefined;
|
|
70
|
-
if (typeof value !== "number" && typeof value !== "bigint") {
|
|
71
|
-
throw new Error(`verification coordination PRAGMA ${name} is invalid`);
|
|
72
|
-
}
|
|
73
|
-
return Number(value);
|
|
74
|
-
}
|
|
75
|
-
function initializeOrValidate(database) {
|
|
76
|
-
database.exec("BEGIN EXCLUSIVE");
|
|
77
|
-
try {
|
|
78
|
-
const version = pragmaNumber(database, "user_version");
|
|
79
|
-
const objects = database.prepare(`
|
|
80
|
-
SELECT name, type FROM sqlite_schema
|
|
81
|
-
WHERE name NOT LIKE 'sqlite_%' ORDER BY name
|
|
82
|
-
`).all();
|
|
83
|
-
if (version === 0 && objects.length === 0) {
|
|
84
|
-
database.exec(`
|
|
85
|
-
CREATE TABLE verification_coordination_schema (
|
|
86
|
-
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
87
|
-
version INTEGER NOT NULL CHECK (version = ${COORDINATION_SCHEMA_VERSION})
|
|
88
|
-
) STRICT;
|
|
89
|
-
CREATE TABLE verification_executions (
|
|
90
|
-
fingerprint TEXT PRIMARY KEY CHECK (length(fingerprint) = 64),
|
|
91
|
-
owner_token TEXT NOT NULL CHECK (length(owner_token) > 0),
|
|
92
|
-
owner_pid INTEGER NOT NULL CHECK (owner_pid >= 0),
|
|
93
|
-
started_at_ms INTEGER NOT NULL CHECK (started_at_ms >= 0),
|
|
94
|
-
supervisor_pid INTEGER CHECK (supervisor_pid IS NULL OR supervisor_pid > 0),
|
|
95
|
-
supervisor_platform TEXT CHECK (supervisor_platform IS NULL OR supervisor_platform IN ('posix', 'win32')),
|
|
96
|
-
completed_at_ms INTEGER CHECK (completed_at_ms IS NULL OR completed_at_ms >= 0),
|
|
97
|
-
receipt_json TEXT CHECK (receipt_json IS NULL OR json_valid(receipt_json)),
|
|
98
|
-
CHECK ((supervisor_pid IS NULL) = (supervisor_platform IS NULL)),
|
|
99
|
-
CHECK ((receipt_json IS NULL) = (completed_at_ms IS NULL)),
|
|
100
|
-
CHECK (receipt_json IS NULL OR (owner_pid = 0 AND supervisor_pid IS NULL))
|
|
101
|
-
) STRICT;
|
|
102
|
-
`);
|
|
103
|
-
database.prepare("INSERT INTO verification_coordination_schema (singleton, version) VALUES (1, ?)")
|
|
104
|
-
.run(COORDINATION_SCHEMA_VERSION);
|
|
105
|
-
database.exec(`PRAGMA user_version = ${COORDINATION_SCHEMA_VERSION}`);
|
|
106
|
-
}
|
|
107
|
-
else if (version !== COORDINATION_SCHEMA_VERSION) {
|
|
108
|
-
throw new Error(`incompatible verification coordination schema version ${version}; expected ${COORDINATION_SCHEMA_VERSION}`);
|
|
109
|
-
}
|
|
110
|
-
const signature = database.prepare(`
|
|
111
|
-
SELECT name, type FROM sqlite_schema
|
|
112
|
-
WHERE name NOT LIKE 'sqlite_%' ORDER BY name
|
|
113
|
-
`).all().map((object) => {
|
|
114
|
-
const row = object;
|
|
115
|
-
return `${row.type}:${row.name}`;
|
|
116
|
-
}).join(",");
|
|
117
|
-
if (signature !== "table:verification_coordination_schema,table:verification_executions") {
|
|
118
|
-
throw new Error(`verification coordination schema contains unexpected objects: ${signature || "none"}`);
|
|
119
|
-
}
|
|
120
|
-
const columns = database.prepare("PRAGMA table_info(verification_executions)").all();
|
|
121
|
-
const columnSignature = columns
|
|
122
|
-
.map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
|
|
123
|
-
.join(",");
|
|
124
|
-
if (columnSignature !== "fingerprint:TEXT:1:1,owner_token:TEXT:1:0,owner_pid:INTEGER:1:0,started_at_ms:INTEGER:1:0,supervisor_pid:INTEGER:0:0,supervisor_platform:TEXT:0:0,completed_at_ms:INTEGER:0:0,receipt_json:TEXT:0:0") {
|
|
125
|
-
throw new Error("verification coordination execution schema is malformed");
|
|
126
|
-
}
|
|
127
|
-
const metadata = database.prepare("SELECT singleton, version FROM verification_coordination_schema").all();
|
|
128
|
-
if (metadata.length !== 1 || Number(metadata[0].singleton) !== 1 || Number(metadata[0].version) !== COORDINATION_SCHEMA_VERSION) {
|
|
129
|
-
throw new Error("verification coordination schema metadata is malformed");
|
|
130
|
-
}
|
|
131
|
-
const quickCheck = database.prepare("PRAGMA quick_check").get();
|
|
132
|
-
if (!quickCheck || Object.values(quickCheck)[0] !== "ok") {
|
|
133
|
-
throw new Error("verification coordination database failed quick_check");
|
|
134
|
-
}
|
|
135
|
-
database.exec("COMMIT");
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
rollbackQuietly(database);
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function parseReceipt(raw) {
|
|
143
|
-
if (Buffer.byteLength(raw) > MAX_RECEIPT_BYTES)
|
|
144
|
-
throw new Error("verification receipt exceeds its size bound");
|
|
145
|
-
return receiptSchema.parse(JSON.parse(raw)).results;
|
|
146
|
-
}
|
|
147
|
-
function serializeReceipt(results) {
|
|
148
|
-
const raw = JSON.stringify(receiptSchema.parse({ version: COORDINATION_SCHEMA_VERSION, results }));
|
|
149
|
-
if (Buffer.byteLength(raw) > MAX_RECEIPT_BYTES)
|
|
150
|
-
throw new Error("verification receipt exceeds its size bound");
|
|
151
|
-
return raw;
|
|
152
|
-
}
|
|
153
|
-
export class VerificationCoordination {
|
|
154
|
-
#databasePath;
|
|
155
|
-
constructor(repositoryRoot) {
|
|
156
|
-
const directory = path.join(repositoryRoot, ".keiyaku", "runtime");
|
|
157
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
158
|
-
this.#databasePath = path.join(directory, "verification.sqlite");
|
|
159
|
-
this.#withDatabase(() => undefined);
|
|
160
|
-
}
|
|
161
|
-
fingerprint(key) {
|
|
162
|
-
return createHash("sha256").update(key).digest("hex");
|
|
163
|
-
}
|
|
164
|
-
claim(fingerprint, staleAfterMs, nowMs = Date.now()) {
|
|
165
|
-
return this.#withTransaction((database) => {
|
|
166
|
-
const row = database.prepare(`
|
|
167
|
-
SELECT owner_token, owner_pid, started_at_ms, supervisor_pid, supervisor_platform, completed_at_ms, receipt_json
|
|
168
|
-
FROM verification_executions WHERE fingerprint = ?
|
|
169
|
-
`).get(fingerprint);
|
|
170
|
-
if (row?.receipt_json !== null && row?.receipt_json !== undefined) {
|
|
171
|
-
return { status: "receipt", results: parseReceipt(row.receipt_json) };
|
|
172
|
-
}
|
|
173
|
-
const token = randomUUID();
|
|
174
|
-
if (!row) {
|
|
175
|
-
database.prepare(`
|
|
176
|
-
INSERT INTO verification_executions
|
|
177
|
-
(fingerprint, owner_token, owner_pid, started_at_ms, supervisor_pid, supervisor_platform, completed_at_ms, receipt_json)
|
|
178
|
-
VALUES (?, ?, ?, ?, NULL, NULL, NULL, NULL)
|
|
179
|
-
`).run(fingerprint, token, process.pid, nowMs);
|
|
180
|
-
return { status: "owner", token };
|
|
181
|
-
}
|
|
182
|
-
const ownerPid = Number(row.owner_pid);
|
|
183
|
-
const stale = nowMs - Number(row.started_at_ms) > staleAfterMs;
|
|
184
|
-
if (processIsLive(ownerPid) && !stale)
|
|
185
|
-
return { status: "wait" };
|
|
186
|
-
database.prepare(`
|
|
187
|
-
UPDATE verification_executions
|
|
188
|
-
SET owner_token = ?, owner_pid = ?, started_at_ms = ?
|
|
189
|
-
WHERE fingerprint = ?
|
|
190
|
-
`).run(token, process.pid, nowMs, fingerprint);
|
|
191
|
-
const platform = row.supervisor_platform;
|
|
192
|
-
const supervisorPid = row.supervisor_pid === null ? undefined : Number(row.supervisor_pid);
|
|
193
|
-
return {
|
|
194
|
-
status: "owner",
|
|
195
|
-
token,
|
|
196
|
-
...(supervisorPid && (platform === "posix" || platform === "win32")
|
|
197
|
-
? { priorSupervisor: { pid: supervisorPid, platform } }
|
|
198
|
-
: {}),
|
|
199
|
-
};
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
recordSupervisor(fingerprint, token, identity) {
|
|
203
|
-
this.#ownedUpdate(fingerprint, token, (database) => {
|
|
204
|
-
database.prepare(`
|
|
205
|
-
UPDATE verification_executions SET supervisor_pid = ?, supervisor_platform = ?
|
|
206
|
-
WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
|
|
207
|
-
`).run(identity.pid, identity.platform, fingerprint, token);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
clearSupervisor(fingerprint, token) {
|
|
211
|
-
this.#ownedUpdate(fingerprint, token, (database) => {
|
|
212
|
-
database.prepare(`
|
|
213
|
-
UPDATE verification_executions SET supervisor_pid = NULL, supervisor_platform = NULL
|
|
214
|
-
WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
|
|
215
|
-
`).run(fingerprint, token);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
complete(fingerprint, token, results, completedAtMs = Date.now()) {
|
|
219
|
-
const recoveryRequired = results.some((result) => result.execution.status === "runner_failed"
|
|
220
|
-
&& result.execution.cause.code === "TREE_TERMINATION_FAILED");
|
|
221
|
-
return this.#withTransaction((database) => {
|
|
222
|
-
if (recoveryRequired) {
|
|
223
|
-
const changed = database.prepare(`
|
|
224
|
-
UPDATE verification_executions
|
|
225
|
-
SET owner_pid = 0, started_at_ms = 0
|
|
226
|
-
WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
|
|
227
|
-
`).run(fingerprint, token).changes;
|
|
228
|
-
return Number(changed) === 1
|
|
229
|
-
? { status: "recovery-required" }
|
|
230
|
-
: { status: "ownership-lost" };
|
|
231
|
-
}
|
|
232
|
-
const receipt = serializeReceipt(results);
|
|
233
|
-
const changed = database.prepare(`
|
|
234
|
-
UPDATE verification_executions
|
|
235
|
-
SET owner_pid = 0, supervisor_pid = NULL, supervisor_platform = NULL, completed_at_ms = ?, receipt_json = ?
|
|
236
|
-
WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
|
|
237
|
-
`).run(completedAtMs, receipt, fingerprint, token).changes;
|
|
238
|
-
if (Number(changed) !== 1)
|
|
239
|
-
return { status: "ownership-lost" };
|
|
240
|
-
const countRow = database.prepare(`
|
|
241
|
-
SELECT count(*) AS count FROM verification_executions WHERE receipt_json IS NOT NULL
|
|
242
|
-
`).get();
|
|
243
|
-
const excess = Number(countRow.count) - VERIFICATION_COMPLETED_RECEIPT_LIMIT;
|
|
244
|
-
if (excess > 0) {
|
|
245
|
-
database.prepare(`
|
|
246
|
-
DELETE FROM verification_executions
|
|
247
|
-
WHERE fingerprint IN (
|
|
248
|
-
SELECT fingerprint FROM verification_executions
|
|
249
|
-
WHERE receipt_json IS NOT NULL
|
|
250
|
-
ORDER BY completed_at_ms ASC, fingerprint ASC
|
|
251
|
-
LIMIT ?
|
|
252
|
-
)
|
|
253
|
-
`).run(excess);
|
|
254
|
-
}
|
|
255
|
-
return { status: "published" };
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
releaseForTakeover(fingerprint, token) {
|
|
259
|
-
this.#withTransaction((database) => {
|
|
260
|
-
database.prepare(`
|
|
261
|
-
UPDATE verification_executions SET owner_pid = 0, started_at_ms = 0
|
|
262
|
-
WHERE fingerprint = ? AND owner_token = ? AND receipt_json IS NULL
|
|
263
|
-
`).run(fingerprint, token);
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
#ownedUpdate(fingerprint, token, update) {
|
|
267
|
-
this.#withTransaction((database) => {
|
|
268
|
-
update(database);
|
|
269
|
-
const row = database.prepare(`
|
|
270
|
-
SELECT owner_token FROM verification_executions
|
|
271
|
-
WHERE fingerprint = ? AND receipt_json IS NULL
|
|
272
|
-
`).get(fingerprint);
|
|
273
|
-
if (row?.owner_token !== token)
|
|
274
|
-
throw new Error("verification coordination ownership changed");
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
#withTransaction(operation) {
|
|
278
|
-
return this.#withDatabase((database) => {
|
|
279
|
-
database.exec("BEGIN IMMEDIATE");
|
|
280
|
-
try {
|
|
281
|
-
const result = operation(database);
|
|
282
|
-
database.exec("COMMIT");
|
|
283
|
-
return result;
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
rollbackQuietly(database);
|
|
287
|
-
throw error;
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
#withDatabase(operation) {
|
|
292
|
-
const database = new DatabaseSync(this.#databasePath, {
|
|
293
|
-
enableDoubleQuotedStringLiterals: false,
|
|
294
|
-
allowExtension: false,
|
|
295
|
-
});
|
|
296
|
-
try {
|
|
297
|
-
database.exec(`PRAGMA busy_timeout = ${DATABASE_BUSY_TIMEOUT_MS}`);
|
|
298
|
-
initializeOrValidate(database);
|
|
299
|
-
return operation(database);
|
|
300
|
-
}
|
|
301
|
-
finally {
|
|
302
|
-
database.close();
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { PROCESS_GROUP_KILL_GRACE_MS } from "../process-group.js";
|
|
5
|
-
export const VERIFICATION_SUPERVISOR_PROTOCOL_VERSION = 1;
|
|
6
|
-
const configureMessageSchema = z.object({
|
|
7
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
8
|
-
type: z.literal("configure"),
|
|
9
|
-
cwd: z.string().min(1),
|
|
10
|
-
command: z.string().min(1),
|
|
11
|
-
args: z.array(z.string()),
|
|
12
|
-
shell: z.boolean(),
|
|
13
|
-
timeoutMs: z.number().int().positive(),
|
|
14
|
-
}).strict();
|
|
15
|
-
const startMessageSchema = z.object({
|
|
16
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
17
|
-
type: z.literal("start"),
|
|
18
|
-
}).strict();
|
|
19
|
-
export const supervisorMessageSchema = z.discriminatedUnion("type", [
|
|
20
|
-
z.object({
|
|
21
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
22
|
-
type: z.literal("ready"),
|
|
23
|
-
}).strict(),
|
|
24
|
-
z.object({
|
|
25
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
26
|
-
type: z.literal("configured"),
|
|
27
|
-
}).strict(),
|
|
28
|
-
z.object({
|
|
29
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
30
|
-
type: z.literal("started"),
|
|
31
|
-
}).strict(),
|
|
32
|
-
z.object({
|
|
33
|
-
version: z.literal(VERIFICATION_SUPERVISOR_PROTOCOL_VERSION),
|
|
34
|
-
type: z.literal("terminal"),
|
|
35
|
-
durationMs: z.number().nonnegative(),
|
|
36
|
-
outcome: z.discriminatedUnion("kind", [
|
|
37
|
-
z.object({ kind: z.literal("exit"), code: z.number().int() }).strict(),
|
|
38
|
-
z.object({ kind: z.literal("signal"), signal: z.string().nullable() }).strict(),
|
|
39
|
-
z.object({ kind: z.literal("spawn-failed"), message: z.string() }).strict(),
|
|
40
|
-
z.object({ kind: z.literal("timeout") }).strict(),
|
|
41
|
-
z.object({ kind: z.literal("tree-termination-failed"), message: z.string() }).strict(),
|
|
42
|
-
]),
|
|
43
|
-
}).strict(),
|
|
44
|
-
]);
|
|
45
|
-
function verificationSupervisorPath() {
|
|
46
|
-
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
47
|
-
return fileURLToPath(new URL(`./verification-supervisor.${extension}`, import.meta.url));
|
|
48
|
-
}
|
|
49
|
-
export function verificationSupervisorArguments() {
|
|
50
|
-
const target = verificationSupervisorPath();
|
|
51
|
-
return import.meta.url.endsWith(".ts") ? ["--import", "tsx", target] : [target];
|
|
52
|
-
}
|
|
53
|
-
function send(message) {
|
|
54
|
-
if (process.connected)
|
|
55
|
-
process.send?.(message);
|
|
56
|
-
}
|
|
57
|
-
function durationMs(started) {
|
|
58
|
-
return Number((process.hrtime.bigint() - started) / 1000000n);
|
|
59
|
-
}
|
|
60
|
-
function forward(source, destination, outputLost) {
|
|
61
|
-
source?.on("data", (chunk) => {
|
|
62
|
-
try {
|
|
63
|
-
if (!destination.write(chunk)) {
|
|
64
|
-
source.pause();
|
|
65
|
-
destination.once("drain", () => source.resume());
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
outputLost();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
async function drainOutput() {
|
|
74
|
-
const drains = [];
|
|
75
|
-
for (const stream of [process.stdout, process.stderr]) {
|
|
76
|
-
if (!stream.destroyed && !stream.closed && stream.writableNeedDrain) {
|
|
77
|
-
drains.push(new Promise((resolve) => stream.once("drain", resolve)));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
await Promise.all(drains);
|
|
81
|
-
}
|
|
82
|
-
function defaultTaskkill(pid) {
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
execFile("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (error) => {
|
|
85
|
-
if (error)
|
|
86
|
-
reject(error);
|
|
87
|
-
else
|
|
88
|
-
resolve();
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
export async function terminateWindowsPayloadTree(pid, taskkill = defaultTaskkill) {
|
|
93
|
-
await taskkill(pid);
|
|
94
|
-
}
|
|
95
|
-
function signalOwnedGroup(signal) {
|
|
96
|
-
if (process.platform === "win32")
|
|
97
|
-
return;
|
|
98
|
-
try {
|
|
99
|
-
process.kill(-process.pid, signal);
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
if (!(error && typeof error === "object" && "code" in error && error.code === "ESRCH"))
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
async function runSupervisor() {
|
|
107
|
-
if (typeof process.send !== "function")
|
|
108
|
-
process.exit(2);
|
|
109
|
-
let payload;
|
|
110
|
-
let payloadStartedAt;
|
|
111
|
-
let commandTimer;
|
|
112
|
-
let killTimer;
|
|
113
|
-
let shutdown;
|
|
114
|
-
let finalized = false;
|
|
115
|
-
let configuration;
|
|
116
|
-
let payloadClosedAtMs;
|
|
117
|
-
let windowsTaskkillComplete = false;
|
|
118
|
-
const finish = async (message) => {
|
|
119
|
-
if (finalized)
|
|
120
|
-
return;
|
|
121
|
-
finalized = true;
|
|
122
|
-
if (commandTimer)
|
|
123
|
-
clearTimeout(commandTimer);
|
|
124
|
-
if (killTimer)
|
|
125
|
-
clearTimeout(killTimer);
|
|
126
|
-
await drainOutput();
|
|
127
|
-
if (message)
|
|
128
|
-
send(message);
|
|
129
|
-
if (process.connected)
|
|
130
|
-
process.disconnect?.();
|
|
131
|
-
process.exit(0);
|
|
132
|
-
};
|
|
133
|
-
const finishShutdown = () => {
|
|
134
|
-
if (shutdown === "timeout") {
|
|
135
|
-
void finish({
|
|
136
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
137
|
-
type: "terminal",
|
|
138
|
-
durationMs: payloadClosedAtMs ?? durationMs(payloadStartedAt),
|
|
139
|
-
outcome: { kind: "timeout" },
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
void finish();
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const beginShutdown = (reason) => {
|
|
147
|
-
if (shutdown || finalized)
|
|
148
|
-
return;
|
|
149
|
-
shutdown = reason;
|
|
150
|
-
if (!payload) {
|
|
151
|
-
void finish();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (process.platform === "win32") {
|
|
155
|
-
if (!payload.pid) {
|
|
156
|
-
void finish();
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
void terminateWindowsPayloadTree(payload.pid).then(() => {
|
|
160
|
-
windowsTaskkillComplete = true;
|
|
161
|
-
if (payloadClosedAtMs !== undefined)
|
|
162
|
-
finishShutdown();
|
|
163
|
-
}, (error) => {
|
|
164
|
-
send({
|
|
165
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
166
|
-
type: "terminal",
|
|
167
|
-
durationMs: durationMs(payloadStartedAt),
|
|
168
|
-
outcome: {
|
|
169
|
-
kind: "tree-termination-failed",
|
|
170
|
-
message: error instanceof Error ? error.message : String(error),
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
signalOwnedGroup("SIGTERM");
|
|
177
|
-
killTimer = setTimeout(() => signalOwnedGroup("SIGKILL"), PROCESS_GROUP_KILL_GRACE_MS);
|
|
178
|
-
};
|
|
179
|
-
process.on("SIGTERM", () => beginShutdown("external"));
|
|
180
|
-
process.on("disconnect", () => beginShutdown("parent-lost"));
|
|
181
|
-
const outputLost = () => beginShutdown("parent-lost");
|
|
182
|
-
process.stdout.once("error", outputLost);
|
|
183
|
-
process.stdout.once("close", outputLost);
|
|
184
|
-
process.stderr.once("error", outputLost);
|
|
185
|
-
process.stderr.once("close", outputLost);
|
|
186
|
-
process.on("message", (raw) => {
|
|
187
|
-
const configured = configureMessageSchema.safeParse(raw);
|
|
188
|
-
if (configured.success && !configuration && !payload && process.connected) {
|
|
189
|
-
configuration = configured.data;
|
|
190
|
-
send({ version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION, type: "configured" });
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const start = startMessageSchema.safeParse(raw);
|
|
194
|
-
if (!start.success || !configuration || payload || !process.connected) {
|
|
195
|
-
void finish();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const message = configuration;
|
|
199
|
-
payloadStartedAt = process.hrtime.bigint();
|
|
200
|
-
try {
|
|
201
|
-
payload = spawn(message.command, message.args, {
|
|
202
|
-
cwd: message.cwd,
|
|
203
|
-
shell: message.shell,
|
|
204
|
-
env: process.env,
|
|
205
|
-
detached: false,
|
|
206
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
void finish({
|
|
211
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
212
|
-
type: "terminal",
|
|
213
|
-
durationMs: durationMs(payloadStartedAt),
|
|
214
|
-
outcome: { kind: "spawn-failed", message: error instanceof Error ? error.message : String(error) },
|
|
215
|
-
});
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
forward(payload.stdout, process.stdout, outputLost);
|
|
219
|
-
forward(payload.stderr, process.stderr, outputLost);
|
|
220
|
-
payload.once("error", (error) => {
|
|
221
|
-
void finish({
|
|
222
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
223
|
-
type: "terminal",
|
|
224
|
-
durationMs: durationMs(payloadStartedAt),
|
|
225
|
-
outcome: { kind: "spawn-failed", message: error.message },
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
payload.once("close", (code, signal) => {
|
|
229
|
-
const elapsed = durationMs(payloadStartedAt);
|
|
230
|
-
payloadClosedAtMs = elapsed;
|
|
231
|
-
if (shutdown && process.platform === "win32") {
|
|
232
|
-
if (windowsTaskkillComplete)
|
|
233
|
-
finishShutdown();
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
else if (shutdown === "parent-lost") {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
else if (shutdown === "timeout") {
|
|
240
|
-
void finish({
|
|
241
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
242
|
-
type: "terminal",
|
|
243
|
-
durationMs: elapsed,
|
|
244
|
-
outcome: { kind: "timeout" },
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
else if (shutdown) {
|
|
248
|
-
void finish();
|
|
249
|
-
}
|
|
250
|
-
else if (code === null) {
|
|
251
|
-
void finish({
|
|
252
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
253
|
-
type: "terminal",
|
|
254
|
-
durationMs: elapsed,
|
|
255
|
-
outcome: { kind: "signal", signal },
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
void finish({
|
|
260
|
-
version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
|
|
261
|
-
type: "terminal",
|
|
262
|
-
durationMs: elapsed,
|
|
263
|
-
outcome: { kind: "exit", code },
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
commandTimer = setTimeout(() => beginShutdown("timeout"), message.timeoutMs);
|
|
268
|
-
commandTimer.unref?.();
|
|
269
|
-
send({ version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION, type: "started" });
|
|
270
|
-
});
|
|
271
|
-
send({ version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION, type: "ready" });
|
|
272
|
-
}
|
|
273
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
274
|
-
void runSupervisor();
|
|
275
|
-
}
|