open-brainy/docs/guides/namespace-migration.md
David Snelling a99b1e83c4 chore: rename to @soulcraftlabs/brainy for Open Brainy on The Source
Prepares the repo for its new home at soulcraftlabs/open-brainy ahead
of the Forgejo transfer: package name, publish registry, release
script, and every install/import reference across docs, src, tests,
examples, and integrations now point at @soulcraftlabs/brainy on
The Source. The npmjs storefront leg and byte-identity pair
verification are stripped from the release script — The Source is
now the only publish target. README gains an Open Brainy explainer
and a registry note for consumers.

@soulcraft/brainy 10.4.2 was the last release under the old name.
2026-08-27 17:07:09 -07:00

4.5 KiB

title slug public category template order description next
Migrating to 9.0 — your fields and system fields guides/namespace-migration true guides guide 1 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.
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:

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):

import * as brainy from '@soulcraftlabs/brainy'
const lawActive = 'FIELD_ADDRESSING_CAPABILITY' in brainy // 'field-addressing/v1'

Where to go next

  • Field addressing — the full contract: the ten system scalars, the relation mirror, refusal semantics, and the cross-engine ordering guarantees.