docs: v9.0.0 release notes — the field-addressing law migration ledger; retitle the shipped 8.11.0 canonical-enumeration entry (header went stale at its cut)
Some checks failed
CI / Node 22 (push) Has been cancelled
CI / Node 24 (push) Has been cancelled
CI / Bun (latest) (push) Has been cancelled

This commit is contained in:
David Snelling 2026-08-04 08:16:04 -07:00
parent 19b477aec0
commit 55a7512c04

View file

@ -31,7 +31,7 @@ is sometimes cited as a 7.x removal — those methods never existed on 7.x; the
--- ---
## Unreleased (canonical enumeration mode for export — storage-walked, canon-complete) ## v8.11.0 — 2026-07-27 (canonical enumeration mode for export — storage-walked, canon-complete)
From a fleet data-migration program's requirement for whole-brain exports that are From a fleet data-migration program's requirement for whole-brain exports that are
provably canon-complete: `export()`'s default enumeration for a whole-brain/predicate provably canon-complete: `export()`'s default enumeration for a whole-brain/predicate
@ -74,7 +74,102 @@ to the caller today.
on CI**, triggered by the release tag, instead of PUTting the tarball from the laptop on CI**, triggered by the release tag, instead of PUTting the tarball from the laptop
over WAN — no change to what gets published or how a consumer installs it. over WAN — no change to what gets published or how a consumer installs it.
## Unreleased (natural field names stop colliding with engine internals) ## v9.0.0 — 2026-08-04 (the field-addressing law: your names and system.*, nothing in between)
**Major.** One law now governs every field name, on every surface:
> **Data is either in main space — where you can use ANY name — or it is in
> `system.*`.**
Read `docs/concepts/field-addressing.md` (published on the docs site) for the
full contract; this entry is the migration ledger.
### Breaking — query surfaces (`where` / `orderBy` / `groupBy` / aggregation)
- **A bare field name ALWAYS addresses your metadata.** `orderBy: 'createdAt'`
no longer silently means the engine timestamp — it now refuses with a typed
`UnresolvableFieldError` naming both candidates unless you actually have a
user field of that name. Engine scalars are addressed explicitly:
`system.id`, `system.type`, `system.subtype`, `system.createdAt`,
`system.updatedAt`, `system.confidence`, `system.weight`,
`system.visibility`, `system.service`, `system.createdBy` (relations mirror
with `system.verb`/`system.sourceId`/`system.targetId`).
**Sweep list:** `where: { subtype: … }``where: { 'system.subtype': … }` ·
`orderBy: 'createdAt'``'system.createdAt'` · `groupBy: ['noun']`
`['system.type']` · any bare `visibility`/`service`/`confidence` filter that
meant the engine value → its `system.*` spelling. Every missed site fails
LOUDLY with the correction in the error message — nothing silently changes
meaning without telling you.
- **Unimplemented `find()` options refuse** (`cursor`, `includeRelations`,
`writeOnly``UnsupportedFindOptionError`); `order` is validated;
accepted-and-ignored is dead as a class.
- **The ordering contract is pinned cross-engine:** missing/null `orderBy`
values sort LAST in both directions, ties break by id ascending, and rows
are never dropped from an ordered read.
### Breaking — write surfaces
- **There are no reserved metadata names anymore.** `metadata: { confidence,
type, id, level, data, content, … }` are ordinary user fields — stored
verbatim, indexed, filterable, sortable, aggregatable, faithful across
restarts, index rebuilds, and `asOf()` time travel. The 8.x
reserved-key-in-bag throw is GONE; code that relied on it (or on the
`'warn'`/`'remap'` lift) must set engine scalars via their dedicated params
(`confidence`, `weight`, `subtype`, `visibility`, …) — the bag never touches
them now.
- **`reservedFieldPolicy` is removed.** Passing it throws at construction with
the migration note. `RESERVED_ENTITY_FIELDS`/`RESERVED_RELATION_FIELDS`
remain exported but now describe the stored record's engine half, not a ban
list; the `NoReservedEntityKeys`/`NoReservedRelationKeys` types are no-op
(deprecated).
- **The one refused spelling:** a metadata key literally starting `system.`
(namespace forgery) — typed error on `add`/`update`/`relate`/`updateRelation`.
- **Name-based index exclusions are gone.** Fields named `content`, `data`,
`id`, `vector`, … in your bag now INDEX like everything else (they were
silently un-indexed before — `where` on them returned `[]` with no error).
Value-shape rules stay, uniform across all names: arrays >10 never become
posting scalars; long values index hashed.
- **Migration transforms receive one normalized view** (engine fields
top-level, your bag nested under `metadata`) regardless of how old the
stored record is, and must return the same shape — a stray non-engine
top-level key refuses with the fix in the message.
### Storage format (automatic, no action)
- New/updated records persist as **nested-bag records** (engine fields
top-level, your bag verbatim under `metadata`, sealed by a format stamp) —
the shape that makes collider names lossless. Old flat records stay
readable forever; nothing rewrites your data in place.
- **Index epoch 3:** derived-index keys split the namespaces (bare user keys ·
literal `system.<field>` keys; the legacy `noun` column is gone). Every
brain rebuilds its derived indexes from canonical once, at first open —
observable via `getIndexStatus()`, no manual step. Pair this release with
the same-day native-accelerator release (its peer floor rises to `>=9`).
- Raw-record consumers (fact-log scanners, export tooling): read bags through
the exported shape-aware splitters (`splitNounMetadataRecord` /
`splitVerbMetadataRecord`) — they handle both record eras.
### Fixed in the same train
- Default visibility exclusion was a silent no-op under the new addressing on
pre-release builds (internal/system-tier rows could leak into default
reads) — now pinned by conformance tests at every lifecycle boundary.
- Per-type count surfaces (`getStats()`, count-by-type) read the new type
column, with a legacy fallback for pre-rebuild reads.
- Aggregation `source.where` evaluated dotted keys as nested paths — dotted
addresses now match per-key, and the internal per-type counts aggregate
rebuilds itself onto the new keys automatically.
### Conformance
Both engines ship a shared self-arming conformance suite (the law cases, the
ordering contract, and the reopen-collider fidelity case: every collider name
written as user data, verified verbatim through live reads, reopen, a forced
epoch rebuild, and time travel). Capability signal:
`FIELD_ADDRESSING_CAPABILITY = 'field-addressing/v1'` plus the typed error
classes, exported from the package root.
## v8.10.3 — 2026-08-03, 8.10-line backport (natural field names stop colliding with engine internals)
From a production report: sorting by a user metadata field named `level` silently From a production report: sorting by a user metadata field named `level` silently
returned insertion order — the engine's internal HNSW node layer (also called returned insertion order — the engine's internal HNSW node layer (also called
@ -98,10 +193,8 @@ engine was wrong, not the caller.
fixed for `update()` but the transact plan builder still staged the fixed for `update()` but the transact plan builder still staged the
unconditional save). If you batch stat touches through `transact()`, this is unconditional save). If you batch stat touches through `transact()`, this is
your write-amplification fix. your write-amplification fix.
- Coming next (announced so parsers and call sites can prepare): one - (The "coming next" note this entry carried shipped as v9.0.0 — the
field-addressing law — bare names = user metadata, `system.<field>` for field-addressing law above.)
engine fields, typed refusals for unresolvable names. Ships as its own
release with a migration advisory; nothing changes in this release.
--- ---