/** * Strategy seam for the optional history + ledger + time-machine * service. Core imports `HistoryStrategy` type-only + `NO_HISTORY` * stub; real implementations of `saveHistory`, `LedgerStore`, * `VaultInstant`, `computePatch`, `diff` etc. are only reachable via * `withHistory()` in `./active.ts`. * * Applications that don't track per-record versioning, don't need the * hash-chained audit ledger, and don't restore to past instants ship * none of the ~1,880 LOC behind this seam. * * Strategy contract: * * - **saveHistory / pruneHistory / clearHistory** — no-ops under * NO_HISTORY. Writes still succeed; no snapshot is captured. * - **getHistoryEntries / getVersionEnvelope / diff** — throw under * NO_HISTORY. These are read APIs the consumer would only call * after explicitly asking for history; the throw guides them to * `@noy-db/hub/history`. * - **envelopePayloadHash / computePatch** — return empty / `[]` * under NO_HISTORY. These are only used inside the * `if (this.ledger)` branch, which is itself gated by * `buildLedger()` returning null. * - **buildLedger** — returns `null` under NO_HISTORY. The Vault's * public `vault.ledger()` accessor throws when null. * - **buildVaultInstant** — throws under NO_HISTORY. `vault.at()` * propagates the throw. * * @internal */ import type { EncryptedEnvelope, NoydbStore, HistoryOptions, PruneOptions } from '../../kernel/types.js'; import type { LedgerStore } from './ledger/store.js'; import type { JsonPatch } from './ledger/patch.js'; import type { DiffEntry } from './diff.js'; import type { VaultInstant, VaultEngine } from './time-machine.js'; import type { EnclaveKey } from '../../kernel/enclave/index.js'; /** * Options accepted by `HistoryStrategy.buildLedger`. Mirrors the * `LedgerStore` constructor verbatim — kept in this file so `core` * code never imports the LedgerStore module at runtime. * * @internal */ export interface BuildLedgerOptions { adapter: NoydbStore; vault: string; encrypted: boolean; getDEK: (collectionName: string) => Promise; actor: string; } /** * @internal */ export interface HistoryStrategy { /** * Persist a full encrypted envelope snapshot of the prior version * under `_history/{collection}:{id}:{paddedVersion}`. No-op under * `NO_HISTORY`. */ saveHistory(adapter: NoydbStore, vault: string, collection: string, recordId: string, envelope: EncryptedEnvelope): Promise; /** * List history envelopes for a record, newest first. Throws under * `NO_HISTORY` — callers reach this via `collection.history()` / * `collection.getVersion()` / `collection.diff()`, which only work * with the strategy enabled. */ getHistoryEntries(adapter: NoydbStore, vault: string, collection: string, recordId: string, options?: HistoryOptions): Promise; /** * Fetch a specific version's envelope. Throws under `NO_HISTORY`. */ getVersionEnvelope(adapter: NoydbStore, vault: string, collection: string, recordId: string, version: number): Promise; /** * Prune history entries by retention rule. Returns `0` under * `NO_HISTORY`. */ pruneHistory(adapter: NoydbStore, vault: string, collection: string, recordId: string | undefined, options: PruneOptions): Promise; /** * Clear all history for vault/collection/record. Returns `0` under * `NO_HISTORY`. */ clearHistory(adapter: NoydbStore, vault: string, collection?: string, recordId?: string): Promise; /** * Crypto-shred (overwrite-to-tombstone) every `_history` version of a * record for GDPR erasure. Returns the number of versions newly * tombstoned, or `0` under `NO_HISTORY` (no history = nothing to shred). */ tombstoneHistory(adapter: NoydbStore, vault: string, collection: string, recordId: string, actor: string, encrypted: boolean): Promise; /** * Re-key every `_history` snapshot of a record from `fromDek` to `toDek` * on a tier move (elevate/demote/putAtTier), mirroring the live-body * rewrap so prior versions are not left decryptable at rest under a tier * the record has moved away from. Optional `tier0Dek` enables the legacy * fallback for snapshots written before this rewrap existed (see * `history.ts` for the full contract). No-op under `NO_HISTORY` (no * history = nothing to rewrap). */ rewrapHistory(adapter: NoydbStore, vault: string, collection: string, recordId: string, fromDek: EnclaveKey, toDek: EnclaveKey, tier0Dek?: EnclaveKey): Promise; /** * Compute the SHA-256 hash of an envelope's encrypted payload, used * by `LedgerStore.append` to track tamper-evidence. Returns the * empty string under `NO_HISTORY` (the call site is gated by * `if (this.ledger)`, so the value is never observed). */ envelopePayloadHash(envelope: EncryptedEnvelope | null): Promise; /** * Compute the JSON patch from `from` → `to`. Returns `[]` under * `NO_HISTORY`. */ computePatch(from: unknown, to: unknown): JsonPatch; /** * Compute the typed diff between two records. Throws under * `NO_HISTORY` — `collection.diff()` is a history-read API. */ diff(recordA: unknown, recordB: unknown): DiffEntry[]; /** * Construct (or return null) a `LedgerStore` for the vault. Returns * `null` under `NO_HISTORY`; the Vault treats null as "no ledger * attached" — collection write paths skip the append branch and the * public `vault.ledger()` accessor throws. */ buildLedger(opts: BuildLedgerOptions): LedgerStore | null; /** * Construct a `VaultInstant` for time-machine reads. Throws under * `NO_HISTORY`. */ buildVaultInstant(engine: VaultEngine, timestamp: string): VaultInstant; } /** * No-history stub. Snapshots and prune/clear are no-ops; reads and * time-machine throw with an actionable message; ledger construction * returns null so the write-path's `if (this.ledger)` branch is dead * code in the bundle. * * @internal */ export declare const NO_HISTORY: HistoryStrategy;