/** * Field-policy contract for the catalog plane. * * Every field on every CatalogEntry, in every vertical, is declared with a * `FieldPolicy` row. The contract has 12 attributes (one path identifier plus * 11 governance attributes) and is intentionally flat and serializable. * * See `docs/architecture/catalog-architecture.md` §4 for the full design. */ /** * The five field classes. Determines how a field is treated by the overlay * resolver, the indexer, and the drift detector. * * - `managed` — not user-overrideable; covers external-source-fed * AND system-managed identity fields * - `structural` — overrideable with constraints; drives search facets * - `merchandisable` — freely overrideable copy, media, marketing fields * - `volatile-indexed` — cached projection with TTL; safe to be slightly stale * - `volatile-live` — never indexed as a live value; always fetched on demand */ export type FieldClass = "managed" | "structural" | "merchandisable" | "volatile-indexed" | "volatile-live"; /** * How an overlay merges with the source projection at read time. * * - `source-only` — overlay forbidden even if the class would allow it * - `replace` — override fully replaces the source value * - `additive-set` — override unions with the source (set semantics, e.g. tags) * - `additive-list` — override appends to the source list * - `list-position` — sparse override per list position (e.g. gallery[2].caption) */ export type MergeRule = "source-only" | "replace" | "additive-set" | "additive-list" | "list-position"; /** * Severity for source-side drift events. Drives notification routing and, * for `critical`, blocks new bookings on the affected entity until ops * acknowledges. */ export type DriftSeverity = "none" | "low" | "medium" | "high" | "critical"; /** * What scope of search-index documents to re-enqueue when this field * mutates. * * - `none` — never reindex on this field's changes * - `entry` — reindex this entity across all variant slices * - `entry-locale` — reindex this entity for the affected locale only * - `facet-affecting` — reindex this entity AND propagate facet aggregations * - `global` — bulk reindex required (rare; identity changes) */ export type ReindexScope = "none" | "entry" | "entry-locale" | "facet-affecting" | "global"; /** * When this field's value should be captured into the booking snapshot. * * - `never` — never snapshotted * - `on-quote` — captured at quote/offer creation * - `on-book` — captured at booking commit * - `on-quote-and-book` — captured at both */ export type SnapshotMode = "never" | "on-quote" | "on-book" | "on-quote-and-book"; /** * Where this field's value lives for query purposes. * * - `blob-only` — stored, not indexed; only retrievable as part of the row * - `indexed-column` — indexed in the search engine for filter/sort/facet * - `first-class-table` — promoted to its own column or table for SQL queries */ export type Queryability = "blob-only" | "indexed-column" | "first-class-table"; /** * Visibility audiences, aligned with the Voyant `Actor` type. A field is * visible to an actor only if their audience is in this set. * * Default deployments use only `staff` and `customer`. Scale-stage deployments * add `partner` and `supplier` as the operator's surface area grows. */ export type Visibility = "staff" | "customer" | "partner" | "supplier"; /** * Which editor role can write overlays for this field. `none` means the field * is not user-editable through the overlay store regardless of class. */ export type EditRole = "none" | "marketing" | "ops" | "finance" | "admin"; /** * UX-level friction applied to overlay writes on this field. The catalog * plane stores this as policy; the consuming surface (admin UI, CMS plugin) * is responsible for rendering the friction. * * - `none` — save-and-go * - `confirm` — confirmation dialog before commit * - `approval` — staged write requiring sign-off before going live */ export type OverrideFriction = "none" | "confirm" | "approval"; /** * How the source side of this field refreshes. `null` means the field has no * source side at all (purely editorial). * * - `sync` — periodic batch pull from source * - `event` — push from source via webhook * - `request` — fetched on read (live API call) * - `static` — set once at creation, never refreshes (system-managed identity) * - `null` — no source side at all (purely editorial fields) */ export type SourceFreshness = "sync" | "event" | "request" | "static" | null; /** * The 12-attribute governance contract for a single CatalogEntry field. * * Path syntax: * - `field` * - `field.subfield` * - `list[]` * - `list[].field` * - `list[].nested.field` * * Inheritance (when a leaf is declared without explicit values for some axes): * - Non-inheriting (must be explicit per leaf): * `class`, `merge`, `editRole`, `overrideFriction`, `snapshot` * - Inheriting (default to nearest declared ancestor): * `drift`, `reindex`, `query`, `localized`, `visibility`, `sourceFreshness` */ export interface FieldPolicy { /** Dotted path; supports `gallery[]`, `geography.countries[].name`. */ path: string; class: FieldClass; merge: MergeRule; drift: DriftSeverity; reindex: ReindexScope; snapshot: SnapshotMode; query: Queryability; /** Whether this field has per-locale variants. */ localized: boolean; /** Audiences that can see this field. Multiple values: a set, not an enum. */ visibility: Visibility[]; editRole: EditRole; overrideFriction: OverrideFriction; sourceFreshness: SourceFreshness; } /** * A partial field-policy declaration: every axis is optional except `path` * and `class`. Inheriting axes default to the nearest declared ancestor; * non-inheriting axes that are missing trigger a build-time error. */ export type FieldPolicyInput = { path: string; } & Partial> & Pick; /** * Error raised when the field-policy registry has structural problems — * duplicate paths, missing non-inheriting axes, malformed paths. */ export declare class FieldPolicyError extends Error { readonly path?: string | undefined; constructor(message: string, path?: string | undefined); } /** * Resolves a list of partial policy declarations into fully-specified * `FieldPolicy` rows by applying per-axis inheritance from declared ancestors. * * Non-inheriting axes (class, merge, editRole, overrideFriction, snapshot) * must be present on every leaf; missing ones throw `FieldPolicyError`. * * Inheriting axes (drift, reindex, query, localized, visibility, * sourceFreshness) fall back to the nearest declared ancestor along the * path tree. If no ancestor declares them, defaults from `INHERITING_DEFAULTS` * apply. */ export declare function defineFieldPolicy(inputs: FieldPolicyInput[]): FieldPolicy[]; /** * Returns the ancestor paths of a given path, ordered from nearest to furthest. * * Examples: * "gallery[].caption" → ["gallery[]"] * "geography.countries[].name" → ["geography.countries[]", "geography"] * "title" → [] */ export declare function ancestorPaths(path: string): string[]; /** * Indexed view of a field-policy registry. Constructed once per vertical at * load time; consumed by the resolver, indexer, and drift detector. */ export interface FieldPolicyRegistry { policies: ReadonlyArray; byPath: ReadonlyMap; /** * Returns the most-specific policy whose path matches `lookupPath`. Path * precedence: exact match wins; element path beats collection path; * collection path beats ancestor object path; finally, fallback to parent. */ resolve(lookupPath: string): FieldPolicy | undefined; } export declare function createFieldPolicyRegistry(policies: FieldPolicy[]): FieldPolicyRegistry;