File size: 4,692 Bytes
867ba1a
39f387e
867ba1a
 
 
 
 
 
 
 
3f831f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867ba1a
 
 
 
 
 
 
 
39f387e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
export const SCHEMA_VERSION = "deltastore-replay-trace-v1";
export const EXCHANGE_SCHEMA_VERSION = "solstice-agent-trace-exchange/v1";

export function parseTrace(value) {
  const trace = typeof value === "string" ? JSON.parse(value) : value;
  if (!trace || trace.schema_version !== SCHEMA_VERSION || !Array.isArray(trace.original_trajectory)) throw new Error("Unsupported or malformed replay trace");
  if (!Array.isArray(trace.checkpoints) || !Array.isArray(trace.branches)) throw new Error("Replay trace lacks checkpoints or branches");
  return trace;
}

// Convert the bundled legacy replay record at the publication boundary. The
// stored fixture remains backwards-compatible; downloaded artifacts are now
// always the shared exchange envelope consumed by Atlas.
export function toExchange(trace) {
  const legacy = parseTrace(trace);
  const findings = (legacy.safety_findings || []).map((finding) => ({
    ...finding,
    evidence_event_ids: finding.evidence_event_ids || finding.evidence_lineage || [],
    evidence_refs: finding.evidence_refs || finding.evidence_lineage || [],
  }));
  return parseExchange({
    schema_version: EXCHANGE_SCHEMA_VERSION,
    trace_id: legacy.trace_id,
    title: legacy.title,
    description: legacy.description,
    source: { type: legacy.source_type || "synthetic", format: legacy.source_format || legacy.schema_version, schema_version: legacy.schema_version },
    task: legacy.task || {},
    environment: legacy.environment || {},
    policy: legacy.policy || {},
    events: (legacy.original_trajectory || []).map((event, index) => ({ ...event, sequence: Number.isInteger(event.sequence) ? event.sequence : index + 1 })),
    checkpoints: legacy.checkpoints || [],
    branches: legacy.branches || [],
    outcomes: { original: legacy.final_outcome || {}, ...(legacy.branches || []).reduce((acc, branch) => ({ ...acc, [branch.branch_id]: branch.final_outcome || {} }), {}) },
    findings,
    redaction: legacy.redaction || {},
    provenance: { ...(legacy.provenance || {}), converted_from: legacy.schema_version },
    model: legacy.model || {},
    harness: legacy.harness || {},
    performance: legacy.performance || {},
  });
}

export function compareBranch(trace, branchId) {
  const branch = trace.branches.find(item => item.branch_id === branchId);
  if (!branch) throw new Error("Branch not found");
  const checkpoint = trace.checkpoints.find(item => item.checkpoint_id === branch.parent_checkpoint_id);
  if (!checkpoint) throw new Error("Branch checkpoint not found");
  const index = trace.original_trajectory.findIndex(event => event.event_id === checkpoint.event_id);
  return { sharedPrefix: trace.original_trajectory.slice(0, index + 1), originalTail: trace.original_trajectory.slice(index + 1), alternate: branch.events, originalOutcome: trace.final_outcome || {}, alternateOutcome: branch.final_outcome || {}, classification: branch.comparison_classification || "outcome_changed" };
}

export function parseExchange(value) {
  const packageValue = typeof value === "string" ? JSON.parse(value) : value;
  if (!packageValue || packageValue.schema_version !== EXCHANGE_SCHEMA_VERSION || !Array.isArray(packageValue.events)) throw new Error("Unsupported or malformed exchange package");
  const ids = new Set();
  let previous = -1;
  for (const event of packageValue.events) {
    if (!event.event_id || ids.has(event.event_id)) throw new Error("Duplicate or missing exchange event ID");
    if (!Number.isInteger(event.sequence) || event.sequence <= previous) throw new Error("Invalid exchange event ordering");
    ids.add(event.event_id); previous = event.sequence;
  }
  return packageValue;
}

export function attachFindings(packageValue, findingsValue) {
  const packageTrace = parseExchange(packageValue);
  const findings = Array.isArray(findingsValue) ? findingsValue : findingsValue?.findings;
  if (!Array.isArray(findings)) throw new Error("Findings must be an array");
  const eventIds = new Set(packageTrace.events.map(event => event.event_id));
  for (const finding of findings) {
    if (finding.trace_id && finding.trace_id !== packageTrace.trace_id) throw new Error("Finding trace ID does not match");
    for (const ref of finding.evidence_refs || finding.evidence_events || []) if (!eventIds.has(ref)) throw new Error("Finding references an unknown event");
  }
  return {...packageTrace, findings};
}

export function downloadJson(value, filename) {
  const blob = new Blob([JSON.stringify(value, null, 2)], {type: "application/json"});
  const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; link.click();
  URL.revokeObjectURL(link.href);
}