feat(8.0): brain.fillSubtypes migration helper + pre-RC1 gap closure
- brain.fillSubtypes(rules): idempotent subtype back-fill for pre-8.0 data.
One rule per NounType/VerbType (literal default or per-entry function);
fills only entries still missing a subtype through the public update()/
updateRelation() paths; returns { scanned, filled, skipped, errors, byType }.
Full unit suite in tests/unit/brainy/fill-subtypes.test.ts.
- Fix getNouns/getVerbs pagination hasMore (peek one past the window) —
was permanently false, silently truncating every multi-page walk.
- find({ near }) without near.id now throws a teaching error instead of an
opaque storage sharding failure; CLI --threshold without --near applies a
plain score floor.
- CLI init/close audit: every one-shot command init()s, close()s, and exits
explicitly; delete the unmaintained interactive REPL; replace the cloud-era
storage subcommands with status/batch-delete; new types/validate commands.
- requireSubtype JSDoc now documents the 8.0 default-on contract; audit()
recommendation points at fillSubtypes.
- Docs: data-storage-architecture rewritten to the real 8.0 on-disk layout;
README storage section reflects filesystem+memory and snapshots; eli5 and
SEMANTIC_VFS /as-of/ semantics corrected; internal tracker IDs and
.strategy references scrubbed from published files.
This commit is contained in:
parent
9b0f4acd5b
commit
c44678390e
30 changed files with 1517 additions and 3226 deletions
|
|
@ -76,7 +76,7 @@ export interface Relation<T = any> {
|
|||
/** Relationship type classification (VerbType enum) */
|
||||
type: VerbType
|
||||
/**
|
||||
* Per-product sub-classification within the VerbType (e.g. a `Manages`
|
||||
* Per-product sub-classification within the VerbType (e.g. a `ReportsTo`
|
||||
* relationship might have `subtype: 'direct'` vs `'dotted-line'`; a `RelatedTo`
|
||||
* edge might carry `'spouse'` / `'sibling'` / `'colleague'`). Flat string, no
|
||||
* hierarchy. Top-level standard field — indexed on the fast path and rolled into
|
||||
|
|
@ -208,6 +208,68 @@ export interface SubtypeRegistry {
|
|||
// Intentionally empty. Consumers extend via declaration merging.
|
||||
}
|
||||
|
||||
/**
|
||||
* A single back-fill rule for `brain.fillSubtypes()`.
|
||||
*
|
||||
* - A **literal string** assigns that subtype to every matching entry that
|
||||
* lacks one (`'general'` — a blanket default).
|
||||
* - A **function** receives the full entry and returns the subtype to assign,
|
||||
* or `undefined` to leave the entry untouched (it is counted as `skipped`
|
||||
* so a later run with a stricter rule can pick it up). Functions can derive
|
||||
* the subtype from existing fields, e.g. `(e) => e.metadata?.kind`.
|
||||
*
|
||||
* @typeParam E - The entry shape the rule sees: `Entity<T>` for NounType keys,
|
||||
* `Relation<T>` for VerbType keys.
|
||||
*/
|
||||
export type FillSubtypeRule<E> = string | ((entry: E) => string | undefined)
|
||||
|
||||
/**
|
||||
* Rule map for `brain.fillSubtypes()` — the 8.0 subtype migration helper.
|
||||
*
|
||||
* Keys are `NounType` values (entity rules) and/or `VerbType` values
|
||||
* (relationship rules); the two vocabularies don't overlap, so a single map
|
||||
* covers both sides. Each value is a {@link FillSubtypeRule}: a literal
|
||||
* subtype string or a function deriving one from the entry.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await brain.fillSubtypes({
|
||||
* [NounType.Person]: (e) => e.metadata?.kind ?? 'unspecified',
|
||||
* [NounType.Thing]: 'general', // literal default
|
||||
* [VerbType.RelatedTo]: 'unspecified' // relationship rule
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export type FillSubtypeRules<T = any> = {
|
||||
[K in NounType]?: FillSubtypeRule<Entity<T>>
|
||||
} & {
|
||||
[K in VerbType]?: FillSubtypeRule<Relation<T>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary returned by `brain.fillSubtypes()`.
|
||||
*
|
||||
* After a run, `skipped` is exactly the remaining migration debt — re-running
|
||||
* `brain.audit()` reports the same entries. Entries that already carry a
|
||||
* subtype count toward `scanned` only (they are not debt, so they are neither
|
||||
* `filled` nor `skipped`).
|
||||
*/
|
||||
export interface FillSubtypesResult {
|
||||
/** Total entries examined (entities + relationships). */
|
||||
scanned: number
|
||||
/** Entries that received a subtype during this run. */
|
||||
filled: number
|
||||
/**
|
||||
* Entries still missing a subtype after the run — either their type has no
|
||||
* rule in the map, or their rule function returned `undefined`/empty.
|
||||
*/
|
||||
skipped: number
|
||||
/** Per-entry write failures (`fillSubtypes` continues past individual errors). */
|
||||
errors: Array<{ id: string; error: string }>
|
||||
/** Fill counts grouped by NounType/VerbType key (only types with fills appear). */
|
||||
byType: Record<string, number>
|
||||
}
|
||||
|
||||
export interface AddParams<T = any> {
|
||||
/** Content to embed and store. Strings are auto-embedded; objects are JSON-stringified for embedding. */
|
||||
data: any | Vector
|
||||
|
|
@ -282,7 +344,7 @@ export interface RelateParams<T = any> {
|
|||
/** Relationship type classification (required) */
|
||||
type: VerbType
|
||||
/**
|
||||
* Per-product sub-classification within the VerbType (e.g. a `Manages`
|
||||
* Per-product sub-classification within the VerbType (e.g. a `ReportsTo`
|
||||
* relationship might have `subtype: 'direct'` or `'dotted-line'`; a `RelatedTo`
|
||||
* edge might carry `'spouse'` or `'colleague'`). Flat string, no hierarchy.
|
||||
* Indexed and rolled up into per-VerbType statistics for fast filtering
|
||||
|
|
@ -1239,16 +1301,21 @@ export interface BrainyConfig {
|
|||
/**
|
||||
* Brain-wide subtype enforcement mode.
|
||||
*
|
||||
* Opt-in in 7.30.0 (default: `false`); becomes the default in 8.0.0.
|
||||
* **Default-on since 8.0.0** (`undefined` resolves to `true`; in 7.30.x this
|
||||
* was an opt-in flag defaulting to `false`).
|
||||
*
|
||||
* - `false` / `undefined` (default): no brain-wide check. Per-type rules
|
||||
* registered via `brain.requireSubtype(type, options)` still apply.
|
||||
* - `true`: every `add()` / `addMany()` / `update()` / `relate()` /
|
||||
* `relateMany()` / `updateRelation()` rejects writes where the entity's
|
||||
* NounType (or relationship's VerbType) has no non-empty `subtype` value.
|
||||
* - `{ except: [NounType.Thing, NounType.Custom] }`: same as `true`, but
|
||||
* the listed types are allowed through without a subtype. Use for genuine
|
||||
* - `true` / `undefined` (default): every `add()` / `addMany()` / `update()` /
|
||||
* `relate()` / `relateMany()` / `updateRelation()` rejects writes where the
|
||||
* entity's NounType (or relationship's VerbType) has no non-empty `subtype`
|
||||
* value. Brainy's own VFS infrastructure writes (`metadata.isVFSEntity` /
|
||||
* `metadata.isVFS`) bypass the check.
|
||||
* - `{ except: [NounType.Thing, NounType.Custom] }`: same as `true`, but the
|
||||
* listed types are allowed through without a subtype. Use for genuine
|
||||
* catch-all types where no subtype makes sense.
|
||||
* - `false`: disable the brain-wide check entirely. Last-resort escape hatch
|
||||
* for opening pre-8.0 data — run `brain.audit()` to find the gaps, back-fill
|
||||
* with `brain.fillSubtypes(rules)`, then remove the opt-out so the default
|
||||
* enforcement protects new writes.
|
||||
*
|
||||
* Per-type registrations always compose with the brain-wide flag — a type
|
||||
* registered with `requireSubtype(type, { required: true })` is always
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue