import type { EncryptedEnvelope } from '../types.js'; /** Awaitable type for potentially async results. */ type Awaitable = T | Promise; /** Declared security posture — a property the kernel enforces (enforcement activates in phases B/C). */ export interface ViaPosture { readonly encryptedAtRest: 'envelope' | 'sealed'; readonly queryable: 'none' | 'det-exact' | 'ordered' | 'full'; readonly exportable: boolean; readonly forgettable: boolean; } /** Opaque marker every feature descriptor extends; the kernel never sees concrete descriptor types. */ export interface ViaDescriptor { readonly _viaBrand: string; } /** Per-call write context (A: minimal; B/C extend). */ export interface ViaWriteCtx { readonly id: string; /** Owning vault name (event payload identity — e.g. i18n:script-violation). */ readonly vault: string; /** Prior stored record (decoded), lazily resolved. Null when creating. */ readonly prior: () => Promise | null>; /** Typed event emission (e.g. i18n:script-violation). */ readonly emit: (event: string, payload: unknown) => void; } /** Per-call read context — mirrors LocaleReadOptions loosely; features narrow. */ export interface ViaReadCtx { readonly locale?: unknown; readonly fallback?: unknown; readonly layer: string; } /** One sealed slot's ciphertext — matches the existing `iv:data` sealed map entries (seam map §2 step 2). */ export interface SealedSlotRef { readonly iv: string; readonly data: string; } /** * A scoped crypto capability handed to a `via` feature's `encodeAtRest`/ * `decodeAtRest`/`erase` hooks — never the keyring, never the enclave. * `sealedSlots` is pre-bound to one `(collection, recordId)`; `reservedEnvelopes` * is a whole-envelope encrypt/decrypt door scoped to collection names under a * declared prefix (e.g. `_dict_`). */ export interface ViaCryptoCtx { readonly sealedSlots: { seal(field: string, plaintext: unknown): Promise; unseal(field: string, ref: SealedSlotRef): Promise; delete(field: string): Promise; }; reservedEnvelopes(prefix: string): { encrypt(collection: string, json: string, v: number): Promise; decrypt(collection: string, env: EncryptedEnvelope): Promise; }; } /** Per-call erase context — `forget()`'s per-ref participation door (phase C). */ export interface ViaEraseCtx { readonly id: string; readonly vault: string; readonly live: unknown; readonly crypto: ViaCryptoCtx; } /** * What an `erase` hook reports back to `forget()`'s summary ledger entry. * `retainedShared` is additive (#629 Task 10) — content NOT erased because * still referenced by another live record (blob's shared-chunk dual * accounting, `shredAllForRecord()`'s `retainedShared` count). Absent/0 for * bindings with no such concept (a sealed field is never "shared"). */ export interface ViaEraseReport { readonly shredded: number; readonly residue: readonly unknown[]; readonly retainedShared?: number; } /** * A feature bound to one collection's declared config. Record-grain hooks — * every hook receives the whole record (matches the real engine signatures, * e.g. quantizeMoneyFields(record, moneyFields)). All hooks optional; * absent = passthrough. Query-participation hooks are SYNC (#553). */ export interface ViaBinding { readonly brand: string; readonly posture: ViaPosture; /** Declared dependencies (field paths / cross-record specs). MANDATORY for any future * derive-bearing binding — phase C validates well-formedness (strings, non-empty, * reference declared fields) and graph-registers a derived edge for each covered * field at declare time, right after `compileViaBindings` builds the bindings * (unknown source field throws `ValidationError`). See * `kernel/collection-config.ts`'s `resolveViaBindingDepsEdges`. */ readonly deps?: readonly string[]; /** Collection-name prefixes this binding's `reservedEnvelopes` capability may address (e.g. `_dict_`). */ readonly reservedPrefixes?: readonly string[]; /** * Does this binding own `field`? Backs `ViaPipeline.postureFor` (#629 Task * 8's posture consumer) — a passive coverage check, independent of * `buildClause`/`compareForOrder` (which some bindings, e.g. classified and * blob, never define). SYNC, no side effects. * * Contract (documentation only — not enforced at runtime, future work): * any binding declaring a non-default posture value — `queryable: 'none'` * (#629 Task 8) or `exportable: false` (#629 Task 9's `redactForExport`) * — MUST implement `covers()`. `postureFor`'s consumers only engage for * fields a binding actively claims via `covers()`; a binding that omits * it would silently fall through to the generic (unrefused/unredacted) * path instead. */ covers?(field: string): boolean; /** Refuse a write before crypto runs (classified step-3 slot: storage:'never' rejection + validators). Throws to refuse. */ enforceWrite?(record: Record, ctx: ViaWriteCtx): void | Promise; /** First pipeline stage (money canonicalizeIncomingMoney). SYNC. */ ingest?(record: Record): Record; /** Decode STORED form to canonical for internal boundaries (gates, derivations, patch bases). SYNC. */ canonicalizeStored?(record: Record): Record; /** Post-validation write encoding (money quantize; i18n translate→script→validate→densify). May be async. */ encodeWrite?(record: Record, ctx: ViaWriteCtx): Awaitable>; /** Final write-pipeline stage: seal/encrypt declared fields via `crypto` before the envelope body is built (classified step-2 slot). */ encodeAtRest?(record: Record, crypto: ViaCryptoCtx): Promise<{ record: Record; sealed?: Record; }>; /** First read-pipeline stage: unseal/decrypt declared fields via `crypto` before `present` runs (classified sealed-handle slot). */ decodeAtRest?(record: Record, sealed: Record, crypto: ViaCryptoCtx, opts: { asHandles: boolean; }): Promise>; /** Read-time presentation (money decode+virtuals; i18n locale/labels/strip). May be async. */ present?(record: Record, ctx: ViaReadCtx): Awaitable>; /** * Late read-time presentation (#669) — runs AFTER every binding's `present()` in the * money+computed present-order segment (`ViaPipeline`'s `_presentOrder`, `kernel/via/ * pipeline.ts`), BEFORE the "everything else" segment (i18n/lookup dressing, taint * redaction) runs. Exists for a binding that needs to react to a virtual computed * field's fresh OWN-field output before anything dresses it (money's MAJOR-UNITS * quantize-and-present for a field that is BOTH money AND `mode:'virtual'` computed) — * `present()` alone can't express "run after computed for THESE fields only, before * computed for all others", since it's a per-BINDING fold, not per-field. Folded over * `bindings` in declaration order (mirrors every other per-binding fold), so lookup/ * i18n dressing and taint/redaction (both in the "everything else" segment) still see * the already-dressed value. */ presentLate?(record: Record, ctx: ViaReadCtx): Awaitable>; /** Returns an opaque clause payload when this binding covers `field`, else undefined. */ buildClause?(field: string, op: string, value: unknown): unknown | undefined; /** Evaluate a payload produced by buildClause against a raw stored value. */ evaluateClause?(actual: unknown, op: string, payload: unknown): boolean; /** * Optional: the STORED-form operand for a direct secondary-index probe * against a payload produced by `buildClause` (#625) — lets * `candidateRecords()` (`query/builder.ts`) hit `CollectionIndexes. * lookupEqual`/`lookupIn` for `==`/`in` clauses instead of falling back * to a linear scan + per-record `evaluateClause`. Returning `undefined` * means "no sound probe for this op/payload" (e.g. a comparison whose * stored form isn't a single index-bucketable value, or an op the index * can't serve) — the caller falls back to the scan. A binding that * never implements this hook always falls back, unchanged from before * this hook existed. */ indexProbe?(op: string, payload: unknown): unknown | undefined; /** * Optional: canonicalize a raw STORED field value into the bucket key an * eager index should use (#672) — lets `CollectionIndexes` group a * pre-declaration / non-canonical stored value (e.g. a money field's * leftover `'0100'` written before its `money()` declaration) under the * SAME key a canonical write produces (`'100'`), so the index-probe fast * path (`indexProbe` above) and the fallback scan (`evaluateClause`) * agree on which records match. `undefined` means "not mine / can't * canonicalize this value" — the caller buckets the raw stringified * value, unchanged from before this hook existed. MUST agree with what * this binding's own `evaluateClause`/`indexProbe` treat as equal, or * the fast path and the scan will disagree. */ canonicalizeIndexKey?(field: string, rawValue: unknown): string | undefined; /** Decode a raw stored record for query/scan results and callback views ('raw' — no virtuals). */ decodeResults?(record: unknown): unknown; /** Exact ordering for a covered field; undefined when the field is not covered. */ compareForOrder?(field: string, a: unknown, b: unknown): number | undefined; /** * Per-key, PER-CALL-locale label resolution for `orderBy(field, dir, * { by: 'label' })` (#650 Task 7) — the sibling `compareForOrder` above * structurally cannot serve, since it carries no locale parameter. * `locale` is the query's per-call locale (`undefined` for a locale-less * query — a binding may fall back to its own descriptor-level default, * mirroring `present`'s displayLocale hinge). `undefined` return = this * binding doesn't resolve a label for `key` at `field` — caller falls * back to the raw stored value, same graceful-degrade discipline every * other via query hook uses. SYNC (#553 — query participation). */ resolveOrderLabel?(field: string, key: string, locale: string | undefined): string | undefined; /** Rewrite an aggregate spec (money exact reducers). */ wrapReducers?(spec: unknown): unknown; /** `forget()`'s per-ref erasure door — shred/report this binding's residue for one record (classified/blob forget participation). */ erase?(ctx: ViaEraseCtx): Promise; describeFragment?(): Record; } /** Binder: constructs a binding from a collection's declared config. Installed by the feature's declaration factory. */ export type ViaBinder = (config: unknown) => ViaBinding; /** @internal — called (idempotently, first-wins) by a feature's declaration factory, e.g. money(). */ export declare function installViaBinder(brand: string, binder: ViaBinder): void; /** @internal — registry presence check. Used by tests (mirrors isMoneyEngineInstalled) and by vault's i18n validator delegators as a no-i18n-ever-declared fast path. */ export declare function isViaInstalled(brand: string): boolean; /** @internal — resolve a binder; throws when the declaration factory never ran (hand-rolled descriptors). */ export declare function viaBinder(brand: string): ViaBinder; export {};