@astrosheep/keiyaku 2.9.12 → 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 +22 -18
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
- 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/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 +12 -9
- 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/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 +1 -1
- 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 +2 -0
- package/build/cli/commands/task/compose/handler.js +2 -2
- package/build/cli/commands/task/compose/meta.js +2 -2
- 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 +43 -11
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +6 -5
- package/build/cli/parse-flags.js +39 -0
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +18 -0
- package/build/cli/parse.js +21 -8
- 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 +6 -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/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 +43 -30
- 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 +42 -5
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +9 -5
- 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 +18 -13
- 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 +125 -79
- 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 +14 -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 +7 -7
- package/build/core/projection/projection-kill.js +15 -6
- 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 +46 -27
- package/build/core/projection/projection-wake.js +27 -89
- 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/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +13 -16
- 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 +54 -6
- package/build/core/repository-ledger/write-transaction.js +9 -4
- 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/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
- 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 +3 -2
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +3 -3
- package/build/core/task/settlement-git.js +1 -4
- 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-store-repository.js +1 -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 +9 -1
- 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 +1 -1
- package/skills/keiyaku-akuma/SKILL.md +21 -20
- package/skills/keiyaku-task/SKILL.md +38 -4
- package/skills/keiyaku-workflow/SKILL.md +51 -11
- 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 -211
- 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
|
@@ -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, {
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { initializeSqliteProcessLifetimeLockDatabase, isSqliteProcessLifetimeLockContention, openSqliteProcessLifetimeLockTransaction, SqliteProcessLifetimeLockError, sqliteProcessLifetimeLockErrorDetail, } from "./sqlite-process-lifetime-lock.js";
|
|
2
|
+
export class RunnerLeaseUnavailableError extends Error {
|
|
3
|
+
code = "RUNNER_LEASE_UNAVAILABLE";
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.name = "RunnerLeaseUnavailableError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function errorDetail(error) {
|
|
10
|
+
return sqliteProcessLifetimeLockErrorDetail(error);
|
|
11
|
+
}
|
|
12
|
+
function isContention(error) {
|
|
13
|
+
return isSqliteProcessLifetimeLockContention(error)
|
|
14
|
+
|| (error instanceof SqliteProcessLifetimeLockError && error.reason === "busy");
|
|
15
|
+
}
|
|
16
|
+
function openGuard(lockPath, busyTimeoutMs) {
|
|
17
|
+
return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "deferred", busyTimeoutMs });
|
|
18
|
+
}
|
|
19
|
+
function openReservation(lockPath, busyTimeoutMs) {
|
|
20
|
+
return openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "immediate", busyTimeoutMs });
|
|
21
|
+
}
|
|
22
|
+
function closeTransaction(transaction) {
|
|
23
|
+
try {
|
|
24
|
+
transaction.close();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function createHeldLock(lockPath, busyTimeoutMs, guard, initialReservation) {
|
|
31
|
+
let closed = false;
|
|
32
|
+
let reservation = initialReservation;
|
|
33
|
+
return {
|
|
34
|
+
releaseReservationForTransfer() {
|
|
35
|
+
if (closed || reservation === undefined)
|
|
36
|
+
return;
|
|
37
|
+
const released = reservation;
|
|
38
|
+
reservation = undefined;
|
|
39
|
+
const failure = closeTransaction(released);
|
|
40
|
+
if (failure !== undefined)
|
|
41
|
+
throw new RunnerLeaseUnavailableError(`runner lease reservation release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
42
|
+
},
|
|
43
|
+
reclaimReservation() {
|
|
44
|
+
if (closed || reservation !== undefined)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
reservation = openReservation(lockPath, busyTimeoutMs);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new RunnerLeaseUnavailableError(`runner lease reservation reclaim failed: ${errorDetail(error)}`, { cause: error });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
close() {
|
|
54
|
+
if (closed)
|
|
55
|
+
return;
|
|
56
|
+
closed = true;
|
|
57
|
+
let failure;
|
|
58
|
+
if (reservation !== undefined)
|
|
59
|
+
failure = closeTransaction(reservation);
|
|
60
|
+
failure ??= closeTransaction(guard);
|
|
61
|
+
if (failure !== undefined)
|
|
62
|
+
throw new RunnerLeaseUnavailableError(`runner lease release failed: ${errorDetail(failure)}`, { cause: failure });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function unavailable(operation, error) {
|
|
67
|
+
return new RunnerLeaseUnavailableError(`${operation}: ${errorDetail(error)}`, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
export function initializeRunnerLeaseDatabase(leasePath) {
|
|
70
|
+
try {
|
|
71
|
+
return initializeSqliteProcessLifetimeLockDatabase(leasePath);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw unavailable("runner lease database could not be initialized", error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function acquireRunnerLease(lockPath, busyTimeoutMs = 0) {
|
|
78
|
+
let guard;
|
|
79
|
+
try {
|
|
80
|
+
guard = openGuard(lockPath, busyTimeoutMs);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw unavailable("runner lease database could not be opened", error);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return createHeldLock(lockPath, busyTimeoutMs, guard, openReservation(lockPath, busyTimeoutMs));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
closeTransaction(guard);
|
|
90
|
+
if (isContention(error))
|
|
91
|
+
throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
|
|
92
|
+
throw unavailable("runner lease acquisition failed", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function acquireRunnerLeaseTransferTarget(lockPath, busyTimeoutMs = 0) {
|
|
96
|
+
let guard;
|
|
97
|
+
try {
|
|
98
|
+
guard = openGuard(lockPath, busyTimeoutMs);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw unavailable("runner lease transfer acquisition failed", error);
|
|
102
|
+
}
|
|
103
|
+
let closed = false;
|
|
104
|
+
return {
|
|
105
|
+
promote() {
|
|
106
|
+
if (closed)
|
|
107
|
+
throw new RunnerLeaseUnavailableError("runner lease transfer target is closed");
|
|
108
|
+
try {
|
|
109
|
+
const reservation = openReservation(lockPath, busyTimeoutMs);
|
|
110
|
+
closed = true;
|
|
111
|
+
return createHeldLock(lockPath, busyTimeoutMs, guard, reservation);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw unavailable("runner lease transfer promotion failed", error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
close() {
|
|
118
|
+
if (closed)
|
|
119
|
+
return;
|
|
120
|
+
closed = true;
|
|
121
|
+
const failure = closeTransaction(guard);
|
|
122
|
+
if (failure !== undefined)
|
|
123
|
+
throw unavailable("runner lease transfer release failed", failure);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function acquireRunnerLostProof(lockPath, busyTimeoutMs = 0) {
|
|
128
|
+
let transaction;
|
|
129
|
+
try {
|
|
130
|
+
transaction = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (isContention(error))
|
|
134
|
+
throw new RunnerLeaseUnavailableError("runner lease is already held", { cause: error });
|
|
135
|
+
throw unavailable("runner lost proof acquisition failed", error);
|
|
136
|
+
}
|
|
137
|
+
let closed = false;
|
|
138
|
+
return {
|
|
139
|
+
close() {
|
|
140
|
+
if (closed)
|
|
141
|
+
return;
|
|
142
|
+
closed = true;
|
|
143
|
+
const failure = closeTransaction(transaction);
|
|
144
|
+
if (failure !== undefined)
|
|
145
|
+
throw unavailable("runner lost proof release failed", failure);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Positive death probe without creating a missing lock database. */
|
|
150
|
+
export function observeRunnerLease(lockPath) {
|
|
151
|
+
let probe;
|
|
152
|
+
try {
|
|
153
|
+
probe = openSqliteProcessLifetimeLockTransaction({ path: lockPath, mode: "exclusive", busyTimeoutMs: 0 });
|
|
154
|
+
return { state: "released" };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (isContention(error))
|
|
158
|
+
return { state: "held" };
|
|
159
|
+
const detail = errorDetail(error);
|
|
160
|
+
return {
|
|
161
|
+
state: "unknown",
|
|
162
|
+
reason: detail.includes("rollback-journal") ? "unsupported-journal-mode" : "probe-failed",
|
|
163
|
+
detail,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
try {
|
|
168
|
+
probe?.close();
|
|
169
|
+
}
|
|
170
|
+
catch { /* no probe cleanup result is death evidence */ }
|
|
171
|
+
}
|
|
172
|
+
}
|
package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js}
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
3
|
const SQLITE_BUSY = 5;
|
|
4
|
-
const SUPPORTED_PLATFORMS = new Set(["darwin", "linux", "win32"]);
|
|
5
4
|
export class SqliteProcessLifetimeLockError extends Error {
|
|
6
5
|
reason;
|
|
7
6
|
code = "SQLITE_PROCESS_LIFETIME_LOCK_UNAVAILABLE";
|
|
@@ -11,16 +10,6 @@ export class SqliteProcessLifetimeLockError extends Error {
|
|
|
11
10
|
this.name = "SqliteProcessLifetimeLockError";
|
|
12
11
|
}
|
|
13
12
|
}
|
|
14
|
-
export class SqliteProcessLifetimeLockUnsupportedError extends Error {
|
|
15
|
-
code = "SQLITE_PROCESS_LIFETIME_LOCK_UNSUPPORTED";
|
|
16
|
-
constructor(platform) {
|
|
17
|
-
super(`SQLite process-lifetime locks are unsupported on platform '${platform}'`);
|
|
18
|
-
this.name = "SqliteProcessLifetimeLockUnsupportedError";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export function isSqliteProcessLifetimeLockPlatformSupported(platform = process.platform) {
|
|
22
|
-
return SUPPORTED_PLATFORMS.has(platform);
|
|
23
|
-
}
|
|
24
13
|
export function sqliteProcessLifetimeLockErrorDetail(error) {
|
|
25
14
|
return error instanceof Error ? error.message : String(error);
|
|
26
15
|
}
|
|
@@ -35,11 +24,6 @@ export function isSqliteProcessLifetimeLockContention(error) {
|
|
|
35
24
|
return errcode === SQLITE_BUSY || code === "ERR_SQLITE_BUSY" || code === "ERR_SQLITE_LOCKED"
|
|
36
25
|
|| message.includes("database is locked") || message.includes("database is busy");
|
|
37
26
|
}
|
|
38
|
-
function assertPlatform(platform) {
|
|
39
|
-
if (!isSqliteProcessLifetimeLockPlatformSupported(platform)) {
|
|
40
|
-
throw new SqliteProcessLifetimeLockUnsupportedError(platform);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
27
|
function assertBusyTimeout(busyTimeoutMs) {
|
|
44
28
|
if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0) {
|
|
45
29
|
throw new SqliteProcessLifetimeLockError("SQLite process-lifetime lock busy timeout must be a non-negative safe integer", "invalid");
|
|
@@ -69,7 +53,7 @@ function closeTransaction(database) {
|
|
|
69
53
|
return failure;
|
|
70
54
|
}
|
|
71
55
|
function throwLockError(prefix, error) {
|
|
72
|
-
if (error instanceof SqliteProcessLifetimeLockError
|
|
56
|
+
if (error instanceof SqliteProcessLifetimeLockError)
|
|
73
57
|
throw error;
|
|
74
58
|
if (isSqliteProcessLifetimeLockContention(error)) {
|
|
75
59
|
throw new SqliteProcessLifetimeLockError(`${prefix} is already held`, "busy", { cause: error });
|
|
@@ -78,10 +62,9 @@ function throwLockError(prefix, error) {
|
|
|
78
62
|
}
|
|
79
63
|
/**
|
|
80
64
|
* Creates only a rollback-journal schema anchor. The empty database persists no
|
|
81
|
-
* ownership facts; an open transaction is the complete ownership signal.
|
|
65
|
+
* ownership facts; an open transaction is the complete physical ownership signal.
|
|
82
66
|
*/
|
|
83
|
-
export function initializeSqliteProcessLifetimeLockDatabase(lockPath
|
|
84
|
-
assertPlatform(platform);
|
|
67
|
+
export function initializeSqliteProcessLifetimeLockDatabase(lockPath) {
|
|
85
68
|
let database;
|
|
86
69
|
try {
|
|
87
70
|
database = new DatabaseSync(lockPath);
|
|
@@ -105,9 +88,7 @@ export function initializeSqliteProcessLifetimeLockDatabase(lockPath, platform =
|
|
|
105
88
|
return lockPath;
|
|
106
89
|
}
|
|
107
90
|
export function openSqliteProcessLifetimeLockTransaction(input) {
|
|
108
|
-
const platform = input.platform ?? process.platform;
|
|
109
91
|
const busyTimeoutMs = input.busyTimeoutMs ?? 0;
|
|
110
|
-
assertPlatform(platform);
|
|
111
92
|
assertBusyTimeout(busyTimeoutMs);
|
|
112
93
|
if (!input.create) {
|
|
113
94
|
try {
|
|
@@ -130,7 +111,7 @@ export function openSqliteProcessLifetimeLockTransaction(input) {
|
|
|
130
111
|
database.exec("CREATE TABLE IF NOT EXISTS lock_anchor (singleton INTEGER PRIMARY KEY CHECK (singleton = 1))");
|
|
131
112
|
}
|
|
132
113
|
database.exec(`BEGIN ${input.mode.toUpperCase()}`);
|
|
133
|
-
// A DEFERRED transaction becomes
|
|
114
|
+
// A DEFERRED transaction becomes a process-lifetime guard only after this read
|
|
134
115
|
// acquires its SHARED lock. Keep this inside every transaction mode.
|
|
135
116
|
assertSchema(database);
|
|
136
117
|
if (journalMode(database) !== "delete") {
|
|
@@ -182,7 +163,6 @@ export async function acquireSqliteProcessLifetimeLock(input) {
|
|
|
182
163
|
mode: "immediate",
|
|
183
164
|
busyTimeoutMs: 0,
|
|
184
165
|
create: true,
|
|
185
|
-
platform: input.platform,
|
|
186
166
|
});
|
|
187
167
|
}
|
|
188
168
|
catch (error) {
|
package/build/core/seal.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
+
import { requireCommitCoordinate } from "../git/commits.js";
|
|
2
3
|
import { createGit, parseRevListOutput, wrapGitError } from "../git/core.js";
|
|
3
4
|
import { effectiveScope } from "./lifecycle-history.js";
|
|
4
5
|
import { scopePathEvidence, } from "./scope.js";
|
|
@@ -42,13 +43,7 @@ export async function receiptsAfterFence(cwd, entries, head = "HEAD") {
|
|
|
42
43
|
return revList(cwd, getFence(entries), await resolveCommit(cwd, head));
|
|
43
44
|
}
|
|
44
45
|
async function resolveCommit(cwd, revision) {
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
return (await git.raw(["rev-parse", "--verify", `${revision}^{commit}`])).trim();
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
throw wrapGitError(`rev-parse ${revision}`, error, cwd);
|
|
51
|
-
}
|
|
46
|
+
return (await requireCommitCoordinate(cwd, revision)).commit;
|
|
52
47
|
}
|
|
53
48
|
async function revList(cwd, from, to) {
|
|
54
49
|
const git = createGit(cwd);
|
|
@@ -120,15 +115,18 @@ async function auditScope(cwd, entries, paths) {
|
|
|
120
115
|
...await scopeViolations(cwd, paths, scope),
|
|
121
116
|
...taskNamespaceViolations(paths, findBind(entries).data.scope),
|
|
122
117
|
].filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
123
|
-
if (scopeViolationEvidence.length > 0) {
|
|
124
|
-
throw new ScopeAuditError(findBind(entries).contract, scope, scopeViolationEvidence);
|
|
125
|
-
}
|
|
126
118
|
const violations = scopeViolationEvidence.map((violation) => violation.path);
|
|
127
119
|
return {
|
|
128
120
|
scopeAudit: { violations },
|
|
129
121
|
scopeViolationEvidence,
|
|
130
122
|
};
|
|
131
123
|
}
|
|
124
|
+
function admitScope(contract, scope, capture, policy) {
|
|
125
|
+
if (policy === "refuse" && capture.scopeViolationEvidence.length > 0) {
|
|
126
|
+
throw new ScopeAuditError(contract, scope, capture.scopeViolationEvidence);
|
|
127
|
+
}
|
|
128
|
+
return capture;
|
|
129
|
+
}
|
|
132
130
|
export class ScopeAuditError extends FlowError {
|
|
133
131
|
scopeViolationEvidence;
|
|
134
132
|
constructor(_contractId, scope, evidence) {
|
|
@@ -155,7 +153,7 @@ function currentFoldedSealArc(model) {
|
|
|
155
153
|
return model.seal === null ? model.intent.arc : null;
|
|
156
154
|
}
|
|
157
155
|
/** Current-read admission capture: scope and fence come only from the folded model. */
|
|
158
|
-
export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = []) {
|
|
156
|
+
export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend = [], scopePolicy) {
|
|
159
157
|
const arc = currentFoldedSealArc(model);
|
|
160
158
|
if (arc === null)
|
|
161
159
|
return null;
|
|
@@ -168,14 +166,18 @@ export async function captureFoldedSeal(cwd, model, head = "HEAD", scopeAppend =
|
|
|
168
166
|
]);
|
|
169
167
|
const effectiveScope = [...model.effectiveScope, ...scopeAppend];
|
|
170
168
|
const bind = model.history.find((row) => row.entry.kind === "bind")?.entry;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (scopeViolationEvidence.length > 0) {
|
|
176
|
-
throw new ScopeAuditError(model.contract, effectiveScope, scopeViolationEvidence);
|
|
169
|
+
const ordinaryViolations = await scopeViolations(cwd, paths, effectiveScope);
|
|
170
|
+
const namespaceViolations = taskNamespaceViolations(paths, bind?.kind === "bind" ? bind.data.scope : []);
|
|
171
|
+
if (namespaceViolations.length > 0) {
|
|
172
|
+
throw new ScopeAuditError(model.contract, effectiveScope, namespaceViolations);
|
|
177
173
|
}
|
|
178
|
-
|
|
174
|
+
const scopeViolationEvidence = ordinaryViolations
|
|
175
|
+
.filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
176
|
+
const scopeAudit = admitScope(model.contract, effectiveScope, {
|
|
177
|
+
scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
|
|
178
|
+
scopeViolationEvidence,
|
|
179
|
+
}, scopePolicy);
|
|
180
|
+
return { arc, oldFence, newFence, receipts, diffstat: stat, ...scopeAudit };
|
|
179
181
|
}
|
|
180
182
|
/** Current seal intent: open explicit arc, else derived a0 while no explicit arc has ever existed. */
|
|
181
183
|
export function currentSealArc(entries) {
|
|
@@ -219,7 +221,14 @@ export async function previewSeal(cwd, entries, head = "HEAD") {
|
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
export async function captureSeal(cwd, entries, head = "HEAD") {
|
|
222
|
-
|
|
224
|
+
const preview = await previewSeal(cwd, entries, head);
|
|
225
|
+
if (!preview.capture)
|
|
226
|
+
return null;
|
|
227
|
+
admitScope("direct seal", effectiveScope(entries), {
|
|
228
|
+
scopeAudit: preview.capture.scopeAudit,
|
|
229
|
+
scopeViolationEvidence: preview.capture.scopeViolationEvidence,
|
|
230
|
+
}, "refuse");
|
|
231
|
+
return preview.capture;
|
|
223
232
|
}
|
|
224
233
|
/** Folded renew capture: preserve receipt hashes while auditing rebased delivery content. */
|
|
225
234
|
export async function captureFoldedRenewSeal(input) {
|
|
@@ -239,21 +248,24 @@ export async function captureFoldedRenewSeal(input) {
|
|
|
239
248
|
]);
|
|
240
249
|
const scope = [...input.effectiveScope, ...input.pendingScopeOverlay];
|
|
241
250
|
const bindScope = await acceptedBindScope(input.cwd, input.contract);
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (scopeViolationEvidence.length > 0) {
|
|
247
|
-
throw new ScopeAuditError(input.contract, scope, scopeViolationEvidence);
|
|
251
|
+
const ordinaryViolations = await scopeViolations(input.cwd, paths, scope);
|
|
252
|
+
const namespaceViolations = taskNamespaceViolations(paths, bindScope);
|
|
253
|
+
if (namespaceViolations.length > 0) {
|
|
254
|
+
throw new ScopeAuditError(input.contract, scope, namespaceViolations);
|
|
248
255
|
}
|
|
256
|
+
const scopeViolationEvidence = ordinaryViolations
|
|
257
|
+
.filter((row, index, all) => all.findIndex((candidate) => candidate.path === row.path) === index);
|
|
258
|
+
const scopeAudit = admitScope(input.contract, scope, {
|
|
259
|
+
scopeAudit: { violations: scopeViolationEvidence.map((violation) => violation.path) },
|
|
260
|
+
scopeViolationEvidence,
|
|
261
|
+
}, input.scopePolicy);
|
|
249
262
|
return {
|
|
250
263
|
arc,
|
|
251
264
|
oldFence,
|
|
252
265
|
newFence: receiptHead,
|
|
253
266
|
receipts,
|
|
254
267
|
diffstat: stat,
|
|
255
|
-
scopeAudit
|
|
256
|
-
scopeViolationEvidence,
|
|
268
|
+
...scopeAudit,
|
|
257
269
|
};
|
|
258
270
|
}
|
|
259
271
|
export async function sealCurrentArc(input) {
|
|
@@ -301,7 +313,7 @@ export async function sealCurrentArc(input) {
|
|
|
301
313
|
prepare: async (admission) => {
|
|
302
314
|
if (admission.business.existing)
|
|
303
315
|
return { capture: admission.business.existing.capture, existing: { transactionObjectId: admission.business.existing.transactionObjectId } };
|
|
304
|
-
return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD") };
|
|
316
|
+
return { capture: input.capture ?? await captureFoldedSeal(admission.business.targetRoot, admission.business.lifecycle, "HEAD", [], "refuse") };
|
|
305
317
|
},
|
|
306
318
|
revalidate: ({ session, prepared }) => {
|
|
307
319
|
assertOperable(input.contractId, session.lifecycle);
|
|
@@ -319,7 +331,7 @@ export async function sealCurrentArc(input) {
|
|
|
319
331
|
effect: async ({ context, prepared }) => {
|
|
320
332
|
const capture = prepared.capture;
|
|
321
333
|
if (!capture)
|
|
322
|
-
return { kind: "refused-before-effects", authoritativeHead: context.session.
|
|
334
|
+
return { kind: "refused-before-effects", authoritativeHead: context.session.pins.repositoryLedgerHead, refusal: { code: "NO_OPEN_ARC", message: "no current arc requires sealing" } };
|
|
323
335
|
const at = new Date(input.nowMs).toISOString();
|
|
324
336
|
{
|
|
325
337
|
const { scopeViolationEvidence: _scopeViolationEvidence, ...durableCapture } = capture;
|
|
@@ -333,11 +345,11 @@ export async function sealCurrentArc(input) {
|
|
|
333
345
|
};
|
|
334
346
|
const result = prepared.existing
|
|
335
347
|
? await context.replayCommitted({ transactionObjectId: prepared.existing.transactionObjectId })
|
|
336
|
-
: await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.
|
|
348
|
+
: await context.publishAuthoritative({ verb: "seal", admittedLedgerHead: context.session.pins.repositoryLedgerHead, acceptedLifecycleTail: context.session.lifecycle.contractHead, entries: [entry] });
|
|
337
349
|
if (result.status === "indeterminate")
|
|
338
|
-
return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.
|
|
350
|
+
return { kind: "precommit-effects-only", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead };
|
|
339
351
|
if (result.status !== "committed")
|
|
340
|
-
return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.
|
|
352
|
+
return { kind: "refused-before-effects", authoritativeHead: result.currentHead ?? context.session.pins.repositoryLedgerHead, refusal: { code: "LEDGER_CAS_CONFLICT", message: `contract ${input.contractId} changed while sealing` } };
|
|
341
353
|
return { kind: "committed", commit: result, entryKind: "seal", value: {
|
|
342
354
|
...capture,
|
|
343
355
|
sealedBy: input.sealedBy,
|