ai-foreman 1.7.8 → 1.7.9

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.
@@ -1,5 +1,5 @@
1
1
  import type { ContextSample, SessionUsageSample } from "rafi-spec";
2
- import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "./adapters/types.js";
2
+ import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, TurnResult } from "./adapters/types.js";
3
3
  export interface AgentStatusSnapshot {
4
4
  role: "builder" | "qa";
5
5
  provider: "claude" | "codex";
@@ -21,13 +21,6 @@ export interface AgentStatusSnapshot {
21
21
  contextSample: ContextSample;
22
22
  sessionUsage?: SessionUsageSample;
23
23
  sessionTransition: string;
24
- lifecycleState?: string;
25
- configuredCeilingPercent?: number;
26
- installedNativeTokenLimit?: number;
27
- installedNativePercent?: number;
28
- compactionCount?: number;
29
- compactMaximum?: number;
30
- settingsRevision?: number;
31
24
  at: string;
32
25
  }
33
26
  export interface StatusClock {
@@ -54,20 +47,6 @@ export interface AgentStatusReporterInput {
54
47
  adapter: BuilderAdapter | (() => BuilderAdapter);
55
48
  compactionCount?: number | (() => number);
56
49
  handoffGeneration?: number | (() => number);
57
- /** Shared enforcement snapshot. When supplied, status never polls occupancy independently. */
58
- contextSnapshot?: () => {
59
- role: "builder" | "qa";
60
- provider: "claude" | "codex";
61
- model: string;
62
- lifecycleState: string;
63
- contextSample: ContextSample;
64
- configuredCeilingPercent: number;
65
- installedNativeTokenLimit?: number;
66
- installedNativePercent?: number;
67
- compactionCount: number;
68
- compactMaximum: number;
69
- settingsRevision: number;
70
- } | undefined;
71
50
  }
72
51
  /**
73
52
  * A non-blocking ten-second reporter. It never invents occupancy or cost: an
@@ -80,7 +59,6 @@ export declare class AgentStatusReporter {
80
59
  private readonly intervalMs;
81
60
  private handle?;
82
61
  private lastGoodContext?;
83
- private readonly lastGoodByRole;
84
62
  constructor(input: AgentStatusReporterInput, emit: (line: string, snapshot: AgentStatusSnapshot) => void, clock?: StatusClock, intervalMs?: number);
85
63
  start(): void;
86
64
  tick(): Promise<void>;
@@ -104,10 +82,10 @@ export declare class RoleStatusAdapter implements BuilderAdapter {
104
82
  adoptSessionRef(ref: import("rafi-spec").ProviderSessionRefV1): void;
105
83
  validateSession(): Promise<import("rafi-spec").SessionAvailabilityV1>;
106
84
  compact(): Promise<CompactResult>;
107
- prepareContextManagement(policy: import("./adapters/types.js").ContextManagementPolicy): Promise<import("./adapters/types.js").PreparedContextManagement>;
108
- updateContextManagement(policy: import("./adapters/types.js").ContextManagementPolicy): Promise<import("./adapters/types.js").PreparedContextManagement>;
109
- interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<import("./adapters/types.js").InterruptResult>;
110
- installManagedTurnDispatcher(dispatcher: import("./adapters/types.js").ManagedTurnDispatcher): void;
85
+ prepareAutoCompaction(thresholdPercent?: number): Promise<void>;
86
+ drainNativeCompactions(): import("./adapters/types.js").NativeCompaction[];
87
+ restoreNativeCompactions(compactions: NativeCompaction[]): void;
88
+ contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
111
89
  contextUsage(): Promise<ContextUsage | undefined>;
112
90
  sessionUsage(): Promise<ProviderSessionUsage | undefined>;
113
91
  switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
@@ -19,7 +19,6 @@ export class AgentStatusReporter {
19
19
  intervalMs;
20
20
  handle;
21
21
  lastGoodContext;
22
- lastGoodByRole = new Map();
23
22
  constructor(input, emit, clock = systemClock, intervalMs = 10_000) {
24
23
  this.input = input;
25
24
  this.emit = emit;
@@ -35,44 +34,23 @@ export class AgentStatusReporter {
35
34
  async tick() {
36
35
  const adapter = this.adapter();
37
36
  const now = this.clock.now();
37
+ let observed;
38
+ try {
39
+ observed = await adapter.contextUsage?.();
40
+ }
41
+ catch {
42
+ observed = undefined;
43
+ }
38
44
  let contextSample;
39
- const hasCoordinator = this.input.contextSnapshot !== undefined;
40
- const coordinated = this.input.contextSnapshot?.();
41
- if (coordinated && coordinated.role === this.input.role) {
42
- contextSample = coordinated.contextSample;
43
- this.input.provider = coordinated.provider;
44
- this.input.model = coordinated.model;
45
- this.input.settingsRevision = coordinated.settingsRevision;
46
- this.input.compactionCount = coordinated.compactionCount;
47
- if (contextSample.freshness === "fresh") {
48
- this.lastGoodContext = contextSample;
49
- this.lastGoodByRole.set(this.input.role, contextSample);
50
- }
45
+ if (observed && Number.isFinite(observed.used)) {
46
+ contextSample = this.contextSample(observed, now);
47
+ this.lastGoodContext = contextSample;
51
48
  }
52
- else if (!hasCoordinator) {
53
- let observed;
54
- try {
55
- observed = await adapter.contextUsage?.();
56
- }
57
- catch {
58
- observed = undefined;
59
- }
60
- if (observed && Number.isFinite(observed.used))
61
- contextSample = this.contextSample(observed, now);
62
- else if (this.lastGoodContext)
63
- contextSample = { ...this.lastGoodContext, freshness: "stale" };
64
- else
65
- contextSample = { ...this.measuringSample(now), freshness: "unavailable" };
49
+ else if (this.lastGoodContext) {
50
+ contextSample = { ...this.lastGoodContext, freshness: "stale" };
66
51
  }
67
52
  else {
68
- const prior = this.lastGoodByRole.get(this.input.role);
69
- contextSample = prior
70
- ? { ...prior, freshness: "stale" }
71
- : { ...this.measuringSample(now), freshness: "unavailable" };
72
- }
73
- if (contextSample.freshness === "fresh") {
74
- this.lastGoodContext = contextSample;
75
- this.lastGoodByRole.set(this.input.role, contextSample);
53
+ contextSample = { ...this.measuringSample(now), freshness: "unavailable" };
76
54
  }
77
55
  let sessionUsage;
78
56
  if (this.input.displaySessionCost) {
@@ -112,19 +90,16 @@ export class AgentStatusReporter {
112
90
  const priorRole = this.input.role;
113
91
  const priorAdapter = this.adapter();
114
92
  const priorSessionId = priorAdapter.sessionId();
115
- if (this.lastGoodContext)
116
- this.lastGoodByRole.set(priorRole, this.lastGoodContext);
117
93
  Object.assign(this.input, patch);
118
94
  if (priorRole !== this.input.role
119
95
  || priorAdapter !== this.adapter()
120
96
  || priorSessionId !== this.adapter().sessionId()
121
97
  || patch.sessionTransition !== undefined)
122
- this.lastGoodContext = this.lastGoodByRole.get(this.input.role);
98
+ this.lastGoodContext = undefined;
123
99
  this.emitSnapshot(this.measuringSample(), undefined);
124
100
  }
125
101
  emitSnapshot(contextSample, sessionUsage, now = this.clock.now()) {
126
- const coordinated = this.input.contextSnapshot?.();
127
- const { adapter: _adapter, displaySessionCost: _display, compactionCount: _compactions, handoffGeneration: _handoffs, contextSnapshot: _contextSnapshot, ...base } = this.input;
102
+ const { adapter: _adapter, displaySessionCost: _display, compactionCount: _compactions, handoffGeneration: _handoffs, ...base } = this.input;
128
103
  const context = contextSample.used === undefined ? undefined : {
129
104
  used: contextSample.used,
130
105
  maximum: contextSample.maximum,
@@ -135,29 +110,13 @@ export class AgentStatusReporter {
135
110
  ...(context ? { context } : {}),
136
111
  contextSample,
137
112
  ...(sessionUsage ? { sessionUsage } : {}),
138
- ...(coordinated && coordinated.role === this.input.role ? {
139
- lifecycleState: coordinated.lifecycleState,
140
- configuredCeilingPercent: coordinated.configuredCeilingPercent,
141
- installedNativeTokenLimit: coordinated.installedNativeTokenLimit,
142
- installedNativePercent: coordinated.installedNativePercent,
143
- compactionCount: coordinated.compactionCount,
144
- compactMaximum: coordinated.compactMaximum,
145
- settingsRevision: coordinated.settingsRevision,
146
- } : {
147
- compactionCount: contextSample.compactionCount,
148
- settingsRevision: contextSample.settingsRevision,
149
- }),
150
113
  at: now.toISOString(),
151
114
  };
152
115
  const line = [
153
116
  `${snapshot.role} ${snapshot.provider}/${snapshot.model}`,
154
117
  `activity=${snapshot.phase}`,
155
118
  formatContext(contextSample),
156
- ...(snapshot.configuredCeilingPercent === undefined ? [] : [`ceiling=${snapshot.configuredCeilingPercent}%`]),
157
- ...(snapshot.installedNativePercent === undefined ? [] : [`native=${snapshot.installedNativePercent.toFixed(1)}%${snapshot.installedNativeTokenLimit === undefined ? "" : `/${snapshot.installedNativeTokenLimit}`}`]),
158
- ...(snapshot.lifecycleState ? [`state=${formatLifecycle(snapshot.lifecycleState)}`] : []),
159
- `compactions=${snapshot.compactionCount ?? contextSample.compactionCount}${snapshot.compactMaximum === undefined ? "" : `/${snapshot.compactMaximum}`}`,
160
- `revision=${snapshot.settingsRevision ?? contextSample.settingsRevision}`,
119
+ `compactions=${contextSample.compactionCount}`,
161
120
  `handoff=${contextSample.handoffGeneration}`,
162
121
  `session=${snapshot.sessionTransition}`,
163
122
  ...(this.input.displaySessionCost ? [formatSessionUsage(sessionUsage)] : []),
@@ -218,12 +177,10 @@ export class RoleStatusAdapter {
218
177
  adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
219
178
  validateSession() { return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped" }); }
220
179
  compact() { this.onActive(this.adapter); return this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" }); }
221
- prepareContextManagement(policy) { this.onActive(this.adapter); if (!this.adapter.prepareContextManagement)
222
- return Promise.reject(new Error("native context management unavailable")); return this.adapter.prepareContextManagement(policy); }
223
- updateContextManagement(policy) { if (!this.adapter.updateContextManagement)
224
- return Promise.reject(new Error("native context reconfiguration unavailable")); return this.adapter.updateContextManagement(policy); }
225
- interruptTurnAtCompactionBoundary(providerEventId) { return this.adapter.interruptTurnAtCompactionBoundary?.(providerEventId) ?? Promise.resolve({ ok: false, error: "compaction-boundary interruption unavailable", providerEventId }); }
226
- installManagedTurnDispatcher(dispatcher) { this.adapter.installManagedTurnDispatcher?.(dispatcher); }
180
+ prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
181
+ drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
182
+ restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
183
+ contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
227
184
  contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
228
185
  sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
229
186
  switchSettings(settings) { return this.adapter.switchSettings?.(settings) ?? Promise.resolve({ ok: false, error: "settings switch unavailable" }); }
@@ -262,19 +219,6 @@ function formatContext(sample) {
262
219
  : `${sample.percentage.toFixed(1)}% (${sample.used ?? "unknown"}/${sample.maximum ?? "unknown"})`;
263
220
  return `context ${occupancy}${sample.freshness === "stale" ? " stale" : ""}`;
264
221
  }
265
- function formatLifecycle(state) {
266
- if (state === "initializing")
267
- return "measuring";
268
- if (state === "threshold_pending")
269
- return "pending";
270
- if (state === "native_compacting" || state === "host_compacting")
271
- return "compacting";
272
- if (state === "compacted_unverified")
273
- return "verifying";
274
- if (state === "handoff_required" || state === "resuming")
275
- return "handing off";
276
- return state;
277
- }
278
222
  function formatSessionUsage(sample) {
279
223
  if (!sample || sample.source === "unavailable")
280
224
  return "session usage unavailable";
@@ -55,45 +55,13 @@ export interface CompactionAttemptRecord {
55
55
  sessionRef?: ProviderSessionRefV1;
56
56
  sessionKey?: string;
57
57
  crossingKey: string;
58
- status: "started" | "succeeded" | "failed" | "unverified";
59
- origin: "provider-auto" | "rafi-manual" | "recovery" | "boundary";
60
- providerEventId?: string;
61
- thresholdGenerationId?: string;
58
+ status: "started" | "succeeded" | "failed";
62
59
  beforeSample?: ContextSample;
63
60
  afterSample?: ContextSample;
64
61
  error?: string;
65
62
  createdAt: string;
66
63
  updatedAt: string;
67
64
  }
68
- export type ThresholdLifecycleState = "initializing" | "armed" | "threshold_pending" | "native_compacting" | "host_compacting" | "compacted_unverified" | "retrying" | "handoff_required" | "resuming";
69
- export interface ThresholdGenerationRecord {
70
- generationId: string;
71
- runId: string;
72
- role: "builder" | "qa";
73
- providerSessionId?: string;
74
- sessionRef?: ProviderSessionRefV1;
75
- sessionKey?: string;
76
- generation: number;
77
- state: ThresholdLifecycleState;
78
- configuredCeilingPercent: number;
79
- installedNativeTokenLimit?: number;
80
- installedNativePercent?: number;
81
- settingsRevision: number;
82
- model: string;
83
- latestSample?: ContextSample;
84
- frozenActionDigest?: string;
85
- providerEventId?: string;
86
- createdAt: string;
87
- updatedAt: string;
88
- }
89
- export interface ActiveRoleAdapterRecord {
90
- runId: string;
91
- role: "builder" | "qa";
92
- providerSessionId?: string;
93
- sessionKey?: string;
94
- settingsRevision: number;
95
- observedAt: string;
96
- }
97
65
  export interface RoleMutationLease {
98
66
  runId: string;
99
67
  role: "builder" | "qa";
@@ -140,8 +108,7 @@ export declare class WorkflowDb {
140
108
  at: string;
141
109
  }>;
142
110
  recordSettings(runId: string, role: string, boundary: number, settings: ResolvedAgentSettings, now?: Date): void;
143
- recordProjectSettingsRevision(revision: number, defaults: unknown, targetsOrNow?: Readonly<Record<string, readonly string[]>> | Date, now?: Date): void;
144
- projectSettingsRevisionTargets(revision: number): Record<string, string[]>;
111
+ recordProjectSettingsRevision(revision: number, defaults: unknown, now?: Date): void;
145
112
  recordTelemetry(runId: string, snapshot: unknown, now?: Date): void;
146
113
  recordContextSample(sample: ContextSample): void;
147
114
  contextSamples(runId: string, role?: "builder" | "qa"): ContextSample[];
@@ -219,28 +186,24 @@ export declare class WorkflowDb {
219
186
  sessionKey?: string;
220
187
  crossingKey: string;
221
188
  beforeSample?: ContextSample;
222
- origin?: CompactionAttemptRecord["origin"];
223
- providerEventId?: string;
224
- thresholdGenerationId?: string;
225
189
  }, now?: Date): CompactionAttemptRecord;
226
190
  finishCompactionAttempt(idempotencyKey: string, input: {
227
191
  ok: boolean;
228
192
  afterSample?: ContextSample;
229
193
  error?: string;
230
- status?: "succeeded" | "failed" | "unverified";
231
194
  }, now?: Date): CompactionAttemptRecord;
195
+ /** Attach an eventually available measurement without changing confirmed accounting. */
196
+ recordCompactionAfterSample(idempotencyKey: string, afterSample: ContextSample, now?: Date): CompactionAttemptRecord;
232
197
  compactionAttempt(idempotencyKey: string): CompactionAttemptRecord | undefined;
233
- linkCompactionProviderEvent(idempotencyKey: string, providerEventId: string): CompactionAttemptRecord;
234
- compactionAttempts(runId: string, role: "builder" | "qa", providerSession?: string | ProviderSessionRefV1): CompactionAttemptRecord[];
235
198
  successfulCompactionCount(runId: string, role: "builder" | "qa", providerSession?: string | ProviderSessionRefV1): number;
236
- upsertThresholdGeneration(input: Omit<ThresholdGenerationRecord, "createdAt" | "updatedAt">, now?: Date): ThresholdGenerationRecord;
237
- thresholdGeneration(generationId: string): ThresholdGenerationRecord | undefined;
238
- thresholdGenerations(runId: string, role?: "builder" | "qa", sessionKey?: string): ThresholdGenerationRecord[];
239
- markRoleAdapterActive(input: ActiveRoleAdapterRecord): void;
240
- clearRoleAdapterActive(runId: string, role: "builder" | "qa", sessionKey?: string): void;
241
- activeRoleAdapters(runId?: string, observedAfter?: string): ActiveRoleAdapterRecord[];
242
199
  recordRecoveryDecision(receipt: BuildRecoveryDecisionReceipt): void;
243
200
  recoveryDecisions(runId: string): BuildRecoveryDecisionReceipt[];
201
+ /**
202
+ * A process may die after dispatching a provider turn but before its durable
203
+ * continuity checkpoint. The provider may have compacted during that gap, so
204
+ * recovery must not treat that role session's compaction history as complete.
205
+ */
206
+ hasUncheckpointedRoleTurn(runId: string, role: "builder" | "qa"): boolean;
244
207
  beginPublication(runId: string, intent: unknown, previousDigests: unknown, now?: Date): PublicationTransaction;
245
208
  updatePublication(transactionId: string, status: PublicationTransaction["status"], now?: Date): PublicationTransaction;
246
209
  publication(transactionId: string): PublicationTransaction | undefined;
@@ -109,24 +109,8 @@ export class WorkflowDb {
109
109
  this.insertEvent(runId, "settings_boundary", `role:${role}:${boundary}`, { revision: settings.settings_revision }, now.toISOString());
110
110
  })();
111
111
  }
