Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
1/**
2 * Compaction Engine — moves cold data to archives while preserving
3 * node headers, provenance edges, approvals, and signatures.
4 */
5
6import type { CompactionEvent } from './models/pipeline.js';
7
8export interface StorageStats {
9 total_objects: number;
10 total_bytes: number;
11 hot_objects: number;
12 hot_bytes: number;
13 cold_objects: number;
14 cold_bytes: number;
15}
16
17export interface CompactionCandidate {
18 object_id: string;
19 object_type: string;
20 age_days: number;
21 size_bytes: number;
22 /** Whether this object must be preserved (header, provenance, approval, sig) */
23 preserve: boolean;
24}
25
26/**
27 * Identify objects eligible for compaction.
28 */
29export function identifyCandidates(
30 objects: CompactionCandidate[],
31 hotWindowDays: number = 30,
32): { toCompact: CompactionCandidate[]; toPreserve: CompactionCandidate[] } {
33 const toCompact: CompactionCandidate[] = [];
34 const toPreserve: CompactionCandidate[] = [];
35
36 for (const obj of objects) {
37 if (obj.preserve) {
38 toPreserve.push(obj);
39 } else if (obj.age_days > hotWindowDays) {
40 toCompact.push(obj);
41 } else {
42 toPreserve.push(obj);
43 }
44 }
45
46 return { toCompact, toPreserve };
47}
48
49/**
50 * Simulate a compaction run and produce an event.
51 */
52export function runCompaction(
53 objects: CompactionCandidate[],
54 trigger: CompactionEvent['trigger'],
55 hotWindowDays: number = 30,
56): CompactionEvent {
57 const { toCompact, toPreserve } = identifyCandidates(objects, hotWindowDays);
58
59 const bytesFreed = toCompact.reduce((sum, o) => sum + o.size_bytes, 0);
60 const preservedHeaders = toPreserve.filter(o => o.object_type === 'node_header').length;
61 const preservedProvenance = toPreserve.filter(o => o.object_type === 'provenance_edge').length;
62 const preservedApprovals = toPreserve.filter(o => o.object_type === 'approval').length;
63 const preservedSignatures = toPreserve.filter(o => o.object_type === 'signature').length;
64
65 return {
66 type: 'CompactionEvent',
67 timestamp: new Date().toISOString(),
68 trigger,
69 nodes_compacted: toCompact.length,
70 bytes_freed: bytesFreed,
71 preserved: {
72 node_headers: preservedHeaders,
73 provenance_edges: preservedProvenance,
74 approvals: preservedApprovals,
75 signatures: preservedSignatures,
76 },
77 };
78}
79
80/**
81 * Check if compaction should be triggered.
82 */
83export function shouldTriggerCompaction(
84 stats: StorageStats,
85 sizeThresholdBytes: number = 100 * 1024 * 1024, // 100MB default
86 daysSinceLastCompaction: number = 0,
87 timeThresholdDays: number = 90,
88): { trigger: boolean; reason: CompactionEvent['trigger'] | null } {
89 if (stats.total_bytes > sizeThresholdBytes) {
90 return { trigger: true, reason: 'size_threshold' };
91 }
92 if (daysSinceLastCompaction > timeThresholdDays) {
93 return { trigger: true, reason: 'time_based' };
94 }
95 return { trigger: false, reason: null };
96}