@osovv/vv-opencode 1.3.8 → 1.4.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +66 -14
  3. package/dist/commands/completion.js +10 -2
  4. package/dist/commands/completion.js.map +1 -1
  5. package/dist/commands/patch-provider.d.ts +148 -1
  6. package/dist/commands/patch-provider.js +151 -5
  7. package/dist/commands/patch-provider.js.map +1 -1
  8. package/dist/lib/orchestration.d.ts +2 -2
  9. package/dist/lib/orchestration.js +31 -2
  10. package/dist/lib/orchestration.js.map +1 -1
  11. package/dist/lib/spec-lint.d.ts +71 -1
  12. package/dist/lib/spec-lint.js +566 -8
  13. package/dist/lib/spec-lint.js.map +1 -1
  14. package/dist/lib/vvoc-config.d.ts +3 -3
  15. package/dist/lib/vvoc-preset-registry.d.ts +42 -30
  16. package/dist/lib/vvoc-preset-registry.js +41 -31
  17. package/dist/lib/vvoc-preset-registry.js.map +1 -1
  18. package/dist/plugins/hashline-edit/index.js.map +1 -1
  19. package/dist/plugins/workflow/checkpoint-io.d.ts +33 -0
  20. package/dist/plugins/workflow/checkpoint-io.js +224 -0
  21. package/dist/plugins/workflow/checkpoint-io.js.map +1 -0
  22. package/dist/plugins/workflow/checkpoints.d.ts +182 -0
  23. package/dist/plugins/workflow/checkpoints.js +1067 -0
  24. package/dist/plugins/workflow/checkpoints.js.map +1 -0
  25. package/dist/plugins/workflow/delegated.d.ts +182 -0
  26. package/dist/plugins/workflow/delegated.js +751 -0
  27. package/dist/plugins/workflow/delegated.js.map +1 -0
  28. package/dist/plugins/workflow/index.js +685 -23
  29. package/dist/plugins/workflow/index.js.map +1 -1
  30. package/dist/plugins/workflow/persistence.d.ts +32 -3
  31. package/dist/plugins/workflow/persistence.js +516 -70
  32. package/dist/plugins/workflow/persistence.js.map +1 -1
  33. package/dist/plugins/workflow/repair.js +3 -1
  34. package/dist/plugins/workflow/repair.js.map +1 -1
  35. package/dist/plugins/workflow/snapshots.d.ts +59 -0
  36. package/dist/plugins/workflow/snapshots.js +261 -0
  37. package/dist/plugins/workflow/snapshots.js.map +1 -0
  38. package/dist/plugins/workflow/state.d.ts +20 -2
  39. package/dist/plugins/workflow/state.js +127 -12
  40. package/dist/plugins/workflow/state.js.map +1 -1
  41. package/dist/plugins/workflow/tooling.d.ts +45 -0
  42. package/dist/plugins/workflow/tooling.js +344 -12
  43. package/dist/plugins/workflow/tooling.js.map +1 -1
  44. package/dist/plugins/workflow/transitions.js +2 -2
  45. package/dist/plugins/workflow/transitions.js.map +1 -1
  46. package/dist/tui/context/analyze.js +3 -1
  47. package/dist/tui/context/analyze.js.map +1 -1
  48. package/package.json +1 -1
  49. package/schemas/vvoc/v3.json +5 -3
  50. package/templates/agents/vv-code-reviewer.md +3 -1
  51. package/templates/agents/vv-implementer.md +5 -2
  52. package/templates/agents/vv-spec-reviewer.md +3 -0
  53. package/templates/skills/vv-execute/SKILL.md +62 -15
  54. package/templates/skills/vv-plan/SKILL.md +13 -3
  55. package/templates/skills/vv-plan/references/plan-template.xml +45 -0
  56. package/templates/skills/vv-review/SKILL.md +1 -0
