@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
|
@@ -216,15 +216,14 @@ function validateAfterPrerequisites(ledger, contracts, isolatedContracts, diseas
|
|
|
216
216
|
const bindTransaction = containingTransactions.get(bindObjectId);
|
|
217
217
|
const ready = state.bind.data.after.every((dependency) => {
|
|
218
218
|
const prerequisite = contracts.get(dependency);
|
|
219
|
-
const
|
|
219
|
+
const prerequisiteBindTransaction = prerequisite === undefined
|
|
220
220
|
? undefined
|
|
221
|
-
: containingTransactions.get(prerequisite
|
|
221
|
+
: containingTransactions.get(prerequisite.entryObjectIds[0]);
|
|
222
222
|
return prerequisite !== undefined &&
|
|
223
223
|
valid.has(dependency) &&
|
|
224
|
-
|
|
225
|
-
terminalTransaction !== undefined &&
|
|
224
|
+
prerequisiteBindTransaction !== undefined &&
|
|
226
225
|
bindTransaction !== undefined &&
|
|
227
|
-
isTypedLedgerAncestor(ledger,
|
|
226
|
+
isTypedLedgerAncestor(ledger, prerequisiteBindTransaction, bindTransaction);
|
|
228
227
|
});
|
|
229
228
|
if (ready) {
|
|
230
229
|
valid.add(contract);
|
|
@@ -240,7 +239,7 @@ function validateAfterPrerequisites(ledger, contracts, isolatedContracts, diseas
|
|
|
240
239
|
objectId: bindObjectId,
|
|
241
240
|
code: "causal-prerequisite",
|
|
242
241
|
contract,
|
|
243
|
-
detail: `after
|
|
242
|
+
detail: `after prerequisite binds were not in typed ancestry: ${state.bind.data.after.join(", ")}`,
|
|
244
243
|
});
|
|
245
244
|
contracts.delete(contract);
|
|
246
245
|
isolatedContracts.add(contract);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function repositoryLedgerHead(value) {
|
|
2
|
+
return value;
|
|
3
|
+
}
|
|
4
|
+
export function lifecycleTransactionId(value) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
export function contractHead(value) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export function carrierDigest(value) {
|
|
11
|
+
if (!/^[0-9a-f]{64}$/.test(value)) {
|
|
12
|
+
throw new Error("carrier digest must be a lowercase SHA-256 hex value");
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export function carrierInputIdentity(input) {
|
|
17
|
+
return {
|
|
18
|
+
surface: "carrier",
|
|
19
|
+
contractId: input.contractId,
|
|
20
|
+
contractHead: input.contractHead,
|
|
21
|
+
bodyDigest: input.bodyDigest,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function carrierRetirementInputIdentity(input) {
|
|
25
|
+
return { surface: "carrier-retirement", ...input };
|
|
26
|
+
}
|
|
27
|
+
export function worktreeInputIdentity(input) {
|
|
28
|
+
return {
|
|
29
|
+
surface: "worktree",
|
|
30
|
+
contractId: input.contractId,
|
|
31
|
+
contractHead: input.contractHead,
|
|
32
|
+
deliveryHead: input.deliveryHead,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function contractViewInputIdentity(input) {
|
|
36
|
+
return {
|
|
37
|
+
surface: "contract-view",
|
|
38
|
+
contractId: input.contractId,
|
|
39
|
+
contractHead: input.contractHead,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function narrowIgnoreInputIdentity(input) {
|
|
43
|
+
return { surface: "narrow-ignore", ...input };
|
|
44
|
+
}
|
|
45
|
+
export function renewDeliveryInputIdentity(input) {
|
|
46
|
+
return { surface: "renew-delivery", ...input };
|
|
47
|
+
}
|
|
48
|
+
export function taskBoardInputIdentity(input) {
|
|
49
|
+
return { surface: "task-board", ...input };
|
|
50
|
+
}
|
|
51
|
+
export function deliveryRefInputIdentity(input) {
|
|
52
|
+
return { surface: "delivery-ref", ...input };
|
|
53
|
+
}
|
|
54
|
+
export function targetWorktreeInputIdentity(input) {
|
|
55
|
+
return { surface: "target-worktree", ...input };
|
|
56
|
+
}
|
|
57
|
+
/** Structural equality keeps surface comparisons independent of process-local receipts. */
|
|
58
|
+
export function sameInputIdentity(left, right) {
|
|
59
|
+
return left !== null && JSON.stringify(left) === JSON.stringify(right);
|
|
60
|
+
}
|
|
@@ -295,12 +295,12 @@ export function activeWorkspaceHereContracts(read) {
|
|
|
295
295
|
}
|
|
296
296
|
/** Runtime ignore projection for one worktree from a single typed repository fold. */
|
|
297
297
|
export function inventoryIgnorePatternsForWorktree(read, targetLifecycle) {
|
|
298
|
-
if (read.observedLedgerHead !== targetLifecycle.observedLedgerHead) {
|
|
299
|
-
throw new Error("narrow-ignore inventory requires one repository-ledger observation");
|
|
300
|
-
}
|
|
301
298
|
const acceptedTarget = read.contracts.find(({ contract }) => contract === targetLifecycle.contract);
|
|
302
299
|
if (!acceptedTarget)
|
|
303
300
|
throw new Error(`contract ${targetLifecycle.contract} is absent from narrow-ignore inventory`);
|
|
301
|
+
if (acceptedTarget.contractHead !== targetLifecycle.contractHead) {
|
|
302
|
+
throw new Error("narrow-ignore inventory requires the current contract semantic head");
|
|
303
|
+
}
|
|
304
304
|
const target = foldedWorktreeCoordinate(acceptedTarget);
|
|
305
305
|
const required = target.workspace === "here"
|
|
306
306
|
? activeWorkspaceHereContracts(read).length > 0
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { openTaskAdmissionEvidenceReader, readTaskAdmissionEvidenceAtCommit, } from "../task/settlement-git.js";
|
|
2
2
|
import { repositoryDeliveryRef, } from "./codec.js";
|
|
3
|
+
import { bindEffectiveScope } from "./fold.js";
|
|
3
4
|
import { observeRepositoryLedgerHead, readAcceptedRepositoryFold } from "./accepted-fold-read.js";
|
|
4
|
-
import { advanceRepositoryCurrentState,
|
|
5
|
+
import { advanceRepositoryCurrentState, publishRepositoryCurrentState, readPinnedCurrentContract, readPinnedRepositoryLifecycle, readRepositoryCurrentStateNodeIds, readRepositoryCurrentStatePin, readRepositoryCurrentStatePredecessor } from "./current-state-store.js";
|
|
5
6
|
import { IncrementalLedgerValidationError, readIncrementalRepositorySegment } from "./incremental-read.js";
|
|
6
7
|
import { RepositoryObjectError } from "./git-object-store.js";
|
|
8
|
+
import { contractHead, repositoryLedgerHead } from "./identity.js";
|
|
7
9
|
/** Public lifecycle state derived only from the accepted repository-ledger fold. */
|
|
8
10
|
export function currentLifecycleState(model) {
|
|
9
11
|
switch (model.phase) {
|
|
@@ -22,6 +24,50 @@ export function currentLifecycleState(model) {
|
|
|
22
24
|
return { state: "forfeited" };
|
|
23
25
|
}
|
|
24
26
|
}
|
|
27
|
+
/** Purely advance an admitted repository snapshot with its just-committed bind. */
|
|
28
|
+
export function advanceRepositorySnapshotWithBind(input) {
|
|
29
|
+
const { repository, ledgerHead, entryObjectId, entry } = input;
|
|
30
|
+
const bootstrap = entry.data.workspace === "worktree" ? entry.data.bootstrap : undefined;
|
|
31
|
+
const model = deepFreeze({
|
|
32
|
+
contractHead: contractHead(entryObjectId),
|
|
33
|
+
contract: entry.contract,
|
|
34
|
+
phase: "active",
|
|
35
|
+
intent: { arc: 0, openedBy: entry.actor, title: entry.data.title, objective: entry.data.objective, brief: null },
|
|
36
|
+
receiptSegments: [],
|
|
37
|
+
seal: null,
|
|
38
|
+
bootstrapReplay: bootstrap
|
|
39
|
+
? { state: "pending", plan: { command: [...bootstrap.command], timeoutMs: bootstrap.timeoutMs } }
|
|
40
|
+
: { state: "not-planned" },
|
|
41
|
+
petition: { state: "none" },
|
|
42
|
+
verdict: null,
|
|
43
|
+
pins: { base: entry.data.base, delivery: entry.data.base, target: { ref: entry.data.target, approvedHead: null } },
|
|
44
|
+
effectiveScope: bindEffectiveScope(entry),
|
|
45
|
+
causal: { after: [...(entry.data.after ?? [])], needs: null },
|
|
46
|
+
resources: {
|
|
47
|
+
deliveryRef: repositoryDeliveryRef(entry.contract),
|
|
48
|
+
worktree: entry.data.workspace === "worktree" ? { place: entry.data.place, state: "held" } : null,
|
|
49
|
+
tasks: [...(entry.data.taskIds ?? [])].map((taskId) => ({ taskId, state: "held" })),
|
|
50
|
+
},
|
|
51
|
+
history: [{ objectId: entryObjectId, entry: structuredClone(entry) }],
|
|
52
|
+
});
|
|
53
|
+
return deepFreeze({
|
|
54
|
+
...structuredClone(repository),
|
|
55
|
+
observedLedgerHead: ledgerHead,
|
|
56
|
+
contracts: [...repository.contracts, model].sort((left, right) => left.contract.localeCompare(right.contract)),
|
|
57
|
+
topology: {
|
|
58
|
+
head: ledgerHead,
|
|
59
|
+
nodes: [...repository.topology.nodes, {
|
|
60
|
+
objectId: ledgerHead,
|
|
61
|
+
type: "transaction",
|
|
62
|
+
ledgerParents: repository.observedLedgerHead ? [repository.observedLedgerHead] : [],
|
|
63
|
+
verb: "bind",
|
|
64
|
+
contract: entry.contract,
|
|
65
|
+
entryObjectIds: [entryObjectId],
|
|
66
|
+
finalEntryObjectId: entryObjectId,
|
|
67
|
+
}],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
25
71
|
function deepFreeze(value) {
|
|
26
72
|
if (value === null || typeof value !== "object" || Object.isFrozen(value))
|
|
27
73
|
return value;
|
|
@@ -199,14 +245,14 @@ function bootstrapReplay(state) {
|
|
|
199
245
|
}
|
|
200
246
|
throw new Error(`failed bootstrap for ${state.contract} has no folded replay disposition`);
|
|
201
247
|
}
|
|
202
|
-
function lifecycleModel(
|
|
248
|
+
function lifecycleModel(_observedLedgerHead, state, nodes, taskAdmissionEvidence) {
|
|
203
249
|
const history = acceptedHistory(state, nodes);
|
|
204
250
|
const verdict = findLastRow(history, "verdict");
|
|
205
251
|
const released = state.terminalObjectId !== null;
|
|
206
252
|
const taskReleased = state.terminal?.kind === "forfeit";
|
|
207
253
|
const selectedTasks = [...(state.bind.data.taskIds ?? [])];
|
|
208
254
|
return deepFreeze({
|
|
209
|
-
|
|
255
|
+
contractHead: contractHead(state.entryObjectIds.at(-1)),
|
|
210
256
|
contract: state.contract,
|
|
211
257
|
phase: state.phase,
|
|
212
258
|
intent: currentIntent(state, history),
|
|
@@ -359,22 +405,25 @@ async function recoverRepositoryLifecycle(cwd, options = {}) {
|
|
|
359
405
|
});
|
|
360
406
|
}
|
|
361
407
|
export async function readRepositoryLifecycle(cwd, options = {}) {
|
|
362
|
-
const observe = options.
|
|
408
|
+
const observe = options.observedHead !== undefined
|
|
409
|
+
? async () => options.observedHead
|
|
410
|
+
: options.observeHead ?? observeRepositoryLedgerHead;
|
|
363
411
|
const observedHead = await observe(cwd);
|
|
364
412
|
const injected = options.observeHead !== undefined || options.readTaskAdmissionEvidence !== undefined;
|
|
365
413
|
if (!injected) {
|
|
366
414
|
const pinned = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
|
|
367
415
|
if (pinned)
|
|
368
416
|
return deepFreeze(pinned);
|
|
369
|
-
const
|
|
417
|
+
const candidatePriorHead = observedHead === null ? null : await readRepositoryCurrentStatePredecessor(cwd, observedHead, options.signal);
|
|
418
|
+
const priorHead = candidatePriorHead === null ? null : await readRepositoryCurrentStatePin(cwd, candidatePriorHead, options.signal);
|
|
370
419
|
if (priorHead && observedHead && priorHead !== observedHead) {
|
|
371
420
|
try {
|
|
372
421
|
const acceptedObjectIds = await readRepositoryCurrentStateNodeIds(cwd, priorHead, options.signal);
|
|
373
422
|
const segment = await readIncrementalRepositorySegment(cwd, priorHead, observedHead, options.signal, acceptedObjectIds ?? undefined);
|
|
374
423
|
if (segment && await advanceRepositoryCurrentState(cwd, priorHead, observedHead, segment, {
|
|
375
424
|
signal: options.signal,
|
|
376
|
-
confirmHead: () => observeRepositoryLedgerHead(cwd),
|
|
377
|
-
materialize: (state, history) => materializeIncrementalContract(cwd, observedHead, state, history),
|
|
425
|
+
confirmHead: options.observedHead !== undefined ? async () => observedHead : () => observeRepositoryLedgerHead(cwd),
|
|
426
|
+
materialize: (state, history) => materializeIncrementalContract(cwd, repositoryLedgerHead(observedHead), state, history),
|
|
378
427
|
})) {
|
|
379
428
|
const advanced = await readPinnedRepositoryLifecycle(cwd, observedHead, options.signal, { includeTopology: options.includeTopology });
|
|
380
429
|
if (advanced)
|
|
@@ -391,7 +440,6 @@ export async function readRepositoryLifecycle(cwd, options = {}) {
|
|
|
391
440
|
|| (error instanceof RepositoryObjectError && error.reason !== "git-failure");
|
|
392
441
|
if (!untrustedDelta)
|
|
393
442
|
throw error;
|
|
394
|
-
await discardRepositoryCurrentState(cwd, options.signal);
|
|
395
443
|
}
|
|
396
444
|
}
|
|
397
445
|
}
|
|
@@ -404,7 +452,7 @@ export async function readRepositoryLifecycle(cwd, options = {}) {
|
|
|
404
452
|
if (!injected) {
|
|
405
453
|
const published = await publishRepositoryCurrentState(cwd, recovered, {
|
|
406
454
|
signal: options.signal,
|
|
407
|
-
confirmHead: () => observeRepositoryLedgerHead(cwd),
|
|
455
|
+
confirmHead: options.observedHead !== undefined ? async () => observedHead : () => observeRepositoryLedgerHead(cwd),
|
|
408
456
|
fold: recoveredFold,
|
|
409
457
|
});
|
|
410
458
|
if (!published)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runGitProcess } from "../../git/core.js";
|
|
2
2
|
import { commitRefTransaction, } from "../../git/refs.js";
|
|
3
3
|
import { REPOSITORY_LEDGER_REF, evidenceObjectId, formatLifecycleEntry, formatLifecycleTransaction, repositoryDeliveryRef, } from "./codec.js";
|
|
4
|
-
import { assertHostObjectId, assertReadableCommit, deriveHostGitCoordinates, } from "./git-object-store.js";
|
|
4
|
+
import { assertHostObjectId, assertReadableCommit, assertReadableCommitClosure, deriveHostGitCoordinates, } from "./git-object-store.js";
|
|
5
5
|
import { assertHonestClaimEvidence } from "./claim-evidence.js";
|
|
6
6
|
import { transactionMembershipError } from "./transaction-membership.js";
|
|
7
7
|
function replayFailure(effect, error) {
|
|
@@ -76,26 +76,26 @@ async function assertAuthoritativeCommitTipClosure(cwd, coordinates, operations)
|
|
|
76
76
|
if (operation.kind !== "update" || operation.ref === REPOSITORY_LEDGER_REF || checked.has(operation.newOid))
|
|
77
77
|
continue;
|
|
78
78
|
assertHostObjectId(coordinates, operation.newOid);
|
|
79
|
-
await
|
|
79
|
+
await assertReadableCommitClosure(cwd, operation.newOid);
|
|
80
80
|
checked.add(operation.newOid);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead) {
|
|
84
|
-
const
|
|
83
|
+
export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead, coordinates) {
|
|
84
|
+
const resolvedCoordinates = coordinates ?? await deriveHostGitCoordinates(cwd);
|
|
85
85
|
if (expectedLedgerHead !== null)
|
|
86
|
-
assertHostObjectId(
|
|
86
|
+
assertHostObjectId(resolvedCoordinates, expectedLedgerHead);
|
|
87
87
|
const evidence = evidenceObjectId(entry);
|
|
88
88
|
if (evidence !== null) {
|
|
89
|
-
assertHostObjectId(
|
|
90
|
-
await assertReadableCommit(cwd,
|
|
89
|
+
assertHostObjectId(resolvedCoordinates, evidence);
|
|
90
|
+
await assertReadableCommit(cwd, resolvedCoordinates, evidence);
|
|
91
91
|
}
|
|
92
92
|
const parents = [
|
|
93
93
|
...(expectedLedgerHead === null ? [] : [expectedLedgerHead]),
|
|
94
94
|
...(evidence === null ? [] : [evidence]),
|
|
95
95
|
];
|
|
96
|
-
const args = ["commit-tree",
|
|
96
|
+
const args = ["commit-tree", resolvedCoordinates.emptyTree, ...parents.flatMap((parent) => ["-p", parent])];
|
|
97
97
|
const result = await runGitProcess(cwd, args, {
|
|
98
|
-
input: formatLifecycleEntry(entry,
|
|
98
|
+
input: formatLifecycleEntry(entry, resolvedCoordinates.objectFormat),
|
|
99
99
|
env: {
|
|
100
100
|
GIT_AUTHOR_NAME: "Keiyaku",
|
|
101
101
|
GIT_AUTHOR_EMAIL: "keiyaku@localhost",
|
|
@@ -110,16 +110,17 @@ export async function buildLifecycleEntry(cwd, entry, expectedLedgerHead) {
|
|
|
110
110
|
throw new Error(`cannot build ${entry.kind} ledger object: ${detail}`, { cause: result.error });
|
|
111
111
|
}
|
|
112
112
|
const objectId = result.stdout.trim();
|
|
113
|
-
assertHostObjectId(
|
|
114
|
-
return { entry, objectId, expectedLedgerHead, parents, coordinates };
|
|
113
|
+
assertHostObjectId(resolvedCoordinates, objectId);
|
|
114
|
+
return { entry, objectId, expectedLedgerHead, parents, coordinates: resolvedCoordinates };
|
|
115
115
|
}
|
|
116
|
-
export async function buildLifecycleTail(cwd, entries, expectedLedgerHead) {
|
|
116
|
+
export async function buildLifecycleTail(cwd, entries, expectedLedgerHead, coordinates) {
|
|
117
117
|
if (entries.length === 0)
|
|
118
118
|
throw new Error("a lifecycle tail requires at least one entry");
|
|
119
|
+
const resolvedCoordinates = coordinates ?? await deriveHostGitCoordinates(cwd);
|
|
119
120
|
const built = [];
|
|
120
121
|
let parent = expectedLedgerHead;
|
|
121
122
|
for (const entry of entries) {
|
|
122
|
-
const next = await buildLifecycleEntry(cwd, entry, parent);
|
|
123
|
+
const next = await buildLifecycleEntry(cwd, entry, parent, resolvedCoordinates);
|
|
123
124
|
built.push(next);
|
|
124
125
|
parent = next.objectId;
|
|
125
126
|
}
|
|
@@ -199,7 +200,8 @@ async function buildTransactionTail(input) {
|
|
|
199
200
|
throw new Error(`bind transaction repeats contract ${contract}`);
|
|
200
201
|
if (input.verb !== "bind" && causalParent === null)
|
|
201
202
|
throw new Error(`${input.verb} transaction has no accepted lifecycle predecessor for ${contract}`);
|
|
202
|
-
const
|
|
203
|
+
const coordinates = await deriveHostGitCoordinates(input.cwd);
|
|
204
|
+
const tail = await buildLifecycleTail(input.cwd, input.entries, causalParent, coordinates);
|
|
203
205
|
const transaction = await buildLifecycleTransactionNode({
|
|
204
206
|
cwd: input.cwd,
|
|
205
207
|
verb: input.verb,
|
|
@@ -246,6 +248,7 @@ async function publishLifecycleWrite(cwd, builtTransaction, final, ownedOperatio
|
|
|
246
248
|
completion: replay.status,
|
|
247
249
|
ledgerHead: builtTransaction.objectId,
|
|
248
250
|
entry: final.entry,
|
|
251
|
+
entryObjectId: final.objectId,
|
|
249
252
|
authoritativeOperations,
|
|
250
253
|
authorityProof: transaction.proof,
|
|
251
254
|
authorityObservations: transaction.observations,
|
|
@@ -278,6 +281,9 @@ export async function publishBindTransaction(input) {
|
|
|
278
281
|
{ kind: "update", ref: input.delivery.ref, newOid: input.delivery.head, expectedOldOid: null },
|
|
279
282
|
];
|
|
280
283
|
if (input.taskBoard) {
|
|
284
|
+
if (input.taskBoard.ref !== input.entry.data.target) {
|
|
285
|
+
throw new Error("task-selected bind task-board ref must equal the entry target");
|
|
286
|
+
}
|
|
281
287
|
operations.push({
|
|
282
288
|
kind: "update",
|
|
283
289
|
ref: input.taskBoard.ref,
|
|
@@ -311,11 +317,12 @@ export async function publishAuthoritativeTransaction(cwd, transaction, options
|
|
|
311
317
|
const entry = final.entry;
|
|
312
318
|
const operations = [];
|
|
313
319
|
if (transaction.verb === "renew" && entry.kind === "renew") {
|
|
314
|
-
assertAuthority("delivery" in transaction, "renew transaction must pin its delivery
|
|
320
|
+
assertAuthority("delivery" in transaction && "target" in transaction, "renew transaction must pin its target and delivery refs");
|
|
321
|
+
assertAuthority(transaction.target.expectedOid === entry.data.newBase, "renew target CAS must equal newBase");
|
|
315
322
|
assertAuthority(transaction.delivery.ref === repositoryDeliveryRef(entry.contract), "renew delivery ref does not name its contract");
|
|
316
323
|
assertAuthority(transaction.delivery.expectedOid === entry.data.oldHead, "renew delivery CAS must equal oldHead");
|
|
317
324
|
assertAuthority(entry.data.oldHead !== entry.data.newHead, "renew must replace the delivery head");
|
|
318
|
-
operations.push({ kind: "update", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid, newOid: entry.data.newHead });
|
|
325
|
+
operations.push({ kind: "verify", ref: transaction.target.ref, expectedOldOid: transaction.target.expectedOid }, { kind: "update", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid, newOid: entry.data.newHead });
|
|
319
326
|
}
|
|
320
327
|
else if (transaction.verb === "forfeit") {
|
|
321
328
|
assertAuthority(entry.kind === "forfeit", "forfeit transaction must end in a forfeit entry");
|
|
@@ -331,7 +338,7 @@ export async function publishAuthoritativeTransaction(cwd, transaction, options
|
|
|
331
338
|
assertAuthority(transaction.target.expectedOid === entry.data.targetHead, "claim target CAS must equal targetHead");
|
|
332
339
|
assertAuthority(transaction.delivery.ref === repositoryDeliveryRef(entry.contract), "claim delivery ref does not name its contract");
|
|
333
340
|
assertAuthority(transaction.delivery.expectedOid === entry.data.deliveryHead, "claim delivery CAS must equal deliveryHead");
|
|
334
|
-
await assertHonestClaimEvidence(cwd, entry.data);
|
|
341
|
+
await assertHonestClaimEvidence(cwd, entry.data, { coordinates: built.transaction.coordinates });
|
|
335
342
|
operations.push({ kind: "update", ref: transaction.target.ref, expectedOldOid: transaction.target.expectedOid, newOid: entry.data.mergeCommit }, { kind: "delete", ref: transaction.delivery.ref, expectedOldOid: transaction.delivery.expectedOid });
|
|
336
343
|
}
|
|
337
344
|
const write = await publishLifecycleWrite(cwd, built.transaction, final, operations, options.replayPlan, options.transactionRunner);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function beginConnectionBoundRunnerClose(database, lease) {
|
|
2
|
+
database.exec("BEGIN IMMEDIATE");
|
|
3
|
+
let open = true;
|
|
4
|
+
let leaseOwnership = "retained";
|
|
5
|
+
const rollback = () => {
|
|
6
|
+
open = false;
|
|
7
|
+
database.exec("ROLLBACK");
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
get leaseOwnership() { return leaseOwnership; },
|
|
11
|
+
commit() {
|
|
12
|
+
if (!open)
|
|
13
|
+
throw new Error("connection-bound runner close is not open");
|
|
14
|
+
try {
|
|
15
|
+
lease.close();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
leaseOwnership = "unknown";
|
|
19
|
+
rollback();
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
leaseOwnership = "consumed";
|
|
23
|
+
try {
|
|
24
|
+
database.exec("COMMIT");
|
|
25
|
+
open = false;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
rollback();
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
rollback() {
|
|
33
|
+
if (leaseOwnership !== "retained") {
|
|
34
|
+
throw new Error(`connection-bound runner lease ownership is ${leaseOwnership}`);
|
|
35
|
+
}
|
|
36
|
+
if (!open)
|
|
37
|
+
throw new Error("connection-bound runner close is no longer open");
|
|
38
|
+
rollback();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { launchDetachedProcess } from "./detached-process.js";
|
|
2
|
+
import { terminateProcessGroup } from "./process-tree.js";
|
|
3
|
+
import { acquireRunnerLeaseTransferTarget, observeRunnerLease, } from "./runner-lease.js";
|
|
4
|
+
export const RUN_CONTROL_BOOTSTRAP_ENV = "KEIYAKU_RUN_CONTROL_BOOTSTRAP";
|
|
5
|
+
function detail(error) {
|
|
6
|
+
return error instanceof Error ? error.message : String(error);
|
|
7
|
+
}
|
|
8
|
+
function send(child, message) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
if (!child.send || !child.connected)
|
|
11
|
+
return reject(new Error("bootstrap channel is unavailable"));
|
|
12
|
+
child.send(message, (error) => error ? reject(error) : resolve());
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function waitForMessage(child, coordinate, expected, deadline, now, setTimer, clearTimer) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const cleanup = () => {
|
|
18
|
+
child.off("message", onMessage);
|
|
19
|
+
child.off("error", onError);
|
|
20
|
+
child.off("exit", onExit);
|
|
21
|
+
clearTimer(timer);
|
|
22
|
+
};
|
|
23
|
+
const onMessage = (value) => {
|
|
24
|
+
const message = value;
|
|
25
|
+
if (message.coordinate !== coordinate || message.kind !== expected)
|
|
26
|
+
return;
|
|
27
|
+
cleanup();
|
|
28
|
+
resolve();
|
|
29
|
+
};
|
|
30
|
+
const onError = (error) => { cleanup(); reject(error); };
|
|
31
|
+
const onExit = () => onError(new Error("child exited during bootstrap"));
|
|
32
|
+
const remaining = deadline - now();
|
|
33
|
+
const timer = setTimer(() => onError(new Error(`bootstrap deadline passed before ${expected}`)), Math.max(0, remaining));
|
|
34
|
+
child.on("message", onMessage);
|
|
35
|
+
child.on("error", onError);
|
|
36
|
+
child.on("exit", onExit);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function stop(child, prefix, terminate) {
|
|
40
|
+
try {
|
|
41
|
+
await terminate(child);
|
|
42
|
+
return { status: "failed", stage: "transfer", detail: prefix };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return { status: "failed", stage: "cleanup", detail: `${prefix}; termination could not be verified: ${detail(error)}` };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function openDetachedLeaseBootstrap(input) {
|
|
49
|
+
const terminate = input.terminate ?? terminateProcessGroup;
|
|
50
|
+
const now = input.now ?? Date.now;
|
|
51
|
+
let child;
|
|
52
|
+
try {
|
|
53
|
+
child = launchDetachedProcess(input.launch, input.spawn);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
try {
|
|
57
|
+
input.parentLease.close();
|
|
58
|
+
return { status: "failed", stage: "spawn", detail: detail(error) };
|
|
59
|
+
}
|
|
60
|
+
catch (closeError) {
|
|
61
|
+
return { status: "failed", stage: "cleanup", detail: `${detail(error)}; parent lease release failed: ${detail(closeError)}` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const ready = waitForMessage(child, input.coordinate, "transfer-ready", input.deadline, now, setTimeout, clearTimeout);
|
|
65
|
+
const acquired = waitForMessage(child, input.coordinate, "ownership-acquired", input.deadline, now, setTimeout, clearTimeout);
|
|
66
|
+
void acquired.catch(() => { });
|
|
67
|
+
try {
|
|
68
|
+
await ready;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
let failure = detail(error);
|
|
72
|
+
try {
|
|
73
|
+
input.parentLease.close();
|
|
74
|
+
}
|
|
75
|
+
catch (closeError) {
|
|
76
|
+
failure += `; parent lease release failed: ${detail(closeError)}`;
|
|
77
|
+
}
|
|
78
|
+
return stop(child, failure, terminate);
|
|
79
|
+
}
|
|
80
|
+
let decision;
|
|
81
|
+
let result;
|
|
82
|
+
return {
|
|
83
|
+
status: "ready",
|
|
84
|
+
bootstrap: {
|
|
85
|
+
child,
|
|
86
|
+
async authorize() {
|
|
87
|
+
if (decision === "reject")
|
|
88
|
+
return { status: "failed", stage: "transfer", detail: "bootstrap was already rejected" };
|
|
89
|
+
if (decision)
|
|
90
|
+
return result;
|
|
91
|
+
decision = "authorize";
|
|
92
|
+
result = (async () => {
|
|
93
|
+
let transferFailure;
|
|
94
|
+
let transferMayBeOwnedByChild = false;
|
|
95
|
+
try {
|
|
96
|
+
input.parentLease.releaseReservationForTransfer();
|
|
97
|
+
await send(child, { kind: "promote", coordinate: input.coordinate });
|
|
98
|
+
transferMayBeOwnedByChild = true;
|
|
99
|
+
try {
|
|
100
|
+
input.parentLease.close();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
transferFailure = error;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await acquired;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
transferFailure ??= error;
|
|
110
|
+
}
|
|
111
|
+
if (transferFailure && observeRunnerLease(input.leasePath).state !== "held")
|
|
112
|
+
throw transferFailure;
|
|
113
|
+
await send(child, { kind: "authorize", coordinate: input.coordinate });
|
|
114
|
+
return { status: "authorized", child };
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
try {
|
|
118
|
+
input.parentLease.close();
|
|
119
|
+
}
|
|
120
|
+
catch { /* cleanup detail is secondary to transfer failure */ }
|
|
121
|
+
if (transferMayBeOwnedByChild && observeRunnerLease(input.leasePath).state === "held") {
|
|
122
|
+
try {
|
|
123
|
+
await send(child, { kind: "authorize", coordinate: input.coordinate });
|
|
124
|
+
}
|
|
125
|
+
catch { /* held lease already proves authority */ }
|
|
126
|
+
return { status: "authorized", child };
|
|
127
|
+
}
|
|
128
|
+
return stop(child, detail(error), terminate);
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
return result;
|
|
132
|
+
},
|
|
133
|
+
async reject() {
|
|
134
|
+
if (decision === "authorize")
|
|
135
|
+
return { status: "failed", stage: "transfer", detail: "bootstrap was already authorized" };
|
|
136
|
+
if (decision)
|
|
137
|
+
return result;
|
|
138
|
+
decision = "reject";
|
|
139
|
+
result = (async () => {
|
|
140
|
+
try {
|
|
141
|
+
await send(child, { kind: "reject", coordinate: input.coordinate });
|
|
142
|
+
}
|
|
143
|
+
catch { /* termination proves rejection */ }
|
|
144
|
+
let failure = "launch rejected";
|
|
145
|
+
try {
|
|
146
|
+
input.parentLease.close();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
failure += `; parent lease release failed: ${detail(error)}`;
|
|
150
|
+
}
|
|
151
|
+
const stopped = await stop(child, failure, terminate);
|
|
152
|
+
return stopped.stage === "cleanup" ? stopped : { status: "rejected" };
|
|
153
|
+
})();
|
|
154
|
+
return result;
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function sendUpstream(message) {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
if (!process.send || !process.connected)
|
|
162
|
+
return reject(new Error("bootstrap channel is unavailable"));
|
|
163
|
+
process.send(message, (error) => error ? reject(error) : resolve());
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function receive(coordinate, expected) {
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const cleanup = () => {
|
|
169
|
+
process.off("message", onMessage);
|
|
170
|
+
process.off("disconnect", onDisconnect);
|
|
171
|
+
};
|
|
172
|
+
const onDisconnect = () => { cleanup(); reject(new Error("bootstrap channel disconnected")); };
|
|
173
|
+
const onMessage = (value) => {
|
|
174
|
+
const message = value;
|
|
175
|
+
if (message.coordinate !== coordinate || (message.kind !== expected && message.kind !== "reject"))
|
|
176
|
+
return;
|
|
177
|
+
cleanup();
|
|
178
|
+
resolve(message);
|
|
179
|
+
};
|
|
180
|
+
process.on("message", onMessage);
|
|
181
|
+
process.on("disconnect", onDisconnect);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export async function receiveDetachedLease(input) {
|
|
185
|
+
if (process.env[RUN_CONTROL_BOOTSTRAP_ENV] !== "1")
|
|
186
|
+
throw new Error("process lacks bootstrap authority");
|
|
187
|
+
let target;
|
|
188
|
+
let lease;
|
|
189
|
+
try {
|
|
190
|
+
target = acquireRunnerLeaseTransferTarget(input.leasePath);
|
|
191
|
+
await sendUpstream({ kind: "transfer-ready", coordinate: input.coordinate });
|
|
192
|
+
if ((await receive(input.coordinate, "promote")).kind === "reject")
|
|
193
|
+
return { status: "rejected" };
|
|
194
|
+
lease = target.promote();
|
|
195
|
+
target = undefined;
|
|
196
|
+
await sendUpstream({ kind: "ownership-acquired", coordinate: input.coordinate });
|
|
197
|
+
if ((await receive(input.coordinate, "authorize")).kind === "reject")
|
|
198
|
+
return { status: "rejected" };
|
|
199
|
+
process.disconnect?.();
|
|
200
|
+
const owned = lease;
|
|
201
|
+
lease = undefined;
|
|
202
|
+
return { status: "authorized", lease: owned };
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
lease?.close();
|
|
206
|
+
target?.close();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
/** Launches a process in its own POSIX process group when the platform supports it. */
|
|
3
|
+
export function launchDetachedProcess(input, spawnProcess = nodeSpawn) {
|
|
4
|
+
const child = spawnProcess(input.command, input.args, {
|
|
5
|
+
...input.options,
|
|
6
|
+
detached: process.platform !== "win32",
|
|
7
|
+
});
|
|
8
|
+
if (child.pid === undefined) {
|
|
9
|
+
throw new Error("detached process did not expose a process id");
|
|
10
|
+
}
|
|
11
|
+
if (input.unref)
|
|
12
|
+
child.unref();
|
|
13
|
+
return child;
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { PROCESS_GROUP_KILL_GRACE_MS, ProcessTreeTerminationError, ownedProcessIdentity, ownedProcessIsLive, terminateOwnedProcess, terminateProcessGroup, } from "./process-tree.js";
|
|
2
|
+
export { launchDetachedProcess, } from "./detached-process.js";
|
|
3
|
+
export { openDetachedLeaseBootstrap, receiveDetachedLease, RUN_CONTROL_BOOTSTRAP_ENV, } from "./detached-lease-bootstrap.js";
|
|
4
|
+
export { initializeRunnerLeaseDatabase, acquireRunnerLease, acquireRunnerLeaseTransferTarget, acquireRunnerLostProof, observeRunnerLease, RunnerLeaseUnavailableError, } from "./runner-lease.js";
|
|
5
|
+
export { beginConnectionBoundRunnerClose, } from "./connection-bound-close.js";
|
|
6
|
+
export { acquireSqliteProcessLifetimeLock, initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "./sqlite-process-lifetime-lock.js";
|
|
@@ -88,7 +88,7 @@ async function terminateIdentity(identity, options) {
|
|
|
88
88
|
throw new Error(`owned process group ${identity.pid} remained after SIGKILL`);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
/** Terminate an orphaned
|
|
91
|
+
/** Terminate an orphaned process identity retained by caller-owned coordination. */
|
|
92
92
|
export async function terminateOwnedProcess(identity, options = {}) {
|
|
93
93
|
try {
|
|
94
94
|
await terminateIdentity(identity, {
|