mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 18:22:42 +00:00
fix(snapshot): replay validated workflow evidence (#984)
* fix(snapshot): bind replay to validated cache entry * fix(snapshot): persist composed workflow results * fix(snapshot): enforce workflow replay identity * fix(snapshot): honor cancellation after route resolution * test(snapshot): make cancellation race deterministic * fix(snapshot): replay all composed workflow evidence * fix(snapshot): index workflow evidence once per snapshot Validate workflow entries against a query and content index instead of scanning the evidence bundle for every entry. * fix(snapshot): reuse previewed routes and bound workflow cache --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
96a46ada0a
commit
1d62e54e1f
11 files changed
+1462
-80
No files matched your search
@@ -9,6 +9,7 @@ import { err, ok, type Result } from "../../domain/result.js";
|
||||
import {
|
||||
analysisQueryId,
|
||||
createAnalysisSnapshotEntry,
|
||||
createAnalysisSnapshotWorkflowEntry,
|
||||
snapshotBinding,
|
||||
snapshotMatchesProfile,
|
||||
snapshotMatchesTarget,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
parseAnalysisSnapshot,
|
||||
type AnalysisSnapshot,
|
||||
type AnalysisSnapshotEntry,
|
||||
type AnalysisSnapshotWorkflowEntry,
|
||||
type AnalysisSnapshotBinding,
|
||||
type AnalysisSnapshotTarget,
|
||||
} from "../../domain/analysisSnapshot.js";
|
||||
@@ -54,6 +56,9 @@ const DOCUMENT_SCOPED_OPERATIONS: ReadonlySet<string> = new Set(
|
||||
),
|
||||
);
|
||||
|
||||
/** Maximum duplicated result bindings retained across direct and workflow queries. */
|
||||
export const SNAPSHOT_CACHE_ENTRY_CEILING = 10_000;
|
||||
|
||||
/** Whether an operation is immutable and independent of provider UI state. */
|
||||
export const isSnapshotCacheable = (
|
||||
operation: AnalysisOperation,
|
||||
@@ -73,6 +78,7 @@ export const isSnapshotCacheable = (
|
||||
/** Bounded in-memory cache for one immutable binary identity. */
|
||||
export class AnalysisSnapshotCache {
|
||||
readonly #entries = new Map<string, AnalysisSnapshotEntry>();
|
||||
readonly #workflowEntries = new Map<string, AnalysisSnapshotWorkflowEntry>();
|
||||
#target: AnalysisSnapshotTarget | undefined;
|
||||
#binding: AnalysisSnapshotBinding | undefined;
|
||||
|
||||
@@ -93,7 +99,7 @@ export class AnalysisSnapshotCache {
|
||||
|
||||
/** Replace cache state when the target or selected profile changes. */
|
||||
select(target: BinaryTarget, profile: AnalysisProfileCommitment): void {
|
||||
if (!this.matches(target, profile)) this.#entries.clear();
|
||||
if (!this.matches(target, profile)) this.#clearEntries();
|
||||
this.#target = snapshotTarget(target);
|
||||
this.#binding = snapshotBinding(profile);
|
||||
}
|
||||
@@ -109,14 +115,20 @@ export class AnalysisSnapshotCache {
|
||||
snapshot.binding.analysis_profile,
|
||||
))
|
||||
)
|
||||
this.#entries.clear();
|
||||
this.#clearEntries();
|
||||
this.#target = structuredClone(snapshot.target);
|
||||
this.#binding = structuredClone(snapshot.binding);
|
||||
let imported = 0;
|
||||
for (const entry of snapshot.entries) {
|
||||
if (!this.#entries.has(entry.query_id) && !this.#hasCapacity()) continue;
|
||||
if (!this.#entries.has(entry.query_id)) imported += 1;
|
||||
this.#entries.set(entry.query_id, structuredClone(entry));
|
||||
}
|
||||
for (const entry of snapshot.workflow_entries ?? []) {
|
||||
if (!this.#workflowEntries.has(entry.query_id) && !this.#hasCapacity())
|
||||
continue;
|
||||
this.#workflowEntries.set(entry.query_id, structuredClone(entry));
|
||||
}
|
||||
return imported;
|
||||
}
|
||||
|
||||
@@ -137,6 +149,9 @@ export class AnalysisSnapshotCache {
|
||||
target: snapshotTargetIdentity,
|
||||
binding,
|
||||
entries: this.entries(),
|
||||
...(this.workflowEntries().length === 0
|
||||
? {}
|
||||
: { workflow_entries: this.workflowEntries() }),
|
||||
evidence_bundle: retainedEvidence,
|
||||
}),
|
||||
);
|
||||
@@ -193,6 +208,47 @@ export class AnalysisSnapshotCache {
|
||||
.map((entry) => structuredClone(entry));
|
||||
}
|
||||
|
||||
/** Return canonical composed-workflow entries for persistence. */
|
||||
workflowEntries(): AnalysisSnapshotWorkflowEntry[] {
|
||||
return [...this.#workflowEntries.values()]
|
||||
.sort((left, right) => left.query_id.localeCompare(right.query_id))
|
||||
.map((entry) => structuredClone(entry));
|
||||
}
|
||||
|
||||
/** Record one exact derived workflow result unless the shared cache is full. */
|
||||
recordWorkflow(input: {
|
||||
readonly target: BinaryTarget;
|
||||
readonly profile: AnalysisProfileCommitment;
|
||||
readonly operation: string;
|
||||
readonly parameters: Readonly<Record<string, JsonValue>>;
|
||||
readonly execution: {
|
||||
readonly result: JsonValue;
|
||||
readonly rawResult: JsonValue | null;
|
||||
readonly provider: AnalysisExecution["provider"];
|
||||
readonly analysisProfile: AnalysisProfileCommitment;
|
||||
readonly limitations: readonly string[];
|
||||
readonly locations: AnalysisExecution["locations"];
|
||||
readonly subject: AnalysisExecution["subject"];
|
||||
};
|
||||
}): void {
|
||||
this.select(input.target, input.profile);
|
||||
const queryId = analysisQueryId(
|
||||
snapshotTarget(input.target),
|
||||
snapshotBinding(input.profile),
|
||||
input.operation,
|
||||
input.parameters,
|
||||
);
|
||||
if (!this.#workflowEntries.has(queryId) && !this.#hasCapacity()) return;
|
||||
const entry = createAnalysisSnapshotWorkflowEntry({
|
||||
target: snapshotTarget(input.target),
|
||||
binding: snapshotBinding(input.profile),
|
||||
operation: input.operation,
|
||||
parameters: input.parameters,
|
||||
execution: input.execution,
|
||||
});
|
||||
this.#workflowEntries.set(entry.query_id, entry);
|
||||
}
|
||||
|
||||
/** Replay an exact provider-specific query, marking its cached provenance. */
|
||||
lookup(
|
||||
target: BinaryTarget,
|
||||
@@ -246,8 +302,14 @@ export class AnalysisSnapshotCache {
|
||||
readonly execution: AnalysisExecution;
|
||||
}): void {
|
||||
const { target, profile, operation, parameters, execution } = input;
|
||||
if (this.#entries.size >= 10_000) return;
|
||||
this.select(target, profile);
|
||||
const queryId = analysisQueryId(
|
||||
snapshotTarget(target),
|
||||
snapshotBinding(profile),
|
||||
operation,
|
||||
parameters,
|
||||
);
|
||||
if (!this.#entries.has(queryId) && !this.#hasCapacity()) return;
|
||||
const entry = createAnalysisSnapshotEntry({
|
||||
target: snapshotTarget(target),
|
||||
binding: snapshotBinding(profile),
|
||||
@@ -260,8 +322,20 @@ export class AnalysisSnapshotCache {
|
||||
|
||||
/** Forget all target-bound entries. */
|
||||
clear(): void {
|
||||
this.#entries.clear();
|
||||
this.#clearEntries();
|
||||
this.#target = undefined;
|
||||
this.#binding = undefined;
|
||||
}
|
||||
|
||||
#clearEntries(): void {
|
||||
this.#entries.clear();
|
||||
this.#workflowEntries.clear();
|
||||
}
|
||||
|
||||
#hasCapacity(): boolean {
|
||||
return (
|
||||
this.#entries.size + this.#workflowEntries.size <
|
||||
SNAPSHOT_CACHE_ENTRY_CEILING
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user