@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
|
@@ -5,7 +5,6 @@ import { isProjectionId, projectionRoot, projectionRootAtCoordinate } from "./pr
|
|
|
5
5
|
import { projectionAliasRootAtCoordinate, readProjectionAliasAtRoot, } from "./projection-alias.js";
|
|
6
6
|
import { resolveProjectionCoordinate } from "./projection-coordinate.js";
|
|
7
7
|
import { readProjectionIdentity } from "./projection-identity.js";
|
|
8
|
-
import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
|
|
9
8
|
import { observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
|
|
10
9
|
import { worktreeContainsCoordinate } from "../worktree-path.js";
|
|
11
10
|
import { readProjectionRow, readProjectionSummary } from "./projection-status-observation.js";
|
|
@@ -75,6 +74,104 @@ export function prioritizeProjectionStatusRows(rows) {
|
|
|
75
74
|
const total = Object.values(byState).reduce((sum, count) => sum + count, 0);
|
|
76
75
|
return total > 0 ? { rows: visible, omitted: { total, byState } } : { rows: visible };
|
|
77
76
|
}
|
|
77
|
+
const TRIAGE_UNKNOWN_LIMIT = 8;
|
|
78
|
+
const TRIAGE_ACTIVE_LIMIT = 8;
|
|
79
|
+
const TRIAGE_RECENT_TERMINAL_LIMIT = 3;
|
|
80
|
+
function isTerminalVerdictState(state) {
|
|
81
|
+
return state === "failed"
|
|
82
|
+
|| state === "dead"
|
|
83
|
+
|| state === "done"
|
|
84
|
+
|| state === "killed"
|
|
85
|
+
|| state === "dismissed"
|
|
86
|
+
|| state === "incompatible";
|
|
87
|
+
}
|
|
88
|
+
function isUnknownRow(row) {
|
|
89
|
+
return row.state === "lost" || row.state === "unknown";
|
|
90
|
+
}
|
|
91
|
+
function isStartupFailedRow(row) {
|
|
92
|
+
return row.state === "startup-timeout";
|
|
93
|
+
}
|
|
94
|
+
function isActiveRow(row) {
|
|
95
|
+
return row.state === "minting" || row.state === "out";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Main status is a bounded lifecycle summary, not a second roster. Keep
|
|
99
|
+
* category limits here so JSON and the human renderer receive the same rows.
|
|
100
|
+
*/
|
|
101
|
+
export function triageProjectionStatusRows(rows) {
|
|
102
|
+
if (rows.length === 0)
|
|
103
|
+
return { rows: [] };
|
|
104
|
+
const ordered = [...rows].sort(compareProjectionStatusRows);
|
|
105
|
+
const startupFailedRows = ordered.filter(isStartupFailedRow);
|
|
106
|
+
const unknownRows = ordered.filter(isUnknownRow);
|
|
107
|
+
const activeRows = ordered.filter(isActiveRow);
|
|
108
|
+
const terminalRows = ordered.filter((row) => isTerminalVerdictState(row.state));
|
|
109
|
+
const boundedObservationFailures = ordered
|
|
110
|
+
.filter((row) => isStartupFailedRow(row) || isUnknownRow(row))
|
|
111
|
+
.slice(0, TRIAGE_UNKNOWN_LIMIT);
|
|
112
|
+
const startupFailed = boundedObservationFailures.filter(isStartupFailedRow);
|
|
113
|
+
const unknown = boundedObservationFailures.filter(isUnknownRow);
|
|
114
|
+
const selected = new Set(boundedObservationFailures.map((row) => row.id));
|
|
115
|
+
const active = ordered.filter((row) => !selected.has(row.id) && isActiveRow(row)).slice(0, TRIAGE_ACTIVE_LIMIT);
|
|
116
|
+
active.forEach((row) => selected.add(row.id));
|
|
117
|
+
const recentTerminal = ordered
|
|
118
|
+
.filter((row) => !selected.has(row.id) && isTerminalVerdictState(row.state))
|
|
119
|
+
.slice(0, TRIAGE_RECENT_TERMINAL_LIMIT);
|
|
120
|
+
recentTerminal.forEach((row) => selected.add(row.id));
|
|
121
|
+
const omittedRows = ordered.filter((row) => !selected.has(row.id));
|
|
122
|
+
const byState = {
|
|
123
|
+
failed: 0, dead: 0, done: 0, killed: 0, dismissed: 0, incompatible: 0,
|
|
124
|
+
};
|
|
125
|
+
for (const row of omittedRows) {
|
|
126
|
+
if (isOmissibleProjectionStatusState(row.state))
|
|
127
|
+
byState[row.state] += 1;
|
|
128
|
+
}
|
|
129
|
+
const omittedTerminalTotal = Object.values(byState).reduce((sum, count) => sum + count, 0);
|
|
130
|
+
return {
|
|
131
|
+
rows: [...startupFailed, ...unknown, ...active, ...recentTerminal],
|
|
132
|
+
triage: {
|
|
133
|
+
startupFailed,
|
|
134
|
+
unknown,
|
|
135
|
+
active,
|
|
136
|
+
recentTerminal,
|
|
137
|
+
totals: {
|
|
138
|
+
startupFailed: startupFailedRows.length,
|
|
139
|
+
unknown: unknownRows.length,
|
|
140
|
+
active: activeRows.length,
|
|
141
|
+
terminal: terminalRows.length,
|
|
142
|
+
},
|
|
143
|
+
overflow: omittedRows.length,
|
|
144
|
+
},
|
|
145
|
+
...(omittedTerminalTotal > 0 ? { omitted: { total: omittedTerminalTotal, byState } } : {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Terminal history is pageable; active observations stay complete. */
|
|
149
|
+
export function paginateProjectionStatusRoster(board, input) {
|
|
150
|
+
const limit = input.limit ?? 20;
|
|
151
|
+
const offset = input.offset ?? 0;
|
|
152
|
+
const nonterminal = board.rows.filter((row) => !isTerminalVerdictState(row.state));
|
|
153
|
+
const terminal = board.rows.filter((row) => isTerminalVerdictState(row.state));
|
|
154
|
+
const selectedTerminal = terminal.slice(offset, offset + limit);
|
|
155
|
+
const omittedRows = [...terminal.slice(0, offset), ...terminal.slice(offset + limit)];
|
|
156
|
+
const byState = {
|
|
157
|
+
failed: 0, dead: 0, done: 0, killed: 0, dismissed: 0, incompatible: 0,
|
|
158
|
+
};
|
|
159
|
+
for (const row of omittedRows) {
|
|
160
|
+
if (isOmissibleProjectionStatusState(row.state))
|
|
161
|
+
byState[row.state] += 1;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
rows: [...nonterminal, ...selectedTerminal],
|
|
165
|
+
...(omittedRows.length > 0 ? { omitted: { total: omittedRows.length, byState } } : {}),
|
|
166
|
+
pagination: {
|
|
167
|
+
limit,
|
|
168
|
+
offset,
|
|
169
|
+
returned: selectedTerminal.length,
|
|
170
|
+
total: terminal.length,
|
|
171
|
+
hasMore: offset + selectedTerminal.length < terminal.length,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
78
175
|
export function requiresProjectionStatusDetails(state) {
|
|
79
176
|
return !isOmissibleProjectionStatusState(state);
|
|
80
177
|
}
|
|
@@ -161,7 +258,29 @@ export async function readOperationalProjectionStatusBoard(cwd, nowMs) {
|
|
|
161
258
|
const rows = summaries.map((summary) => readProjectionRow(summary, nowMs, requiresProjectionStatusDetails(summary.state)));
|
|
162
259
|
// A published root is an observable surface even when it currently has no
|
|
163
260
|
// rows. The caller must distinguish that fact from an unavailable root.
|
|
164
|
-
return
|
|
261
|
+
return triageProjectionStatusRows(rows);
|
|
262
|
+
}
|
|
263
|
+
/** Full roster read. The main board applies triage separately; this surface never caps stored projections. */
|
|
264
|
+
export async function readProjectionRosterStatusBoard(cwd, nowMs) {
|
|
265
|
+
const coordinate = await resolveProjectionCoordinate(cwd);
|
|
266
|
+
const topology = readProjectionRootTopology(coordinate);
|
|
267
|
+
if (!topology)
|
|
268
|
+
return null;
|
|
269
|
+
const aliases = await readProjectionStatusAliases(projectionAliasRootAtCoordinate(coordinate));
|
|
270
|
+
const summaries = [];
|
|
271
|
+
for (const akuma of topology.akumas) {
|
|
272
|
+
if (akuma.startsWith("."))
|
|
273
|
+
continue;
|
|
274
|
+
const scoped = readAkumaProjectionStatusSummaries(topology.root, akuma, aliases, nowMs);
|
|
275
|
+
if (scoped === null)
|
|
276
|
+
return null;
|
|
277
|
+
summaries.push(...scoped);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
rows: summaries
|
|
281
|
+
.map((summary) => readProjectionRow(summary, nowMs, requiresProjectionStatusDetails(summary.state)))
|
|
282
|
+
.sort(compareProjectionStatusRows),
|
|
283
|
+
};
|
|
165
284
|
}
|
|
166
285
|
export async function readExactProjectionStatus(cwd, nowMs, projectionId) {
|
|
167
286
|
if (!isProjectionId(projectionId))
|
|
@@ -191,6 +310,36 @@ export async function readExactProjectionStatus(cwd, nowMs, projectionId) {
|
|
|
191
310
|
const aliases = await readProjectionStatusAliases(projectionAliasRootAtCoordinate(coordinate));
|
|
192
311
|
return readProjectionRow(readProjectionSummary(dir, projectionId, aliases.get(projectionId), nowMs), nowMs, true);
|
|
193
312
|
}
|
|
313
|
+
/** Shared bounded selector for human and structured status consumers. */
|
|
314
|
+
export async function selectProjectionStatus(cwd, nowMs, input) {
|
|
315
|
+
if (input.kind === "operational")
|
|
316
|
+
return { input, board: await readOperationalProjectionStatusBoard(cwd, nowMs) };
|
|
317
|
+
const coordinate = await resolveProjectionCoordinate(cwd);
|
|
318
|
+
const canonicalId = isProjectionId(input.token)
|
|
319
|
+
? input.token
|
|
320
|
+
: await readProjectionAliasAtRoot(projectionAliasRootAtCoordinate(coordinate), input.token);
|
|
321
|
+
if (!canonicalId)
|
|
322
|
+
return { input, board: null };
|
|
323
|
+
const row = await readExactProjectionStatus(cwd, nowMs, canonicalId);
|
|
324
|
+
if (!row)
|
|
325
|
+
return { input, board: null };
|
|
326
|
+
const aliasMovedTo = input.token === canonicalId
|
|
327
|
+
? undefined
|
|
328
|
+
: await readProjectionAliasAtRoot(projectionAliasRootAtCoordinate(coordinate), input.token);
|
|
329
|
+
return {
|
|
330
|
+
input,
|
|
331
|
+
board: {
|
|
332
|
+
rows: [{
|
|
333
|
+
...row,
|
|
334
|
+
addressedBy: {
|
|
335
|
+
token: input.token,
|
|
336
|
+
kind: input.token === canonicalId ? "canonical" : "alias",
|
|
337
|
+
...(aliasMovedTo && aliasMovedTo !== canonicalId ? { aliasMovedTo } : {}),
|
|
338
|
+
},
|
|
339
|
+
}],
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
194
343
|
export async function readAkumaNonterminalProjectionStatus(cwd, nowMs, akuma) {
|
|
195
344
|
if (!isProjectionId(`${akuma}/00000000`))
|
|
196
345
|
return null;
|
|
@@ -278,14 +427,12 @@ export async function hasStrandableProjectionGeneration(input) {
|
|
|
278
427
|
}
|
|
279
428
|
const observation = observeProjectionLifeSnapshot(projectionDir, {
|
|
280
429
|
nowMs: input.nowMs,
|
|
281
|
-
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
282
430
|
});
|
|
283
431
|
if (observation.compatibility !== "current")
|
|
284
432
|
continue;
|
|
285
433
|
const phase = observation.life.phase;
|
|
286
434
|
if (phase === "active"
|
|
287
435
|
|| phase === "minting"
|
|
288
|
-
|| phase === "startup-timeout"
|
|
289
436
|
|| phase === "unknown") {
|
|
290
437
|
return true;
|
|
291
438
|
}
|
|
@@ -6,9 +6,7 @@ import { resolveProjectionCoordinate } from "./projection-coordinate.js";
|
|
|
6
6
|
import { readProjectionIdentity } from "./projection-identity.js";
|
|
7
7
|
import { RESPONSE_ARTIFACT_ID_RE } from "../response-artifact-id.js";
|
|
8
8
|
import { extractTerminalTimestamp } from "./generation/model.js";
|
|
9
|
-
import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./generation/projection-generation-launcher.js";
|
|
10
9
|
import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
|
|
11
|
-
import { deriveProjectionLife } from "./projection-life-protocol.js";
|
|
12
10
|
import { readProjectionGenerationContinuationSnapshot, } from "./generation/projection-generation-continuation.js";
|
|
13
11
|
import { projectTerminalFailure, } from "./projection-terminal-failure.js";
|
|
14
12
|
import { invalidActivityEventDiagnostic, selectValidStoredAgentEvents, } from "../stored-agent-event.js";
|
|
@@ -179,26 +177,16 @@ export async function waitForProjection(input) {
|
|
|
179
177
|
const observedAtMs = now();
|
|
180
178
|
const continuation = readContinuationSnapshot(input.projectionDirectory, 10);
|
|
181
179
|
const current = continuation.current;
|
|
182
|
-
|
|
183
|
-
records: continuation.records,
|
|
180
|
+
const observation = observeProjectionLifeSnapshot(input.projectionDirectory, {
|
|
184
181
|
nowMs: observedAtMs,
|
|
185
|
-
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
186
182
|
});
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
nowMs: observedAtMs,
|
|
190
|
-
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
191
|
-
});
|
|
192
|
-
if (observation.compatibility === "incompatible") {
|
|
193
|
-
life = { phase: "unknown", diagnostic: observation.diagnostic };
|
|
194
|
-
}
|
|
195
|
-
else if ((observation.records?.at(-1)?.seq ?? 0) !== continuation.generationSequence) {
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
life = observation.life;
|
|
200
|
-
}
|
|
183
|
+
if (observation.compatibility === "incompatible") {
|
|
184
|
+
return { state: "unknown", diagnostic: observation.diagnostic };
|
|
201
185
|
}
|
|
186
|
+
if ((observation.records?.at(-1)?.seq ?? 0) !== continuation.generationSequence) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const life = observation.life;
|
|
202
190
|
if (!wakeAttempted
|
|
203
191
|
&& current
|
|
204
192
|
&& life.phase !== "active"
|
|
@@ -225,7 +213,10 @@ export async function waitForProjection(input) {
|
|
|
225
213
|
const terminalAtMs = verdict === undefined ? undefined : Date.parse(verdict.at);
|
|
226
214
|
const terminalWithinDeadline = terminal && (deadline === undefined
|
|
227
215
|
|| (typeof terminalAtMs === "number" && Number.isFinite(terminalAtMs) && terminalAtMs < deadline));
|
|
228
|
-
|
|
216
|
+
const runnerDiedWithoutVerdict = verdict === undefined && life.phase === "lost";
|
|
217
|
+
if (terminalWithinDeadline
|
|
218
|
+
|| runnerDiedWithoutVerdict
|
|
219
|
+
|| (deadline !== undefined && observedAtMs >= deadline)) {
|
|
229
220
|
const revalidated = readContinuationSnapshot(input.projectionDirectory, 10);
|
|
230
221
|
if (revalidated.generationSequence !== continuation.generationSequence)
|
|
231
222
|
continue;
|
|
@@ -253,6 +244,9 @@ export async function waitForProjection(input) {
|
|
|
253
244
|
...(text ? { outcomeText: text } : {}),
|
|
254
245
|
};
|
|
255
246
|
}
|
|
247
|
+
if (runnerDiedWithoutVerdict) {
|
|
248
|
+
return { state: "lost", pact, snapshot };
|
|
249
|
+
}
|
|
256
250
|
return {
|
|
257
251
|
state: "timeout",
|
|
258
252
|
pact,
|
|
@@ -276,7 +270,9 @@ export async function waitForProjectionSet(input) {
|
|
|
276
270
|
if (input.signal?.aborted)
|
|
277
271
|
abort();
|
|
278
272
|
input.signal?.addEventListener("abort", abort, { once: true });
|
|
279
|
-
|
|
273
|
+
// Admission order is provenance. Presentation applies its own operational
|
|
274
|
+
// triage ordering after this fixed set has been observed.
|
|
275
|
+
const members = [...input.projections];
|
|
280
276
|
const now = input.now ?? Date.now;
|
|
281
277
|
const deadlineMs = input.timeoutMs === undefined ? undefined : now() + input.timeoutMs;
|
|
282
278
|
try {
|
|
@@ -285,7 +281,13 @@ export async function waitForProjectionSet(input) {
|
|
|
285
281
|
...(deadlineMs === undefined ? {} : { deadlineMs }),
|
|
286
282
|
now,
|
|
287
283
|
signal: controller.signal,
|
|
288
|
-
}).then((result) => ({ index,
|
|
284
|
+
}).then((result) => ({ index, member, result }), (error) => ({ index, member, error })));
|
|
285
|
+
const memberResult = (member, result) => ({
|
|
286
|
+
id: member.id,
|
|
287
|
+
...(member.inputToken ? { inputToken: member.inputToken } : {}),
|
|
288
|
+
...(member.alias ? { alias: member.alias } : {}),
|
|
289
|
+
result,
|
|
290
|
+
});
|
|
289
291
|
if (input.selection === "all") {
|
|
290
292
|
const settled = await Promise.all(waits);
|
|
291
293
|
for (const item of settled) {
|
|
@@ -294,23 +296,40 @@ export async function waitForProjectionSet(input) {
|
|
|
294
296
|
}
|
|
295
297
|
return {
|
|
296
298
|
selection: "all",
|
|
297
|
-
results: settled.map((item) =>
|
|
299
|
+
results: settled.map((item) => {
|
|
300
|
+
const value = item;
|
|
301
|
+
return memberResult(value.member, value.result);
|
|
302
|
+
}),
|
|
298
303
|
};
|
|
299
304
|
}
|
|
300
305
|
const pending = new Set(waits.keys());
|
|
301
|
-
const observed =
|
|
306
|
+
const observed = new Map();
|
|
302
307
|
while (pending.size > 0) {
|
|
303
308
|
const next = await Promise.race([...pending].map((index) => waits[index]));
|
|
304
309
|
pending.delete(next.index);
|
|
305
310
|
if ("error" in next)
|
|
306
311
|
throw next.error;
|
|
307
312
|
if (next.result.state === "terminal") {
|
|
313
|
+
observed.set(next.index, next.result);
|
|
308
314
|
controller.abort();
|
|
309
|
-
|
|
315
|
+
// A winner ends the waiting condition, not the admitted read set. Let
|
|
316
|
+
// in-flight readers unwind, then take one immediate read-only snapshot
|
|
317
|
+
// of every member under a shared cutoff.
|
|
318
|
+
await Promise.all(waits);
|
|
319
|
+
const snapshotDeadlineMs = now();
|
|
320
|
+
const snapshots = await Promise.all(members.map(async (member, index) => memberResult(member, observed.get(index) ?? await waitForProjection({
|
|
321
|
+
projectionDirectory: member.projectionDirectory,
|
|
322
|
+
deadlineMs: snapshotDeadlineMs,
|
|
323
|
+
now,
|
|
324
|
+
}))));
|
|
325
|
+
return { selection: "any", selectedId: next.member.id, results: snapshots };
|
|
310
326
|
}
|
|
311
|
-
observed.
|
|
327
|
+
observed.set(next.index, next.result);
|
|
312
328
|
}
|
|
313
|
-
return {
|
|
329
|
+
return {
|
|
330
|
+
selection: "any",
|
|
331
|
+
results: members.map((member, index) => memberResult(member, observed.get(index))),
|
|
332
|
+
};
|
|
314
333
|
}
|
|
315
334
|
finally {
|
|
316
335
|
input.signal?.removeEventListener("abort", abort);
|
|
@@ -4,11 +4,13 @@ import { isRecoverableSessionEnvelope } from "../../agents/harness/index.js";
|
|
|
4
4
|
import { ProjectionStateError } from "../atomic-publish.js";
|
|
5
5
|
import { applyContinuationEffort, readProjectionGenerationContinuationSnapshot, } from "./generation/projection-generation-continuation.js";
|
|
6
6
|
import { createProjectionExecutionId } from "./generation/projection-generation-identity.js";
|
|
7
|
-
import {
|
|
7
|
+
import { startProjectionGeneration } from "./generation/projection-generation-launcher.js";
|
|
8
8
|
import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, } from "./generation/store.js";
|
|
9
9
|
import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
|
|
10
|
-
import {
|
|
10
|
+
import { acquireRunnerLease, acquireRunnerLostProof, RunnerLeaseUnavailableError, } from "../run-control/runner-lease.js";
|
|
11
|
+
import { projectionRunnerLeasePath } from "./generation/projection-generation-identity.js";
|
|
11
12
|
import { setTimeout as delay } from "node:timers/promises";
|
|
13
|
+
const PROJECTION_WAKE_POLL_MS = 20;
|
|
12
14
|
function incompatibleProjectionError(projectionId) {
|
|
13
15
|
const subject = projectionId ? `Projection '${projectionId}'` : "Projection";
|
|
14
16
|
const statusCoordinate = projectionId ?? "<projection-id>";
|
|
@@ -21,7 +23,6 @@ function incompatibleProjectionError(projectionId) {
|
|
|
21
23
|
export function assertProjectionTellable(projectionDirectory) {
|
|
22
24
|
const observation = observeProjectionLifeSnapshot(projectionDirectory, {
|
|
23
25
|
nowMs: Date.now(),
|
|
24
|
-
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
25
26
|
});
|
|
26
27
|
if (observation.compatibility === "incompatible")
|
|
27
28
|
throw incompatibleProjectionError();
|
|
@@ -165,12 +166,10 @@ export function persistInterruptWakeFailure(projectionDirectory, error, nowMs =
|
|
|
165
166
|
export async function tellProjection(projectionDirectory, projectionId, effort, dependencies = {}) {
|
|
166
167
|
const startGeneration = dependencies.startGeneration ?? startProjectionGeneration;
|
|
167
168
|
const sleep = dependencies.sleep ?? ((milliseconds) => delay(milliseconds));
|
|
168
|
-
const observationStartedAt = Date.now();
|
|
169
169
|
for (;;) {
|
|
170
170
|
const nowMs = Date.now();
|
|
171
171
|
const observation = observeProjectionLifeSnapshot(projectionDirectory, {
|
|
172
172
|
nowMs,
|
|
173
|
-
startupTimeoutMs: PROJECTION_ADOPTION_TIMEOUT_MS,
|
|
174
173
|
});
|
|
175
174
|
if (observation.compatibility === "incompatible") {
|
|
176
175
|
throw incompatibleProjectionError(projectionId);
|
|
@@ -179,10 +178,7 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
|
|
|
179
178
|
if (life.phase === "active")
|
|
180
179
|
return "active";
|
|
181
180
|
if (life.phase === "minting" || life.phase === "quiescent") {
|
|
182
|
-
|
|
183
|
-
throw sessionWakeFailure(projectionId, new Error("projection has no generation continuation"));
|
|
184
|
-
}
|
|
185
|
-
await sleep(PROJECTION_ADOPTION_POLL_MS);
|
|
181
|
+
await sleep(PROJECTION_WAKE_POLL_MS);
|
|
186
182
|
continue;
|
|
187
183
|
}
|
|
188
184
|
if (life.phase === "unknown") {
|
|
@@ -200,7 +196,7 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
|
|
|
200
196
|
const successorExecutionId = createProjectionExecutionId(nowMs);
|
|
201
197
|
let facts;
|
|
202
198
|
try {
|
|
203
|
-
facts = successorFacts(current, { ...snapshot, ...(effort ? { effort } : {}) }, nowMs,
|
|
199
|
+
facts = successorFacts(current, { ...snapshot, ...(effort ? { effort } : {}) }, nowMs, current.adoption !== undefined);
|
|
204
200
|
}
|
|
205
201
|
catch (error) {
|
|
206
202
|
if (error instanceof FlowError && error.code === "PROJECTION_WAKE_FAILED") {
|
|
@@ -210,80 +206,13 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
|
|
|
210
206
|
}
|
|
211
207
|
const store = openProjectionGenerationStore(projectionDirectory);
|
|
212
208
|
try {
|
|
213
|
-
if (life.phase === "startup-timeout") {
|
|
214
|
-
const terminalAt = new Date(nowMs).toISOString();
|
|
215
|
-
let deathEvidence;
|
|
216
|
-
try {
|
|
217
|
-
deathEvidence = acquireProjectionRunnerSettlementLock(projectionDirectory);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (error instanceof ProjectionRunnerLockUnavailableError)
|
|
221
|
-
return "pending";
|
|
222
|
-
return "pending";
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
store.beginWriteTransaction();
|
|
226
|
-
const protectedCurrent = store.readCurrentGeneration();
|
|
227
|
-
if (protectedCurrent?.launch.executionId !== current.launch.executionId
|
|
228
|
-
|| protectedCurrent.adoption
|
|
229
|
-
|| protectedCurrent.verdict) {
|
|
230
|
-
store.rollbackWriteTransaction();
|
|
231
|
-
return "pending";
|
|
232
|
-
}
|
|
233
|
-
if (protectedCurrent.killIntent) {
|
|
234
|
-
const settled = store.settleKillIfRunnerDead({
|
|
235
|
-
executionId: current.launch.executionId,
|
|
236
|
-
releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
|
|
237
|
-
terminalAt,
|
|
238
|
-
});
|
|
239
|
-
if (settled.status === "committed")
|
|
240
|
-
store.commitWriteTransaction();
|
|
241
|
-
else
|
|
242
|
-
store.rollbackWriteTransaction();
|
|
243
|
-
return "pending";
|
|
244
|
-
}
|
|
245
|
-
deathEvidence.close();
|
|
246
|
-
let launchOwnership;
|
|
247
|
-
try {
|
|
248
|
-
launchOwnership = acquireProjectionRunnerLock(projectionDirectory);
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
store.rollbackWriteTransaction();
|
|
252
|
-
if (error instanceof ProjectionRunnerLockUnavailableError)
|
|
253
|
-
return "pending";
|
|
254
|
-
return "pending";
|
|
255
|
-
}
|
|
256
|
-
return await startCommittedSuccessor(projectionDirectory, projectionId, successorExecutionId, startGeneration, {
|
|
257
|
-
commitLaunch: () => {
|
|
258
|
-
const transition = store.supersedeStartupTimeout({
|
|
259
|
-
executionId: current.launch.executionId,
|
|
260
|
-
releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
|
|
261
|
-
diagnostic: { detail: "runner did not adopt before startup timeout" },
|
|
262
|
-
successorExecutionId,
|
|
263
|
-
successorFacts: facts,
|
|
264
|
-
terminalAt,
|
|
265
|
-
});
|
|
266
|
-
if (transition.status === "committed")
|
|
267
|
-
store.commitWriteTransaction();
|
|
268
|
-
else
|
|
269
|
-
store.rollbackWriteTransaction();
|
|
270
|
-
return transition.status === "committed";
|
|
271
|
-
},
|
|
272
|
-
launchOwnership,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
finally {
|
|
276
|
-
store.rollbackWriteTransaction();
|
|
277
|
-
deathEvidence.close();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
209
|
if (life.phase === "lost" && !current.verdict) {
|
|
281
210
|
let deathEvidence;
|
|
282
211
|
try {
|
|
283
|
-
deathEvidence =
|
|
212
|
+
deathEvidence = acquireRunnerLostProof(projectionRunnerLeasePath(projectionDirectory));
|
|
284
213
|
}
|
|
285
214
|
catch (error) {
|
|
286
|
-
if (error instanceof
|
|
215
|
+
if (error instanceof RunnerLeaseUnavailableError)
|
|
287
216
|
return "active";
|
|
288
217
|
return "pending";
|
|
289
218
|
}
|
|
@@ -291,7 +220,6 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
|
|
|
291
220
|
store.beginWriteTransaction();
|
|
292
221
|
const protectedCurrent = store.readCurrentGeneration();
|
|
293
222
|
if (protectedCurrent?.launch.executionId !== current.launch.executionId
|
|
294
|
-
|| !protectedCurrent.adoption
|
|
295
223
|
|| protectedCurrent.verdict) {
|
|
296
224
|
store.rollbackWriteTransaction();
|
|
297
225
|
return "pending";
|
|
@@ -309,26 +237,36 @@ export async function tellProjection(projectionDirectory, projectionId, effort,
|
|
|
309
237
|
store.rollbackWriteTransaction();
|
|
310
238
|
return "pending";
|
|
311
239
|
}
|
|
240
|
+
const currentWasAdopted = protectedCurrent.adoption !== undefined;
|
|
312
241
|
deathEvidence.close();
|
|
313
242
|
let launchOwnership;
|
|
314
243
|
try {
|
|
315
|
-
launchOwnership =
|
|
244
|
+
launchOwnership = acquireRunnerLease(projectionRunnerLeasePath(projectionDirectory));
|
|
316
245
|
}
|
|
317
246
|
catch (error) {
|
|
318
247
|
store.rollbackWriteTransaction();
|
|
319
|
-
if (error instanceof
|
|
248
|
+
if (error instanceof RunnerLeaseUnavailableError)
|
|
320
249
|
return "active";
|
|
321
250
|
return "pending";
|
|
322
251
|
}
|
|
323
252
|
return await startCommittedSuccessor(projectionDirectory, projectionId, successorExecutionId, startGeneration, {
|
|
324
253
|
commitLaunch: () => {
|
|
325
|
-
const transition =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
254
|
+
const transition = currentWasAdopted
|
|
255
|
+
? store.replaceDeadRunner({
|
|
256
|
+
executionId: current.launch.executionId,
|
|
257
|
+
releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
|
|
258
|
+
successorExecutionId,
|
|
259
|
+
successorFacts: facts,
|
|
260
|
+
terminalAt,
|
|
261
|
+
})
|
|
262
|
+
: store.supersedeStartupTimeout({
|
|
263
|
+
executionId: current.launch.executionId,
|
|
264
|
+
releasedLockEvidence: { observedAt: terminalAt, state: "acquired" },
|
|
265
|
+
diagnostic: { detail: "startup owner died before adoption" },
|
|
266
|
+
successorExecutionId,
|
|
267
|
+
successorFacts: facts,
|
|
268
|
+
terminalAt,
|
|
269
|
+
});
|
|
332
270
|
if (transition.status === "committed")
|
|
333
271
|
store.commitWriteTransaction();
|
|
334
272
|
else
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProjectionStateError } from "../../atomic-publish.js";
|
|
2
|
-
import { withRead, withWrite } from "
|
|
2
|
+
import { withProjectionHeartRead as withRead, withProjectionHeartWrite as withWrite, } from "../generation/database.js";
|
|
3
3
|
import { parseFence, requireExecutionId, submittedFromRow } from "./model.js";
|
|
4
4
|
function selectTell(database, tellId) {
|
|
5
5
|
return database.prepare(`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProjectionStateError, randomHex8 } from "../../atomic-publish.js";
|
|
2
|
-
import { withRead, withReadSnapshot, withWrite } from "
|
|
2
|
+
import { withProjectionHeartRead as withRead, withProjectionHeartReadSnapshot as withReadSnapshot, withProjectionHeartWrite as withWrite, } from "../generation/database.js";
|
|
3
3
|
import { assertTellIdRandomHex, originalFromRow, PENDING_PROJECTION_TELL_STATES, parseFence, requireExecutionId, submittedFromRow, } from "./model.js";
|
|
4
4
|
export { bindTellLaunchReservation, claimAndReserveFencedTells, claimFencedTells, isTellLaunchAdmissionMember, listTellLaunchAdmissions, listTellLaunchBatch, markTellSubmitted, } from "./launch-store.js";
|
|
5
5
|
function selectTell(database, tellId) {
|