@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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { openProjectionGenerationStore, } from "./store.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { acquireRunnerLease, observeRunnerLease, } from "../../run-control/runner-lease.js";
|
|
3
|
+
import { beginConnectionBoundRunnerClose } from "../../run-control/connection-bound-close.js";
|
|
4
|
+
import { createProjectionExecutionId, projectionRunnerLeasePath } from "./projection-generation-identity.js";
|
|
4
5
|
import { applyContinuationEffort, } from "./projection-generation-continuation.js";
|
|
5
|
-
import { openProjectionTellObserver } from "
|
|
6
|
+
import { openProjectionTellObserver } from "./database.js";
|
|
6
7
|
import { OUTCOME_ERROR_VERSION } from "../../../agents/types.js";
|
|
8
|
+
import { openProjectionGenerationDoorbell, } from "./projection-generation-doorbell.js";
|
|
7
9
|
/** Private runner-side kill grace. Not a setting, env, parameter, or export. */
|
|
8
10
|
const KILL_GRACE_MS = 5000;
|
|
9
11
|
function successorLaunchFacts(launch, completionFacts, effort, now) {
|
|
@@ -51,10 +53,10 @@ function readOutcomeState(state) {
|
|
|
51
53
|
}
|
|
52
54
|
function acquireRunnerLockOrObserveDuplicate(projectionDirectory) {
|
|
53
55
|
try {
|
|
54
|
-
return
|
|
56
|
+
return acquireRunnerLease(projectionRunnerLeasePath(projectionDirectory));
|
|
55
57
|
}
|
|
56
58
|
catch (error) {
|
|
57
|
-
if (
|
|
59
|
+
if (observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state === "held") {
|
|
58
60
|
return undefined;
|
|
59
61
|
}
|
|
60
62
|
throw error;
|
|
@@ -75,9 +77,38 @@ export async function runProjectionGeneration(input) {
|
|
|
75
77
|
let adopted = false;
|
|
76
78
|
let settled = false;
|
|
77
79
|
let tellObserver;
|
|
80
|
+
let doorbell;
|
|
78
81
|
let graceTimer;
|
|
79
|
-
let intentTimer;
|
|
80
82
|
let handle;
|
|
83
|
+
let gracefulAbortInvoked = false;
|
|
84
|
+
let forceAbortInvoked = false;
|
|
85
|
+
let sawKillIntent = false;
|
|
86
|
+
let sawInterruptIntent = false;
|
|
87
|
+
let wakeAfterLockRelease = false;
|
|
88
|
+
let pullStopIntent;
|
|
89
|
+
let notifyMainWait;
|
|
90
|
+
const commitTerminal = (stage) => {
|
|
91
|
+
if (!runnerLock)
|
|
92
|
+
throw new Error("runner lease is not held");
|
|
93
|
+
const close = beginConnectionBoundRunnerClose(store.connectionForRunnerClose(), runnerLock);
|
|
94
|
+
try {
|
|
95
|
+
const transition = stage();
|
|
96
|
+
if (transition.status !== "committed") {
|
|
97
|
+
close.rollback();
|
|
98
|
+
return transition;
|
|
99
|
+
}
|
|
100
|
+
close.commit();
|
|
101
|
+
runnerLock = undefined;
|
|
102
|
+
return transition;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (close.leaseOwnership === "retained")
|
|
106
|
+
close.rollback();
|
|
107
|
+
else
|
|
108
|
+
runnerLock = undefined;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
81
112
|
const clearGraceTimer = () => {
|
|
82
113
|
if (graceTimer === undefined)
|
|
83
114
|
return;
|
|
@@ -89,15 +120,50 @@ export async function runProjectionGeneration(input) {
|
|
|
89
120
|
?? acquireRunnerLockOrObserveDuplicate(input.projectionDirectory);
|
|
90
121
|
if (!runnerLock)
|
|
91
122
|
return { status: "duplicate" };
|
|
92
|
-
|
|
123
|
+
tellObserver = openProjectionTellObserver(input.projectionDirectory);
|
|
124
|
+
// Establish the version fence before the listener becomes reachable. The
|
|
125
|
+
// forced pull below still sees intent committed before listener startup.
|
|
126
|
+
tellObserver.hasDataVersionChanged();
|
|
127
|
+
pullStopIntent = (force = false) => {
|
|
128
|
+
try {
|
|
129
|
+
if (!force && !tellObserver.hasDataVersionChanged())
|
|
130
|
+
return undefined;
|
|
131
|
+
const live = store.readCurrentGeneration();
|
|
132
|
+
if (live?.launch.executionId !== input.executionId || live?.verdict)
|
|
133
|
+
return live;
|
|
134
|
+
sawKillIntent = live.killIntent !== undefined;
|
|
135
|
+
sawInterruptIntent = live.interruptIntent !== undefined;
|
|
136
|
+
if (!sawKillIntent && !sawInterruptIntent)
|
|
137
|
+
return live;
|
|
138
|
+
if (gracefulAbortInvoked || !handle)
|
|
139
|
+
return live;
|
|
140
|
+
gracefulAbortInvoked = true;
|
|
141
|
+
void handle.abort("graceful").catch(() => {
|
|
142
|
+
// A failed native stop does not alter durable intent truth.
|
|
143
|
+
});
|
|
144
|
+
return live;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Observation failure is unknown, never evidence about the provider.
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
doorbell = await openProjectionGenerationDoorbell({
|
|
152
|
+
identity: store.readIdentity(),
|
|
153
|
+
executionId: input.executionId,
|
|
154
|
+
onRing: () => {
|
|
155
|
+
pullStopIntent?.(true);
|
|
156
|
+
notifyMainWait?.();
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
// This is the one baseline heart pull after listener registration. It
|
|
160
|
+
// covers intent committed while the listener did not yet exist.
|
|
161
|
+
const initial = pullStopIntent(true);
|
|
93
162
|
if (initial?.launch.executionId !== input.executionId
|
|
94
163
|
|| initial.adoption
|
|
95
164
|
|| initial.verdict) {
|
|
96
165
|
return { status: "rejected" };
|
|
97
166
|
}
|
|
98
|
-
tellObserver = openProjectionTellObserver(input.projectionDirectory);
|
|
99
|
-
// Prime before adoption so a concurrent stop write cannot become the baseline.
|
|
100
|
-
tellObserver.hasDataVersionChanged();
|
|
101
167
|
const adoption = store.adoptionIfCurrent({
|
|
102
168
|
executionId: input.executionId,
|
|
103
169
|
facts: { adoptedAt: new Date(now()).toISOString() },
|
|
@@ -112,10 +178,10 @@ export async function runProjectionGeneration(input) {
|
|
|
112
178
|
// Adoption is the provider-admission boundary. A kill that committed first
|
|
113
179
|
// settles without constructing a provider execution.
|
|
114
180
|
if (current.killIntent) {
|
|
115
|
-
const killed = store.runnerKilledIfRequested({
|
|
181
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
116
182
|
executionId: input.executionId,
|
|
117
183
|
terminalAt: new Date(now()).toISOString(),
|
|
118
|
-
});
|
|
184
|
+
}));
|
|
119
185
|
if (killed.status !== "committed")
|
|
120
186
|
return { status: "rejected" };
|
|
121
187
|
settled = true;
|
|
@@ -129,10 +195,10 @@ export async function runProjectionGeneration(input) {
|
|
|
129
195
|
}
|
|
130
196
|
if (current.killIntent) {
|
|
131
197
|
await prepared.dispose();
|
|
132
|
-
const killed = store.runnerKilledIfRequested({
|
|
198
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
133
199
|
executionId: input.executionId,
|
|
134
200
|
terminalAt: new Date(now()).toISOString(),
|
|
135
|
-
});
|
|
201
|
+
}));
|
|
136
202
|
if (killed.status !== "committed")
|
|
137
203
|
return { status: "rejected" };
|
|
138
204
|
settled = true;
|
|
@@ -140,68 +206,35 @@ export async function runProjectionGeneration(input) {
|
|
|
140
206
|
}
|
|
141
207
|
handle = prepared.start();
|
|
142
208
|
const settleInterrupted = (facts = {}) => {
|
|
143
|
-
|
|
209
|
+
pullStopIntent?.(true);
|
|
210
|
+
const transition = commitTerminal(() => store.runnerInterruptedIfRequested({
|
|
144
211
|
executionId: input.executionId,
|
|
145
212
|
facts,
|
|
146
213
|
terminalAt: new Date(now()).toISOString(),
|
|
147
|
-
});
|
|
214
|
+
}));
|
|
148
215
|
if (transition.status !== "committed")
|
|
149
216
|
return { status: "rejected" };
|
|
150
217
|
settled = true;
|
|
151
|
-
|
|
152
|
-
runnerLock = undefined;
|
|
153
|
-
void input.wakeAfterInterrupt?.().catch(() => {
|
|
154
|
-
// The ordinary wake path persists its typed failure. A later tell can retry.
|
|
155
|
-
});
|
|
218
|
+
wakeAfterLockRelease = true;
|
|
156
219
|
return { status: "completed" };
|
|
157
220
|
};
|
|
158
221
|
const settleRunnerOutcome = (execution) => {
|
|
159
|
-
|
|
222
|
+
pullStopIntent?.(true);
|
|
223
|
+
const transition = commitTerminal(() => store.runnerVerdictIfOpen({
|
|
160
224
|
executionId: input.executionId,
|
|
161
225
|
verdict: execution.verdict,
|
|
162
226
|
facts: execution.facts,
|
|
163
227
|
terminalAt: new Date(now()).toISOString(),
|
|
164
|
-
});
|
|
228
|
+
}));
|
|
165
229
|
if (transition.status !== "committed")
|
|
166
230
|
return { status: "rejected" };
|
|
167
231
|
settled = true;
|
|
168
232
|
if (transition.records[0]?.facts.state === "interrupted") {
|
|
169
|
-
|
|
170
|
-
runnerLock = undefined;
|
|
171
|
-
void input.wakeAfterInterrupt?.().catch(() => {
|
|
172
|
-
// The ordinary wake path persists its typed failure. A later observer retries.
|
|
173
|
-
});
|
|
233
|
+
wakeAfterLockRelease = true;
|
|
174
234
|
}
|
|
175
235
|
return { status: "completed" };
|
|
176
236
|
};
|
|
177
|
-
|
|
178
|
-
let forceAbortInvoked = false;
|
|
179
|
-
let sawKillIntent = false;
|
|
180
|
-
let sawInterruptIntent = false;
|
|
181
|
-
const observeStopBoundary = () => {
|
|
182
|
-
try {
|
|
183
|
-
if (!tellObserver.hasDataVersionChanged())
|
|
184
|
-
return;
|
|
185
|
-
const live = store.readCurrentGeneration();
|
|
186
|
-
if (live?.launch.executionId !== input.executionId)
|
|
187
|
-
return;
|
|
188
|
-
if (live.verdict)
|
|
189
|
-
return;
|
|
190
|
-
sawKillIntent = live.killIntent !== undefined;
|
|
191
|
-
sawInterruptIntent = live.interruptIntent !== undefined;
|
|
192
|
-
if (!sawKillIntent && !sawInterruptIntent)
|
|
193
|
-
return;
|
|
194
|
-
if (gracefulAbortInvoked)
|
|
195
|
-
return;
|
|
196
|
-
gracefulAbortInvoked = true;
|
|
197
|
-
void handle.abort("graceful").catch(() => {
|
|
198
|
-
// Abort observation failures fail closed for this boundary.
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// Observation failures fail closed and must not change provider outcome.
|
|
203
|
-
}
|
|
204
|
-
};
|
|
237
|
+
const observeStopBoundary = () => { pullStopIntent?.(); };
|
|
205
238
|
// Mutable box so async settlement is readable without control-flow narrowing traps.
|
|
206
239
|
const outcomeBox = { status: "pending" };
|
|
207
240
|
let resolveActivity;
|
|
@@ -213,6 +246,7 @@ export async function runProjectionGeneration(input) {
|
|
|
213
246
|
resolveActivity = undefined;
|
|
214
247
|
resolve?.();
|
|
215
248
|
};
|
|
249
|
+
notifyMainWait = bumpActivity;
|
|
216
250
|
const outcomeWatch = handle.outcome.then((execution) => {
|
|
217
251
|
outcomeBox.status = "fulfilled";
|
|
218
252
|
outcomeBox.execution = execution;
|
|
@@ -226,6 +260,7 @@ export async function runProjectionGeneration(input) {
|
|
|
226
260
|
});
|
|
227
261
|
// Always rejection-observe so abandon paths never surface unhandled rejections.
|
|
228
262
|
observeAbandoned(outcomeWatch);
|
|
263
|
+
const eventDrainState = { closed: false };
|
|
229
264
|
const eventDrain = (async () => {
|
|
230
265
|
try {
|
|
231
266
|
for await (const _event of handle.events) {
|
|
@@ -236,20 +271,20 @@ export async function runProjectionGeneration(input) {
|
|
|
236
271
|
}
|
|
237
272
|
}
|
|
238
273
|
catch {
|
|
239
|
-
//
|
|
274
|
+
// Public event delivery is observation-only; outcome owns terminal truth.
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
eventDrainState.closed = true;
|
|
278
|
+
bumpActivity();
|
|
240
279
|
}
|
|
241
|
-
bumpActivity();
|
|
242
280
|
})();
|
|
243
281
|
observeAbandoned(eventDrain);
|
|
244
|
-
// A provider may be silently executing when an operator interrupts it.
|
|
245
|
-
// The durable observer is the sole stop-intent authority in that interval.
|
|
246
|
-
intentTimer = setInterval(() => {
|
|
247
|
-
observeStopBoundary();
|
|
248
|
-
bumpActivity();
|
|
249
|
-
}, 100);
|
|
250
|
-
intentTimer.unref?.();
|
|
251
282
|
while (outcomeBox.status === "pending" && !sawKillIntent && !sawInterruptIntent) {
|
|
252
|
-
|
|
283
|
+
const wakeSources = [outcomeWatch, activityChanged()];
|
|
284
|
+
if (!eventDrainState.closed)
|
|
285
|
+
wakeSources.push(eventDrain);
|
|
286
|
+
await Promise.race(wakeSources);
|
|
287
|
+
observeStopBoundary();
|
|
253
288
|
}
|
|
254
289
|
if (sawKillIntent || sawInterruptIntent) {
|
|
255
290
|
// Grace race: outcome settlement inside the window is stop proof.
|
|
@@ -281,11 +316,11 @@ export async function runProjectionGeneration(input) {
|
|
|
281
316
|
throw killOutcome.error;
|
|
282
317
|
throw new Error("interrupt outcome did not establish provider stop");
|
|
283
318
|
}
|
|
284
|
-
const killed = store.runnerKilledIfRequested({
|
|
319
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
285
320
|
executionId: input.executionId,
|
|
286
321
|
...(facts && Object.keys(facts).length > 0 ? { facts } : {}),
|
|
287
322
|
terminalAt: new Date(now()).toISOString(),
|
|
288
|
-
});
|
|
323
|
+
}));
|
|
289
324
|
if (killed.status !== "committed")
|
|
290
325
|
return { status: "rejected" };
|
|
291
326
|
settled = true;
|
|
@@ -297,17 +332,17 @@ export async function runProjectionGeneration(input) {
|
|
|
297
332
|
await handle.abort("force");
|
|
298
333
|
if (sawInterruptIntent)
|
|
299
334
|
return settleInterrupted();
|
|
300
|
-
const killed = store.runnerKilledIfRequested({
|
|
335
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
301
336
|
executionId: input.executionId,
|
|
302
337
|
terminalAt: new Date(now()).toISOString(),
|
|
303
|
-
});
|
|
338
|
+
}));
|
|
304
339
|
if (killed.status !== "committed")
|
|
305
340
|
return { status: "rejected" };
|
|
306
341
|
settled = true;
|
|
307
342
|
return { status: "completed" };
|
|
308
343
|
}
|
|
309
344
|
catch (error) {
|
|
310
|
-
const failed = store.verdictIfOpen({
|
|
345
|
+
const failed = commitTerminal(() => store.verdictIfOpen({
|
|
311
346
|
executionId: input.executionId,
|
|
312
347
|
verdict: "failed",
|
|
313
348
|
facts: {
|
|
@@ -323,7 +358,7 @@ export async function runProjectionGeneration(input) {
|
|
|
323
358
|
exitCode: null,
|
|
324
359
|
},
|
|
325
360
|
},
|
|
326
|
-
});
|
|
361
|
+
}));
|
|
327
362
|
// Durably completed after the failed verdict; outer catch must not overwrite.
|
|
328
363
|
settled = true;
|
|
329
364
|
if (failed.status !== "committed")
|
|
@@ -354,6 +389,7 @@ export async function runProjectionGeneration(input) {
|
|
|
354
389
|
try {
|
|
355
390
|
const successorOwnership = runnerLock;
|
|
356
391
|
await input.startSuccessor(successorExecutionId, () => {
|
|
392
|
+
pullStopIntent?.(true);
|
|
357
393
|
const transition = store.handoff({
|
|
358
394
|
executionId: input.executionId,
|
|
359
395
|
completionFacts: execution.facts,
|
|
@@ -387,7 +423,7 @@ export async function runProjectionGeneration(input) {
|
|
|
387
423
|
return settleRunnerOutcome(execution);
|
|
388
424
|
}
|
|
389
425
|
catch (error) {
|
|
390
|
-
if (adopted && !settled) {
|
|
426
|
+
if (adopted && !settled && runnerLock) {
|
|
391
427
|
let cleanupError;
|
|
392
428
|
if (handle) {
|
|
393
429
|
try {
|
|
@@ -397,9 +433,10 @@ export async function runProjectionGeneration(input) {
|
|
|
397
433
|
cleanupError = failure;
|
|
398
434
|
}
|
|
399
435
|
}
|
|
436
|
+
pullStopIntent?.(true);
|
|
400
437
|
const live = store.readCurrentGeneration();
|
|
401
438
|
const detail = error instanceof Error ? error.message : String(error);
|
|
402
|
-
store.runnerVerdictIfOpen({
|
|
439
|
+
const failed = commitTerminal(() => store.runnerVerdictIfOpen({
|
|
403
440
|
executionId: input.executionId,
|
|
404
441
|
verdict: "failed",
|
|
405
442
|
facts: {
|
|
@@ -419,14 +456,13 @@ export async function runProjectionGeneration(input) {
|
|
|
419
456
|
},
|
|
420
457
|
},
|
|
421
458
|
terminalAt: new Date(now()).toISOString(),
|
|
422
|
-
});
|
|
459
|
+
}));
|
|
460
|
+
settled = failed.status === "committed";
|
|
423
461
|
}
|
|
424
462
|
throw error;
|
|
425
463
|
}
|
|
426
464
|
finally {
|
|
427
465
|
clearGraceTimer();
|
|
428
|
-
if (intentTimer !== undefined)
|
|
429
|
-
clearInterval(intentTimer);
|
|
430
466
|
try {
|
|
431
467
|
tellObserver?.close();
|
|
432
468
|
}
|
|
@@ -434,10 +470,20 @@ export async function runProjectionGeneration(input) {
|
|
|
434
470
|
// Observer cleanup cannot reopen life or product authority.
|
|
435
471
|
}
|
|
436
472
|
try {
|
|
437
|
-
|
|
473
|
+
await doorbell?.close();
|
|
438
474
|
}
|
|
439
475
|
finally {
|
|
440
|
-
|
|
476
|
+
try {
|
|
477
|
+
runnerLock?.close();
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
store.close();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (wakeAfterLockRelease) {
|
|
484
|
+
void input.wakeAfterInterrupt?.().catch(() => {
|
|
485
|
+
// A later tell retries the ordinary wake path after its durable failure.
|
|
486
|
+
});
|
|
441
487
|
}
|
|
442
488
|
}
|
|
443
489
|
}
|
|
@@ -1,47 +1,12 @@
|
|
|
1
1
|
import { prepareProjectionGenerationExecution } from "./projection-generation-execution.js";
|
|
2
|
-
import {
|
|
2
|
+
import { receiveDetachedLease, } from "../../run-control/index.js";
|
|
3
3
|
import { runProjectionGeneration } from "./projection-generation-runner.js";
|
|
4
|
-
import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./projection-generation-launcher.js";
|
|
5
4
|
import { startProjectionGeneration } from "./projection-generation-launcher.js";
|
|
6
5
|
import { snapshotPendingTells } from "./projection-generation-continuation.js";
|
|
7
6
|
import { persistInterruptWakeFailure, tellProjection } from "../projection-wake.js";
|
|
8
7
|
import { readProjectionIdentity } from "../projection-identity.js";
|
|
9
8
|
import * as path from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
function sendBootstrapMessage(message) {
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
if (!process.send || !process.connected) {
|
|
14
|
-
reject(new Error("projection runner bootstrap channel is unavailable"));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
process.send(message, (error) => error ? reject(error) : resolve());
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function waitForLaunchCommand(executionId, expected) {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
const cleanup = () => {
|
|
23
|
-
process.off("message", onMessage);
|
|
24
|
-
process.off("disconnect", onDisconnect);
|
|
25
|
-
};
|
|
26
|
-
const onDisconnect = () => {
|
|
27
|
-
cleanup();
|
|
28
|
-
reject(new Error("projection runner bootstrap channel disconnected before launch authorization"));
|
|
29
|
-
};
|
|
30
|
-
const onMessage = (value) => {
|
|
31
|
-
const command = value;
|
|
32
|
-
if (command.executionId !== executionId)
|
|
33
|
-
return;
|
|
34
|
-
if (expected === "promote-leash" && command.kind !== "promote-leash" && command.kind !== "launch-rejected")
|
|
35
|
-
return;
|
|
36
|
-
if (expected === "launch-authority" && command.kind !== "launch-committed" && command.kind !== "launch-rejected")
|
|
37
|
-
return;
|
|
38
|
-
cleanup();
|
|
39
|
-
resolve(command);
|
|
40
|
-
};
|
|
41
|
-
process.on("message", onMessage);
|
|
42
|
-
process.on("disconnect", onDisconnect);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
9
|
+
import { projectionRunnerLeasePath } from "./projection-generation-identity.js";
|
|
45
10
|
export async function wakeProjectionAfterInterrupt(projectionDirectory, wake = tellProjection) {
|
|
46
11
|
const pact = readProjectionIdentity(projectionDirectory);
|
|
47
12
|
try {
|
|
@@ -53,30 +18,17 @@ export async function wakeProjectionAfterInterrupt(projectionDirectory, wake = t
|
|
|
53
18
|
}
|
|
54
19
|
}
|
|
55
20
|
export async function runProjectionGenerationRuntime(projectionDirectory, executionId) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
21
|
+
const transfer = await receiveDetachedLease({
|
|
22
|
+
coordinate: executionId,
|
|
23
|
+
leasePath: projectionRunnerLeasePath(projectionDirectory),
|
|
24
|
+
});
|
|
25
|
+
if (transfer.status === "rejected")
|
|
26
|
+
return;
|
|
61
27
|
try {
|
|
62
|
-
transferTarget = acquireProjectionRunnerTransferTarget(projectionDirectory, process.platform, PROJECTION_ADOPTION_TIMEOUT_MS);
|
|
63
|
-
await sendBootstrapMessage({ kind: "leash-inherited", executionId });
|
|
64
|
-
const promotion = await waitForLaunchCommand(executionId, "promote-leash");
|
|
65
|
-
if (promotion.kind === "launch-rejected")
|
|
66
|
-
return;
|
|
67
|
-
runnerLock = transferTarget.promote();
|
|
68
|
-
transferTarget = undefined;
|
|
69
|
-
await sendBootstrapMessage({ kind: "leash-acquired", executionId });
|
|
70
|
-
const command = await waitForLaunchCommand(executionId, "launch-authority");
|
|
71
|
-
if (command.kind === "launch-rejected")
|
|
72
|
-
return;
|
|
73
|
-
process.disconnect?.();
|
|
74
|
-
const ownedLock = runnerLock;
|
|
75
|
-
runnerLock = undefined;
|
|
76
28
|
await runProjectionGeneration({
|
|
77
29
|
projectionDirectory,
|
|
78
30
|
executionId,
|
|
79
|
-
runnerLock:
|
|
31
|
+
runnerLock: transfer.lease,
|
|
80
32
|
prepareExecution: (launch) => prepareProjectionGenerationExecution(projectionDirectory, launch),
|
|
81
33
|
snapshotPendingTells: () => snapshotPendingTells(projectionDirectory),
|
|
82
34
|
startSuccessor: async (successorExecutionId, commitLaunch, launchOwnership) => {
|
|
@@ -90,8 +42,5 @@ export async function runProjectionGenerationRuntime(projectionDirectory, execut
|
|
|
90
42
|
wakeAfterInterrupt: () => wakeProjectionAfterInterrupt(projectionDirectory),
|
|
91
43
|
});
|
|
92
44
|
}
|
|
93
|
-
finally {
|
|
94
|
-
runnerLock?.close();
|
|
95
|
-
transferTarget?.close();
|
|
96
|
-
}
|
|
45
|
+
finally { /* execution owns and closes the transferred lease */ }
|
|
97
46
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { GENERATION_VERDICT_STATES, } from "./model.js";
|
|
2
|
+
function recordOfKind(record, kind) {
|
|
3
|
+
return record.kind === kind ? record : undefined;
|
|
4
|
+
}
|
|
5
|
+
/** Select the newest generation by its Projection ledger sequence. */
|
|
6
|
+
export function selectCurrentGeneration(records) {
|
|
7
|
+
let launch;
|
|
8
|
+
for (const record of records)
|
|
9
|
+
launch = recordOfKind(record, "launch") ?? launch;
|
|
10
|
+
if (!launch)
|
|
11
|
+
return null;
|
|
12
|
+
let adoption;
|
|
13
|
+
let killIntent;
|
|
14
|
+
let interruptIntent;
|
|
15
|
+
let verdict;
|
|
16
|
+
for (const record of records) {
|
|
17
|
+
if (record.seq <= launch.seq || record.executionId !== launch.executionId)
|
|
18
|
+
continue;
|
|
19
|
+
adoption ??= recordOfKind(record, "adoption");
|
|
20
|
+
killIntent ??= recordOfKind(record, "kill-intent");
|
|
21
|
+
interruptIntent ??= recordOfKind(record, "interrupt-intent");
|
|
22
|
+
verdict ??= recordOfKind(record, "verdict");
|
|
23
|
+
}
|
|
24
|
+
return { launch, ...(adoption ? { adoption } : {}), ...(killIntent ? { killIntent } : {}), ...(interruptIntent ? { interruptIntent } : {}), ...(verdict ? { verdict } : {}) };
|
|
25
|
+
}
|
|
26
|
+
function verdictState(record) {
|
|
27
|
+
const state = record?.facts.state;
|
|
28
|
+
return typeof state === "string" && GENERATION_VERDICT_STATES.includes(state)
|
|
29
|
+
? state
|
|
30
|
+
: undefined;
|
|
31
|
+
}
|
|
32
|
+
function phaseForVerdict(state) {
|
|
33
|
+
switch (state) {
|
|
34
|
+
case "completed": return "done";
|
|
35
|
+
case "dismissed": return "dismissed";
|
|
36
|
+
case "interrupted": return "done";
|
|
37
|
+
case "killed": return "killed";
|
|
38
|
+
case "lost-runner": return "lost";
|
|
39
|
+
case "failed":
|
|
40
|
+
case "launch-failed": return "failed";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function deriveProjectionLife(input) {
|
|
44
|
+
const chain = selectCurrentGeneration(input.records);
|
|
45
|
+
if (!chain)
|
|
46
|
+
return { phase: "quiescent" };
|
|
47
|
+
const executionId = chain.launch.executionId;
|
|
48
|
+
if (chain.verdict) {
|
|
49
|
+
const state = verdictState(chain.verdict);
|
|
50
|
+
return state
|
|
51
|
+
? { phase: phaseForVerdict(state), executionId, verdict: state }
|
|
52
|
+
: { phase: "unknown", executionId, diagnostic: "current generation has an invalid verdict" };
|
|
53
|
+
}
|
|
54
|
+
if (!chain.adoption) {
|
|
55
|
+
switch (input.runnerLease) {
|
|
56
|
+
case "held": return { phase: "minting", executionId };
|
|
57
|
+
case "released": return { phase: "lost", executionId, diagnostic: "startup owner died before adoption" };
|
|
58
|
+
case "unknown":
|
|
59
|
+
case undefined: return { phase: "unknown", executionId, diagnostic: "runner lease could not be determined" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
switch (input.runnerLease) {
|
|
63
|
+
case "held": return { phase: "active", executionId };
|
|
64
|
+
case "released": return { phase: "lost", executionId };
|
|
65
|
+
case "unknown":
|
|
66
|
+
case undefined: return { phase: "unknown", executionId, diagnostic: "runner lease could not be determined" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, assertDatabaseOpen,
|
|
1
|
+
import { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, assertDatabaseOpen, closeDatabase, connectionBoundGenerationStore, openMutableDatabase, openReadOnlyDatabase, readValidatedHistory, } from "./database.js";
|
|
2
2
|
import { initializeIdentity as initializeProjectionIdentity, readIdentity as readProjectionIdentity, } from "./identity.js";
|
|
3
3
|
import * as transitions from "./transitions.js";
|
|
4
4
|
export { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, };
|
|
@@ -7,10 +7,12 @@ export class ProjectionGenerationStore {
|
|
|
7
7
|
databasePath;
|
|
8
8
|
readOnly;
|
|
9
9
|
#database;
|
|
10
|
+
#generationStore;
|
|
10
11
|
constructor(databasePath, database, readOnly) {
|
|
11
12
|
this.databasePath = databasePath;
|
|
12
13
|
this.#database = database;
|
|
13
14
|
this.readOnly = readOnly;
|
|
15
|
+
this.#generationStore = connectionBoundGenerationStore(database, readOnly);
|
|
14
16
|
}
|
|
15
17
|
static open(projectionDirectory) {
|
|
16
18
|
const { databasePath, database } = openMutableDatabase(projectionDirectory);
|
|
@@ -21,25 +23,33 @@ export class ProjectionGenerationStore {
|
|
|
21
23
|
return new ProjectionGenerationStore(databasePath, database, true);
|
|
22
24
|
}
|
|
23
25
|
close() {
|
|
24
|
-
|
|
26
|
+
assertDatabaseOpen(this.#database);
|
|
27
|
+
this.#generationStore.rollbackWriteTransaction();
|
|
25
28
|
closeDatabase(this.#database);
|
|
26
29
|
}
|
|
27
30
|
beginWriteTransaction() {
|
|
28
|
-
|
|
31
|
+
assertDatabaseOpen(this.#database);
|
|
32
|
+
this.#generationStore.beginWriteTransaction();
|
|
29
33
|
}
|
|
30
34
|
commitWriteTransaction() {
|
|
31
|
-
|
|
35
|
+
assertDatabaseOpen(this.#database);
|
|
36
|
+
this.#generationStore.commitWriteTransaction();
|
|
32
37
|
}
|
|
33
38
|
rollbackWriteTransaction() {
|
|
34
|
-
|
|
39
|
+
assertDatabaseOpen(this.#database);
|
|
40
|
+
this.#generationStore.rollbackWriteTransaction();
|
|
41
|
+
}
|
|
42
|
+
connectionForRunnerClose() {
|
|
43
|
+
assertDatabaseOpen(this.#database);
|
|
44
|
+
return this.#database;
|
|
35
45
|
}
|
|
36
46
|
readRecords() {
|
|
37
47
|
assertDatabaseOpen(this.#database);
|
|
38
|
-
return readValidatedHistory(this.#database).records;
|
|
48
|
+
return readValidatedHistory(this.#database, this.readOnly).records;
|
|
39
49
|
}
|
|
40
50
|
readCurrentGeneration() {
|
|
41
51
|
assertDatabaseOpen(this.#database);
|
|
42
|
-
return readValidatedHistory(this.#database).current;
|
|
52
|
+
return readValidatedHistory(this.#database, this.readOnly).current;
|
|
43
53
|
}
|
|
44
54
|
initializeIdentity(identity) {
|
|
45
55
|
initializeProjectionIdentity(this.#database, this.databasePath, this.readOnly, identity);
|