ai-foreman 1.7.20 → 1.7.21

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 (189) hide show
  1. package/README.md +12 -2
  2. package/dist/activity.d.ts +3 -0
  3. package/dist/activity.js +18 -6
  4. package/dist/adapters/claude.d.ts +16 -0
  5. package/dist/adapters/claude.js +28 -3
  6. package/dist/adapters/codex.d.ts +20 -0
  7. package/dist/adapters/codex.js +99 -5
  8. package/dist/adapters/recovering.d.ts +9 -0
  9. package/dist/adapters/recovering.js +10 -0
  10. package/dist/adapters/types.d.ts +15 -0
  11. package/dist/agentRun.d.ts +7 -0
  12. package/dist/agentRun.js +24 -13
  13. package/dist/branch/currentGuard.d.ts +1 -0
  14. package/dist/branch/currentGuard.js +1 -0
  15. package/dist/branch/finalization.js +6 -1
  16. package/dist/branch/git.d.ts +1 -0
  17. package/dist/branch/git.js +15 -1
  18. package/dist/branch/runner.d.ts +2 -0
  19. package/dist/branch/runner.js +90 -4
  20. package/dist/buildAdmission.js +2 -0
  21. package/dist/buildAssignment.d.ts +33 -0
  22. package/dist/buildAssignment.js +215 -0
  23. package/dist/buildInterventions.d.ts +90 -0
  24. package/dist/buildInterventions.js +334 -0
  25. package/dist/buildOwnershipReconciliation.d.ts +19 -0
  26. package/dist/buildOwnershipReconciliation.js +41 -0
  27. package/dist/buildOwnershipRepair.d.ts +33 -0
  28. package/dist/buildOwnershipRepair.js +111 -0
  29. package/dist/buildRuns.d.ts +6 -0
  30. package/dist/buildRuns.js +23 -17
  31. package/dist/buildSyntheticWork.d.ts +2 -0
  32. package/dist/buildSyntheticWork.js +30 -0
  33. package/dist/buildWorkAdmission.d.ts +17 -0
  34. package/dist/buildWorkAdmission.js +57 -0
  35. package/dist/buildWorkAuthorization.d.ts +2 -0
  36. package/dist/buildWorkAuthorization.js +23 -0
  37. package/dist/buildWorkContext.d.ts +3 -0
  38. package/dist/buildWorkContext.js +6 -0
  39. package/dist/buildWorkMigration.d.ts +9 -0
  40. package/dist/buildWorkMigration.js +206 -0
  41. package/dist/buildWorkReconciliation.d.ts +12 -0
  42. package/dist/buildWorkReconciliation.js +70 -0
  43. package/dist/builderGuidanceFollowup.d.ts +15 -0
  44. package/dist/builderGuidanceFollowup.js +34 -0
  45. package/dist/cli/doctor.js +3 -2
  46. package/dist/cli/graph.d.ts +2 -0
  47. package/dist/cli/graph.js +75 -0
  48. package/dist/cli/manager.d.ts +8 -0
  49. package/dist/cli/manager.js +234 -12
  50. package/dist/cli/recovery.js +3 -2
  51. package/dist/cli/runtimeAuthPrompt.js +28 -8
  52. package/dist/cli/start.js +152 -48
  53. package/dist/cli/status.js +20 -5
  54. package/dist/cli/tickets.js +9 -7
  55. package/dist/continuity.d.ts +11 -0
  56. package/dist/continuity.js +22 -6
  57. package/dist/foreman.d.ts +10 -2
  58. package/dist/foreman.js +330 -39
  59. package/dist/graph/boundary.d.ts +15 -0
  60. package/dist/graph/boundary.js +50 -0
  61. package/dist/graph/bridge.d.ts +9 -0
  62. package/dist/graph/bridge.js +146 -0
  63. package/dist/graph/bridge.py +205 -0
  64. package/dist/graph/config.d.ts +11 -0
  65. package/dist/graph/config.js +40 -0
  66. package/dist/graph/corpus.d.ts +16 -0
  67. package/dist/graph/corpus.js +188 -0
  68. package/dist/graph/derived.d.ts +17 -0
  69. package/dist/graph/derived.js +73 -0
  70. package/dist/graph/host.d.ts +4 -0
  71. package/dist/graph/host.js +160 -0
  72. package/dist/graph/install.d.ts +3 -0
  73. package/dist/graph/install.js +50 -0
  74. package/dist/graph/lifecycle.d.ts +11 -0
  75. package/dist/graph/lifecycle.js +124 -0
  76. package/dist/graph/maintenance.d.ts +43 -0
  77. package/dist/graph/maintenance.js +301 -0
  78. package/dist/graph/manager.d.ts +9 -0
  79. package/dist/graph/manager.js +92 -0
  80. package/dist/graph/preparation.d.ts +41 -0
  81. package/dist/graph/preparation.js +43 -0
  82. package/dist/graph/read.d.ts +27 -0
  83. package/dist/graph/read.js +152 -0
  84. package/dist/graph/semantic.d.ts +17 -0
  85. package/dist/graph/semantic.js +27 -0
  86. package/dist/graph/session.d.ts +8 -0
  87. package/dist/graph/session.js +92 -0
  88. package/dist/graph/sources.d.ts +4 -0
  89. package/dist/graph/sources.js +11 -0
  90. package/dist/graph/storage.d.ts +38 -0
  91. package/dist/graph/storage.js +159 -0
  92. package/dist/graph/turn.d.ts +17 -0
  93. package/dist/graph/turn.js +360 -0
  94. package/dist/graph/util.d.ts +5 -0
  95. package/dist/graph/util.js +51 -0
  96. package/dist/handoffs.d.ts +1 -0
  97. package/dist/handoffs.js +5 -1
  98. package/dist/humanDecision.d.ts +21 -2
  99. package/dist/humanDecision.js +169 -11
  100. package/dist/index.js +2 -0
  101. package/dist/installOwnership.d.ts +46 -0
  102. package/dist/installOwnership.js +440 -0
  103. package/dist/linuxProbeInventory.d.ts +12 -0
  104. package/dist/linuxProbeInventory.js +48 -0
  105. package/dist/log.d.ts +1 -1
  106. package/dist/log.js +7 -1
  107. package/dist/managerEvidence.d.ts +25 -0
  108. package/dist/managerEvidence.js +326 -0
  109. package/dist/managerEvidenceArtifacts.d.ts +38 -0
  110. package/dist/managerEvidenceArtifacts.js +118 -0
  111. package/dist/managerPacket.d.ts +2 -0
  112. package/dist/managerPacket.js +23 -0
  113. package/dist/markers.d.ts +1 -1
  114. package/dist/markers.js +2 -2
  115. package/dist/observability.js +1 -1
  116. package/dist/processIdentity.js +7 -0
  117. package/dist/providerPhase.d.ts +13 -0
  118. package/dist/providerPhase.js +26 -0
  119. package/dist/qaAuthorizedDisposition.d.ts +24 -0
  120. package/dist/qaAuthorizedDisposition.js +26 -0
  121. package/dist/qaBuildGate.d.ts +4 -0
  122. package/dist/qaBuildGate.js +216 -0
  123. package/dist/qaBuilderCoverage.d.ts +5 -0
  124. package/dist/qaBuilderCoverage.js +78 -0
  125. package/dist/qaContractCoverage.d.ts +10 -0
  126. package/dist/qaContractCoverage.js +55 -0
  127. package/dist/qaContractDelivery.d.ts +23 -0
  128. package/dist/qaContractDelivery.js +145 -0
  129. package/dist/qaContractFreshness.d.ts +15 -0
  130. package/dist/qaContractFreshness.js +36 -0
  131. package/dist/qaDeliveryJournal.d.ts +10 -0
  132. package/dist/qaDeliveryJournal.js +16 -1
  133. package/dist/qaEffectiveConfig.d.ts +20 -0
  134. package/dist/qaEffectiveConfig.js +40 -0
  135. package/dist/qaEvidenceReader.d.ts +14 -0
  136. package/dist/qaEvidenceReader.js +176 -0
  137. package/dist/qaFailureDelivery.d.ts +2 -0
  138. package/dist/qaFailureDelivery.js +107 -10
  139. package/dist/qaHandbackMigration.d.ts +1 -1
  140. package/dist/qaHandbackMigration.js +3 -3
  141. package/dist/qaPreparation.d.ts +26 -0
  142. package/dist/qaPreparation.js +150 -0
  143. package/dist/qaPreparationInputs.d.ts +8 -0
  144. package/dist/qaPreparationInputs.js +47 -0
  145. package/dist/qaPreparationMetrics.d.ts +145 -0
  146. package/dist/qaPreparationMetrics.js +120 -0
  147. package/dist/qaPreparationPolicy.d.ts +5 -0
  148. package/dist/qaPreparationPolicy.js +51 -0
  149. package/dist/qaPreparationStore.d.ts +157 -0
  150. package/dist/qaPreparationStore.js +412 -0
  151. package/dist/qaProtocolV2.d.ts +28 -0
  152. package/dist/qaProtocolV2.js +12 -0
  153. package/dist/qaReview.d.ts +6 -0
  154. package/dist/qaReview.js +234 -24
  155. package/dist/qaTimeline.d.ts +18 -0
  156. package/dist/qaTimeline.js +123 -0
  157. package/dist/qaVerificationContract.d.ts +17 -0
  158. package/dist/qaVerificationContract.js +236 -0
  159. package/dist/readinessCleanup.js +12 -5
  160. package/dist/recoveryGuidance.d.ts +9 -0
  161. package/dist/recoveryGuidance.js +13 -0
  162. package/dist/runtimeAuth.d.ts +1 -0
  163. package/dist/runtimeAuth.js +5 -1
  164. package/dist/runtimeReadiness.d.ts +1 -0
  165. package/dist/runtimeReadiness.js +13 -1
  166. package/dist/sessionIdentity.d.ts +2 -0
  167. package/dist/sessionIdentity.js +14 -0
  168. package/dist/sessionLifecycle.js +3 -3
  169. package/dist/sources/sourceRegistry.js +7 -0
  170. package/dist/stateTransfer.js +50 -1
  171. package/dist/statusReporter.d.ts +1 -0
  172. package/dist/statusReporter.js +1 -0
  173. package/dist/supervisedStart.js +18 -8
  174. package/dist/terminalStyle.d.ts +21 -0
  175. package/dist/terminalStyle.js +50 -0
  176. package/dist/ticketPopulation.d.ts +1 -0
  177. package/dist/ticketPopulation.js +5 -1
  178. package/dist/tickets/commands.js +2 -2
  179. package/dist/tickets/queue.js +1 -1
  180. package/dist/tickets/ticketSchema.d.ts +198 -0
  181. package/dist/tickets/ticketSchema.js +2 -0
  182. package/dist/workflowDb.d.ts +51 -5
  183. package/dist/workflowDb.js +338 -7
  184. package/dist/workflowReader.d.ts +109 -2
  185. package/dist/workflowReader.js +48 -4
  186. package/package.json +84 -7
  187. package/scripts/copy-graph-assets.mjs +3 -0
  188. package/scripts/graph-benchmark.mjs +20 -0
  189. package/scripts/graph-evaluation.mjs +85 -0
