/** * Event-log compaction: truncate old hash-chained event-log records behind a * confirmed checkpoint to reclaim storage on long-running workflows. * * The canonical checkpoint at `KEYS.checkpoint` already holds the compacted * execution state, and resume rebuilds from that checkpoint alone (never from * event replay), so deleting records below a retention window does not affect * resume correctness. To keep {@link EventLog.verify} from falsely reporting a * broken hash chain after truncation, compaction writes an atomic **watermark** * record in the SAME storage batch as the deletes: `verify()` seeds its chain * walk from the watermark instead of genesis. * * Compaction is folded into the checkpoint commit batch (see * `checkpoint-io.ts`), so the deletes and the watermark commit atomically with * the new checkpoint — there is never a committed state where records are gone * but the watermark is absent. * * @module core/engine/event-log-compaction */ import type { BatchOperation, Storage } from '../../storage/interface.ts'; import { type CheckpointReplayPayload } from './checkpoint-replay.ts'; /** * Upper bound on event-log records deleted in a single compaction (one * checkpoint commit). A workflow that enables `retentionWindow` with a large * pre-existing backlog compacts incrementally across successive checkpoints — * the watermark advances by at most this many records per commit — rather than * emitting one unbounded batch that could exceed storage batch limits, stall the * execution path, or load the whole backlog into memory at once. */ export declare const MAX_COMPACTION_BATCH = 1000; /** * The compaction watermark stored at `ev:{id}:watermark`. Marks where the live * (post-compaction) event-log prefix begins so {@link EventLog.verify} can walk * the surviving chain without a genesis predecessor. * * Internal: the shape is a storage implementation detail and is intentionally * not exported from the package barrel. */ export type EventLogWatermark = { type: 'event-log-watermark'; version: 1; /** Lowest SURVIVING sequence; all records with `sequence < this` are deleted. */ sequence: number; /** * `hashBytes` of the raw stored bytes of the last deleted record * (`sequence - 1`). Equals the surviving record `sequence`'s own `prevHash` * field, so `verify()` validates the first surviving link from this seed. */ prevHash: string; /** * Highest sequence deleted across ALL compactions (`sequence - 1`). Since the * watermark only advances forward, `[0, deletedThrough]` is the complete * deleted prefix — unambiguous across incremental batches. */ deletedThrough: number; /** * Internal replay deltas folded from compacted checkpoint events. Canonical * checkpoints can prune consumed results only because recovery can seed replay * from the event log; when old events are compacted, their replay deltas move * here before deletion. */ checkpointReplay?: CheckpointReplayPayload; }; /** * Narrow an unknown decoded value to {@link EventLogWatermark}, rejecting * internally inconsistent or out-of-range records (negative/non-integer * sequences, `deletedThrough` that is not `sequence - 1`, empty `prevHash`) so a * corrupt or hand-tampered watermark is never treated as authoritative. */ export declare function isEventLogWatermark(value: unknown): value is EventLogWatermark; /** Read and decode the watermark for a workflow, or `null` when absent/invalid. */ export declare function readEventLogWatermark(storage: Storage, workflowId: string): Promise; /** Outcome of a compaction that contributed operations to a checkpoint batch. */ export type CompactionResult = { /** The watermark written in this batch. */ watermark: EventLogWatermark; /** Raw stored bytes of the deleted records, in ascending sequence order. */ deletedEntries: Uint8Array[]; /** Inclusive sequence bounds of the records deleted in this batch. */ deletedRange: { from: number; to: number; }; }; /** * Append the delete + watermark operations for one compaction pass onto * `operations`, to be committed atomically alongside a checkpoint batch. * * Returns `null` (a no-op — `operations` is left untouched) when: * - compaction is disabled (`retentionWindow` is `null`); * - there is nothing new to delete (`batchFirstSurviving <= currentFloor`); * - the last-deleted record (`batchFirstSurviving - 1`) is missing — we cannot * derive a valid `prevHash`, so we abort rather than write an unvalidatable * watermark, leaving any pre-existing corruption visible to `verify()`; * - the delete range is non-contiguous (a gap already exists) — aborting keeps * compaction from advancing the watermark past pre-existing corruption. * * @param retentionWindow Keep at most this many most-recent records, or `null` * to disable compaction (a no-op). * @param headSequence The event-log head sequence AFTER the current * checkpoint append (`newHead.sequence`). The retention window is measured * against this, NOT `checkpoint.step`, so step/sequence need not stay coupled. */ export declare function appendCompactionOperations(storage: Storage, workflowId: string, headSequence: number, retentionWindow: number | null, operations: BatchOperation[]): Promise; /** * Serialize the raw stored bytes of a deleted event-log range for an * {@link import('../types/archive-adapter.ts').ArchiveAdapter}. Round-trips via * the codec: `decode()` on the result yields the original `Uint8Array[]`. */ export declare function serializeDeletedEntries(entries: Uint8Array[]): Uint8Array;