export function parsePack(text: string) { if (Buffer.byteLength(text) > 1_048_576) throw Error("pack too large"); const p = JSON.parse(text); if ( p.schemaVersion !== 1 || !Array.isArray(p.rows) || !Array.isArray(p.redactions) || !Number.isFinite(Date.parse(p.observedAt)) ) throw Error("invalid context pack"); for (const r of p.rows) { if ( !r.object?.uid || typeof r.reason !== "string" || typeof r.type !== "string" || !Number.isInteger(r.count) || r.count < 1 || !Array.isArray(r.eventUIDs) || !r.eventUIDs.length || r.eventUIDs.some((x) => typeof x !== "string" || !x) || !Number.isFinite(Date.parse(r.firstOccurrence)) || !Number.isFinite(Date.parse(r.lastOccurrence)) || Date.parse(r.firstOccurrence) > Date.parse(r.lastOccurrence) ) throw Error("invalid timeline row"); } return p; } const escape = (s: any) => String(s ?? "").replace( /[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ c ]!, ); export function timeline(p: any) { const rows = p.rows .map( (r: any) => `
Observed ${escape(p.observedAt)}. Occurrence time is shown separately.
| Occurrence | Object identity | Evidence | Event UIDs |
|---|
${p.redactions.map(escape).join("; ")}. Event reasons suggest investigations, not proven causes.
`; }