@astrosheep/keiyaku 2.9.11 → 2.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -85
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +4 -4
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +14 -10
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/handler.js +4 -5
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +2 -2
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/compose/handler.js +18 -0
- package/build/cli/commands/task/compose/meta.js +10 -0
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +48 -12
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +14 -8
- package/build/cli/parse-flags.js +60 -21
- package/build/cli/parse-metadata.js +10 -4
- package/build/cli/parse-selectors.js +22 -4
- package/build/cli/parse.js +43 -30
- package/build/cli/projection-address.js +7 -7
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +33 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/addressing.js +3 -3
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +46 -33
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +43 -6
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +60 -27
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +19 -14
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +132 -76
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +24 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +8 -8
- package/build/core/projection/projection-kill.js +20 -10
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +48 -29
- package/build/core/projection/projection-wake.js +28 -88
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/claim-evidence.js +1 -1
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +464 -349
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +57 -9
- package/build/core/repository-ledger/write-transaction.js +24 -17
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +328 -0
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +4 -3
- package/build/core/task/settlement-git.js +70 -30
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-git-store.js +1 -0
- package/build/core/task/task-store-repository.js +2 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +15 -2
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +2 -2
- package/skills/keiyaku-akuma/SKILL.md +27 -21
- package/skills/keiyaku-task/SKILL.md +61 -5
- package/skills/keiyaku-workflow/SKILL.md +81 -26
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -382
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -1,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,7 +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({
|
|
182
|
+
executionId: input.executionId,
|
|
183
|
+
terminalAt: new Date(now()).toISOString(),
|
|
184
|
+
}));
|
|
116
185
|
if (killed.status !== "committed")
|
|
117
186
|
return { status: "rejected" };
|
|
118
187
|
settled = true;
|
|
@@ -126,7 +195,10 @@ export async function runProjectionGeneration(input) {
|
|
|
126
195
|
}
|
|
127
196
|
if (current.killIntent) {
|
|
128
197
|
await prepared.dispose();
|
|
129
|
-
const killed = store.runnerKilledIfRequested({
|
|
198
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
199
|
+
executionId: input.executionId,
|
|
200
|
+
terminalAt: new Date(now()).toISOString(),
|
|
201
|
+
}));
|
|
130
202
|
if (killed.status !== "committed")
|
|
131
203
|
return { status: "rejected" };
|
|
132
204
|
settled = true;
|
|
@@ -134,68 +206,35 @@ export async function runProjectionGeneration(input) {
|
|
|
134
206
|
}
|
|
135
207
|
handle = prepared.start();
|
|
136
208
|
const settleInterrupted = (facts = {}) => {
|
|
137
|
-
|
|
209
|
+
pullStopIntent?.(true);
|
|
210
|
+
const transition = commitTerminal(() => store.runnerInterruptedIfRequested({
|
|
138
211
|
executionId: input.executionId,
|
|
139
212
|
facts,
|
|
140
213
|
terminalAt: new Date(now()).toISOString(),
|
|
141
|
-
});
|
|
214
|
+
}));
|
|
142
215
|
if (transition.status !== "committed")
|
|
143
216
|
return { status: "rejected" };
|
|
144
217
|
settled = true;
|
|
145
|
-
|
|
146
|
-
runnerLock = undefined;
|
|
147
|
-
void input.wakeAfterInterrupt?.().catch(() => {
|
|
148
|
-
// The ordinary wake path persists its typed failure. A later tell can retry.
|
|
149
|
-
});
|
|
218
|
+
wakeAfterLockRelease = true;
|
|
150
219
|
return { status: "completed" };
|
|
151
220
|
};
|
|
152
221
|
const settleRunnerOutcome = (execution) => {
|
|
153
|
-
|
|
222
|
+
pullStopIntent?.(true);
|
|
223
|
+
const transition = commitTerminal(() => store.runnerVerdictIfOpen({
|
|
154
224
|
executionId: input.executionId,
|
|
155
225
|
verdict: execution.verdict,
|
|
156
226
|
facts: execution.facts,
|
|
157
227
|
terminalAt: new Date(now()).toISOString(),
|
|
158
|
-
});
|
|
228
|
+
}));
|
|
159
229
|
if (transition.status !== "committed")
|
|
160
230
|
return { status: "rejected" };
|
|
161
231
|
settled = true;
|
|
162
232
|
if (transition.records[0]?.facts.state === "interrupted") {
|
|
163
|
-
|
|
164
|
-
runnerLock = undefined;
|
|
165
|
-
void input.wakeAfterInterrupt?.().catch(() => {
|
|
166
|
-
// The ordinary wake path persists its typed failure. A later observer retries.
|
|
167
|
-
});
|
|
233
|
+
wakeAfterLockRelease = true;
|
|
168
234
|
}
|
|
169
235
|
return { status: "completed" };
|
|
170
236
|
};
|
|
171
|
-
|
|
172
|
-
let forceAbortInvoked = false;
|
|
173
|
-
let sawKillIntent = false;
|
|
174
|
-
let sawInterruptIntent = false;
|
|
175
|
-
const observeStopBoundary = () => {
|
|
176
|
-
try {
|
|
177
|
-
if (!tellObserver.hasDataVersionChanged())
|
|
178
|
-
return;
|
|
179
|
-
const live = store.readCurrentGeneration();
|
|
180
|
-
if (live?.launch.executionId !== input.executionId)
|
|
181
|
-
return;
|
|
182
|
-
if (live.verdict)
|
|
183
|
-
return;
|
|
184
|
-
sawKillIntent = live.killIntent !== undefined;
|
|
185
|
-
sawInterruptIntent = live.interruptIntent !== undefined;
|
|
186
|
-
if (!sawKillIntent && !sawInterruptIntent)
|
|
187
|
-
return;
|
|
188
|
-
if (gracefulAbortInvoked)
|
|
189
|
-
return;
|
|
190
|
-
gracefulAbortInvoked = true;
|
|
191
|
-
void handle.abort("graceful").catch(() => {
|
|
192
|
-
// Abort observation failures fail closed for this boundary.
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
// Observation failures fail closed and must not change provider outcome.
|
|
197
|
-
}
|
|
198
|
-
};
|
|
237
|
+
const observeStopBoundary = () => { pullStopIntent?.(); };
|
|
199
238
|
// Mutable box so async settlement is readable without control-flow narrowing traps.
|
|
200
239
|
const outcomeBox = { status: "pending" };
|
|
201
240
|
let resolveActivity;
|
|
@@ -207,6 +246,7 @@ export async function runProjectionGeneration(input) {
|
|
|
207
246
|
resolveActivity = undefined;
|
|
208
247
|
resolve?.();
|
|
209
248
|
};
|
|
249
|
+
notifyMainWait = bumpActivity;
|
|
210
250
|
const outcomeWatch = handle.outcome.then((execution) => {
|
|
211
251
|
outcomeBox.status = "fulfilled";
|
|
212
252
|
outcomeBox.execution = execution;
|
|
@@ -220,6 +260,7 @@ export async function runProjectionGeneration(input) {
|
|
|
220
260
|
});
|
|
221
261
|
// Always rejection-observe so abandon paths never surface unhandled rejections.
|
|
222
262
|
observeAbandoned(outcomeWatch);
|
|
263
|
+
const eventDrainState = { closed: false };
|
|
223
264
|
const eventDrain = (async () => {
|
|
224
265
|
try {
|
|
225
266
|
for await (const _event of handle.events) {
|
|
@@ -230,20 +271,20 @@ export async function runProjectionGeneration(input) {
|
|
|
230
271
|
}
|
|
231
272
|
}
|
|
232
273
|
catch {
|
|
233
|
-
//
|
|
274
|
+
// Public event delivery is observation-only; outcome owns terminal truth.
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
eventDrainState.closed = true;
|
|
278
|
+
bumpActivity();
|
|
234
279
|
}
|
|
235
|
-
bumpActivity();
|
|
236
280
|
})();
|
|
237
281
|
observeAbandoned(eventDrain);
|
|
238
|
-
// A provider may be silently executing when an operator interrupts it.
|
|
239
|
-
// The durable observer is the sole stop-intent authority in that interval.
|
|
240
|
-
intentTimer = setInterval(() => {
|
|
241
|
-
observeStopBoundary();
|
|
242
|
-
bumpActivity();
|
|
243
|
-
}, 100);
|
|
244
|
-
intentTimer.unref?.();
|
|
245
282
|
while (outcomeBox.status === "pending" && !sawKillIntent && !sawInterruptIntent) {
|
|
246
|
-
|
|
283
|
+
const wakeSources = [outcomeWatch, activityChanged()];
|
|
284
|
+
if (!eventDrainState.closed)
|
|
285
|
+
wakeSources.push(eventDrain);
|
|
286
|
+
await Promise.race(wakeSources);
|
|
287
|
+
observeStopBoundary();
|
|
247
288
|
}
|
|
248
289
|
if (sawKillIntent || sawInterruptIntent) {
|
|
249
290
|
// Grace race: outcome settlement inside the window is stop proof.
|
|
@@ -275,10 +316,11 @@ export async function runProjectionGeneration(input) {
|
|
|
275
316
|
throw killOutcome.error;
|
|
276
317
|
throw new Error("interrupt outcome did not establish provider stop");
|
|
277
318
|
}
|
|
278
|
-
const killed = store.runnerKilledIfRequested({
|
|
319
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
279
320
|
executionId: input.executionId,
|
|
280
321
|
...(facts && Object.keys(facts).length > 0 ? { facts } : {}),
|
|
281
|
-
|
|
322
|
+
terminalAt: new Date(now()).toISOString(),
|
|
323
|
+
}));
|
|
282
324
|
if (killed.status !== "committed")
|
|
283
325
|
return { status: "rejected" };
|
|
284
326
|
settled = true;
|
|
@@ -290,14 +332,17 @@ export async function runProjectionGeneration(input) {
|
|
|
290
332
|
await handle.abort("force");
|
|
291
333
|
if (sawInterruptIntent)
|
|
292
334
|
return settleInterrupted();
|
|
293
|
-
const killed = store.runnerKilledIfRequested({
|
|
335
|
+
const killed = commitTerminal(() => store.runnerKilledIfRequested({
|
|
336
|
+
executionId: input.executionId,
|
|
337
|
+
terminalAt: new Date(now()).toISOString(),
|
|
338
|
+
}));
|
|
294
339
|
if (killed.status !== "committed")
|
|
295
340
|
return { status: "rejected" };
|
|
296
341
|
settled = true;
|
|
297
342
|
return { status: "completed" };
|
|
298
343
|
}
|
|
299
344
|
catch (error) {
|
|
300
|
-
const failed = store.verdictIfOpen({
|
|
345
|
+
const failed = commitTerminal(() => store.verdictIfOpen({
|
|
301
346
|
executionId: input.executionId,
|
|
302
347
|
verdict: "failed",
|
|
303
348
|
facts: {
|
|
@@ -313,7 +358,7 @@ export async function runProjectionGeneration(input) {
|
|
|
313
358
|
exitCode: null,
|
|
314
359
|
},
|
|
315
360
|
},
|
|
316
|
-
});
|
|
361
|
+
}));
|
|
317
362
|
// Durably completed after the failed verdict; outer catch must not overwrite.
|
|
318
363
|
settled = true;
|
|
319
364
|
if (failed.status !== "committed")
|
|
@@ -344,6 +389,7 @@ export async function runProjectionGeneration(input) {
|
|
|
344
389
|
try {
|
|
345
390
|
const successorOwnership = runnerLock;
|
|
346
391
|
await input.startSuccessor(successorExecutionId, () => {
|
|
392
|
+
pullStopIntent?.(true);
|
|
347
393
|
const transition = store.handoff({
|
|
348
394
|
executionId: input.executionId,
|
|
349
395
|
completionFacts: execution.facts,
|
|
@@ -377,7 +423,7 @@ export async function runProjectionGeneration(input) {
|
|
|
377
423
|
return settleRunnerOutcome(execution);
|
|
378
424
|
}
|
|
379
425
|
catch (error) {
|
|
380
|
-
if (adopted && !settled) {
|
|
426
|
+
if (adopted && !settled && runnerLock) {
|
|
381
427
|
let cleanupError;
|
|
382
428
|
if (handle) {
|
|
383
429
|
try {
|
|
@@ -387,9 +433,10 @@ export async function runProjectionGeneration(input) {
|
|
|
387
433
|
cleanupError = failure;
|
|
388
434
|
}
|
|
389
435
|
}
|
|
436
|
+
pullStopIntent?.(true);
|
|
390
437
|
const live = store.readCurrentGeneration();
|
|
391
438
|
const detail = error instanceof Error ? error.message : String(error);
|
|
392
|
-
store.runnerVerdictIfOpen({
|
|
439
|
+
const failed = commitTerminal(() => store.runnerVerdictIfOpen({
|
|
393
440
|
executionId: input.executionId,
|
|
394
441
|
verdict: "failed",
|
|
395
442
|
facts: {
|
|
@@ -409,14 +456,13 @@ export async function runProjectionGeneration(input) {
|
|
|
409
456
|
},
|
|
410
457
|
},
|
|
411
458
|
terminalAt: new Date(now()).toISOString(),
|
|
412
|
-
});
|
|
459
|
+
}));
|
|
460
|
+
settled = failed.status === "committed";
|
|
413
461
|
}
|
|
414
462
|
throw error;
|
|
415
463
|
}
|
|
416
464
|
finally {
|
|
417
465
|
clearGraceTimer();
|
|
418
|
-
if (intentTimer !== undefined)
|
|
419
|
-
clearInterval(intentTimer);
|
|
420
466
|
try {
|
|
421
467
|
tellObserver?.close();
|
|
422
468
|
}
|
|
@@ -424,10 +470,20 @@ export async function runProjectionGeneration(input) {
|
|
|
424
470
|
// Observer cleanup cannot reopen life or product authority.
|
|
425
471
|
}
|
|
426
472
|
try {
|
|
427
|
-
|
|
473
|
+
await doorbell?.close();
|
|
428
474
|
}
|
|
429
475
|
finally {
|
|
430
|
-
|
|
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
|
+
});
|
|
431
487
|
}
|
|
432
488
|
}
|
|
433
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);
|