112
- recordProjectSettingsRevision(revision, defaults, targetsOrNow = {}, now = new Date()) {
113
- const targets = targetsOrNow instanceof Date ? {} : targetsOrNow;
114
- const recordedAt = targetsOrNow instanceof Date ? targetsOrNow : now;
115
- this.db.transaction(() => {
116
- this.db.prepare(`INSERT INTO project_settings_revisions(revision,defaults_json,created_at) VALUES(?,?,?)
117
- ON CONFLICT(revision) DO UPDATE SET defaults_json=excluded.defaults_json,created_at=excluded.created_at`).run(revision, json(defaults), recordedAt.toISOString());
118
- const entries = Object.entries(targets);
119
- if (entries.length) {
120
- this.db.prepare("DELETE FROM project_settings_revision_targets WHERE revision=?").run(revision);
121
- const insert = this.db.prepare("INSERT INTO project_settings_revision_targets(revision,role,fields_json) VALUES(?,?,?)");
122
- for (const [role, fields] of entries)
123
- insert.run(revision, role, json([...new Set(fields)].sort()));
124
- }
125
- })();
126
- }
127
- projectSettingsRevisionTargets(revision) {
128
- const rows = this.db.prepare("SELECT role,fields_json FROM project_settings_revision_targets WHERE revision=? ORDER BY role").all(revision);
129
- return Object.fromEntries(rows.map((row) => [row.role, parseJson(row.fields_json)]));
112
+ recordProjectSettingsRevision(revision, defaults, now = new Date()) {
113
+ this.db.prepare("INSERT OR REPLACE INTO project_settings_revisions(revision,defaults_json,created_at) VALUES(?,?,?)").run(revision, json(defaults), now.toISOString());
130
114
  }
131
115
  recordTelemetry(runId, snapshot, now = new Date()) {
132
116
  this.ensureRun(runId, "build", now);
@@ -169,22 +153,12 @@ export class WorkflowDb {
169
153
  this.db.prepare(`INSERT INTO live_settings_acknowledgments(run_id,role,provider_session_id,session_key,session_ref_json,revision,acknowledged_at)
170
154
  VALUES(?,?,?,?,?,?,?) ON CONFLICT(run_id,role,revision) DO UPDATE SET provider_session_id=excluded.provider_session_id,session_key=excluded.session_key,session_ref_json=excluded.session_ref_json,acknowledged_at=excluded.acknowledged_at`)
171
155
  .run(ack.runId, ack.role, session.id, session.key, session.refJson, ack.revision, ack.acknowledgedAt);
172
- if (session.key) {
173
- this.db.prepare(`INSERT INTO live_settings_session_acknowledgments(run_id,role,provider_session_id,session_key,session_ref_json,revision,acknowledged_at)
174
- VALUES(?,?,?,?,?,?,?) ON CONFLICT(run_id,role,session_key,revision) DO UPDATE SET provider_session_id=excluded.provider_session_id,session_ref_json=excluded.session_ref_json,acknowledged_at=excluded.acknowledged_at`)
175
- .run(ack.runId, ack.role, session.id, session.key, session.refJson, ack.revision, ack.acknowledgedAt);
176
- }
177
156
  this.insertEvent(ack.runId, "live_settings_acknowledged", `settings:${ack.revision}`, { role: ack.role, providerSessionId: session.id, sessionKey: session.key }, ack.acknowledgedAt);
178
157
  })();
179
158
  }