@@ -0,0 +1,1067 @@
1
+ // FILE: src/plugins/workflow/checkpoints.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Registered delegated plan runs with declared review checkpoints, generation-bound reviewer linkage, fingerprint-verified outcomes, and rework authorization.
5
+ // SCOPE: Atomic plan-run registration binding canonical task ids to stable delegated work items, idempotent re-registration with explicit drift rejection, checkpoint start with prerequisite acceptance and fresh fingerprints through linked review_only work items, reviewer launch/result recording bound to the current generation, verify deriving passed/failed/stale/stopped outcomes from recorded results plus recomputed fingerprints and approval-input hashes, final complete sealing, failed-checkpoint rework authorization, wave-barrier and overlapping-write gates, and read-only run views. No agent dispatch or command execution.
6
+ // DEPENDS: [node:crypto, src/plugins/workflow/checkpoint-io.ts, src/plugins/workflow/delegated.ts, src/plugins/workflow/snapshots.ts, src/plugins/workflow/state.ts]
7
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-WORKFLOW-DELEGATED, M-WORKFLOW-SNAPSHOTS, M-WORKFLOW-STATE, M-SPEC-LINT, V-M-WORKFLOW-CHECKPOINTS]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // MAX_CHECKPOINT_REVIEW_ATTEMPTS - Maximum review generations per checkpoint (initial plus one correction).
14
+ // DelegatedReviewerOutcome - One reviewer's recorded outcome inside a generation.
15
+ // DelegatedCheckpointReview - Current in-flight review generation state.
16
+ // DelegatedCheckpointHistoryEntry - One settled generation outcome as historical evidence.
17
+ // DelegatedRunCheckpoint - Registered checkpoint runtime state.
18
+ // DelegatedRunTask - Canonical task to work-item binding.
19
+ // DelegatedPlanRun - Registered plan-run registry entry.
20
+ // RegisterDelegatedPlanInput - Registration input carrying the loaded approved plan.
21
+ // RegisterDelegatedPlanResult - Registration outcome including idempotent reuse.
22
+ // StartDelegatedCheckpointInput - Checkpoint start input identities.
23
+ // StartDelegatedCheckpointResult - Checkpoint start outcome with launch instructions.
24
+ // RecordReviewerLaunchResult - Reviewer launch binding outcome.
25
+ // RecordReviewerResultResult - Reviewer result recording outcome with round completeness.
26
+ // VerifyDelegatedCheckpointInput - Verify request with optional final complete flag.
27
+ // VerifyDelegatedCheckpointResult - Derived verify outcome and optional sealed completion.
28
+ // AuthorizeReworkInput - Failed-checkpoint rework authorization request.
29
+ // AuthorizeReworkResult - Rework authorization outcome delegating to the guarded reducer.
30
+ // checkpointBarrierUnsatisfied - Unsatisfied barriers blocking a wave's task launches.
31
+ // findOverlappingInFlightReview - In-flight checkpoint scope overlap detection for declared writes.
32
+ // getDelegatedRunView - Read-only run serialization for tooling output.
33
+ // registerDelegatedPlan - Atomically register a validated approved delegated plan.
34
+ // registerDelegatedPlanInStore - Store-level registration used by the plugin and tests.
35
+ // startDelegatedCheckpoint - Start a due checkpoint generation with a fresh fingerprint.
36
+ // startDelegatedCheckpointInStore - Store-level checkpoint start.
37
+ // recordCheckpointReviewerLaunch - Bind one reviewer launch call to the current generation.
38
+ // recordCheckpointReviewerLaunchInStore - Store-level reviewer launch binding.
39
+ // recordCheckpointReviewerResult - Record one reviewer outcome for the current generation.
40
+ // recordCheckpointReviewerResultInStore - Store-level reviewer result recording.
41
+ // verifyDelegatedCheckpoint - Derive the checkpoint outcome from results and fresh fingerprints.
42
+ // verifyDelegatedCheckpointInStore - Store-level verify used by the plugin and tests.
43
+ // authorizeReworkFromFailedCheckpoint - Validate failed-checkpoint rework authorization.
44
+ // authorizeReworkFromFailedCheckpointInStore - Store-level rework authorization.
45
+ // END_MODULE_MAP
46
+ //
47
+ // START_CHANGE_SUMMARY
48
+ // LAST_CHANGE: [C-DELEGATED-WORKFLOW-ASTRA-PRESETS - Initial registry: registration, generations, fingerprint verification, final sealing, rework authorization, and barrier gates.]
49
+ // END_CHANGE_SUMMARY
50
+ import { createHash } from "node:crypto";
51
+ import { readFile } from "node:fs/promises";
52
+ import { contentSha256 } from "./checkpoint-io.js";
53
+ import { currentDelegatedAcceptance, reworkDelegatedWorkItem, validateDelegatedWriteScope, } from "./delegated.js";
54
+ import { captureWorkflowSnapshot } from "./snapshots.js";
55
+ import { cloneRecord, createRecordLookupKey, } from "./state.js";
56
+ // START_BLOCK_REGISTRY_TYPES
57
+ export const MAX_CHECKPOINT_REVIEW_ATTEMPTS = 2;
58
+ // END_BLOCK_REGISTRY_TYPES
59
+ // START_BLOCK_REGISTRY_HELPERS
60
+ function toIsoNow() {
61
+ return new Date().toISOString();
62
+ }
63
+ function findRun(data, runId) {
64
+ return data.planRuns.get(runId);
65
+ }
66
+ function findRecord(data, sessionId, workItemId) {
67
+ return data.records.get(createRecordLookupKey(sessionId, workItemId));
68
+ }
69
+ function runIdFor(plan) {
70
+ return `run-${createHash("sha256")
71
+ .update(`${plan.planPath}\n${plan.planSha256}\n${plan.specSha256}`)
72
+ .digest("hex")
73
+ .slice(0, 16)}`;
74
+ }
75
+ function definitionsEqual(left, right) {
76
+ return JSON.stringify(left) === JSON.stringify(right);
77
+ }
78
+ function cloneCheckpoint(checkpoint) {
79
+ return {
80
+ ...checkpoint,
81
+ covers: [...checkpoint.covers],
82
+ scope: [...checkpoint.scope],
83
+ reviewers: [...checkpoint.reviewers],
84
+ history: checkpoint.history.map((entry) => ({ ...entry })),
85
+ ...(checkpoint.currentReview
86
+ ? {
87
+ currentReview: {
88
+ ...checkpoint.currentReview,
89
+ coveredAttemptIds: [...checkpoint.currentReview.coveredAttemptIds],
90
+ reviewerCallIds: { ...checkpoint.currentReview.reviewerCallIds },
91
+ results: { ...checkpoint.currentReview.results },
92
+ },
93
+ }
94
+ : {}),
95
+ };
96
+ }
97
+ function cloneRun(run) {
98
+ return {
99
+ ...run,
100
+ tasks: new Map([...run.tasks].map(([taskId, task]) => [taskId, { ...task }])),
101
+ checkpoints: new Map([...run.checkpoints].map(([id, checkpoint]) => [id, cloneCheckpoint(checkpoint)])),
102
+ };
103
+ }
104
+ function waveIndexOf(run, wave) {
105
+ return run.definition.waves.indexOf(wave);
106
+ }
107
+ /** Covered-attempt identities for the currently accepted attempts of covered tasks. */
108
+ function currentCoveredAttemptIds(data, run, checkpoint) {
109
+ const ids = [];
110
+ for (const taskId of checkpoint.covers) {
111
+ const task = run.tasks.get(taskId);
112
+ if (!task)
113
+ return { ok: false, taskId };
114
+ const record = findRecord(data, run.sessionId, task.workItemId);
115
+ const acceptance = record ? currentDelegatedAcceptance(record) : undefined;
116
+ if (!acceptance)
117
+ return { ok: false, taskId };
118
+ ids.push(`${task.workItemId}#${acceptance.attempt}`);
119
+ }
120
+ return { ok: true, ids };
121
+ }
122
+ // START_CONTRACT: registerDelegatedPlan
123
+ // PURPOSE: Atomically register all declared tasks and checkpoints of a validated approved delegated plan without launching agents.
124
+ // INPUTS: { store: WorkItemStore - backing store, input: RegisterDelegatedPlanInput - session and loaded approved plan }
125
+ // OUTPUTS: { RegisterDelegatedPlanResult - created or idempotently reused run, or an explicit drift/binding rejection }
126
+ // SIDE_EFFECTS: [Creates the registry entry and bound delegated work items; rolls back created items on failure]
127
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-WORKFLOW-DELEGATED, loadApprovedDelegatedPlan]
128
+ // END_CONTRACT: registerDelegatedPlan
129
+ export function registerDelegatedPlan(store, input) {
130
+ return registerDelegatedPlanInStore(store.getStoreData(), input);
131
+ }
132
+ export function registerDelegatedPlanInStore(data, input) {
133
+ if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
134
+ return {
135
+ ok: false,
136
+ errorCode: "INVALID_INPUT",
137
+ message: "sessionId must be a non-empty string",
138
+ };
139
+ }
140
+ const runId = runIdFor(input.plan);
141
+ const existing = findRun(data, runId);
142
+ if (existing) {
143
+ if (existing.sessionId !== input.sessionId) {
144
+ return {
145
+ ok: false,
146
+ errorCode: "SESSION_MISMATCH",
147
+ message: `run ${runId} belongs to session ${existing.sessionId}`,
148
+ };
149
+ }
150
+ const identical = existing.planPath === input.plan.planPath &&
151
+ existing.specPath === input.plan.specPath &&
152
+ existing.workspaceRoot === input.plan.workspaceRoot &&
153
+ existing.planSha256 === input.plan.planSha256 &&
154
+ existing.specSha256 === input.plan.specSha256 &&
155
+ definitionsEqual(existing.definition, input.plan.definition);
156
+ if (!identical) {
157
+ return {
158
+ ok: false,
159
+ errorCode: "PLAN_DRIFT",
160
+ message: `run ${runId} was registered from different approved inputs; supersede or replan instead of resetting progress`,
161
+ };
162
+ }
163
+ return { ok: true, runId, reused: true, run: cloneRun(existing) };
164
+ }
165
+ const samePlanPath = [...data.planRuns.values()].find((run) => run.planPath === input.plan.planPath && run.sessionId === input.sessionId);
166
+ if (samePlanPath) {
167
+ return {
168
+ ok: false,
169
+ errorCode: "PLAN_DRIFT",
170
+ message: `plan ${input.plan.planPath} is already registered as run ${samePlanPath.runId} with different content; explicit plan amendment is required`,
171
+ };
172
+ }
173
+ // Stage all bound delegated work items before committing the registry entry.
174
+ // Write scopes are validated with the same normalizer the runtime snapshots
175
+ // use, so a plan that reaches registration can never declare a scope the
176
+ // snapshot normalizer or the persistence validator would reject later.
177
+ const createdWorkItemIds = [];
178
+ const tasks = new Map();
179
+ for (const task of input.plan.definition.tasks) {
180
+ const key = `delegated-${runId}-${task.taskId}`;
181
+ const scopeValidation = validateDelegatedWriteScope(task.writeScope);
182
+ if (!scopeValidation.ok) {
183
+ rollbackCreatedItems(data, input.sessionId, createdWorkItemIds);
184
+ return {
185
+ ok: false,
186
+ errorCode: "TASK_BINDING_FAILED",
187
+ message: `binding task ${task.taskId} failed: write scope rejected (${scopeValidation.message ?? "invalid scope"})`,
188
+ };
189
+ }
190
+ const opened = openInStore(data, {
191
+ sessionId: input.sessionId,
192
+ key,
193
+ title: `Delegated ${task.taskId}: ${task.taskElement}`,
194
+ mode: "delegated",
195
+ requiredReviewers: [],
196
+ writeScope: scopeValidation.paths ?? [],
197
+ planRunId: runId,
198
+ planTaskId: task.taskId,
199
+ });
200
+ if (!opened || !opened.ok) {
201
+ rollbackCreatedItems(data, input.sessionId, createdWorkItemIds);
202
+ return {
203
+ ok: false,
204
+ errorCode: "TASK_BINDING_FAILED",
205
+ message: `binding task ${task.taskId} failed: ${opened && !opened.ok ? opened.message : "unknown"}`,
206
+ };
207
+ }
208
+ createdWorkItemIds.push(opened.record.workItemId);
209
+ tasks.set(task.taskId, { taskId: task.taskId, workItemId: opened.record.workItemId });
210
+ }
211
+ const checkpoints = new Map();
212
+ for (const checkpoint of input.plan.definition.checkpoints) {
213
+ checkpoints.set(checkpoint.checkpointId, {
214
+ checkpointId: checkpoint.checkpointId,
215
+ kind: checkpoint.kind,
216
+ afterWave: checkpoint.afterWave,
217
+ covers: [...checkpoint.covers],
218
+ scope: [...checkpoint.scope],
219
+ reviewers: [...checkpoint.reviewers],
220
+ status: "pending",
221
+ attempts: 0,
222
+ history: [],
223
+ });
224
+ }
225
+ const finalCheckpoint = input.plan.definition.checkpoints.find((c) => c.kind === "final");
226
+ const run = {
227
+ runId,
228
+ sessionId: input.sessionId,
229
+ planPath: input.plan.planPath,
230
+ specPath: input.plan.specPath,
231
+ workspaceRoot: input.plan.workspaceRoot,
232
+ planSha256: input.plan.planSha256,
233
+ specSha256: input.plan.specSha256,
234
+ definition: input.plan.definition,
235
+ registeredAt: toIsoNow(),
236
+ status: "active",
237
+ ...(finalCheckpoint ? { finalCheckpointId: finalCheckpoint.checkpointId } : {}),
238
+ tasks,
239
+ checkpoints,
240
+ };
241
+ data.planRuns.set(runId, run);
242
+ return { ok: true, runId, reused: false, run: cloneRun(run) };
243
+ }
244
+ function openInStore(data, input) {
245
+ const sessionIndex = data.keyIndexBySession.get(input.sessionId) ??
246
+ (() => {
247
+ const index = new Map();
248
+ data.keyIndexBySession.set(input.sessionId, index);
249
+ return index;
250
+ })();
251
+ const existingId = sessionIndex.get(input.key);
252
+ if (existingId) {
253
+ const existing = data.records.get(createRecordLookupKey(input.sessionId, existingId));
254
+ if (existing) {
255
+ const sameIntent = existing.title === input.title &&
256
+ existing.mode === input.mode &&
257
+ existing.delegated?.planRunId === input.planRunId &&
258
+ existing.delegated?.planTaskId === input.planTaskId &&
259
+ JSON.stringify(existing.delegated?.writeScope ?? []) ===
260
+ JSON.stringify([...(input.writeScope ?? [])]);
261
+ if (!sameIntent) {
262
+ return {
263
+ ok: false,
264
+ errorCode: "WORK_ITEM_KEY_CONFLICT",
265
+ message: `WORK_ITEM_KEY_CONFLICT: key ${input.key} is already associated with different workflow intent`,
266
+ existingWorkItemId: existing.workItemId,
267
+ };
268
+ }
269
+ return {
270
+ ok: true,
271
+ reused: true,
272
+ record: cloneRecord(existing),
273
+ header: `VVOC_WORK_ITEM_ID: ${existing.workItemId}`,
274
+ };
275
+ }
276
+ sessionIndex.delete(input.key);
277
+ }
278
+ const workItemId = `wi-${data.nextId}`;
279
+ data.nextId += 1;
280
+ const now = toIsoNow();
281
+ const record = {
282
+ sessionId: input.sessionId,
283
+ workItemId,
284
+ key: input.key,
285
+ title: input.title,
286
+ mode: input.mode,
287
+ requiredReviewers: [],
288
+ state: "open",
289
+ delegated: {
290
+ writeScope: [...(input.writeScope ?? [])],
291
+ ...(input.planRunId && input.planTaskId
292
+ ? { planRunId: input.planRunId, planTaskId: input.planTaskId }
293
+ : {}),
294
+ attempts: [],
295
+ decisions: [],
296
+ acceptances: [],
297
+ reworkHistory: [],
298
+ },
299
+ completedReviewRoundCount: 0,
300
+ specReviewCount: 0,
301
+ codeReviewCount: 0,
302
+ createdAt: now,
303
+ updatedAt: now,
304
+ };
305
+ sessionIndex.set(input.key, workItemId);
306
+ data.records.set(createRecordLookupKey(input.sessionId, workItemId), record);
307
+ return {
308
+ ok: true,
309
+ reused: false,
310
+ record: cloneRecord(record),
311
+ header: `VVOC_WORK_ITEM_ID: ${workItemId}`,
312
+ };
313
+ }
314
+ function rollbackCreatedItems(data, sessionId, workItemIds) {
315
+ for (const workItemId of workItemIds) {
316
+ const record = data.records.get(createRecordLookupKey(sessionId, workItemId));
317
+ if (!record)
318
+ continue;
319
+ if (record.state !== "open")
320
+ continue; // never roll back items that progressed
321
+ data.records.delete(createRecordLookupKey(sessionId, workItemId));
322
+ const sessionIndex = data.keyIndexBySession.get(sessionId);
323
+ if (sessionIndex?.get(record.key) === workItemId) {
324
+ sessionIndex.delete(record.key);
325
+ }
326
+ }
327
+ }
328
+ // START_CONTRACT: startDelegatedCheckpoint
329
+ // PURPOSE: Start a due checkpoint generation after prerequisite acceptance, capturing a fresh scope fingerprint and opening the declared reviewer set.
330
+ // INPUTS: { store: WorkItemStore - backing store, input: StartDelegatedCheckpointInput - session, run, and checkpoint identity }
331
+ // OUTPUTS: { StartDelegatedCheckpointResult - launch instructions for the linked review_only item or a coded rejection }
332
+ // SIDE_EFFECTS: [Mutates checkpoint state and creates the linked review_only work item]
333
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-WORKFLOW-STATE, captureWorkflowSnapshot]
334
+ // END_CONTRACT: startDelegatedCheckpoint
335
+ export async function startDelegatedCheckpoint(store, input) {
336
+ return startDelegatedCheckpointInStore(store.getStoreData(), input);
337
+ }
338
+ export async function startDelegatedCheckpointInStore(data, input) {
339
+ const run = findRun(data, input.runId);
340
+ if (!run) {
341
+ return { ok: false, errorCode: "RUN_NOT_FOUND", message: `RUN_NOT_FOUND: ${input.runId}` };
342
+ }
343
+ if (run.sessionId !== input.sessionId) {
344
+ return {
345
+ ok: false,
346
+ errorCode: "SESSION_MISMATCH",
347
+ message: `SESSION_MISMATCH: run ${input.runId} belongs to session ${run.sessionId}`,
348
+ };
349
+ }
350
+ if (run.status === "sealed") {
351
+ return {
352
+ ok: false,
353
+ errorCode: "RUN_SEALED",
354
+ message: `RUN_SEALED: run ${input.runId} is complete`,
355
+ };
356
+ }
357
+ const checkpoint = run.checkpoints.get(input.checkpointId);
358
+ if (!checkpoint) {
359
+ return {
360
+ ok: false,
361
+ errorCode: "CHECKPOINT_NOT_FOUND",
362
+ message: `CHECKPOINT_NOT_FOUND: ${input.checkpointId} is not declared in run ${input.runId}`,
363
+ };
364
+ }
365
+ if (checkpoint.status === "passed") {
366
+ return {
367
+ ok: false,
368
+ errorCode: "ALREADY_PASSED",
369
+ message: `ALREADY_PASSED: ${input.checkpointId} passed at generation ${checkpoint.attempts}`,
370
+ };
371
+ }
372
+ if (checkpoint.status === "in_review") {
373
+ return {
374
+ ok: false,
375
+ errorCode: "ALREADY_IN_REVIEW",
376
+ message: `ALREADY_IN_REVIEW: ${input.checkpointId} generation ${checkpoint.currentReview?.generation} is in flight`,
377
+ };
378
+ }
379
+ if (checkpoint.attempts >= MAX_CHECKPOINT_REVIEW_ATTEMPTS) {
380
+ return {
381
+ ok: false,
382
+ errorCode: "ATTEMPTS_EXHAUSTED",
383
+ message: `ATTEMPTS_EXHAUSTED: ${input.checkpointId} consumed ${checkpoint.attempts} review generations; explicit recovery is required`,
384
+ };
385
+ }
386
+ const covered = currentCoveredAttemptIds(data, run, checkpoint);
387
+ if (!covered.ok) {
388
+ return {
389
+ ok: false,
390
+ errorCode: "PREREQUISITES_NOT_ACCEPTED",
391
+ message: `PREREQUISITES_NOT_ACCEPTED: covered task ${covered.taskId} has no currently accepted attempt`,
392
+ };
393
+ }
394
+ const overlapping = [...run.checkpoints.values()].find((other) => other.checkpointId !== checkpoint.checkpointId &&
395
+ other.status === "in_review" &&
396
+ other.scope.some((file) => checkpoint.scope.includes(file)));
397
+ if (overlapping) {
398
+ return {
399
+ ok: false,
400
+ errorCode: "OVERLAPPING_SCOPE",
401
+ message: `OVERLAPPING_SCOPE: checkpoint ${overlapping.checkpointId} is in review and shares declared scope files`,
402
+ };
403
+ }
404
+ const snapshot = await captureWorkflowSnapshot({
405
+ workspaceRoot: run.workspaceRoot,
406
+ declaredPaths: checkpoint.scope,
407
+ coveredAttemptIds: covered.ids,
408
+ });
409
+ if (!snapshot.ok) {
410
+ return {
411
+ ok: false,
412
+ errorCode: "SNAPSHOT_FAILED",
413
+ message: `SNAPSHOT_FAILED: ${snapshot.message}`,
414
+ };
415
+ }
416
+ const generation = checkpoint.attempts + 1;
417
+ const now = toIsoNow();
418
+ const reviewKey = `checkpoint-${input.runId}-${input.checkpointId}-g${generation}`;
419
+ const reviewItem = openReviewItem(data, run.sessionId, reviewKey, checkpoint.reviewers);
420
+ if (!reviewItem.ok) {
421
+ return {
422
+ ok: false,
423
+ errorCode: "SNAPSHOT_FAILED",
424
+ message: `failed to open linked review item: ${reviewItem.message}`,
425
+ };
426
+ }
427
+ const updated = {
428
+ ...checkpoint,
429
+ status: "in_review",
430
+ attempts: generation,
431
+ lastOutcome: undefined,
432
+ currentReview: {
433
+ reviewWorkItemId: reviewItem.record.workItemId,
434
+ generation,
435
+ startedAt: now,
436
+ startFingerprint: snapshot.snapshot.fingerprint,
437
+ coveredAttemptIds: covered.ids,
438
+ reviewerCallIds: {},
439
+ results: {},
440
+ },
441
+ };
442
+ run.checkpoints.set(input.checkpointId, updated);
443
+ return {
444
+ ok: true,
445
+ checkpoint: cloneCheckpoint(updated),
446
+ reviewWorkItemId: reviewItem.record.workItemId,
447
+ header: reviewItem.header,
448
+ reviewersToLaunch: [...updated.reviewers],
449
+ generation,
450
+ };
451
+ }
452
+ function openReviewItem(data, sessionId, key, reviewers) {
453
+ const sessionIndex = data.keyIndexBySession.get(sessionId) ??
454
+ (() => {
455
+ const index = new Map();
456
+ data.keyIndexBySession.set(sessionId, index);
457
+ return index;
458
+ })();
459
+ const existingId = sessionIndex.get(key);
460
+ if (existingId) {
461
+ const existing = data.records.get(createRecordLookupKey(sessionId, existingId));
462
+ if (existing) {
463
+ return {
464
+ ok: true,
465
+ record: cloneRecord(existing),
466
+ header: `VVOC_WORK_ITEM_ID: ${existing.workItemId}`,
467
+ };
468
+ }
469
+ sessionIndex.delete(key);
470
+ }
471
+ const workItemId = `wi-${data.nextId}`;
472
+ data.nextId += 1;
473
+ const now = toIsoNow();
474
+ const sortedReviewers = [...reviewers].sort((left, right) => left === right ? 0 : left === "spec" ? -1 : 1);
475
+ const record = {
476
+ sessionId,
477
+ workItemId,
478
+ key,
479
+ title: `Checkpoint review ${key}`,
480
+ mode: "review_only",
481
+ requiredReviewers: sortedReviewers,
482
+ state: "awaiting_reviews",
483
+ currentRound: {
484
+ round: 1,
485
+ requiredReviewers: sortedReviewers,
486
+ pendingReviewers: sortedReviewers,
487
+ inFlightReviewers: [],
488
+ completedReviewers: [],
489
+ results: {},
490
+ status: "active",
491
+ createdAt: now,
492
+ },
493
+ completedReviewRoundCount: 0,
494
+ specReviewCount: 0,
495
+ codeReviewCount: 0,
496
+ createdAt: now,
497
+ updatedAt: now,
498
+ };
499
+ sessionIndex.set(key, workItemId);
500
+ data.records.set(createRecordLookupKey(sessionId, workItemId), record);
501
+ return { ok: true, record: cloneRecord(record), header: `VVOC_WORK_ITEM_ID: ${workItemId}` };
502
+ }
503
+ function resolveActiveReview(data, runId, checkpointId) {
504
+ const run = findRun(data, runId);
505
+ if (!run) {
506
+ return { ok: false, errorCode: "RUN_NOT_FOUND", message: `RUN_NOT_FOUND: ${runId}` };
507
+ }
508
+ const checkpoint = run.checkpoints.get(checkpointId);
509
+ if (!checkpoint) {
510
+ return {
511
+ ok: false,
512
+ errorCode: "CHECKPOINT_NOT_FOUND",
513
+ message: `CHECKPOINT_NOT_FOUND: ${checkpointId}`,
514
+ };
515
+ }
516
+ if (checkpoint.status !== "in_review" || !checkpoint.currentReview) {
517
+ return {
518
+ ok: false,
519
+ errorCode: "NOT_IN_REVIEW",
520
+ message: `NOT_IN_REVIEW: ${checkpointId} is ${checkpoint.status}`,
521
+ };
522
+ }
523
+ return { ok: true, run, checkpoint, review: checkpoint.currentReview };
524
+ }
525
+ // START_CONTRACT: recordCheckpointReviewerLaunch
526
+ // PURPOSE: Bind one reviewer launch call identity to the current checkpoint generation.
527
+ // INPUTS: { store: WorkItemStore - backing store, input: { runId, checkpointId, reviewer, callId } }
528
+ // OUTPUTS: { RecordReviewerLaunchResult - binding outcome }
529
+ // SIDE_EFFECTS: [Mutates the current generation's reviewer call map]
530
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-PLUGIN-WORKFLOW]
531
+ // END_CONTRACT: recordCheckpointReviewerLaunch
532
+ export function recordCheckpointReviewerLaunch(store, input) {
533
+ return recordCheckpointReviewerLaunchInStore(store.getStoreData(), input);
534
+ }
535
+ export function recordCheckpointReviewerLaunchInStore(data, input) {
536
+ const active = resolveActiveReview(data, input.runId, input.checkpointId);
537
+ if (!active.ok)
538
+ return active;
539
+ if (!active.checkpoint.reviewers.includes(input.reviewer)) {
540
+ return {
541
+ ok: false,
542
+ errorCode: "REVIEWER_NOT_DECLARED",
543
+ message: `REVIEWER_NOT_DECLARED: ${input.reviewer} is not declared for ${input.checkpointId}`,
544
+ };
545
+ }
546
+ if (active.review.reviewerCallIds[input.reviewer]) {
547
+ return {
548
+ ok: false,
549
+ errorCode: "ALREADY_LAUNCHED",
550
+ message: `ALREADY_LAUNCHED: ${input.reviewer} already launched for generation ${active.review.generation}`,
551
+ };
552
+ }
553
+ if (active.review.results[input.reviewer]) {
554
+ return {
555
+ ok: false,
556
+ errorCode: "ALREADY_RECORDED",
557
+ message: `ALREADY_RECORDED: ${input.reviewer} already recorded a result`,
558
+ };
559
+ }
560
+ const callId = input.callId?.trim() ?? "";
561
+ if (!callId) {
562
+ return {
563
+ ok: false,
564
+ errorCode: "REVIEWER_NOT_DECLARED",
565
+ message: "callId must be a non-empty string",
566
+ };
567
+ }
568
+ active.review.reviewerCallIds[input.reviewer] = callId;
569
+ return { ok: true };
570
+ }
571
+ // START_CONTRACT: recordCheckpointReviewerResult
572
+ // PURPOSE: Record one reviewer outcome for the current generation, rejecting stale or mismatched callbacks.
573
+ // INPUTS: { store: WorkItemStore - backing store, input: { runId, checkpointId, reviewer, callId, status } }
574
+ // OUTPUTS: { RecordReviewerResultResult - recording outcome and round completeness }
575
+ // SIDE_EFFECTS: [Mutates the current generation's recorded results]
576
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-PLUGIN-WORKFLOW]
577
+ // END_CONTRACT: recordCheckpointReviewerResult
578
+ export function recordCheckpointReviewerResult(store, input) {
579
+ return recordCheckpointReviewerResultInStore(store.getStoreData(), input);
580
+ }
581
+ export function recordCheckpointReviewerResultInStore(data, input) {
582
+ const active = resolveActiveReview(data, input.runId, input.checkpointId);
583
+ if (!active.ok)
584
+ return active;
585
+ if (!active.checkpoint.reviewers.includes(input.reviewer)) {
586
+ return {
587
+ ok: false,
588
+ errorCode: "REVIEWER_NOT_DECLARED",
589
+ message: `REVIEWER_NOT_DECLARED: ${input.reviewer} is not declared for ${input.checkpointId}`,
590
+ };
591
+ }
592
+ if (active.review.results[input.reviewer]) {
593
+ return {
594
+ ok: false,
595
+ errorCode: "ALREADY_RECORDED",
596
+ message: `ALREADY_RECORDED: ${input.reviewer} already recorded a result for generation ${active.review.generation}`,
597
+ };
598
+ }
599
+ const boundCallId = active.review.reviewerCallIds[input.reviewer];
600
+ if (boundCallId && input.callId !== boundCallId) {
601
+ return {
602
+ ok: false,
603
+ errorCode: "CALLBACK_MISMATCH",
604
+ message: `CALLBACK_MISMATCH: ${input.reviewer} result call ${String(input.callId)} does not match launch call ${boundCallId}`,
605
+ };
606
+ }
607
+ active.review.results[input.reviewer] = {
608
+ status: input.status,
609
+ recordedAt: toIsoNow(),
610
+ };
611
+ const roundComplete = active.checkpoint.reviewers.every((reviewer) => active.review.results[reviewer] !== undefined);
612
+ return { ok: true, roundComplete };
613
+ }
614
+ async function approvalInputsCurrent(run) {
615
+ try {
616
+ const planContent = await readFile(run.planPath, "utf8");
617
+ const specContent = await readFile(run.specPath, "utf8");
618
+ return (contentSha256(planContent) === run.planSha256 && contentSha256(specContent) === run.specSha256);
619
+ }
620
+ catch {
621
+ return false;
622
+ }
623
+ }
624
+ // START_CONTRACT: verifyDelegatedCheckpoint
625
+ // PURPOSE: Derive the checkpoint outcome from recorded reviewer results and freshly recomputed fingerprints and approval-input hashes.
626
+ // INPUTS: { store: WorkItemStore - backing store, input: VerifyDelegatedCheckpointInput - verify request with optional final complete }
627
+ // OUTPUTS: { VerifyDelegatedCheckpointResult - derived outcome and optional sealed completion, or a coded rejection }
628
+ // SIDE_EFFECTS: [Settles generations and seals completed runs; read-only for already-passed checkpoints]
629
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-WORKFLOW-SNAPSHOTS, captureWorkflowSnapshot]
630
+ // END_CONTRACT: verifyDelegatedCheckpoint
631
+ export async function verifyDelegatedCheckpoint(store, input) {
632
+ return verifyDelegatedCheckpointInStore(store.getStoreData(), input);
633
+ }
634
+ export async function verifyDelegatedCheckpointInStore(data, input) {
635
+ const run = findRun(data, input.runId);
636
+ if (!run) {
637
+ return { ok: false, errorCode: "RUN_NOT_FOUND", message: `RUN_NOT_FOUND: ${input.runId}` };
638
+ }
639
+ const checkpoint = run.checkpoints.get(input.checkpointId);
640
+ if (!checkpoint) {
641
+ return {
642
+ ok: false,
643
+ errorCode: "CHECKPOINT_NOT_FOUND",
644
+ message: `CHECKPOINT_NOT_FOUND: ${input.checkpointId}`,
645
+ };
646
+ }
647
+ if (input.complete === true && checkpoint.kind !== "final") {
648
+ return {
649
+ ok: false,
650
+ errorCode: "FINAL_COMPLETE_ON_NON_FINAL",
651
+ message: `FINAL_COMPLETE_ON_NON_FINAL: complete is only permitted for the final checkpoint, not ${input.checkpointId}`,
652
+ };
653
+ }
654
+ // Historical milestones stay passed; rechecking only reports snapshot currency.
655
+ // A passed FINAL checkpoint may still seal its run through an explicit
656
+ // complete request, provided completion conditions and approval inputs are
657
+ // freshly satisfied (bounded rework stays possible before this succeeds).
658
+ if (checkpoint.status === "passed") {
659
+ if (input.complete === true) {
660
+ if (checkpoint.kind !== "final") {
661
+ return {
662
+ ok: false,
663
+ errorCode: "FINAL_COMPLETE_ON_NON_FINAL",
664
+ message: `FINAL_COMPLETE_ON_NON_FINAL: complete is only permitted for the final checkpoint, not ${input.checkpointId}`,
665
+ };
666
+ }
667
+ if (!(await approvalInputsCurrent(run))) {
668
+ return {
669
+ ok: false,
670
+ errorCode: "PLAN_DRIFT",
671
+ message: `PLAN_DRIFT: approved plan or spec content changed after registration of run ${input.runId}`,
672
+ };
673
+ }
674
+ const coveredForCompletion = currentCoveredAttemptIds(data, run, checkpoint);
675
+ if (!coveredForCompletion.ok) {
676
+ return {
677
+ ok: false,
678
+ errorCode: "COMPLETE_REQUIREMENTS_UNMET",
679
+ message: `COMPLETE_REQUIREMENTS_UNMET: covered task ${coveredForCompletion.taskId} lost its current acceptance`,
680
+ };
681
+ }
682
+ const completion = evaluateRunCompletion(data, run, checkpoint);
683
+ if (!completion.ok) {
684
+ return { ok: false, errorCode: "COMPLETE_REQUIREMENTS_UNMET", message: completion.message };
685
+ }
686
+ run.status = "sealed";
687
+ run.sealedAt = toIsoNow();
688
+ return {
689
+ ok: true,
690
+ checkpoint: cloneCheckpoint(checkpoint),
691
+ outcome: "passed",
692
+ snapshotCurrent: true,
693
+ sealedRun: true,
694
+ };
695
+ }
696
+ const covered = currentCoveredAttemptIds(data, run, checkpoint);
697
+ const coveredIds = covered.ok ? covered.ids : [];
698
+ const snapshot = await captureWorkflowSnapshot({
699
+ workspaceRoot: run.workspaceRoot,
700
+ declaredPaths: checkpoint.scope,
701
+ coveredAttemptIds: coveredIds,
702
+ });
703
+ if (!snapshot.ok) {
704
+ return {
705
+ ok: false,
706
+ errorCode: "SNAPSHOT_FAILED",
707
+ message: `SNAPSHOT_FAILED: ${snapshot.message}`,
708
+ };
709
+ }
710
+ const historicalFingerprint = checkpoint.history[checkpoint.history.length - 1]?.fingerprint;
711
+ return {
712
+ ok: true,
713
+ checkpoint: cloneCheckpoint(checkpoint),
714
+ outcome: "already-passed",
715
+ snapshotCurrent: snapshot.snapshot.fingerprint === historicalFingerprint,
716
+ };
717
+ }
718
+ if (checkpoint.status !== "in_review" || !checkpoint.currentReview) {
719
+ return {
720
+ ok: false,
721
+ errorCode: "COMPLETE_REQUIREMENTS_UNMET",
722
+ message: `COMPLETE_REQUIREMENTS_UNMET: ${input.checkpointId} is ${checkpoint.status}; start it before verifying`,
723
+ };
724
+ }
725
+ const review = checkpoint.currentReview;
726
+ if (!(await approvalInputsCurrent(run))) {
727
+ return {
728
+ ok: false,
729
+ errorCode: "PLAN_DRIFT",
730
+ message: `PLAN_DRIFT: approved plan or spec content changed after registration of run ${input.runId}`,
731
+ };
732
+ }
733
+ const covered = currentCoveredAttemptIds(data, run, checkpoint);
734
+ if (!covered.ok) {
735
+ return {
736
+ ok: false,
737
+ errorCode: "COMPLETE_REQUIREMENTS_UNMET",
738
+ message: `COMPLETE_REQUIREMENTS_UNMET: covered task ${covered.taskId} lost its current acceptance`,
739
+ };
740
+ }
741
+ const snapshot = await captureWorkflowSnapshot({
742
+ workspaceRoot: run.workspaceRoot,
743
+ declaredPaths: checkpoint.scope,
744
+ coveredAttemptIds: covered.ids,
745
+ });
746
+ if (!snapshot.ok) {
747
+ return {
748
+ ok: false,
749
+ errorCode: "SNAPSHOT_FAILED",
750
+ message: `SNAPSHOT_FAILED: ${snapshot.message}`,
751
+ };
752
+ }
753
+ const snapshotCurrent = snapshot.snapshot.fingerprint === review.startFingerprint &&
754
+ JSON.stringify(covered.ids) === JSON.stringify(review.coveredAttemptIds);
755
+ const anyNeedsContext = checkpoint.reviewers.some((reviewer) => review.results[reviewer]?.status === "NEEDS_CONTEXT");
756
+ if (anyNeedsContext) {
757
+ const updated = { ...checkpoint, lastOutcome: "stopped" };
758
+ run.checkpoints.set(input.checkpointId, updated);
759
+ return {
760
+ ok: true,
761
+ checkpoint: cloneCheckpoint(updated),
762
+ outcome: "stopped",
763
+ snapshotCurrent,
764
+ };
765
+ }
766
+ const allRecorded = checkpoint.reviewers.every((reviewer) => review.results[reviewer] !== undefined);
767
+ if (!allRecorded) {
768
+ const updated = { ...checkpoint, lastOutcome: "incomplete" };
769
+ run.checkpoints.set(input.checkpointId, updated);
770
+ return {
771
+ ok: true,
772
+ checkpoint: cloneCheckpoint(updated),
773
+ outcome: "incomplete",
774
+ snapshotCurrent,
775
+ };
776
+ }
777
+ const allPass = checkpoint.reviewers.every((reviewer) => review.results[reviewer]?.status === "PASS");
778
+ const now = toIsoNow();
779
+ if (!snapshotCurrent) {
780
+ const settled = {
781
+ ...checkpoint,
782
+ status: "failed",
783
+ lastOutcome: "stale",
784
+ currentReview: undefined,
785
+ history: [
786
+ ...checkpoint.history,
787
+ {
788
+ generation: review.generation,
789
+ outcome: "stale",
790
+ fingerprint: snapshot.snapshot.fingerprint,
791
+ completedAt: now,
792
+ },
793
+ ],
794
+ };
795
+ run.checkpoints.set(input.checkpointId, settled);
796
+ return {
797
+ ok: true,
798
+ checkpoint: cloneCheckpoint(settled),
799
+ outcome: "stale",
800
+ snapshotCurrent: false,
801
+ };
802
+ }
803
+ if (allPass) {
804
+ const settled = {
805
+ ...checkpoint,
806
+ status: "passed",
807
+ lastOutcome: "passed",
808
+ currentReview: undefined,
809
+ history: [
810
+ ...checkpoint.history,
811
+ {
812
+ generation: review.generation,
813
+ outcome: "passed",
814
+ fingerprint: review.startFingerprint,
815
+ completedAt: now,
816
+ },
817
+ ],
818
+ };
819
+ run.checkpoints.set(input.checkpointId, settled);
820
+ if (input.complete === true) {
821
+ const completion = evaluateRunCompletion(data, run, settled);
822
+ if (!completion.ok) {
823
+ return { ok: false, errorCode: "COMPLETE_REQUIREMENTS_UNMET", message: completion.message };
824
+ }
825
+ run.status = "sealed";
826
+ run.sealedAt = now;
827
+ return {
828
+ ok: true,
829
+ checkpoint: cloneCheckpoint(settled),
830
+ outcome: "passed",
831
+ snapshotCurrent: true,
832
+ sealedRun: true,
833
+ };
834
+ }
835
+ return {
836
+ ok: true,
837
+ checkpoint: cloneCheckpoint(settled),
838
+ outcome: "passed",
839
+ snapshotCurrent: true,
840
+ };
841
+ }
842
+ const failed = {
843
+ ...checkpoint,
844
+ status: "failed",
845
+ lastOutcome: "failed",
846
+ currentReview: undefined,
847
+ history: [
848
+ ...checkpoint.history,
849
+ {
850
+ generation: review.generation,
851
+ outcome: "failed",
852
+ fingerprint: review.startFingerprint,
853
+ completedAt: now,
854
+ },
855
+ ],
856
+ };
857
+ run.checkpoints.set(input.checkpointId, failed);
858
+ return {
859
+ ok: true,
860
+ checkpoint: cloneCheckpoint(failed),
861
+ outcome: "failed",
862
+ snapshotCurrent: true,
863
+ };
864
+ }
865
+ function evaluateRunCompletion(data, run, finalCheckpoint) {
866
+ for (const task of run.definition.tasks) {
867
+ const binding = run.tasks.get(task.taskId);
868
+ if (!binding) {
869
+ return { ok: false, message: `task ${task.taskId} has no bound work item` };
870
+ }
871
+ const record = findRecord(data, run.sessionId, binding.workItemId);
872
+ const acceptance = record ? currentDelegatedAcceptance(record) : undefined;
873
+ if (!acceptance) {
874
+ return { ok: false, message: `task ${task.taskId} has no currently accepted attempt` };
875
+ }
876
+ }
877
+ for (const checkpoint of run.checkpoints.values()) {
878
+ if (checkpoint.checkpointId === finalCheckpoint.checkpointId)
879
+ continue;
880
+ if (checkpoint.status !== "passed") {
881
+ return {
882
+ ok: false,
883
+ message: `milestone ${checkpoint.checkpointId} is ${checkpoint.status}, not passed`,
884
+ };
885
+ }
886
+ }
887
+ const coveredScope = new Set(run.definition.tasks.flatMap((task) => task.writeScope));
888
+ for (const scopeFile of coveredScope) {
889
+ if (!finalCheckpoint.scope.includes(scopeFile)) {
890
+ return {
891
+ ok: false,
892
+ message: `final checkpoint scope does not cover declared write scope file ${scopeFile}`,
893
+ };
894
+ }
895
+ }
896
+ return { ok: true };
897
+ }
898
+ // START_CONTRACT: authorizeReworkFromFailedCheckpoint
899
+ // PURPOSE: Validate a failed-checkpoint rework authorization for a covered accepted task and apply the guarded rework reducer.
900
+ // INPUTS: { store: WorkItemStore - backing store, input: AuthorizeReworkInput - run, checkpoint, work item, and bounded reason }
901
+ // OUTPUTS: { AuthorizeReworkResult - applied rework or a coded rejection }
902
+ // SIDE_EFFECTS: [Reopens the covered delegated item via reworkDelegatedWorkItem]
903
+ // LINKS: [M-WORKFLOW-CHECKPOINTS, M-WORKFLOW-DELEGATED, reworkDelegatedWorkItem]
904
+ // END_CONTRACT: authorizeReworkFromFailedCheckpoint
905
+ export function authorizeReworkFromFailedCheckpoint(store, input) {
906
+ return authorizeReworkFromFailedCheckpointInStore(store.getStoreData(), input);
907
+ }
908
+ export function authorizeReworkFromFailedCheckpointInStore(data, input) {
909
+ const run = findRun(data, input.runId);
910
+ if (!run) {
911
+ return { ok: false, errorCode: "RUN_NOT_FOUND", message: `RUN_NOT_FOUND: ${input.runId}` };
912
+ }
913
+ if (run.sessionId !== input.sessionId) {
914
+ return {
915
+ ok: false,
916
+ errorCode: "SESSION_MISMATCH",
917
+ message: `SESSION_MISMATCH: run ${input.runId} belongs to session ${run.sessionId}`,
918
+ };
919
+ }
920
+ if (run.status === "sealed") {
921
+ return {
922
+ ok: false,
923
+ errorCode: "RUN_SEALED",
924
+ message: `RUN_SEALED: run ${input.runId} is complete; rework requires a new change`,
925
+ };
926
+ }
927
+ const checkpoint = run.checkpoints.get(input.checkpointId);
928
+ if (!checkpoint) {
929
+ return {
930
+ ok: false,
931
+ errorCode: "CHECKPOINT_NOT_FOUND",
932
+ message: `CHECKPOINT_NOT_FOUND: ${input.checkpointId}`,
933
+ };
934
+ }
935
+ if (checkpoint.status !== "failed") {
936
+ return {
937
+ ok: false,
938
+ errorCode: "CHECKPOINT_NOT_FAILED",
939
+ message: `CHECKPOINT_NOT_FAILED: ${input.checkpointId} is ${checkpoint.status}`,
940
+ };
941
+ }
942
+ if (checkpoint.lastOutcome === "stopped") {
943
+ return {
944
+ ok: false,
945
+ errorCode: "HARD_STOP_CHECKPOINT",
946
+ message: `HARD_STOP_CHECKPOINT: ${input.checkpointId} stopped on NEEDS_CONTEXT; resolve the hard stop first`,
947
+ };
948
+ }
949
+ const record = findRecord(data, input.sessionId, input.workItemId);
950
+ if (!record || record.mode !== "delegated" || !record.delegated) {
951
+ return {
952
+ ok: false,
953
+ errorCode: "WORK_ITEM_NOT_BOUND",
954
+ message: `WORK_ITEM_NOT_BOUND: ${input.workItemId} is not a delegated work item`,
955
+ };
956
+ }
957
+ if (record.delegated.planRunId !== input.runId || !record.delegated.planTaskId) {
958
+ return {
959
+ ok: false,
960
+ errorCode: "WORK_ITEM_NOT_BOUND",
961
+ message: `WORK_ITEM_NOT_BOUND: ${input.workItemId} is not bound to run ${input.runId}`,
962
+ };
963
+ }
964
+ if (!checkpoint.covers.includes(record.delegated.planTaskId)) {
965
+ return {
966
+ ok: false,
967
+ errorCode: "TASK_NOT_COVERED",
968
+ message: `TASK_NOT_COVERED: ${input.checkpointId} does not cover task ${record.delegated.planTaskId}`,
969
+ };
970
+ }
971
+ const reworked = reworkDelegatedWorkItem({ getStoreData: () => data }, {
972
+ sessionId: input.sessionId,
973
+ workItemId: input.workItemId,
974
+ planRunId: input.runId,
975
+ failedCheckpointId: input.checkpointId,
976
+ reason: input.reason,
977
+ });
978
+ if (!reworked.ok) {
979
+ return {
980
+ ok: false,
981
+ errorCode: "REWORK_REJECTED",
982
+ message: `REWORK_REJECTED: ${reworked.message}`,
983
+ };
984
+ }
985
+ return { ok: true, reworkId: reworked.reworkId, grantedAttempts: reworked.grantedAttempts };
986
+ }
987
+ // START_BLOCK_BARRIER_GATES
988
+ /** Unsatisfied checkpoint barriers blocking task launches for the given wave, in declaration order. */
989
+ export function checkpointBarrierUnsatisfied(data, runId, wave) {
990
+ const run = findRun(data, runId);
991
+ if (!run)
992
+ return { ok: false, message: `RUN_NOT_FOUND: ${runId}` };
993
+ const waveIndex = waveIndexOf(run, wave);
994
+ if (waveIndex < 0)
995
+ return { ok: false, message: `wave ${wave} is not declared in run ${runId}` };
996
+ const blockers = [];
997
+ for (const checkpoint of run.checkpoints.values()) {
998
+ if (checkpoint.kind !== "milestone")
999
+ continue;
1000
+ const afterIndex = waveIndexOf(run, checkpoint.afterWave);
1001
+ if (afterIndex < waveIndex && checkpoint.status !== "passed") {
1002
+ blockers.push(checkpoint.checkpointId);
1003
+ }
1004
+ }
1005
+ return { ok: true, blockers };
1006
+ }
1007
+ /** In-flight checkpoint review whose scope intersects the declared write scope. */
1008
+ export function findOverlappingInFlightReview(data, runId, writeScope) {
1009
+ const run = findRun(data, runId);
1010
+ if (!run)
1011
+ return undefined;
1012
+ for (const checkpoint of run.checkpoints.values()) {
1013
+ if (checkpoint.status !== "in_review")
1014
+ continue;
1015
+ if (checkpoint.scope.some((file) => writeScope.includes(file))) {
1016
+ return checkpoint.checkpointId;
1017
+ }
1018
+ }
1019
+ return undefined;
1020
+ }
1021
+ // END_BLOCK_BARRIER_GATES
1022
+ // START_BLOCK_RUN_VIEW
1023
+ /** Read-only run serialization for tooling output and persistence consumers. */
1024
+ export function getDelegatedRunView(data, runId) {
1025
+ const run = findRun(data, runId);
1026
+ if (!run)
1027
+ return undefined;
1028
+ return {
1029
+ runId: run.runId,
1030
+ sessionId: run.sessionId,
1031
+ planPath: run.planPath,
1032
+ specPath: run.specPath,
1033
+ workspaceRoot: run.workspaceRoot,
1034
+ status: run.status,
1035
+ registeredAt: run.registeredAt,
1036
+ ...(run.sealedAt ? { sealedAt: run.sealedAt } : {}),
1037
+ finalCheckpointId: run.finalCheckpointId,
1038
+ tasks: [...run.tasks.values()].map((task) => ({
1039
+ taskId: task.taskId,
1040
+ workItemId: task.workItemId,
1041
+ })),
1042
+ checkpoints: [...run.checkpoints.values()].map((checkpoint) => ({
1043
+ checkpointId: checkpoint.checkpointId,
1044
+ kind: checkpoint.kind,
1045
+ afterWave: checkpoint.afterWave,
1046
+ covers: checkpoint.covers,
1047
+ scope: checkpoint.scope,
1048
+ reviewers: checkpoint.reviewers,
1049
+ status: checkpoint.status,
1050
+ attempts: checkpoint.attempts,
1051
+ ...(checkpoint.lastOutcome ? { lastOutcome: checkpoint.lastOutcome } : {}),
1052
+ ...(checkpoint.currentReview
1053
+ ? {
1054
+ currentReview: {
1055
+ reviewWorkItemId: checkpoint.currentReview.reviewWorkItemId,
1056
+ generation: checkpoint.currentReview.generation,
1057
+ coveredAttemptIds: checkpoint.currentReview.coveredAttemptIds,
1058
+ recordedReviewers: Object.keys(checkpoint.currentReview.results),
1059
+ },
1060
+ }
1061
+ : {}),
1062
+ history: checkpoint.history,
1063
+ })),
1064
+ };
1065
+ }
1066
+ // END_BLOCK_RUN_VIEW
1067
+ //# sourceMappingURL=checkpoints.js.map