/** * The classified `ViaBinding` (#629 Task 5, LIVE since Task 6) — wires * classified-fields declaration/write-enforcement/at-rest sealing/erasure * into the kernel's generic Via port. Mirrors `via/money/binding.ts`'s * inline declaration-time validation and `via/i18n/binding.ts`'s * `linkXVia` static-link pattern — except the link itself is EAGER * (`port/with/classified-strategy.ts` calls `linkClassifiedVia()` at module * load, not lazily from a `classified.*()` preset call): several fixtures * build a raw `ClassifiedFieldSpec` literal without ever calling a preset, * and the binder must be installed before `compileViaBindings` needs it * regardless. * * `compileViaBindings` (`kernel/collection-config.ts`) compiles this binding * in whenever a collection declares `classifiedFields` — money then i18n * then classified, order pinned. `kernel/collection.ts`'s `_putInternal` * runs `enforceClassifiedWrite`'s effect via the pipeline's `enforceWrite` * phase; the codec's `encodeAtRest`/`decodeAtRest` hooks (Task 3's boundary) * replace the inline `sensitiveFields` seal path for any collection this * binding is compiled into. * * `declare` — `classifiedBinding(cfg)` runs `resolveClassifiedFields` + * `guardClassifiedCompat` at CONSTRUCTION time (the same #553 pattern * `moneyBinding`'s `validateMoneyFieldPaths(moneyFields)` call uses) — * mirrors today's "door 1" (`collection-config.ts`'s * `resolveCollectionConfig`). Both may throw `ClassifiedConfigError`. */ import type { ViaBinding } from '../../kernel/via/index.js'; import { type ClassifiedEntry } from './resolve.js'; import { type ClassifiedGuardCtx } from './guards.js'; /** * One classified per-slot shred/residue verdict — mirrors * `RecordCodec.SealedShredSlot` (`kernel/enclave/record-keys/record-codec.ts`) * byte-for-byte. Duplicated here (not imported) so this binding never * reaches into `kernel/enclave/` (`via-enclave-isolation`, #629) — the * closure that PRODUCES real values of this shape is codec-owned and * injected via {@link ClassifiedViaConfig.classifySealedShred}. */ export interface ClassifiedShredSlot { readonly field: string; readonly class: 'shreddable' | 'dekResidue' | 'live-shreddable+dekResidue-in-backups'; } /** * Config a classified collection's declarations resolve to — the binding's * construction input. `entries`/`guardCtx` are the raw "door 1" inputs * (`resolveClassifiedFields`/`guardClassifiedCompat`'s own parameters); * `classifySealedShred`/`purgeSealedCekEnvelopes` are OPTIONAL codec/vault- * provided closures for the `erase` hook — undefined in this dormant task * (no caller wires them yet), wired for real in #629 Task 10 ("posture * enforcement — forget + erase hooks live"). */ export interface ClassifiedViaConfig { readonly entries: Record; readonly collectionName: string; readonly guardCtx: ClassifiedGuardCtx; /** * Codec-provided closure mirroring `RecordCodec.classifySealedShred` — * classifies a live envelope's `_sealed` slots for crypto-shred * completeness (see that method's doc comment for the shreddable/ * dekResidue/both-class semantics). `live` is `ViaEraseCtx.live`'s opaque * envelope, passed straight through. NOT `readonly` (#629 Task 10): * `compileViaBindings` builds this cfg before the owning `Collection`'s * `RecordCodec` exists, so `Collection`'s constructor mutates this field * in place once `this.codec` is available — the closure `eraseClassified` * reads is looked up at CALL time, not at binding-construction time, so * the late assignment is picked up correctly. */ classifySealedShred?: (live: unknown) => Promise<{ readonly slots: readonly ClassifiedShredSlot[]; }>; /** * Vault-provided closure that purges every `_sealed_cek///*` * host-delivery envelope for one record (mirrors `rotateRecordCek`'s own * prefix-delete) and returns the count purged. Deliberately left UNWIRED * by `compileViaBindings` (#629 Task 10) — `forget-sealed-erasure.test.ts` * proves this purge is UNCONDITIONAL on `sealRecordToHost` usage alone, * independent of whether `classifiedFields` is declared (a bare * `sensitive: [...]` collection exercises it with no classified binding * at all) — vault.ts keeps owning it directly so that case stays correct. */ readonly purgeSealedCekEnvelopes?: (id: string) => Promise; } export declare function classifiedBinding(cfg: ClassifiedViaConfig): ViaBinding; export declare function linkClassifiedVia(): void;