2026-08-03 13:27:36 -07:00
|
|
|
/**
|
|
|
|
|
* @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 {
|
2026-08-03 16:01:02 -07:00
|
|
|
const rec = entity as unknown as Record<string, unknown>
|
|
|
|
|
const bag =
|
|
|
|
|
rec.metadata && typeof rec.metadata === 'object'
|
|
|
|
|
? (rec.metadata as Record<string, unknown>)
|
|
|
|
|
: null
|
|
|
|
|
|
2026-08-03 13:27:36 -07:00
|
|
|
if (address.scope === 'system') {
|
feat(namespace): NO SPECIAL NAMES + storage fidelity — the ruled completion of the field-addressing law
The write side of the law, ruled 2026-08-03: data is either in main space
where developers can use anything, or it is in system.*.
- The reserved-name write door DIES: add/update/relate/updateRelation
metadata bags accept EVERY name (confidence, type, id, data, level,
content, ...) as ordinary user fields — indexed, filterable, sortable,
aggregatable, identical to any other field. The remap/enforce/warn
machinery, the reservedFieldPolicy config (now a typed init refusal),
and the compile-time metadata key bans are all removed. The one write
refusal left: keys spelled 'system.*' (namespace forgery), now enforced
on all four write doors.
- STORED RECORDS GO NESTED (v2): engine fields top-level, the user bag
nested verbatim under 'metadata', sealed by a format stamp — by-name
storage discrimination is unsound once colliders are admitted. Legacy
flat records stay readable forever through the shape-aware splitters
(sound for them: the old door refused colliders). Time travel rides the
same split (generation store snapshots whole records).
- Name-based index exclusions DIE: user frame indexes every name; the
excludeFields/indexedFields knobs and their silent-[] holes are gone;
bulk-payload protection is value-shape only, uniform across names.
- Consumer-sweep findings fixed in the same wave: per-type counts read
the frozen 'system.type' column (addToIndex sort, affinity tracking,
cold-count rehydration, VFS type bitmaps — legacy 'noun' fallback for
pre-rebuild reads); resolveHiddenIds addresses 'system.visibility'
(bare 'visibility' was a silent no-op under the law — VFS/system
entities leaked into default reads).
- Fidelity fallout fixed in the owning layers: readEntityFieldAddress
reads the bag first (colliders were absent-shadowed by its own guard)
and never serves system addresses from the bag; blob history refs read
the bag shape-aware; migration transforms now receive ONE normalized
view (engine fields + nested bag) regardless of stored era, and stray
flat-habit keys refuse with the fix in the message.
- THE REOPEN-COLLIDER CONFORMANCE CASE (required before any RC counts as
gates-green): all ten collider names + plumbing names written as user
fields, verified verbatim + queryable across live reads, flush+reopen,
a forced epoch rebuild, and asOf time travel; relation mirror; forgery
refusals; legacy flat-record compat. 8/8 green.
Gates: unit 1901/1901 (exit 0) · integration 758 (exit 0) · conformance
27/27 (exit 0) · consumer test sweep migrated (10 files).
2026-08-03 16:59:13 -07:00
|
|
|
// 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`.
|
2026-08-03 16:01:02 -07:00
|
|
|
const top = rec[address.field]
|
|
|
|
|
if (top !== undefined) return top
|
feat(namespace): NO SPECIAL NAMES + storage fidelity — the ruled completion of the field-addressing law
The write side of the law, ruled 2026-08-03: data is either in main space
where developers can use anything, or it is in system.*.
- The reserved-name write door DIES: add/update/relate/updateRelation
metadata bags accept EVERY name (confidence, type, id, data, level,
content, ...) as ordinary user fields — indexed, filterable, sortable,
aggregatable, identical to any other field. The remap/enforce/warn
machinery, the reservedFieldPolicy config (now a typed init refusal),
and the compile-time metadata key bans are all removed. The one write
refusal left: keys spelled 'system.*' (namespace forgery), now enforced
on all four write doors.
- STORED RECORDS GO NESTED (v2): engine fields top-level, the user bag
nested verbatim under 'metadata', sealed by a format stamp — by-name
storage discrimination is unsound once colliders are admitted. Legacy
flat records stay readable forever through the shape-aware splitters
(sound for them: the old door refused colliders). Time travel rides the
same split (generation store snapshots whole records).
- Name-based index exclusions DIE: user frame indexes every name; the
excludeFields/indexedFields knobs and their silent-[] holes are gone;
bulk-payload protection is value-shape only, uniform across names.
- Consumer-sweep findings fixed in the same wave: per-type counts read
the frozen 'system.type' column (addToIndex sort, affinity tracking,
cold-count rehydration, VFS type bitmaps — legacy 'noun' fallback for
pre-rebuild reads); resolveHiddenIds addresses 'system.visibility'
(bare 'visibility' was a silent no-op under the law — VFS/system
entities leaked into default reads).
- Fidelity fallout fixed in the owning layers: readEntityFieldAddress
reads the bag first (colliders were absent-shadowed by its own guard)
and never serves system addresses from the bag; blob history refs read
the bag shape-aware; migration transforms now receive ONE normalized
view (engine fields + nested bag) regardless of stored era, and stray
flat-habit keys refuse with the fix in the message.
- THE REOPEN-COLLIDER CONFORMANCE CASE (required before any RC counts as
gates-green): all ten collider names + plumbing names written as user
fields, verified verbatim + queryable across live reads, flush+reopen,
a forced epoch rebuild, and asOf time travel; relation mirror; forgery
refusals; legacy flat-record compat. 8/8 green.
Gates: unit 1901/1901 (exit 0) · integration 758 (exit 0) · conformance
27/27 (exit 0) · consumer test sweep migrated (10 files).
2026-08-03 16:59:13 -07:00
|
|
|
if (address.field === 'type') return rec.noun
|
2026-08-03 16:01:02 -07:00
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
feat(namespace): NO SPECIAL NAMES + storage fidelity — the ruled completion of the field-addressing law
The write side of the law, ruled 2026-08-03: data is either in main space
where developers can use anything, or it is in system.*.
- The reserved-name write door DIES: add/update/relate/updateRelation
metadata bags accept EVERY name (confidence, type, id, data, level,
content, ...) as ordinary user fields — indexed, filterable, sortable,
aggregatable, identical to any other field. The remap/enforce/warn
machinery, the reservedFieldPolicy config (now a typed init refusal),
and the compile-time metadata key bans are all removed. The one write
refusal left: keys spelled 'system.*' (namespace forgery), now enforced
on all four write doors.
- STORED RECORDS GO NESTED (v2): engine fields top-level, the user bag
nested verbatim under 'metadata', sealed by a format stamp — by-name
storage discrimination is unsound once colliders are admitted. Legacy
flat records stay readable forever through the shape-aware splitters
(sound for them: the old door refused colliders). Time travel rides the
same split (generation store snapshots whole records).
- Name-based index exclusions DIE: user frame indexes every name; the
excludeFields/indexedFields knobs and their silent-[] holes are gone;
bulk-payload protection is value-shape only, uniform across names.
- Consumer-sweep findings fixed in the same wave: per-type counts read
the frozen 'system.type' column (addToIndex sort, affinity tracking,
cold-count rehydration, VFS type bitmaps — legacy 'noun' fallback for
pre-rebuild reads); resolveHiddenIds addresses 'system.visibility'
(bare 'visibility' was a silent no-op under the law — VFS/system
entities leaked into default reads).
- Fidelity fallout fixed in the owning layers: readEntityFieldAddress
reads the bag first (colliders were absent-shadowed by its own guard)
and never serves system addresses from the bag; blob history refs read
the bag shape-aware; migration transforms now receive ONE normalized
view (engine fields + nested bag) regardless of stored era, and stray
flat-habit keys refuse with the fix in the message.
- THE REOPEN-COLLIDER CONFORMANCE CASE (required before any RC counts as
gates-green): all ten collider names + plumbing names written as user
fields, verified verbatim + queryable across live reads, flush+reopen,
a forced epoch rebuild, and asOf time travel; relation mirror; forgery
refusals; legacy flat-record compat. 8/8 green.
Gates: unit 1901/1901 (exit 0) · integration 758 (exit 0) · conformance
27/27 (exit 0) · consumer test sweep migrated (10 files).
2026-08-03 16:59:13 -07:00
|
|
|
// 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.
|
2026-08-03 16:01:02 -07:00
|
|
|
if (
|
|
|
|
|
SYSTEM_ENTITY_SCALARS.has(address.field) ||
|
|
|
|
|
PLUMBING_FIELDS.has(address.field) ||
|
|
|
|
|
address.field === 'noun'
|
|
|
|
|
) {
|
|
|
|
|
return undefined
|
2026-08-03 13:27:36 -07:00
|
|
|
}
|
2026-08-03 16:01:02 -07:00
|
|
|
return rec[address.field]
|
2026-08-03 13:27:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 ` +
|
2026-08-03 16:07:27 -07:00
|
|
|
`field exists, or check the field name (system.${raw} is NOT valid — '${raw}' is ` +
|
|
|
|
|
`not one of the engine's system scalars).`
|
2026-08-03 13:27:36 -07:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-03 16:01:02 -07:00
|
|
|
/**
|
|
|
|
|
* @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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-03 13:27:36 -07:00
|
|
|
/**
|
|
|
|
|
* @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.
|
|
|
|
|
*/
|
2026-08-03 16:01:02 -07:00
|
|
|
export class InvalidFieldAddressError extends UnresolvableFieldError {
|
2026-08-03 13:27:36 -07:00
|
|
|
constructor(raw: string, kind: FieldAddressKind, systemMap: ReadonlySet<string>) {
|
|
|
|
|
const valid = [...systemMap].map((f) => `system.${f}`).join(', ')
|
|
|
|
|
super(
|
2026-08-03 16:01:02 -07:00
|
|
|
raw,
|
|
|
|
|
kind,
|
2026-08-03 13:27:36 -07:00
|
|
|
`'${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'
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-03 15:51:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-03 15:53:13 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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'
|