@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,6 +1,13 @@
|
|
|
1
|
-
import { GENERATION_VERDICT_STATES, } from "../projection-life-protocol.js";
|
|
2
1
|
import { OutcomeErrorValidationError, parseCurrentOutcomeError, } from "../../../agents/harness/index.js";
|
|
3
|
-
export
|
|
2
|
+
export const GENERATION_VERDICT_STATES = [
|
|
3
|
+
"completed",
|
|
4
|
+
"failed",
|
|
5
|
+
"launch-failed",
|
|
6
|
+
"lost-runner",
|
|
7
|
+
"dismissed",
|
|
8
|
+
"interrupted",
|
|
9
|
+
"killed",
|
|
10
|
+
];
|
|
4
11
|
export const VERDICTS = new Set(GENERATION_VERDICT_STATES);
|
|
5
12
|
export class ProjectionGenerationStoreError extends Error {
|
|
6
13
|
code = "PROJECTION_GENERATION_STORE_ERROR";
|
|
@@ -12,17 +19,17 @@ export class ProjectionGenerationStoreError extends Error {
|
|
|
12
19
|
this.observation = options?.observation ?? "unknown";
|
|
13
20
|
}
|
|
14
21
|
}
|
|
15
|
-
|
|
16
|
-
return error instanceof ProjectionGenerationStoreError
|
|
17
|
-
&& error.observation === "incompatible";
|
|
18
|
-
}
|
|
19
|
-
export function isPlainObject(value) {
|
|
22
|
+
function isPlainObject(value) {
|
|
20
23
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
21
24
|
return false;
|
|
22
25
|
const prototype = Object.getPrototypeOf(value);
|
|
23
26
|
return prototype === Object.prototype || prototype === null;
|
|
24
27
|
}
|
|
25
|
-
export function
|
|
28
|
+
export function isIncompatibleProjectionGenerationStoreError(error) {
|
|
29
|
+
return error instanceof ProjectionGenerationStoreError
|
|
30
|
+
&& error.observation === "incompatible";
|
|
31
|
+
}
|
|
32
|
+
export function assertJsonValue(value, location, ancestors = new Set()) {
|
|
26
33
|
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
27
34
|
return;
|
|
28
35
|
if (typeof value === "number") {
|
|
@@ -30,9 +37,8 @@ export function assertJsonValue(value, location, ancestors) {
|
|
|
30
37
|
throw new ProjectionGenerationStoreError(`${location} contains a non-finite number`);
|
|
31
38
|
return;
|
|
32
39
|
}
|
|
33
|
-
if (typeof value !== "object")
|
|
40
|
+
if (typeof value !== "object")
|
|
34
41
|
throw new ProjectionGenerationStoreError(`${location} contains a non-JSON value`);
|
|
35
|
-
}
|
|
36
42
|
if (ancestors.has(value))
|
|
37
43
|
throw new ProjectionGenerationStoreError(`${location} contains a cycle`);
|
|
38
44
|
ancestors.add(value);
|
|
@@ -43,9 +49,8 @@ export function assertJsonValue(value, location, ancestors) {
|
|
|
43
49
|
}
|
|
44
50
|
if (!isPlainObject(value))
|
|
45
51
|
throw new ProjectionGenerationStoreError(`${location} must contain only plain JSON objects`);
|
|
46
|
-
for (const [key, item] of Object.entries(value))
|
|
52
|
+
for (const [key, item] of Object.entries(value))
|
|
47
53
|
assertJsonValue(item, `${location}.${key}`, ancestors);
|
|
48
|
-
}
|
|
49
54
|
}
|
|
50
55
|
finally {
|
|
51
56
|
ancestors.delete(value);
|
|
@@ -54,7 +59,7 @@ export function assertJsonValue(value, location, ancestors) {
|
|
|
54
59
|
export function cloneFacts(value, location) {
|
|
55
60
|
if (!isPlainObject(value))
|
|
56
61
|
throw new ProjectionGenerationStoreError(`${location} must be a JSON object`);
|
|
57
|
-
assertJsonValue(value, location
|
|
62
|
+
assertJsonValue(value, location);
|
|
58
63
|
return JSON.parse(JSON.stringify(value));
|
|
59
64
|
}
|
|
60
65
|
export function assertExecutionId(executionId) {
|
|
@@ -247,7 +252,7 @@ export function validateHistory(records) {
|
|
|
247
252
|
export function extractTerminalTimestamp(facts) {
|
|
248
253
|
if (!facts)
|
|
249
254
|
return undefined;
|
|
250
|
-
for (const key of ["completedAt", "failedAt", "dismissedAt", "killedAt"
|
|
255
|
+
for (const key of ["terminalAt", "completedAt", "failedAt", "dismissedAt", "killedAt"]) {
|
|
251
256
|
const value = facts[key];
|
|
252
257
|
if (typeof value === "string" && Number.isFinite(Date.parse(value)))
|
|
253
258
|
return value;
|
|
@@ -52,7 +52,7 @@ function readContinuationFromDatabase(database, limit = 10) {
|
|
|
52
52
|
/** One heart snapshot owns generation selection and all continuation facts. */
|
|
53
53
|
export function readProjectionGenerationContinuationSnapshot(projectionDirectory, limit = 10) {
|
|
54
54
|
return withGenerationReadSnapshot(projectionDirectory, (database) => {
|
|
55
|
-
const { records, current } = readValidatedHistory(database);
|
|
55
|
+
const { records, current } = readValidatedHistory(database, true);
|
|
56
56
|
const continuation = readContinuationFromDatabase(database, limit);
|
|
57
57
|
return {
|
|
58
58
|
records,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as net from "node:net";
|
|
4
|
+
const CONNECT_TIMEOUT_MS = 50;
|
|
5
|
+
const POSIX_PREFIX = "/tmp/keiyaku-doorbell-";
|
|
6
|
+
const WINDOWS_PREFIX = "\\\\.\\pipe\\keiyaku-doorbell-";
|
|
7
|
+
function endpointDigest(identity, executionId) {
|
|
8
|
+
return createHash("sha256")
|
|
9
|
+
.update(JSON.stringify(identity))
|
|
10
|
+
.update("\0")
|
|
11
|
+
.update(executionId)
|
|
12
|
+
.digest("hex");
|
|
13
|
+
}
|
|
14
|
+
/** A bounded, deterministic endpoint with no projection-directory pathname. */
|
|
15
|
+
export function projectionGenerationDoorbellAddress(identity, executionId) {
|
|
16
|
+
const digest = endpointDigest(identity, executionId);
|
|
17
|
+
return process.platform === "win32"
|
|
18
|
+
? `${WINDOWS_PREFIX}${digest}`
|
|
19
|
+
: `${POSIX_PREFIX}${digest}.sock`;
|
|
20
|
+
}
|
|
21
|
+
function removeStalePosixSocket(address) {
|
|
22
|
+
try {
|
|
23
|
+
const stat = fs.lstatSync(address);
|
|
24
|
+
if (!stat.isSocket())
|
|
25
|
+
throw new Error(`doorbell endpoint is not a socket: ${address}`);
|
|
26
|
+
fs.unlinkSync(address);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === "ENOENT")
|
|
30
|
+
return;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function listen(server, address) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const onError = (error) => {
|
|
37
|
+
server.off("listening", onListening);
|
|
38
|
+
reject(error);
|
|
39
|
+
};
|
|
40
|
+
const onListening = () => {
|
|
41
|
+
server.off("error", onError);
|
|
42
|
+
resolve();
|
|
43
|
+
};
|
|
44
|
+
server.once("error", onError);
|
|
45
|
+
server.once("listening", onListening);
|
|
46
|
+
server.listen(address);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export async function openProjectionGenerationDoorbell(input) {
|
|
50
|
+
const address = projectionGenerationDoorbellAddress(input.identity, input.executionId);
|
|
51
|
+
if (process.platform !== "win32")
|
|
52
|
+
removeStalePosixSocket(address);
|
|
53
|
+
const server = net.createServer((socket) => {
|
|
54
|
+
socket.destroy();
|
|
55
|
+
input.onRing();
|
|
56
|
+
});
|
|
57
|
+
await listen(server, address);
|
|
58
|
+
return {
|
|
59
|
+
address,
|
|
60
|
+
close: async () => {
|
|
61
|
+
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
|
|
62
|
+
if (process.platform !== "win32") {
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(address);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== "ENOENT")
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Best-effort wake only. Connection result is deliberately discarded: heart
|
|
76
|
+
* durability and leash evidence remain the only product/liveness authority.
|
|
77
|
+
*/
|
|
78
|
+
export function ringProjectionGenerationDoorbell(identity, executionId) {
|
|
79
|
+
const socket = net.createConnection(projectionGenerationDoorbellAddress(identity, executionId));
|
|
80
|
+
const timeout = setTimeout(() => socket.destroy(), CONNECT_TIMEOUT_MS);
|
|
81
|
+
timeout.unref?.();
|
|
82
|
+
const finish = () => {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
socket.destroy();
|
|
85
|
+
};
|
|
86
|
+
socket.once("connect", finish);
|
|
87
|
+
socket.once("error", finish);
|
|
88
|
+
}
|
|
@@ -20,6 +20,27 @@ function requireString(value, field) {
|
|
|
20
20
|
function optionalString(value, field) {
|
|
21
21
|
return value === undefined ? undefined : requireString(value, field);
|
|
22
22
|
}
|
|
23
|
+
function optionalCarrier(value) {
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (!isObject(value) || value.path !== ".keiyaku/KEIYAKU.md" || !isObject(value.identity)) {
|
|
27
|
+
throw new Error("generation launch carrier must be a verified carrier coordinate");
|
|
28
|
+
}
|
|
29
|
+
const { identity } = value;
|
|
30
|
+
if (typeof identity.contractId !== "string" || identity.contractId.trim() === ""
|
|
31
|
+
|| typeof identity.contractHead !== "string" || identity.contractHead.trim() === ""
|
|
32
|
+
|| typeof identity.bodyDigest !== "string" || identity.bodyDigest.trim() === "") {
|
|
33
|
+
throw new Error("generation launch carrier identity must contain non-blank contractId, contractHead, and bodyDigest");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
path: ".keiyaku/KEIYAKU.md",
|
|
37
|
+
identity: {
|
|
38
|
+
contractId: identity.contractId,
|
|
39
|
+
contractHead: identity.contractHead,
|
|
40
|
+
bodyDigest: identity.bodyDigest,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
23
44
|
function requirePositiveSafeInteger(value, field) {
|
|
24
45
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
25
46
|
throw new Error(`generation launch ${field} must be a positive safe integer`);
|
|
@@ -54,6 +75,7 @@ function parseLaunchInputs(launch) {
|
|
|
54
75
|
const context = optionalString(raw.context, "context");
|
|
55
76
|
const resumePath = optionalString(raw.resumePath, "resumePath");
|
|
56
77
|
const sourceArtifactId = optionalString(raw.sourceArtifactId, "sourceArtifactId");
|
|
78
|
+
const carrier = optionalCarrier(raw.carrier);
|
|
57
79
|
return {
|
|
58
80
|
prompt: requireString(raw.prompt, "prompt"),
|
|
59
81
|
cwd: requireString(raw.cwd, "cwd"),
|
|
@@ -69,6 +91,7 @@ function parseLaunchInputs(launch) {
|
|
|
69
91
|
...(raw.session ? { session: raw.session } : {}),
|
|
70
92
|
...(resumePath ? { resumePath } : {}),
|
|
71
93
|
...(sourceArtifactId ? { sourceArtifactId } : {}),
|
|
94
|
+
...(carrier ? { carrier } : {}),
|
|
72
95
|
};
|
|
73
96
|
}
|
|
74
97
|
function persistenceError(error) {
|
|
@@ -161,6 +184,7 @@ export function prepareProjectionGenerationExecution(projectionDirectory, launch
|
|
|
161
184
|
idleTimeoutMs: inputs.idleTimeoutMs,
|
|
162
185
|
executionTimeoutMs: inputs.executionTimeoutMs,
|
|
163
186
|
initialTurn: maximumValidEnvelopeTurn(projectionDirectory),
|
|
187
|
+
carrier: inputs.carrier,
|
|
164
188
|
}), providerLoaderPorts);
|
|
165
189
|
const observerController = createProjectionExecutionObserver({
|
|
166
190
|
projectionDirectory,
|
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { ProjectionStateError } from "../../atomic-publish.js";
|
|
4
4
|
import { createUlid } from "../../ids.js";
|
|
5
5
|
const EXECUTION_ID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
6
|
+
export const PROJECTION_RUNNER_LEASE_FILE = "leash";
|
|
6
7
|
export function createProjectionExecutionId(nowMs = Date.now()) {
|
|
7
8
|
return createUlid(nowMs, [...randomBytes(10)]);
|
|
8
9
|
}
|
|
@@ -15,3 +16,6 @@ export function executionStdioLogPath(projectionDirectory, executionId) {
|
|
|
15
16
|
assertProjectionExecutionId(executionId);
|
|
16
17
|
return path.join(projectionDirectory, `${executionId}.stdio.log`);
|
|
17
18
|
}
|
|
19
|
+
export function projectionRunnerLeasePath(projectionDirectory) {
|
|
20
|
+
return path.join(projectionDirectory, PROJECTION_RUNNER_LEASE_FILE);
|
|
21
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { PROJECTION_ADOPTION_TIMEOUT_DEFAULT_MS } from "../../../config/settings/knobs.js";
|
|
2
3
|
import { openProjectionGenerationStore } from "./store.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { prepareProjectionGenerationRunner, } from "./projection-generation-process.js";
|
|
5
|
+
import { acquireRunnerLease, observeRunnerLease, } from "../../run-control/runner-lease.js";
|
|
6
|
+
import { openDetachedLeaseBootstrap } from "../../run-control/detached-lease-bootstrap.js";
|
|
7
|
+
import { projectionRunnerLeasePath } from "./projection-generation-identity.js";
|
|
8
|
+
const PROJECTION_ADOPTION_POLL_MS = 20;
|
|
7
9
|
function errorDetail(error) {
|
|
8
10
|
return error instanceof Error ? error.message : String(error);
|
|
9
11
|
}
|
|
@@ -21,86 +23,39 @@ function recordDeadStartup(projectionDirectory, executionId, detail, stage, term
|
|
|
21
23
|
store.close();
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
const onError = (error) => {
|
|
42
|
-
failure = error;
|
|
43
|
-
for (const waiter of waiters.values())
|
|
44
|
-
waiter.reject(error);
|
|
45
|
-
waiters.clear();
|
|
46
|
-
};
|
|
47
|
-
const onExit = () => onError(new Error("runner exited during bootstrap"));
|
|
48
|
-
child.on("message", onMessage);
|
|
49
|
-
child.on("error", onError);
|
|
50
|
-
child.on("exit", onExit);
|
|
51
|
-
return {
|
|
52
|
-
waitFor(kind) {
|
|
53
|
-
if (seen.has(kind))
|
|
54
|
-
return Promise.resolve();
|
|
55
|
-
if (failure)
|
|
56
|
-
return Promise.reject(failure);
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const timer = setTimeout(() => {
|
|
59
|
-
waiters.delete(kind);
|
|
60
|
-
reject(failure ?? new Error(`runner did not report ${kind} before startup timeout`));
|
|
61
|
-
}, PROJECTION_ADOPTION_TIMEOUT_MS);
|
|
62
|
-
waiters.set(kind, {
|
|
63
|
-
resolve: () => {
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
waiters.delete(kind);
|
|
66
|
-
resolve();
|
|
67
|
-
},
|
|
68
|
-
reject: (error) => {
|
|
69
|
-
clearTimeout(timer);
|
|
70
|
-
waiters.delete(kind);
|
|
71
|
-
reject(error);
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
close() {
|
|
77
|
-
child.off("message", onMessage);
|
|
78
|
-
child.off("error", onError);
|
|
79
|
-
child.off("exit", onExit);
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function sendBootstrapCommand(child, command) {
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
if (!child.send || !child.connected) {
|
|
86
|
-
reject(new Error("runner bootstrap channel is unavailable"));
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
child.send(command, (error) => error ? reject(error) : resolve());
|
|
90
|
-
});
|
|
26
|
+
const ADOPTION_TIMEOUT_DETAIL = "runner did not adopt before projection adoption timeout";
|
|
27
|
+
async function failAdoptionDeadline(projectionDirectory, executionId, child, now, terminateChild, detail = ADOPTION_TIMEOUT_DETAIL) {
|
|
28
|
+
try {
|
|
29
|
+
await terminateChild(child);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return { status: "failed", cause: "adoption", detail: `${detail}; runner termination could not be verified: ${errorDetail(error)}` };
|
|
33
|
+
}
|
|
34
|
+
const lease = observeRunnerLease(projectionRunnerLeasePath(projectionDirectory));
|
|
35
|
+
if (lease.state !== "released")
|
|
36
|
+
return {
|
|
37
|
+
status: "failed",
|
|
38
|
+
cause: "adoption",
|
|
39
|
+
detail: `${detail}; runner lock ${lease.state === "held" ? "remained held" : `could not be verified: ${lease.detail}`}`,
|
|
40
|
+
};
|
|
41
|
+
recordDeadStartup(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString());
|
|
42
|
+
return { status: "failed", cause: "adoption", detail };
|
|
91
43
|
}
|
|
92
44
|
/** Spawn one committed launch and return only after its exact adoption row. */
|
|
93
45
|
export async function startProjectionGeneration(projectionDirectory, executionId, dependencies = {}) {
|
|
94
46
|
const now = dependencies.now ?? Date.now;
|
|
95
47
|
const sleep = dependencies.sleep ?? ((milliseconds) => delay(milliseconds));
|
|
96
|
-
const
|
|
48
|
+
const adoptionTimeoutMs = dependencies.adoptionTimeoutMs ?? PROJECTION_ADOPTION_TIMEOUT_DEFAULT_MS;
|
|
49
|
+
// Bootstrap IPC and durable adoption are one launch operation, not retries
|
|
50
|
+
// with independent startup allowances.
|
|
51
|
+
const deadline = now() + adoptionTimeoutMs;
|
|
97
52
|
let launchOwnership;
|
|
98
53
|
try {
|
|
99
54
|
launchOwnership = dependencies.launchOwnership
|
|
100
|
-
??
|
|
55
|
+
?? acquireRunnerLease(projectionRunnerLeasePath(projectionDirectory));
|
|
101
56
|
}
|
|
102
57
|
catch (error) {
|
|
103
|
-
if (
|
|
58
|
+
if (observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state === "held") {
|
|
104
59
|
return { status: "pending" };
|
|
105
60
|
}
|
|
106
61
|
return {
|
|
@@ -109,11 +64,9 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
109
64
|
detail: `runner construction failed: ${errorDetail(error)}`,
|
|
110
65
|
};
|
|
111
66
|
}
|
|
112
|
-
let
|
|
113
|
-
let childPromoted = false;
|
|
114
|
-
let child;
|
|
67
|
+
let prepared;
|
|
115
68
|
try {
|
|
116
|
-
|
|
69
|
+
prepared = prepareProjectionGenerationRunner(projectionDirectory, executionId, dependencies);
|
|
117
70
|
}
|
|
118
71
|
catch (error) {
|
|
119
72
|
const detail = `runner spawn failed: ${errorDetail(error)}`;
|
|
@@ -121,60 +74,48 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
121
74
|
launchOwnership.close();
|
|
122
75
|
return { status: "failed", cause: "spawn", detail };
|
|
123
76
|
}
|
|
124
|
-
const
|
|
77
|
+
const opened = await openDetachedLeaseBootstrap({
|
|
78
|
+
coordinate: executionId,
|
|
79
|
+
leasePath: projectionRunnerLeasePath(projectionDirectory),
|
|
80
|
+
parentLease: launchOwnership,
|
|
81
|
+
launch: prepared.launch,
|
|
82
|
+
deadline,
|
|
83
|
+
now,
|
|
84
|
+
spawn: dependencies.spawn,
|
|
85
|
+
terminate: dependencies.terminateChild,
|
|
86
|
+
});
|
|
87
|
+
prepared.close();
|
|
88
|
+
if (opened.status === "failed") {
|
|
89
|
+
const detail = opened.stage === "spawn" ? `runner spawn failed: ${opened.detail}` : `runner construction failed: ${opened.detail}`;
|
|
90
|
+
recordDeadStartup(projectionDirectory, executionId, detail, opened.stage === "spawn" ? "spawn" : "adoption", new Date(now()).toISOString());
|
|
91
|
+
return { status: "failed", cause: opened.stage === "spawn" ? "spawn" : "adoption", detail };
|
|
92
|
+
}
|
|
93
|
+
let committed = true;
|
|
94
|
+
let commitFailure;
|
|
125
95
|
try {
|
|
126
|
-
|
|
127
|
-
if (dependencies.commitLaunch && !dependencies.commitLaunch()) {
|
|
128
|
-
await sendBootstrapCommand(child, { kind: "launch-rejected", executionId });
|
|
129
|
-
if (launcherAcquiredOwnership)
|
|
130
|
-
launchOwnership.close();
|
|
131
|
-
return { status: "failed", cause: "adoption", detail: "generation launch transaction was rejected" };
|
|
132
|
-
}
|
|
133
|
-
launchCommitted = true;
|
|
134
|
-
launchOwnership.releaseReservationForTransfer();
|
|
135
|
-
await sendBootstrapCommand(child, { kind: "promote-leash", executionId });
|
|
136
|
-
await bootstrap.waitFor("leash-acquired");
|
|
137
|
-
childPromoted = true;
|
|
138
|
-
await sendBootstrapCommand(child, { kind: "launch-committed", executionId });
|
|
139
|
-
try {
|
|
140
|
-
launchOwnership.close();
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
// The promoted child owns lifecycle truth; parent guard cleanup is diagnostic only.
|
|
144
|
-
}
|
|
96
|
+
committed = dependencies.commitLaunch?.() ?? true;
|
|
145
97
|
}
|
|
146
98
|
catch (error) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
launchOwnership.close();
|
|
151
|
-
}
|
|
152
|
-
catch { }
|
|
153
|
-
return { status: "pending", child };
|
|
154
|
-
}
|
|
155
|
-
if (!launchCommitted) {
|
|
156
|
-
launchOwnership.close();
|
|
157
|
-
return { status: "failed", cause: "adoption", detail: `runner construction failed: ${errorDetail(error)}` };
|
|
158
|
-
}
|
|
159
|
-
try {
|
|
160
|
-
launchOwnership.reclaimReservation();
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
launchOwnership.close();
|
|
164
|
-
return { status: "pending", child };
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
recordDeadStartup(projectionDirectory, executionId, `runner construction failed: ${errorDetail(error)}`, "adoption", new Date(now()).toISOString());
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
170
|
-
launchOwnership.close();
|
|
171
|
-
}
|
|
172
|
-
return { status: "failed", cause: "adoption", detail: `runner construction failed: ${errorDetail(error)}` };
|
|
99
|
+
committed = false;
|
|
100
|
+
commitFailure = error;
|
|
173
101
|
}
|
|
174
|
-
|
|
175
|
-
bootstrap.
|
|
102
|
+
if (!committed) {
|
|
103
|
+
const rejected = await opened.bootstrap.reject();
|
|
104
|
+
return {
|
|
105
|
+
status: "failed",
|
|
106
|
+
cause: "adoption",
|
|
107
|
+
detail: rejected.status === "failed"
|
|
108
|
+
? rejected.detail
|
|
109
|
+
: commitFailure ? `generation launch transaction failed: ${errorDetail(commitFailure)}` : "generation launch transaction was rejected",
|
|
110
|
+
};
|
|
176
111
|
}
|
|
177
|
-
const
|
|
112
|
+
const authorized = await opened.bootstrap.authorize();
|
|
113
|
+
if (authorized.status === "failed") {
|
|
114
|
+
const detail = now() >= deadline ? ADOPTION_TIMEOUT_DETAIL : `runner construction failed: ${authorized.detail}`;
|
|
115
|
+
recordDeadStartup(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString());
|
|
116
|
+
return { status: "failed", cause: "adoption", detail };
|
|
117
|
+
}
|
|
118
|
+
const child = authorized.child;
|
|
178
119
|
for (;;) {
|
|
179
120
|
const observedAt = now();
|
|
180
121
|
const store = openProjectionGenerationStore(projectionDirectory);
|
|
@@ -202,19 +143,12 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
202
143
|
finally {
|
|
203
144
|
store.close();
|
|
204
145
|
}
|
|
146
|
+
if (observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state === "held") {
|
|
147
|
+
return { status: "adopted", child };
|
|
148
|
+
}
|
|
205
149
|
if (observedAt >= deadline)
|
|
206
150
|
break;
|
|
207
151
|
await sleep(Math.max(0, Math.min(PROJECTION_ADOPTION_POLL_MS, deadline - observedAt)));
|
|
208
152
|
}
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const current = store.readCurrentGeneration();
|
|
212
|
-
if (current?.launch.executionId === executionId && current.adoption) {
|
|
213
|
-
return { status: "adopted", child };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
store.close();
|
|
218
|
-
}
|
|
219
|
-
return { status: "pending", child };
|
|
153
|
+
return failAdoptionDeadline(projectionDirectory, executionId, child, now, dependencies.terminateChild ?? (await import("../../run-control/process-tree.js")).terminateProcessGroup);
|
|
220
154
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { RUN_CONTROL_BOOTSTRAP_ENV } from "../../run-control/index.js";
|
|
5
5
|
import { executionStdioLogPath } from "./projection-generation-identity.js";
|
|
6
6
|
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
7
7
|
// Lives at src/core/projection/generation/ — four levels up is the package root.
|
|
@@ -17,9 +17,7 @@ export function projectionGenerationRunnerArgv(projectionDirectory, executionId)
|
|
|
17
17
|
executionId,
|
|
18
18
|
];
|
|
19
19
|
}
|
|
20
|
-
export
|
|
21
|
-
export function spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
|
|
22
|
-
const spawn = dependencies.spawn ?? nodeSpawn;
|
|
20
|
+
export function prepareProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
|
|
23
21
|
const openSync = dependencies.openSync ?? fs.openSync;
|
|
24
22
|
const closeSync = dependencies.closeSync ?? fs.closeSync;
|
|
25
23
|
const absoluteDirectory = path.resolve(projectionDirectory);
|
|
@@ -29,18 +27,21 @@ export function spawnProjectionGenerationRunner(projectionDirectory, executionId
|
|
|
29
27
|
const logPath = executionStdioLogPath(absoluteDirectory, executionId);
|
|
30
28
|
const logFd = openSync(logPath, "a");
|
|
31
29
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
return {
|
|
31
|
+
launch: {
|
|
32
|
+
command,
|
|
33
|
+
args,
|
|
34
|
+
unref: true,
|
|
35
|
+
options: {
|
|
36
|
+
stdio: ["ignore", logFd, logFd, "ipc"],
|
|
37
|
+
env: { ...process.env, [RUN_CONTROL_BOOTSTRAP_ENV]: "1" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
close: () => closeSync(logFd),
|
|
41
|
+
};
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
catch (error) {
|
|
44
44
|
closeSync(logFd);
|
|
45
|
+
throw error;
|
|
45
46
|
}
|
|
46
47
|
}
|