--- title: Migrating to 9.0 — your fields and system fields slug: guides/namespace-migration public: true category: guides template: guide order: 1 description: The simple story of the 9.0 field-addressing change and the mechanical checklist for updating your call sites — every miss fails loudly with the fix in the error. next: - concepts/field-addressing --- # Migrating to 9.0 — your fields and system fields The one-sentence version: **your data's field names are now completely yours, the engine's own fields all live behind one `system.` prefix, and nothing in between can silently go wrong anymore.** ## What changed, simply **1. Any field name just works.** Before 9.0 the engine quietly owned certain names. A field called `level` could be shadowed by the engine's internal index layer of the same name (sorts silently returned insertion order); names like `confidence` or `subtype` were rejected inside `metadata`; names like `content` or `id` were silently never indexed, so filtering on them returned nothing. All of that is gone. Any name — `level`, `confidence`, `type`, `id`, `content`, anything — is stored exactly as written and works with every feature: filtering, sorting, grouping, aggregation, search, and time-travel reads. **2. The engine's fields moved behind `system.`.** The engine still keeps its own per-record bookkeeping — creation time, type, confidence, and so on. Those are reached one way only now: spelled out, e.g. `system.createdAt`, `system.type`. They are just as queryable and sortable as before. `orderBy: 'createdAt'` means *your* field named `createdAt`; `orderBy: 'system.createdAt'` means the engine's timestamp. No guessing, no priority rules. **3. Storage keeps the two physically separate.** New records store your metadata in its own nested compartment, so a user field named `confidence` and the engine's confidence live side by side, both intact, through restarts, index rebuilds, and `asOf()` history. Old records stay readable forever; nothing rewrites your data. **4. Mistakes are loud.** An ambiguous or unknown field name is a typed error naming the fix. Unimplemented options refuse instead of being ignored. The only forbidden name in your metadata is one literally starting with `system.`. ## The mechanical checklist Every missed site fails **loudly** with the correction in the error message — nothing silently changes meaning. Sweep these patterns: | Before (8.x) | After (9.0) | |---|---| | `orderBy: 'createdAt'` (meaning the engine timestamp) | `orderBy: 'system.createdAt'` | | `where: { subtype: 'invoice' }` (the engine subtype) | `where: { 'system.subtype': 'invoice' }` | | `where: { confidence: { greaterThan: 0.8 } }` (the engine scalar) | `where: { 'system.confidence': { greaterThan: 0.8 } }` | | `groupBy: ['noun']` or `groupBy: ['type']` | `groupBy: ['system.type']` | | `where: { visibility: 'internal' }` / `{ service: … }` (engine values) | `'system.visibility'` / `'system.service'` | | `metadata: { confidence: 0.9 }` expecting a throw or a lift to the engine scalar | it is YOUR field now — set the engine scalar via the `confidence` param | | `new Brainy({ reservedFieldPolicy: … })` | remove the option (it throws with this note) | | `find({ cursor })` / `includeRelations` / `writeOnly` | refuse with `UnsupportedFindOptionError` — they were silently ignored before | If a bare name in a query was genuinely *your* field all along (`orderBy: 'score'`, `where: { status: 'active' }`), **change nothing** — bare names mean your fields, always. ## What happens at first open Each existing database rebuilds its derived indexes once, automatically, at the first open on 9.0 (index epoch 3 — the index keys split the two namespaces). One-time cost, observable via `getIndexStatus()`; no manual step, and your stored data is not modified. ## For tooling and raw-record readers If you read raw stored records (fact-log scanners, export tooling), use the exported shape-aware splitters — they handle both record eras: ```typescript import { splitNounMetadataRecord } from '@soulcraftlabs/brainy' const { reserved, custom } = splitNounMetadataRecord(rawRecord) // reserved = engine fields · custom = the user's bag, ANY names ``` Feature detection (never version-sniff): ```typescript import * as brainy from '@soulcraftlabs/brainy' const lawActive = 'FIELD_ADDRESSING_CAPABILITY' in brainy // 'field-addressing/v1' ``` ## Where to go next - [Field addressing](../concepts/field-addressing.md) — the full contract: the ten system scalars, the relation mirror, refusal semantics, and the cross-engine ordering guarantees.