@@ -154,6 +154,7 @@ export async function superviseStart(projectDir, runId, policy, selection) {
154
154
  db.ensureRun(runId, "build");
155
155
  db.freezeAutonomyPolicy(runId, policy);
156
156
  let exitCode = 2;
157
+ let stopRequestSource;
157
158
  const supervisor = new DurableSupervisor({ projectDir, runId, policy, admission: localBuildAuthority(projectDir),
158
159
  checkpoint: () => db.getRun(runId)?.checkpoint ?? "preparing",
159
160
  spawnWorker: generation => {
@@ -201,8 +202,10 @@ export async function superviseStart(projectDir, runId, policy, selection) {
201
202
  }
202
203
  catch { /* exited */ }
203
204
  };
204
- const watchdog = setInterval(() => { if (Date.now() - lastHeartbeat > 120_000)
205
- stop(); }, 5_000);
205
+ const watchdog = setInterval(() => { if (Date.now() - lastHeartbeat > 120_000) {
206
+ stopRequestSource = "heartbeat-timeout";
207
+ stop();
208
+ } }, 5_000);
206
209
  watchdog.unref();
207
210
  child.on("message", message => { if (message && typeof message === "object" && "kind" in message && message.kind === "rafi-worker-heartbeat")
208
211
  lastHeartbeat = Date.now(); });
@@ -237,6 +240,9 @@ export async function superviseStart(projectDir, runId, policy, selection) {
237
240
  clearTimeout(killTimer);
238
241
  exitCode = code ?? 2;
239
242
  try {
243
+ const exitState = db.getRun(runId);
244
+ if (exitState)
245
+ db.transitionSupervisor(runId, generation, { checkpoint: exitState.checkpoint, event: "worker_exit", payload: { code, signal, stopRequested: stopping, source: stopRequestSource ?? "unknown", at: new Date().toISOString(), generation } });
240
246
  // Durable records are authoritative even when the worker never sent IPC.
241
247
  const deadline = Date.now() + 5000;
242
248
  const owned = db.readinessProcesses(runId).filter(row => {
@@ -277,7 +283,10 @@ export async function superviseStart(projectDir, runId, policy, selection) {
277
283
  }
278
284
  else {
279
285
  const state = db.getRun(runId);
280
- if (code === 0 && state?.status !== "completed") {
286
+ if (code === 0 && state?.status === "cancelled") {
287
+ resolve({ kind: "completed" });
288
+ }
289
+ else if (code === 0 && state?.status !== "completed") {
281
290
  exitCode = 2;
282
291
  resolve({ kind: "waiting_for_human" });
283
292
  }
@@ -294,15 +303,16 @@ export async function superviseStart(projectDir, runId, policy, selection) {
294
303
  return { pid: child.pid, result, stop };
295
304
  },
296
305
  });
297
- const stop = () => supervisor.requestStop();
298
- process.once("SIGINT", stop);
299
- process.once("SIGTERM", stop);
306
+ const stopInt = () => { stopRequestSource = "parent-SIGINT"; supervisor.requestStop(); };
307
+ const stopTerm = () => { stopRequestSource = "parent-SIGTERM"; supervisor.requestStop(); };
308
+ process.once("SIGINT", stopInt);
309
+ process.once("SIGTERM", stopTerm);
300
310
  try {
301
311
  await supervisor.run();
302
312
  }
303
313
  finally {
304
- process.off("SIGINT", stop);
305
- process.off("SIGTERM", stop);
314
+ process.off("SIGINT", stopInt);
315
+ process.off("SIGTERM", stopTerm);
306
316
  db.close();
307
317
  }
308
318
  process.exitCode = exitCode;
@@ -0,0 +1,21 @@
1
+ /** Small, dependency-free terminal styling and display sanitization helpers. */
2
+ export type TerminalColorRole = "accent" | "success" | "warning" | "error" | "info";
3
+ export interface TerminalStyleStream {
4
+ readonly isTTY?: boolean;
5
+ }
6
+ export interface TerminalStyleContext {
7
+ readonly stream?: TerminalStyleStream;
8
+ readonly env?: NodeJS.ProcessEnv;
9
+ readonly platform?: NodeJS.Platform;
10
+ /** Declared data output is always plain, including FORCE_COLOR sessions. */
11
+ readonly machineReadable?: boolean;
12
+ }
13
+ export declare function terminalColorEnabled(context?: TerminalStyleContext): boolean;
14
+ export declare function styleTerminalText(role: TerminalColorRole, text: string, context?: TerminalStyleContext): string;
15
+ export declare function accent(text: string, context?: TerminalStyleContext): string;
16
+ export declare function success(text: string, context?: TerminalStyleContext): string;
17
+ export declare function warning(text: string, context?: TerminalStyleContext): string;
18
+ export declare function error(text: string, context?: TerminalStyleContext): string;
19
+ export declare function info(text: string, context?: TerminalStyleContext): string;
20
+ /** Remove terminal control sequences while retaining intended line breaks. */
21
+ export declare function sanitizeTerminalText(value: string): string;
@@ -0,0 +1,50 @@
1
+ /** Small, dependency-free terminal styling and display sanitization helpers. */
2
+ const ANSI = {
3
+ accent: "\x1b[36m",
4
+ success: "\x1b[32m",
5
+ warning: "\x1b[33m",
6
+ error: "\x1b[31m",
7
+ info: "\x1b[34m",
8
+ };
9
+ const RESET = "\x1b[39m";
10
+ export function terminalColorEnabled(context = {}) {
11
+ if (context.machineReadable)
12
+ return false;
13
+ const env = context.env ?? process.env;
14
+ const stream = context.stream ?? process.stdout;
15
+ if (Object.hasOwn(env, "NO_COLOR"))
16
+ return false;
17
+ if (env.TERM?.toLowerCase() === "dumb")
18
+ return false;
19
+ if (env.FORCE_COLOR === "0")
20
+ return false;
21
+ if (["1", "2", "3"].includes(env.FORCE_COLOR ?? ""))
22
+ return true;
23
+ if (!stream.isTTY)
24
+ return false;
25
+ // Modern Windows terminals advertise ANSI support. A bare legacy console TTY
26
+ // is not enough evidence, so leave it readable and unstyled.
27
+ if ((context.platform ?? process.platform) === "win32") {
28
+ return Boolean(env.WT_SESSION || env.ANSICON || env.ConEmuANSI === "ON" || env.TERM);
29
+ }
30
+ return true;
31
+ }
32
+ export function styleTerminalText(role, text, context = {}) {
33
+ const safeText = sanitizeTerminalText(text);
34
+ return terminalColorEnabled(context) ? `${ANSI[role]}${safeText}${RESET}` : safeText;
35
+ }
36
+ export function accent(text, context) { return styleTerminalText("accent", text, context); }
37
+ export function success(text, context) { return styleTerminalText("success", text, context); }
38
+ export function warning(text, context) { return styleTerminalText("warning", text, context); }
39
+ export function error(text, context) { return styleTerminalText("error", text, context); }
40
+ export function info(text, context) { return styleTerminalText("info", text, context); }
41
+ /** Remove terminal control sequences while retaining intended line breaks. */
42
+ export function sanitizeTerminalText(value) {
43
+ return value
44
+ // CSI, OSC, DCS/SOS/PM/APC, and two-byte ESC controls.
45
+ .replace(/\x1b\](?:[^\x07\x1b]|\x1b(?!\\))*(?:\x07|\x1b\\)?/gs, "")
46
+ .replace(/\x1b(?:[PX^_](?:.|\n)*?\x1b\\|\[[0-?]*[ -/]*[@-~]|[@-_])/g, "")
47
+ .replace(/\x9d[^\x07]*(?:\x07)?/g, "")
48
+ .replace(/\x9b[0-?]*[ -/]*[@-~]/g, "")
49
+ .replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
50
+ }
@@ -2,6 +2,7 @@ import type { StructuredPlanV1 } from "rafi-spec";
2
2
  import type { TicketDef } from "./tickets/ticketSchema.js";
3
3
  import { type DeliveryConfig } from "./tickets/delivery.js";
4
4
  export interface TicketSliceProposal {
5
+ qa_preparation?: TicketDef["qa_preparation"];
5
6
  slice_ref: string;
6
7
  title: string;
7
8
  area: string;
@@ -1,3 +1,4 @@
1
+ import { canonicalContractJson } from "./qaVerificationContract.js";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import { closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
4
  import { dirname, join, resolve } from "node:path";
@@ -30,6 +31,9 @@ export function validateTicketPopulationProposal(proposal, plan, existing) {
30
31
  const planned = new Set(plan.slices.map((slice) => slice.slice_ref));
31
32
  const mapped = new Set();
32
33
  for (const ticket of proposal.tickets) {
34
+ const depth = plan.slices.find(slice => slice.slice_ref === ticket.slice_ref)?.qa_preparation;
35
+ if (ticket.qa_preparation && (!depth || canonicalContractJson(ticket.qa_preparation) !== canonicalContractJson(depth)))
36
+ issues.push(`ticket-maker cannot override planner QA depth for ${ticket.slice_ref}`);
33
37
  if (!planned.has(ticket.slice_ref))
34
38
  issues.push(`unknown slice mapping ${ticket.slice_ref}`);
35
39
  if (mapped.has(ticket.slice_ref))
@@ -64,7 +68,7 @@ export function materializeTicketPopulation(proposal, plan, existing) {
64
68
  const materialized = proposal.tickets.map((slice, index) => {
65
69
  const retained = byPlanSlice.get(`${plan.plan_id}:${slice.slice_ref}`);
66
70
  return {
67
- ...(retained ?? {}), id: sliceToTicket.get(slice.slice_ref), order: retained?.order ?? nextOrder(existing, index),
71
+ ...(retained ?? {}), qa_preparation: structuredClone(plan.slices.find(item => item.slice_ref === slice.slice_ref)?.qa_preparation), id: sliceToTicket.get(slice.slice_ref), order: retained?.order ?? nextOrder(existing, index),
68
72
  title: slice.title, area: slice.area, priority: slice.priority, size: slice.size, risk: slice.risk,
69
73
  depends_on: slice.depends_on.map((ref) => sliceToTicket.get(ref)), summary: slice.summary,
70
74
  acceptance: [...slice.acceptance], required_tests: [...slice.required_tests], likely_files: [...slice.likely_files],
@@ -259,8 +259,8 @@ export function cmdBlock(projectDir, ticketId, opts) {
259
259
  ctx.db.upsertState(ticketId, {
260
260
  status: "blocked",
261
261
  blocked_by_json: JSON.stringify(merged),
262
- blocker_type: opts.blockerType ?? "dependency",
263
- blocker_notes: opts.unblockCriteria ?? null,
262
+ blocker_type: opts.blockerType ?? (merged.length ? "dependency" : "external"),
263
+ blocker_notes: opts.unblockCriteria ?? opts.summary ?? "Resolve the external prerequisite before continuing",
264
264
  first_blocked_at: state?.first_blocked_at ?? now,
265
265
  last_checked_at: now,
266
266
  last_worked_at: now,
@@ -31,7 +31,7 @@ export function buildNextQueue(ticketDefs, states, queueLimit) {
31
31
  blockedBy: blockedBy.length ? blockedBy.join(", ") : "None",
32
32
  size: ticket.size,
33
33
  risk: ticket.risk,
34
- nextAction: state?.next_action ?? defaultNextAction(ticket, displayStatus, blockedBy),
34
+ nextAction: state?.next_action ?? (displayStatus === "blocked" && !blockedBy.length ? `Resolve blocker: ${state?.blocker_notes ?? "external prerequisite requires recovery"}` : defaultNextAction(ticket, displayStatus, blockedBy)),
35
35
  requiredTests: ticket.required_tests.join("; "),
36
36
  evidence: state?.evidence ?? "N/A until implemented.",
37
37
  likelyFiles: ticket.likely_files.length ? ticket.likely_files.join(", ") : "unknown",
@@ -1,3 +1,4 @@
1
+ import { type QaPreparationDepthDecisionV1 } from "rafi-spec";
1
2
  export type TicketPriority = "P0" | "P1" | "P2" | "P3";
2
3
  export type TicketSize = "XS" | "S" | "M" | "L" | "XL";
3
4
  export type TicketRisk = "Low" | "Medium" | "High";
@@ -22,6 +23,7 @@ export interface TicketPlanRef {
22
23
  slice_ref: string;
23
24
  }
24
25
  export interface TicketDef {
26
+ qa_preparation?: QaPreparationDepthDecisionV1;
25
27
  id: string;
26
28
  order: number;
27
29
  title: string;
@@ -47,6 +49,104 @@ export declare const TICKET_JSON_SCHEMA: {
47
49
  readonly required: readonly ["id", "order", "title", "area", "priority", "size", "risk", "depends_on", "summary", "acceptance", "required_tests", "likely_files"];
48
50
  readonly additionalProperties: true;
49
51
  readonly properties: {
52
+ readonly qa_preparation: {
53
+ readonly type: "object";
54
+ readonly additionalProperties: false;
55
+ readonly required: readonly ["version", "decisionId", "level", "rationale", "riskFactors", "policyVersion", "plannerOperationId", "plannerIdentity", "timestamp", "minimumLevel", "minimumReasons"];
56
+ readonly properties: {
57
+ readonly version: {
58
+ readonly const: 1;
59
+ };
60
+ readonly decisionId: {
61
+ readonly type: "string";
62
+ readonly minLength: 1;
63
+ };
64
+ readonly level: {
65
+ readonly type: "integer";
66
+ readonly minimum: 1;
67
+ readonly maximum: 5;
68
+ };
69
+ readonly rationale: {
70
+ readonly type: "string";
71
+ readonly minLength: 1;
72
+ };
73
+ readonly policyVersion: {
74
+ readonly const: "qa-preparation-v1";
75
+ };
76
+ readonly plannerOperationId: {
77
+ readonly type: "string";
78
+ readonly minLength: 1;
79
+ };
80
+ readonly plannerIdentity: {
81
+ readonly type: "string";
82
+ readonly minLength: 1;
83
+ };
84
+ readonly timestamp: {
85
+ readonly type: "string";
86
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}T";
87
+ };
88
+ readonly minimumLevel: {
89
+ readonly type: "integer";
90
+ readonly minimum: 1;
91
+ readonly maximum: 5;
92
+ };
93
+ readonly minimumReasons: {
94
+ readonly type: "array";
95
+ readonly items: {
96
+ readonly type: "string";
97
+ readonly minLength: 1;
98
+ };
99
+ };
100
+ readonly predecessorDecisionId: {
101
+ readonly type: "string";
102
+ readonly minLength: 1;
103
+ };
104
+ readonly lowRisk: {
105
+ readonly type: "object";
106
+ readonly additionalProperties: false;
107
+ readonly required: readonly ["narrowBehavior", "understoodDependencies", "adequateVerification"];
108
+ readonly properties: {
109
+ readonly narrowBehavior: {
110
+ readonly type: "string";
111
+ readonly minLength: 1;
112
+ };
113
+ readonly understoodDependencies: {
114
+ readonly type: "string";
115
+ readonly minLength: 1;
116
+ };
117
+ readonly adequateVerification: {
118
+ readonly type: "string";
119
+ readonly minLength: 1;
120
+ };
121
+ };
122
+ };
123
+ readonly riskFactors: {
124
+ readonly type: "array";
125
+ readonly items: {
126
+ readonly type: "object";
127
+ readonly additionalProperties: false;
128
+ readonly required: readonly ["category", "rationale", "references"];
129
+ readonly properties: {
130
+ readonly category: {
131
+ readonly enum: string[];
132
+ };
133
+ readonly rationale: {
134
+ readonly type: "string";
135
+ readonly minLength: 1;
136
+ };
137
+ readonly references: {
138
+ readonly type: "array";
139
+ readonly minItems: 1;
140
+ readonly items: {
141
+ readonly type: "string";
142
+ readonly minLength: 1;
143
+ };
144
+ };
145
+ };
146
+ };
147
+ };
148
+ };
149
+ };
50
150
  readonly id: {
51
151
  readonly type: "string";
52
152
  readonly pattern: "^[A-Za-z][A-Za-z0-9-_]*$";
@@ -212,6 +312,104 @@ export declare const TICKETS_FILE_SCHEMA: {
212
312
  readonly required: readonly ["id", "order", "title", "area", "priority", "size", "risk", "depends_on", "summary", "acceptance", "required_tests", "likely_files"];
213
313
  readonly additionalProperties: true;
214
314
  readonly properties: {
315
+ readonly qa_preparation: {
316
+ readonly type: "object";
317
+ readonly additionalProperties: false;
318
+ readonly required: readonly ["version", "decisionId", "level", "rationale", "riskFactors", "policyVersion", "plannerOperationId", "plannerIdentity", "timestamp", "minimumLevel", "minimumReasons"];
319
+ readonly properties: {
320
+ readonly version: {
321
+ readonly const: 1;
322
+ };
323
+ readonly decisionId: {
324
+ readonly type: "string";
325
+ readonly minLength: 1;
326
+ };
327
+ readonly level: {
328
+ readonly type: "integer";
329
+ readonly minimum: 1;
330
+ readonly maximum: 5;
331
+ };
332
+ readonly rationale: {
333
+ readonly type: "string";
334
+ readonly minLength: 1;
335
+ };
336
+ readonly policyVersion: {
337
+ readonly const: "qa-preparation-v1";
338
+ };
339
+ readonly plannerOperationId: {
340
+ readonly type: "string";
341
+ readonly minLength: 1;
342
+ };
343
+ readonly plannerIdentity: {
344
+ readonly type: "string";
345
+ readonly minLength: 1;
346
+ };
347
+ readonly timestamp: {
348
+ readonly type: "string";
349
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}T";
350
+ };
351
+ readonly minimumLevel: {
352
+ readonly type: "integer";
353
+ readonly minimum: 1;
354
+ readonly maximum: 5;
355
+ };
356
+ readonly minimumReasons: {
357
+ readonly type: "array";
358
+ readonly items: {
359
+ readonly type: "string";
360
+ readonly minLength: 1;
361
+ };
362
+ };
363
+ readonly predecessorDecisionId: {
364
+ readonly type: "string";
365
+ readonly minLength: 1;
366
+ };
367
+ readonly lowRisk: {
368
+ readonly type: "object";
369
+ readonly additionalProperties: false;
370
+ readonly required: readonly ["narrowBehavior", "understoodDependencies", "adequateVerification"];
371
+ readonly properties: {
372
+ readonly narrowBehavior: {
373
+ readonly type: "string";
374
+ readonly minLength: 1;
375
+ };
376
+ readonly understoodDependencies: {
377
+ readonly type: "string";
378
+ readonly minLength: 1;
379
+ };
380
+ readonly adequateVerification: {
381
+ readonly type: "string";
382
+ readonly minLength: 1;
383
+ };
384
+ };
385
+ };
386
+ readonly riskFactors: {
387
+ readonly type: "array";
388
+ readonly items: {
389
+ readonly type: "object";
390
+ readonly additionalProperties: false;
391
+ readonly required: readonly ["category", "rationale", "references"];
392
+ readonly properties: {
393
+ readonly category: {
394
+ readonly enum: string[];
395
+ };
396
+ readonly rationale: {
397
+ readonly type: "string";
398
+ readonly minLength: 1;
399
+ };
400
+ readonly references: {
401
+ readonly type: "array";
402
+ readonly minItems: 1;
403
+ readonly items: {
404
+ readonly type: "string";
405
+ readonly minLength: 1;
406
+ };
407
+ };
408
+ };
409
+ };
410
+ };
411
+ };
412
+ };
215
413
  readonly id: {
216
414
  readonly type: "string";
217
415
  readonly pattern: "^[A-Za-z][A-Za-z0-9-_]*$";
@@ -1,3 +1,4 @@
1
+ import { qaPreparationDepthDecisionV1Schema } from "rafi-spec";
1
2
  export const TICKET_JSON_SCHEMA = {
2
3
  type: "object",
3
4
  required: [
@@ -6,6 +7,7 @@ export const TICKET_JSON_SCHEMA = {
6
7
  ],
7
8
  additionalProperties: true,
8
9
  properties: {
10
+ qa_preparation: qaPreparationDepthDecisionV1Schema,
9
11
  id: { type: "string", pattern: "^[A-Za-z][A-Za-z0-9-_]*$" },
10
12
  order: { type: "number", minimum: 0 },
11
13
  title: { type: "string", minLength: 1 },
@@ -1,7 +1,13 @@
1
+ import { type GraphDerivedAccess } from "./graph/derived.js";
2
+ import { QaPreparationStore } from "./qaPreparationStore.js";
3
+ import { GraphStore } from "./graph/storage.js";
4
+ import { reconcileWork, type BuildOwnershipRepairV1 } from "./buildWorkReconciliation.js";
5
+ import { verifyGuidance, verifyBuilderGuidance, finishGuidance, type InstructionRecipient } from "./buildInterventions.js";
6
+ import { type AdmitWorkInput } from "./buildWorkAdmission.js";
1
7
  import { type ReadinessProcess } from "./readinessCleanup.js";
2
8
  import { type BuildAdmission, type BuildLaunch } from "./buildAdmission.js";
3
9
  import type { BuildRecoveryDecisionReceipt, ContextSample, ContinuityCheckpoint, ContinuityDelta, ContinuityEvent, ContinuityHead, ContinuityHeadState, HandoffLineage, HandoffAcceptanceReceiptV1, HandoffManifestV1, LiveSettingsAcknowledgment, OperationLifecycle, PendingHumanDecision, ProviderSessionRefV1, RecoveryAttemptOutcome, RecoveryAttemptReceipt, ResolvedAutonomyPolicy, ResolvedAgentSettings, SessionUsageSample, StructuredInterruption, SupervisorState, WorkflowIssue } from "rafi-spec";
4
- import type { QaDeliveryTurnV3, QaDeliveryOutcome, QaDeliveryInvocationV3 } from "./qaDeliveryJournal.js";
10
+ import type { QaDeliveryTurn, QaDeliveryOutcome, QaDeliveryInvocationV3 } from "./qaDeliveryJournal.js";
5
11
  import type { BranchResumeSession } from "./branch/resume.js";
6
12
  import { type BuilderRemediationReceiptV2, type BuilderRemediationReceiptV3, type FrozenQaSourceStateV2, type HandoffAcceptanceReceiptV2, type QaPassCertificateV2, type QaReducerEventV2, type QaReducerStateV2, type QaReportDisposition, type QaReviewBasisV2, type QaTurnIntentV2, type QaTurnReceiptV2 } from "./qaProtocolV2.js";
7
13
  export declare const WORKFLOW_DB_FILE = ".rafi/recovery.sqlite3";
@@ -205,6 +211,7 @@ export declare class WorkflowDb {
205
211
  private writerLease?;
206
212
  private coordinatorTransition;
207
213
  private lineageTransition;
214
+ private workAuthority;
208
215
  constructor(projectDir: string, path?: string, readinessAccess?: {
209
216
  probeId: string;
210
217
  } | {
@@ -213,6 +220,37 @@ export declare class WorkflowDb {
213
220
  /** Connection-local SQL fences: helper/reconciler cannot gain general writer rights. */
214
221
  private restrictReadinessConnection;
215
222
  close(): void;
223
+ qaPreparationStore(): QaPreparationStore;
224
+ graphStore(): GraphStore;
225
+ admitWork(input: AdmitWorkInput): import("rafi-spec").BuildWorkAdmissionV1;
226
+ private assertInstructionOwner;
227
+ reserveGuidance(runId: string, workId: string, recipient: InstructionRecipient, operationId: string, sourceDigest: string, text: string, reserve?: boolean): {
228
+ text: string;
229
+ ids: string[];
230
+ };
231
+ reconcileInstructionDeliveries(runId: string, workId: string): void;
232
+ decisionWorkId(decision: PendingHumanDecision): string | undefined;
233
+ finishGuidance(ids: string[], recipient: InstructionRecipient, result: Parameters<typeof finishGuidance>[3]): void;
234
+ builderVerificationContext(runId: string, workId: string, sourceDigest: string): {
235
+ text: string;
236
+ ids: string[];
237
+ };
238
+ verifyBuilderGuidance(ids: string[], certificate: Parameters<typeof verifyBuilderGuidance>[2], response: string, receiptDigest: string): void;
239
+ verifyGuidance(ids: string[], certificate: Parameters<typeof verifyGuidance>[2], response: string): void;
240
+ hasQueuedBuilderGuidance(runId: string, workId: string): boolean;
241
+ instruction(id: string): import("./buildInterventions.js").BuildInstruction | undefined;
242
+ assertFinalizationControls(runId: string, workId: string): void;
243
+ consumeInstructionControls(runId: string, workId: string, kind?: "questions" | "attempts"): string | undefined;
244
+ reconcileWork(request: BuildOwnershipRepairV1, input: Parameters<typeof reconcileWork>[2]): Record<string, unknown>;
245
+ reconciliation(id: string): Record<string, unknown> | undefined;
246
+ workDefinitions(runId: string): Array<{
247
+ workId: string;
248
+ kind: string;
249
+ definition: unknown;
250
+ }>;
251
+ recordWorkAssignment(runId: string, workId: string, operationId: string, record: unknown): void;
252
+ admittedWork(runId: string, workId: string): import("rafi-spec").BuildWorkAdmissionV1 | undefined;
253
+ assertAdmittedWork(runId: string, workId: string): import("rafi-spec").BuildWorkAdmissionV1;
216
254
  /** Atomic local changes only; never await provider or filesystem work here. */
217
255
  atomic<T>(work: () => T): T;
218
256
  buildAdmission(): BuildAdmission | undefined;
@@ -330,6 +368,7 @@ export declare class WorkflowDb {
330
368
  }, now?: Date): ContinuityCheckpoint;
331
369
  continuityCheckpoints(runId: string, role?: "builder" | "qa"): ContinuityCheckpoint[];
332
370
  latestContinuityCheckpoint(runId: string, role: "builder" | "qa"): ContinuityCheckpoint | undefined;
371
+ private assertGraphContinuityAccess;
333
372
  continuityHead(runId: string, role: "builder" | "qa" | "run"): ContinuityHead | undefined;
334
373
  setContinuityHeadState(runId: string, role: "builder" | "qa", state: ContinuityHeadState, now?: Date): ContinuityHead | undefined;
335
374
  recordSession(runId: string, role: string, stream: string, session: string | ProviderSessionRefV1 | undefined, transition: string, settings: ResolvedAgentSettings, now?: Date): void;
@@ -355,8 +394,12 @@ export declare class WorkflowDb {
355
394
  choices: PendingHumanDecision["choices"];
356
395
  evidence?: PendingHumanDecision["evidence"];
357
396
  }, now?: Date): PendingHumanDecision;
397
+ cancelPendingHumanDecisions(runId: string, reason: string, now?: Date): void;
398
+ humanDecisionKey(decisionId: string): string | undefined;
358
399
  humanDecision(decisionId: string): PendingHumanDecision | undefined;
359
400
  pendingHumanDecisions(runId: string): PendingHumanDecision[];
401
+ decisionContinuationAvailable(runId: string, decisionId: string): boolean;
402
+ nextDecisionContinuationKey(runId: string, decisionId: string): string;
360
403
  answeredTicketDecisions(runId: string, scopeRevision: string): PendingHumanDecision[];
361
404
  /** Unconsumed answers to an older definition must not silently reopen a ticket. */
362
405
  staleTicketDecisions(runId: string, scopeRevision: string): PendingHumanDecision[];
@@ -384,6 +427,7 @@ export declare class WorkflowDb {
384
427
  operation(idempotencyKey: string): OperationRecord | undefined;
385
428
  operations(runId: string): OperationRecord[];
386
429
  putEvidence(kind: "handoff" | "diff" | "test" | "qa", value: string | Buffer, now?: Date): string;
430
+ registerGraphEvidence(value: string | Buffer, access: GraphDerivedAccess | GraphDerivedAccess[]): void;
387
431
  getEvidence(digest: string): Buffer | undefined;
388
432
  putQaRecoveryHead(input: Omit<QaRecoveryHeadRecord, "updatedAt">, now?: Date): QaRecoveryHeadRecord;
389
433
  qaRecoveryHead(runId: string, ticketId?: string): QaRecoveryHeadRecord | undefined;
@@ -398,8 +442,8 @@ export declare class WorkflowDb {
398
442
  qaRemediationAttempts(runId: string, ticketId: string): QaRemediationAttemptRecord[];
399
443
  qaRemediationAttempt(attemptId: string): QaRemediationAttemptRecord | undefined;
400
444
  recordQaDeliveryInvocation(record: QaDeliveryInvocationV3): void;
401
- recordQaDeliveryTurn(turn: QaDeliveryTurnV3): void;
402
- qaDeliveryTurns(operationId: string): QaDeliveryTurnV3[];
445
+ recordQaDeliveryTurn(turn: QaDeliveryTurn): void;
446
+ qaDeliveryTurns(operationId: string): QaDeliveryTurn[];
403
447
  /** Attempts, including ambiguous intents, are the common legacy/production ledger. */
404
448
  qaAutomaticRemediationCount(runId: string, ticketId: string): number;
405
449
  authorizeQaRemediation(runId: string, ticketId: string, reviewAttemptId: string, reason: string): string;
@@ -474,7 +518,8 @@ export declare class WorkflowDb {
474
518
  setQaReportDisposition(reportDigest: string, disposition: QaReportDisposition, reason: string, now?: Date): void;
475
519
  markQaReportsRecheckRequired(runId: string, ticketId: string, reason: string, now?: Date): void;
476
520
  resolveQaReportsAfterPass(runId: string, ticketId: string, now?: Date): void;
477
- commitQaWaiver(runId: string, ticketId: string, expectedRevision: number, reason: string, now?: Date): QaReducerStateV2;
521
+ authorizeContractQaWaiver(runId: string, workId: string, decisionId: string): string;
522
+ commitQaWaiver(runId: string, ticketId: string, expectedRevision: number, reason: string, now?: Date, authorizationRef?: string): QaReducerStateV2;
478
523
  builderRemediationReceipt(operationId: string): BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3 | undefined;
479
524
  recordBuilderRemediationReceipt(receipt: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3): void;
480
525
  commitBuilderRemediationSuccess(receipt: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3, expectedRevision: number, now?: Date): QaReducerStateV2;
@@ -488,12 +533,13 @@ export declare class WorkflowDb {
488
533
  receipt?: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3;
489
534
  expectedRevision: number;
490
535
  }, now?: Date): QaReducerStateV2;
536
+ private assertContractCertificate;
491
537
  issueQaPassCertificate(input: Omit<QaPassCertificateV2, "version" | "certificateId" | "unresolvedReportCount" | "issuedAt">, now?: Date): QaPassCertificateV2;
492
538
  commitQaPass(input: Omit<QaPassCertificateV2, "version" | "certificateId" | "unresolvedReportCount" | "issuedAt">, expectedRevision: number, now?: Date): QaPassCertificateV2;
493
539
  /** Accept a passing verdict, its review-attempt outcome, report resolution, certificate, and reducer edge atomically. */
494
540
  commitQaPassAttempt(attemptId: string, input: Omit<QaPassCertificateV2, "version" | "certificateId" | "unresolvedReportCount" | "issuedAt">, detail: string, expectedRevision: number, now?: Date): QaPassCertificateV2;
495
541
  consumeQaPassCertificate(runId: string, ticketId: string, certificateId: string, consumer: string, now?: Date): QaPassCertificateV2;
496
- invalidateUnconsumedQaPassCertificates(runId: string, ticketId: string, reason: string, now?: Date): void;
542
+ invalidateUnconsumedQaPassCertificates(runId: string, ticketId: string, reason: string, now?: Date, issuedBefore?: string): void;
497
543
  invalidateQaPassBeforeFinalization(input: {
498
544
  runId: string;
499
545
  ticketId: string;