180
159
  settingsAcknowledgments(revision) {
181
- const scoped = this.db.prepare("SELECT * FROM live_settings_session_acknowledgments WHERE revision=? ORDER BY acknowledged_at").all(revision);
182
- const covered = new Set(scoped.map((row) => `${row.run_id}:${row.role}`));
183
- const legacy = this.db.prepare("SELECT * FROM live_settings_acknowledgments WHERE revision=? ORDER BY acknowledged_at").all(revision)
184
- .filter((row) => !covered.has(`${row.run_id}:${row.role}`));
185
- return [...scoped, ...legacy]
186
- .sort((left, right) => left.acknowledged_at.localeCompare(right.acknowledged_at))
187
- .map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), revision: row.revision, acknowledgedAt: row.acknowledged_at }));
160
+ const rows = this.db.prepare("SELECT * FROM live_settings_acknowledgments WHERE revision=? ORDER BY acknowledged_at").all(revision);
161
+ return rows.map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), revision: row.revision, acknowledgedAt: row.acknowledged_at }));
188
162
  }
189
163
  appendContinuityEvent(input, now = new Date()) {
190
164
  this.ensureRun(input.runId, "build", now);
@@ -466,17 +440,9 @@ export class WorkflowDb {
466
440
  this.ensureRun(input.runId);
467
441
  const at = now.toISOString();
468
442
  const session = sessionParts(input.sessionRef, input.providerSessionId, input.sessionKey);
469
- this.db.prepare(`INSERT OR IGNORE INTO compaction_attempts(idempotency_key,run_id,role,provider_session_id,session_key,session_ref_json,crossing_key,status,before_sample_json,origin,provider_event_id,threshold_generation_id,created_at,updated_at)
470
- VALUES(?,?,?,?,?,?,?,'started',?,?,?,?,?,?)`).run(input.idempotencyKey, input.runId, input.role, session.id, session.key, session.refJson, input.crossingKey, input.beforeSample ? json(input.beforeSample) : null, input.origin ?? "rafi-manual", input.providerEventId ?? null, input.thresholdGenerationId ?? null, at, at);
471
- const direct = this.compactionAttempt(input.idempotencyKey);
472
- if (direct)
473
- return direct;
474
- if (session.key && input.providerEventId) {
475
- const duplicate = this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND provider_event_id=?").get(input.runId, input.role, session.key, input.providerEventId);
476
- if (duplicate)
477
- return compactionAttemptFromRow(duplicate);
478
- }
479
- throw new Error(`failed to persist compaction attempt ${input.idempotencyKey}`);
443
+ this.db.prepare(`INSERT OR IGNORE INTO compaction_attempts(idempotency_key,run_id,role,provider_session_id,session_key,session_ref_json,crossing_key,status,before_sample_json,created_at,updated_at)
444
+ VALUES(?,?,?,?,?,?,?,'started',?,?,?)`).run(input.idempotencyKey, input.runId, input.role, session.id, session.key, session.refJson, input.crossingKey, input.beforeSample ? json(input.beforeSample) : null, at, at);
445
+ return this.compactionAttempt(input.idempotencyKey);
480
446
  }
481
447
  finishCompactionAttempt(idempotencyKey, input, now = new Date()) {
482
448
  const prior = this.compactionAttempt(idempotencyKey);
@@ -485,100 +451,34 @@ export class WorkflowDb {
485
451
  if (prior.status !== "started")
486
452
  return prior;
487
453
  this.db.prepare("UPDATE compaction_attempts SET status=?,after_sample_json=?,error=?,updated_at=? WHERE idempotency_key=?")
488
- .run(input.status ?? (input.ok ? "succeeded" : "failed"), input.afterSample ? json(input.afterSample) : null, input.error ? sanitizeText(input.error).slice(0, 2000) : null, now.toISOString(), idempotencyKey);
489
- const status = input.status ?? (input.ok ? "succeeded" : "failed");
490
- this.insertEvent(prior.runId, `compaction_${status}`, `context:${prior.role}`, { idempotencyKey, providerSessionId: prior.providerSessionId, sessionKey: prior.sessionKey, crossingKey: prior.crossingKey, origin: prior.origin, providerEventId: prior.providerEventId }, now.toISOString());
454
+ .run(input.ok ? "succeeded" : "failed", input.afterSample ? json(input.afterSample) : null, input.error ? sanitizeText(input.error).slice(0, 2000) : null, now.toISOString(), idempotencyKey);
455
+ this.insertEvent(prior.runId, input.ok ? "compaction_succeeded" : "compaction_failed", `context:${prior.role}`, { idempotencyKey, providerSessionId: prior.providerSessionId, sessionKey: prior.sessionKey, crossingKey: prior.crossingKey }, now.toISOString());
491
456
  return this.compactionAttempt(idempotencyKey);
492
457
  }
493
- compactionAttempt(idempotencyKey) {
494
- const row = this.db.prepare("SELECT * FROM compaction_attempts WHERE idempotency_key=?").get(idempotencyKey);
495
- return row ? compactionAttemptFromRow(row) : undefined;
496
- }
497
- linkCompactionProviderEvent(idempotencyKey, providerEventId) {
458
+ /** Attach an eventually available measurement without changing confirmed accounting. */
459
+ recordCompactionAfterSample(idempotencyKey, afterSample, now = new Date()) {
498
460
  const prior = this.compactionAttempt(idempotencyKey);
499
461
  if (!prior)
500
462
  throw new Error(`compaction attempt not found: ${idempotencyKey}`);
501
- if (prior.providerEventId === providerEventId)
463
+ if (prior.status !== "succeeded")
502
464
  return prior;
503
- this.db.prepare("UPDATE compaction_attempts SET provider_event_id=?,updated_at=? WHERE idempotency_key=?")
504
- .run(providerEventId, new Date().toISOString(), idempotencyKey);
465
+ this.db.prepare("UPDATE compaction_attempts SET after_sample_json=?,updated_at=? WHERE idempotency_key=?")
466
+ .run(json(afterSample), now.toISOString(), idempotencyKey);
505
467
  return this.compactionAttempt(idempotencyKey);
506
468
  }
507
- compactionAttempts(runId, role, providerSession) {
508
- if (!providerSession)
509
- return [];
510
- const session = sessionParts(typeof providerSession === "object" ? providerSession : undefined, typeof providerSession === "string" ? providerSession : undefined);
511
- const rows = session.key
512
- ? this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? ORDER BY created_at,idempotency_key").all(runId, role, session.key)
513
- : this.db.prepare("SELECT * FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL ORDER BY created_at,idempotency_key").all(runId, role, session.id);
514
- return rows.map(compactionAttemptFromRow);
469
+ compactionAttempt(idempotencyKey) {
470
+ const row = this.db.prepare("SELECT * FROM compaction_attempts WHERE idempotency_key=?").get(idempotencyKey);
471
+ return row ? compactionAttemptFromRow(row) : undefined;
515
472
  }
516
473
  successfulCompactionCount(runId, role, providerSession) {
517
474
  if (!providerSession)
518
475
  return 0;
519
476
  const session = sessionParts(typeof providerSession === "object" ? providerSession : undefined, typeof providerSession === "string" ? providerSession : undefined);
520
477
  const row = session.key
521
- ? this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND status IN ('succeeded','unverified')`).get(runId, role, session.key)
522
- : this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL AND status IN ('succeeded','unverified')`).get(runId, role, session.id);
478
+ ? this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND session_key=? AND status='succeeded'`).get(runId, role, session.key)
479
+ : this.db.prepare(`SELECT COUNT(*) AS count FROM compaction_attempts WHERE run_id=? AND role=? AND provider_session_id=? AND session_key IS NULL AND status='succeeded'`).get(runId, role, session.id);
523
480
  return row.count;
524
481
  }
525
- upsertThresholdGeneration(input, now = new Date()) {
526
- this.ensureRun(input.runId);
527
- const at = now.toISOString();
528
- const session = sessionParts(input.sessionRef, input.providerSessionId, input.sessionKey);
529
- this.db.prepare(`INSERT INTO threshold_generations(
530
- generation_id,run_id,role,provider_session_id,session_key,session_ref_json,generation,state,configured_ceiling_percent,
531
- installed_native_token_limit,installed_native_percent,settings_revision,model,latest_sample_json,frozen_action_digest,provider_event_id,created_at,updated_at
532
- ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
533
- ON CONFLICT(generation_id) DO UPDATE SET
534
- provider_session_id=excluded.provider_session_id,session_key=excluded.session_key,session_ref_json=excluded.session_ref_json,
535
- state=excluded.state,configured_ceiling_percent=excluded.configured_ceiling_percent,
536
- installed_native_token_limit=excluded.installed_native_token_limit,installed_native_percent=excluded.installed_native_percent,
537
- settings_revision=excluded.settings_revision,model=excluded.model,latest_sample_json=excluded.latest_sample_json,
538
- frozen_action_digest=COALESCE(excluded.frozen_action_digest,threshold_generations.frozen_action_digest),
539
- provider_event_id=COALESCE(excluded.provider_event_id,threshold_generations.provider_event_id),updated_at=excluded.updated_at`).run(input.generationId, input.runId, input.role, session.id, session.key, session.refJson, input.generation, input.state, input.configuredCeilingPercent, input.installedNativeTokenLimit ?? null, input.installedNativePercent ?? null, input.settingsRevision, input.model, input.latestSample ? json(input.latestSample) : null, input.frozenActionDigest ?? null, input.providerEventId ?? null, at, at);
540
- return this.thresholdGeneration(input.generationId);
541
- }
542
- thresholdGeneration(generationId) {
543
- const row = this.db.prepare("SELECT * FROM threshold_generations WHERE generation_id=?").get(generationId);
544
- return row ? thresholdGenerationFromRow(row) : undefined;
545
- }
546
- thresholdGenerations(runId, role, sessionKey) {
547
- const rows = role && sessionKey
548
- ? this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? AND role=? AND session_key=? ORDER BY generation").all(runId, role, sessionKey)
549
- : role
550
- ? this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? AND role=? ORDER BY created_at,generation").all(runId, role)
551
- : this.db.prepare("SELECT * FROM threshold_generations WHERE run_id=? ORDER BY created_at,generation").all(runId);
552
- return rows.map(thresholdGenerationFromRow);
553
- }
554
- markRoleAdapterActive(input) {
555
- this.ensureRun(input.runId);
556
- const sessionKey = input.sessionKey ?? `${input.role}:unscoped:${input.providerSessionId ?? "pending"}`;
557
- this.db.prepare(`INSERT INTO active_role_adapter_sessions(run_id,role,provider_session_id,session_key,settings_revision,observed_at)
558
- VALUES(?,?,?,?,?,?) ON CONFLICT(run_id,role,session_key) DO UPDATE SET provider_session_id=excluded.provider_session_id,settings_revision=excluded.settings_revision,observed_at=excluded.observed_at`)
559
- .run(input.runId, input.role, input.providerSessionId ?? null, sessionKey, input.settingsRevision, input.observedAt);
560
- }
561
- clearRoleAdapterActive(runId, role, sessionKey) {
562
- if (sessionKey)
563
- this.db.prepare("DELETE FROM active_role_adapter_sessions WHERE run_id=? AND role=? AND session_key=?").run(runId, role, sessionKey);
564
- else
565
- this.db.prepare("DELETE FROM active_role_adapter_sessions WHERE run_id=? AND role=?").run(runId, role);
566
- }
567
- activeRoleAdapters(runId, observedAfter) {
568
- const clauses = [];
569
- const values = [];
570
- if (runId) {
571
- clauses.push("run_id=?");
572
- values.push(runId);
573
- }
574
- if (observedAfter) {
575
- clauses.push("observed_at>=?");
576
- values.push(observedAfter);
577
- }
578
- const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
579
- const rows = this.db.prepare(`SELECT * FROM active_role_adapter_sessions${where} ORDER BY run_id,role,session_key`).all(...values);
580
- return rows.map((row) => ({ runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), settingsRevision: row.settings_revision, observedAt: row.observed_at }));
581
- }
582
482
  recordRecoveryDecision(receipt) {
583
483
  this.ensureRun(receipt.runId);
584
484
  const digest = digestJson(receipt);
@@ -592,6 +492,25 @@ export class WorkflowDb {
592
492
  recoveryDecisions(runId) {
593
493
  return this.db.prepare("SELECT receipt_json FROM recovery_decisions WHERE run_id=? ORDER BY decision_id").all(runId).map((row) => parseJson(row.receipt_json));
594
494
  }
495
+ /**
496
+ * A process may die after dispatching a provider turn but before its durable
497
+ * continuity checkpoint. The provider may have compacted during that gap, so
498
+ * recovery must not treat that role session's compaction history as complete.
499
+ */
500
+ hasUncheckpointedRoleTurn(runId, role) {
501
+ const events = this.continuityEvents(runId);
502
+ let startSequence = 0;
503
+ for (const event of events) {
504
+ if (event.kind === "turn_started" && event.role === "host" && event.payload.role === role) {
505
+ startSequence = event.sequence;
506
+ }
507
+ else if (startSequence && event.sequence > startSequence
508
+ && (event.kind === "turn_completed" || event.kind === "turn_completed_after_repair" || event.kind === "fresh_successor_accepted")) {
509
+ startSequence = 0;
510
+ }
511
+ }
512
+ return startSequence > 0;
513
+ }
595
514
  beginPublication(runId, intent, previousDigests, now = new Date()) {
596
515
  const transactionId = randomUUID();
597
516
  const at = now.toISOString();
@@ -706,7 +625,6 @@ export class WorkflowDb {
706
625
  CREATE TABLE IF NOT EXISTS workflow_events(sequence INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),event_type TEXT NOT NULL,checkpoint TEXT NOT NULL,payload_json TEXT NOT NULL,created_at TEXT NOT NULL);
707
626
  CREATE TABLE IF NOT EXISTS role_settings(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,boundary INTEGER NOT NULL,revision INTEGER NOT NULL,settings_json TEXT NOT NULL,created_at TEXT NOT NULL,PRIMARY KEY(run_id,role,boundary));
708
627
  CREATE TABLE IF NOT EXISTS project_settings_revisions(revision INTEGER PRIMARY KEY,defaults_json TEXT NOT NULL,created_at TEXT NOT NULL);
709
- CREATE TABLE IF NOT EXISTS project_settings_revision_targets(revision INTEGER NOT NULL REFERENCES project_settings_revisions(revision),role TEXT NOT NULL,fields_json TEXT NOT NULL,PRIMARY KEY(revision,role));
710
628
  CREATE TABLE IF NOT EXISTS workflow_telemetry(id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),snapshot_json TEXT NOT NULL,created_at TEXT NOT NULL);
711
629
  CREATE TABLE IF NOT EXISTS provider_sessions(id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,stream TEXT NOT NULL,provider TEXT NOT NULL,model TEXT NOT NULL,session_id TEXT,session_key TEXT,session_ref_json TEXT,transition TEXT NOT NULL,settings_revision INTEGER NOT NULL,created_at TEXT NOT NULL);
712
630
  CREATE TABLE IF NOT EXISTS provider_session_bindings(session_key TEXT PRIMARY KEY,provider_session_id TEXT NOT NULL,role TEXT NOT NULL,session_ref_json TEXT NOT NULL,observed_at TEXT NOT NULL);
@@ -719,15 +637,12 @@ export class WorkflowDb {
719
637
  CREATE TABLE IF NOT EXISTS context_samples(sample_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,sample_json TEXT NOT NULL,observed_at TEXT NOT NULL);
720
638
  CREATE TABLE IF NOT EXISTS session_usage_samples(sample_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,sample_json TEXT NOT NULL,observed_at TEXT NOT NULL);
721
639
  CREATE TABLE IF NOT EXISTS live_settings_acknowledgments(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,revision INTEGER NOT NULL,acknowledged_at TEXT NOT NULL,PRIMARY KEY(run_id,role,revision));
722
- CREATE TABLE IF NOT EXISTS live_settings_session_acknowledgments(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT NOT NULL,session_ref_json TEXT,revision INTEGER NOT NULL,acknowledged_at TEXT NOT NULL,PRIMARY KEY(run_id,role,session_key,revision));
723
640
  CREATE TABLE IF NOT EXISTS continuity_events(sequence INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,kind TEXT NOT NULL,payload_json TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,authoritative_state_revision INTEGER NOT NULL,session_key TEXT,session_ref_json TEXT,created_at TEXT NOT NULL);
724
641
  CREATE TABLE IF NOT EXISTS continuity_checkpoints(checkpoint_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,event_sequence INTEGER NOT NULL,state TEXT NOT NULL,delta_json TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,predecessor_digest TEXT,authoritative_state_revision INTEGER NOT NULL,session_key TEXT,session_ref_json TEXT,created_at TEXT NOT NULL);
725
642
  CREATE TABLE IF NOT EXISTS continuity_heads(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,state TEXT NOT NULL,event_sequence INTEGER NOT NULL,digest TEXT NOT NULL,authoritative_state_revision INTEGER NOT NULL,updated_at TEXT NOT NULL,PRIMARY KEY(run_id,role));
726
643
  CREATE TABLE IF NOT EXISTS handoffs(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),generation INTEGER NOT NULL,role TEXT NOT NULL,manifest_digest TEXT NOT NULL,markdown_digest TEXT NOT NULL,predecessor_session_id TEXT,predecessor_session_key TEXT,predecessor_session_ref_json TEXT,successor_session_id TEXT,successor_session_key TEXT,successor_session_ref_json TEXT,state TEXT NOT NULL,failure TEXT,created_at TEXT NOT NULL,accepted_at TEXT,PRIMARY KEY(run_id,generation));
727
644
  CREATE TABLE IF NOT EXISTS role_mutation_leases(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,generation INTEGER NOT NULL,provider_session_id TEXT NOT NULL,provider_session_key TEXT,provider_session_ref_json TEXT,moved_at TEXT NOT NULL,PRIMARY KEY(run_id,role));
728
- CREATE TABLE IF NOT EXISTS compaction_attempts(idempotency_key TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,crossing_key TEXT NOT NULL,status TEXT NOT NULL,before_sample_json TEXT,after_sample_json TEXT,error TEXT,origin TEXT NOT NULL DEFAULT 'rafi-manual',provider_event_id TEXT,threshold_generation_id TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL);
729
- CREATE TABLE IF NOT EXISTS threshold_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,generation INTEGER NOT NULL,state TEXT NOT NULL,configured_ceiling_percent REAL NOT NULL,installed_native_token_limit INTEGER,installed_native_percent REAL,settings_revision INTEGER NOT NULL,model TEXT NOT NULL,latest_sample_json TEXT,frozen_action_digest TEXT,provider_event_id TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL,UNIQUE(run_id,role,session_key,generation));
730
- CREATE TABLE IF NOT EXISTS active_role_adapter_sessions(run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT NOT NULL,settings_revision INTEGER NOT NULL,observed_at TEXT NOT NULL,PRIMARY KEY(run_id,role,session_key));
645
+ CREATE TABLE IF NOT EXISTS compaction_attempts(idempotency_key TEXT PRIMARY KEY,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),role TEXT NOT NULL,provider_session_id TEXT,session_key TEXT,session_ref_json TEXT,crossing_key TEXT NOT NULL,status TEXT NOT NULL,before_sample_json TEXT,after_sample_json TEXT,error TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL);
731
646
  CREATE TABLE IF NOT EXISTS recovery_decisions(decision_id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES workflow_runs(run_id),decided_at TEXT NOT NULL,mode TEXT NOT NULL,digest TEXT NOT NULL UNIQUE,session_key TEXT,session_ref_json TEXT,receipt_json TEXT NOT NULL);
732
647
  CREATE INDEX IF NOT EXISTS workflow_events_run ON workflow_events(run_id,sequence);
733
648
  CREATE INDEX IF NOT EXISTS operations_run ON operation_journal(run_id,status);
@@ -745,7 +660,7 @@ export class WorkflowDb {
745
660
  continuity_checkpoints: { session_key: "TEXT", session_ref_json: "TEXT" },
746
661
  handoffs: { predecessor_session_key: "TEXT", predecessor_session_ref_json: "TEXT", successor_session_key: "TEXT", successor_session_ref_json: "TEXT" },
747
662
  role_mutation_leases: { provider_session_key: "TEXT", provider_session_ref_json: "TEXT" },
748
- compaction_attempts: { session_key: "TEXT", session_ref_json: "TEXT", origin: "TEXT NOT NULL DEFAULT 'rafi-manual'", provider_event_id: "TEXT", threshold_generation_id: "TEXT" },
663
+ compaction_attempts: { session_key: "TEXT", session_ref_json: "TEXT" },
749
664
  recovery_decisions: { session_key: "TEXT", session_ref_json: "TEXT" },
750
665
  })) {
751
666
  for (const [column, definition] of Object.entries(columns))
@@ -757,10 +672,6 @@ export class WorkflowDb {
757
672
  CREATE INDEX IF NOT EXISTS context_samples_scoped ON context_samples(run_id,role,session_key,sample_id);
758
673
  CREATE INDEX IF NOT EXISTS session_usage_scoped ON session_usage_samples(run_id,role,session_key,sample_id);
759
674
  CREATE INDEX IF NOT EXISTS compaction_session_scoped ON compaction_attempts(run_id,role,session_key,status);
760
- CREATE UNIQUE INDEX IF NOT EXISTS compaction_native_event_unique ON compaction_attempts(run_id,role,session_key,provider_event_id) WHERE provider_event_id IS NOT NULL;
761
- CREATE INDEX IF NOT EXISTS threshold_generation_scoped ON threshold_generations(run_id,role,session_key,generation);
762
- CREATE INDEX IF NOT EXISTS active_role_adapter_session_observed ON active_role_adapter_sessions(observed_at,run_id,role);
763
- CREATE INDEX IF NOT EXISTS live_settings_session_revision ON live_settings_session_acknowledgments(revision,run_id,role,session_key);
764
675
  `);
765
676
  }
766
677
  ensureColumn(table, column, definition) {
@@ -775,23 +686,7 @@ function continuityEventFromRow(row) { return { sequence: row.sequence, runId: r
775
686
  function continuityCheckpointFromRow(row) { return { checkpointId: row.checkpoint_id, runId: row.run_id, role: row.role, sequence: row.event_sequence, state: row.state, delta: parseJson(row.delta_json), digest: row.digest, ...(row.predecessor_digest ? { predecessorDigest: row.predecessor_digest } : {}), authoritativeStateRevision: row.authoritative_state_revision, createdAt: row.created_at, ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}) }; }
776
687
  function continuityHeadFromRow(row) { return { runId: row.run_id, role: row.role, state: row.state, sequence: row.event_sequence, digest: row.digest, authoritativeStateRevision: row.authoritative_state_revision, updatedAt: row.updated_at }; }
777
688
  function handoffFromRow(row) { return { runId: row.run_id, generation: row.generation, manifestDigest: row.manifest_digest, markdownDigest: row.markdown_digest, ...(row.predecessor_session_id ? { predecessorSessionId: row.predecessor_session_id } : {}), ...(row.predecessor_session_ref_json ? { predecessorSessionRef: parseJson(row.predecessor_session_ref_json) } : {}), ...(row.successor_session_id ? { successorSessionId: row.successor_session_id } : {}), ...(row.successor_session_ref_json ? { successorSessionRef: parseJson(row.successor_session_ref_json) } : {}), state: row.state, createdAt: row.created_at, ...(row.accepted_at ? { acceptedAt: row.accepted_at } : {}) }; }
778
- function compactionAttemptFromRow(row) { return { idempotencyKey: row.idempotency_key, runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), crossingKey: row.crossing_key, status: row.status, origin: row.origin ?? "rafi-manual", ...(row.provider_event_id ? { providerEventId: row.provider_event_id } : {}), ...(row.threshold_generation_id ? { thresholdGenerationId: row.threshold_generation_id } : {}), ...(row.before_sample_json ? { beforeSample: parseJson(row.before_sample_json) } : {}), ...(row.after_sample_json ? { afterSample: parseJson(row.after_sample_json) } : {}), ...(row.error ? { error: row.error } : {}), createdAt: row.created_at, updatedAt: row.updated_at }; }
779
- function thresholdGenerationFromRow(row) {
780
- return {
781
- generationId: row.generation_id, runId: row.run_id, role: row.role,
782
- ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}),
783
- ...(row.session_key ? { sessionKey: row.session_key } : {}),
784
- ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}),
785
- generation: row.generation, state: row.state, configuredCeilingPercent: row.configured_ceiling_percent,
786
- ...(row.installed_native_token_limit === null ? {} : { installedNativeTokenLimit: row.installed_native_token_limit }),
787
- ...(row.installed_native_percent === null ? {} : { installedNativePercent: row.installed_native_percent }),
788
- settingsRevision: row.settings_revision, model: row.model,
789
- ...(row.latest_sample_json ? { latestSample: parseJson(row.latest_sample_json) } : {}),
790
- ...(row.frozen_action_digest ? { frozenActionDigest: row.frozen_action_digest } : {}),
791
- ...(row.provider_event_id ? { providerEventId: row.provider_event_id } : {}),
792
- createdAt: row.created_at, updatedAt: row.updated_at,
793
- };
794
- }
689
+ function compactionAttemptFromRow(row) { return { idempotencyKey: row.idempotency_key, runId: row.run_id, role: row.role, ...(row.provider_session_id ? { providerSessionId: row.provider_session_id } : {}), ...(row.session_key ? { sessionKey: row.session_key } : {}), ...(row.session_ref_json ? { sessionRef: parseJson(row.session_ref_json) } : {}), crossingKey: row.crossing_key, status: row.status, ...(row.before_sample_json ? { beforeSample: parseJson(row.before_sample_json) } : {}), ...(row.after_sample_json ? { afterSample: parseJson(row.after_sample_json) } : {}), ...(row.error ? { error: row.error } : {}), createdAt: row.created_at, updatedAt: row.updated_at }; }
795
690
  function json(value) { return JSON.stringify(value ?? null); }
796
691
  function parseJson(value) { return JSON.parse(value); }
797
692
  function sessionParts(ref, rawId, suppliedKey) {