/** * kernel/via/reconcile.ts — #664 the late-attach reconcile-path dispatch. * * `vault.ts`'s reconcile branch (a SECOND-OR-LATER `vault.collection(name, {...})` call on an * already-open collection) used to be a flat 5-branch `if (coll && X) coll._applyX(...)` ladder * with no collision guard and no i18n/dictKey machinery at all (i18nFields/dictKeyFields were * silently ignored on re-open — only the fresh-construction branch ever wired them). This module * moves that whole ladder OUT of `vault.ts` (a kernel-surface-ceiling-guarded file) into ONE * dispatch entry point, {@link reconcileViaAttach}, that: * * 1. runs the late-attach collision guard ({@link guardReconcileCollisions}, via/compose.ts) * BEFORE any mutation — both the incoming×incoming and existing×incoming recipes; * 2. routes the FIVE pre-existing reconciles (money/computed/fieldMeta/meta/classified) through * their unchanged `Collection._applyX` methods (collection.ts is NOT touched — this dispatch * only orchestrates, matching the pre-#664 call order exactly); * 3. commits the two-phase graph-edge reconcile (`via/graph-wiring.ts`) + taint overlay, exactly * as before; * 4. wires i18n/dictKey late-attach via {@link reconcileI18nFields}/{@link reconcileDictKeyFields} * — rebuilding the pipeline through {@link Collection._setVia} (#666's writer seam), mirroring * the fresh-construction wiring (`collection-config.ts#compileViaBindings`'s i18n slot + * `vault.ts`'s registry population) without duplicating it in a second place; * 5. (#664 Part 2b) wires lookup()/enumOf()/dict() late-attach via {@link reconcileLookupFields} * — tier-scoped: enum/static tiers are a clean, self-contained attach; the reserved (dict) * tier additionally updates the SAME vault registries the fresh path populates * (`dictKeyFieldRegistry`/`reservedLookupCollections`/`staticDescriptorByField`/`staticByName`/ * `staticDictNames`, via `collectLookupDictCompat`); the matrix (`backing:'collection'`) tier * is gated by {@link refuseUnattachableMatrixLookupFields} — refuses with a `ValidationError` * unless the backing collection is already open (this vault session) and prefetch-enabled. * * `ViaReconcileVaultCtx` takes the vault-resident registry Maps/closures the i18n/dictKey wiring * needs as a plain structural bag of ARGUMENTS (mirroring `via/lookup/registry.ts`'s own * "#650 Task 1" extraction pattern) — never a `Vault` import (this file is part of the kernel * spine's `port/with/`-only import discipline — S5 port-layering, `scripts/check-architecture.mjs` * `checkPortLayering`), so `vault.ts` passes `this` once its relevant fields are readable from * outside the class (see `Vault`'s field declarations). */ import type { Collection } from '../collection.js'; import { type HasWritableViaPipeline } from './pipeline.js'; import { type MergedViaFields } from './compose.js'; import type { ViaGraph } from './graph.js'; import { type I18nStrategy, type I18nTextDescriptor, type DictKeyDescriptor, type StaticDictDescriptor, type DictionaryHandle } from '../../port/with/i18n-strategy.js'; import { type LookupDescriptor } from '../../port/with/lookup-strategy.js'; type AnyCollection = Collection>; /** * Structural reconcile-capable collection handle: `_via`/`_setVia` (#666's writer seam) plus the * five pre-existing `_applyX` late-attach methods (#623/#629/#638). Every parameter type is * DERIVED off `Collection` itself via `Parameters<...>` rather than imported by name — several * (`FieldMeta`, `CollectionMeta`, `ComputedFields`) are with-* service types the kernel spine may * not statically import (S5 port-layering); deriving them off the already-typed `Collection` * method avoids a second, duplicate type declaration too (mirrors `via/graph-wiring.ts`'s * `ComputedFieldsParam` trick). */ export interface ReconcilableCollection extends HasWritableViaPipeline { _applyMoneyFields(moneyFields: Parameters[0]): void; _applyComputed(computed: Parameters[0]): void; _applyFieldMeta(fieldMeta: Parameters[0]): void; _applyMeta(meta: Parameters[0]): void; _applyClassifiedFields(classifiedFields: Parameters[0]): void; /** #671 items 1-3 — the reader + writer seam for `getDictionary`/legacy describe() lists/`presentForJoin`. */ readonly _viaFieldsSnapshot: AnyCollection['_viaFieldsSnapshot']; _reconcileReadState(patch: Parameters[0]): void; } /** The vault-resident state/closures the i18n/dictKey late-attach wiring reads and writes — * the SAME registries `vault.ts`'s fresh-construction branch populates (`i18nFieldRegistry` * :884-886, the dictKeyFields split :903-940, `dictLabelResolver`/`i18nPutValidator`/ * `autoTranslateHook` :1111-1155), passed as plain values (never `this: Vault`) so this module * never imports the `Vault` class. */ export interface ViaReconcileVaultCtx { readonly i18nStrategy: I18nStrategy; /** — named `locale`, not `defaultLocale`, to match `Vault`'s own field name 1:1 (so `this` * satisfies this interface structurally with zero adapter object at the call site). */ readonly locale: string | undefined; readonly translateText: ((text: string, from: string, to: string, field: string, collection: string) => Promise) | undefined; readonly i18nFieldRegistry: Map>; readonly dictKeyFieldRegistry: Map>; readonly staticDescriptorByField: Map>; readonly reservedLookupCollections: Map; readonly staticByName: Map; readonly staticDictNames: Set; dictionary(name: string): DictionaryHandle; enforceI18nOnPut(collectionName: string, record: unknown): void; enforceStaticDictOnPut(collectionName: string, record: unknown): void; /** #664 Part 2b — the matrix-tier lookup late-attach gate: "is this dimension already open in * this vault session" (never constructs a fresh collection) — `Vault._getCollection`. */ getOpenCollection(name: string): AnyCollection | undefined; /** #664 Part 2b — the vault's CONSTRUCTING collection accessor the lookup binding's lazy * closures (`getLookupBacking`/`membership`/`getAltIndex`/`snapshotFor`) read through — mirrors * `vault.ts`'s fresh-construct `(n) => this.collection>(n)` closure * verbatim. Only ever invoked, for a matrix-tier dimension, once {@link getOpenCollection}'s * gate has already confirmed it is open — never the door that opens it. */ getCollection(name: string): AnyCollection; } /** * #664 Part 2 — late-attach i18nText fields. First-wins (mirrors `_applyMoneyFields`'s own * first-wins convention): a no-op once the collection already has an 'i18n' binding, whether * that binding came from fresh construction or an earlier reconcile call. `dictKeyFields` is an * optional SECOND family arriving in the SAME `vault.collection()` call (`mergeViaFields` already * combined them for this one call) — passed through so ONE binding rebuild covers both, exactly * like `compileViaBindings` builds ONE 'i18n' binding from both maps at fresh construction. */ export declare function reconcileI18nFields(coll: ReconcilableCollection, vaultCtx: ViaReconcileVaultCtx, name: string, i18nFields: Record | undefined, dictKeyFields?: Record): void; /** * #664 Part 2 — late-attach dictKeyFields (dictKey()/staticDict()) with NO i18nFields in the same * call — the dictKey-only late-attach door. First-wins, same convention as * {@link reconcileI18nFields} (and, transitively, a no-op if that sibling function already * handled a combined i18nFields+dictKeyFields call for this collection). */ export declare function reconcileDictKeyFields(coll: ReconcilableCollection, vaultCtx: ViaReconcileVaultCtx, name: string, dictKeyFields: Record | undefined): void; /** * #664 Part 2b — late-attach lookup()/enumOf()/dict() fields, tier-scoped (issue #664, spec- * ratified tier policy): * * - **enum** (`backing:'static'`, no `table`) / **static** (`+table`) — self-contained: * membership/labels come from the declared `keys`/`table` alone, no vault registry touch, no * cross-collection read. Clean attach, same shape as i18n/dictKey's own reconcile door. * - **reserved** (`dict()` / `lookup(dim, { backing:'reserved' })`) — additionally registers into * the SAME vault registries the fresh path populates (`vault.ts`'s dictKeyFields/lookupFields * registry-population loop) — `dictKeyFieldRegistry`/`reservedLookupCollections` for a bare * dict field; `staticDescriptorByField`/`staticByName`/`staticDictNames` for a table-bearing * static field — via `collectLookupDictCompat` (#650 Task 2's alias-equivalence bridge, the * SAME helper the fresh path already calls), reused verbatim, not duplicated. * - **matrix** (`backing:'collection'`) — gated by {@link refuseUnattachableMatrixLookupFields}. * * First-wins (mirrors {@link reconcileI18nFields}): a no-op once the collection already has a * 'lookup' binding, whether from fresh construction or an earlier reconcile call. * * Post-attach, registers the SAME cross-collection `'ref'` graph edges the fresh path does * (`registerLookupRefEdges` — internally skips the static tier, which has no backing collection to * reference-check) — `ViaGraph.referencingEdgesOf` sees them immediately, so delete-time * restrict/cascade/nullify semantics are live post-attach too, same as a fresh declaration. */ export declare function reconcileLookupFields(coll: ReconcilableCollection, vaultCtx: ViaReconcileVaultCtx, graph: ViaGraph, name: string, lookupFields: Record | undefined): void; /** The late-attach reconcile call's field-declaring options — the slice of `vault.collection()`'s * `options` a reconcile call may carry, plus the `mergeViaFields` output that feeds both the * guard and the money/i18n/dictKey/lookup reconciles. `blobFields` has no `_apply*` door (guard * visibility only — an incoming×incoming collision with `blobFields` must still refuse, even * though a bare blobFields late-attach is otherwise silently inert, matching pre-#664 behavior). */ export interface ReconcileLateAttachPlan { readonly effectiveViaFields: MergedViaFields; readonly computed?: Parameters[0]; readonly fieldMeta?: Parameters[0]; readonly meta?: Parameters[0]; readonly classifiedFields?: Parameters[0]; readonly blobFields?: Record; } /** * #664 — the ONE late-attach reconcile dispatch `vault.ts` calls, replacing its former 5-branch * `_apply*` ladder. Order: guard (before any mutation) → the matrix-tier lookup gate (also before * any mutation — see below) → the five pre-existing reconciles, UNCHANGED call order/semantics → * graph-edge commit + taint overlay (unchanged) → i18n/dictKey → lookup (#664 Part 2b). */ export declare function reconcileViaAttach(coll: ReconcilableCollection, graph: ViaGraph, name: string, vaultCtx: ViaReconcileVaultCtx, plan: ReconcileLateAttachPlan): void; export {};