/** * @module db/fieldAddressing * @description The one field-addressing law for every query surface (find()'s * `where` / `orderBy` / `groupBy`, aggregation `source.where`), ruled * 2026-08-03 after a production incident in which a user metadata field * named `level` was silently shadowed by the engine's internal HNSW node * layer (VENUE-BRAINY-ORDERBY-NOOP — thread id kept verbatim as the audit * key; it names no product): * * 1. A BARE field name addresses the user's metadata field. Always. * No priority resolution, no fallback chain — `orderBy: 'level'` * reads `entity.metadata.level`, full stop. * 2. `system.` addresses an engine scalar, reachable ONLY with the * explicit prefix. The entity map is exactly ten scalars; the relation * map mirrors it with `verb`/`sourceId`/`targetId` as the structural * members. * 3. Engine plumbing (`vector`, `connections`, `level`, `data`, `_rev`) is * INVISIBLE to the query surface in either spelling — `system.level` * refuses; bare `level` is the user's field. * 4. `metadata.` is the explicit spelling of the bare form — * identical semantics on every path. * 5. Anything unresolvable refuses with a TYPED error naming both * candidate spellings — an accepted name either works or refuses; * there is no third state. * * This module is the SINGLE source of truth for the law: parsing, the maps, * and the refusal builders live here so the JS engine, the provider seams, * and the cross-engine conformance suite can never drift on the contract. */ import type { HNSWNounWithMetadata, HNSWVerbWithMetadata } from '../coreTypes.js' /** * @description The entity-side `system.*` map — EXACTLY the ten engine * scalars David ruled queryable (2026-08-03). Adding a name here is a * cross-engine contract change: the native accelerator's conformance suite * pins this list verbatim, so any edit must ship as a paired release. */ export const SYSTEM_ENTITY_SCALARS: ReadonlySet = new Set([ 'id', 'type', 'subtype', 'createdAt', 'updatedAt', 'confidence', 'weight', 'visibility', 'service', 'createdBy' ]) /** * @description The relation-side `system.*` map — the verb mirror of * {@link SYSTEM_ENTITY_SCALARS}: `verb`, `sourceId`, `targetId` are the * structural members beside the eight shared scalars. Same one law, same * pairing rule for edits. */ export const SYSTEM_RELATION_SCALARS: ReadonlySet = new Set([ 'verb', 'sourceId', 'targetId', 'subtype', 'createdAt', 'updatedAt', 'confidence', 'weight', 'visibility', 'service', 'createdBy' ]) /** * @description Engine plumbing — never addressable from the query surface in * ANY spelling. `level` is the HNSW node layer (the incident field: listing * it as resolvable shadowed real user data); `data` is the payload container, * not a scalar — content is reached through the content/text-search APIs, * and addressing it as a sortable field would lie about its shape. */ export const PLUMBING_FIELDS: ReadonlySet = new Set([ 'vector', 'connections', 'level', 'data', '_rev' ]) /** @description Which record kind a field address is being resolved against. */ export type FieldAddressKind = 'entity' | 'relation' /** * @description A parsed, law-valid field address. `scope` says which side of * the record the name lives on; `field` is the unprefixed name to read. */ export interface FieldAddress { /** 'metadata' = the user's field (bare or `metadata.`-prefixed); 'system' = an engine scalar. */ scope: 'metadata' | 'system' /** The field name with any scope prefix removed. */ field: string /** The exact spelling the caller used — preserved for error text and telemetry. */ raw: string } /** * Parse a query-surface field name under the one law. Pure and data-blind: * this validates the ADDRESS (spelling + map membership), not whether any * row actually carries the field — data-aware refusals (the did-you-mean * for a bare system-scalar name no row carries) belong to the query layer, * which calls {@link buildUnresolvableMessage} with index knowledge. * * @param raw - The field name as the caller wrote it (`level`, * `metadata.level`, `system.createdAt`, …) * @param kind - Entity or relation resolution (selects the system map) * @returns The parsed {@link FieldAddress} * @throws {InvalidFieldAddressError} for a `system.*` name outside the ruled * map (including every plumbing field) or a malformed spelling — the error * text enumerates the valid system scalars so the fix is in the message. * * @example * parseFieldAddress('level', 'entity') // { scope: 'metadata', field: 'level' } * parseFieldAddress('metadata.level', 'entity') // { scope: 'metadata', field: 'level' } * parseFieldAddress('system.createdAt', 'entity') // { scope: 'system', field: 'createdAt' } * parseFieldAddress('system.level', 'entity') // throws — plumbing is invisible */ export function parseFieldAddress( raw: string, kind: FieldAddressKind ): FieldAddress { const systemMap = kind === 'entity' ? SYSTEM_ENTITY_SCALARS : SYSTEM_RELATION_SCALARS if (raw.startsWith('system.')) { const field = raw.slice('system.'.length) if (!systemMap.has(field)) { throw new InvalidFieldAddressError(raw, kind, systemMap) } return { scope: 'system', field, raw } } if (raw.startsWith('metadata.')) { const field = raw.slice('metadata.'.length) if (field.length === 0) { throw new InvalidFieldAddressError(raw, kind, systemMap) } return { scope: 'metadata', field, raw } } if (raw.length === 0) { throw new InvalidFieldAddressError(raw, kind, systemMap) } // Bare name = the user's metadata field. Always. Even when the same name // exists in the system map — `confidence` as a bare name is the user's // metadata field named confidence; the engine scalar is system.confidence. return { scope: 'metadata', field: raw, raw } } /** * Read the addressed value off an entity. The ONLY sanctioned way a query * surface turns a {@link FieldAddress} into a value — direct property reads * against records re-create the shadow class this module exists to kill. * * @returns The value, or `undefined` when the record does not carry it * (missing values sort LAST in both directions per the ordering contract — * they are never grounds for dropping a row). */ export function readEntityFieldAddress( entity: HNSWNounWithMetadata, address: FieldAddress ): unknown { const rec = entity as unknown as Record const bag = rec.metadata && typeof rec.metadata === 'object' ? (rec.metadata as Record) : null if (address.scope === 'system') { // System scalars live at the record's top level, NEVER in the user's // bag — a user field named `confidence` must be unreachable from // system.confidence (and vice versa). Entity views carry the scalars // top-level directly; record-derived views spell the type `noun`. const top = rec[address.field] if (top !== undefined) return top if (address.field === 'type') return rec.noun return undefined } // User scope: the bag IS the user's namespace, authoritative — EVERY name // reads from it, engine spellings included (`bag.confidence` is the user's // confidence field under the field-addressing law). if (bag) return bag[address.field] // No bag at all: a LEGACY flat record (pre-nested-bag storage). Its keys // matching system/plumbing names are the ENGINE's — the pre-law write door // refused user colliders — so a bare system name reads as ABSENT rather // than resurrecting the shadow this module exists to kill. Same for the // legacy 'noun' spelling. if ( SYSTEM_ENTITY_SCALARS.has(address.field) || PLUMBING_FIELDS.has(address.field) || address.field === 'noun' ) { return undefined } return rec[address.field] } /** * Relation twin of {@link readEntityFieldAddress}. The stored flat record * keys the relation type under `verb`; public Relation shapes may carry it * as `type` — both spellings of the record are read, the ADDRESS is always * `system.verb`. */ export function readRelationFieldAddress( verb: HNSWVerbWithMetadata, address: FieldAddress ): unknown { if (address.scope === 'system') { const rec = verb as unknown as Record if (address.field === 'verb') return rec.verb ?? rec.type return rec[address.field] } return verb.metadata?.[address.field] } /** * Build the ruled did-you-mean refusal text for a bare name that resolved to * metadata but is UNKNOWN to the index — the data-aware half of the law, * called by the query layer once it has consulted the known-field set: * * "no metadata field 'createdAt' — did you mean system.createdAt or * metadata.createdAt?" * * When the bare name is NOT a system scalar the system candidate is omitted * (there is only one thing the caller could have meant; the refusal exists * because refusing beats silently sorting nothing). */ export function buildUnresolvableMessage( raw: string, kind: FieldAddressKind ): string { const systemMap = kind === 'entity' ? SYSTEM_ENTITY_SCALARS : SYSTEM_RELATION_SCALARS if (systemMap.has(raw)) { return ( `no metadata field '${raw}' — did you mean system.${raw} or metadata.${raw}? ` + `(bare names always address your metadata; engine fields need the system. prefix)` ) } return ( `no metadata field '${raw}' on this store — nothing carries it, so an ordered or ` + `filtered read against it cannot mean anything. Spell it metadata.${raw} once the ` + `field exists, or check the field name (system.${raw} is NOT valid — '${raw}' is ` + `not one of the engine's system scalars).` ) } /** * @description Refusal for a syntactically valid address that resolves to * NOTHING — a bare name no user field carries. Carries the did-you-mean * (both candidate spellings when the name collides with a system scalar) so * the fix ships inside the error. Thrown by the query layer with index * knowledge, never by the pure parser. */ export class UnresolvableFieldError extends Error { public readonly raw: string public readonly kind: FieldAddressKind constructor(raw: string, kind: FieldAddressKind, messageOverride?: string) { super(messageOverride ?? buildUnresolvableMessage(raw, kind)) this.name = 'UnresolvableFieldError' this.raw = raw this.kind = kind } } /** * @description Refusal for a malformed or out-of-map field ADDRESS — * `system.` (including all plumbing), an empty * name, or a bare `metadata.` prefix. The message carries the full valid * system map so the fix never needs a docs lookup. */ export class InvalidFieldAddressError extends UnresolvableFieldError { constructor(raw: string, kind: FieldAddressKind, systemMap: ReadonlySet) { const valid = [...systemMap].map((f) => `system.${f}`).join(', ') super( raw, kind, `'${raw}' is not an addressable ${kind} field. Bare names address your own ` + `metadata fields; engine fields are exactly: ${valid}. Engine plumbing ` + `(vector, connections, level, data, _rev) is not part of the query surface.` ) this.name = 'InvalidFieldAddressError' } } /** * @description Refusal for a find() option that is accepted by the type * surface but NOT implemented — an accepted option must work or refuse; * accepted-and-ignored died as a class (sealed 2026-08-03). Names the * option and the honest state so nobody discovers a no-op by measurement. */ export class UnsupportedFindOptionError extends Error { public readonly option: string constructor(option: string) { super( `find() option '${option}' is not implemented — it used to be silently ` + `ignored, which read as working. Remove it from the call (or track the ` + `feature request); it will be honored or refused, never swallowed.` ) this.name = 'UnsupportedFindOptionError' this.option = option } } /** * @description The capability signal both engines' conformance suites arm on * (never a version guess): its presence at the package root means the one * field-addressing law is LIVE on every query surface — bare = user metadata, * `system.*` = the ruled scalars, plumbing invisible, refusals typed. */ export const FIELD_ADDRESSING_CAPABILITY = 'field-addressing/v1'