@astrosheep/keiyaku 2.9.4 → 2.9.6
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/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/harness/execution-handle.js +1 -0
- package/build/agents/harness/outcome.js +31 -5
- package/build/agents/harness/projection.js +151 -78
- package/build/agents/harness/router.js +2 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
- package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
- package/build/agents/providers/codex-app-server/adapter.js +43 -10
- package/build/agents/providers/codex-app-server/events.js +23 -0
- package/build/agents/providers/codex-app-server/registration.js +2 -1
- package/build/agents/providers/opencode-sdk/adapter.js +7 -0
- package/build/agents/providers/opencode-sdk/registration.js +2 -1
- package/build/agents/providers/pi/adapter.js +10 -0
- package/build/agents/providers/pi/events.js +4 -3
- package/build/agents/providers/pi/registration.js +3 -1
- package/build/agents/types.js +2 -0
- package/build/cli/commands/akuma/akuma/meta.js +1 -1
- package/build/cli/commands/akuma/list/meta-ls.js +1 -1
- package/build/cli/commands/akuma/view/meta.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/arc/handler.js +1 -1
- package/build/cli/commands/contract/arc/meta.js +4 -4
- package/build/cli/commands/contract/audit/handler.js +16 -0
- package/build/cli/commands/contract/audit/meta.js +16 -0
- package/build/cli/commands/contract/bind/meta.js +20 -11
- package/build/cli/commands/contract/catalog.js +2 -0
- package/build/cli/commands/contract/forfeit/meta.js +1 -1
- package/build/cli/commands/contract/log/meta.js +1 -1
- package/build/cli/commands/contract/petition/handler.js +1 -2
- package/build/cli/commands/contract/petition/meta.js +6 -6
- package/build/cli/commands/contract/renew/handler.js +2 -2
- package/build/cli/commands/contract/renew/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/call/meta.js +4 -1
- package/build/cli/commands/projection/kill/meta.js +1 -1
- package/build/cli/commands/projection/revive/meta.js +4 -1
- package/build/cli/commands/projection/status/meta.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +1 -1
- package/build/cli/commands/projection/tell/meta.js +4 -1
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/skills/install/meta.js +1 -1
- package/build/cli/commands/skills/skills/meta.js +1 -1
- package/build/cli/commands/system/completion/meta.js +1 -1
- package/build/cli/commands/system/dump-env/meta.js +1 -1
- package/build/cli/commands/system/guide/meta.js +1 -1
- package/build/cli/commands/task/add/handler.js +2 -2
- package/build/cli/commands/task/add/meta.js +4 -1
- package/build/cli/commands/task/doctor/meta.js +1 -1
- package/build/cli/commands/task/done/meta.js +1 -1
- package/build/cli/commands/task/drop/meta.js +1 -1
- package/build/cli/commands/task/ls/handler.js +10 -4
- package/build/cli/commands/task/ls/meta.js +3 -3
- package/build/cli/commands/task/show/meta.js +1 -1
- package/build/cli/commands/task/start/meta.js +1 -1
- package/build/cli/commands/task/stop/meta.js +1 -1
- package/build/cli/commands/task/task/meta.js +1 -1
- package/build/cli/commands/task/update/handler.js +2 -3
- package/build/cli/commands/task/update/meta.js +4 -1
- package/build/cli/completion.js +9 -2
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +3 -3
- package/build/cli/index.js +59 -75
- package/build/cli/output-stream-error-policy.js +19 -5
- package/build/cli/parse-flags.js +19 -2
- package/build/cli/parse-metadata.js +1 -7
- package/build/cli/parse-selectors.js +1 -0
- package/build/cli/parse.js +16 -7
- package/build/cli/projection-address.js +6 -1
- package/build/cli/render/audit.js +121 -0
- package/build/cli/render/errors.js +18 -1
- package/build/cli/render/format.js +5 -2
- package/build/cli/render/misc.js +3 -1
- package/build/cli/render/projection-activity.js +12 -6
- package/build/cli/render/status.js +28 -18
- package/build/cli/render/success-response.js +6 -5
- package/build/cli/render/task.js +12 -43
- package/build/cli/render/terminal-failure.js +7 -2
- package/build/cli/render/wait.js +36 -8
- package/build/config/env-keys.js +2 -0
- package/build/config/env.js +3 -0
- package/build/core/amend.js +20 -94
- package/build/core/arc.js +14 -6
- package/build/core/audit/candidate.js +48 -0
- package/build/core/audit/coordinates.js +180 -0
- package/build/core/audit/evidence.js +158 -0
- package/build/core/audit/facade.js +42 -0
- package/build/core/audit/readiness.js +4 -0
- package/build/core/audit/report.js +38 -0
- package/build/core/audit.js +1 -0
- package/build/core/bind.js +1 -1
- package/build/core/call/execution.js +1 -0
- package/build/core/contract-view.js +2 -3
- package/build/core/draft.js +33 -20
- package/build/core/entry.js +3 -4
- package/build/core/markdown/lex.js +2 -1
- package/build/core/projection/generation/database.js +41 -16
- package/build/core/projection/generation/model.js +22 -2
- package/build/core/projection/generation/projection-generation-execution.js +20 -7
- package/build/core/projection/generation/projection-generation-launcher.js +4 -4
- package/build/core/projection/generation/projection-generation-runner.js +1 -0
- package/build/core/projection/generation/store.js +1 -1
- package/build/core/projection/generation/transitions.js +19 -3
- package/build/core/projection/index.js +2 -2
- package/build/core/projection/projection-execution-observer.js +35 -21
- package/build/core/projection/projection-life-observer.js +16 -2
- package/build/core/projection/projection-status.js +67 -22
- package/build/core/projection/projection-terminal-failure.js +2 -0
- package/build/core/projection/projection-wait.js +6 -9
- package/build/core/projection/projection-wake.js +30 -6
- package/build/core/renew.js +41 -22
- package/build/core/scope.js +129 -2
- package/build/core/seal.js +36 -15
- package/build/core/settlement/petition-claim-gates.js +4 -33
- package/build/core/settlement/petition-preview.js +1 -1
- package/build/core/settlement/petition.js +10 -3
- package/build/core/settlement/settlement.js +3 -10
- package/build/core/settlement/verification.js +59 -0
- package/build/core/status/board.js +48 -28
- package/build/core/stored-agent-event.js +3 -2
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +20 -12
- package/build/core/task/document.js +5 -0
- package/build/core/task/index.js +1 -1
- package/build/core/task/model.js +8 -1
- package/build/core/task/query.js +214 -0
- package/build/core/task/settlement-git.js +115 -16
- package/build/core/task/settlement-policy.js +17 -9
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-contract.js +125 -51
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -0
- package/build/flow-error.js +2 -4
- package/build/generated/version.js +2 -2
- package/build/git/diff/parsers.js +23 -13
- package/build/git/diff/structured.js +127 -0
- package/build/index.js +7 -7
- package/build/telemetry/logger.js +4 -2
- package/package.json +1 -1
- package/skills/keiyaku/SKILL.md +2 -2
- package/skills/keiyaku-akuma/SKILL.md +17 -6
- package/skills/keiyaku-task/SKILL.md +16 -7
- package/skills/keiyaku-workflow/SKILL.md +48 -13
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function collectFindings(input) {
|
|
2
|
+
const findings = [];
|
|
3
|
+
const selector = `keiyaku @${input.contractId}`;
|
|
4
|
+
if (input.rehearsal.status === "clean")
|
|
5
|
+
findings.push({ code: "BASE_DRIFT", detail: `${input.rehearsal.oldBase} -> ${input.rehearsal.newBase}`, nextCommand: `${selector} renew` });
|
|
6
|
+
if (input.rehearsal.status === "conflict")
|
|
7
|
+
findings.push({ code: "RENEW_CONFLICT", detail: input.rehearsal.paths.join(", "), nextCommand: `${selector} renew` });
|
|
8
|
+
if (input.rehearsal.status === "unknown")
|
|
9
|
+
findings.push({ code: "RENEW_UNKNOWN", detail: `${input.rehearsal.cause.code}: ${input.rehearsal.cause.message}`, nextCommand: `${selector} audit` });
|
|
10
|
+
for (const row of input.scope?.filter((item) => item.class === "violated") ?? [])
|
|
11
|
+
findings.push({ code: "SCOPE_RESIDUE", detail: `${row.path}: ${row.residue.length} unexplained changes`, nextCommand: `${selector} amend --scope-add ${JSON.stringify(row.path)}` });
|
|
12
|
+
if (input.verification.status === "evaluated") {
|
|
13
|
+
for (const result of input.verification.results) {
|
|
14
|
+
if (result.status === "executed" && result.exitCode !== 0)
|
|
15
|
+
findings.push({ code: "VERIFICATION_FAILED", detail: `${result.label} exited ${result.exitCode}`, nextCommand: result.command });
|
|
16
|
+
if (result.status === "unverifiable")
|
|
17
|
+
findings.push({ code: "VERIFICATION_UNVERIFIABLE", detail: `${result.label}: ${result.cause}`, nextCommand: result.command });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const residue = input.readiness.deliveryResidue;
|
|
21
|
+
if (residue.tracked.length + residue.staged.length + residue.unstaged.length + residue.untracked.length > 0)
|
|
22
|
+
findings.push({ code: "DELIVERY_RESIDUE", detail: `registered delivery worktree has uncommitted residue at ${input.deliveryCwd}`, nextCommand: `git -C ${JSON.stringify(input.deliveryCwd)} status --short` });
|
|
23
|
+
if (input.readiness.arc.max > 0 && input.readiness.arc.open === null && input.readiness.arc.looseCommits.length > 0)
|
|
24
|
+
findings.push({ code: "LOOSE_COMMITS", detail: `${input.readiness.arc.looseCommits.length} loose commits after fence`, nextCommand: `printf '# Continue\\n\\n## Objective\\nReceipt loose commits.\\n\\n## Brief\\nContinue delivery.\\n' | ${selector} arc` });
|
|
25
|
+
if (input.readiness.renewSession.open)
|
|
26
|
+
findings.push({ code: "RENEW_SESSION_OPEN", detail: "renew recovery session is open", nextCommand: `${selector} renew --continue` });
|
|
27
|
+
if (input.readiness.rewriteGuard.liveProjection)
|
|
28
|
+
findings.push({ code: "LIVE_REWRITE_GUARD", detail: "a live projection owns the delivery worktree", nextCommand: "keiyaku status" });
|
|
29
|
+
for (const dependency of input.readiness.dependencies.filter((item) => !item.satisfied))
|
|
30
|
+
findings.push({ code: "DEPENDENCY_OPEN", detail: `dependency ${dependency.id} is ${dependency.state}`, nextCommand: `keiyaku @${dependency.id} log` });
|
|
31
|
+
if (input.readiness.petition.state === "petitioned" && input.readiness.petition.queue?.position !== 1)
|
|
32
|
+
findings.push({ code: "PETITION_QUEUE", detail: `petition queue position ${input.readiness.petition.queue?.position ?? "unknown"}`, nextCommand: "keiyaku status" });
|
|
33
|
+
return findings;
|
|
34
|
+
}
|
|
35
|
+
export function emptyRaceReport(input) {
|
|
36
|
+
const findings = [{ code: "COORDINATE_RACE", detail: input.detail, nextCommand: `keiyaku @${input.contractId} audit`, race: input.race }];
|
|
37
|
+
return { contract: input.contractId, coordinates: input.coordinates, terms: input.terms, rehearsal: { status: "unknown", cause: { code: "COORDINATE_RACE", message: input.detail } }, candidate: null, diff: null, presentation: null, files: null, scope: null, verification: { status: "not_evaluated", reason: "coordinate_race", results: [] }, readiness: null, findings, counts: { files: 0, selected: 0, omitted: 0, inScope: 0, downgraded: 0, violated: 0, findings: 1 } };
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auditContract } from "./audit/facade.js";
|
package/build/core/bind.js
CHANGED
|
@@ -119,7 +119,7 @@ function incompleteTaskBindError(taskId, missing) {
|
|
|
119
119
|
const template = renderMissingBindSectionsTemplate(missing);
|
|
120
120
|
const message = [
|
|
121
121
|
`task ${coordinate} carries no ${describeMissingBindSections(missing)}`,
|
|
122
|
-
`» keiyaku bind --task ${coordinate} <<'KEIYAKU'`,
|
|
122
|
+
`» keiyaku bind --task ${coordinate} - <<'KEIYAKU'`,
|
|
123
123
|
template,
|
|
124
124
|
"KEIYAKU",
|
|
125
125
|
].join("\n");
|
|
@@ -159,6 +159,7 @@ export async function buildCallConfig(agentName, prompt, cwd, options) {
|
|
|
159
159
|
task: options.task ?? prompt,
|
|
160
160
|
launchSnapshot,
|
|
161
161
|
idleTimeoutMs: resolvedTerms.idleTimeoutMs,
|
|
162
|
+
executionTimeoutMs: resolvedTerms.executionTimeoutMs,
|
|
162
163
|
cwd,
|
|
163
164
|
historyCwd: options.historyStorageCwd ?? cwd,
|
|
164
165
|
...(options.evidenceCwd !== undefined ? { evidenceCwd: options.evidenceCwd } : {}),
|
|
@@ -5,6 +5,7 @@ import { shortCommitHash } from "../git/commits.js";
|
|
|
5
5
|
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
6
6
|
import { readLedger } from "./ledger.js";
|
|
7
7
|
import { deriveContractState } from "./status/lifecycle.js";
|
|
8
|
+
import { renderScopeFragment } from "./scope.js";
|
|
8
9
|
const VERB_COLUMN_WIDTH = 11;
|
|
9
10
|
const TIME_COLUMN_WIDTH = 6;
|
|
10
11
|
const ACTOR_COLUMN_WIDTH = 11;
|
|
@@ -41,8 +42,6 @@ function sealAggregate(entries) {
|
|
|
41
42
|
function scopeStatusFact(entry) {
|
|
42
43
|
if (entry.data.scopeAudit.violations.length === 0)
|
|
43
44
|
return "scope ✓ clean";
|
|
44
|
-
if (entry.data.scopeAudit.waived)
|
|
45
|
-
return `scope ▲ waived ${entry.data.scopeAudit.violations.length}`;
|
|
46
45
|
return `scope ✕ ${entry.data.scopeAudit.violations.length}`;
|
|
47
46
|
}
|
|
48
47
|
function formatDiffstat(insertions, deletions) {
|
|
@@ -258,7 +257,7 @@ export function renderContractView(input) {
|
|
|
258
257
|
bind.data.objective,
|
|
259
258
|
"",
|
|
260
259
|
"## Scope",
|
|
261
|
-
...bind.data.scope.
|
|
260
|
+
...renderScopeFragment(bind.data.scope).split("\n"),
|
|
262
261
|
"",
|
|
263
262
|
"## Checks",
|
|
264
263
|
...bind.data.checks.map((check) => `- ${check}`),
|
package/build/core/draft.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
2
|
import { parseToAST } from "./markdown/parser.js";
|
|
3
3
|
import { extractListItems, renderSectionContent } from "./markdown/render.js";
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeSectionTitle } from "./markdown/titles.js";
|
|
5
|
+
import { markdownSourceSections, renderScopeFragment, validateScopeFragmentLines } from "./scope.js";
|
|
5
6
|
const KNOWN_SECTIONS = new Set(["objective", "scope", "checks"]);
|
|
6
7
|
const BIND_DRAFT_SECTION_ORDER = ["name", "objective", "scope", "checks"];
|
|
7
8
|
function invalidBindDraft(message) {
|
|
@@ -74,7 +75,16 @@ function extractSingleLineChecks(section) {
|
|
|
74
75
|
});
|
|
75
76
|
}
|
|
76
77
|
function collectBindDraftNodes(content) {
|
|
77
|
-
|
|
78
|
+
// Scope is parsed from source lines below. Hide its body from the Markdown AST
|
|
79
|
+
// so fence-like and heading-like payload bytes cannot acquire structure there.
|
|
80
|
+
let structuralContent = content;
|
|
81
|
+
const scopeSources = markdownSourceSections(content)
|
|
82
|
+
.filter((section) => section.level === 2 && normalizeSectionTitle(section.title) === "scope")
|
|
83
|
+
.sort((left, right) => right.contentStart - left.contentStart);
|
|
84
|
+
for (const source of scopeSources) {
|
|
85
|
+
structuralContent = `${structuralContent.slice(0, source.contentStart)}\`\`\`\nscope\n\`\`\`\n${structuralContent.slice(source.end)}`;
|
|
86
|
+
}
|
|
87
|
+
const ast = parseToAST(structuralContent);
|
|
78
88
|
let name;
|
|
79
89
|
let seenH1 = false;
|
|
80
90
|
const sections = new Map();
|
|
@@ -107,21 +117,22 @@ function collectBindDraftNodes(content) {
|
|
|
107
117
|
}
|
|
108
118
|
return { ...(name === undefined ? {} : { name }), sections, extensions };
|
|
109
119
|
}
|
|
110
|
-
function parseScopeSection(
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
invalidBindDraft(
|
|
120
|
+
function parseScopeSection(content) {
|
|
121
|
+
const sourceSections = markdownSourceSections(content).filter((section) => section.level === 2 && normalizeSectionTitle(section.title) === "scope");
|
|
122
|
+
if (sourceSections.length !== 1)
|
|
123
|
+
invalidBindDraft("bind draft must contain exactly one Scope section");
|
|
124
|
+
const source = sourceSections[0];
|
|
125
|
+
try {
|
|
126
|
+
return validateScopeFragmentLines(content.slice(source.contentStart, source.end).split(/\r?\n/), {
|
|
127
|
+
heading: "## Scope",
|
|
128
|
+
emptyMessage: "Scope must be a gitignore-subset fragment; found empty Scope section",
|
|
129
|
+
});
|
|
114
130
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof FlowError)
|
|
133
|
+
throw new FlowError(error.code, `INVALID_BIND_DRAFT: ${error.message}`);
|
|
134
|
+
throw error;
|
|
118
135
|
}
|
|
119
|
-
return scopeItems.map((item) => {
|
|
120
|
-
if (item.trim().length === 0) {
|
|
121
|
-
invalidBindDraft("empty scope pattern in bind draft");
|
|
122
|
-
}
|
|
123
|
-
return normalizeScopePattern(item);
|
|
124
|
-
});
|
|
125
136
|
}
|
|
126
137
|
/**
|
|
127
138
|
* Parse any subset of the bind document grammar. Absent sections stay undefined
|
|
@@ -142,8 +153,9 @@ export function parsePartialBindDraft(content) {
|
|
|
142
153
|
partial.objective = objectiveText;
|
|
143
154
|
}
|
|
144
155
|
const scope = sections.get("scope");
|
|
145
|
-
if (scope)
|
|
146
|
-
partial.scope = parseScopeSection(
|
|
156
|
+
if (scope) {
|
|
157
|
+
partial.scope = parseScopeSection(content);
|
|
158
|
+
}
|
|
147
159
|
const checks = sections.get("checks");
|
|
148
160
|
if (checks)
|
|
149
161
|
partial.checks = extractSingleLineChecks(checks);
|
|
@@ -192,7 +204,7 @@ export function renderMissingBindSectionsTemplate(missing) {
|
|
|
192
204
|
continue;
|
|
193
205
|
}
|
|
194
206
|
if (section === "scope") {
|
|
195
|
-
lines.push("## Scope", "
|
|
207
|
+
lines.push("## Scope", "```", "<pattern>", "<another-pattern>", "```");
|
|
196
208
|
continue;
|
|
197
209
|
}
|
|
198
210
|
lines.push("## Checks", "- <verification>");
|
|
@@ -233,6 +245,7 @@ export function parseBindDraft(content) {
|
|
|
233
245
|
invalidBindDraft("missing Objective section in bind draft");
|
|
234
246
|
if (!scope)
|
|
235
247
|
invalidBindDraft("missing Scope section in bind draft");
|
|
248
|
+
const parsedScope = parseScopeSection(content);
|
|
236
249
|
if (!checks)
|
|
237
250
|
invalidBindDraft("missing Checks section in bind draft");
|
|
238
251
|
const objectiveText = renderSectionContent(objective).trim();
|
|
@@ -241,7 +254,7 @@ export function parseBindDraft(content) {
|
|
|
241
254
|
return {
|
|
242
255
|
name,
|
|
243
256
|
objective: objectiveText,
|
|
244
|
-
scope:
|
|
257
|
+
scope: parsedScope,
|
|
245
258
|
checks: extractSingleLineChecks(checks),
|
|
246
259
|
extensions,
|
|
247
260
|
};
|
|
@@ -250,7 +263,7 @@ export function renderBindDraft(draft) {
|
|
|
250
263
|
const sections = [
|
|
251
264
|
`# ${draft.name}`,
|
|
252
265
|
`## Objective\n${draft.objective}`,
|
|
253
|
-
`## Scope\n${draft.scope
|
|
266
|
+
`## Scope\n${renderScopeFragment(draft.scope)}`,
|
|
254
267
|
`## Checks\n${draft.checks.map((check) => `- ${check}`).join("\n")}`,
|
|
255
268
|
];
|
|
256
269
|
for (const extension of draft.extensions) {
|
package/build/core/entry.js
CHANGED
|
@@ -35,7 +35,6 @@ const diffstatSchema = z
|
|
|
35
35
|
const scopeAuditSchema = z
|
|
36
36
|
.object({
|
|
37
37
|
violations: z.array(nonEmptyString),
|
|
38
|
-
waived: z.boolean(),
|
|
39
38
|
})
|
|
40
39
|
.strict();
|
|
41
40
|
export const fullBranchRefSchema = z
|
|
@@ -200,10 +199,10 @@ export const forfeitEntryDataSchema = z
|
|
|
200
199
|
note: nonEmptyString.optional(),
|
|
201
200
|
})
|
|
202
201
|
.strict();
|
|
203
|
-
function makeEntrySchema(kind, data) {
|
|
202
|
+
function makeEntrySchema(kind, data, version = 1) {
|
|
204
203
|
return z
|
|
205
204
|
.object({
|
|
206
|
-
v: z.literal(
|
|
205
|
+
v: z.literal(version),
|
|
207
206
|
kind: z.literal(kind),
|
|
208
207
|
contract: z.string().trim().regex(CONTRACT_ID_RE, "contract id cannot contain whitespace"),
|
|
209
208
|
at: isoUtcSchema,
|
|
@@ -214,7 +213,7 @@ function makeEntrySchema(kind, data) {
|
|
|
214
213
|
}
|
|
215
214
|
export const bindEntrySchema = makeEntrySchema("bind", bindEntryDataSchema);
|
|
216
215
|
export const arcEntrySchema = makeEntrySchema("arc", arcEntryDataSchema);
|
|
217
|
-
export const sealEntrySchema = makeEntrySchema("seal", sealEntryDataSchema);
|
|
216
|
+
export const sealEntrySchema = makeEntrySchema("seal", sealEntryDataSchema, 2);
|
|
218
217
|
export const amendEntrySchema = makeEntrySchema("amend", amendEntryDataSchema);
|
|
219
218
|
export const renewEntrySchema = makeEntrySchema("renew", renewEntryDataSchema);
|
|
220
219
|
export const petitionEntrySchema = makeEntrySchema("petition", petitionEntryDataSchema);
|
|
@@ -173,7 +173,8 @@ export function stripTrailingBlankLines(lines) {
|
|
|
173
173
|
return trimmed;
|
|
174
174
|
}
|
|
175
175
|
export function isSectionHeaderToken(token) {
|
|
176
|
-
|
|
176
|
+
// Column-0 only: indented ATX-looking lines remain content, not structure.
|
|
177
|
+
return token.type === "header" && token.leadingSpaces === 0 && (token.level === 1 || token.level === 2);
|
|
177
178
|
}
|
|
178
179
|
export function isFenceClosingToken(token, fence) {
|
|
179
180
|
return token.type === "fence" && token.delimiter === fence.delimiter && token.length >= fence.length;
|
|
@@ -57,11 +57,40 @@ const TELL_TABLE_SQL = `
|
|
|
57
57
|
) STRICT
|
|
58
58
|
`;
|
|
59
59
|
const closedDatabases = new WeakSet();
|
|
60
|
+
/** Classify only proved missing schema evidence as historical incompatibility. */
|
|
61
|
+
export function projectionGenerationStoreInspectionError(databasePath, error) {
|
|
62
|
+
if (error instanceof ProjectionGenerationStoreError)
|
|
63
|
+
return error;
|
|
64
|
+
const code = error && typeof error === "object" && "code" in error
|
|
65
|
+
? error.code
|
|
66
|
+
: undefined;
|
|
67
|
+
if (code === "ENOENT") {
|
|
68
|
+
return new ProjectionGenerationStoreError(`projection generation store does not exist at ${databasePath}`, {
|
|
69
|
+
cause: error,
|
|
70
|
+
observation: "incompatible",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return new ProjectionGenerationStoreError(`projection generation store could not be inspected at ${databasePath}`, {
|
|
74
|
+
cause: error,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function projectionGenerationStoreReadError(databasePath, error) {
|
|
78
|
+
if (error instanceof ProjectionGenerationStoreError)
|
|
79
|
+
return error;
|
|
80
|
+
const sqliteCode = error && typeof error === "object" && "errcode" in error
|
|
81
|
+
? error.errcode
|
|
82
|
+
: undefined;
|
|
83
|
+
const incompatible = sqliteCode === 11 || sqliteCode === 26;
|
|
84
|
+
return new ProjectionGenerationStoreError(`cannot read projection generation store at ${databasePath}`, {
|
|
85
|
+
cause: error,
|
|
86
|
+
...(incompatible ? { observation: "incompatible" } : {}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
60
89
|
function pragmaNumber(database, pragma) {
|
|
61
90
|
const row = database.prepare(`PRAGMA ${pragma}`).get();
|
|
62
91
|
const value = row ? Object.values(row)[0] : undefined;
|
|
63
92
|
if (typeof value !== "number" && typeof value !== "bigint") {
|
|
64
|
-
throw new ProjectionGenerationStoreError(`PRAGMA ${pragma} returned an invalid value
|
|
93
|
+
throw new ProjectionGenerationStoreError(`PRAGMA ${pragma} returned an invalid value`, { observation: "incompatible" });
|
|
65
94
|
}
|
|
66
95
|
return Number(value);
|
|
67
96
|
}
|
|
@@ -76,7 +105,7 @@ export function rollbackQuietly(database) {
|
|
|
76
105
|
function validateSchemaShape(database) {
|
|
77
106
|
const version = pragmaNumber(database, "user_version");
|
|
78
107
|
if (version !== PROJECTION_GENERATION_SCHEMA_VERSION) {
|
|
79
|
-
throw new ProjectionGenerationStoreError(`incompatible projection generation schema version ${version}; expected ${PROJECTION_GENERATION_SCHEMA_VERSION}
|
|
108
|
+
throw new ProjectionGenerationStoreError(`incompatible projection generation schema version ${version}; expected ${PROJECTION_GENERATION_SCHEMA_VERSION}`, { observation: "incompatible" });
|
|
80
109
|
}
|
|
81
110
|
const objects = database.prepare(`
|
|
82
111
|
SELECT name, type
|
|
@@ -86,36 +115,36 @@ function validateSchemaShape(database) {
|
|
|
86
115
|
`).all();
|
|
87
116
|
const signature = objects.map((object) => `${object.type}:${object.name}`).join(",");
|
|
88
117
|
if (signature !== "table:generation_records,table:generation_schema,table:projection_identity,table:projection_tells") {
|
|
89
|
-
throw new ProjectionGenerationStoreError(`projection generation schema contains unexpected objects: ${signature || "none"}
|
|
118
|
+
throw new ProjectionGenerationStoreError(`projection generation schema contains unexpected objects: ${signature || "none"}`, { observation: "incompatible" });
|
|
90
119
|
}
|
|
91
120
|
const metadata = database.prepare("SELECT singleton, version FROM generation_schema").all();
|
|
92
121
|
if (metadata.length !== 1 || Number(metadata[0].singleton) !== 1 || Number(metadata[0].version) !== PROJECTION_GENERATION_SCHEMA_VERSION) {
|
|
93
|
-
throw new ProjectionGenerationStoreError("projection generation schema metadata is malformed");
|
|
122
|
+
throw new ProjectionGenerationStoreError("projection generation schema metadata is malformed", { observation: "incompatible" });
|
|
94
123
|
}
|
|
95
124
|
const recordColumns = database.prepare("PRAGMA table_info(generation_records)").all();
|
|
96
125
|
const columnSignature = recordColumns
|
|
97
126
|
.map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
|
|
98
127
|
.join(",");
|
|
99
128
|
if (columnSignature !== "seq:INTEGER:0:1,kind:TEXT:1:0,execution_id:TEXT:1:0,facts_json:TEXT:1:0") {
|
|
100
|
-
throw new ProjectionGenerationStoreError("projection generation record schema is malformed");
|
|
129
|
+
throw new ProjectionGenerationStoreError("projection generation record schema is malformed", { observation: "incompatible" });
|
|
101
130
|
}
|
|
102
131
|
const identityColumns = database.prepare("PRAGMA table_info(projection_identity)").all();
|
|
103
132
|
const identitySignature = identityColumns
|
|
104
133
|
.map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
|
|
105
134
|
.join(",");
|
|
106
135
|
if (identitySignature !== "singleton:INTEGER:0:1,version:INTEGER:1:0,pid:INTEGER:1:0,akuma:TEXT:1:0,provider:TEXT:1:0,authority_json:TEXT:1:0,binding_json:TEXT:1:0,minted_at:TEXT:1:0,workspace_root:TEXT:1:0,effective_policy_json:TEXT:0:0") {
|
|
107
|
-
throw new ProjectionGenerationStoreError("projection identity schema is malformed");
|
|
136
|
+
throw new ProjectionGenerationStoreError("projection identity schema is malformed", { observation: "incompatible" });
|
|
108
137
|
}
|
|
109
138
|
const tellColumns = database.prepare("PRAGMA table_info(projection_tells)").all();
|
|
110
139
|
const tellSignature = tellColumns
|
|
111
140
|
.map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
|
|
112
141
|
.join(",");
|
|
113
142
|
if (tellSignature !== "tell_id:TEXT:1:1,text:TEXT:1:0,created_at:TEXT:1:0,effort:TEXT:0:0,state:TEXT:1:0,execution_id:TEXT:0:0,fence_json:TEXT:0:0,replayed_from_json:TEXT:0:0,delivered_at:TEXT:0:0") {
|
|
114
|
-
throw new ProjectionGenerationStoreError("projection tell schema is malformed");
|
|
143
|
+
throw new ProjectionGenerationStoreError("projection tell schema is malformed", { observation: "incompatible" });
|
|
115
144
|
}
|
|
116
145
|
const quickCheck = database.prepare("PRAGMA quick_check").get();
|
|
117
146
|
if (!quickCheck || Object.values(quickCheck)[0] !== "ok") {
|
|
118
|
-
throw new ProjectionGenerationStoreError("projection generation database failed quick_check");
|
|
147
|
+
throw new ProjectionGenerationStoreError("projection generation database failed quick_check", { observation: "incompatible" });
|
|
119
148
|
}
|
|
120
149
|
}
|
|
121
150
|
function initializeOrValidateSchema(database) {
|
|
@@ -138,7 +167,7 @@ function initializeOrValidateSchema(database) {
|
|
|
138
167
|
database.exec(`PRAGMA user_version = ${PROJECTION_GENERATION_SCHEMA_VERSION}`);
|
|
139
168
|
}
|
|
140
169
|
else if (version !== PROJECTION_GENERATION_SCHEMA_VERSION) {
|
|
141
|
-
throw new ProjectionGenerationStoreError(`incompatible projection generation schema version ${version}; expected ${PROJECTION_GENERATION_SCHEMA_VERSION}
|
|
170
|
+
throw new ProjectionGenerationStoreError(`incompatible projection generation schema version ${version}; expected ${PROJECTION_GENERATION_SCHEMA_VERSION}`, { observation: "incompatible" });
|
|
142
171
|
}
|
|
143
172
|
validateSchemaShape(database);
|
|
144
173
|
database.exec("COMMIT");
|
|
@@ -195,13 +224,11 @@ export function openReadOnlyDatabase(projectionDirectory) {
|
|
|
195
224
|
const databasePath = path.join(projectionDirectory, PROJECTION_GENERATION_STORE_FILE);
|
|
196
225
|
try {
|
|
197
226
|
if (!fs.lstatSync(databasePath).isFile()) {
|
|
198
|
-
throw new ProjectionGenerationStoreError("projection generation store must be a regular file");
|
|
227
|
+
throw new ProjectionGenerationStoreError("projection generation store must be a regular file", { observation: "incompatible" });
|
|
199
228
|
}
|
|
200
229
|
}
|
|
201
230
|
catch (error) {
|
|
202
|
-
|
|
203
|
-
throw error;
|
|
204
|
-
throw new ProjectionGenerationStoreError(`projection generation store does not exist at ${databasePath}`, { cause: error });
|
|
231
|
+
throw projectionGenerationStoreInspectionError(databasePath, error);
|
|
205
232
|
}
|
|
206
233
|
let database;
|
|
207
234
|
try {
|
|
@@ -220,9 +247,7 @@ export function openReadOnlyDatabase(projectionDirectory) {
|
|
|
220
247
|
database?.close();
|
|
221
248
|
}
|
|
222
249
|
catch { /* preserve the read/open failure */ }
|
|
223
|
-
|
|
224
|
-
throw error;
|
|
225
|
-
throw new ProjectionGenerationStoreError(`cannot read projection generation store at ${databasePath}`, { cause: error });
|
|
250
|
+
throw projectionGenerationStoreReadError(databasePath, error);
|
|
226
251
|
}
|
|
227
252
|
}
|
|
228
253
|
export function assertDatabaseOpen(database) {
|
|
@@ -13,11 +13,18 @@ const OUTCOME_ERROR_KEYS = new Set([
|
|
|
13
13
|
]);
|
|
14
14
|
export class ProjectionGenerationStoreError extends Error {
|
|
15
15
|
code = "PROJECTION_GENERATION_STORE_ERROR";
|
|
16
|
+
/** Historical schemas/outcomes are not a current lifecycle observation. */
|
|
17
|
+
observation;
|
|
16
18
|
constructor(message, options) {
|
|
17
19
|
super(message, options);
|
|
18
20
|
this.name = "ProjectionGenerationStoreError";
|
|
21
|
+
this.observation = options?.observation ?? "unknown";
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
export function isIncompatibleProjectionGenerationStoreError(error) {
|
|
25
|
+
return error instanceof ProjectionGenerationStoreError
|
|
26
|
+
&& error.observation === "incompatible";
|
|
27
|
+
}
|
|
21
28
|
export function isPlainObject(value) {
|
|
22
29
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
23
30
|
return false;
|
|
@@ -131,11 +138,14 @@ export function validateVerdictFacts(value, location) {
|
|
|
131
138
|
if (typeof facts.state !== "string" || !VERDICTS.has(facts.state)) {
|
|
132
139
|
throw new ProjectionGenerationStoreError(`${location}.state is not in the closed verdict vocabulary`);
|
|
133
140
|
}
|
|
141
|
+
if (facts.terminalAt !== undefined) {
|
|
142
|
+
facts.terminalAt = assertIsoTimestamp(facts.terminalAt, `${location}.terminalAt`);
|
|
143
|
+
}
|
|
134
144
|
if (facts.state === "failed") {
|
|
135
145
|
const error = isPlainObject(facts.error) ? facts.error : undefined;
|
|
136
146
|
const version = error?.version;
|
|
137
147
|
if (!error || version !== OUTCOME_ERROR_VERSION) {
|
|
138
|
-
throw new ProjectionGenerationStoreError(`${location}.error has incompatible version ${version === undefined ? "missing" : String(version)}; expected ${OUTCOME_ERROR_VERSION}
|
|
148
|
+
throw new ProjectionGenerationStoreError(`${location}.error has incompatible version ${version === undefined ? "missing" : String(version)}; expected ${OUTCOME_ERROR_VERSION}`, { observation: "incompatible" });
|
|
139
149
|
}
|
|
140
150
|
if (Object.keys(error).some((key) => !OUTCOME_ERROR_KEYS.has(key))) {
|
|
141
151
|
throw new ProjectionGenerationStoreError(`${location}.error is not the current closed OutcomeError shape`);
|
|
@@ -181,7 +191,7 @@ export function parseStoredRow(row) {
|
|
|
181
191
|
decoded = JSON.parse(row.facts_json);
|
|
182
192
|
}
|
|
183
193
|
catch (error) {
|
|
184
|
-
throw new ProjectionGenerationStoreError(`generation row ${seq} contains malformed facts JSON`, { cause: error });
|
|
194
|
+
throw new ProjectionGenerationStoreError(`generation row ${seq} contains malformed facts JSON`, { cause: error, observation: "incompatible" });
|
|
185
195
|
}
|
|
186
196
|
const location = `generation row ${seq} facts`;
|
|
187
197
|
const facts = row.kind === "launch"
|
|
@@ -240,3 +250,13 @@ export function validateHistory(records) {
|
|
|
240
250
|
}
|
|
241
251
|
return current;
|
|
242
252
|
}
|
|
253
|
+
export function extractTerminalTimestamp(facts) {
|
|
254
|
+
if (!facts)
|
|
255
|
+
return undefined;
|
|
256
|
+
for (const key of ["completedAt", "failedAt", "dismissedAt", "killedAt", "terminalAt"]) {
|
|
257
|
+
const value = facts[key];
|
|
258
|
+
if (typeof value === "string" && Number.isFinite(Date.parse(value)))
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
@@ -2,8 +2,8 @@ import { initializeConfig } from "../../../config/env.js";
|
|
|
2
2
|
import { buildCallTerms } from "../../../agents/call-terms.js";
|
|
3
3
|
import { parseResolvedAkumaLaunchSnapshot } from "../../../agents/launch-snapshot/model.js";
|
|
4
4
|
import { revive, call, } from "../../../agents/harness/index.js";
|
|
5
|
-
import { demoteSubmittedTellsForReplay, readProjectionEvents, } from "../projection-core.js";
|
|
6
|
-
import { createProjectionExecutionObserver, } from "../projection-execution-observer.js";
|
|
5
|
+
import { claimFencedTells, demoteSubmittedTellsForReplay, readTellOriginal, readProjectionEvents, } from "../projection-core.js";
|
|
6
|
+
import { createProjectionExecutionObserver, renderProjectionTellFrame, } from "../projection-execution-observer.js";
|
|
7
7
|
import { readProjectionIdentity } from "../projection-identity.js";
|
|
8
8
|
import { selectValidStoredAgentEvents } from "../../stored-agent-event.js";
|
|
9
9
|
import { persistResponseHistory } from "../../transcripts.js";
|
|
@@ -58,6 +58,7 @@ function parseLaunchInputs(launch) {
|
|
|
58
58
|
...(context ? { context } : {}),
|
|
59
59
|
launchSnapshot,
|
|
60
60
|
idleTimeoutMs: requirePositiveSafeInteger(raw.idleTimeoutMs, "idleTimeoutMs"),
|
|
61
|
+
executionTimeoutMs: requirePositiveSafeInteger(raw.executionTimeoutMs, "executionTimeoutMs"),
|
|
61
62
|
persistProjectionResponse: raw.persistProjectionResponse,
|
|
62
63
|
...(raw.session ? { session: raw.session } : {}),
|
|
63
64
|
...(resumePath ? { resumePath } : {}),
|
|
@@ -140,13 +141,25 @@ export function executeProjectionGeneration(projectionDirectory, launch, provide
|
|
|
140
141
|
initializeConfig(process.env, inputs.cwd);
|
|
141
142
|
const executionId = launch.executionId;
|
|
142
143
|
demoteSubmittedTellsForReplay(projectionDirectory, executionId);
|
|
143
|
-
const
|
|
144
|
+
const initialTellIds = claimFencedTells(projectionDirectory, launch.facts.tellFence ?? [], executionId);
|
|
145
|
+
const tellPrompt = initialTellIds.map((tellId) => {
|
|
146
|
+
const tell = readTellOriginal(projectionDirectory, "inflight", tellId);
|
|
147
|
+
return renderProjectionTellFrame(tellId, tell.text);
|
|
148
|
+
}).join("\n\n");
|
|
149
|
+
const prompt = tellPrompt === ""
|
|
150
|
+
? inputs.prompt
|
|
151
|
+
: inputs.session
|
|
152
|
+
? tellPrompt
|
|
153
|
+
: `${inputs.prompt}\n\n${tellPrompt}`;
|
|
154
|
+
const terms = attachProviderLoaderPorts(buildCallTerms(inputs.launchSnapshot, prompt, inputs.cwd, {
|
|
144
155
|
idleTimeoutMs: inputs.idleTimeoutMs,
|
|
156
|
+
executionTimeoutMs: inputs.executionTimeoutMs,
|
|
145
157
|
initialTurn: maximumValidEnvelopeTurn(projectionDirectory),
|
|
146
158
|
}), providerLoaderPorts);
|
|
147
159
|
const observerController = createProjectionExecutionObserver({
|
|
148
160
|
projectionDirectory,
|
|
149
161
|
executionId,
|
|
162
|
+
initialTellIds,
|
|
150
163
|
});
|
|
151
164
|
let projection;
|
|
152
165
|
try {
|
|
@@ -159,7 +172,7 @@ export function executeProjectionGeneration(projectionDirectory, launch, provide
|
|
|
159
172
|
: call(terms, { observer: observerController.observer });
|
|
160
173
|
}
|
|
161
174
|
catch (error) {
|
|
162
|
-
observerController.close();
|
|
175
|
+
void observerController.close();
|
|
163
176
|
throw error;
|
|
164
177
|
}
|
|
165
178
|
const outcome = projection.outcome.then(async (result) => {
|
|
@@ -174,10 +187,10 @@ export function executeProjectionGeneration(projectionDirectory, launch, provide
|
|
|
174
187
|
};
|
|
175
188
|
}
|
|
176
189
|
finally {
|
|
177
|
-
observerController.close();
|
|
190
|
+
await observerController.close();
|
|
178
191
|
}
|
|
179
|
-
}, (error) => {
|
|
180
|
-
observerController.close();
|
|
192
|
+
}, async (error) => {
|
|
193
|
+
await observerController.close();
|
|
181
194
|
throw error;
|
|
182
195
|
});
|
|
183
196
|
return {
|
|
@@ -6,13 +6,13 @@ export const PROJECTION_ADOPTION_POLL_MS = 20;
|
|
|
6
6
|
function errorDetail(error) {
|
|
7
7
|
return error instanceof Error ? error.message : String(error);
|
|
8
8
|
}
|
|
9
|
-
function recordLaunchFailure(projectionDirectory, executionId, detail, stage) {
|
|
9
|
+
function recordLaunchFailure(projectionDirectory, executionId, detail, stage, terminalAt) {
|
|
10
10
|
const store = openProjectionGenerationStore(projectionDirectory);
|
|
11
11
|
try {
|
|
12
12
|
return store.verdictIfOpen({
|
|
13
13
|
executionId,
|
|
14
14
|
verdict: "launch-failed",
|
|
15
|
-
facts: { stage, detail },
|
|
15
|
+
facts: { stage, detail, ...(terminalAt ? { terminalAt } : {}) },
|
|
16
16
|
}).status === "committed";
|
|
17
17
|
}
|
|
18
18
|
finally {
|
|
@@ -29,7 +29,7 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
29
29
|
}
|
|
30
30
|
catch (error) {
|
|
31
31
|
const detail = `runner spawn failed: ${errorDetail(error)}`;
|
|
32
|
-
recordLaunchFailure(projectionDirectory, executionId, detail, "spawn");
|
|
32
|
+
recordLaunchFailure(projectionDirectory, executionId, detail, "spawn", new Date(now()).toISOString());
|
|
33
33
|
return { status: "failed", cause: "spawn", detail };
|
|
34
34
|
}
|
|
35
35
|
const deadline = now() + PROJECTION_ADOPTION_TIMEOUT_MS;
|
|
@@ -75,7 +75,7 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
75
75
|
store.close();
|
|
76
76
|
}
|
|
77
77
|
const detail = "runner did not commit adoption before startup timeout";
|
|
78
|
-
if (!recordLaunchFailure(projectionDirectory, executionId, detail, "adoption")) {
|
|
78
|
+
if (!recordLaunchFailure(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString())) {
|
|
79
79
|
const raced = openProjectionGenerationStore(projectionDirectory);
|
|
80
80
|
try {
|
|
81
81
|
const current = raced.readCurrentGeneration();
|
|
@@ -2,7 +2,7 @@ import { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE,
|
|
|
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, };
|
|
5
|
-
export { GENERATION_VERDICT_STATES, ProjectionGenerationStoreError, } from "./model.js";
|
|
5
|
+
export { GENERATION_VERDICT_STATES, isIncompatibleProjectionGenerationStoreError, ProjectionGenerationStoreError, } from "./model.js";
|
|
6
6
|
export class ProjectionGenerationStore {
|
|
7
7
|
databasePath;
|
|
8
8
|
readOnly;
|
|
@@ -149,7 +149,7 @@ export function verdictIfOpen(database, readOnly, input) {
|
|
|
149
149
|
if (input.verdict === "launch-failed" && current.adoption) {
|
|
150
150
|
return rejected("already-adopted", current);
|
|
151
151
|
}
|
|
152
|
-
const facts = validateVerdictFacts({ ...(input.facts ?? {}), state: input.verdict }, "verdict facts");
|
|
152
|
+
const facts = validateVerdictFacts({ ...(input.facts ?? {}), state: input.verdict, ...(input.terminalAt ? { terminalAt: assertIsoTimestamp(input.terminalAt, "terminalAt") } : {}) }, "verdict facts");
|
|
153
153
|
return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
|
|
154
154
|
});
|
|
155
155
|
}
|
|
@@ -160,6 +160,9 @@ export function supersedeStartupTimeout(database, readOnly, input) {
|
|
|
160
160
|
const startupTimeoutMs = assertPositiveSafeInteger(input.startupTimeoutMs, "startupTimeoutMs");
|
|
161
161
|
const diagnostic = cloneFacts(input.diagnostic, "startup-timeout diagnostic");
|
|
162
162
|
const successorFacts = validateLaunchFacts(input.successorFacts, "successor launch facts");
|
|
163
|
+
const terminalAt = input.terminalAt === undefined
|
|
164
|
+
? undefined
|
|
165
|
+
: assertIsoTimestamp(input.terminalAt, "terminalAt");
|
|
163
166
|
return writeTransition(database, readOnly, () => {
|
|
164
167
|
const history = readValidatedHistory(database);
|
|
165
168
|
const current = history.current;
|
|
@@ -180,7 +183,12 @@ export function supersedeStartupTimeout(database, readOnly, input) {
|
|
|
180
183
|
if (observedAtMs - launchedAtMs < startupTimeoutMs) {
|
|
181
184
|
return rejected("startup-timeout-not-elapsed", current);
|
|
182
185
|
}
|
|
183
|
-
const
|
|
186
|
+
const verdictFactsBase = {
|
|
187
|
+
...diagnostic,
|
|
188
|
+
state: "launch-failed",
|
|
189
|
+
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
190
|
+
};
|
|
191
|
+
const verdictFacts = validateVerdictFacts(verdictFactsBase, "startup-timeout verdict facts");
|
|
184
192
|
const firstSeq = history.records.length + 1;
|
|
185
193
|
return committed([
|
|
186
194
|
appendRecord(database, "verdict", executionId, verdictFacts, firstSeq),
|
|
@@ -196,6 +204,9 @@ export function replaceDeadRunner(database, readOnly, input) {
|
|
|
196
204
|
throw new ProjectionGenerationStoreError("released runner-lock evidence must not be empty");
|
|
197
205
|
}
|
|
198
206
|
const successorFacts = validateLaunchFacts(input.successorFacts, "dead-runner successor launch facts");
|
|
207
|
+
const terminalAt = input.terminalAt === undefined
|
|
208
|
+
? undefined
|
|
209
|
+
: assertIsoTimestamp(input.terminalAt, "terminalAt");
|
|
199
210
|
return writeTransition(database, readOnly, () => {
|
|
200
211
|
const history = readValidatedHistory(database);
|
|
201
212
|
const current = history.current;
|
|
@@ -212,7 +223,12 @@ export function replaceDeadRunner(database, readOnly, input) {
|
|
|
212
223
|
if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
|
|
213
224
|
return rejected("execution-id-already-used", current);
|
|
214
225
|
}
|
|
215
|
-
const
|
|
226
|
+
const verdictFactsBase = {
|
|
227
|
+
state: "lost-runner",
|
|
228
|
+
releasedLockEvidence,
|
|
229
|
+
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
230
|
+
};
|
|
231
|
+
const verdictFacts = validateVerdictFacts(verdictFactsBase, "dead-runner verdict facts");
|
|
216
232
|
const firstSeq = history.records.length + 1;
|
|
217
233
|
return committed([
|
|
218
234
|
appendRecord(database, "verdict", executionId, verdictFacts, firstSeq),
|
|
@@ -10,13 +10,13 @@ export { resolveProjectionCoordinate } from "./projection-coordinate.js";
|
|
|
10
10
|
export { appendGenerationEvent, executionEventsPath, isProjectionId, isPublishedProjection, isoFromNowMs, projectionDir, projectionRoot, readProjectionEvents, } from "./projection-core.js";
|
|
11
11
|
export { readProjectionIdentity } from "./projection-identity.js";
|
|
12
12
|
export { killProjectionGeneration } from "./projection-kill.js";
|
|
13
|
-
export { observeProjectionLife } from "./projection-life-observer.js";
|
|
13
|
+
export { observeProjectionLife, observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
|
|
14
14
|
export { mintProjection, mintRepositoryProjection, ProjectionMintCollisionError, } from "./projection-mint.js";
|
|
15
15
|
export { acquireProjectionRunnerLock, initializeProjectionRunnerLockDatabase, isProjectionRunnerLockPlatformSupported, observeProjectionRunnerLock, projectionRunnerLockPath, } from "./projection-runner-lock.js";
|
|
16
16
|
export { hasStrandableProjectionGeneration, prioritizeProjectionStatusRows, readProjectionStatusBoard, } from "./projection-status.js";
|
|
17
17
|
export { projectTerminalFailure, } from "./projection-terminal-failure.js";
|
|
18
18
|
export { snapshotCommissionProjectionIds, waitForProjection, waitForProjectionSet, } from "./projection-wait.js";
|
|
19
|
-
export { assertTellEffortSupported, tellProjection, } from "./projection-wake.js";
|
|
19
|
+
export { assertTellEffortSupported, assertProjectionTellable, tellProjection, } from "./projection-wake.js";
|
|
20
20
|
export { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, ProjectionGenerationStore, } from "./generation/store.js";
|
|
21
21
|
export { createProjectionExecutionId } from "./generation/projection-generation-identity.js";
|
|
22
22
|
export { PROJECTION_ADOPTION_TIMEOUT_MS, startProjectionGeneration, } from "./generation/projection-generation-launcher.js";
|