feat(namespace): the one field-addressing law as a single source of truth — parseFieldAddress + the ruled ten-scalar system maps + plumbing invisibility + refusal builders (module only; query surfaces wire in next)
This commit is contained in:
parent
f6b14d21c0
commit
8f9a9989e9
1 changed files with 246 additions and 0 deletions
246
src/db/fieldAddressing.ts
Normal file
246
src/db/fieldAddressing.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* @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.<field>` 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.<field>` 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<string> = 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<string> = 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<string> = 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 {
|
||||
if (address.scope === 'system') {
|
||||
return (entity as unknown as Record<string, unknown>)[address.field]
|
||||
}
|
||||
return entity.metadata?.[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<string, unknown>
|
||||
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.`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Refusal for a malformed or out-of-map field ADDRESS —
|
||||
* `system.<anything-not-in-the-map>` (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 Error {
|
||||
public readonly raw: string
|
||||
public readonly kind: FieldAddressKind
|
||||
|
||||
constructor(raw: string, kind: FieldAddressKind, systemMap: ReadonlySet<string>) {
|
||||
const valid = [...systemMap].map((f) => `system.${f}`).join(', ')
|
||||
super(
|
||||
`'${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'
|
||||
this.raw = raw
|
||||
this.kind = kind
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue