ai-foreman 1.7.29 → 2.0.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 (156) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/claude.d.ts +23 -12
  3. package/dist/adapters/claude.js +231 -26
  4. package/dist/adapters/codex.d.ts +19 -7
  5. package/dist/adapters/codex.js +202 -19
  6. package/dist/adapters/recovering.d.ts +2 -0
  7. package/dist/adapters/recovering.js +5 -1
  8. package/dist/adapters/structured.d.ts +71 -0
  9. package/dist/adapters/structured.js +213 -0
  10. package/dist/adapters/types.d.ts +85 -9
  11. package/dist/agentRun.d.ts +2 -0
  12. package/dist/agentRun.js +2 -1
  13. package/dist/boundedTurnEvidence.d.ts +18 -0
  14. package/dist/boundedTurnEvidence.js +76 -0
  15. package/dist/branch/currentGuard.d.ts +2 -0
  16. package/dist/branch/currentGuard.js +2 -0
  17. package/dist/branch/git.js +3 -8
  18. package/dist/branch/planner.js +1 -4
  19. package/dist/branch/runner.d.ts +2 -1
  20. package/dist/branch/runner.js +10 -7
  21. package/dist/buildAdmission.js +4 -0
  22. package/dist/buildApproval.d.ts +15 -0
  23. package/dist/buildApproval.js +28 -9
  24. package/dist/buildAssignment.d.ts +1 -0
  25. package/dist/buildAssignment.js +82 -23
  26. package/dist/buildInterventions.d.ts +2 -2
  27. package/dist/buildInterventions.js +11 -4
  28. package/dist/buildLaunchGate.js +3 -2
  29. package/dist/buildRuns.d.ts +1 -0
  30. package/dist/buildRuns.js +6 -1
  31. package/dist/builderGuidanceFollowup.js +17 -6
  32. package/dist/cli/doctor.js +3 -1
  33. package/dist/cli/manager.js +22 -20
  34. package/dist/cli/start.js +85 -39
  35. package/dist/cli/tickets.d.ts +0 -8
  36. package/dist/cli/tickets.js +50 -60
  37. package/dist/continuity.d.ts +10 -16
  38. package/dist/continuity.js +223 -208
  39. package/dist/continuityOrigin.d.ts +13 -0
  40. package/dist/continuityOrigin.js +91 -0
  41. package/dist/dispatchNotSent.d.ts +4 -0
  42. package/dist/dispatchNotSent.js +10 -0
  43. package/dist/foreman.d.ts +15 -16
  44. package/dist/foreman.js +70 -278
  45. package/dist/gitExclude.d.ts +16 -0
  46. package/dist/gitExclude.js +46 -0
  47. package/dist/graph/boundary.d.ts +1 -1
  48. package/dist/graph/boundary.js +1 -1
  49. package/dist/graph/host.js +8 -19
  50. package/dist/graph/manager.d.ts +1 -1
  51. package/dist/graph/manager.js +1 -1
  52. package/dist/graph/preparation.d.ts +38 -0
  53. package/dist/graph/preparation.js +7 -6
  54. package/dist/graph/semantic.d.ts +1 -0
  55. package/dist/graph/semantic.js +4 -1
  56. package/dist/graph/session.js +7 -2
  57. package/dist/graph/turn.d.ts +3 -4
  58. package/dist/graph/turn.js +29 -41
  59. package/dist/handoffProtocol.js +2 -2
  60. package/dist/handoffs.d.ts +5 -3
  61. package/dist/handoffs.js +92 -88
  62. package/dist/hostGraphApplicability.d.ts +2 -1
  63. package/dist/hostGraphApplicability.js +17 -19
  64. package/dist/hostQaAugmentation.js +21 -4
  65. package/dist/hostQaComparison.d.ts +21 -2
  66. package/dist/hostQaComparison.js +43 -29
  67. package/dist/hostSourcePreservation.d.ts +38 -0
  68. package/dist/hostSourcePreservation.js +72 -0
  69. package/dist/humanDecision.d.ts +7 -0
  70. package/dist/humanDecision.js +11 -0
  71. package/dist/interviews.d.ts +7 -0
  72. package/dist/interviews.js +18 -1
  73. package/dist/legacyCommunicationDisplay.d.ts +2 -0
  74. package/dist/legacyCommunicationDisplay.js +2 -0
  75. package/dist/managerPacket.js +3 -3
  76. package/dist/markers.d.ts +4 -3
  77. package/dist/markers.js +4 -20
  78. package/dist/observability.js +3 -8
  79. package/dist/probeIpc.d.ts +14 -0
  80. package/dist/probeIpc.js +29 -0
  81. package/dist/qaBuildGate.d.ts +21 -0
  82. package/dist/qaBuildGate.js +60 -13
  83. package/dist/qaBuilderCoverage.js +22 -13
  84. package/dist/qaBuilderEvidence.d.ts +16 -1
  85. package/dist/qaBuilderEvidence.js +48 -8
  86. package/dist/qaContractCoverage.d.ts +2 -2
  87. package/dist/qaContractCoverage.js +4 -9
  88. package/dist/qaContractDelivery.js +34 -20
  89. package/dist/qaCoverageAssembly.d.ts +22 -0
  90. package/dist/qaCoverageAssembly.js +16 -0
  91. package/dist/qaDeliveryJournal.d.ts +2 -0
  92. package/dist/qaDeliveryJournal.js +19 -0
  93. package/dist/qaFailureDelivery.js +97 -85
  94. package/dist/qaHostTransformation.d.ts +26 -0
  95. package/dist/qaHostTransformation.js +100 -0
  96. package/dist/qaPreparation.d.ts +8 -20
  97. package/dist/qaPreparation.js +153 -254
  98. package/dist/qaPreparationInputs.js +19 -0
  99. package/dist/qaPreparationStore.d.ts +11 -0
  100. package/dist/qaPreparationStore.js +44 -4
  101. package/dist/qaPrerequisites.d.ts +2 -1
  102. package/dist/qaPrerequisites.js +21 -1
  103. package/dist/qaProtocolV2.d.ts +15 -0
  104. package/dist/qaRecovery.d.ts +0 -3
  105. package/dist/qaRecovery.js +21 -27
  106. package/dist/qaReview.js +241 -406
  107. package/dist/qaReviewEvidence.d.ts +7 -3
  108. package/dist/qaReviewEvidence.js +15 -4
  109. package/dist/qaRuntime.js +1 -1
  110. package/dist/qaSnapshot.js +76 -21
  111. package/dist/qaStructuredResponse.d.ts +18 -0
  112. package/dist/qaStructuredResponse.js +81 -0
  113. package/dist/qaTimeline.js +2 -2
  114. package/dist/qaVerificationContract.d.ts +1 -2
  115. package/dist/qaVerificationContract.js +24 -35
  116. package/dist/readinessGate.js +5 -4
  117. package/dist/recoveryOwners.d.ts +32 -0
  118. package/dist/recoveryOwners.js +87 -0
  119. package/dist/runtimePreflight.d.ts +11 -0
  120. package/dist/runtimePreflight.js +77 -0
  121. package/dist/runtimeReadiness.js +23 -15
  122. package/dist/sources/sourceRegistry.d.ts +0 -3
  123. package/dist/sources/sourceRegistry.js +0 -42
  124. package/dist/statusReporter.d.ts +2 -0
  125. package/dist/statusReporter.js +2 -0
  126. package/dist/structuredCutover.d.ts +31 -0
  127. package/dist/structuredCutover.js +510 -0
  128. package/dist/structuredCutoverFiles.d.ts +10 -0
  129. package/dist/structuredCutoverFiles.js +181 -0
  130. package/dist/structuredDispatchAuthority.d.ts +22 -0
  131. package/dist/structuredDispatchAuthority.js +110 -0
  132. package/dist/structuredDispatchRecord.d.ts +29 -0
  133. package/dist/structuredDispatchRecord.js +36 -0
  134. package/dist/structuredLiveBudget.d.ts +2 -0
  135. package/dist/structuredLiveBudget.js +23 -0
  136. package/dist/structuredOperation.d.ts +18 -0
  137. package/dist/structuredOperation.js +279 -0
  138. package/dist/supervisedStart.js +35 -21
  139. package/dist/ticketPlanning.d.ts +1 -376
  140. package/dist/ticketPlanning.js +2 -49
  141. package/dist/ticketPopulation.d.ts +0 -3
  142. package/dist/ticketPopulation.js +4 -21
  143. package/dist/ticketPopulationPageAuthority.d.ts +13 -0
  144. package/dist/ticketPopulationPageAuthority.js +30 -0
  145. package/dist/ticketPopulationPaging.d.ts +27 -0
  146. package/dist/ticketPopulationPaging.js +127 -0
  147. package/dist/tickets/renderMarkdown.d.ts +14 -0
  148. package/dist/tickets/renderMarkdown.js +39 -5
  149. package/dist/tickets/ticketLoader.js +10 -0
  150. package/dist/tickets/ticketSchema.d.ts +107 -0
  151. package/dist/tickets/ticketSchema.js +4 -1
  152. package/dist/workflowDb.d.ts +116 -3
  153. package/dist/workflowDb.js +852 -61
  154. package/dist/workflowReader.d.ts +1 -0
  155. package/dist/workflowReader.js +9 -0
  156. package/package.json +23 -7
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Runtime environment checks that must run before Rafi's main modules load.
3
+ * Uses only Node built-ins so it still reports clearly on an unsupported
4
+ * Node, where importing the CLI itself fails with an opaque SyntaxError.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import { fileURLToPath } from "node:url";
9
+ const REINSTALL_FIX = "reinstall Rafi under the active Node (npm install -g @rafi-ai/cli); in a Rafi workspace run `corepack pnpm install --frozen-lockfile`";
10
+ function packageJson() {
11
+ try {
12
+ return JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ /** Minimal semver check for the range forms Rafi declares: "24.x", "^13.0.3", "13.0.3". */
19
+ export function satisfiesRange(version, range) {
20
+ const parse = (value) => value.replace(/^[v^~=]/, "").split(".").map(part => Number.parseInt(part, 10));
21
+ const [major, minor = 0, patch = 0] = parse(version);
22
+ const spec = range.trim();
23
+ if (/^\d+\.x(\.x)?$/.test(spec))
24
+ return major === Number.parseInt(spec, 10);
25
+ const [wantMajor, wantMinor = 0, wantPatch = 0] = parse(spec);
26
+ if (spec.startsWith("^"))
27
+ return major === wantMajor && (minor > wantMinor || minor === wantMinor && patch >= wantPatch);
28
+ return major === wantMajor && minor === wantMinor && patch === wantPatch;
29
+ }
30
+ export function checkRuntimeEnvironment() {
31
+ const pkg = packageJson();
32
+ const checks = [];
33
+ const nodeRange = pkg.engines?.node;
34
+ const nodeSupported = !nodeRange || satisfiesRange(process.versions.node, nodeRange);
35
+ if (nodeRange) {
36
+ const ok = nodeSupported;
37
+ checks.push({ ok, label: `node ${nodeRange}`, detail: `${process.version} (${process.execPath})`,
38
+ ...(ok ? {} : { fix: `run Rafi with Node ${nodeRange}, e.g. \`nvm use 24\`, and reinstall it there` }) });
39
+ }
40
+ const sqliteRange = pkg.dependencies?.["better-sqlite3"];
41
+ const require = createRequire(import.meta.url);
42
+ let installed;
43
+ try {
44
+ installed = require("better-sqlite3/package.json").version;
45
+ }
46
+ catch { /* reported below */ }
47
+ if (sqliteRange) {
48
+ const ok = Boolean(installed && satisfiesRange(installed, sqliteRange));
49
+ checks.push({ ok, label: `better-sqlite3 ${sqliteRange}`, detail: installed ? `installed ${installed}` : "not installed",
50
+ ...(ok ? {} : { fix: `the installed native SQLite module does not match the declared version; ${REINSTALL_FIX}` }) });
51
+ }
52
+ // Only probe the native binding on a supported Node with the declared
53
+ // version: under an unsupported Node the probe can abort the process
54
+ // (uncatchable N-API fatal error, seen on Node 18), and a mismatched module
55
+ // may load and then crash later (seen with 12.10 under Node 24).
56
+ if (nodeSupported && installed && (!sqliteRange || satisfiesRange(installed, sqliteRange))) {
57
+ try {
58
+ const Database = require("better-sqlite3");
59
+ new Database(":memory:").close();
60
+ checks.push({ ok: true, label: "native SQLite loads", detail: `Node ABI ${process.versions.modules}` });
61
+ }
62
+ catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ const abi = /NODE_MODULE_VERSION (\d+)/.exec(message)?.[1];
65
+ checks.push({ ok: false, label: "native SQLite loads", detail: abi ? `built for Node ABI ${abi}, active Node ABI ${process.versions.modules}` : message.split("\n")[0],
66
+ fix: `rebuild the native module for this Node: ${REINSTALL_FIX}` });
67
+ }
68
+ }
69
+ return checks;
70
+ }
71
+ /** Text for a failed preflight, or undefined when every check passed. */
72
+ export function runtimePreflightFailure(checks = checkRuntimeEnvironment()) {
73
+ const failed = checks.filter(check => !check.ok);
74
+ if (!failed.length)
75
+ return undefined;
76
+ return ["rafi: this runtime environment cannot run Rafi:", ...failed.map(check => ` - ${check.label}: ${check.detail}${check.fix ? `\n fix: ${check.fix}` : ""}`)].join("\n");
77
+ }
@@ -1,3 +1,4 @@
1
+ import { ProbeIpcTracker } from "./probeIpc.js";
1
2
  import { fileURLToPath } from "node:url";
2
3
  import { cleanupReadiness } from "./readinessCleanup.js";
3
4
  import { windowsProbeCommand } from "./windowsProbeJob.js";
@@ -150,26 +151,31 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
150
151
  let providerSpawnError;
151
152
  let providerResult;
152
153
  let startupTimer;
154
+ const probeMessages = new ProbeIpcTracker(tag, child.pid);
153
155
  if (useHost) {
154
156
  startupTimer = setTimeout(() => { startupError = new RuntimeCleanupError("Readiness helper startup timed out"); abort(); }, 15_000);
155
- child.on("message", message => {
156
- if (!message || typeof message !== "object" || !("kind" in message))
157
+ child.on("message", raw => {
158
+ if (settled)
159
+ return;
160
+ const message = probeMessages.accept(raw);
161
+ if (message === "conflict") {
162
+ startupError = new RuntimeCleanupError("Readiness helper sent conflicting terminal results");
163
+ abort();
164
+ return;
165
+ }
166
+ if (!message)
157
167
  return;
158
168
  if (message.kind === "rafi-probe-result") {
159
- const reported = message;
160
- if ((reported.exitCode === null || Number.isInteger(reported.exitCode)) && (reported.signal === null || typeof reported.signal === "string")) {
161
- providerResult = reported;
162
- setTimeout(() => { if (!settled) {
163
- child.stdout?.destroy();
164
- child.stderr?.destroy();
165
- void finish(providerResult.exitCode, providerResult.signal);
166
- } }, 100).unref();
167
- }
169
+ providerResult = { exitCode: message.exitCode, signal: message.signal };
170
+ setTimeout(() => { if (!settled) {
171
+ child.stdout?.destroy();
172
+ child.stderr?.destroy();
173
+ void finish(providerResult.exitCode, providerResult.signal);
174
+ } }, 100).unref();
168
175
  return;
169
176
  }
170
177
  if (message.kind === "rafi-probe-spawn-error") {
171
- const reported = message;
172
- providerSpawnError = Object.assign(new Error(reported.message ?? "Provider could not start"), { code: reported.code });
178
+ providerSpawnError = Object.assign(new Error(message.message), { code: message.code ?? undefined });
173
179
  return;
174
180
  }
175
181
  if (message.kind !== "rafi-probe-registered")
@@ -186,7 +192,7 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
186
192
  }
187
193
  if (authority && ownedProcess)
188
194
  journal.authorizeReadinessHelper(authority, ownedProcess, child.pid);
189
- child.send("rafi-probe-authorized", error => { if (error) {
195
+ child.send({ version: 1, kind: "rafi-probe-authorized", id: tag, pid: child.pid }, error => { if (error) {
190
196
  startupError = error;
191
197
  abort();
192
198
  } });
@@ -204,7 +210,7 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
204
210
  const notifySupervisor = (active) => {
205
211
  if (process.send && process.connected && process.env.RAFI_BUILD_WORKER_RUN && child.pid) {
206
212
  try {
207
- process.send({ kind: "rafi-readiness-child", active, tag, pid: child.pid, processStart: active ? processStartIdentity(child.pid) : undefined }, () => { });
213
+ process.send({ version: 1, kind: "rafi-readiness-child", runId: process.env.RAFI_BUILD_WORKER_RUN, generation: Number(process.env.RAFI_BUILD_WORKER_GENERATION), workerPid: process.pid, active, tag, pid: child.pid, processStart: active ? processStartIdentity(child.pid) ?? null : null }, () => { });
208
214
  }
209
215
  catch { /* Durable ownership survives a disconnected supervisor. */ }
210
216
  }
@@ -253,6 +259,8 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
253
259
  if (settled)
254
260
  return;
255
261
  settled = true;
262
+ if (useHost && !probeMessages.terminal && !cancelled && !timedOut && !spawnError)
263
+ startupError ??= new RuntimeCleanupError("Readiness helper exited without a correlated terminal result");
256
264
  if (providerResult) {
257
265
  exitCode = providerResult.exitCode;
258
266
  signal = providerResult.signal;
@@ -1,6 +1,4 @@
1
1
  import type { ProjectSourceEntry, ProjectSourceLocator, ProjectSourceType, SourceRegistryConfig, SourceSnapshotStorage, TicketSetupSource } from "rafi-spec";
2
- export declare const SOURCE_REQUEST_START = "RAFI_SOURCE_REQUEST_START";
3
- export declare const SOURCE_REQUEST_END = "RAFI_SOURCE_REQUEST_END";
4
2
  export interface StructuredSourceRequest {
5
3
  type?: ProjectSourceType;
6
4
  description?: string;
@@ -38,7 +36,6 @@ export declare function removeSource(registry: SourceRegistryConfig, id: string)
38
36
  export declare function discardStagedSourceCaptures(projectDir: string, original: SourceRegistryConfig, staged: SourceRegistryConfig): void;
39
37
  /** Compatibility bridge for legacy ticket setup callers. Existing entries and versions are retained. */
40
38
  export declare function appendLegacyTicketSources(registry: SourceRegistryConfig, sources: TicketSetupSource[]): SourceRegistryConfig;
41
- export declare function extractSourceRequests(output: string): StructuredSourceRequest[];
42
39
  /** Deterministically recognize only an entire obvious locator; all other language stays pending for agent interpretation. */
43
40
  export declare function sourceRequestFromAnswer(answer: string, projectDir: string): StructuredSourceRequest;
44
41
  export declare function registerSourceRequests(projectDir: string, registry: SourceRegistryConfig, requests: StructuredSourceRequest[], opts?: {
@@ -8,10 +8,7 @@ import { parse, stringify } from "yaml";
8
8
  import { fetchAndSnapshotUrl, normalizePublicHttpUrl, snapshotExternalLocalFile } from "../tickets/sourceFetch.js";
9
9
  import { fetchJiraIssues, fetchLinearIssues } from "../tickets/importer.js";
10
10
  import { withActivityPhase } from "../activity.js";
11
- import { structuredSourceRequestSchema, zodIssues } from "rafi-spec";
12
11
  const execFileAsync = promisify(execFile);
13
- export const SOURCE_REQUEST_START = "RAFI_SOURCE_REQUEST_START";
14
- export const SOURCE_REQUEST_END = "RAFI_SOURCE_REQUEST_END";
15
12
  const CONFIG_FILE = "rafi-config.yaml";
16
13
  const DEFAULT_REGISTRY = { version: 1, snapshot_storage: "local", entries: [] };
17
14
  export function emptySourceRegistry(storage = "local") {
@@ -138,45 +135,6 @@ export function appendLegacyTicketSources(registry, sources) {
138
135
  migrateTicketSource(next, source, warnings);
139
136
  return next;
140
137
  }
141
- export function extractSourceRequests(output) {
142
- const requests = [];
143
- let cursor = 0;
144
- while (true) {
145
- const start = output.indexOf(SOURCE_REQUEST_START, cursor);
146
- if (start < 0)
147
- break;
148
- const end = output.indexOf(SOURCE_REQUEST_END, start + SOURCE_REQUEST_START.length);
149
- if (end < 0)
150
- throw new Error(`source request is missing ${SOURCE_REQUEST_END}`);
151
- const body = output.slice(start + SOURCE_REQUEST_START.length, end).trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
152
- let value;
153
- try {
154
- value = JSON.parse(body);
155
- }
156
- catch (error) {
157
- throw new Error(`source request is invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
158
- }
159
- const values = Array.isArray(value) ? value : [value];
160
- for (const item of values) {
161
- const parsed = structuredSourceRequestSchema.safeParse(item);
162
- if (!parsed.success)
163
- throw new Error(`source request contract validation failed:\n${zodIssues(parsed.error).map((issue) => `- ${issue}`).join("\n")}`);
164
- requests.push(parsed.data);
165
- }
166
- cursor = end + SOURCE_REQUEST_END.length;
167
- }
168
- // Compatibility with the original single-line protocol. Preserve the complete remainder.
169
- for (const match of output.matchAll(/^SOURCE_REQUEST:\s*(.+)$/gim)) {
170
- const legacy = structuredSourceRequestSchema.safeParse({ description: match[1].trim() });
171
- // Legacy lines are intentionally converted into the same typed contract;
172
- // they remain description-only pending requests after registration, but
173
- // they no longer bypass shape validation.
174
- if (!legacy.success)
175
- throw new Error(`legacy source request contract validation failed:\n${zodIssues(legacy.error).map((issue) => `- ${issue}`).join("\n")}`);
176
- requests.push(legacy.data);
177
- }
178
- return requests;
179
- }
180
138
  /** Deterministically recognize only an entire obvious locator; all other language stays pending for agent interpretation. */
181
139
  export function sourceRequestFromAnswer(answer, projectDir) {
182
140
  const value = answer.trim();
@@ -74,6 +74,8 @@ export declare class RoleStatusAdapter implements BuilderAdapter {
74
74
  private readonly adapter;
75
75
  private readonly onActive;
76
76
  private readonly onClose;
77
+ postConfirmedDispatch(reference: import("./adapters/types.js").ConfirmedDispatchReference): Promise<TurnResult | void>;
78
+ structuredDispatchRequirement(): import("./adapters/types.js").StructuredDispatchRequirement | undefined;
77
79
  readonly agent: "claude" | "codex";
78
80
  constructor(adapter: BuilderAdapter, onActive: (adapter: BuilderAdapter) => void, onClose: () => void);
79
81
  contractCapabilities(): {
@@ -164,6 +164,8 @@ export class RoleStatusAdapter {
164
164
  adapter;
165
165
  onActive;
166
166
  onClose;
167
+ postConfirmedDispatch(reference) { return this.adapter.postConfirmedDispatch?.(reference) ?? Promise.resolve(); }
168
+ structuredDispatchRequirement() { return this.adapter.structuredDispatchRequirement?.(); }
167
169
  agent;
168
170
  constructor(adapter, onActive, onClose) {
169
171
  this.adapter = adapter;
@@ -0,0 +1,31 @@
1
+ import type Database from "better-sqlite3";
2
+ export declare const STRUCTURED_CUTOVER = "003_structured_communication_v2_cutover";
3
+ export declare const STRUCTURED_AUDIT_CLOSURE = "004_structured_communication_audit_closure";
4
+ export declare const STRUCTURED_CUTOVER_MATRIX_VERSION = 3;
5
+ export declare const STRUCTURED_CUTOVER_REASON = "structured-communication-v2-cutover";
6
+ export declare const STRUCTURED_AUTHORITY_TABLES: readonly string[];
7
+ type Row = Record<string, any>;
8
+ type ForeignKey = {
9
+ id: number;
10
+ seq: number;
11
+ table: string;
12
+ from: string;
13
+ to: string;
14
+ };
15
+ export interface CutoverOwnershipEntry {
16
+ table: string;
17
+ disposition: "retain" | "purge-owned";
18
+ columns: string[];
19
+ foreignKeys: ForeignKey[];
20
+ jsonColumns: string[];
21
+ owner: "retained-artifact" | "run-id" | "foreign-key" | "session-key" | "contract-id" | "json-run-id";
22
+ evidenceReferenceExtractor: "normalized-refs-with-conservative-unknown-retention";
23
+ deletionOrder: number | null;
24
+ }
25
+ export declare function cutoverOwnershipMatrix(db: Database.Database): CutoverOwnershipEntry[];
26
+ export declare function assertStructuredCutoverComplete(db: Database.Database): void;
27
+ /** Classification is structural; JSON spelling or merely containing a tuple key is never proof. */
28
+ export declare function invalidStructuredRun(db: Database.Database, run: Row, projectDir?: string): boolean;
29
+ /** Archive then delete only selected rows. Foreign-key closure computes child ownership and deletion order. */
30
+ export declare function purgeStructuredRun(db: Database.Database, runId: string, matrix: CutoverOwnershipEntry[], fileReferences?: ReadonlySet<string>): number;
31
+ export {};