From fcb24ab627a63e69df0286ea77d1522df32ba2fc Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 3 Aug 2026 15:11:24 -0700 Subject: [PATCH] =?UTF-8?q?docs(namespace):=20the=20d.ts=20JSDoc=20wave=20?= =?UTF-8?q?=E2=80=94=20the=20sealed=20field-addressing=20law=20on=20the=20?= =?UTF-8?q?full=20find=20+=20aggregation=20surface,=20present-tense,=20wit?= =?UTF-8?q?h=20the=20refusal=20semantics=20and=20migration=20note=20inline?= =?UTF-8?q?=20(comment-only;=20verified=20zero=20code=20lines=20changed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/brainy.types.ts | 123 ++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 17 deletions(-) diff --git a/src/types/brainy.types.ts b/src/types/brainy.types.ts index 89be78b9..6c133cf0 100644 --- a/src/types/brainy.types.ts +++ b/src/types/brainy.types.ts @@ -498,6 +498,43 @@ export interface UpdateRelationParams { * - **Graph:** `connected` for relationship traversal (via GraphAdjacencyIndex) * * See also: [Query Operators](../../docs/QUERY_OPERATORS.md) for all `where` operators. + * + * @remarks + * **Field-addressing law.** Governs every query-surface field name — `where` + * and `orderBy` on this interface, plus `AggregateSource.where` and + * `AggregateDefinition.groupBy` in the aggregation engine: + * + * 1. A bare name (e.g. `'level'`, `'rank'`, `'score'`) always means the + * caller's own metadata field — it reads `entity.metadata.`. There + * is no fallback to an engine-internal field of the same name and no + * priority resolution between the two; metadata wins unconditionally. + * 2. `system.` reaches an engine scalar, explicitly, and only for + * these ten: `id`, `type`, `subtype`, `createdAt`, `updatedAt`, + * `confidence`, `weight`, `visibility`, `service`, `createdBy`. + * 3. `vector`, `connections`, `level` (the engine-internal node field — a + * different thing from a user metadata field also named `level`), + * `data`, and `_rev` are invisible plumbing: neither spelling can + * address them from a query surface. + * 4. `metadata.` is the explicit spelling of the bare form and means + * exactly the same thing as rule 1. + * 5. A name that matches none of the above — most often a bare name that + * collides with one of the ten system-scalar names in rule 2 — REFUSES + * with a typed {@link UnresolvableFieldError} naming both candidates, + * e.g. `no metadata field 'createdAt' — did you mean system.createdAt or + * metadata.createdAt?`. The same loud-refusal principle covers whole + * options: the previously accepted-and-silently-ignored `cursor`, + * `includeRelations`, and `writeOnly` now throw + * {@link UnsupportedFindOptionError} instead of doing nothing. + * 6. **Ordering contract** (identical on the pure-JS engine and the native + * accelerator): rows missing or `null` on the `orderBy` field sort LAST + * in BOTH `asc` and `desc` order and are never dropped from the result; + * ties break by `id` ascending. + * + * Migration note: a call site written against the old rule — e.g. + * `orderBy: 'createdAt'` or `where: { visibility: 'internal' }` meaning the + * engine scalar — now refuses instead of silently reading the wrong field. + * The thrown error names the exact fix (`system.createdAt`). A loud + * refusal with the fix in hand beats a silent behavior flip. */ export interface FindParams { // Vector Intelligence @@ -516,7 +553,18 @@ export interface FindParams { * `{ exists: true }`, `{ missing: true }`) use `where: { subtype: { …operators… } }`. */ subtype?: string | string[] - /** Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`) */ + /** + * Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`). + * Field names follow the field-addressing law — see the `@remarks` on + * {@link FindParams}: a bare key is always the caller's metadata field; + * an engine scalar needs the explicit `system.` form. + * + * @example + * ```typescript + * await brain.find({ where: { level: { greaterThan: 5 } } }) // metadata.level + * await brain.find({ where: { 'system.visibility': 'internal' } }) // engine scalar + * ``` + */ where?: Partial // Visibility @@ -548,29 +596,49 @@ export interface FindParams { // Control options limit?: number // Max results (default: 10) offset?: number // Skip N results + /** + * @deprecated Not implemented. Passing `cursor` throws + * {@link UnsupportedFindOptionError} — it used to be accepted and + * silently ignored, which masked that no cursor pagination ever ran. Use + * `offset` / `limit` until cursor pagination ships. + */ cursor?: string // Cursor-based pagination // Sorting /** - * Field to sort by. User metadata fields sort by their stored values — - * including natural names like `level`, `rank`, or `score` (an engine-internal - * field can never shadow your metadata; fixed 2026-08 after a production - * report). System timestamps (`createdAt`, `updatedAt`) sort by entity age. + * Field to sort by. Follows the field-addressing law (see the `@remarks` + * on {@link FindParams}): a bare name (`'level'`, `'rank'`, `'score'`, …) + * always sorts by that metadata field; the ten engine scalars sort only + * via the explicit `system.` form (e.g. `'system.createdAt'`); a + * name that resolves to neither throws {@link UnresolvableFieldError} + * naming the fix. * * Ordering contract (identical on the pure-JS engine and the native - * accelerator): entities missing the field sort LAST in both directions — - * they are never dropped from the result; ties break deterministically. + * accelerator): rows missing or `null` on this field sort LAST in BOTH + * `asc` and `desc` order and are never dropped from the result; ties + * break by `id` ascending. * - * NOTE — the field-addressing law is changing (announced 2026-08): bare - * names will mean user metadata ALWAYS, and system fields will be reached - * explicitly as `system.` (e.g. `system.createdAt`), with typed - * refusals for unresolvable names. Until that release, bare `createdAt` - * and friends keep resolving to the system fields as documented above. + * @example + * ```typescript + * await brain.find({ orderBy: 'level', order: 'desc' }) // metadata.level + * await brain.find({ orderBy: 'system.createdAt', order: 'desc' }) // engine scalar + * ``` */ orderBy?: string + /** + * Sort direction: `'asc'` (default) or `'desc'`. Per the ordering + * contract on `orderBy`, rows missing/`null` on the sorted field sort + * LAST in both directions — `order` never moves them to the front. + */ order?: 'asc' | 'desc' // Sort direction: 'asc' (default) or 'desc' // Advanced options + /** + * @deprecated Not implemented. Passing `includeRelations` throws + * {@link UnsupportedFindOptionError} — it used to be accepted and + * silently ignored, so no relationships were ever attached. Fetch + * relationships separately via `brain.related()`. + */ includeRelations?: boolean // Include entity relationships excludeVFS?: boolean // Exclude VFS entities from results (default: false - VFS included) service?: string // Multi-tenancy filter @@ -603,6 +671,11 @@ export interface FindParams { } // Performance options + /** + * @deprecated Not implemented. Passing `writeOnly` throws + * {@link UnsupportedFindOptionError} — it used to be accepted and + * silently ignored, so validation was never actually skipped. + */ writeOnly?: boolean // Skip validation for high-speed ingestion // Aggregation @@ -1352,7 +1425,10 @@ export type GroupByDimension = export interface AggregateSource { /** Filter by entity type(s) */ type?: NounType | NounType[] - /** Metadata filter (same syntax as find({ where })) */ + /** + * Metadata filter — same syntax and field-addressing law as find()'s + * `where` (see the `@remarks` on {@link FindParams}). + */ where?: Record /** Multi-tenancy service filter */ service?: string @@ -1366,7 +1442,11 @@ export interface AggregateDefinition { name: string /** Which entities contribute to this aggregate */ source: AggregateSource - /** Dimensions to group by */ + /** + * Dimensions to group by — field names follow the same field-addressing + * law as find()'s `where` / `orderBy` (see the `@remarks` on + * {@link FindParams}). + */ groupBy: GroupByDimension[] /** Named metrics to compute */ metrics: Record @@ -1425,16 +1505,25 @@ export interface AggregateGroupState { export interface AggregateQueryParams { /** Name of the aggregate to query */ name: string - /** Filter aggregate groups by their key values */ + /** + * Filter aggregate groups by their key values — same field-addressing + * law as find() (see the `@remarks` on {@link FindParams}). + */ where?: Record /** * Filter groups by their computed METRIC values (SQL HAVING). Same BFO operators as * `where`, but applied to the derived metric results plus `count`, e.g. * `{ revenue: { greaterThan: 1000 } }`. Evaluated per group (O(groups), independent of - * entity count), before sort/pagination. + * entity count), before sort/pagination. Metric names and `count` are looked up + * directly, not field-addressed; a group-KEY field used here follows the same + * field-addressing law as find() (see the `@remarks` on {@link FindParams}). */ having?: Record - /** Sort by metric name or group key field */ + /** + * Sort by metric name (a key from `metrics`, looked up directly) or by a + * group key field — a group key field follows the same field-addressing + * law as find()'s `orderBy` (see the `@remarks` on {@link FindParams}). + */ orderBy?: string /** Sort direction */ order?: 'asc' | 'desc'