@ranimontagna/agent-toolkit 0.2.3 → 0.3.1
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/CHANGELOG.md +16 -0
- package/README.md +45 -7
- package/dist/bin/agent-toolkit.js +9 -4
- package/dist/bin/agent-toolkit.js.map +1 -1
- package/dist/src/ariadne/args.d.ts +2 -0
- package/dist/src/ariadne/args.js +146 -0
- package/dist/src/ariadne/args.js.map +1 -0
- package/dist/src/ariadne/checks.d.ts +24 -0
- package/dist/src/ariadne/checks.js +74 -0
- package/dist/src/ariadne/checks.js.map +1 -0
- package/dist/src/ariadne/cli.d.ts +38 -0
- package/dist/src/ariadne/cli.js +206 -0
- package/dist/src/ariadne/cli.js.map +1 -0
- package/dist/src/ariadne/doctor.d.ts +16 -0
- package/dist/src/ariadne/doctor.js +207 -0
- package/dist/src/ariadne/doctor.js.map +1 -0
- package/dist/src/ariadne/git.d.ts +58 -0
- package/dist/src/ariadne/git.js +577 -0
- package/dist/src/ariadne/git.js.map +1 -0
- package/dist/src/ariadne/import.d.ts +2 -0
- package/dist/src/ariadne/import.js +93 -0
- package/dist/src/ariadne/import.js.map +1 -0
- package/dist/src/ariadne/init.d.ts +47 -0
- package/dist/src/ariadne/init.js +345 -0
- package/dist/src/ariadne/init.js.map +1 -0
- package/dist/src/ariadne/lock.d.ts +22 -0
- package/dist/src/ariadne/lock.js +478 -0
- package/dist/src/ariadne/lock.js.map +1 -0
- package/dist/src/ariadne/loop.d.ts +26 -0
- package/dist/src/ariadne/loop.js +1316 -0
- package/dist/src/ariadne/loop.js.map +1 -0
- package/dist/src/ariadne/ownership.d.ts +32 -0
- package/dist/src/ariadne/ownership.js +161 -0
- package/dist/src/ariadne/ownership.js.map +1 -0
- package/dist/src/ariadne/process.d.ts +27 -0
- package/dist/src/ariadne/process.js +302 -0
- package/dist/src/ariadne/process.js.map +1 -0
- package/dist/src/ariadne/prompt.d.ts +11 -0
- package/dist/src/ariadne/prompt.js +29 -0
- package/dist/src/ariadne/prompt.js.map +1 -0
- package/dist/src/ariadne/render.d.ts +7 -0
- package/dist/src/ariadne/render.js +72 -0
- package/dist/src/ariadne/render.js.map +1 -0
- package/dist/src/ariadne/result.d.ts +54 -0
- package/dist/src/ariadne/result.js +129 -0
- package/dist/src/ariadne/result.js.map +1 -0
- package/dist/src/ariadne/runtimes/antigravity.d.ts +2 -0
- package/dist/src/ariadne/runtimes/antigravity.js +17 -0
- package/dist/src/ariadne/runtimes/antigravity.js.map +1 -0
- package/dist/src/ariadne/runtimes/claude.d.ts +2 -0
- package/dist/src/ariadne/runtimes/claude.js +17 -0
- package/dist/src/ariadne/runtimes/claude.js.map +1 -0
- package/dist/src/ariadne/runtimes/codex.d.ts +2 -0
- package/dist/src/ariadne/runtimes/codex.js +24 -0
- package/dist/src/ariadne/runtimes/codex.js.map +1 -0
- package/dist/src/ariadne/runtimes/gemini.d.ts +2 -0
- package/dist/src/ariadne/runtimes/gemini.js +18 -0
- package/dist/src/ariadne/runtimes/gemini.js.map +1 -0
- package/dist/src/ariadne/runtimes/index.d.ts +4 -0
- package/dist/src/ariadne/runtimes/index.js +82 -0
- package/dist/src/ariadne/runtimes/index.js.map +1 -0
- package/dist/src/ariadne/runtimes/opencode.d.ts +2 -0
- package/dist/src/ariadne/runtimes/opencode.js +19 -0
- package/dist/src/ariadne/runtimes/opencode.js.map +1 -0
- package/dist/src/ariadne/runtimes/shared.d.ts +24 -0
- package/dist/src/ariadne/runtimes/shared.js +141 -0
- package/dist/src/ariadne/runtimes/shared.js.map +1 -0
- package/dist/src/ariadne/runtimes/types.d.ts +56 -0
- package/dist/src/ariadne/runtimes/types.js +3 -0
- package/dist/src/ariadne/runtimes/types.js.map +1 -0
- package/dist/src/ariadne/schema.d.ts +11 -0
- package/dist/src/ariadne/schema.js +214 -0
- package/dist/src/ariadne/schema.js.map +1 -0
- package/dist/src/ariadne/status.d.ts +72 -0
- package/dist/src/ariadne/status.js +280 -0
- package/dist/src/ariadne/status.js.map +1 -0
- package/dist/src/ariadne/store.d.ts +99 -0
- package/dist/src/ariadne/store.js +738 -0
- package/dist/src/ariadne/store.js.map +1 -0
- package/dist/src/ariadne/types.d.ts +128 -0
- package/dist/src/ariadne/types.js +5 -0
- package/dist/src/ariadne/types.js.map +1 -0
- package/dist/src/ariadne/usage.d.ts +1 -0
- package/dist/src/ariadne/usage.js +8 -0
- package/dist/src/ariadne/usage.js.map +1 -0
- package/dist/src/build-skills-index.d.ts +3 -0
- package/dist/src/build-skills-index.js +50 -0
- package/dist/src/build-skills-index.js.map +1 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.js +14 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/context.js.map +1 -1
- package/dist/src/lock-update.js.map +1 -1
- package/dist/src/main.js.map +1 -1
- package/dist/src/release.js.map +1 -1
- package/dist/src/skill-targets.js.map +1 -1
- package/dist/src/skills-audit.d.ts +6 -0
- package/dist/src/skills-audit.js +68 -2
- package/dist/src/skills-audit.js.map +1 -1
- package/dist/src/skills-index.d.ts +12 -0
- package/dist/src/skills-index.js +50 -0
- package/dist/src/skills-index.js.map +1 -0
- package/dist/src/stage-agent-skills.d.ts +1 -0
- package/dist/src/stage-agent-skills.js +32 -0
- package/dist/src/stage-agent-skills.js.map +1 -0
- package/dist/src/system.js.map +1 -1
- package/dist/src/tool-lock.d.ts +20 -0
- package/dist/src/tool-lock.js +26 -0
- package/dist/src/tool-lock.js.map +1 -1
- package/dist/src/usage.js +1 -0
- package/dist/src/usage.js.map +1 -1
- package/package.json +15 -8
- package/skills/frontend/accessibility/BRIEF.md +11 -0
- package/skills/frontend/accessibility/NOTICE.md +3 -0
- package/skills/frontend/accessibility/SKILL.md +10 -0
- package/skills/frontend/astro/astro-react-landing/BRIEF.md +10 -0
- package/skills/frontend/astro/astro-react-landing/SKILL.md +42 -0
- package/skills/frontend/design/frontend-design/BRIEF.md +10 -0
- package/skills/frontend/design/frontend-design/SKILL.md +38 -0
- package/skills/frontend/design/ui-ux-pro-max/BRIEF.md +11 -0
- package/skills/frontend/design/ui-ux-pro-max/NOTICE.md +3 -0
- package/skills/frontend/design/ui-ux-pro-max/SKILL.md +15 -0
- package/skills/frontend/gsap/gsap-motion/BRIEF.md +10 -0
- package/skills/frontend/gsap/gsap-motion/SKILL.md +36 -0
- package/skills/frontend/seo/seo-page/BRIEF.md +10 -0
- package/skills/frontend/seo/seo-page/SKILL.md +34 -0
- package/skills/quality/biome-formatting/BRIEF.md +9 -0
- package/skills/quality/biome-formatting/SKILL.md +27 -0
- package/skills/research/instagram-public-research/BRIEF.md +10 -0
- package/skills/research/instagram-public-research/SKILL.md +41 -0
- package/skills/research/research-data-collection/BRIEF.md +11 -0
- package/skills/research/research-data-collection/SKILL.md +40 -0
- package/skills/workflow/ariadne/NOTICE.md +11 -0
- package/skills/workflow/ariadne/SKILL.md +96 -0
- package/skills/workflow/ariadne-prd/NOTICE.md +11 -0
- package/skills/workflow/ariadne-prd/SKILL.md +69 -0
- package/skills.index.json +299 -0
- package/tools.lock.json +40 -21
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertOwnershipCertification, assertPersistedOwnership, captureOwnershipCertification, saveOwnershipCertification, } from "./ownership.js";
|
|
4
|
+
import { buildIterationPrompt } from "./prompt.js";
|
|
5
|
+
import { formatCoordinatorProgressEntry, formatProgressEntry, readAgentResult, sanitizeDurableValue, sanitizeQualityCheckResult, sanitizeValidatedResult, } from "./result.js";
|
|
6
|
+
import { AriadneStateError, assertRunnableConfig, assertStoryTransition, } from "./schema.js";
|
|
7
|
+
import { ARIADNE_OWNERSHIP_CHECKPOINT_MARKER } from "./store.js";
|
|
8
|
+
export const ARIADNE_EXIT_CODES = {
|
|
9
|
+
complete: 0,
|
|
10
|
+
incomplete: 1,
|
|
11
|
+
blocked: 1,
|
|
12
|
+
budget_exhausted: 1,
|
|
13
|
+
interrupted: 130,
|
|
14
|
+
structural_error: 4,
|
|
15
|
+
};
|
|
16
|
+
class RuntimeOwnershipViolationError extends AriadneStateError {
|
|
17
|
+
certifiedHead;
|
|
18
|
+
observedHead;
|
|
19
|
+
certifiedRef;
|
|
20
|
+
observedRef;
|
|
21
|
+
changed;
|
|
22
|
+
constructor(certifiedHead, observedHead, certifiedRef, observedRef, changed) {
|
|
23
|
+
super(changed.includes("head") ? "$git" : "$.ariadne", changed.includes("head")
|
|
24
|
+
? "Ariadne runtime changed Git HEAD; runtime agents must not commit"
|
|
25
|
+
: "Ariadne runtime edited canonical Ariadne state; only the coordinator may update prd.json or progress.md");
|
|
26
|
+
this.certifiedHead = certifiedHead;
|
|
27
|
+
this.observedHead = observedHead;
|
|
28
|
+
this.certifiedRef = certifiedRef;
|
|
29
|
+
this.observedRef = observedRef;
|
|
30
|
+
this.changed = changed;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function selectStory(prd) {
|
|
34
|
+
const active = prd.userStories.find((story) => story.status === "in_progress");
|
|
35
|
+
if (active)
|
|
36
|
+
return active;
|
|
37
|
+
let selected;
|
|
38
|
+
for (const story of prd.userStories) {
|
|
39
|
+
if (story.status !== "pending")
|
|
40
|
+
continue;
|
|
41
|
+
if (!selected || story.priority < selected.priority)
|
|
42
|
+
selected = story;
|
|
43
|
+
}
|
|
44
|
+
return selected;
|
|
45
|
+
}
|
|
46
|
+
function transitionStory(story, status) {
|
|
47
|
+
assertStoryTransition(story.status, status);
|
|
48
|
+
story.status = status;
|
|
49
|
+
}
|
|
50
|
+
function isProcessAlive(pid) {
|
|
51
|
+
try {
|
|
52
|
+
process.kill(pid, 0);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return error.code === "EPERM";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function summary(input) {
|
|
60
|
+
return {
|
|
61
|
+
schemaVersion: 1,
|
|
62
|
+
command: "run",
|
|
63
|
+
outcome: input.outcome,
|
|
64
|
+
runtime: input.options.runtime,
|
|
65
|
+
iterations: input.iterations,
|
|
66
|
+
completedStoryIds: input.completedStoryIds,
|
|
67
|
+
...(input.activeStoryId ? { activeStoryId: input.activeStoryId } : {}),
|
|
68
|
+
...(input.blockedStoryId ? { blockedStoryId: input.blockedStoryId } : {}),
|
|
69
|
+
...(input.lastRunId ? { lastRunId: input.lastRunId } : {}),
|
|
70
|
+
...(input.commit ? { commit: input.commit } : {}),
|
|
71
|
+
...(input.inspection ? { inspection: input.inspection } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function errorMessage(error) {
|
|
75
|
+
return error instanceof Error ? error.message : String(error);
|
|
76
|
+
}
|
|
77
|
+
function sanitizeAttemptFailure(failure) {
|
|
78
|
+
return { ...failure, message: sanitizeDurableValue(failure.message) };
|
|
79
|
+
}
|
|
80
|
+
const FAILURE_CATEGORIES = new Set([
|
|
81
|
+
"process",
|
|
82
|
+
"result",
|
|
83
|
+
"criterion",
|
|
84
|
+
"check",
|
|
85
|
+
"commit",
|
|
86
|
+
"invariant",
|
|
87
|
+
]);
|
|
88
|
+
function readPriorAttemptFailure(store, storyId) {
|
|
89
|
+
let runIds;
|
|
90
|
+
try {
|
|
91
|
+
runIds = fs.readdirSync(store.paths.runs);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error.code === "ENOENT")
|
|
95
|
+
return undefined;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
const failures = [];
|
|
99
|
+
for (const runId of runIds) {
|
|
100
|
+
const runDir = path.join(store.paths.runs, runId);
|
|
101
|
+
try {
|
|
102
|
+
if (!fs.lstatSync(runDir).isDirectory())
|
|
103
|
+
continue;
|
|
104
|
+
const attempt = JSON.parse(fs.readFileSync(path.join(runDir, "attempt.json"), "utf8"));
|
|
105
|
+
const failure = JSON.parse(fs.readFileSync(path.join(runDir, "failure.json"), "utf8"));
|
|
106
|
+
if (attempt.storyId !== storyId ||
|
|
107
|
+
failure.runId !== runId ||
|
|
108
|
+
typeof failure.category !== "string" ||
|
|
109
|
+
!FAILURE_CATEGORIES.has(failure.category) ||
|
|
110
|
+
typeof failure.message !== "string" ||
|
|
111
|
+
failure.message === "" ||
|
|
112
|
+
typeof failure.timestamp !== "string" ||
|
|
113
|
+
Number.isNaN(Date.parse(failure.timestamp))) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
failures.push(sanitizeAttemptFailure(failure));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code === "ENOENT")
|
|
120
|
+
continue;
|
|
121
|
+
if (error instanceof SyntaxError)
|
|
122
|
+
continue;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return failures.sort((left, right) => Date.parse(right.timestamp) - Date.parse(left.timestamp))[0];
|
|
127
|
+
}
|
|
128
|
+
export function recordFailedAttempt(prd, storyId, failure, maxAttempts) {
|
|
129
|
+
const story = prd.userStories.find((candidate) => candidate.id === storyId);
|
|
130
|
+
if (!story) {
|
|
131
|
+
throw new Error(`Unable to record ${failure.category} failure for missing story ${storyId}`);
|
|
132
|
+
}
|
|
133
|
+
const blocked = story.attempts >= maxAttempts;
|
|
134
|
+
transitionStory(story, blocked ? "blocked" : "in_progress");
|
|
135
|
+
return { prd, blocked };
|
|
136
|
+
}
|
|
137
|
+
function assertAdapterMatches(options, deps) {
|
|
138
|
+
if (deps.adapter.name !== options.runtime) {
|
|
139
|
+
throw new Error(`Ariadne runtime adapter mismatch: expected ${options.runtime}, received ${deps.adapter.name}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function acquireIterationLock(runId, deps) {
|
|
143
|
+
return deps.acquireLock({
|
|
144
|
+
lockPath: deps.store.paths.lock,
|
|
145
|
+
runId,
|
|
146
|
+
runDir: path.join(deps.store.paths.runs, runId),
|
|
147
|
+
pid: process.pid,
|
|
148
|
+
now: deps.now,
|
|
149
|
+
isProcessAlive,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function sanitizeInvocation(invocation) {
|
|
153
|
+
const credential = /(?:api[-_]?key|token|password|secret|authorization|credential)/i;
|
|
154
|
+
let redactNext = false;
|
|
155
|
+
const args = invocation.args.map((argument) => {
|
|
156
|
+
if (redactNext) {
|
|
157
|
+
redactNext = false;
|
|
158
|
+
return "[REDACTED]";
|
|
159
|
+
}
|
|
160
|
+
const equals = argument.indexOf("=");
|
|
161
|
+
if (equals > 0 && credential.test(argument.slice(0, equals))) {
|
|
162
|
+
return `${argument.slice(0, equals + 1)}[REDACTED]`;
|
|
163
|
+
}
|
|
164
|
+
if (credential.test(argument) && argument.startsWith("-")) {
|
|
165
|
+
redactNext = true;
|
|
166
|
+
}
|
|
167
|
+
return argument;
|
|
168
|
+
});
|
|
169
|
+
return { command: invocation.command, args, cwd: invocation.cwd };
|
|
170
|
+
}
|
|
171
|
+
function captureCanonicalSnapshot(deps, prd, expectedBranch, progress) {
|
|
172
|
+
const initialRef = deps.git.headRef();
|
|
173
|
+
const initialHead = deps.git.head();
|
|
174
|
+
if (deps.git.headRef() !== initialRef || deps.git.head() !== initialHead) {
|
|
175
|
+
throw new AriadneStateError("$git", "Ariadne symbolic HEAD changed while the runtime boundary was certified");
|
|
176
|
+
}
|
|
177
|
+
if (expectedBranch !== undefined &&
|
|
178
|
+
initialRef !== `refs/heads/${expectedBranch}`) {
|
|
179
|
+
throw new AriadneStateError("$git", `Ariadne expected symbolic HEAD refs/heads/${expectedBranch}, found ${initialRef}`);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
initialHead,
|
|
183
|
+
initialRef,
|
|
184
|
+
prd,
|
|
185
|
+
progress: progress ??
|
|
186
|
+
deps.store.captureCanonicalCertificate(deps.store.paths.progress),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function assertRuntimeOwnership(snapshot, deps) {
|
|
190
|
+
const changed = [];
|
|
191
|
+
let observedHead = "unavailable";
|
|
192
|
+
let observedRef = "unavailable";
|
|
193
|
+
try {
|
|
194
|
+
observedRef = deps.git.headRef();
|
|
195
|
+
observedHead = deps.git.head();
|
|
196
|
+
if (deps.git.headRef() !== observedRef ||
|
|
197
|
+
deps.git.head() !== observedHead ||
|
|
198
|
+
observedHead !== snapshot.initialHead ||
|
|
199
|
+
observedRef !== snapshot.initialRef) {
|
|
200
|
+
changed.push("head");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
changed.push("head");
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
deps.store.assertCanonicalCertificate(snapshot.prd);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
changed.push("prd");
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
deps.store.assertCanonicalCertificate(snapshot.progress);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
changed.push("progress");
|
|
217
|
+
}
|
|
218
|
+
if (changed.length > 0) {
|
|
219
|
+
throw new RuntimeOwnershipViolationError(snapshot.initialHead, observedHead, snapshot.initialRef, observedRef, changed);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function inspectDryRun(options, deps) {
|
|
223
|
+
const config = deps.store.loadConfig();
|
|
224
|
+
const prd = deps.store.loadPrd();
|
|
225
|
+
const blocked = prd.userStories.find((story) => story.status === "blocked");
|
|
226
|
+
const active = blocked ?? selectStory(prd);
|
|
227
|
+
deps.git.assertReady(prd.branchName, prd.userStories.some((story) => story.status === "in_progress" || story.status === "blocked"));
|
|
228
|
+
const promptPath = path.join(deps.store.paths.runs, "dry-run", "prompt.md");
|
|
229
|
+
const invocation = deps.adapter.buildInvocation({
|
|
230
|
+
runId: "dry-run",
|
|
231
|
+
projectRoot: deps.store.projectRoot,
|
|
232
|
+
promptPath,
|
|
233
|
+
relativePromptPath: path.relative(deps.store.projectRoot, promptPath),
|
|
234
|
+
});
|
|
235
|
+
const detection = deps.detection ?? deps.adapter.detect();
|
|
236
|
+
const inspection = {
|
|
237
|
+
project: {
|
|
238
|
+
root: deps.store.projectRoot,
|
|
239
|
+
name: prd.project,
|
|
240
|
+
branch: prd.branchName,
|
|
241
|
+
},
|
|
242
|
+
selectedStory: active ? { ...active } : null,
|
|
243
|
+
blockedStory: blocked ? { ...blocked } : null,
|
|
244
|
+
promptPath,
|
|
245
|
+
invocation: sanitizeInvocation(invocation),
|
|
246
|
+
runtime: {
|
|
247
|
+
name: detection.name,
|
|
248
|
+
state: detection.state,
|
|
249
|
+
...(detection.version ? { version: detection.version } : {}),
|
|
250
|
+
},
|
|
251
|
+
checks: [...config.qualityChecks],
|
|
252
|
+
limits: {
|
|
253
|
+
maxAttemptsPerStory: config.maxAttemptsPerStory,
|
|
254
|
+
maxIterations: options.maxIterations ?? null,
|
|
255
|
+
maxRuntimeMs: options.maxRuntimeMs ?? null,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
return summary({
|
|
259
|
+
options,
|
|
260
|
+
outcome: blocked ? "blocked" : active ? "incomplete" : "complete",
|
|
261
|
+
iterations: 0,
|
|
262
|
+
completedStoryIds: [],
|
|
263
|
+
...(active ? { activeStoryId: active.id } : {}),
|
|
264
|
+
...(blocked ? { blockedStoryId: blocked.id } : {}),
|
|
265
|
+
inspection,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
export async function runAriadneLoop(options, deps) {
|
|
269
|
+
assertAdapterMatches(options, deps);
|
|
270
|
+
if (deps.selectionCertification) {
|
|
271
|
+
assertOwnershipCertification({
|
|
272
|
+
store: deps.store,
|
|
273
|
+
git: deps.git,
|
|
274
|
+
now: deps.now,
|
|
275
|
+
certification: deps.selectionCertification,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
assertPersistedOwnership({ store: deps.store, git: deps.git, now: deps.now });
|
|
279
|
+
if (options.dryRun) {
|
|
280
|
+
const certification = deps.selectionCertification ??
|
|
281
|
+
captureOwnershipCertification({
|
|
282
|
+
store: deps.store,
|
|
283
|
+
git: deps.git,
|
|
284
|
+
runId: "dry-run",
|
|
285
|
+
storyId: selectStory(deps.store.loadPrd())?.id ?? "dry-run",
|
|
286
|
+
});
|
|
287
|
+
const inspection = inspectDryRun(options, deps);
|
|
288
|
+
assertOwnershipCertification({
|
|
289
|
+
store: deps.store,
|
|
290
|
+
git: deps.git,
|
|
291
|
+
now: deps.now,
|
|
292
|
+
certification,
|
|
293
|
+
});
|
|
294
|
+
return inspection;
|
|
295
|
+
}
|
|
296
|
+
const completedStoryIds = [];
|
|
297
|
+
let iterations = 0;
|
|
298
|
+
let lastRunId;
|
|
299
|
+
let commit;
|
|
300
|
+
let currentAttempt;
|
|
301
|
+
let activeCertification;
|
|
302
|
+
const finalizedAttemptRunIds = new Set();
|
|
303
|
+
const startedAt = deps.now().getTime();
|
|
304
|
+
const priorFailures = new Map();
|
|
305
|
+
let parentSignal;
|
|
306
|
+
const onParentSigint = () => {
|
|
307
|
+
parentSignal = "SIGINT";
|
|
308
|
+
};
|
|
309
|
+
const onParentSigterm = () => {
|
|
310
|
+
parentSignal = "SIGTERM";
|
|
311
|
+
};
|
|
312
|
+
const runtimeBudgetReached = () => options.maxRuntimeMs !== undefined &&
|
|
313
|
+
deps.now().getTime() - startedAt >= options.maxRuntimeMs;
|
|
314
|
+
const persistCertification = (certification) => {
|
|
315
|
+
activeCertification = certification;
|
|
316
|
+
try {
|
|
317
|
+
saveOwnershipCertification({
|
|
318
|
+
store: deps.store,
|
|
319
|
+
certification,
|
|
320
|
+
now: deps.now,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
// Convert a concurrent canonical/ref mutation into the precise ownership
|
|
325
|
+
// surface before the outer containment handler persists quarantine.
|
|
326
|
+
assertRuntimeOwnership({
|
|
327
|
+
initialHead: certification.certifiedHead,
|
|
328
|
+
initialRef: certification.certifiedRef,
|
|
329
|
+
prd: certification.prd,
|
|
330
|
+
progress: certification.progress,
|
|
331
|
+
}, deps);
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const activeStoryId = () => {
|
|
336
|
+
const prd = deps.store.loadPrd();
|
|
337
|
+
return selectStory(prd)?.id;
|
|
338
|
+
};
|
|
339
|
+
const finishAttempt = (outcome, failureCategory) => {
|
|
340
|
+
if (!currentAttempt)
|
|
341
|
+
return;
|
|
342
|
+
const attempt = currentAttempt;
|
|
343
|
+
const finishedAt = deps.now();
|
|
344
|
+
let finalHead;
|
|
345
|
+
try {
|
|
346
|
+
finalHead = deps.git.head();
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// Preserve the original failure if Git itself is no longer readable.
|
|
350
|
+
}
|
|
351
|
+
deps.store.writeRunJson(attempt.runId, "summary.json", {
|
|
352
|
+
schemaVersion: 1,
|
|
353
|
+
runId: attempt.runId,
|
|
354
|
+
storyId: attempt.storyId,
|
|
355
|
+
runtime: options.runtime,
|
|
356
|
+
...(attempt.runtimeVersion
|
|
357
|
+
? { runtimeVersion: attempt.runtimeVersion }
|
|
358
|
+
: {}),
|
|
359
|
+
attempt: attempt.attempt,
|
|
360
|
+
invocation: attempt.invocation,
|
|
361
|
+
startedAt: attempt.startedAt,
|
|
362
|
+
finishedAt: finishedAt.toISOString(),
|
|
363
|
+
durationMs: Math.max(0, finishedAt.getTime() - attempt.startedMs),
|
|
364
|
+
outcome,
|
|
365
|
+
...(failureCategory ? { failureCategory } : {}),
|
|
366
|
+
initialHead: attempt.initialHead,
|
|
367
|
+
initialRef: attempt.initialRef,
|
|
368
|
+
...(finalHead ? { finalHead } : {}),
|
|
369
|
+
...(attempt.process ? { process: attempt.process } : {}),
|
|
370
|
+
...(attempt.validatedResult
|
|
371
|
+
? { validatedResult: attempt.validatedResult }
|
|
372
|
+
: {}),
|
|
373
|
+
...(attempt.checks ? { checks: attempt.checks } : {}),
|
|
374
|
+
});
|
|
375
|
+
finalizedAttemptRunIds.add(attempt.runId);
|
|
376
|
+
currentAttempt = undefined;
|
|
377
|
+
};
|
|
378
|
+
const finishCoordinatorRun = (runId, storyId, outcome, reason) => {
|
|
379
|
+
const timestamp = deps.now().toISOString();
|
|
380
|
+
let head;
|
|
381
|
+
try {
|
|
382
|
+
head = deps.git.head();
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// The stop record remains useful when Git metadata is unavailable.
|
|
386
|
+
}
|
|
387
|
+
deps.store.writeRunJson(runId, "summary.json", {
|
|
388
|
+
schemaVersion: 1,
|
|
389
|
+
runId,
|
|
390
|
+
...(storyId ? { storyId } : {}),
|
|
391
|
+
runtime: options.runtime,
|
|
392
|
+
startedAt: timestamp,
|
|
393
|
+
finishedAt: timestamp,
|
|
394
|
+
durationMs: 0,
|
|
395
|
+
outcome,
|
|
396
|
+
reason,
|
|
397
|
+
...(head ? { initialHead: head, finalHead: head } : {}),
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const stop = (outcome, reason, runId, storyId) => {
|
|
401
|
+
const result = summary({
|
|
402
|
+
options,
|
|
403
|
+
outcome,
|
|
404
|
+
iterations,
|
|
405
|
+
completedStoryIds,
|
|
406
|
+
...(storyId ? { activeStoryId: storyId } : {}),
|
|
407
|
+
...((runId ?? lastRunId) ? { lastRunId: runId ?? lastRunId } : {}),
|
|
408
|
+
...(commit ? { commit } : {}),
|
|
409
|
+
});
|
|
410
|
+
if (runId) {
|
|
411
|
+
deps.store.writeRunJson(runId, "stop.json", {
|
|
412
|
+
schemaVersion: 1,
|
|
413
|
+
outcome,
|
|
414
|
+
reason,
|
|
415
|
+
timestamp: deps.now().toISOString(),
|
|
416
|
+
iterations,
|
|
417
|
+
});
|
|
418
|
+
if (currentAttempt?.runId === runId)
|
|
419
|
+
finishAttempt(outcome);
|
|
420
|
+
else if (!finalizedAttemptRunIds.has(runId)) {
|
|
421
|
+
finishCoordinatorRun(runId, storyId, outcome, reason);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
};
|
|
426
|
+
const failAttempt = (prd, story, failure, maxAttempts, evidence = {}) => {
|
|
427
|
+
const durableFailure = sanitizeAttemptFailure(failure);
|
|
428
|
+
const state = recordFailedAttempt(prd, story.id, durableFailure, maxAttempts);
|
|
429
|
+
const prdCertificate = deps.store.savePrd(state.prd);
|
|
430
|
+
deps.store.writeRunJson(durableFailure.runId, "failure.json", durableFailure);
|
|
431
|
+
const progressCertificate = deps.store.appendProgress(evidence.result
|
|
432
|
+
? formatProgressEntry({
|
|
433
|
+
timestamp: durableFailure.timestamp,
|
|
434
|
+
runId: durableFailure.runId,
|
|
435
|
+
story,
|
|
436
|
+
runtime: options.runtime,
|
|
437
|
+
result: evidence.result,
|
|
438
|
+
checks: evidence.checks ?? [],
|
|
439
|
+
outcome: state.blocked ? "blocked" : "failed",
|
|
440
|
+
failureCategory: durableFailure.category,
|
|
441
|
+
failureReason: durableFailure.message,
|
|
442
|
+
})
|
|
443
|
+
: formatCoordinatorProgressEntry({
|
|
444
|
+
timestamp: durableFailure.timestamp,
|
|
445
|
+
runId: durableFailure.runId,
|
|
446
|
+
storyId: story.id,
|
|
447
|
+
runtime: options.runtime,
|
|
448
|
+
outcome: state.blocked ? "blocked" : "failed",
|
|
449
|
+
failureCategory: durableFailure.category,
|
|
450
|
+
failureReason: durableFailure.message,
|
|
451
|
+
}));
|
|
452
|
+
if (activeCertification?.runId === durableFailure.runId &&
|
|
453
|
+
activeCertification.storyId === story.id) {
|
|
454
|
+
persistCertification({
|
|
455
|
+
...activeCertification,
|
|
456
|
+
prd: prdCertificate,
|
|
457
|
+
progress: progressCertificate,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
priorFailures.set(story.id, durableFailure);
|
|
461
|
+
finishAttempt(state.blocked ? "blocked" : "failed", durableFailure.category);
|
|
462
|
+
return state.blocked;
|
|
463
|
+
};
|
|
464
|
+
const blockedSummary = (storyId, runId) => {
|
|
465
|
+
const result = summary({
|
|
466
|
+
options,
|
|
467
|
+
outcome: "blocked",
|
|
468
|
+
iterations,
|
|
469
|
+
completedStoryIds,
|
|
470
|
+
activeStoryId: storyId,
|
|
471
|
+
blockedStoryId: storyId,
|
|
472
|
+
lastRunId: runId,
|
|
473
|
+
...(commit ? { commit } : {}),
|
|
474
|
+
});
|
|
475
|
+
deps.store.writeRunJson(runId, "stop.json", {
|
|
476
|
+
schemaVersion: 1,
|
|
477
|
+
outcome: "blocked",
|
|
478
|
+
reason: "max_attempts",
|
|
479
|
+
timestamp: deps.now().toISOString(),
|
|
480
|
+
iterations,
|
|
481
|
+
});
|
|
482
|
+
finishAttempt("blocked");
|
|
483
|
+
return result;
|
|
484
|
+
};
|
|
485
|
+
const recordTerminalFailure = (story, failure, outcome, evidence = {}) => {
|
|
486
|
+
const durableFailure = sanitizeAttemptFailure(failure);
|
|
487
|
+
deps.store.writeRunJson(durableFailure.runId, "failure.json", durableFailure);
|
|
488
|
+
const progressCertificate = deps.store.appendProgress(evidence.result
|
|
489
|
+
? formatProgressEntry({
|
|
490
|
+
timestamp: durableFailure.timestamp,
|
|
491
|
+
runId: durableFailure.runId,
|
|
492
|
+
story,
|
|
493
|
+
runtime: options.runtime,
|
|
494
|
+
result: evidence.result,
|
|
495
|
+
checks: evidence.checks ?? [],
|
|
496
|
+
outcome,
|
|
497
|
+
failureCategory: durableFailure.category,
|
|
498
|
+
failureReason: durableFailure.message,
|
|
499
|
+
})
|
|
500
|
+
: formatCoordinatorProgressEntry({
|
|
501
|
+
timestamp: durableFailure.timestamp,
|
|
502
|
+
runId: durableFailure.runId,
|
|
503
|
+
storyId: story.id,
|
|
504
|
+
runtime: options.runtime,
|
|
505
|
+
outcome,
|
|
506
|
+
failureCategory: durableFailure.category,
|
|
507
|
+
failureReason: durableFailure.message,
|
|
508
|
+
}));
|
|
509
|
+
if (activeCertification?.runId === durableFailure.runId &&
|
|
510
|
+
activeCertification.storyId === story.id) {
|
|
511
|
+
persistCertification({
|
|
512
|
+
...activeCertification,
|
|
513
|
+
progress: progressCertificate,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
const rejectOwnershipViolation = (error, story, runId) => {
|
|
518
|
+
const failure = sanitizeAttemptFailure({
|
|
519
|
+
runId,
|
|
520
|
+
category: "invariant",
|
|
521
|
+
message: errorMessage(error),
|
|
522
|
+
timestamp: deps.now().toISOString(),
|
|
523
|
+
});
|
|
524
|
+
let observedHead = "unavailable";
|
|
525
|
+
let observedRef = "unavailable";
|
|
526
|
+
try {
|
|
527
|
+
observedHead = deps.git.head();
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// The durable marker must still survive unreadable Git metadata.
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
observedRef = deps.git.headRef();
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// The durable marker must still survive detached/corrupt HEAD.
|
|
537
|
+
}
|
|
538
|
+
const certifiedHead = error instanceof RuntimeOwnershipViolationError
|
|
539
|
+
? error.certifiedHead
|
|
540
|
+
: (currentAttempt?.initialHead ?? "unavailable");
|
|
541
|
+
const certifiedRef = error instanceof RuntimeOwnershipViolationError
|
|
542
|
+
? error.certifiedRef
|
|
543
|
+
: (currentAttempt?.initialRef ?? "unavailable");
|
|
544
|
+
deps.store.saveOwnershipViolation({
|
|
545
|
+
schemaVersion: 1,
|
|
546
|
+
runId,
|
|
547
|
+
storyId: story.id,
|
|
548
|
+
detectedAt: failure.timestamp,
|
|
549
|
+
certifiedHead,
|
|
550
|
+
observedHead: error instanceof RuntimeOwnershipViolationError
|
|
551
|
+
? error.observedHead
|
|
552
|
+
: observedHead,
|
|
553
|
+
certifiedRef,
|
|
554
|
+
observedRef: error instanceof RuntimeOwnershipViolationError
|
|
555
|
+
? error.observedRef
|
|
556
|
+
: observedRef,
|
|
557
|
+
changed: error instanceof RuntimeOwnershipViolationError
|
|
558
|
+
? [...error.changed]
|
|
559
|
+
: ["operational"],
|
|
560
|
+
});
|
|
561
|
+
try {
|
|
562
|
+
deps.store.writeRunJson(runId, "failure.json", failure);
|
|
563
|
+
finishAttempt("structural_error", "invariant");
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// A containment violation may make this run directory unsafe. The
|
|
567
|
+
// quarantine marker above is persisted independently at the project root;
|
|
568
|
+
// never follow the compromised run path merely to finish diagnostics.
|
|
569
|
+
}
|
|
570
|
+
// Canonical state may be attacker-controlled here, so do not load, save,
|
|
571
|
+
// append, stage, or reset it. Preserve the evidence for manual recovery.
|
|
572
|
+
throw error instanceof AriadneStateError
|
|
573
|
+
? error
|
|
574
|
+
: new AriadneStateError(`$.userStories[${story.id}]`, failure.message);
|
|
575
|
+
};
|
|
576
|
+
const releaseIterationLock = (lock, story, runId) => {
|
|
577
|
+
try {
|
|
578
|
+
lock.release();
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
if (story && !deps.store.loadOwnershipViolation()) {
|
|
582
|
+
rejectOwnershipViolation(error, story, runId);
|
|
583
|
+
}
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
process.on("SIGINT", onParentSigint);
|
|
588
|
+
process.on("SIGTERM", onParentSigterm);
|
|
589
|
+
let recheckPersistedBoundary = false;
|
|
590
|
+
try {
|
|
591
|
+
for (;;) {
|
|
592
|
+
if (recheckPersistedBoundary) {
|
|
593
|
+
assertPersistedOwnership({
|
|
594
|
+
store: deps.store,
|
|
595
|
+
git: deps.git,
|
|
596
|
+
now: deps.now,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
recheckPersistedBoundary = true;
|
|
600
|
+
if (options.signal?.aborted || parentSignal) {
|
|
601
|
+
return stop("interrupted", options.signal?.aborted ? "cancelled" : "signal", lastRunId, activeStoryId());
|
|
602
|
+
}
|
|
603
|
+
if (options.maxIterations !== undefined &&
|
|
604
|
+
iterations >= options.maxIterations) {
|
|
605
|
+
return stop("budget_exhausted", "max_iterations", lastRunId, activeStoryId());
|
|
606
|
+
}
|
|
607
|
+
if (runtimeBudgetReached()) {
|
|
608
|
+
return stop("budget_exhausted", "max_runtime", lastRunId, activeStoryId());
|
|
609
|
+
}
|
|
610
|
+
const runId = deps.createRunId();
|
|
611
|
+
const lock = acquireIterationLock(runId, deps);
|
|
612
|
+
let lockedStory;
|
|
613
|
+
try {
|
|
614
|
+
const config = deps.store.loadConfig();
|
|
615
|
+
const prd = deps.store.loadPrd();
|
|
616
|
+
assertRunnableConfig(config);
|
|
617
|
+
const hasPreservedDiff = prd.userStories.some((story) => story.status === "in_progress" || story.status === "blocked");
|
|
618
|
+
deps.git.assertReady(prd.branchName, hasPreservedDiff);
|
|
619
|
+
const blockedStory = prd.userStories.find((story) => story.status === "blocked");
|
|
620
|
+
if (blockedStory) {
|
|
621
|
+
return summary({
|
|
622
|
+
options,
|
|
623
|
+
outcome: "blocked",
|
|
624
|
+
iterations,
|
|
625
|
+
completedStoryIds,
|
|
626
|
+
activeStoryId: blockedStory.id,
|
|
627
|
+
blockedStoryId: blockedStory.id,
|
|
628
|
+
...(lastRunId ? { lastRunId } : {}),
|
|
629
|
+
...(commit ? { commit } : {}),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
const activeStory = selectStory(prd);
|
|
633
|
+
if (!activeStory) {
|
|
634
|
+
return summary({
|
|
635
|
+
options,
|
|
636
|
+
outcome: "complete",
|
|
637
|
+
iterations,
|
|
638
|
+
completedStoryIds,
|
|
639
|
+
...(lastRunId ? { lastRunId } : {}),
|
|
640
|
+
...(commit ? { commit } : {}),
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
lockedStory = activeStory;
|
|
644
|
+
if (activeStory.status === "in_progress" &&
|
|
645
|
+
activeStory.attempts >= config.maxAttemptsPerStory) {
|
|
646
|
+
transitionStory(activeStory, "blocked");
|
|
647
|
+
const blockedPrdCertificate = deps.store.savePrd(prd);
|
|
648
|
+
deps.store.writeRunJson(runId, "stop.json", {
|
|
649
|
+
schemaVersion: 1,
|
|
650
|
+
outcome: "blocked",
|
|
651
|
+
reason: "max_attempts",
|
|
652
|
+
timestamp: deps.now().toISOString(),
|
|
653
|
+
iterations,
|
|
654
|
+
});
|
|
655
|
+
finishCoordinatorRun(runId, activeStory.id, "blocked", "max_attempts");
|
|
656
|
+
const blockedSnapshot = captureCanonicalSnapshot(deps, blockedPrdCertificate, prd.branchName);
|
|
657
|
+
persistCertification({
|
|
658
|
+
runId,
|
|
659
|
+
storyId: activeStory.id,
|
|
660
|
+
certifiedHead: blockedSnapshot.initialHead,
|
|
661
|
+
certifiedRef: blockedSnapshot.initialRef,
|
|
662
|
+
prd: blockedSnapshot.prd,
|
|
663
|
+
progress: blockedSnapshot.progress,
|
|
664
|
+
});
|
|
665
|
+
return summary({
|
|
666
|
+
options,
|
|
667
|
+
outcome: "blocked",
|
|
668
|
+
iterations,
|
|
669
|
+
completedStoryIds,
|
|
670
|
+
activeStoryId: activeStory.id,
|
|
671
|
+
blockedStoryId: activeStory.id,
|
|
672
|
+
lastRunId: runId,
|
|
673
|
+
...(commit ? { commit } : {}),
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
if (runtimeBudgetReached()) {
|
|
677
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
678
|
+
}
|
|
679
|
+
if (options.signal?.aborted || parentSignal) {
|
|
680
|
+
return stop("interrupted", options.signal?.aborted ? "cancelled" : "signal", runId, activeStory.id);
|
|
681
|
+
}
|
|
682
|
+
if (options.persistRuntimeSelection &&
|
|
683
|
+
config.runtime !== options.runtime) {
|
|
684
|
+
config.runtime = options.runtime;
|
|
685
|
+
deps.store.saveConfig(config);
|
|
686
|
+
}
|
|
687
|
+
const continuedStory = activeStory.status === "in_progress";
|
|
688
|
+
transitionStory(activeStory, "in_progress");
|
|
689
|
+
const preProbePrdCertificate = deps.store.savePrd(prd);
|
|
690
|
+
let preProbeProgressCertificate = deps.store.captureCanonicalCertificate(deps.store.paths.progress);
|
|
691
|
+
if (!preProbeProgressCertificate.contents.includes(ARIADNE_OWNERSHIP_CHECKPOINT_MARKER)) {
|
|
692
|
+
preProbeProgressCertificate = deps.store.appendProgress(ARIADNE_OWNERSHIP_CHECKPOINT_MARKER, preProbeProgressCertificate);
|
|
693
|
+
}
|
|
694
|
+
const runDir = deps.store.createRunDir(runId);
|
|
695
|
+
const promptPath = path.join(runDir, "prompt.md");
|
|
696
|
+
const resultPath = path.join(runDir, "result.json");
|
|
697
|
+
const priorFailure = priorFailures.get(activeStory.id) ??
|
|
698
|
+
readPriorAttemptFailure(deps.store, activeStory.id);
|
|
699
|
+
const prompt = buildIterationPrompt({
|
|
700
|
+
runId,
|
|
701
|
+
story: activeStory,
|
|
702
|
+
projectRoot: deps.store.projectRoot,
|
|
703
|
+
resultPath,
|
|
704
|
+
qualityChecks: config.qualityChecks,
|
|
705
|
+
...(priorFailure ? { priorFailure: priorFailure.message } : {}),
|
|
706
|
+
hasExistingDiff: continuedStory,
|
|
707
|
+
});
|
|
708
|
+
const promptIdentity = deps.store.writeRunTextExclusive(runId, "prompt.md", prompt);
|
|
709
|
+
const resultIdentity = deps.store.writeRunTextExclusive(runId, "result.json", "");
|
|
710
|
+
const invocation = deps.adapter.buildInvocation({
|
|
711
|
+
runId,
|
|
712
|
+
projectRoot: deps.store.projectRoot,
|
|
713
|
+
promptPath,
|
|
714
|
+
relativePromptPath: path.relative(deps.store.projectRoot, promptPath),
|
|
715
|
+
});
|
|
716
|
+
const preProbeSnapshot = captureCanonicalSnapshot(deps, preProbePrdCertificate, prd.branchName, preProbeProgressCertificate);
|
|
717
|
+
persistCertification({
|
|
718
|
+
runId,
|
|
719
|
+
storyId: activeStory.id,
|
|
720
|
+
certifiedHead: preProbeSnapshot.initialHead,
|
|
721
|
+
certifiedRef: preProbeSnapshot.initialRef,
|
|
722
|
+
prd: preProbeSnapshot.prd,
|
|
723
|
+
progress: preProbeSnapshot.progress,
|
|
724
|
+
});
|
|
725
|
+
const detection = deps.detection ?? deps.adapter.detect();
|
|
726
|
+
try {
|
|
727
|
+
assertRuntimeOwnership(preProbeSnapshot, deps);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
731
|
+
}
|
|
732
|
+
const attempt = activeStory.attempts + 1;
|
|
733
|
+
activeStory.attempts = attempt;
|
|
734
|
+
const activePrdCertificate = deps.store.savePrd(prd);
|
|
735
|
+
const canonicalSnapshot = {
|
|
736
|
+
...preProbeSnapshot,
|
|
737
|
+
prd: activePrdCertificate,
|
|
738
|
+
};
|
|
739
|
+
persistCertification({
|
|
740
|
+
runId,
|
|
741
|
+
storyId: activeStory.id,
|
|
742
|
+
certifiedHead: canonicalSnapshot.initialHead,
|
|
743
|
+
certifiedRef: canonicalSnapshot.initialRef,
|
|
744
|
+
prd: canonicalSnapshot.prd,
|
|
745
|
+
progress: canonicalSnapshot.progress,
|
|
746
|
+
});
|
|
747
|
+
const attemptStartedAt = deps.now();
|
|
748
|
+
currentAttempt = {
|
|
749
|
+
runId,
|
|
750
|
+
storyId: activeStory.id,
|
|
751
|
+
attempt,
|
|
752
|
+
startedAt: attemptStartedAt.toISOString(),
|
|
753
|
+
startedMs: attemptStartedAt.getTime(),
|
|
754
|
+
...(detection.version ? { runtimeVersion: detection.version } : {}),
|
|
755
|
+
initialHead: canonicalSnapshot.initialHead,
|
|
756
|
+
initialRef: canonicalSnapshot.initialRef,
|
|
757
|
+
invocation: sanitizeInvocation(invocation),
|
|
758
|
+
};
|
|
759
|
+
deps.store.writeRunJson(runId, "attempt.json", {
|
|
760
|
+
schemaVersion: 1,
|
|
761
|
+
runId,
|
|
762
|
+
storyId: activeStory.id,
|
|
763
|
+
runtime: options.runtime,
|
|
764
|
+
...(detection.version ? { runtimeVersion: detection.version } : {}),
|
|
765
|
+
attempt,
|
|
766
|
+
startedAt: attemptStartedAt.toISOString(),
|
|
767
|
+
initialHead: canonicalSnapshot.initialHead,
|
|
768
|
+
initialRef: canonicalSnapshot.initialRef,
|
|
769
|
+
invocation: currentAttempt.invocation,
|
|
770
|
+
});
|
|
771
|
+
let runBoundary = deps.store.certifyRunBoundary(runId, ["prompt.md", "result.json", "attempt.json"], [promptIdentity, resultIdentity]);
|
|
772
|
+
const assertOwnershipBoundary = (snapshot) => {
|
|
773
|
+
try {
|
|
774
|
+
assertRuntimeOwnership(snapshot, deps);
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const assertOperationalBoundary = (boundary = runBoundary) => {
|
|
781
|
+
try {
|
|
782
|
+
lock.assertIntegrity();
|
|
783
|
+
deps.store.assertRunBoundary(boundary);
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
const assertAttemptBoundary = (snapshot = canonicalSnapshot, boundary = runBoundary) => {
|
|
790
|
+
// Ownership must be evaluated first so a simultaneous containment
|
|
791
|
+
// violation cannot suppress the durable quarantine marker.
|
|
792
|
+
assertOwnershipBoundary(snapshot);
|
|
793
|
+
assertOperationalBoundary(boundary);
|
|
794
|
+
};
|
|
795
|
+
assertAttemptBoundary();
|
|
796
|
+
iterations += 1;
|
|
797
|
+
lastRunId = runId;
|
|
798
|
+
const remainingRuntime = options.maxRuntimeMs === undefined
|
|
799
|
+
? undefined
|
|
800
|
+
: Math.max(1, options.maxRuntimeMs - (deps.now().getTime() - startedAt));
|
|
801
|
+
const processPaths = {
|
|
802
|
+
stdoutPath: path.join(runDir, "runtime.stdout.log"),
|
|
803
|
+
stderrPath: path.join(runDir, "runtime.stderr.log"),
|
|
804
|
+
};
|
|
805
|
+
let processResult;
|
|
806
|
+
let processError;
|
|
807
|
+
let processFailed = false;
|
|
808
|
+
let processOutputCertified = false;
|
|
809
|
+
try {
|
|
810
|
+
processResult = await deps.runProcess(invocation, {
|
|
811
|
+
...processPaths,
|
|
812
|
+
...(remainingRuntime === undefined
|
|
813
|
+
? {}
|
|
814
|
+
: { timeoutMs: remainingRuntime }),
|
|
815
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
816
|
+
certifyOutput: (identities) => {
|
|
817
|
+
assertAttemptBoundary();
|
|
818
|
+
runBoundary = deps.store.extendRunBoundary(runBoundary, [
|
|
819
|
+
identities.stdout,
|
|
820
|
+
identities.stderr,
|
|
821
|
+
]);
|
|
822
|
+
assertAttemptBoundary();
|
|
823
|
+
processOutputCertified = true;
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
processFailed = true;
|
|
829
|
+
processError = error;
|
|
830
|
+
}
|
|
831
|
+
assertAttemptBoundary();
|
|
832
|
+
if (processFailed) {
|
|
833
|
+
if (processError instanceof AriadneStateError) {
|
|
834
|
+
rejectOwnershipViolation(processError, activeStory, runId);
|
|
835
|
+
}
|
|
836
|
+
if (options.signal?.aborted || parentSignal) {
|
|
837
|
+
return stop("interrupted", options.signal?.aborted ? "cancelled" : "signal", runId, activeStory.id);
|
|
838
|
+
}
|
|
839
|
+
if (runtimeBudgetReached()) {
|
|
840
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
841
|
+
}
|
|
842
|
+
const failure = {
|
|
843
|
+
runId,
|
|
844
|
+
category: "process",
|
|
845
|
+
message: errorMessage(processError),
|
|
846
|
+
timestamp: deps.now().toISOString(),
|
|
847
|
+
};
|
|
848
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory)) {
|
|
849
|
+
return blockedSummary(activeStory.id, runId);
|
|
850
|
+
}
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
if (processResult === undefined) {
|
|
854
|
+
return rejectOwnershipViolation(new AriadneStateError("$.process", "Ariadne runtime completed without process metadata"), activeStory, runId);
|
|
855
|
+
}
|
|
856
|
+
if (!processOutputCertified) {
|
|
857
|
+
rejectOwnershipViolation(new AriadneStateError("$.process", "Ariadne runtime returned without creator-certified output identities"), activeStory, runId);
|
|
858
|
+
}
|
|
859
|
+
assertAttemptBoundary();
|
|
860
|
+
currentAttempt.process = {
|
|
861
|
+
status: processResult.status,
|
|
862
|
+
signal: processResult.signal,
|
|
863
|
+
durationMs: processResult.durationMs,
|
|
864
|
+
timedOut: processResult.timedOut,
|
|
865
|
+
aborted: processResult.aborted,
|
|
866
|
+
...processPaths,
|
|
867
|
+
};
|
|
868
|
+
deps.store.writeRunJson(runId, "process.json", processResult);
|
|
869
|
+
if (processResult.timedOut && options.maxRuntimeMs !== undefined) {
|
|
870
|
+
const failure = {
|
|
871
|
+
runId,
|
|
872
|
+
category: "process",
|
|
873
|
+
message: "Ariadne runtime budget expired during the agent process",
|
|
874
|
+
timestamp: deps.now().toISOString(),
|
|
875
|
+
};
|
|
876
|
+
failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory);
|
|
877
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
878
|
+
}
|
|
879
|
+
if (processResult.aborted || options.signal?.aborted) {
|
|
880
|
+
const failure = {
|
|
881
|
+
runId,
|
|
882
|
+
category: "process",
|
|
883
|
+
message: "Ariadne runtime was cancelled",
|
|
884
|
+
timestamp: deps.now().toISOString(),
|
|
885
|
+
};
|
|
886
|
+
recordTerminalFailure(activeStory, failure, "interrupted");
|
|
887
|
+
return stop("interrupted", "cancelled", runId, activeStory.id);
|
|
888
|
+
}
|
|
889
|
+
if (parentSignal) {
|
|
890
|
+
const failure = {
|
|
891
|
+
runId,
|
|
892
|
+
category: "process",
|
|
893
|
+
message: `Ariadne runtime was interrupted by parent ${parentSignal}`,
|
|
894
|
+
timestamp: deps.now().toISOString(),
|
|
895
|
+
};
|
|
896
|
+
recordTerminalFailure(activeStory, failure, "interrupted");
|
|
897
|
+
return stop("interrupted", "signal", runId, activeStory.id);
|
|
898
|
+
}
|
|
899
|
+
if (processResult.signal === "SIGINT" ||
|
|
900
|
+
processResult.signal === "SIGTERM") {
|
|
901
|
+
const failure = {
|
|
902
|
+
runId,
|
|
903
|
+
category: "process",
|
|
904
|
+
message: `Ariadne runtime was interrupted by ${processResult.signal}`,
|
|
905
|
+
timestamp: deps.now().toISOString(),
|
|
906
|
+
};
|
|
907
|
+
recordTerminalFailure(activeStory, failure, "interrupted");
|
|
908
|
+
return stop("interrupted", "signal", runId, activeStory.id);
|
|
909
|
+
}
|
|
910
|
+
let runtimeOutcome;
|
|
911
|
+
try {
|
|
912
|
+
runtimeOutcome = deps.adapter.interpretResult(processResult);
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
const failure = {
|
|
916
|
+
runId,
|
|
917
|
+
category: "process",
|
|
918
|
+
message: "Unable to interpret runtime exit metadata; inspect machine-local logs.",
|
|
919
|
+
timestamp: deps.now().toISOString(),
|
|
920
|
+
};
|
|
921
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory)) {
|
|
922
|
+
return blockedSummary(activeStory.id, runId);
|
|
923
|
+
}
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (!runtimeOutcome.ok) {
|
|
927
|
+
const failure = {
|
|
928
|
+
runId,
|
|
929
|
+
category: "process",
|
|
930
|
+
message: `Runtime exited with status ${runtimeOutcome.status ?? "unknown"}; inspect machine-local logs.`,
|
|
931
|
+
timestamp: deps.now().toISOString(),
|
|
932
|
+
};
|
|
933
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory)) {
|
|
934
|
+
return blockedSummary(activeStory.id, runId);
|
|
935
|
+
}
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
let result;
|
|
939
|
+
try {
|
|
940
|
+
assertAttemptBoundary();
|
|
941
|
+
result = readAgentResult(resultPath, {
|
|
942
|
+
runId,
|
|
943
|
+
storyId: activeStory.id,
|
|
944
|
+
acceptanceCriteria: activeStory.acceptanceCriteria,
|
|
945
|
+
projectRoot: deps.store.projectRoot,
|
|
946
|
+
}, resultIdentity);
|
|
947
|
+
assertAttemptBoundary();
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
const failure = {
|
|
951
|
+
runId,
|
|
952
|
+
category: "result",
|
|
953
|
+
message: errorMessage(error),
|
|
954
|
+
timestamp: deps.now().toISOString(),
|
|
955
|
+
};
|
|
956
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory)) {
|
|
957
|
+
return blockedSummary(activeStory.id, runId);
|
|
958
|
+
}
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
currentAttempt.validatedResult = sanitizeValidatedResult(result);
|
|
962
|
+
if (result.outcome !== "completed") {
|
|
963
|
+
const category = result.criteria.some((criterion) => !criterion.passed)
|
|
964
|
+
? "criterion"
|
|
965
|
+
: "result";
|
|
966
|
+
const failure = {
|
|
967
|
+
runId,
|
|
968
|
+
category,
|
|
969
|
+
message: result.failureReason ??
|
|
970
|
+
`Ariadne runtime did not complete ${activeStory.id}`,
|
|
971
|
+
timestamp: deps.now().toISOString(),
|
|
972
|
+
};
|
|
973
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
974
|
+
result,
|
|
975
|
+
})) {
|
|
976
|
+
return blockedSummary(activeStory.id, runId);
|
|
977
|
+
}
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (runtimeBudgetReached()) {
|
|
981
|
+
const failure = {
|
|
982
|
+
runId,
|
|
983
|
+
category: "check",
|
|
984
|
+
message: "Ariadne runtime budget expired before quality checks",
|
|
985
|
+
timestamp: deps.now().toISOString(),
|
|
986
|
+
};
|
|
987
|
+
recordTerminalFailure(activeStory, failure, "budget_exhausted", {
|
|
988
|
+
result,
|
|
989
|
+
});
|
|
990
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
991
|
+
}
|
|
992
|
+
if (options.signal?.aborted || parentSignal) {
|
|
993
|
+
const failure = {
|
|
994
|
+
runId,
|
|
995
|
+
category: "check",
|
|
996
|
+
message: "Ariadne was interrupted before quality checks",
|
|
997
|
+
timestamp: deps.now().toISOString(),
|
|
998
|
+
};
|
|
999
|
+
recordTerminalFailure(activeStory, failure, "interrupted", {
|
|
1000
|
+
result,
|
|
1001
|
+
});
|
|
1002
|
+
return stop("interrupted", options.signal?.aborted ? "cancelled" : "signal", runId, activeStory.id);
|
|
1003
|
+
}
|
|
1004
|
+
let checks;
|
|
1005
|
+
const certifiedCheckOutputs = new Set();
|
|
1006
|
+
const remainingCheckRuntime = options.maxRuntimeMs === undefined
|
|
1007
|
+
? undefined
|
|
1008
|
+
: Math.max(1, options.maxRuntimeMs - (deps.now().getTime() - startedAt));
|
|
1009
|
+
try {
|
|
1010
|
+
checks = await deps.runChecks({
|
|
1011
|
+
commands: config.qualityChecks,
|
|
1012
|
+
projectRoot: deps.store.projectRoot,
|
|
1013
|
+
runDir,
|
|
1014
|
+
runProcess: deps.runProcess,
|
|
1015
|
+
assertRunDirectory: () => assertAttemptBoundary(),
|
|
1016
|
+
certifyOutput: (identities) => {
|
|
1017
|
+
assertAttemptBoundary();
|
|
1018
|
+
runBoundary = deps.store.extendRunBoundary(runBoundary, [
|
|
1019
|
+
identities.stdout,
|
|
1020
|
+
identities.stderr,
|
|
1021
|
+
]);
|
|
1022
|
+
certifiedCheckOutputs.add(identities.stdout.source);
|
|
1023
|
+
certifiedCheckOutputs.add(identities.stderr.source);
|
|
1024
|
+
assertAttemptBoundary();
|
|
1025
|
+
},
|
|
1026
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1027
|
+
...(remainingCheckRuntime === undefined
|
|
1028
|
+
? {}
|
|
1029
|
+
: { timeoutMs: remainingCheckRuntime }),
|
|
1030
|
+
});
|
|
1031
|
+
assertAttemptBoundary();
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
assertAttemptBoundary();
|
|
1035
|
+
if (error instanceof AriadneStateError) {
|
|
1036
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
1037
|
+
}
|
|
1038
|
+
if (options.signal?.aborted || parentSignal) {
|
|
1039
|
+
const failure = {
|
|
1040
|
+
runId,
|
|
1041
|
+
category: "check",
|
|
1042
|
+
message: "Ariadne quality checks were interrupted",
|
|
1043
|
+
timestamp: deps.now().toISOString(),
|
|
1044
|
+
};
|
|
1045
|
+
recordTerminalFailure(activeStory, failure, "interrupted", {
|
|
1046
|
+
result,
|
|
1047
|
+
});
|
|
1048
|
+
return stop("interrupted", options.signal?.aborted ? "cancelled" : "signal", runId, activeStory.id);
|
|
1049
|
+
}
|
|
1050
|
+
if (runtimeBudgetReached()) {
|
|
1051
|
+
const failure = {
|
|
1052
|
+
runId,
|
|
1053
|
+
category: "check",
|
|
1054
|
+
message: "Ariadne runtime budget expired during quality checks",
|
|
1055
|
+
timestamp: deps.now().toISOString(),
|
|
1056
|
+
};
|
|
1057
|
+
recordTerminalFailure(activeStory, failure, "budget_exhausted", {
|
|
1058
|
+
result,
|
|
1059
|
+
});
|
|
1060
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
1061
|
+
}
|
|
1062
|
+
const failure = {
|
|
1063
|
+
runId,
|
|
1064
|
+
category: "check",
|
|
1065
|
+
message: errorMessage(error),
|
|
1066
|
+
timestamp: deps.now().toISOString(),
|
|
1067
|
+
};
|
|
1068
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
1069
|
+
result,
|
|
1070
|
+
})) {
|
|
1071
|
+
return blockedSummary(activeStory.id, runId);
|
|
1072
|
+
}
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const uncertifiedCheck = checks.find((check) => !certifiedCheckOutputs.has(check.stdoutPath) ||
|
|
1076
|
+
!certifiedCheckOutputs.has(check.stderrPath));
|
|
1077
|
+
if (uncertifiedCheck) {
|
|
1078
|
+
rejectOwnershipViolation(new AriadneStateError("$.checks", `Ariadne quality check returned without creator-certified output identities: ${uncertifiedCheck.command}`), activeStory, runId);
|
|
1079
|
+
}
|
|
1080
|
+
const durableChecks = checks.map(sanitizeQualityCheckResult);
|
|
1081
|
+
currentAttempt.checks = durableChecks.map((check) => ({
|
|
1082
|
+
command: check.command,
|
|
1083
|
+
status: check.status,
|
|
1084
|
+
signal: check.signal,
|
|
1085
|
+
durationMs: check.durationMs,
|
|
1086
|
+
timedOut: check.timedOut,
|
|
1087
|
+
timeoutOrigin: check.timeoutOrigin,
|
|
1088
|
+
aborted: check.aborted,
|
|
1089
|
+
}));
|
|
1090
|
+
deps.store.writeRunJson(runId, "checks.json", durableChecks);
|
|
1091
|
+
const timedOutCheck = checks.find((check) => check.timedOut);
|
|
1092
|
+
if (timedOutCheck?.timeoutOrigin === "global_budget") {
|
|
1093
|
+
const failure = {
|
|
1094
|
+
runId,
|
|
1095
|
+
category: "check",
|
|
1096
|
+
message: `Ariadne runtime budget expired during quality check: ${timedOutCheck.command}`,
|
|
1097
|
+
timestamp: deps.now().toISOString(),
|
|
1098
|
+
};
|
|
1099
|
+
failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
1100
|
+
result,
|
|
1101
|
+
checks,
|
|
1102
|
+
});
|
|
1103
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
1104
|
+
}
|
|
1105
|
+
if (timedOutCheck) {
|
|
1106
|
+
const failure = {
|
|
1107
|
+
runId,
|
|
1108
|
+
category: "check",
|
|
1109
|
+
message: `Ariadne quality check timed out: ${timedOutCheck.command}`,
|
|
1110
|
+
timestamp: deps.now().toISOString(),
|
|
1111
|
+
};
|
|
1112
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
1113
|
+
result,
|
|
1114
|
+
checks,
|
|
1115
|
+
})) {
|
|
1116
|
+
return blockedSummary(activeStory.id, runId);
|
|
1117
|
+
}
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const abortedCheck = checks.find((check) => check.aborted);
|
|
1121
|
+
if (abortedCheck || options.signal?.aborted) {
|
|
1122
|
+
const failure = {
|
|
1123
|
+
runId,
|
|
1124
|
+
category: "check",
|
|
1125
|
+
message: `Ariadne quality check was cancelled: ${abortedCheck?.command ?? "external cancellation"}`,
|
|
1126
|
+
timestamp: deps.now().toISOString(),
|
|
1127
|
+
};
|
|
1128
|
+
recordTerminalFailure(activeStory, failure, "interrupted", {
|
|
1129
|
+
result,
|
|
1130
|
+
checks,
|
|
1131
|
+
});
|
|
1132
|
+
return stop("interrupted", "cancelled", runId, activeStory.id);
|
|
1133
|
+
}
|
|
1134
|
+
const interruptedCheck = checks.find((check) => check.signal === "SIGINT" || check.signal === "SIGTERM");
|
|
1135
|
+
if (parentSignal || interruptedCheck) {
|
|
1136
|
+
const failure = {
|
|
1137
|
+
runId,
|
|
1138
|
+
category: "check",
|
|
1139
|
+
message: `Ariadne quality checks were interrupted by ${parentSignal ?? interruptedCheck?.signal}`,
|
|
1140
|
+
timestamp: deps.now().toISOString(),
|
|
1141
|
+
};
|
|
1142
|
+
recordTerminalFailure(activeStory, failure, "interrupted", {
|
|
1143
|
+
result,
|
|
1144
|
+
checks,
|
|
1145
|
+
});
|
|
1146
|
+
return stop("interrupted", "signal", runId, activeStory.id);
|
|
1147
|
+
}
|
|
1148
|
+
if (runtimeBudgetReached()) {
|
|
1149
|
+
const failure = {
|
|
1150
|
+
runId,
|
|
1151
|
+
category: "check",
|
|
1152
|
+
message: "Ariadne runtime budget expired after quality checks",
|
|
1153
|
+
timestamp: deps.now().toISOString(),
|
|
1154
|
+
};
|
|
1155
|
+
recordTerminalFailure(activeStory, failure, "budget_exhausted", {
|
|
1156
|
+
result,
|
|
1157
|
+
checks,
|
|
1158
|
+
});
|
|
1159
|
+
return stop("budget_exhausted", "max_runtime", runId, activeStory.id);
|
|
1160
|
+
}
|
|
1161
|
+
if (checks.length !== config.qualityChecks.length) {
|
|
1162
|
+
const failure = {
|
|
1163
|
+
runId,
|
|
1164
|
+
category: "check",
|
|
1165
|
+
message: `Ariadne quality checks did not all run for ${activeStory.id}`,
|
|
1166
|
+
timestamp: deps.now().toISOString(),
|
|
1167
|
+
};
|
|
1168
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
1169
|
+
result,
|
|
1170
|
+
checks,
|
|
1171
|
+
})) {
|
|
1172
|
+
return blockedSummary(activeStory.id, runId);
|
|
1173
|
+
}
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
const failedCheck = checks.find((check) => check.status !== 0);
|
|
1177
|
+
if (failedCheck) {
|
|
1178
|
+
const failure = {
|
|
1179
|
+
runId,
|
|
1180
|
+
category: "check",
|
|
1181
|
+
message: `Ariadne quality check failed: ${failedCheck.command}`,
|
|
1182
|
+
timestamp: deps.now().toISOString(),
|
|
1183
|
+
};
|
|
1184
|
+
if (failAttempt(prd, activeStory, failure, config.maxAttemptsPerStory, {
|
|
1185
|
+
result,
|
|
1186
|
+
checks,
|
|
1187
|
+
})) {
|
|
1188
|
+
return blockedSummary(activeStory.id, runId);
|
|
1189
|
+
}
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
assertAttemptBoundary();
|
|
1193
|
+
transitionStory(activeStory, "completed");
|
|
1194
|
+
let publicationSnapshot;
|
|
1195
|
+
try {
|
|
1196
|
+
const completedPrdCertificate = deps.store.savePrd(prd);
|
|
1197
|
+
const completedProgressCertificate = deps.store.appendProgress(formatProgressEntry({
|
|
1198
|
+
timestamp: deps.now().toISOString(),
|
|
1199
|
+
runId,
|
|
1200
|
+
story: activeStory,
|
|
1201
|
+
runtime: options.runtime,
|
|
1202
|
+
result,
|
|
1203
|
+
checks,
|
|
1204
|
+
}), canonicalSnapshot.progress);
|
|
1205
|
+
publicationSnapshot = {
|
|
1206
|
+
initialHead: canonicalSnapshot.initialHead,
|
|
1207
|
+
initialRef: canonicalSnapshot.initialRef,
|
|
1208
|
+
prd: completedPrdCertificate,
|
|
1209
|
+
progress: completedProgressCertificate,
|
|
1210
|
+
};
|
|
1211
|
+
assertOwnershipBoundary(publicationSnapshot);
|
|
1212
|
+
assertOperationalBoundary();
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
1216
|
+
}
|
|
1217
|
+
const assertPublicationBoundary = () => {
|
|
1218
|
+
assertOwnershipBoundary(publicationSnapshot);
|
|
1219
|
+
assertOperationalBoundary();
|
|
1220
|
+
};
|
|
1221
|
+
try {
|
|
1222
|
+
assertPublicationBoundary();
|
|
1223
|
+
deps.git.stageAll(canonicalSnapshot.initialHead, canonicalSnapshot.initialRef);
|
|
1224
|
+
assertPublicationBoundary();
|
|
1225
|
+
commit = deps.git.commit(activeStory, canonicalSnapshot.initialHead, assertPublicationBoundary, canonicalSnapshot.initialRef);
|
|
1226
|
+
}
|
|
1227
|
+
catch (error) {
|
|
1228
|
+
// A late runtime/background mutation must quarantine before any
|
|
1229
|
+
// coordinator rollback writes. These calls throw structurally when
|
|
1230
|
+
// either ownership or the operational boundary changed.
|
|
1231
|
+
assertPublicationBoundary();
|
|
1232
|
+
if (error instanceof AriadneStateError) {
|
|
1233
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
1234
|
+
}
|
|
1235
|
+
// Commit failure is the sole recovery transition from completed back
|
|
1236
|
+
// to in-progress; the completed state was never durably certified.
|
|
1237
|
+
activeStory.status = "in_progress";
|
|
1238
|
+
const failedPrdCertificate = deps.store.savePrd(prd);
|
|
1239
|
+
const failedProgressCertificate = deps.store.appendProgress(formatProgressEntry({
|
|
1240
|
+
timestamp: deps.now().toISOString(),
|
|
1241
|
+
runId,
|
|
1242
|
+
story: activeStory,
|
|
1243
|
+
runtime: options.runtime,
|
|
1244
|
+
result,
|
|
1245
|
+
checks,
|
|
1246
|
+
outcome: "failed",
|
|
1247
|
+
failureCategory: "commit_failure",
|
|
1248
|
+
failureReason: errorMessage(error),
|
|
1249
|
+
}));
|
|
1250
|
+
persistCertification({
|
|
1251
|
+
runId,
|
|
1252
|
+
storyId: activeStory.id,
|
|
1253
|
+
certifiedHead: canonicalSnapshot.initialHead,
|
|
1254
|
+
certifiedRef: canonicalSnapshot.initialRef,
|
|
1255
|
+
prd: failedPrdCertificate,
|
|
1256
|
+
progress: failedProgressCertificate,
|
|
1257
|
+
});
|
|
1258
|
+
const failure = sanitizeAttemptFailure({
|
|
1259
|
+
runId,
|
|
1260
|
+
category: "commit",
|
|
1261
|
+
message: errorMessage(error),
|
|
1262
|
+
timestamp: deps.now().toISOString(),
|
|
1263
|
+
});
|
|
1264
|
+
deps.store.writeRunJson(runId, "failure.json", failure);
|
|
1265
|
+
finishAttempt("failed", "commit");
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
1268
|
+
finishAttempt("completed");
|
|
1269
|
+
try {
|
|
1270
|
+
deps.git.assertPublished(commit);
|
|
1271
|
+
deps.store.assertCanonicalCertificate(publicationSnapshot.prd);
|
|
1272
|
+
deps.store.assertCanonicalCertificate(publicationSnapshot.progress);
|
|
1273
|
+
assertOperationalBoundary();
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
rejectOwnershipViolation(error, activeStory, runId);
|
|
1277
|
+
}
|
|
1278
|
+
persistCertification({
|
|
1279
|
+
runId,
|
|
1280
|
+
storyId: activeStory.id,
|
|
1281
|
+
certifiedHead: commit,
|
|
1282
|
+
certifiedRef: publicationSnapshot.initialRef,
|
|
1283
|
+
prd: publicationSnapshot.prd,
|
|
1284
|
+
progress: publicationSnapshot.progress,
|
|
1285
|
+
});
|
|
1286
|
+
completedStoryIds.push(activeStory.id);
|
|
1287
|
+
if (!selectStory(prd)) {
|
|
1288
|
+
return summary({
|
|
1289
|
+
options,
|
|
1290
|
+
outcome: "complete",
|
|
1291
|
+
iterations,
|
|
1292
|
+
completedStoryIds,
|
|
1293
|
+
lastRunId,
|
|
1294
|
+
commit,
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
catch (error) {
|
|
1299
|
+
if (lockedStory &&
|
|
1300
|
+
error instanceof AriadneStateError &&
|
|
1301
|
+
!deps.store.loadOwnershipViolation()) {
|
|
1302
|
+
rejectOwnershipViolation(error, lockedStory, runId);
|
|
1303
|
+
}
|
|
1304
|
+
throw error;
|
|
1305
|
+
}
|
|
1306
|
+
finally {
|
|
1307
|
+
releaseIterationLock(lock, lockedStory, runId);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
finally {
|
|
1312
|
+
process.removeListener("SIGINT", onParentSigint);
|
|
1313
|
+
process.removeListener("SIGTERM", onParentSigterm);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
//# sourceMappingURL=loop.js.map
|