brainy/src/types/brainy.types.ts

1983 lines
75 KiB
TypeScript
Raw Normal View History

/**
* 🧠 Brainy 3.0 Type Definitions
*
* Beautiful, consistent, type-safe interfaces for the future of neural databases
*/
feat(8.0): full query surface at historical generations via ephemeral index materialization Historical Db values (now()/asOf() pins that history has moved past) now serve the COMPLETE query surface - vector/hybrid search, graph traversal, cursor pagination, and aggregation - by materializing ephemeral in-memory indexes over the exact at-generation record set. The historical-query throw is gone; NotYetSupportedAtHistoricalGenerationError is deleted. Materializer (Brainy.materializeAtGeneration): - Copies the at-G record set (live bytes for ids untouched since the pin, immutable before-images otherwise) into a fresh MemoryStorage; a final reconciliation pass under the commit mutex makes the copy exact even when transactions commit mid-build. - Opens a read-only Brainy over the copy: init rebuilds the metadata and graph-adjacency indexes from the records; the vector index is built by inserting every at-G vector (the at-G HNSW graph never existed on disk, so there is nothing to restore). Host embedder and aggregate definitions are shared - no second model load, aggregates backfill at-G values. - Cost is the documented contract: O(n at G) time and memory, ONCE per Db (handle cached; freed by release(), with a FinalizationRegistry backstop that also closes leaked readers). A native VersionedIndexProvider serves the same reads from retained segments with no rebuild. Db routing (src/db/db.ts): metadata-level find()/related() keep the free record path; index-only dimensions (query/vector/near/connected/cursor/ aggregate/includeRelations/non-metadata modes) route to the cached materialization; unsupported where-operators on the record path re-route there too instead of erroring. Speculative with() overlays keep the one honest boundary - SpeculativeOverlayError (overlay entities carry no embeddings, so index reads over them would be silently incomplete); metadata find()/get()/filter related() work on overlays. UpdateParams.vector contract now honored: an explicit pre-computed vector applies directly (with dimension validation) in update() and transact update ops, re-indexing HNSW - previously it was silently ignored unless data also changed. GraphAdjacencyIndex: adjacency now derives from the two verb-id LSM trees filtered through the live-verb tombstone set (entity->entity edge trees deleted - they carried no verb ids, so removeVerb could never tombstone them and traversal served stale neighbors forever). Neighbor reads batch- load live verbs via the unified cache; addVerb seeds the cache. Proofs (tests/integration/db-mvcc.test.ts, 24 green): historical vector search finds old vector placement including since-deleted entities; historical graph traversal walks the old wiring after a rewire; historical aggregation computes at-G group values; asOf() pins get the same surface; the materialization builds once per Db and release() closes the ephemeral reader (it refuses reads afterwards); overlays throw the documented error. ADR-001 updated to the no-throws historical model.
2026-06-11 08:12:11 -07:00
import { StorageAdapter, Vector } from '../coreTypes.js'
import type { EntityVisibility } from '../coreTypes.js'
import { NounType, VerbType } from './graphTypes.js'
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
import type {
EntityMetadataInput,
EntityMetadataPatch,
RelationMetadataInput,
RelationMetadataPatch
} from './reservedFields.js'
// ============= Core Types =============
/**
* Entity (Noun) the fundamental data unit in Brainy
*
* **Data vs Metadata:**
* - `data`: Content used for vector embeddings. Searchable via **semantic similarity**
* (HNSW index) and hybrid text+semantic search. NOT queryable via `where` filters.
* - `metadata`: Structured fields indexed by MetadataIndex. Queryable via `where`
* filters in `find()`. Standard system fields (noun, createdAt, etc.) are stored
* alongside user metadata but extracted to top-level Entity fields on read.
*/
export interface Entity<T = any> {
/** Unique identifier (UUID v4) */
id: string
/** Embedding vector (384-dim by default). Empty array when loaded without includeVectors. */
vector: Vector
/** Entity type classification (NounType enum) */
type: NounType
feat: subtype top-level field + trackField + migrateField Promotes `subtype?: string` to a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy — the consumer-chosen vocabulary for sub-classifying entities within a NounType (Person → employee/customer, Document → invoice/contract, etc.). Layer 1 — subtype field + rollup - HNSWNounWithMetadata.subtype + STANDARD_ENTITY_FIELDS entry - Entity / Result / AddParams / UpdateParams / FindParams threading - add()/update() persist subtype on storageMetadata + entityForIndexing - get()/find() route through the standard-field fast path - subtypeCountsByType (Map<NounTypeIdx, Map<subtype, count>>) on BaseStorage, mirrored after nounCountsByType with the same self-heal rebuild and persisted to _system/subtype-statistics.json - brain.counts.bySubtype(type, subtype?) — O(1) point + breakdown - brain.counts.topSubtypes(type, n) — top-N by count - brain.subtypesOf(type) — distinct subtypes seen - find({ type, subtype }) and find({ subtype: ['a','b'] }) on the fast path Layer 2 — trackField for other facets - brain.trackField(name, { perType?, values? }) registers a field for cardinality + per-NounType breakdown stats. Backed by the aggregation engine (auto-defines __fieldCounts__<name>), backfill-on-define applies. - brain.counts.byField(name, { type? }) returns value frequencies - Optional vocabulary whitelist rejects off-vocabulary writes at add/update Layer 3 — generic migrateField - brain.migrateField({ from, to, readBoth?, batchSize?, onProgress? }) streams every entity, copies the value from one path to another, and (unless readBoth) clears the source. Supports top-level standard fields, metadata.X, and data.X paths. Idempotent — safe to re-run. Docs - New guide: docs/guides/subtypes-and-facets.md (Layer 1 + 2 + 3) - README, DATA_MODEL, QUERY_OPERATORS, api/README, finite-type-system, quick-start all treat subtype as a core primitive with anonymous example vocabularies (employee/customer/invoice/milestone). Tests - 26 new integration tests covering write/read/update/delete round-trips, counts rollup decrement + re-route on mutation, trackField + byField with and without perType, vocabulary whitelist enforcement, and migrateField for metadata.X → subtype and data.X → subtype paths including readBoth deprecation-window semantics. Unit suite: 1468/1468 passing. Type-check + build clean.
2026-06-04 17:24:36 -07:00
/**
* Per-product sub-classification within the NounType (e.g. a `Person` entity might
* have `subtype: 'employee'` or `'customer'`; a `Document` might have `subtype: 'invoice'`).
* Flat string, no hierarchy. Top-level standard field indexed on the fast path and
* rolled into per-NounType statistics.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field). Absent === `'public'` (counted and
* returned everywhere). `'internal'` hides the entity from default `find()` / counts /
* `stats()` (opt back in with `find({ includeInternal: true })`); `'system'` is Brainy
* plumbing, hidden unless `find({ includeSystem: true })`. See {@link EntityVisibility}.
*/
visibility?: EntityVisibility
/** Opaque content — used for embeddings and semantic search. Not indexed by MetadataIndex. */
data?: any
/** User-defined structured fields — indexed and queryable via `where` filters. */
metadata?: T
/** Multi-tenancy service identifier */
service?: string
/** Creation timestamp (ms since epoch) */
createdAt: number
/** Last update timestamp (ms since epoch) */
updatedAt?: number
/** Source that created this entity (e.g., augmentation info) */
createdBy?: string
/** Type classification confidence (0-1) */
confidence?: number
/** Entity importance/salience (0-1) */
weight?: number
feat: per-entity _rev + update({ ifRev }) CAS + add({ ifAbsent }) Optimistic concurrency for multi-writer coordination — the read-then-CAS lock pattern + idempotent-bootstrap singleton inserts. Lands the surface the SDK scheduler asked for in BRAINY-EXPOSE-TRANSACTIONS, scoped to what ships cleanly without painting the 8.0 transact() API into a corner. A. PER-ENTITY _rev FIELD - src/coreTypes.ts — HNSWNounWithMetadata gains optional `_rev?: number`; STANDARD_ENTITY_FIELDS includes it so resolveEntityField routes correctly. - src/types/brainy.types.ts — Entity<T> gains optional `_rev?: number`; Result<T> mirrors it on the convenience flatten layer. - src/brainy.ts add() — initializes `_rev: 1` in storageMetadata. New entities always start at 1. - src/brainy.ts update() — reads currentRev off the persisted metadata (falls back to 1 for pre-7.31.0 entities with no _rev), writes `_rev: currentRev + 1` into the updated metadata. Every successful update() bumps by exactly 1. - src/brainy.ts convertNounToEntity + convertMetadataToEntity — both pull _rev out of the storage metadata and surface it at the top level of Entity. Pre-7.31.0 entities (no _rev in storage) read as `_rev: 1` so consumers see a consistent value. - src/storage/baseStorage.ts — six destructure sites updated to pull _rev out of the metadata bag so it doesn't leak into customMetadata. Noun-side returns surface _rev; verb-side destructures correctly but doesn't expose it on HNSWVerbWithMetadata (verb CAS is future work). - src/brainy.ts createResult() — flattens entity._rev onto the Result for backward compat with the existing convenience-field layer. B. update({ ifRev }) OPTIMISTIC CONCURRENCY - src/types/brainy.types.ts — UpdateParams<T> gains optional `ifRev?: number`. - src/transaction/RevisionConflictError.ts (NEW) — carries `{ id, expected, actual }`. Message names the recipe: refetch with brain.get() and retry with the latest _rev. Subclass of Error. - src/brainy.ts update() — when params.ifRev is provided, compares against currentRev (the rev we just read) and throws RevisionConflictError on mismatch before any storage write. Omitting ifRev keeps the prior unconditional-update behavior; existing consumers see no change. - src/transaction/index.ts — exports RevisionConflictError. - src/index.ts — public export of RevisionConflictError. C. add({ ifAbsent }) BY-ID IDEMPOTENT INSERT - src/types/brainy.types.ts — AddParams<T> gains optional `ifAbsent?: boolean`; AddManyParams<T> mirrors it as a batch-level flag. - src/brainy.ts add() — when params.id AND params.ifAbsent, pre-reads storage.getNounMetadata(id); if present, returns the existing id without writing. No throw, no overwrite. Ignored when id is omitted (a fresh UUID can never collide). - src/brainy.ts addMany() — propagates the batch-level ifAbsent to each item's add() call. Per-item ifAbsent takes precedence so callers can override individual rows. WHAT'S NOT SHIPPED (and why) A public brain.transaction(fn) wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes (SaveNounMetadata, etc.) that take StorageAdapter as a constructor argument. A clean high-level facade in 7.31.0 would have meant either: (a) Delegate to brain.add() / update() / relate(). Each of those opens its own internal transaction and commits before the closure returns. Looks atomic, isn't — a footgun. (b) Thread an optional `tx?` through every internal write site in brainy.ts (8 transactionManager.executeTransaction sites). ~2 days of real refactor with regression surface, and the API shape changes again in 8.0 anyway. The SDK scheduler's actual ask (BRAINY-EXPOSE-TRANSACTIONS) is the read-then-CAS lock pattern. _rev + ifRev solves it completely. Multi-write atomicity is the 8.0 brain.transact() use case where the Datomic-style immutable Db makes it atomic by construction — that's the right home. TESTS - New tests/integration/rev-and-ifabsent.test.ts (18 tests): - _rev initialization (1 on add) and surface on get fast/full paths + find - _rev auto-bump on update across multiple writes - update({ ifRev }) pass / fail / message format / omitted / legacy-no-rev - add({ ifAbsent }) writes when absent / no-op when present / id-required - addMany({ ifAbsent }) propagation + per-item override - SDK-scheduler scenario: two concurrent CAS updates, one wins one throws - Unit suite unchanged: 1468/1468. - All integration subtype + verb + strict + find-limits + new rev suites pass: 96/96. DOCS - New docs/guides/optimistic-concurrency.md (public: true) — full reference: the lock pattern, read-modify-write retry, idempotent bootstrap, how _rev interacts with brain.versions, branches/fork, and VFS, what's coming in 8.0. - docs/api/README.md — add() and update() entries get the new params + tips pointing at the new guide. - RELEASES.md v7.31.0 entry. CORTEX COMPATIBILITY Zero changes required. _rev is a metadata column already supported by NativeColumnStore. The auto-bump runs in Brainy JS before any storage call; Cortex never sees the per-entity counter. 8.0 FORWARD-COMPAT _rev, ifRev, RevisionConflictError, and ifAbsent survive the 8.0 Db redesign unchanged. 8.0 layers brain.transact(tx, { ifAtGeneration }) for whole-tx CAS on top of the same per-entity mechanism — per-entity for single-record patterns (job locks, idempotent state machines), generation-based for "did the world move under me." Locked in .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md § C-6. Verification - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit - All integration suites including new rev-and-ifabsent: 96/96 - npm run build: clean - Closed-source product reference audit: clean
2026-06-09 10:04:24 -07:00
/**
* Monotonic revision counter, auto-bumped on every successful `update()`.
* Starts at `1` on `add()`. Pre-7.31.0 entities without `_rev` are read as `1`.
* Pass back as `update({ id, ..., ifRev: _rev })` for optimistic-concurrency CAS
* throws `RevisionConflictError` if the persisted rev moved since read.
*/
_rev?: number
}
/**
* Relation (Verb) a typed edge connecting two entities
*
* **Data vs Metadata (on relationships):**
* - `data`: Opaque content stored on the relationship. If provided during relate(),
* overrides the auto-computed vector (default: average of source+target vectors).
* - `metadata`: Structured queryable fields on the edge (e.g., role, startDate).
*/
export interface Relation<T = any> {
/** Unique identifier (UUID v4) */
id: string
/** Source entity ID */
from: string
/** Target entity ID */
to: string
/** Relationship type classification (VerbType enum) */
type: VerbType
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
/**
* Per-product sub-classification within the VerbType (e.g. a `ReportsTo`
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
* 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
* per-VerbType statistics so queries like `related({ verb, subtype })` hit
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
* the column-store directly.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field), the verb mirror of `Entity.visibility`.
* Absent === `'public'` (counted and returned everywhere). `'internal'` hides the edge
* from default `related()` / counts / `stats()` (opt back in with
* `related({ includeInternal: true })`); `'system'` is Brainy plumbing. See
* {@link EntityVisibility}.
*/
visibility?: EntityVisibility
/** Connection strength (0-1, default: 1.0) */
weight?: number
/** Opaque content for the relationship (overrides auto-computed vector if provided) */
data?: any
/** User-defined structured fields on the edge */
metadata?: T
/** Multi-tenancy service identifier */
service?: string
/** Creation timestamp (ms since epoch) */
createdAt: number
/** Last update timestamp (ms since epoch) */
updatedAt?: number
/** Relationship certainty (0-1) */
confidence?: number
/** Evidence for why this relationship was detected */
evidence?: RelationEvidence
}
/**
* Evidence for why a relationship was detected
*/
export interface RelationEvidence {
sourceText?: string // Text that indicated this relationship
position?: { // Position in source text
start: number
end: number
}
method: 'neural' | 'pattern' | 'structural' | 'explicit' // How it was detected
reasoning?: string // Human-readable explanation
}
/**
* Search result with similarity score
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
*
* Flattens commonly-used entity fields to top level for convenience,
* while preserving full entity in 'entity' field for backward compatibility.
*/
export interface Result<T = any> {
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
// Search metadata
id: string
score: number
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
// Convenience: Common entity fields flattened to top level
type?: NounType // Entity type (from entity.type)
feat: subtype top-level field + trackField + migrateField Promotes `subtype?: string` to a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy — the consumer-chosen vocabulary for sub-classifying entities within a NounType (Person → employee/customer, Document → invoice/contract, etc.). Layer 1 — subtype field + rollup - HNSWNounWithMetadata.subtype + STANDARD_ENTITY_FIELDS entry - Entity / Result / AddParams / UpdateParams / FindParams threading - add()/update() persist subtype on storageMetadata + entityForIndexing - get()/find() route through the standard-field fast path - subtypeCountsByType (Map<NounTypeIdx, Map<subtype, count>>) on BaseStorage, mirrored after nounCountsByType with the same self-heal rebuild and persisted to _system/subtype-statistics.json - brain.counts.bySubtype(type, subtype?) — O(1) point + breakdown - brain.counts.topSubtypes(type, n) — top-N by count - brain.subtypesOf(type) — distinct subtypes seen - find({ type, subtype }) and find({ subtype: ['a','b'] }) on the fast path Layer 2 — trackField for other facets - brain.trackField(name, { perType?, values? }) registers a field for cardinality + per-NounType breakdown stats. Backed by the aggregation engine (auto-defines __fieldCounts__<name>), backfill-on-define applies. - brain.counts.byField(name, { type? }) returns value frequencies - Optional vocabulary whitelist rejects off-vocabulary writes at add/update Layer 3 — generic migrateField - brain.migrateField({ from, to, readBoth?, batchSize?, onProgress? }) streams every entity, copies the value from one path to another, and (unless readBoth) clears the source. Supports top-level standard fields, metadata.X, and data.X paths. Idempotent — safe to re-run. Docs - New guide: docs/guides/subtypes-and-facets.md (Layer 1 + 2 + 3) - README, DATA_MODEL, QUERY_OPERATORS, api/README, finite-type-system, quick-start all treat subtype as a core primitive with anonymous example vocabularies (employee/customer/invoice/milestone). Tests - 26 new integration tests covering write/read/update/delete round-trips, counts rollup decrement + re-route on mutation, trackField + byField with and without perType, vocabulary whitelist enforcement, and migrateField for metadata.X → subtype and data.X → subtype paths including readBoth deprecation-window semantics. Unit suite: 1468/1468 passing. Type-check + build clean.
2026-06-04 17:24:36 -07:00
subtype?: string // Per-product sub-classification (from entity.subtype)
visibility?: EntityVisibility // Visibility tier (from entity.visibility); absent === 'public'
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
metadata?: T // Entity metadata (from entity.metadata)
data?: any // Entity data (from entity.data)
confidence?: number // Type classification confidence (from entity.confidence)
weight?: number // Entity importance (from entity.weight)
feat: per-entity _rev + update({ ifRev }) CAS + add({ ifAbsent }) Optimistic concurrency for multi-writer coordination — the read-then-CAS lock pattern + idempotent-bootstrap singleton inserts. Lands the surface the SDK scheduler asked for in BRAINY-EXPOSE-TRANSACTIONS, scoped to what ships cleanly without painting the 8.0 transact() API into a corner. A. PER-ENTITY _rev FIELD - src/coreTypes.ts — HNSWNounWithMetadata gains optional `_rev?: number`; STANDARD_ENTITY_FIELDS includes it so resolveEntityField routes correctly. - src/types/brainy.types.ts — Entity<T> gains optional `_rev?: number`; Result<T> mirrors it on the convenience flatten layer. - src/brainy.ts add() — initializes `_rev: 1` in storageMetadata. New entities always start at 1. - src/brainy.ts update() — reads currentRev off the persisted metadata (falls back to 1 for pre-7.31.0 entities with no _rev), writes `_rev: currentRev + 1` into the updated metadata. Every successful update() bumps by exactly 1. - src/brainy.ts convertNounToEntity + convertMetadataToEntity — both pull _rev out of the storage metadata and surface it at the top level of Entity. Pre-7.31.0 entities (no _rev in storage) read as `_rev: 1` so consumers see a consistent value. - src/storage/baseStorage.ts — six destructure sites updated to pull _rev out of the metadata bag so it doesn't leak into customMetadata. Noun-side returns surface _rev; verb-side destructures correctly but doesn't expose it on HNSWVerbWithMetadata (verb CAS is future work). - src/brainy.ts createResult() — flattens entity._rev onto the Result for backward compat with the existing convenience-field layer. B. update({ ifRev }) OPTIMISTIC CONCURRENCY - src/types/brainy.types.ts — UpdateParams<T> gains optional `ifRev?: number`. - src/transaction/RevisionConflictError.ts (NEW) — carries `{ id, expected, actual }`. Message names the recipe: refetch with brain.get() and retry with the latest _rev. Subclass of Error. - src/brainy.ts update() — when params.ifRev is provided, compares against currentRev (the rev we just read) and throws RevisionConflictError on mismatch before any storage write. Omitting ifRev keeps the prior unconditional-update behavior; existing consumers see no change. - src/transaction/index.ts — exports RevisionConflictError. - src/index.ts — public export of RevisionConflictError. C. add({ ifAbsent }) BY-ID IDEMPOTENT INSERT - src/types/brainy.types.ts — AddParams<T> gains optional `ifAbsent?: boolean`; AddManyParams<T> mirrors it as a batch-level flag. - src/brainy.ts add() — when params.id AND params.ifAbsent, pre-reads storage.getNounMetadata(id); if present, returns the existing id without writing. No throw, no overwrite. Ignored when id is omitted (a fresh UUID can never collide). - src/brainy.ts addMany() — propagates the batch-level ifAbsent to each item's add() call. Per-item ifAbsent takes precedence so callers can override individual rows. WHAT'S NOT SHIPPED (and why) A public brain.transaction(fn) wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes (SaveNounMetadata, etc.) that take StorageAdapter as a constructor argument. A clean high-level facade in 7.31.0 would have meant either: (a) Delegate to brain.add() / update() / relate(). Each of those opens its own internal transaction and commits before the closure returns. Looks atomic, isn't — a footgun. (b) Thread an optional `tx?` through every internal write site in brainy.ts (8 transactionManager.executeTransaction sites). ~2 days of real refactor with regression surface, and the API shape changes again in 8.0 anyway. The SDK scheduler's actual ask (BRAINY-EXPOSE-TRANSACTIONS) is the read-then-CAS lock pattern. _rev + ifRev solves it completely. Multi-write atomicity is the 8.0 brain.transact() use case where the Datomic-style immutable Db makes it atomic by construction — that's the right home. TESTS - New tests/integration/rev-and-ifabsent.test.ts (18 tests): - _rev initialization (1 on add) and surface on get fast/full paths + find - _rev auto-bump on update across multiple writes - update({ ifRev }) pass / fail / message format / omitted / legacy-no-rev - add({ ifAbsent }) writes when absent / no-op when present / id-required - addMany({ ifAbsent }) propagation + per-item override - SDK-scheduler scenario: two concurrent CAS updates, one wins one throws - Unit suite unchanged: 1468/1468. - All integration subtype + verb + strict + find-limits + new rev suites pass: 96/96. DOCS - New docs/guides/optimistic-concurrency.md (public: true) — full reference: the lock pattern, read-modify-write retry, idempotent bootstrap, how _rev interacts with brain.versions, branches/fork, and VFS, what's coming in 8.0. - docs/api/README.md — add() and update() entries get the new params + tips pointing at the new guide. - RELEASES.md v7.31.0 entry. CORTEX COMPATIBILITY Zero changes required. _rev is a metadata column already supported by NativeColumnStore. The auto-bump runs in Brainy JS before any storage call; Cortex never sees the per-entity counter. 8.0 FORWARD-COMPAT _rev, ifRev, RevisionConflictError, and ifAbsent survive the 8.0 Db redesign unchanged. 8.0 layers brain.transact(tx, { ifAtGeneration }) for whole-tx CAS on top of the same per-entity mechanism — per-entity for single-record patterns (job locks, idempotent state machines), generation-based for "did the world move under me." Locked in .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md § C-6. Verification - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit - All integration suites including new rev-and-ifabsent: 96/96 - npm run build: clean - Closed-source product reference audit: clean
2026-06-09 10:04:24 -07:00
_rev?: number // Monotonic revision counter (from entity._rev) — pass back as update({ ifRev }) for CAS
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
// Full entity (preserved for backward compatibility)
entity: Entity<T>
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
// Score transparency
explanation?: ScoreExplanation
// Match visibility - shows what matched in hybrid search
textMatches?: string[] // Query words found in entity (e.g., ["david", "warrior"])
textScore?: number // Normalized text match score (0-1)
semanticScore?: number // Semantic similarity score (0-1)
matchSource?: 'text' | 'semantic' | 'both' // Where this result came from
rrfScore?: number // Raw RRF fusion score (for advanced ranking analysis)
// Aggregation: present only on rows returned by find({ aggregate }). These mirror the
// documented AggregateResult shape so callers can read group/metric data at the top level
// instead of digging into `metadata`. (The same values are also flattened into `metadata`
// for backward compatibility.)
groupKey?: Record<string, string | number> // Group-by key values for this aggregate row
metrics?: Record<string, number> // Computed metric values (sum/avg/min/max/count)
count?: number // Total entity count in this group
}
/**
* Score explanation for transparency
*/
export interface ScoreExplanation {
vectorScore?: number
metadataScore?: number
graphScore?: number
boosts?: Record<string, number>
penalties?: Record<string, number>
}
// ============= Operation Parameters =============
/**
* Parameters for adding entities
*
* **Data vs Metadata:**
* - `data` is embedded into a vector and searchable via semantic similarity (HNSW).
* It is NOT indexed by MetadataIndex and NOT queryable via `where` filters.
* - `metadata` is indexed by MetadataIndex and queryable via `where` filters in `find()`.
*/
refactor(8.0): SubtypeRegistry hook + drop multi-hop subtype throw (scaffold steps 11-12) Two small additions per BRAINY-8.0-SUBTYPE-CONTRACT § C-3 + § C-4. STEP 11 — SubtypeRegistry declaration-merging hook (§ C-3) src/types/brainy.types.ts - New empty `SubtypeRegistry` interface. Consumers extend via TypeScript module augmentation to declare per-`(NounType, subtype)` metadata shapes. Brainy ships no entries; every entry is a consumer concern. - Usage pattern documented inline: declare module '@soulcraft/brainy' { interface SubtypeRegistry { 'person:employee': { employeeId: string; department: string } 'document:invoice': { invoiceNumber: string; amount: number } } } - Consumers with no entries see no type-level change (metadata stays T = any). src/index.ts - Public export of `SubtypeRegistry` as a type so consumers can declare-merge it. The typed `add<NounType.Person, 'employee'>()` overloads + `brain.fillSubtypes()` migration helper are NOT in this commit — they require the C-1 runtime flip to land first (per step 10 deferral). The interface stub ships now so external consumers can start the declare-merging pattern in their own code immediately. STEP 12 — drop the multi-hop subtype throw (§ C-4) src/brainy.ts - Deleted the `find({ connected: { subtype, depth > 1 } })` throw that 7.30.x emitted ("Use depth: 1, or wait for Cortex native traversal"). - Replaced the throw block with an explanatory comment: cortex's D.3 `findConnectedSubtype` handles the multi-hop case natively; open-core JS falls back to per-hop edge enumeration (correct but slow past ~10 K reachable edges per source). Per BRAINY-8.0-RENAME-COORDINATION § G.3.b (cortex-confirmed): the native graph index is ready for unbounded `depth` from brainy with no rebuild required, and BFS-with-cycle-guard semantics hold at any depth. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing race-condition outstanding)
2026-06-09 14:29:09 -07:00
/**
* Declaration-merging registry for per-`(type, subtype)` metadata shapes
* (Brainy 8.0, per BRAINY-8.0-SUBTYPE-CONTRACT § C-3).
*
* Consumers extend this interface in their own code to tell TypeScript what
* shape `metadata` takes for a given `(NounType, subtype)` pair. Brainy
* doesn't ship any entries every entry is a consumer concern.
*
* @example
* ```ts
* declare module '@soulcraft/brainy' {
* interface SubtypeRegistry {
* // For NounType.Person, subtype 'employee':
* 'person:employee': { employeeId: string; department: string }
* // For NounType.Document, subtype 'invoice':
* 'document:invoice': { invoiceNumber: string; amount: number }
* }
* }
* ```
*
* Keys are `${NounType}:${subtype}`. Values describe the metadata shape.
* Brainy reads this registry (when present) to give typed `metadata`
* autocomplete + type-checking on `add()` / `update()` / `find()`. Consumers
* with no registry entries see no type-level change the metadata bag
* stays `T = any`.
*/
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
/** Entity type classification (required) */
type: NounType
feat: subtype top-level field + trackField + migrateField Promotes `subtype?: string` to a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy — the consumer-chosen vocabulary for sub-classifying entities within a NounType (Person → employee/customer, Document → invoice/contract, etc.). Layer 1 — subtype field + rollup - HNSWNounWithMetadata.subtype + STANDARD_ENTITY_FIELDS entry - Entity / Result / AddParams / UpdateParams / FindParams threading - add()/update() persist subtype on storageMetadata + entityForIndexing - get()/find() route through the standard-field fast path - subtypeCountsByType (Map<NounTypeIdx, Map<subtype, count>>) on BaseStorage, mirrored after nounCountsByType with the same self-heal rebuild and persisted to _system/subtype-statistics.json - brain.counts.bySubtype(type, subtype?) — O(1) point + breakdown - brain.counts.topSubtypes(type, n) — top-N by count - brain.subtypesOf(type) — distinct subtypes seen - find({ type, subtype }) and find({ subtype: ['a','b'] }) on the fast path Layer 2 — trackField for other facets - brain.trackField(name, { perType?, values? }) registers a field for cardinality + per-NounType breakdown stats. Backed by the aggregation engine (auto-defines __fieldCounts__<name>), backfill-on-define applies. - brain.counts.byField(name, { type? }) returns value frequencies - Optional vocabulary whitelist rejects off-vocabulary writes at add/update Layer 3 — generic migrateField - brain.migrateField({ from, to, readBoth?, batchSize?, onProgress? }) streams every entity, copies the value from one path to another, and (unless readBoth) clears the source. Supports top-level standard fields, metadata.X, and data.X paths. Idempotent — safe to re-run. Docs - New guide: docs/guides/subtypes-and-facets.md (Layer 1 + 2 + 3) - README, DATA_MODEL, QUERY_OPERATORS, api/README, finite-type-system, quick-start all treat subtype as a core primitive with anonymous example vocabularies (employee/customer/invoice/milestone). Tests - 26 new integration tests covering write/read/update/delete round-trips, counts rollup decrement + re-route on mutation, trackField + byField with and without perType, vocabulary whitelist enforcement, and migrateField for metadata.X → subtype and data.X → subtype paths including readBoth deprecation-window semantics. Unit suite: 1468/1468 passing. Type-check + build clean.
2026-06-04 17:24:36 -07:00
/**
* Per-product sub-classification within the NounType (e.g. a `Person` entity might have
* `subtype: 'employee'` or `'customer'`; a `Document` might have `subtype: 'invoice'`).
* Flat string, no hierarchy consumers choose the vocabulary. Indexed and rolled up into
* per-NounType statistics for fast `find({ type, subtype })` filtering and `groupBy:['subtype']`
* aggregation on the standard-field fast path.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field). Omit (or pass `'public'`) for normal
* data that is counted and returned everywhere. Pass `'internal'` for app-internal
* data that should be hidden from default `find()` / counts / `stats()` but stay
* retrievable via `find({ includeInternal: true })`. The `'system'` tier is reserved
* for Brainy's own plumbing and is intentionally not accepted here. Stored only when
* not `'public'`.
*/
visibility?: 'public' | 'internal'
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
/**
* Structured queryable fields indexed by MetadataIndex, used in `where` filters.
*
* Reserved entity fields (`RESERVED_ENTITY_FIELDS` `noun`, `subtype`, `visibility`,
* `createdAt`, `updatedAt`, `confidence`, `weight`, `service`, `data`, `createdBy`,
* `_rev`) may NOT appear here they have dedicated top-level params and the type makes
* a literal reserved key a compile error. Untyped (JavaScript) callers that pass one
* anyway are normalized at write time: user-settable fields remap to their top-level
* param (top-level wins when both are supplied), system-managed fields are dropped with
* a one-shot warning.
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
*/
metadata?: EntityMetadataInput<T>
/** Custom entity ID (auto-generated UUID v4 if not provided) */
id?: string
/** Pre-computed embedding vector (skips auto-embedding when provided) */
vector?: Vector
/** Multi-tenancy service identifier */
service?: string
/** Type classification confidence (0-1) */
confidence?: number
/** Entity importance/salience (0-1) */
weight?: number
/** Track which augmentation created this entity */
createdBy?: { augmentation: string; version: string }
feat: per-entity _rev + update({ ifRev }) CAS + add({ ifAbsent }) Optimistic concurrency for multi-writer coordination — the read-then-CAS lock pattern + idempotent-bootstrap singleton inserts. Lands the surface the SDK scheduler asked for in BRAINY-EXPOSE-TRANSACTIONS, scoped to what ships cleanly without painting the 8.0 transact() API into a corner. A. PER-ENTITY _rev FIELD - src/coreTypes.ts — HNSWNounWithMetadata gains optional `_rev?: number`; STANDARD_ENTITY_FIELDS includes it so resolveEntityField routes correctly. - src/types/brainy.types.ts — Entity<T> gains optional `_rev?: number`; Result<T> mirrors it on the convenience flatten layer. - src/brainy.ts add() — initializes `_rev: 1` in storageMetadata. New entities always start at 1. - src/brainy.ts update() — reads currentRev off the persisted metadata (falls back to 1 for pre-7.31.0 entities with no _rev), writes `_rev: currentRev + 1` into the updated metadata. Every successful update() bumps by exactly 1. - src/brainy.ts convertNounToEntity + convertMetadataToEntity — both pull _rev out of the storage metadata and surface it at the top level of Entity. Pre-7.31.0 entities (no _rev in storage) read as `_rev: 1` so consumers see a consistent value. - src/storage/baseStorage.ts — six destructure sites updated to pull _rev out of the metadata bag so it doesn't leak into customMetadata. Noun-side returns surface _rev; verb-side destructures correctly but doesn't expose it on HNSWVerbWithMetadata (verb CAS is future work). - src/brainy.ts createResult() — flattens entity._rev onto the Result for backward compat with the existing convenience-field layer. B. update({ ifRev }) OPTIMISTIC CONCURRENCY - src/types/brainy.types.ts — UpdateParams<T> gains optional `ifRev?: number`. - src/transaction/RevisionConflictError.ts (NEW) — carries `{ id, expected, actual }`. Message names the recipe: refetch with brain.get() and retry with the latest _rev. Subclass of Error. - src/brainy.ts update() — when params.ifRev is provided, compares against currentRev (the rev we just read) and throws RevisionConflictError on mismatch before any storage write. Omitting ifRev keeps the prior unconditional-update behavior; existing consumers see no change. - src/transaction/index.ts — exports RevisionConflictError. - src/index.ts — public export of RevisionConflictError. C. add({ ifAbsent }) BY-ID IDEMPOTENT INSERT - src/types/brainy.types.ts — AddParams<T> gains optional `ifAbsent?: boolean`; AddManyParams<T> mirrors it as a batch-level flag. - src/brainy.ts add() — when params.id AND params.ifAbsent, pre-reads storage.getNounMetadata(id); if present, returns the existing id without writing. No throw, no overwrite. Ignored when id is omitted (a fresh UUID can never collide). - src/brainy.ts addMany() — propagates the batch-level ifAbsent to each item's add() call. Per-item ifAbsent takes precedence so callers can override individual rows. WHAT'S NOT SHIPPED (and why) A public brain.transaction(fn) wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes (SaveNounMetadata, etc.) that take StorageAdapter as a constructor argument. A clean high-level facade in 7.31.0 would have meant either: (a) Delegate to brain.add() / update() / relate(). Each of those opens its own internal transaction and commits before the closure returns. Looks atomic, isn't — a footgun. (b) Thread an optional `tx?` through every internal write site in brainy.ts (8 transactionManager.executeTransaction sites). ~2 days of real refactor with regression surface, and the API shape changes again in 8.0 anyway. The SDK scheduler's actual ask (BRAINY-EXPOSE-TRANSACTIONS) is the read-then-CAS lock pattern. _rev + ifRev solves it completely. Multi-write atomicity is the 8.0 brain.transact() use case where the Datomic-style immutable Db makes it atomic by construction — that's the right home. TESTS - New tests/integration/rev-and-ifabsent.test.ts (18 tests): - _rev initialization (1 on add) and surface on get fast/full paths + find - _rev auto-bump on update across multiple writes - update({ ifRev }) pass / fail / message format / omitted / legacy-no-rev - add({ ifAbsent }) writes when absent / no-op when present / id-required - addMany({ ifAbsent }) propagation + per-item override - SDK-scheduler scenario: two concurrent CAS updates, one wins one throws - Unit suite unchanged: 1468/1468. - All integration subtype + verb + strict + find-limits + new rev suites pass: 96/96. DOCS - New docs/guides/optimistic-concurrency.md (public: true) — full reference: the lock pattern, read-modify-write retry, idempotent bootstrap, how _rev interacts with brain.versions, branches/fork, and VFS, what's coming in 8.0. - docs/api/README.md — add() and update() entries get the new params + tips pointing at the new guide. - RELEASES.md v7.31.0 entry. CORTEX COMPATIBILITY Zero changes required. _rev is a metadata column already supported by NativeColumnStore. The auto-bump runs in Brainy JS before any storage call; Cortex never sees the per-entity counter. 8.0 FORWARD-COMPAT _rev, ifRev, RevisionConflictError, and ifAbsent survive the 8.0 Db redesign unchanged. 8.0 layers brain.transact(tx, { ifAtGeneration }) for whole-tx CAS on top of the same per-entity mechanism — per-entity for single-record patterns (job locks, idempotent state machines), generation-based for "did the world move under me." Locked in .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md § C-6. Verification - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit - All integration suites including new rev-and-ifabsent: 96/96 - npm run build: clean - Closed-source product reference audit: clean
2026-06-09 10:04:24 -07:00
/**
* Conditional insert. When `true` AND a custom `id` is supplied AND an entity with
* that `id` already exists, `add()` returns the existing `id` without writing no
* throw, no overwrite. Used for idempotent bootstrap and create-or-noop patterns.
* Ignored when `id` is omitted (a freshly generated UUID can never collide).
*/
ifAbsent?: boolean
/**
* Create-or-update in a single call. When `true` AND a custom `id` is supplied AND
* an entity with that `id` already exists, `add()` applies the supplied fields as an
* UPDATE instead of the default destructive overwrite: metadata is MERGED (not
* replaced), `data` re-embeds only when it changed, `_rev` is bumped, and the
* original `createdAt` is PRESERVED. When no entity with that `id` exists, `add()`
* inserts normally (a fresh `_rev` of 1). Ignored behaves as a plain insert when
* `id` is omitted, since a freshly generated UUID can never collide.
*
* Mutually exclusive with {@link AddParams.ifAbsent}: `ifAbsent` SKIPS an existing
* entity, `upsert` MERGES into it supplying both throws.
*
* @example
* // First call inserts; a later call with the same id merges + bumps _rev.
* await brain.add({ id: 'customer-42', type: 'customer', data: 'Acme', upsert: true })
* await brain.add({ id: 'customer-42', type: 'customer', metadata: { tier: 'gold' }, upsert: true })
*/
upsert?: boolean
}
/**
* Parameters for updating entities
*/
export interface UpdateParams<T = any> {
id: string // Entity to update
data?: any // New content to re-embed
type?: NounType // Change type
feat: subtype top-level field + trackField + migrateField Promotes `subtype?: string` to a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy — the consumer-chosen vocabulary for sub-classifying entities within a NounType (Person → employee/customer, Document → invoice/contract, etc.). Layer 1 — subtype field + rollup - HNSWNounWithMetadata.subtype + STANDARD_ENTITY_FIELDS entry - Entity / Result / AddParams / UpdateParams / FindParams threading - add()/update() persist subtype on storageMetadata + entityForIndexing - get()/find() route through the standard-field fast path - subtypeCountsByType (Map<NounTypeIdx, Map<subtype, count>>) on BaseStorage, mirrored after nounCountsByType with the same self-heal rebuild and persisted to _system/subtype-statistics.json - brain.counts.bySubtype(type, subtype?) — O(1) point + breakdown - brain.counts.topSubtypes(type, n) — top-N by count - brain.subtypesOf(type) — distinct subtypes seen - find({ type, subtype }) and find({ subtype: ['a','b'] }) on the fast path Layer 2 — trackField for other facets - brain.trackField(name, { perType?, values? }) registers a field for cardinality + per-NounType breakdown stats. Backed by the aggregation engine (auto-defines __fieldCounts__<name>), backfill-on-define applies. - brain.counts.byField(name, { type? }) returns value frequencies - Optional vocabulary whitelist rejects off-vocabulary writes at add/update Layer 3 — generic migrateField - brain.migrateField({ from, to, readBoth?, batchSize?, onProgress? }) streams every entity, copies the value from one path to another, and (unless readBoth) clears the source. Supports top-level standard fields, metadata.X, and data.X paths. Idempotent — safe to re-run. Docs - New guide: docs/guides/subtypes-and-facets.md (Layer 1 + 2 + 3) - README, DATA_MODEL, QUERY_OPERATORS, api/README, finite-type-system, quick-start all treat subtype as a core primitive with anonymous example vocabularies (employee/customer/invoice/milestone). Tests - 26 new integration tests covering write/read/update/delete round-trips, counts rollup decrement + re-route on mutation, trackField + byField with and without perType, vocabulary whitelist enforcement, and migrateField for metadata.X → subtype and data.X → subtype paths including readBoth deprecation-window semantics. Unit suite: 1468/1468 passing. Type-check + build clean.
2026-06-04 17:24:36 -07:00
subtype?: string // Change subtype (set to '' or null-equivalent via dedicated unset is future work)
/**
* Change the visibility tier. Omit to preserve the existing value. Toggling between
* `'public'` and `'internal'` moves the entity in/out of the default-visible counts and
* `find()` results. The `'system'` tier is Brainy-internal (e.g. re-asserted on the VFS
* root during repair) consumer code should only ever use `'public'` / `'internal'`.
*/
visibility?: EntityVisibility
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
/**
* Metadata fields to merge (or replace when `merge: false`). Reserved entity
* fields (`RESERVED_ENTITY_FIELDS`) may NOT appear here `confidence` /
* `weight` / `subtype` / `visibility` have dedicated params on this call, and the rest
* are system-managed. A literal reserved key is a compile error; untyped callers
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
* are normalized at write time (remap user-settable, drop system-managed
* with a one-shot warning).
*/
metadata?: EntityMetadataPatch<T>
merge?: boolean // Merge or replace metadata (default: true)
vector?: Vector // New pre-computed vector
feat: add confidence/weight to Entity and flatten Result fields for convenient access Add confidence and weight properties to Entity interface and flatten Result fields to top level for improved developer experience and API consistency. Breaking Changes: None (all changes are backward compatible) Phase 2 - Entity Confidence & Weight: - Add confidence (type classification certainty) and weight (entity importance) to Entity interface - Add confidence/weight parameters to AddParams and UpdateParams - Update convertNounToEntity() to extract confidence/weight from storage - Update add() and update() methods to preserve confidence/weight in metadata - Enable developers to specify and access entity confidence/weight scores Phase 3 - Result Field Flattening: - Flatten commonly-used entity fields (type, metadata, data, confidence, weight) to Result top level - Add createResult() helper for consistent Result construction - Update all find() code paths to use createResult() - Enable direct access: result.metadata instead of result.entity.metadata - Preserve full entity in result.entity for backward compatibility VFS Fix (from previous work): - Fix VFSStructureGenerator to use brain.vfs() cached instance instead of creating separate instance - Improve VFS error messages with step-by-step guidance - Update examples to show correct vfs.init() usage - Add comprehensive VFS import verification tests Documentation Updates: - Update API_REFERENCE.md with confidence/weight examples and flattened Result documentation - Enhance JSDoc for add(), get(), find(), similar() with v4.3.0 examples - Document Result structure changes and backward compatibility - Add migration examples showing both old and new access patterns Tests: - Add 16 comprehensive tests for Entity confidence/weight exposure - Add tests for Result field flattening - Add tests for backward compatibility - All tests passing (16/16) API Consistency: - Entity: direct access to confidence/weight - Result: flattened fields + nested entity (both work) - Relation: already had confidence/weight (consistent) - VFS: inherits from Entity (automatic) Files Changed: - src/types/brainy.types.ts - Updated Entity, AddParams, UpdateParams, Result interfaces - src/brainy.ts - Updated implementation and JSDoc for all affected methods - tests/integration/entity-confidence-weight.test.ts - 16 comprehensive tests - docs/API_REFERENCE.md - Updated with v4.3.0 examples - src/importers/VFSStructureGenerator.ts - VFS fix - src/vfs/VirtualFileSystem.ts - Improved error messages - examples/unified-import-example.ts - Added vfs.init() example - tests/integration/vfs-*-verification.test.ts - VFS verification tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:19:50 -07:00
confidence?: number // Update type classification confidence
weight?: number // Update entity importance/salience
feat: per-entity _rev + update({ ifRev }) CAS + add({ ifAbsent }) Optimistic concurrency for multi-writer coordination — the read-then-CAS lock pattern + idempotent-bootstrap singleton inserts. Lands the surface the SDK scheduler asked for in BRAINY-EXPOSE-TRANSACTIONS, scoped to what ships cleanly without painting the 8.0 transact() API into a corner. A. PER-ENTITY _rev FIELD - src/coreTypes.ts — HNSWNounWithMetadata gains optional `_rev?: number`; STANDARD_ENTITY_FIELDS includes it so resolveEntityField routes correctly. - src/types/brainy.types.ts — Entity<T> gains optional `_rev?: number`; Result<T> mirrors it on the convenience flatten layer. - src/brainy.ts add() — initializes `_rev: 1` in storageMetadata. New entities always start at 1. - src/brainy.ts update() — reads currentRev off the persisted metadata (falls back to 1 for pre-7.31.0 entities with no _rev), writes `_rev: currentRev + 1` into the updated metadata. Every successful update() bumps by exactly 1. - src/brainy.ts convertNounToEntity + convertMetadataToEntity — both pull _rev out of the storage metadata and surface it at the top level of Entity. Pre-7.31.0 entities (no _rev in storage) read as `_rev: 1` so consumers see a consistent value. - src/storage/baseStorage.ts — six destructure sites updated to pull _rev out of the metadata bag so it doesn't leak into customMetadata. Noun-side returns surface _rev; verb-side destructures correctly but doesn't expose it on HNSWVerbWithMetadata (verb CAS is future work). - src/brainy.ts createResult() — flattens entity._rev onto the Result for backward compat with the existing convenience-field layer. B. update({ ifRev }) OPTIMISTIC CONCURRENCY - src/types/brainy.types.ts — UpdateParams<T> gains optional `ifRev?: number`. - src/transaction/RevisionConflictError.ts (NEW) — carries `{ id, expected, actual }`. Message names the recipe: refetch with brain.get() and retry with the latest _rev. Subclass of Error. - src/brainy.ts update() — when params.ifRev is provided, compares against currentRev (the rev we just read) and throws RevisionConflictError on mismatch before any storage write. Omitting ifRev keeps the prior unconditional-update behavior; existing consumers see no change. - src/transaction/index.ts — exports RevisionConflictError. - src/index.ts — public export of RevisionConflictError. C. add({ ifAbsent }) BY-ID IDEMPOTENT INSERT - src/types/brainy.types.ts — AddParams<T> gains optional `ifAbsent?: boolean`; AddManyParams<T> mirrors it as a batch-level flag. - src/brainy.ts add() — when params.id AND params.ifAbsent, pre-reads storage.getNounMetadata(id); if present, returns the existing id without writing. No throw, no overwrite. Ignored when id is omitted (a fresh UUID can never collide). - src/brainy.ts addMany() — propagates the batch-level ifAbsent to each item's add() call. Per-item ifAbsent takes precedence so callers can override individual rows. WHAT'S NOT SHIPPED (and why) A public brain.transaction(fn) wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes (SaveNounMetadata, etc.) that take StorageAdapter as a constructor argument. A clean high-level facade in 7.31.0 would have meant either: (a) Delegate to brain.add() / update() / relate(). Each of those opens its own internal transaction and commits before the closure returns. Looks atomic, isn't — a footgun. (b) Thread an optional `tx?` through every internal write site in brainy.ts (8 transactionManager.executeTransaction sites). ~2 days of real refactor with regression surface, and the API shape changes again in 8.0 anyway. The SDK scheduler's actual ask (BRAINY-EXPOSE-TRANSACTIONS) is the read-then-CAS lock pattern. _rev + ifRev solves it completely. Multi-write atomicity is the 8.0 brain.transact() use case where the Datomic-style immutable Db makes it atomic by construction — that's the right home. TESTS - New tests/integration/rev-and-ifabsent.test.ts (18 tests): - _rev initialization (1 on add) and surface on get fast/full paths + find - _rev auto-bump on update across multiple writes - update({ ifRev }) pass / fail / message format / omitted / legacy-no-rev - add({ ifAbsent }) writes when absent / no-op when present / id-required - addMany({ ifAbsent }) propagation + per-item override - SDK-scheduler scenario: two concurrent CAS updates, one wins one throws - Unit suite unchanged: 1468/1468. - All integration subtype + verb + strict + find-limits + new rev suites pass: 96/96. DOCS - New docs/guides/optimistic-concurrency.md (public: true) — full reference: the lock pattern, read-modify-write retry, idempotent bootstrap, how _rev interacts with brain.versions, branches/fork, and VFS, what's coming in 8.0. - docs/api/README.md — add() and update() entries get the new params + tips pointing at the new guide. - RELEASES.md v7.31.0 entry. CORTEX COMPATIBILITY Zero changes required. _rev is a metadata column already supported by NativeColumnStore. The auto-bump runs in Brainy JS before any storage call; Cortex never sees the per-entity counter. 8.0 FORWARD-COMPAT _rev, ifRev, RevisionConflictError, and ifAbsent survive the 8.0 Db redesign unchanged. 8.0 layers brain.transact(tx, { ifAtGeneration }) for whole-tx CAS on top of the same per-entity mechanism — per-entity for single-record patterns (job locks, idempotent state machines), generation-based for "did the world move under me." Locked in .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md § C-6. Verification - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit - All integration suites including new rev-and-ifabsent: 96/96 - npm run build: clean - Closed-source product reference audit: clean
2026-06-09 10:04:24 -07:00
/**
* Optimistic concurrency check. When provided, the update fails with
* `RevisionConflictError` if the persisted entity's `_rev` does not equal `ifRev`.
* `_rev` is auto-bumped on every successful update read it from the entity returned
* by `get()` / `find()` / `search()`. Omit to skip the check (unconditional update).
*/
ifRev?: number
}
/**
* Parameters for creating relationships
*
* **Data vs Metadata (on relationships):**
* - `data`: Opaque content for the edge. If provided, overrides the auto-computed
* vector (default: average of source+target entity vectors).
* - `metadata`: Structured queryable fields on the edge.
*/
export interface RelateParams<T = any> {
/** Source entity ID (required — must exist) */
from: string
/** Target entity ID (required — must exist) */
to: string
/** Relationship type classification (required) */
type: VerbType
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
/**
* Per-product sub-classification within the VerbType (e.g. a `ReportsTo`
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
* 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
* (`related({ verb, subtype })`) and aggregation (`groupBy:['subtype']`).
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
*/
subtype?: string
/**
* Visibility tier (reserved top-level field), the verb mirror of `AddParams.visibility`.
* Omit (or pass `'public'`) for normal edges that are counted and returned everywhere.
* Pass `'internal'` for app-internal edges hidden from default `related()` / counts /
* `stats()` but retrievable via `related({ includeInternal: true })`. The `'system'`
* tier is reserved for Brainy's own plumbing and is not accepted here. Stored only when
* not `'public'`.
*/
visibility?: 'public' | 'internal'
/** Connection strength (0-1, default: 1.0) */
weight?: number
/** Content for the relationship (optional — overrides auto-computed vector) */
data?: any
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
/**
* Structured queryable fields on the edge. Reserved relationship fields
* (`RESERVED_RELATION_FIELDS` `verb`, `subtype`, `visibility`, `createdAt`,
* `updatedAt`, `confidence`, `weight`, `service`, `data`, `createdBy`, `_rev`) may NOT
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
* appear here they have dedicated params. A literal reserved key is a
* compile error; untyped callers are normalized at write time.
*/
metadata?: RelationMetadataInput<T>
/** Create reverse edge too (default: false) */
bidirectional?: boolean
/** Multi-tenancy service identifier */
service?: string
/** Relationship certainty (0-1) */
confidence?: number
/** Evidence for why this relationship exists */
evidence?: RelationEvidence
}
/**
* Parameters for updating relationships
*/
export interface UpdateRelationParams<T = any> {
id: string // Relation to update
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
type?: VerbType // Change verb type
subtype?: string // Change sub-classification (omit to preserve existing)
/**
* Change the visibility tier. Omit to preserve the existing value. The verb mirror of
* `UpdateParams.visibility`; `'system'` is Brainy-internal consumer code should only
* use `'public'` / `'internal'`.
*/
visibility?: EntityVisibility
weight?: number // New weight
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
confidence?: number // New confidence (0-1)
data?: any // New content
feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write Brainy-owned field names (noun/verb, subtype, createdAt, updatedAt, confidence, weight, service, data, createdBy, _rev) now have exactly one home — top level — enforced by three layers driven from a single source of truth, src/types/reservedFields.ts (RESERVED_ENTITY_FIELDS / RESERVED_RELATION_FIELDS, exported): 1. Compile time — AddParams/UpdateParams/RelateParams/UpdateRelationParams metadata (and the transact() ops that extend them) reject a literal reserved key as a TypeScript error while keeping generic T ergonomics (typed bags, untyped brains, index-signature shapes, and a documented exemption for T-declared reserved keys). Pinned by @ts-expect-error type tests run under vitest typecheck mode on every unit run. 2. Write time — the 7.x update() remap is ported to 8.0 and extended to every write path: add/update/relate/updateRelation, their transact() mirrors, and db.with() overlays. User-settable fields lift to their dedicated param (top-level wins when both are supplied — closes the 7.x trap where update({metadata:{confidence}}) silently no-oped), and system-managed fields drop with a one-shot warning naming the right path. A remapped subtype satisfies subtype-pairing enforcement exactly like a top-level one. 3. Read time — every storage combine goes through one canonical hydration helper (hydrateNounWithMetadata / hydrateVerbWithMetadata over splitNoun/VerbMetadataRecord), so reserved fields surface ONLY top-level and entity/relation.metadata carry ONLY custom fields on live reads, batch reads, paginated listings, getRelations by source/target, streamed verbs, and historical asOf() materialization alike. Read-path echoes found and fixed (previously the full stored record — including the verb type key — leaked inside metadata): noun pagination, verb pagination, getVerbsBySource/ByTarget (adjacency + shard fallback), getVerbsBySourceBatch (which also dropped subtype/data), and the filesystem verb stream. getRelations() results now surface confidence/updatedAt top-level via verbsToRelations, updateRelation() no longer erases service/createdBy, relate() persists its top-level confidence/service params, and the dead convertHNSWVerbToGraphVerb echo path is deleted. Import paths (CLI extract, deduplicator, coordinators, neural import) write confidence through the dedicated param instead of the bag. UpdateRelationParams is now exported from the package root. Documented for consumers in docs/concepts/consistency-model.md ("Reserved fields") and RELEASES.md. Regression tests ported from the 7.x fix and extended to the full 8.0 contract (17 runtime tests + 41 type-level assertions); full unit suite 1427/1427, db-mvcc integration 24/24.
2026-06-11 13:12:50 -07:00
/**
* Metadata fields to merge (or replace when `merge: false`). Reserved
* relationship fields (`RESERVED_RELATION_FIELDS`) may NOT appear here
* a literal reserved key is a compile error; untyped callers are
* normalized at write time.
*/
metadata?: RelationMetadataPatch<T>
merge?: boolean // Merge or replace metadata
}
// ============= Query Parameters =============
/**
* Unified find parameters Triple Intelligence search
*
* Combines three search dimensions in one query:
* - **Vector:** `query` or `vector` for semantic/hybrid similarity search (searches `data`)
* - **Metadata:** `where` for structured field filters (queries `metadata` via MetadataIndex)
* - **Graph:** `connected` for relationship traversal (via GraphAdjacencyIndex)
*
* See also: [Query Operators](../../docs/QUERY_OPERATORS.md) for all `where` operators.
*/
export interface FindParams<T = any> {
// Vector Intelligence
/** Natural language or semantic search query (embedded and matched via HNSW + text index) */
query?: string
/** Direct vector search (pre-computed embedding) */
vector?: Vector
// Metadata Intelligence
/** Filter by entity type(s). Alias for `where.noun`. */
type?: NounType | NounType[]
feat: subtype top-level field + trackField + migrateField Promotes `subtype?: string` to a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy — the consumer-chosen vocabulary for sub-classifying entities within a NounType (Person → employee/customer, Document → invoice/contract, etc.). Layer 1 — subtype field + rollup - HNSWNounWithMetadata.subtype + STANDARD_ENTITY_FIELDS entry - Entity / Result / AddParams / UpdateParams / FindParams threading - add()/update() persist subtype on storageMetadata + entityForIndexing - get()/find() route through the standard-field fast path - subtypeCountsByType (Map<NounTypeIdx, Map<subtype, count>>) on BaseStorage, mirrored after nounCountsByType with the same self-heal rebuild and persisted to _system/subtype-statistics.json - brain.counts.bySubtype(type, subtype?) — O(1) point + breakdown - brain.counts.topSubtypes(type, n) — top-N by count - brain.subtypesOf(type) — distinct subtypes seen - find({ type, subtype }) and find({ subtype: ['a','b'] }) on the fast path Layer 2 — trackField for other facets - brain.trackField(name, { perType?, values? }) registers a field for cardinality + per-NounType breakdown stats. Backed by the aggregation engine (auto-defines __fieldCounts__<name>), backfill-on-define applies. - brain.counts.byField(name, { type? }) returns value frequencies - Optional vocabulary whitelist rejects off-vocabulary writes at add/update Layer 3 — generic migrateField - brain.migrateField({ from, to, readBoth?, batchSize?, onProgress? }) streams every entity, copies the value from one path to another, and (unless readBoth) clears the source. Supports top-level standard fields, metadata.X, and data.X paths. Idempotent — safe to re-run. Docs - New guide: docs/guides/subtypes-and-facets.md (Layer 1 + 2 + 3) - README, DATA_MODEL, QUERY_OPERATORS, api/README, finite-type-system, quick-start all treat subtype as a core primitive with anonymous example vocabularies (employee/customer/invoice/milestone). Tests - 26 new integration tests covering write/read/update/delete round-trips, counts rollup decrement + re-route on mutation, trackField + byField with and without perType, vocabulary whitelist enforcement, and migrateField for metadata.X → subtype and data.X → subtype paths including readBoth deprecation-window semantics. Unit suite: 1468/1468 passing. Type-check + build clean.
2026-06-04 17:24:36 -07:00
/**
* Filter by per-product subtype (top-level standard field uses the fast path, not the
* metadata fallback). Pass a single string for equality, or an array for set membership
* (e.g. `subtype: ['employee', 'contractor']`). For operator-form predicates (e.g.
* `{ exists: true }`, `{ missing: true }`) use `where: { subtype: { …operators… } }`.
*/
subtype?: string | string[]
/** Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`) */
where?: Partial<T>
// Visibility
/**
* Also return `visibility: 'internal'` entities. By default `find()` returns ONLY
* public entities (those with no `visibility` field, or `visibility: 'public'`);
* app-internal entities are hidden. Set `true` to include them. Applied as a hard
* candidate filter, so `limit` / `offset` stay correct. Does NOT include `'system'`
* entities use `includeSystem` for those.
*/
includeInternal?: boolean
/**
* Also return `visibility: 'system'` entities (Brainy's own plumbing, e.g. the VFS
* root). Hidden by default and even when `includeInternal` is set. Set `true` only
* when you specifically need to see system entities. Applied as a hard candidate
* filter, so `limit` / `offset` stay correct.
*/
includeSystem?: boolean
// Graph Intelligence
connected?: GraphConstraints
// Proximity search
near?: {
id: string // Find near this entity
threshold?: number // Min similarity (0-1)
}
// Control options
limit?: number // Max results (default: 10)
offset?: number // Skip N results
cursor?: string // Cursor-based pagination
// Sorting
orderBy?: string // Field to sort by (e.g., 'createdAt', 'title', 'metadata.priority')
order?: 'asc' | 'desc' // Sort direction: 'asc' (default) or 'desc'
// Advanced options
mode?: SearchMode // Search strategy
explain?: boolean // Return scoring explanation
includeRelations?: boolean // Include entity relationships
excludeVFS?: boolean // Exclude VFS entities from results (default: false - VFS included)
service?: string // Multi-tenancy filter
/**
* Return each result's stored embedding under `entity.vector`. Defaults to `false`,
* in which case `entity.vector` is an empty array (the perf default vectors are
* large and most reads don't need them). Set `true` to get the persisted vector
* back from `find()` without a recompute, e.g. to feed downstream similarity math.
* Mirrors {@link GetOptions.includeVectors} on `get()`.
*/
includeVectors?: boolean
// Hybrid search options
searchMode?: 'auto' | 'text' | 'semantic' | 'vector' | 'hybrid' // Search strategy: auto (default), text-only, semantic/vector-only, or explicit hybrid
hybridAlpha?: number // Weight between text (0.0) and semantic (1.0) search, default: auto-detected by query length
// Triple Intelligence Fusion
fusion?: {
strategy?: 'adaptive' | 'weighted' | 'progressive'
weights?: {
vector?: number
graph?: number
field?: number
}
}
// Performance options
writeOnly?: boolean // Skip validation for high-speed ingestion
// Aggregation
/** Query a named aggregate definition. String shorthand or full query params. */
aggregate?: string | AggregateQueryParams
}
/**
* Graph constraints for search
*/
export interface GraphConstraints {
to?: string // Connected to this entity
from?: string // Connected from this entity
via?: VerbType | VerbType[] // Via these relationship types
type?: VerbType | VerbType[] // Alias for via
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
/**
* Filter traversal edges by VerbType subtype. Single string for equality,
* array for set membership. Composes with `via` `{ via: 'manages', subtype:
* 'direct' }` traverses only direct-management edges, not dotted-line. Edges
* without a subtype value are excluded when this filter is set.
*/
subtype?: string | string[]
depth?: number // Max traversal depth (default: 1)
direction?: 'in' | 'out' | 'both' // Direction of traversal (default: 'both')
bidirectional?: boolean // Consider both directions
}
/**
* Search modes
*/
export type SearchMode =
| 'auto' // Automatically choose best mode (default - enables hybrid text+semantic)
| 'vector' // Pure vector search (semantic only)
| 'semantic' // Alias for vector search
| 'text' // Pure text/keyword search
| 'metadata' // Pure metadata filtering
| 'graph' // Pure graph traversal
| 'hybrid' // Combine all intelligences (explicit hybrid mode)
/**
* Parameters for similarity search
*/
export interface SimilarParams<T = any> {
to: string | Entity<T> | Vector // Find similar to this
limit?: number // Max results (default: 10)
threshold?: number // Min similarity score
type?: NounType | NounType[] // Restrict to types
where?: Partial<T> // Additional filters
service?: string // Multi-tenancy
excludeVFS?: boolean // Exclude VFS entities (default: false - VFS included)
}
/**
* Parameters for `brain.related()` / `db.related()`
*
* All parameters are optional. When called without parameters, returns all relationships
* with pagination (default limit: 100).
*
* @example
* ```typescript
* // Get all relationships (default limit: 100)
* const all = await brain.related()
*
* // Get relationships from a specific entity (string shorthand)
* const fromEntity = await brain.related(entityId)
*
* // Equivalent to:
* const fromEntity2 = await brain.related({ from: entityId })
*
* // Get relationships to a specific entity
* const toEntity = await brain.related({ to: entityId })
*
* // Filter by relationship type
* const friends = await brain.related({ type: VerbType.FriendOf })
*
* // Pagination
* const page2 = await brain.related({ offset: 100, limit: 50 })
*
* // Combined filters
* const filtered = await brain.related({
* from: entityId,
* type: VerbType.WorksWith,
* limit: 20
* })
* ```
*
* Fixed bug where calling without parameters returned empty array
* Added string ID shorthand syntax
*/
export interface RelatedParams {
/**
* Filter by source entity ID
*
* Returns all relationships originating from this entity.
*/
from?: string
/**
* Filter by target entity ID
*
* Returns all relationships pointing to this entity.
*/
to?: string
/**
* Filter by INCIDENT entity ID every relationship touching this entity in
* EITHER direction (where it is the source OR the target), deduped. The
* one-call "all edges on a noun" query: equivalent to merging
* `related({ from: id })` and `related({ to: id })` but in a single
* O(degree) call. Mutually exclusive with `from` / `to` (pass `node` for
* both directions, or `from` / `to` for one). Composes with `type` /
* `subtype` / visibility filters.
*
* @example
* // Everything connected to this person, in or out.
* const edges = await brain.related({ node: personId })
*/
node?: string
/**
* Filter by relationship type(s)
*
* Can be a single VerbType or array of VerbTypes.
*/
type?: VerbType | VerbType[]
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
/**
* Filter by VerbType subtype
*
* Top-level standard field uses the fast path, not the metadata fallback.
* Pass a single string for equality (e.g. `subtype: 'direct'`), or an array
* for set membership (e.g. `subtype: ['direct', 'dotted-line']`).
*/
subtype?: string | string[]
/**
* Also return `visibility: 'internal'` relationships.
*
* By default `related()` returns ONLY public relationships (those with no
* `visibility` field, or `visibility: 'public'`); app-internal edges are hidden.
* Set `true` to include them. Applied as a hard candidate filter so `limit` /
* `offset` stay correct. Does NOT include `'system'` edges use `includeSystem`.
*/
includeInternal?: boolean
/**
* Also return `visibility: 'system'` relationships (Brainy's own plumbing).
*
* Hidden by default and even when `includeInternal` is set. Set `true` only when
* you specifically need system edges. Applied as a hard candidate filter so
* `limit` / `offset` stay correct.
*/
includeSystem?: boolean
/**
* Maximum number of results to return
*
* @default 100
*/
limit?: number
/**
* Number of results to skip (offset-based pagination)
*
* @default 0
*/
offset?: number
/**
* Cursor for cursor-based pagination
*
* More efficient than offset for large result sets.
*/
cursor?: string
/**
* Filter by service (multi-tenancy)
*
* Only return relationships belonging to this service.
*/
service?: string
}
// ============= Graph views (brain.graph.*) =============
/**
* A node in a {@link GraphView} a lightweight reference (id + classification +
* hop depth), NOT a full {@link Entity}. Graph reads are structure-first; resolve
* a node to its full entity with `get(id)` when you need its `data` / metadata.
*/
export interface GraphNode {
/** Entity id. */
id: string
/** Entity type (present when the view hydrates node classification). */
type?: NounType
/** Per-product subtype (present when hydrated). */
subtype?: string
/** Hop distance from the nearest seed (0 = a seed); present on subgraph results. */
depth?: number
}
/**
* A hydrated view of a region of the graph the discovered nodes plus the edges
* among them, in id form (the public face of the provider's columnar int form).
*/
export interface GraphView<T = any> {
/** The nodes in this region (seeds + everything reached). */
nodes: GraphNode[]
/** The edges among `nodes`, as full {@link Relation} objects. */
edges: Relation<T>[]
/** `true` when a `maxNodes` / `maxEdges` cap truncated the result. */
truncated?: boolean
}
/**
* Options for {@link GraphApi.subgraph}.
*/
export interface SubgraphOptions {
/** Max hop distance from any seed (default `1`). */
depth?: number
/** Edge direction to follow (default `'both'`). */
direction?: 'in' | 'out' | 'both'
/** Restrict traversed edges to these relationship type(s). */
type?: VerbType | VerbType[]
/** Restrict traversed edges to these subtype(s). */
subtype?: string | string[]
/** Also traverse through `internal`-visibility edges/nodes (hidden by default). */
includeInternal?: boolean
/** Also traverse through `system`-visibility edges/nodes (hidden by default). */
includeSystem?: boolean
/** Cap on returned nodes (sets `GraphView.truncated`). */
maxNodes?: number
/** Cap on returned edges. */
maxEdges?: number
/**
* Hydrate each node's `type` / `subtype` (one batch read). Default `true`;
* set `false` to skip it when you only need graph structure (ids + edges).
*/
hydrateNodes?: boolean
}
feat(8.0): brain.graph.export() + noun-walk cursor + noun visibility hydration brain.graph.export() streams the WHOLE graph in one O(N+E) pass — node chunks then edge chunks, async-iterable — the right primitive for visualizing all data (vs. paging per node). Native snapshot-consistent graphCursor when present, else a TS cursor walk over nouns + verbs. Building it surfaced two latent noun bugs, both fixed here (each a correctness win beyond export): - getNounsWithPagination IGNORED its cursor ('offset-based, cursor planned') — the noun mirror of the verb bug fixed in the cursor-pagination commit. Latent because the only multi-page consumer (aggregate backfill) uses one big page; a small chunkSize needed page 2 and, trusting the returned-but-ignored nextCursor, re-fetched page 0 forever (infinite loop). Ported the proven verb cursor: opaque cn1:<shard>:<id> token, stable within-shard id order, resume-after, O(N) at any chunk size. (Generalized verbIdFromVectorPath → idFromVectorPath.) - hydrateNounWithMetadata DROPPED 'visibility' (noun mirror of the Fix #1 verb hydration bug) — so getNouns-fed visibility filters saw nothing and leaked system (VFS root) / internal nodes. Now hydrated. - New: GraphApi.export + GraphExportOptions (chunkSize, includeInternal/System, includeNodes/Edges). hydrateNativeSubgraph extracted + shared by subgraph+export. Test: graph-export.test.ts — full-graph completeness incl. isolated nodes, default-hides-internal, node/edge include toggles, chunkSize chunking (the case that exposed the cursor hang). Full gate green.
2026-06-21 10:22:12 -07:00
/**
* Options for {@link GraphApi.export}.
*/
export interface GraphExportOptions {
/** Nodes/edges per streamed chunk (default `1000`). */
chunkSize?: number
/** Also include `internal`-visibility nodes/edges (hidden by default). */
includeInternal?: boolean
/** Also include `system`-visibility nodes/edges (hidden by default). */
includeSystem?: boolean
/** Stream the nodes (default `true`; set `false` to stream only edges). */
includeNodes?: boolean
/** Stream the edges (default `true`; set `false` to stream only nodes). */
includeEdges?: boolean
}
/**
* Options for {@link GraphApi.communities}.
*/
export interface GraphCommunitiesOptions {
/** Treat edges as directed when grouping (default `false` — direction-agnostic). */
directed?: boolean
/** Also group through `internal`-visibility nodes/edges (hidden by default). */
includeInternal?: boolean
/** Also group through `system`-visibility nodes/edges (hidden by default). */
includeSystem?: boolean
}
/**
* Result of {@link GraphApi.communities} the graph partitioned into connected
* groups of entity ids.
*/
export interface GraphCommunitiesResult {
/** Each group is the member entity ids; largest group first. */
groups: string[][]
/** Number of distinct groups (`= groups.length`). */
count: number
}
/**
* Options for {@link GraphApi.rank} (importance ranking). INTENT-level only the
* ranking ALGORITHM is the provider's choice (the TS fallback uses PageRank), so
* no algorithm-tuning knobs are exposed.
*/
export interface GraphRankOptions {
/** Return only the top-K nodes by score (default: all, descending). */
topK?: number
/** Also rank through `internal`-visibility nodes/edges (hidden by default). */
includeInternal?: boolean
/** Also rank through `system`-visibility nodes/edges (hidden by default). */
includeSystem?: boolean
}
/** One ranked entity from {@link GraphApi.rank}, descending by `score`. */
export interface GraphRankEntry {
/** The entity id. */
id: string
/** The importance score (relative; higher = more central). */
score: number
}
/**
* Options for {@link GraphApi.path} (best route between two entities).
*/
export interface GraphPathOptions {
/** Edge direction to follow (default `'both'`). */
direction?: 'in' | 'out' | 'both'
/** Optimize for fewest `'hops'` (default) or least summed edge `'weight'`. */
by?: 'hops' | 'weight'
/** Restrict the route to these relationship type(s). */
type?: VerbType | VerbType[]
/** Also route through `internal`-visibility nodes/edges (hidden by default). */
includeInternal?: boolean
/** Also route through `system`-visibility nodes/edges (hidden by default). */
includeSystem?: boolean
/** Abandon the search past this many hops. */
maxDepth?: number
}
/**
* Result of {@link GraphApi.path} the route from `from` to `to`, or `null` when
* unreachable.
*/
export interface GraphPathResult {
/** The entity ids along the route, `from` … `to` inclusive. */
nodes: string[]
/** The relationship (verb) ids traversed, one fewer than `nodes`. */
relationships: string[]
/** Route cost: number of hops, or summed edge weight when `by: 'weight'`. */
cost: number
}
/**
* The `brain.graph` namespace graph-shaped reads over the knowledge graph.
* Routes to a native {@link import('../plugin.js').GraphAccelerationProvider}
* when one is registered, otherwise serves the same results from Brainy's
* pure-TS adjacency (correct at small/medium scale).
*/
export interface GraphApi<T = any> {
/**
* @description Extract the subgraph reachable from one or more seed entities
* the bounded multi-hop neighborhood, as nodes + edges. The one-call answer to
* "show me everything around this node, N hops out".
* @param seeds - A seed entity id, or an array of them.
* @param options - Depth, direction, edge/visibility filters, and caps.
* @returns The hydrated {@link GraphView}.
* @example
* const view = await brain.graph.subgraph(personId, { depth: 2 })
* // view.nodes = the 2-hop neighborhood; view.edges = the relations among them
*/
subgraph(seeds: string | string[], options?: SubgraphOptions): Promise<GraphView<T>>
feat(8.0): brain.graph.export() + noun-walk cursor + noun visibility hydration brain.graph.export() streams the WHOLE graph in one O(N+E) pass — node chunks then edge chunks, async-iterable — the right primitive for visualizing all data (vs. paging per node). Native snapshot-consistent graphCursor when present, else a TS cursor walk over nouns + verbs. Building it surfaced two latent noun bugs, both fixed here (each a correctness win beyond export): - getNounsWithPagination IGNORED its cursor ('offset-based, cursor planned') — the noun mirror of the verb bug fixed in the cursor-pagination commit. Latent because the only multi-page consumer (aggregate backfill) uses one big page; a small chunkSize needed page 2 and, trusting the returned-but-ignored nextCursor, re-fetched page 0 forever (infinite loop). Ported the proven verb cursor: opaque cn1:<shard>:<id> token, stable within-shard id order, resume-after, O(N) at any chunk size. (Generalized verbIdFromVectorPath → idFromVectorPath.) - hydrateNounWithMetadata DROPPED 'visibility' (noun mirror of the Fix #1 verb hydration bug) — so getNouns-fed visibility filters saw nothing and leaked system (VFS root) / internal nodes. Now hydrated. - New: GraphApi.export + GraphExportOptions (chunkSize, includeInternal/System, includeNodes/Edges). hydrateNativeSubgraph extracted + shared by subgraph+export. Test: graph-export.test.ts — full-graph completeness incl. isolated nodes, default-hides-internal, node/edge include toggles, chunkSize chunking (the case that exposed the cursor hang). Full gate green.
2026-06-21 10:22:12 -07:00
/**
* @description Stream the WHOLE graph in one O(N+E) pass as a sequence of
* {@link GraphView} chunks the right primitive for visualizing all data
* (vs. paging per node). Node chunks come first, then edge chunks; assemble
* them on the consumer side. Backed by cursor pagination, so a full walk is
* O(N+E) at any chunk size (no re-scan).
* @param options - Chunk size, visibility, and node/edge inclusion toggles.
* @returns An async-iterable of graph chunks.
* @example
* for await (const chunk of brain.graph.export()) {
* addNodes(chunk.nodes); addEdges(chunk.edges)
* }
*/
export(options?: GraphExportOptions): AsyncIterable<GraphView<T>>
/**
* @description Rank entities by graph importance (influence / centrality) the
* one-call answer to "which nodes matter most". The TS fallback uses PageRank;
* a native provider may use personalized PageRank / eigenvector centrality.
* @param options - `topK`, visibility opt-ins.
* @returns Entities with scores, DESCENDING (most important first).
* @example
* const top = await brain.graph.rank({ topK: 10 })
* top[0] // { id, score } — the most central entity
*/
rank(options?: GraphRankOptions): Promise<GraphRankEntry[]>
/**
* @description Partition the graph into connected communities (clusters of
* related entities) "which things group together".
* @param options - `directed`, visibility opt-ins.
* @returns The groups (member ids) + their count.
* @example
* const { groups, count } = await brain.graph.communities()
*/
communities(options?: GraphCommunitiesOptions): Promise<GraphCommunitiesResult>
/**
* @description Find the best route between two entities fewest hops (default)
* or least summed edge weight (`by: 'weight'`). The pathfinding ALGORITHM is
* the provider's choice (TS fallback: BFS for hops, Dijkstra for weight).
* @param from - Start entity id (natural keys are resolved).
* @param to - End entity id.
* @param options - Direction, `by`, type filter, `maxDepth`, visibility opt-ins.
* @returns The route (`nodes` + `relationships` + `cost`), or `null` if unreachable.
* @example
* const route = await brain.graph.path(aliceId, bobId)
* route?.nodes // [aliceId, …, bobId]
*/
path(from: string, to: string, options?: GraphPathOptions): Promise<GraphPathResult | null>
}
// ============= Batch Operations =============
/**
* Batch add parameters
*/
export interface AddManyParams<T = any> {
items: AddParams<T>[] // Items to add
parallel?: boolean // Process in parallel (default: true)
chunkSize?: number // Batch size (default: 100)
onProgress?: (done: number, total: number) => void
continueOnError?: boolean // Continue if some fail
feat: per-entity _rev + update({ ifRev }) CAS + add({ ifAbsent }) Optimistic concurrency for multi-writer coordination — the read-then-CAS lock pattern + idempotent-bootstrap singleton inserts. Lands the surface the SDK scheduler asked for in BRAINY-EXPOSE-TRANSACTIONS, scoped to what ships cleanly without painting the 8.0 transact() API into a corner. A. PER-ENTITY _rev FIELD - src/coreTypes.ts — HNSWNounWithMetadata gains optional `_rev?: number`; STANDARD_ENTITY_FIELDS includes it so resolveEntityField routes correctly. - src/types/brainy.types.ts — Entity<T> gains optional `_rev?: number`; Result<T> mirrors it on the convenience flatten layer. - src/brainy.ts add() — initializes `_rev: 1` in storageMetadata. New entities always start at 1. - src/brainy.ts update() — reads currentRev off the persisted metadata (falls back to 1 for pre-7.31.0 entities with no _rev), writes `_rev: currentRev + 1` into the updated metadata. Every successful update() bumps by exactly 1. - src/brainy.ts convertNounToEntity + convertMetadataToEntity — both pull _rev out of the storage metadata and surface it at the top level of Entity. Pre-7.31.0 entities (no _rev in storage) read as `_rev: 1` so consumers see a consistent value. - src/storage/baseStorage.ts — six destructure sites updated to pull _rev out of the metadata bag so it doesn't leak into customMetadata. Noun-side returns surface _rev; verb-side destructures correctly but doesn't expose it on HNSWVerbWithMetadata (verb CAS is future work). - src/brainy.ts createResult() — flattens entity._rev onto the Result for backward compat with the existing convenience-field layer. B. update({ ifRev }) OPTIMISTIC CONCURRENCY - src/types/brainy.types.ts — UpdateParams<T> gains optional `ifRev?: number`. - src/transaction/RevisionConflictError.ts (NEW) — carries `{ id, expected, actual }`. Message names the recipe: refetch with brain.get() and retry with the latest _rev. Subclass of Error. - src/brainy.ts update() — when params.ifRev is provided, compares against currentRev (the rev we just read) and throws RevisionConflictError on mismatch before any storage write. Omitting ifRev keeps the prior unconditional-update behavior; existing consumers see no change. - src/transaction/index.ts — exports RevisionConflictError. - src/index.ts — public export of RevisionConflictError. C. add({ ifAbsent }) BY-ID IDEMPOTENT INSERT - src/types/brainy.types.ts — AddParams<T> gains optional `ifAbsent?: boolean`; AddManyParams<T> mirrors it as a batch-level flag. - src/brainy.ts add() — when params.id AND params.ifAbsent, pre-reads storage.getNounMetadata(id); if present, returns the existing id without writing. No throw, no overwrite. Ignored when id is omitted (a fresh UUID can never collide). - src/brainy.ts addMany() — propagates the batch-level ifAbsent to each item's add() call. Per-item ifAbsent takes precedence so callers can override individual rows. WHAT'S NOT SHIPPED (and why) A public brain.transaction(fn) wrapper was on the table but was cut. The internal TransactionManager exposes raw Operation classes (SaveNounMetadata, etc.) that take StorageAdapter as a constructor argument. A clean high-level facade in 7.31.0 would have meant either: (a) Delegate to brain.add() / update() / relate(). Each of those opens its own internal transaction and commits before the closure returns. Looks atomic, isn't — a footgun. (b) Thread an optional `tx?` through every internal write site in brainy.ts (8 transactionManager.executeTransaction sites). ~2 days of real refactor with regression surface, and the API shape changes again in 8.0 anyway. The SDK scheduler's actual ask (BRAINY-EXPOSE-TRANSACTIONS) is the read-then-CAS lock pattern. _rev + ifRev solves it completely. Multi-write atomicity is the 8.0 brain.transact() use case where the Datomic-style immutable Db makes it atomic by construction — that's the right home. TESTS - New tests/integration/rev-and-ifabsent.test.ts (18 tests): - _rev initialization (1 on add) and surface on get fast/full paths + find - _rev auto-bump on update across multiple writes - update({ ifRev }) pass / fail / message format / omitted / legacy-no-rev - add({ ifAbsent }) writes when absent / no-op when present / id-required - addMany({ ifAbsent }) propagation + per-item override - SDK-scheduler scenario: two concurrent CAS updates, one wins one throws - Unit suite unchanged: 1468/1468. - All integration subtype + verb + strict + find-limits + new rev suites pass: 96/96. DOCS - New docs/guides/optimistic-concurrency.md (public: true) — full reference: the lock pattern, read-modify-write retry, idempotent bootstrap, how _rev interacts with brain.versions, branches/fork, and VFS, what's coming in 8.0. - docs/api/README.md — add() and update() entries get the new params + tips pointing at the new guide. - RELEASES.md v7.31.0 entry. CORTEX COMPATIBILITY Zero changes required. _rev is a metadata column already supported by NativeColumnStore. The auto-bump runs in Brainy JS before any storage call; Cortex never sees the per-entity counter. 8.0 FORWARD-COMPAT _rev, ifRev, RevisionConflictError, and ifAbsent survive the 8.0 Db redesign unchanged. 8.0 layers brain.transact(tx, { ifAtGeneration }) for whole-tx CAS on top of the same per-entity mechanism — per-entity for single-record patterns (job locks, idempotent state machines), generation-based for "did the world move under me." Locked in .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md § C-6. Verification - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit - All integration suites including new rev-and-ifabsent: 96/96 - npm run build: clean - Closed-source product reference audit: clean
2026-06-09 10:04:24 -07:00
/**
* Conditional insert applied to every item. Equivalent to setting `ifAbsent: true`
* on each item individually. Item-level `ifAbsent` overrides the batch flag.
*/
ifAbsent?: boolean
/**
* Create-or-update applied to every item. Equivalent to setting `upsert: true` on
* each item individually (see {@link AddParams.upsert}): an item whose custom `id`
* already exists is MERGED as an update rather than overwritten. Item-level `upsert`
* overrides the batch flag. Mutually exclusive with {@link AddManyParams.ifAbsent}
* at the item level an item resolving to both throws.
*/
upsert?: boolean
}
/**
* Batch update parameters
*/
export interface UpdateManyParams<T = any> {
items: UpdateParams<T>[] // Items to update
parallel?: boolean
chunkSize?: number
onProgress?: (done: number, total: number) => void
continueOnError?: boolean
}
/**
* Batch remove parameters
*/
export interface RemoveManyParams {
ids?: string[] // Specific IDs to remove
type?: NounType // Remove all of type
where?: any // Remove by metadata
limit?: number // Max to remove (safety)
/**
* Entities removed per transaction chunk. Each chunk is one atomic transaction
* a chunk commits or rolls back together, and failures are isolated to their chunk.
* Defaults to the storage adapter's `maxBatchSize` (memory: 1000, S3/R2: 100,
* GCS: 50), so zero-config removals match the adapter's characteristics. Override
* only to tune throughput vs. transaction granularity.
*/
chunkSize?: number
onProgress?: (done: number, total: number) => void
continueOnError?: boolean // Continue processing if a removal fails
}
/**
* Batch relate parameters
*/
export interface RelateManyParams<T = any> {
items: RelateParams<T>[] // Relations to create
parallel?: boolean
chunkSize?: number
onProgress?: (done: number, total: number) => void
continueOnError?: boolean
}
/**
* Batch result
*/
export interface BatchResult<T = any> {
successful: T[] // Successfully processed items
failed: Array<{ // Failed items with errors
item: any
error: string
}>
total: number // Total attempted
duration: number // Time taken in ms
}
// ============= Import Progress =============
feat: comprehensive import progress tracking for all 7 formats Add real-time progress reporting throughout the entire import pipeline with a standardized API that works across all supported formats. Workshop Team Feature Request: - Eliminates "0% complete" hangs during AI extraction - Shows continuous progress with entities/sec, throughput, ETA - Reports contextual messages ("Processing page 5 of 23") - Standardized progress API for CSV, PDF, Excel, JSON, Markdown, YAML, DOCX Core Changes: - Add FormatHandlerProgressHooks interface for extensible progress - Wire up all 3 binary format handlers (CSV, PDF, Excel) with 7+ progress points - Wire up all 4 text format importers (JSON, Markdown, YAML, DOCX) - Add ImportProgress interface with stage, message, counts, throughput, ETA - ImportCoordinator normalizes all format progress to standard interface CLI Improvements: - Import command now uses brain.import() directly with full progress - Add --include-vfs flag to find command (v4.4.0 compatibility) - Add --confidence and --weight options to add command Documentation: - docs/guides/standard-import-progress.md - Universal API guide - docs/guides/import-progress-implementation.md - Developer guide - docs/guides/import-progress-examples.md - Practical examples - JSDoc on brain.import() with universal handler examples Result: ONE progress handler works for ALL 7 formats with zero format-specific code! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 14:45:46 -07:00
/**
* Import stage enumeration
*/
export type ImportStage =
| 'detecting' // Detecting file format
| 'reading' // Reading file from disk/network
| 'parsing' // Parsing file structure (CSV rows, PDF pages, Excel sheets)
| 'extracting' // Extracting entities using AI
| 'indexing' // Creating graph nodes and relationships
| 'completing' // Final cleanup and stats
/**
* Overall import status
*/
export type ImportStatus =
| 'starting' // Initializing import
| 'processing' // Actively importing
| 'completing' // Finalizing
| 'done' // Complete
/**
* Comprehensive import progress information
*
* Provides multi-dimensional progress tracking:
* - Bytes processed (always deterministic)
* - Entities extracted and indexed
* - Stage-specific progress
* - Time estimates
* - Performance metrics
*
*/
export interface ImportProgress {
// Overall Progress
overall_progress: number // 0-100 weighted estimate across all stages
overall_status: ImportStatus // High-level status
// Current Stage
stage: ImportStage // What's happening now
stage_progress: number // 0-100 within current stage (0 if unknown)
stage_message: string // Human-readable: "Extracting entities from PDF..."
// Bytes (Always Available - most deterministic metric)
bytes_processed: number // Bytes read/processed so far
total_bytes: number // Total file size (0 if streaming/unknown)
bytes_percentage: number // Convenience: bytes_processed / total_bytes * 100
bytes_per_second?: number // Processing rate (relevant during parsing)
// Entities (Available when extraction starts)
entities_extracted: number // Entities found during AI extraction
entities_indexed: number // Entities added to Brainy graph
entities_per_second?: number // Entities/sec (relevant during extraction/indexing)
estimated_total_entities?: number // Estimated final count
estimation_confidence?: number // 0-1 confidence in estimation
// Timing
elapsed_ms: number // Time since import started
estimated_remaining_ms?: number // Estimated time remaining
estimated_total_ms?: number // Estimated total time
// Context (helps users understand what's happening)
current_item?: string // "Processing page 5 of 23"
current_file?: string // "Sheet: Q2 Sales Data"
file_number?: number // 3 (when importing multiple files)
total_files?: number // 10
// Performance Metrics (for debugging/optimization)
metrics?: {
parsing_rate_mbps?: number // MB/s during parsing
extraction_rate_entities_per_sec?: number // Entities/s during extraction
indexing_rate_entities_per_sec?: number // Entities/s during indexing
memory_usage_mb?: number // Current memory usage
peak_memory_mb?: number // Peak memory usage
}
// Backwards Compatibility (for legacy code)
current: number // Alias for entities_indexed
total: number // Alias for estimated_total_entities or 0
}
/**
* Import progress callback - backwards compatible
*
* Supports both legacy (current, total) and new (ImportProgress object) signatures
*/
export type ImportProgressCallback =
| ((progress: ImportProgress) => void)
| ((current: number, total: number) => void)
/**
* Stage weight configuration for overall progress calculation
*
* These weights reflect the typical time distribution across stages.
* Extraction is typically the slowest stage (60% of time).
*/
export interface StageWeights {
detecting: number // Default: 0.01 (1%)
reading: number // Default: 0.05 (5%)
parsing: number // Default: 0.10 (10%)
extracting: number // Default: 0.60 (60% - slowest!)
indexing: number // Default: 0.20 (20%)
completing: number // Default: 0.04 (4%)
}
/**
* Import result statistics
*/
export interface ImportStats {
graphNodesCreated: number // Entities added to graph
graphEdgesCreated: number // Relationships created
vfsFilesCreated: number // VFS files created
duration: number // Total time in ms
bytesProcessed: number // Total bytes read
averageRate: number // Average entities/sec
peakMemoryMB?: number // Peak memory usage
}
/**
* Import operation result
*/
export interface ImportResult {
success: boolean
stats: ImportStats
errors?: Array<{
stage: ImportStage
message: string
error?: any
}>
}
// ============= Advanced Operations =============
feat: brain.get() metadata-only optimization (v5.11.1 Phase 1) Core implementation for 76-81% faster brain.get() by default. ## Changes **Type Definitions** (src/types/brainy.types.ts): - Added GetOptions interface with includeVectors option - Comprehensive JSDoc explaining when to use includeVectors - Performance characteristics documented (76-81% faster, 95% less bandwidth) **brain.get() Optimization** (src/brainy.ts): - Updated signature: async get(id, options?: GetOptions) - Routes to metadata-only by default (includeVectors ?? false) - Fast path: storage.getNounMetadata() - 10ms, 300 bytes - Full path: storage.getNoun() - 43ms, 6KB (when includeVectors: true) - Added convertMetadataToEntity() method for fast path - Updated similar() to use includeVectors: true (needs vectors) **Storage Documentation** (src/storage/baseStorage.ts): - Enhanced getNounMetadata() JSDoc with performance notes - Explains what's included vs excluded - Usage examples and when to use vs getNoun() ## Performance Impact - brain.get(): 43ms → 10ms (76% faster) - VFS operations: 53ms → 10ms (81% faster) - automatic benefit - Bandwidth: 6KB → 300 bytes (95% reduction) - Memory: 6KB → 300 bytes (87% reduction) ## Breaking Change Default behavior: brain.get(id) returns entity WITHOUT vectors (empty array). Opt-in for vectors: brain.get(id, { includeVectors: true }) Impact: <6% of code needs update (only code computing similarity on retrieved entity). ## Status Phase 1 COMPLETE: - ✅ Core implementation - ✅ JSDoc comprehensive - ✅ Build passes (zero TypeScript errors) Phase 2-4 PENDING: - ⏳ Unit tests - ⏳ Integration tests - ⏳ Documentation updates (24 files) - ⏳ Migration guide See .strategy/V5.11.1-IMPLEMENTATION-PLAN.md for full plan.
2025-11-18 15:31:29 -08:00
/**
* Options for brain.get() entity retrieval
*
* **Performance Optimization**:
feat: brain.get() metadata-only optimization (v5.11.1 Phase 1) Core implementation for 76-81% faster brain.get() by default. ## Changes **Type Definitions** (src/types/brainy.types.ts): - Added GetOptions interface with includeVectors option - Comprehensive JSDoc explaining when to use includeVectors - Performance characteristics documented (76-81% faster, 95% less bandwidth) **brain.get() Optimization** (src/brainy.ts): - Updated signature: async get(id, options?: GetOptions) - Routes to metadata-only by default (includeVectors ?? false) - Fast path: storage.getNounMetadata() - 10ms, 300 bytes - Full path: storage.getNoun() - 43ms, 6KB (when includeVectors: true) - Added convertMetadataToEntity() method for fast path - Updated similar() to use includeVectors: true (needs vectors) **Storage Documentation** (src/storage/baseStorage.ts): - Enhanced getNounMetadata() JSDoc with performance notes - Explains what's included vs excluded - Usage examples and when to use vs getNoun() ## Performance Impact - brain.get(): 43ms → 10ms (76% faster) - VFS operations: 53ms → 10ms (81% faster) - automatic benefit - Bandwidth: 6KB → 300 bytes (95% reduction) - Memory: 6KB → 300 bytes (87% reduction) ## Breaking Change Default behavior: brain.get(id) returns entity WITHOUT vectors (empty array). Opt-in for vectors: brain.get(id, { includeVectors: true }) Impact: <6% of code needs update (only code computing similarity on retrieved entity). ## Status Phase 1 COMPLETE: - ✅ Core implementation - ✅ JSDoc comprehensive - ✅ Build passes (zero TypeScript errors) Phase 2-4 PENDING: - ⏳ Unit tests - ⏳ Integration tests - ⏳ Documentation updates (24 files) - ⏳ Migration guide See .strategy/V5.11.1-IMPLEMENTATION-PLAN.md for full plan.
2025-11-18 15:31:29 -08:00
* By default, brain.get() loads ONLY metadata (not vectors), resulting in:
* - **76-81% faster** reads (10ms vs 43ms for metadata-only)
* - **95% less bandwidth** (300 bytes vs 6KB per entity)
* - **87% less memory** (optimal for VFS and large-scale operations)
*
* **When to use includeVectors**:
* - Computing similarity on a specific entity (not search): `brain.similar({ to: entity.vector })`
* - Manual vector operations: `cosineSimilarity(entity.vector, otherVector)`
* - Inspecting embeddings for debugging
*
* **When NOT to use includeVectors** (metadata-only is sufficient):
* - VFS operations (readFile, stat, readdir) - 100% of cases
* - Existence checks: `if (await brain.get(id))`
* - Metadata inspection: `entity.metadata`, `entity.data`, `entity.type`
* - Relationship traversal: `brain.related({ from: id })`
feat: brain.get() metadata-only optimization (v5.11.1 Phase 1) Core implementation for 76-81% faster brain.get() by default. ## Changes **Type Definitions** (src/types/brainy.types.ts): - Added GetOptions interface with includeVectors option - Comprehensive JSDoc explaining when to use includeVectors - Performance characteristics documented (76-81% faster, 95% less bandwidth) **brain.get() Optimization** (src/brainy.ts): - Updated signature: async get(id, options?: GetOptions) - Routes to metadata-only by default (includeVectors ?? false) - Fast path: storage.getNounMetadata() - 10ms, 300 bytes - Full path: storage.getNoun() - 43ms, 6KB (when includeVectors: true) - Added convertMetadataToEntity() method for fast path - Updated similar() to use includeVectors: true (needs vectors) **Storage Documentation** (src/storage/baseStorage.ts): - Enhanced getNounMetadata() JSDoc with performance notes - Explains what's included vs excluded - Usage examples and when to use vs getNoun() ## Performance Impact - brain.get(): 43ms → 10ms (76% faster) - VFS operations: 53ms → 10ms (81% faster) - automatic benefit - Bandwidth: 6KB → 300 bytes (95% reduction) - Memory: 6KB → 300 bytes (87% reduction) ## Breaking Change Default behavior: brain.get(id) returns entity WITHOUT vectors (empty array). Opt-in for vectors: brain.get(id, { includeVectors: true }) Impact: <6% of code needs update (only code computing similarity on retrieved entity). ## Status Phase 1 COMPLETE: - ✅ Core implementation - ✅ JSDoc comprehensive - ✅ Build passes (zero TypeScript errors) Phase 2-4 PENDING: - ⏳ Unit tests - ⏳ Integration tests - ⏳ Documentation updates (24 files) - ⏳ Migration guide See .strategy/V5.11.1-IMPLEMENTATION-PLAN.md for full plan.
2025-11-18 15:31:29 -08:00
* - Search operations: `brain.find()` generates embeddings automatically
*
* @example
* ```typescript
* // ✅ FAST (default): Metadata-only - 10ms, 300 bytes
* const entity = await brain.get(id)
* console.log(entity.data, entity.metadata) // ✅ Available
* console.log(entity.vector) // Empty Float32Array (stub)
*
* // ✅ FULL: Load vectors when needed - 43ms, 6KB
* const fullEntity = await brain.get(id, { includeVectors: true })
* const similarity = cosineSimilarity(fullEntity.vector, otherVector)
*
* // ✅ VFS automatically uses fast path (no change needed)
* await vfs.readFile('/file.txt') // 53ms → 10ms (81% faster)
* ```
*
*/
export interface GetOptions {
/**
* Include 384-dimensional vector embeddings in the response
*
* **Default: false** (metadata-only for 76-81% speedup)
*
* Set to `true` when you need to:
* - Compute similarity on this specific entity's vector
* - Perform manual vector operations
* - Inspect embeddings for debugging
*
* **Note**: Search operations (`brain.find()`) generate vectors automatically,
* so you don't need this flag for search. Only for direct vector operations
* on a retrieved entity.
*
* @default false
*/
includeVectors?: boolean
}
/**
* Graph traversal parameters
*/
export interface TraverseParams {
from: string | string[] // Starting node(s)
direction?: 'out' | 'in' | 'both' // Traversal direction
types?: VerbType[] // Edge types to follow
depth?: number // Max depth (default: 2)
strategy?: 'bfs' | 'dfs' // Breadth or depth first
filter?: (entity: Entity, depth: number, path: string[]) => boolean
limit?: number // Max nodes to visit
}
// ============= Aggregation Engine Types =============
/**
* Supported aggregation operations
*/
export type AggregationOp =
| 'sum'
| 'count'
| 'avg'
| 'min'
| 'max'
| 'stddev'
| 'variance'
/** Exact percentile (requires `p` in `[0,1]` on the metric def) — value-multiset, delete-safe. */
| 'percentile'
/** Exact count of distinct values — value-multiset, delete-safe. */
| 'distinctCount'
/**
* Time window granularity for GROUP BY time dimensions
*/
export type TimeWindowGranularity =
| 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
| { seconds: number }
/**
* A GROUP BY dimension one of:
* - a plain metadata field name (string),
* - a time-windowed field (`{ field, window }`), or
* - an **unnest** field (`{ field, unnest: true }`): the field holds an array, and the entity
* contributes once to a group per distinct element (e.g. `tags: string[]` tag frequency).
* An entity whose unnest field is missing or an empty array contributes to no group.
*/
export type GroupByDimension =
| string
| { field: string; window: TimeWindowGranularity }
| { field: string; unnest: true }
/**
* Source filter for which entities feed into an aggregate
*/
export interface AggregateSource {
/** Filter by entity type(s) */
type?: NounType | NounType[]
/** Metadata filter (same syntax as find({ where })) */
where?: Record<string, unknown>
/** Multi-tenancy service filter */
service?: string
}
/**
* Full aggregate definition registered via brain.defineAggregate()
*/
export interface AggregateDefinition {
/** Unique name for this aggregate (used in queries) */
name: string
/** Which entities contribute to this aggregate */
source: AggregateSource
/** Dimensions to group by */
groupBy: GroupByDimension[]
/** Named metrics to compute */
metrics: Record<string, AggregateMetricDef>
/** Control materialization of results as NounType.Measurement entities */
materialize?: boolean | { debounceMs?: number; trackSources?: boolean }
}
/**
* Single metric definition within an aggregate
*/
export interface AggregateMetricDef {
/** Aggregation operation */
op: AggregationOp
/** Metadata field to aggregate (required for all ops except count) */
field?: string
/** Percentile fraction in `[0, 1]` — required when `op === 'percentile'`, ignored otherwise. */
p?: number
}
/**
* Internal running state for a single metric.
* Tracks enough to compute all operations incrementally.
*/
export interface MetricState {
sum: number
count: number
min: number
max: number
/** Running M2 for Welford's online variance (sum of squared differences from mean) */
m2?: number
/**
* Value multiset (String(value) occurrence count) for exact percentile + distinctCount.
* Maintained only for `percentile`/`distinctCount` metrics. JSON-serializable; mirrors
* Cortex's `value_counts` so JS and native agree bit-for-bit.
*/
valueCounts?: Record<string, number>
}
/**
* Internal state for one aggregate group (one combination of group key values)
*/
export interface AggregateGroupState {
/** The group key values (e.g., { category: 'food', period: '2024-01' }) */
groupKey: Record<string, string | number>
/** Running metric states keyed by metric name */
metrics: Record<string, MetricState>
/** Entity ID of the materialized Measurement entity (if materialized) */
materializedEntityId?: string
/** Timestamp of last update */
lastUpdated: number
}
/**
* Query parameters for reading aggregate results
*/
export interface AggregateQueryParams {
/** Name of the aggregate to query */
name: string
/** Filter aggregate groups by their key values */
where?: Record<string, unknown>
/**
* 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.
*/
having?: Record<string, unknown>
/** Sort by metric name or group key field */
orderBy?: string
/** Sort direction */
order?: 'asc' | 'desc'
/** Max results */
limit?: number
/** Skip N results */
offset?: number
}
/**
* A single aggregate result row
*/
export interface AggregateResult {
/** Group key values for this row */
groupKey: Record<string, string | number>
/** Computed metric values (derived from MetricState based on op) */
metrics: Record<string, number>
/** Total entity count in this group */
count: number
/** Entity ID of materialized Measurement (if materialized) */
entityId?: string
}
/**
* Provider interface for Cortex-accelerated aggregation.
* When registered as 'aggregation' provider, Brainy delegates to this.
*/
export interface AggregationProvider {
/** Register an aggregate definition (caches compiled definition for hot path) */
defineAggregate?(def: AggregateDefinition): void
/** Remove a registered aggregate definition */
removeAggregate?(name: string): void
/** Incrementally update aggregation state when an entity changes */
incrementalUpdate(
name: string,
def: AggregateDefinition,
entity: Record<string, unknown>,
op: 'add' | 'update' | 'delete',
prev?: Record<string, unknown>
): AggregateGroupState[]
/** Compute the group key for a given entity */
computeGroupKey(
entity: Record<string, unknown>,
groupBy: GroupByDimension[]
): Record<string, string | number>
/** Rebuild an entire aggregate from scratch */
rebuildAggregate(
def: AggregateDefinition,
entities: Array<Record<string, unknown>>
): Map<string, AggregateGroupState>
/** Query aggregate state with filtering/sorting/pagination */
queryAggregate(
state: Map<string, AggregateGroupState>,
params: AggregateQueryParams
): AggregateResult[]
/** Restore previously serialized state (called during init) */
restoreState?(data: string): void
/** Serialize internal state for persistence (called during flush) */
serializeState?(): string
}
// ============= Configuration =============
/**
* Integration Hub configuration
*
* Enables external tool integrations: Excel, Power BI, Google Sheets, etc.
* Works in all environments with zero external dependencies.
*
* @example
* ```typescript
* // Enable all integrations with defaults
* new Brainy({ integrations: true })
*
* // Custom configuration
* new Brainy({
* integrations: {
* basePath: '/api/v1',
* enable: ['odata', 'sheets']
* }
* })
* ```
*/
export interface IntegrationsConfig {
/** Base path for all integration endpoints (default: '') */
basePath?: string
/** Which integrations to enable (default: all) */
enable?: ('odata' | 'sheets' | 'sse' | 'webhooks')[] | 'all'
/** Per-integration config overrides */
config?: {
odata?: { basePath?: string }
sheets?: { basePath?: string }
sse?: { basePath?: string; heartbeatInterval?: number }
webhooks?: { maxRetries?: number }
}
}
/**
* Operator-facing summary returned by `brain.stats()`. Designed to fit in a
* terminal screen so an incident responder can read it at a glance: counts,
* mode, lock owner, indexed fields, and index health flags.
*/
export interface BrainyStats {
/** Whether this instance can mutate. `reader` is set by `openReadOnly()` and `asOf()`. */
mode: 'writer' | 'reader'
/** Total entity (noun) count across all types. */
entityCount: number
/** Breakdown by NounType name (`person`, `event`, ...). Zero-count types omitted. */
entitiesByType: Record<string, number>
/** Total relationship (verb) count across all types. */
relationCount: number
/** Breakdown by VerbType name. Zero-count types omitted. */
relationsByType: Record<string, number>
/** Indexed metadata field names known to the metadata index. */
fieldRegistry: string[]
/** Per-index health flags. `true` = index has entries OR no entities exist yet. */
indexHealth: {
refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6) Step 6 of the brainy 8.0 rename scaffolding. Removes every legacy 'hnsw'- flavoured public name introduced as a compat shim in steps 1-5. The algorithm-neutral surface is now the only surface. REMOVED — PHASE A (type aliases) src/plugin.ts - Removed `export type HnswProvider = VectorIndexProvider` alias. - Removed `export type DiskAnnProvider = VectorIndexProvider` alias. src/index.ts - Removed `export const HNSWIndex = JsHnswVectorIndex` alias. src/types/brainy.types.ts - Removed `BrainyStats.indexHealth.hnsw` field (the new `vector` field carries the same boolean). src/brainy.ts - `stats()` no longer emits the legacy `hnsw` field on `indexHealth`. REMOVED — PHASE B (storage adapter rename, 8 adapters) Repo-wide rename: `saveHNSWData` → `saveVectorIndexData` and `getHNSWData` → `getVectorIndexData` across: - src/storage/adapters/baseStorageAdapter.ts (abstract declarations) - src/storage/adapters/fileSystemStorage.ts - src/storage/adapters/gcsStorage.ts - src/storage/adapters/r2Storage.ts - src/storage/adapters/s3CompatibleStorage.ts - src/storage/adapters/azureBlobStorage.ts - src/storage/adapters/opfsStorage.ts - src/storage/adapters/memoryStorage.ts - src/storage/adapters/historicalStorageAdapter.ts - src/brainy.ts (call sites) - src/hnsw/hnswIndex.ts + src/hnsw/typeAwareHNSWIndex.ts (call sites) The default-delegation wrappers added in scaffold step 4 are removed (would have been duplicate declarations after the rename). REMOVED — PHASE C (cache category 'hnsw') src/utils/unifiedCache.ts - Cache-category union narrowed from 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' to 'vectors' | 'metadata' | 'embedding' | 'other' - typeAccessCounts, typeSizes, typeCounts, accessRatios, sizeRatios, and the fairness-check iterator all drop the 'hnsw' key. - Per planning § 2.5: pre-8.0 'hnsw' cache entries are an in-memory category. Cache is rebuildable; entries naturally don't exist after a restart, so no migration path is needed. src/hnsw/hnswIndex.ts - 3 cache.set() call sites migrated from category 'hnsw' to 'vectors'. - Cache key prefix `hnsw:vector:` → `vector:`. - typeCounts/typeSizes/typeAccessCounts accessor renames `.hnsw` → `.vectors`. tests/unit/utils/unifiedCache-eviction.test.ts - Test fixtures updated: cache.set(..., 'hnsw', ...) → cache.set(..., 'vectors', ...). - Stats assertion `stats.typeSizes.hnsw` → `stats.typeSizes.vectors`. REMOVED — PHASE D (config.hnsw) src/types/brainy.types.ts - Removed `BrainyConfig.hnsw` field entirely. The 8.0 surface is `BrainyConfig.vector.{recall, quantization, vectorStorage, advanced}`. src/brainy.ts - normalizeConfig() no longer emits a `hnsw` field on Required<BrainyConfig>. - setupIndex() rewired: - Reads from `this.config.vector` (not `this.config.hnsw`). - Calls `resolveJsHnswConfig(this.config.vector)` to translate the `recall` preset into M / efConstruction / efSearch knobs (with `advanced.hnsw` overrides winning when supplied). - Imports `resolveJsHnswConfig` from './utils/recallPreset.js'. NOT IN THIS COMMIT (deliberately) - Persisted file path migration `_system/hnsw-*.json` → `_system/vector-index-*.json`. Requires dual-read logic on boot across 8 storage adapters. Per integration doc lines 531-539: "Reader accepts either spelling on load; writer emits the new spelling only; brains self-migrate on the next persist after upgrade." This is a separate body of work and lands in a follow-up commit before 8.0 GA. - `config.hnswPersistMode` top-level field is unchanged. It's not in the rename inventory; a future commit can fold it into `config.vector.persistMode` if desired. - strictConfig enforcement wiring. The field is accepted by normalizeConfig() with default 'warn'; the actual warning emission at knob-mismatch sites lands when the config-resolution layer is touched for the persisted-path migration. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit (one test fixture updated to use the new category name; assertion still passes after fixture rename) - npm run build: clean The 8.0 PR's user-facing surface is now algorithm-neutral end-to-end: VectorIndexProvider, config.vector.recall, JsHnswVectorIndex, saveVectorIndexData, 'vectors' cache category, indexHealth.vector, strictConfig — all standalone. No legacy 'hnsw' name remains in any public type or method signature.
2026-06-09 13:28:53 -07:00
/** Vector index health (8.0 — open-core JS HNSW path or a native acceleration provider). */
refactor(8.0): strictConfig + brain.stats() vector field + wireConnectionsCodec feature-detect (scaffold step 5) Step 5 of the brainy 8.0 rename scaffolding. Three small additions, all honoring contracts locked in handoff thread BRAINY-8.0-RENAME-COORDINATION: A. strictConfig: boolean | 'warn' (§ B.6) src/types/brainy.types.ts - New BrainyConfig.strictConfig field. Three tiers: - false: no enforcement (knob mismatches silently accepted) - 'warn' (8.0 default): one-shot warning per call site listing ignored knobs + escape-valve recipe + docs link - true: hard error at init() - Format matches the 7.30.1 teaching-error pattern (problem → escape valves → caller location → docs link). - Applies to ALL provider knob mismatches, not just vector (e.g. config.aggregation.x with no aggregation provider). src/brainy.ts - normalizeConfig() defaults to 'warn'. Wiring into the actual enforcement sites lands in the final cleanup commit alongside the removal of legacy knob names. B. brain.stats().indexHealth.vector (§ planning § 2.6) src/types/brainy.types.ts - BrainyStats.indexHealth gains a new `vector: boolean` field. Existing `hnsw: boolean` field marked @deprecated; same boolean, kept as a compat alias until the final cleanup. src/brainy.ts:6272 - Implementation populates both `hnsw` and `vector` from the same `vectorHealthy` boolean. Refactored to async IIFE so the graph-size await sits cleanly alongside the new field. C. wireConnectionsCodec feature-detect (integration doc lines 459-461) src/brainy.ts:9469-9486 - Added `typeof this.index.setConnectionsCodec === 'function'` guard before invoking. Native vector-index providers (DiskANN-style) persist the graph as a single mmap'd file with no per-node connection lists and have no analogue. Pre-8.0 the wire was unconditional and relied on the native wrapper exposing a no-op method; 8.0 makes it feature-detected. NO-OP scope No behavioural change in scaffolding. brain.stats() now returns both field names (callers see the new field but the old still works). strictConfig is defaulted but not yet enforced. wireConnectionsCodec behaves identically when called against an impl that has the codec method (the JS HNSW path); guards cleanly against impls that don't. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:12:39 -07:00
vector: boolean
metadata: boolean
graph: boolean
}
/** Storage backend info — `backend` is the adapter class name. */
storage: {
backend: string
rootDir?: string
}
/**
* Writer lock metadata if one is currently held on this directory. Present
* for both writer instances (their own lock) and readers inspecting a
* directory with a live writer.
*/
writerLock?: {
pid: number
hostname: string
startedAt: string
lastHeartbeat: string
version: string
rootDir?: string
}
/** The Brainy library version this instance was built against. */
version: string
}
/**
* Brainy configuration
*/
export interface BrainyConfig {
feat(8.0): full query surface at historical generations via ephemeral index materialization Historical Db values (now()/asOf() pins that history has moved past) now serve the COMPLETE query surface - vector/hybrid search, graph traversal, cursor pagination, and aggregation - by materializing ephemeral in-memory indexes over the exact at-generation record set. The historical-query throw is gone; NotYetSupportedAtHistoricalGenerationError is deleted. Materializer (Brainy.materializeAtGeneration): - Copies the at-G record set (live bytes for ids untouched since the pin, immutable before-images otherwise) into a fresh MemoryStorage; a final reconciliation pass under the commit mutex makes the copy exact even when transactions commit mid-build. - Opens a read-only Brainy over the copy: init rebuilds the metadata and graph-adjacency indexes from the records; the vector index is built by inserting every at-G vector (the at-G HNSW graph never existed on disk, so there is nothing to restore). Host embedder and aggregate definitions are shared - no second model load, aggregates backfill at-G values. - Cost is the documented contract: O(n at G) time and memory, ONCE per Db (handle cached; freed by release(), with a FinalizationRegistry backstop that also closes leaked readers). A native VersionedIndexProvider serves the same reads from retained segments with no rebuild. Db routing (src/db/db.ts): metadata-level find()/related() keep the free record path; index-only dimensions (query/vector/near/connected/cursor/ aggregate/includeRelations/non-metadata modes) route to the cached materialization; unsupported where-operators on the record path re-route there too instead of erroring. Speculative with() overlays keep the one honest boundary - SpeculativeOverlayError (overlay entities carry no embeddings, so index reads over them would be silently incomplete); metadata find()/get()/filter related() work on overlays. UpdateParams.vector contract now honored: an explicit pre-computed vector applies directly (with dimension validation) in update() and transact update ops, re-indexing HNSW - previously it was silently ignored unless data also changed. GraphAdjacencyIndex: adjacency now derives from the two verb-id LSM trees filtered through the live-verb tombstone set (entity->entity edge trees deleted - they carried no verb ids, so removeVerb could never tombstone them and traversal served stale neighbors forever). Neighbor reads batch- load live verbs via the unified cache; addVerb seeds the cache. Proofs (tests/integration/db-mvcc.test.ts, 24 green): historical vector search finds old vector placement including since-deleted entities; historical graph traversal walks the old wiring after a rewire; historical aggregation computes at-G group values; asOf() pins get the same surface; the materialization builds once per Db and release() closes the ephemeral reader (it refuses reads afterwards); overlays throw the documented error. ADR-001 updated to the no-throws historical model.
2026-06-11 08:12:11 -07:00
/**
* Storage configuration: either a config object resolved through the
* storage factory, or a pre-constructed adapter instance (used directly
* e.g. `storage: new MemoryStorage()`; Brainy's historical-query
* materializer hands a pre-populated instance through this path).
*/
storage?:
| {
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0 8.0 RC cleanup toward "one place per thing, zero-config, no deprecation": - Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})` / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor, NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept. - Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams). Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` → now `find({ query, limit })`. - Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native storage provider resolves the identical root (no split-brain). - Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now applied as a post-filter on `result.score` (the documented way to bound semantic results). - Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()` and failed dimension validation; they are metadata-only updates now. - Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an in-place path change that preserves the blob and the entity id, for files and directories. - Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability. Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk flushes and emits `progress.queryable`, so imported data is queryable during the import. - Sweep all docs, comments, and JSDoc for the removed/changed APIs. Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
/**
* Storage backend. Optional a top-level `path` implies `'filesystem'`,
* so `storage: { path: '/data' }` works without it. `'auto'` (the
* default) picks filesystem on Node, memory in a browser.
*/
type?: 'auto' | 'memory' | 'filesystem'
/**
* **Canonical** directory for filesystem storage. The rest of the API
* already speaks `path` (`persist(path)`, `Brainy.load(path)`,
* `asOf(path)`, `restore(path)`). Specifying it implies
* `type: 'filesystem'`. Passed through to plugin-provided storage
* factories (e.g. native mmap providers) so they resolve the same root.
* @example
* new Brainy({ storage: { path: '/var/lib/app/data' } })
*/
path?: string
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0 8.0 RC cleanup toward "one place per thing, zero-config, no deprecation": - Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})` / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor, NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept. - Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams). Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` → now `find({ query, limit })`. - Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native storage provider resolves the identical root (no split-brain). - Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now applied as a post-filter on `result.score` (the documented way to bound semantic results). - Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()` and failed dimension validation; they are metadata-only updates now. - Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an in-place path change that preserves the blob and the entity id, for files and directories. - Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability. Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk flushes and emits `progress.queryable`, so imported data is queryable during the import. - Sweep all docs, comments, and JSDoc for the removed/changed APIs. Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
/**
* @deprecated REMOVED in 8.0 passing `rootDirectory` THROWS. Use the
* canonical {@link path}.
*/
rootDirectory?: string
/**
* @deprecated REMOVED in 8.0 a nested `options.path` / `options.rootDirectory`
* THROWS. Use the top-level {@link path}.
*/
feat(8.0): full query surface at historical generations via ephemeral index materialization Historical Db values (now()/asOf() pins that history has moved past) now serve the COMPLETE query surface - vector/hybrid search, graph traversal, cursor pagination, and aggregation - by materializing ephemeral in-memory indexes over the exact at-generation record set. The historical-query throw is gone; NotYetSupportedAtHistoricalGenerationError is deleted. Materializer (Brainy.materializeAtGeneration): - Copies the at-G record set (live bytes for ids untouched since the pin, immutable before-images otherwise) into a fresh MemoryStorage; a final reconciliation pass under the commit mutex makes the copy exact even when transactions commit mid-build. - Opens a read-only Brainy over the copy: init rebuilds the metadata and graph-adjacency indexes from the records; the vector index is built by inserting every at-G vector (the at-G HNSW graph never existed on disk, so there is nothing to restore). Host embedder and aggregate definitions are shared - no second model load, aggregates backfill at-G values. - Cost is the documented contract: O(n at G) time and memory, ONCE per Db (handle cached; freed by release(), with a FinalizationRegistry backstop that also closes leaked readers). A native VersionedIndexProvider serves the same reads from retained segments with no rebuild. Db routing (src/db/db.ts): metadata-level find()/related() keep the free record path; index-only dimensions (query/vector/near/connected/cursor/ aggregate/includeRelations/non-metadata modes) route to the cached materialization; unsupported where-operators on the record path re-route there too instead of erroring. Speculative with() overlays keep the one honest boundary - SpeculativeOverlayError (overlay entities carry no embeddings, so index reads over them would be silently incomplete); metadata find()/get()/filter related() work on overlays. UpdateParams.vector contract now honored: an explicit pre-computed vector applies directly (with dimension validation) in update() and transact update ops, re-indexing HNSW - previously it was silently ignored unless data also changed. GraphAdjacencyIndex: adjacency now derives from the two verb-id LSM trees filtered through the live-verb tombstone set (entity->entity edge trees deleted - they carried no verb ids, so removeVerb could never tombstone them and traversal served stale neighbors forever). Neighbor reads batch- load live verbs via the unified cache; addVerb seeds the cache. Proofs (tests/integration/db-mvcc.test.ts, 24 green): historical vector search finds old vector placement including since-deleted entities; historical graph traversal walks the old wiring after a rewire; historical aggregation computes at-G group values; asOf() pins get the same surface; the materialization builds once per Db and release() closes the ephemeral reader (it refuses reads afterwards); overlays throw the documented error. ADR-001 updated to the no-throws historical model.
2026-06-11 08:12:11 -07:00
options?: any
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0 8.0 RC cleanup toward "one place per thing, zero-config, no deprecation": - Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})` / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor, NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept. - Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams). Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` → now `find({ query, limit })`. - Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native storage provider resolves the identical root (no split-brain). - Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now applied as a post-filter on `result.score` (the documented way to bound semantic results). - Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()` and failed dimension validation; they are metadata-only updates now. - Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an in-place path change that preserves the blob and the entity id, for files and directories. - Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability. Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk flushes and emits `progress.queryable`, so imported data is queryable during the import. - Sweep all docs, comments, and JSDoc for the removed/changed APIs. Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
/**
* @deprecated REMOVED in 8.0 `fileSystemStorage.path` / `.rootDirectory`
* THROWS. Use the top-level {@link path}.
*/
fileSystemStorage?: { path?: string; rootDirectory?: string; [key: string]: any }
feat(8.0): full query surface at historical generations via ephemeral index materialization Historical Db values (now()/asOf() pins that history has moved past) now serve the COMPLETE query surface - vector/hybrid search, graph traversal, cursor pagination, and aggregation - by materializing ephemeral in-memory indexes over the exact at-generation record set. The historical-query throw is gone; NotYetSupportedAtHistoricalGenerationError is deleted. Materializer (Brainy.materializeAtGeneration): - Copies the at-G record set (live bytes for ids untouched since the pin, immutable before-images otherwise) into a fresh MemoryStorage; a final reconciliation pass under the commit mutex makes the copy exact even when transactions commit mid-build. - Opens a read-only Brainy over the copy: init rebuilds the metadata and graph-adjacency indexes from the records; the vector index is built by inserting every at-G vector (the at-G HNSW graph never existed on disk, so there is nothing to restore). Host embedder and aggregate definitions are shared - no second model load, aggregates backfill at-G values. - Cost is the documented contract: O(n at G) time and memory, ONCE per Db (handle cached; freed by release(), with a FinalizationRegistry backstop that also closes leaked readers). A native VersionedIndexProvider serves the same reads from retained segments with no rebuild. Db routing (src/db/db.ts): metadata-level find()/related() keep the free record path; index-only dimensions (query/vector/near/connected/cursor/ aggregate/includeRelations/non-metadata modes) route to the cached materialization; unsupported where-operators on the record path re-route there too instead of erroring. Speculative with() overlays keep the one honest boundary - SpeculativeOverlayError (overlay entities carry no embeddings, so index reads over them would be silently incomplete); metadata find()/get()/filter related() work on overlays. UpdateParams.vector contract now honored: an explicit pre-computed vector applies directly (with dimension validation) in update() and transact update ops, re-indexing HNSW - previously it was silently ignored unless data also changed. GraphAdjacencyIndex: adjacency now derives from the two verb-id LSM trees filtered through the live-verb tombstone set (entity->entity edge trees deleted - they carried no verb ids, so removeVerb could never tombstone them and traversal served stale neighbors forever). Neighbor reads batch- load live verbs via the unified cache; addVerb seeds the cache. Proofs (tests/integration/db-mvcc.test.ts, 24 green): historical vector search finds old vector placement including since-deleted entities; historical graph traversal walks the old wiring after a rewire; historical aggregation computes at-G group values; asOf() pins get the same surface; the materialization builds once per Db and release() closes the ephemeral reader (it refuses reads afterwards); overlays throw the documented error. ADR-001 updated to the no-throws historical model.
2026-06-11 08:12:11 -07:00
}
| StorageAdapter
/**
* Disable the automatic index rebuild check during `init()`. By default
* Brainy auto-decides from dataset size: small datasets rebuild missing
* indexes inline, large datasets rebuild lazily on first query. Set `true`
* only when an operator wants full manual control via `repairIndex()`.
*/
disableAutoRebuild?: boolean
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
/**
* Vector index configuration (Brainy 8.0).
*
* Two knobs. No escape hatch. The algorithm-internal HNSW knobs
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
* (`M`, `efConstruction`, `efSearch`, `ml`, ) and DiskANN knobs
* (`pqM`, `searchListSize`, `alpha`, ) are deliberately not exposed
* the `recall` preset covers the legitimate quality/latency tradeoff
* range, and the open-core defaults match Brainy 7.x's defaults
* byte-for-byte so a `'balanced'`-default upgrade is a no-op.
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
*
* **Closed-form contract** locked in handoff thread
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
* BRAINY-8.0-RENAME-COORDINATION § A.2 + § G.2 (cortex-confirmed 2026-06-09).
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
*/
vector?: {
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
/**
* Recall preset.
* - `'fast'` minimum-latency search, accepts lower recall
* - `'balanced'` (default) Brainy 7.x defaults, the right pick for almost everyone
* - `'accurate'` maximum recall, accepts higher latency
*
* Means the same thing whether the open-core JS HNSW path is in play
* or a native vector provider has taken over the `'vector'` provider
* key Brainy translates to HNSW knobs; native providers translate
* to their own (e.g. DiskANN's `defaultLSearch` / `defaultPaddingFactor`).
*/
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
recall?: 'fast' | 'balanced' | 'accurate'
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
/**
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
* Vector persistence mode. `'immediate'` writes per-noun graph state on
* every `add()`; durable but slower. `'deferred'` writes only on
* `flush()` / `close()`; faster bulk ingest.
*
* **Auto-selected from the storage adapter when omitted:** `'immediate'`
* on filesystem storage (durability is the point of a persistent
* backend), `'deferred'` on memory storage (nothing survives the process
* anyway, so per-add persistence writes are pure overhead).
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
*
* Brainy 7.x called this `hnswPersistMode` at the top level. The 8.0
* surface folds it under `config.vector` alongside `recall`.
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
*/
refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) Per user direction and BRAINY-8.0-RENAME-COORDINATION § G.2: the `config.vector.advanced.{hnsw, diskann}` escape-hatch surface was over-engineered. The 8.0 config surface collapses to **three knobs**: ``` config.vector = { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4 } persistMode?: 'immediate' | 'deferred' } ``` The algorithm-internal HNSW knobs (`M`, `efConstruction`, `efSearch`, `ml`) and DiskANN knobs (`pqM`, `searchListSize`, `alpha`, …) are no longer exposed at the public surface. The `recall` preset covers the legitimate quality/latency tradeoff; algorithm-internal tuning is intentionally hidden. If users need it later, we can add it back — shipping with too few knobs is easier to evolve than shipping with too many. CHANGES src/types/brainy.types.ts - BrainyConfig.hnswPersistMode (7.x top-level) → BrainyConfig.vector.persistMode. - BrainyConfig.vector — dropped `advanced.{hnsw, diskann}` sub-block. - BrainyConfig.vector.quantization — dropped `rerankMultiplier` (hardcoded to 3). - BrainyConfig.vector — dropped `vectorStorage: 'memory' | 'lazy'`. Brainy always keeps vectors resident in 8.0; the lazy-eviction path was a cloud-storage optimisation that's no longer needed (cloud adapters are gone). - JSDoc tightened to reflect the closed-form contract. src/utils/recallPreset.ts - resolveJsHnswConfig() signature narrowed: accepts `{ recall? }` only (no more `advanced` parameter). src/brainy.ts - normalizeConfig() — no longer carries `hnswPersistMode` on the resolved config. Dropped the deprecated 'gcs-native' warning, the gcsStorage HMAC-key warning, and the lenient storage-pairing block (dead code now that cloud adapters are gone). - resolveHNSWPersistMode() — collapsed to a one-liner: `return this.config.vector?.persistMode ?? 'immediate'`. The cloud-vs-local smart-default branching is no longer relevant (filesystem is the only persistent backend in 8.0). - setupIndex() — reads quantization fields from config.vector directly; rerankMultiplier hardcoded to 3. NO-OP scope The recall preset's 'balanced' = brainy 7.x DEFAULT_CONFIG byte-for-byte, so users who don't set recall get the same behavior. Users who set config.hnsw.* in 7.x will need to migrate to config.vector.* per the 8.0 release notes; pure 7.x defaults users see no change. VERIFICATION - npx tsc --noEmit: clean - npm test: 1408 / 1409 (same pre-existing test-isolation race condition from step 7, not related to step 8)
2026-06-09 14:20:57 -07:00
persistMode?: 'immediate' | 'deferred'
refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) Step 2 of the brainy 8.0 rename scaffolding. Adds the new algorithm-neutral config surface alongside the legacy config.hnsw path (which becomes a compat shim removed in the final cleanup commit). CHANGES src/utils/recallPreset.ts (NEW) - DEFAULT_RECALL = 'balanced' - HNSW_PRESETS table — fast/balanced/accurate → (M, efConstruction, efSearch). 'balanced' equals brainy 7.x DEFAULT_CONFIG byte-for-byte so a 7.x → 8.0 upgrade with no explicit recall value is a no-op. - resolveRecallHnswKnobs(preset) — preset → knobs. - resolveJsHnswConfig(vectorConfig) — preset first, then `advanced.hnsw` overrides win. Explicit knobs always beat the preset. src/types/brainy.types.ts - New optional `vector` field on BrainyConfig: vector?: { recall?: 'fast' | 'balanced' | 'accurate' quantization?: { enabled?, bits?: 8 | 4, rerankMultiplier? } vectorStorage?: 'memory' | 'lazy' advanced?: { hnsw?: { M?, efConstruction?, efSearch?, ml? } diskann?: { pqM?, pqKsub?, maxDegree?, searchListSize?, alpha?, ... } } } - `hnsw` field marked @deprecated with note that it stays as a compat shim during the 8.0 rename and is removed in the final cleanup commit. src/utils/unifiedCache.ts - Cache-category union widened from 'hnsw' | 'metadata' | 'embedding' | 'other' to 'hnsw' | 'vectors' | 'metadata' | 'embedding' | 'other' - 5 union sites + 4 typed-counter maps (typeAccessCounts, checkFairness typeSizes/typeCounts/accessRatios/sizeRatios, getStats typeSizes/typeCounts, evictType loop) all gained the 'vectors' key with initial value 0. - The hard-coded fairness-check loop now iterates both keys. - Final 8.0 cleanup will narrow back to 'vectors' | 'metadata' | 'embedding' | 'other'. src/brainy.ts (normalizeConfig) - Adds the new `vector` field to the Required<BrainyConfig> return shape so TS compiles. Reads config?.vector ?? undefined as any (same pattern as the other optional config fields). NO-OP scope No behavioural change. The new config.vector path is silently ignored at runtime in this commit — wired up in step 4 (storage + index resolution). Pure surface plumbing. Tests + build green. VERIFICATION - npx tsc --noEmit: clean - npm test: 1468 / 1468 unit
2026-06-09 13:06:10 -07:00
}
/**
feat(8.0): Model-B per-write generation-stamping + adaptive retention knob Every write — transact() AND single-op add/update/remove/relate — is now its own immutable generation (Model-B), so a now() pin always freezes and asOf/since/diff/history/transactionLog reflect single-ops exactly like transacts. Closes the Model-A hole where pins did not freeze against single-op writes. Generation-stamping: - GenerationStore.commitSingleOp: a one-operation commitTransaction with deferred durability. Wired into add/update/remove/relate/updateRelation/ unrelate + removeMany (the *Many and VFS paths delegate to these). - Async group-commit (flushPendingSingleOps): the live write is acknowledged immediately; its before-image is buffered in an in-memory pending tier that resolveAt/chains/changedBetween/tx-log read like on-disk generations, so the synchronous now() freezes with no forced flush. One fsync per window (triggers: size / 50ms timer / flush / close / transact / compactHistory). - Crash recovery is drop-without-restore for group-commit generations (marked groupCommit:true): a crash mid-flush discards the partial generation and never restores its before-images, which would otherwise revert the already-acknowledged live write. - Init-time infrastructure (the VFS root) is the un-versioned generation-0 baseline: a fresh brain reports generation()===0 and an empty transactionLog(); the first user write is generation 1. - Historical find()/related() overlay bound is the full reserved watermark (generation()), so un-flushed single-op writes are overlaid too. Retention knob: - config `history` -> `retention`: 'all' | 'adaptive' | { maxGenerations?, maxAge?, maxBytes?, budgetBytes?, autoCompact? }; unset -> adaptive (disk/RAM pressure, zero-config). CompactHistoryOptions floors -> caps (retainGenerations->maxGenerations, retainMs->maxAge, +maxBytes): reclaim oldest-unpinned while ANY cap is exceeded; pins always exempt. - brain.setRetentionBudget(bytes) drives the adaptive byte budget at runtime (a coordinator's fair-share input). Per-generation bytes recorded in each delta enable historyBytes() introspection without a storage size API. Tests: per-write generation resolution, pin freeze vs add/update/remove, drop-without-restore corruption-trap (fault injector), clean-reopen replay, maxBytes/maxAge/no-cap reclamation, retention-then-reopen. 107 db/generation/ temporal tests green, tsc clean. Docs (ADR-001, consistency-model, snapshots guide, api reference, RELEASES) updated to per-write granularity + retention.
2026-06-22 15:19:58 -07:00
* Generational-history retention policy (8.0 MVCC the `retention` knob).
*
feat(8.0): Model-B per-write generation-stamping + adaptive retention knob Every write — transact() AND single-op add/update/remove/relate — is now its own immutable generation (Model-B), so a now() pin always freezes and asOf/since/diff/history/transactionLog reflect single-ops exactly like transacts. Closes the Model-A hole where pins did not freeze against single-op writes. Generation-stamping: - GenerationStore.commitSingleOp: a one-operation commitTransaction with deferred durability. Wired into add/update/remove/relate/updateRelation/ unrelate + removeMany (the *Many and VFS paths delegate to these). - Async group-commit (flushPendingSingleOps): the live write is acknowledged immediately; its before-image is buffered in an in-memory pending tier that resolveAt/chains/changedBetween/tx-log read like on-disk generations, so the synchronous now() freezes with no forced flush. One fsync per window (triggers: size / 50ms timer / flush / close / transact / compactHistory). - Crash recovery is drop-without-restore for group-commit generations (marked groupCommit:true): a crash mid-flush discards the partial generation and never restores its before-images, which would otherwise revert the already-acknowledged live write. - Init-time infrastructure (the VFS root) is the un-versioned generation-0 baseline: a fresh brain reports generation()===0 and an empty transactionLog(); the first user write is generation 1. - Historical find()/related() overlay bound is the full reserved watermark (generation()), so un-flushed single-op writes are overlaid too. Retention knob: - config `history` -> `retention`: 'all' | 'adaptive' | { maxGenerations?, maxAge?, maxBytes?, budgetBytes?, autoCompact? }; unset -> adaptive (disk/RAM pressure, zero-config). CompactHistoryOptions floors -> caps (retainGenerations->maxGenerations, retainMs->maxAge, +maxBytes): reclaim oldest-unpinned while ANY cap is exceeded; pins always exempt. - brain.setRetentionBudget(bytes) drives the adaptive byte budget at runtime (a coordinator's fair-share input). Per-generation bytes recorded in each delta enable historyBytes() introspection without a storage size API. Tests: per-write generation resolution, pin freeze vs add/update/remove, drop-without-restore corruption-trap (fault injector), clean-reopen replay, maxBytes/maxAge/no-cap reclamation, retention-then-reopen. 107 db/generation/ temporal tests green, tsc clean. Docs (ADR-001, consistency-model, snapshots guide, api reference, RELEASES) updated to per-write granularity + retention.
2026-06-22 15:19:58 -07:00
* Under Model-B EVERY write (`transact()` AND single-op `add`/`update`/
* `remove`/`relate`) produces an immutable generation record-set serving
* historical reads (`asOf()`, pinned `Db` values). Without compaction those
* accumulate, so Brainy **auto-compacts on every `flush()` and `close()`**.
* Live `Db` pins are ALWAYS exempt from reclamation, in every mode.
*
feat(8.0): Model-B per-write generation-stamping + adaptive retention knob Every write — transact() AND single-op add/update/remove/relate — is now its own immutable generation (Model-B), so a now() pin always freezes and asOf/since/diff/history/transactionLog reflect single-ops exactly like transacts. Closes the Model-A hole where pins did not freeze against single-op writes. Generation-stamping: - GenerationStore.commitSingleOp: a one-operation commitTransaction with deferred durability. Wired into add/update/remove/relate/updateRelation/ unrelate + removeMany (the *Many and VFS paths delegate to these). - Async group-commit (flushPendingSingleOps): the live write is acknowledged immediately; its before-image is buffered in an in-memory pending tier that resolveAt/chains/changedBetween/tx-log read like on-disk generations, so the synchronous now() freezes with no forced flush. One fsync per window (triggers: size / 50ms timer / flush / close / transact / compactHistory). - Crash recovery is drop-without-restore for group-commit generations (marked groupCommit:true): a crash mid-flush discards the partial generation and never restores its before-images, which would otherwise revert the already-acknowledged live write. - Init-time infrastructure (the VFS root) is the un-versioned generation-0 baseline: a fresh brain reports generation()===0 and an empty transactionLog(); the first user write is generation 1. - Historical find()/related() overlay bound is the full reserved watermark (generation()), so un-flushed single-op writes are overlaid too. Retention knob: - config `history` -> `retention`: 'all' | 'adaptive' | { maxGenerations?, maxAge?, maxBytes?, budgetBytes?, autoCompact? }; unset -> adaptive (disk/RAM pressure, zero-config). CompactHistoryOptions floors -> caps (retainGenerations->maxGenerations, retainMs->maxAge, +maxBytes): reclaim oldest-unpinned while ANY cap is exceeded; pins always exempt. - brain.setRetentionBudget(bytes) drives the adaptive byte budget at runtime (a coordinator's fair-share input). Per-generation bytes recorded in each delta enable historyBytes() introspection without a storage size API. Tests: per-write generation resolution, pin freeze vs add/update/remove, drop-without-restore corruption-trap (fault injector), clean-reopen replay, maxBytes/maxAge/no-cap reclamation, retention-then-reopen. 107 db/generation/ temporal tests green, tsc clean. Docs (ADR-001, consistency-model, snapshots guide, api reference, RELEASES) updated to per-write granularity + retention.
2026-06-22 15:19:58 -07:00
* Modes:
* - **unset ADAPTIVE (default):** the retention horizon tracks
* disk/RAM pressure. A machine-level byte budget governs how much history
* is kept driven by `budgetBytes` when a coordinator (e.g. cor's
* `ResourceManager`, fair-shared across co-located instances) sets it, else
* a local `os.freemem`/filesystem-free probe. Oldest-unpinned history is
* reclaimed when the budget is exceeded. Zero-config.
* - **`'all'` unbounded:** never reclaim history (index compaction for
* query speed still runs the decouple). The legacy keep-everything
* behavior, now explicit opt-in.
* - **`{ maxGenerations?, maxAge?, maxBytes? }` explicit CAPS:** reclaim
* the oldest unpinned generations while ANY supplied cap is exceeded
* (predictable ops). `maxAge` in ms; `maxBytes` total history bytes.
*
feat(8.0): Model-B per-write generation-stamping + adaptive retention knob Every write — transact() AND single-op add/update/remove/relate — is now its own immutable generation (Model-B), so a now() pin always freezes and asOf/since/diff/history/transactionLog reflect single-ops exactly like transacts. Closes the Model-A hole where pins did not freeze against single-op writes. Generation-stamping: - GenerationStore.commitSingleOp: a one-operation commitTransaction with deferred durability. Wired into add/update/remove/relate/updateRelation/ unrelate + removeMany (the *Many and VFS paths delegate to these). - Async group-commit (flushPendingSingleOps): the live write is acknowledged immediately; its before-image is buffered in an in-memory pending tier that resolveAt/chains/changedBetween/tx-log read like on-disk generations, so the synchronous now() freezes with no forced flush. One fsync per window (triggers: size / 50ms timer / flush / close / transact / compactHistory). - Crash recovery is drop-without-restore for group-commit generations (marked groupCommit:true): a crash mid-flush discards the partial generation and never restores its before-images, which would otherwise revert the already-acknowledged live write. - Init-time infrastructure (the VFS root) is the un-versioned generation-0 baseline: a fresh brain reports generation()===0 and an empty transactionLog(); the first user write is generation 1. - Historical find()/related() overlay bound is the full reserved watermark (generation()), so un-flushed single-op writes are overlaid too. Retention knob: - config `history` -> `retention`: 'all' | 'adaptive' | { maxGenerations?, maxAge?, maxBytes?, budgetBytes?, autoCompact? }; unset -> adaptive (disk/RAM pressure, zero-config). CompactHistoryOptions floors -> caps (retainGenerations->maxGenerations, retainMs->maxAge, +maxBytes): reclaim oldest-unpinned while ANY cap is exceeded; pins always exempt. - brain.setRetentionBudget(bytes) drives the adaptive byte budget at runtime (a coordinator's fair-share input). Per-generation bytes recorded in each delta enable historyBytes() introspection without a storage size API. Tests: per-write generation resolution, pin freeze vs add/update/remove, drop-without-restore corruption-trap (fault injector), clean-reopen replay, maxBytes/maxAge/no-cap reclamation, retention-then-reopen. 107 db/generation/ temporal tests green, tsc clean. Docs (ADR-001, consistency-model, snapshots guide, api reference, RELEASES) updated to per-write granularity + retention.
2026-06-22 15:19:58 -07:00
* `autoCompact: false` disables the automatic flush/close compaction (manage
* manually via `brain.compactHistory()`). `budgetBytes` is the settable
* adaptive byte budget a coordinator drives (also via `brain.setRetentionBudget()`).
* Long-term archives belong in `db.persist(path)` snapshots, which compaction
* never touches.
*/
feat(8.0): Model-B per-write generation-stamping + adaptive retention knob Every write — transact() AND single-op add/update/remove/relate — is now its own immutable generation (Model-B), so a now() pin always freezes and asOf/since/diff/history/transactionLog reflect single-ops exactly like transacts. Closes the Model-A hole where pins did not freeze against single-op writes. Generation-stamping: - GenerationStore.commitSingleOp: a one-operation commitTransaction with deferred durability. Wired into add/update/remove/relate/updateRelation/ unrelate + removeMany (the *Many and VFS paths delegate to these). - Async group-commit (flushPendingSingleOps): the live write is acknowledged immediately; its before-image is buffered in an in-memory pending tier that resolveAt/chains/changedBetween/tx-log read like on-disk generations, so the synchronous now() freezes with no forced flush. One fsync per window (triggers: size / 50ms timer / flush / close / transact / compactHistory). - Crash recovery is drop-without-restore for group-commit generations (marked groupCommit:true): a crash mid-flush discards the partial generation and never restores its before-images, which would otherwise revert the already-acknowledged live write. - Init-time infrastructure (the VFS root) is the un-versioned generation-0 baseline: a fresh brain reports generation()===0 and an empty transactionLog(); the first user write is generation 1. - Historical find()/related() overlay bound is the full reserved watermark (generation()), so un-flushed single-op writes are overlaid too. Retention knob: - config `history` -> `retention`: 'all' | 'adaptive' | { maxGenerations?, maxAge?, maxBytes?, budgetBytes?, autoCompact? }; unset -> adaptive (disk/RAM pressure, zero-config). CompactHistoryOptions floors -> caps (retainGenerations->maxGenerations, retainMs->maxAge, +maxBytes): reclaim oldest-unpinned while ANY cap is exceeded; pins always exempt. - brain.setRetentionBudget(bytes) drives the adaptive byte budget at runtime (a coordinator's fair-share input). Per-generation bytes recorded in each delta enable historyBytes() introspection without a storage size API. Tests: per-write generation resolution, pin freeze vs add/update/remove, drop-without-restore corruption-trap (fault injector), clean-reopen replay, maxBytes/maxAge/no-cap reclamation, retention-then-reopen. 107 db/generation/ temporal tests green, tsc clean. Docs (ADR-001, consistency-model, snapshots guide, api reference, RELEASES) updated to per-write granularity + retention.
2026-06-22 15:19:58 -07:00
retention?:
| 'all'
| 'adaptive'
| {
/** Keep at most this many recent committed generations (cap). */
maxGenerations?: number
/** Keep generations committed within this window in ms (cap). */
maxAge?: number
/** Keep total generational-history bytes at or below this (cap). */
maxBytes?: number
/** Adaptive byte budget for this brain, driven by a coordinator (e.g. cor). */
budgetBytes?: number
/** Run compaction automatically on flush()/close() (default: true). */
autoCompact?: boolean
}
// Memory management options
feat: COW always-on architecture + cloud storage clear() fix (v5.11.0) Major architectural improvements and critical bug fixes: ## COW Always-On Architecture - Removed cowEnabled flag from BaseStorage (COW cannot be disabled) - Eliminated marker file system (checkClearMarker, createClearMarker) - Simplified all code paths to assume COW is always enabled - COW automatically re-initializes after clear() operations ## Critical Bug Fix: Cloud Storage clear() - Fixed GCS clear() using correct paths (branches/ instead of entities/nouns/) - Fixed S3Compatible clear() path structure - Fixed R2 clear() implementation - Fixed Azure, FileSystem, OPFS, Memory clear() COW flag handling - clear() now deletes: branches/, _cow/, _system/ - Result: Cloud buckets can now be fully cleared (previously impossible) ## Container Memory Detection - Auto-detect Docker/K8s/Cloud Run memory limits (cgroup v1/v2) - Smart memory allocation (75% graph data, 25% query operations) - Environment variable support (CLOUD_RUN_MEMORY, MEMORY_LIMIT) - Production-grade containerized deployment support ## CommitLog streamHistory Feature - Added streamable commit history with pagination - Efficient memory usage for large commit histories - Support for branch filtering and time ranges ## Comprehensive Storage Documentation - Complete v5.11.0 file structure reference - Detailed path construction algorithms - 8 common storage scenarios with examples - Type-first storage, sharding, COW architecture explained - Public docs: docs/architecture/data-storage-architecture.md (1063 lines) ## Files Modified (14 files) - All 8 storage adapters (GCS, S3, R2, Azure, FS, OPFS, Memory, Historical) - BaseStorage core architecture - CommitLog with streaming - Brainy memory configuration - Parameter validation with container detection - Storage architecture documentation ## Breaking Changes NONE - COW was already enabled by default. This removes the ability to disable it. ## Migration No action required. Upgrade and clear() will work correctly on cloud storage. ## Impact - Users can now clear cloud storage buckets completely - No more corrupted buckets after clear() operations - Container deployments automatically optimize memory allocation - COW is mandatory and always enabled (safer, simpler) v5.11.0 - Production ready
2025-11-18 13:44:02 -08:00
maxQueryLimit?: number // Override auto-detected query result limit (max: 100000)
reservedQueryMemory?: number // Memory reserved for queries in bytes (e.g., 1073741824 = 1GB)
/**
* Controls when the WASM embedding engine is initialized.
*
* **Adaptive default (8.0):** when omitted, the engine eagerly initializes
* during `init()` whenever the WASM embedder is the *active* one i.e. no
* native `'embeddings'` provider is registered and this instance is a
* writer (not `mode: 'reader'`) running outside unit tests. The WASM module
* (93MB with the embedded model) takes 90-140s to compile on throttled
* CPUs, so paying that during boot rather than on the first `embed()`-driven
* call is the right default for a single-process server.
*
* The adaptive path skips itself automatically when a native embeddings
* provider owns embeddings, in reader-mode (readers query existing vectors
* and never embed), and in unit-test mode (kept fast via the mock embedder).
*
* - `true` force eager init during `init()` (the adaptive default already
* does this for the active-embedder writer case; set it explicitly to be
* unambiguous).
* - `false` explicit override to force lazy init (first `embed()` call)
* even when this instance is the active embedder.
*/
eagerEmbeddings?: boolean
// Plugin configuration
// Controls which plugins are loaded during init()
// - undefined (default): Auto-detect installed plugins (@soulcraft/cor, etc.)
// - false: No plugins — skip auto-detection entirely
// - []: No plugins — skip auto-detection entirely
// - ['@soulcraft/cor']: Load only specified plugins, no auto-detection
plugins?: string[] | false
// Logging configuration
verbose?: boolean // Enable verbose logging
silent?: boolean // Suppress all logging output
// Integration Hub
// Enable external tool integrations: Excel, Power BI, Google Sheets, etc.
// - true: Enable all integrations with default paths
// - false/undefined: Disable integrations (default)
// - IntegrationsConfig: Custom configuration
integrations?: boolean | IntegrationsConfig
// Migration configuration
// - true/undefined (default): Automatically run pending data migrations during
// init() for small datasets (<10K entities). Larger datasets defer with a
// notice — call brain.migrate() explicitly (optionally with backupTo).
// - false: Never auto-run; log a notice when pending migrations exist.
autoMigrate?: boolean
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
/**
* Brain-wide subtype enforcement mode.
*
* **Default-on since 8.0.0** (`undefined` resolves to `true`; in 7.30.x this
* was an opt-in flag defaulting to `false`).
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
*
* - `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
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
* 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.
feat: verb subtype + updateRelation + requireSubtype enforcement Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive shipped for entities only; this release ships the symmetric verb mirror plus the enforcement layer for ensuring every entity AND every relationship has both type AND subtype. Layer V1 — verb subtype mirror - HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField() - Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended, GetRelationsParams.subtype, GraphConstraints.subtype (for find connected) - relate() persists subtype on verbMetadata + GraphVerb + transaction ops - getRelations({ type, subtype }) fast-path filter with set membership - find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on the JS path; explicit error on depth > 1 pointing at Cortex native) - verbsToRelations + storage destructure sites surface subtype to top-level - All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich with subtype from metadata Layer V2 — updateRelation() closes a pre-7.30 gap - New first-class verb update method (parallel to update() for nouns) - Changes subtype/type/weight/confidence/data/metadata in place - Re-indexes in graph adjacency when verb type changes; id preserved - validateUpdateRelationParams enforces id + at-least-one-field-to-update Layer V3 — verb subtype storage rollup - verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage - verbSubtypeByIdCache for self-heal during update/delete - incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state - loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to _system/verb-subtype-statistics.json (mirrors noun-side shape) - rebuildVerbSubtypeCounts for poison recovery / explicit repair - getVerbSubtypeCountsByType accessor for the public counts API - Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata Layer V4 — verb counts API + relationshipSubtypesOf - brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point - brain.counts.topRelationshipSubtypes(verb, n) — top N by count - brain.relationshipSubtypesOf(verb) — sorted distinct subtypes Layer V5 — migrateField extended to verbs - New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun') - Mirror verb iteration via storage.getVerbs() with same path semantics - verbToRelationLike + buildRelationMigrationUpdate helpers project the storage verb shape onto the Entity<T>-shaped surface readPath understands - Routes through new updateRelation() for the verb-side rewrite Enforcement (opt-in in 7.30, default in 8.0) - brain.requireSubtype(type, options) — unified API for NounType OR VerbType. Registers per-type rules with optional values whitelist; composes with the brain-wide flag. - new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public write path validates the pairing guarantee. - { except: [NounType.Thing, ...] } form for catch-all type exemptions - Atomic-fail semantics on addMany / relateMany — pre-validate every item before any storage write, throw on first failure with item index - Per-type rules + brain-wide flag both throw with descriptive messages - VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so brain's own VFS writes don't get rejected when strict mode is on VFS labeling — concrete subtypes for infrastructure entities - VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection) - VFS directories: subtype: 'vfs-directory' - VFS files: subtype: 'vfs-file' (NounType still mime-based) - VFS containment edges: VerbType.Contains + subtype: 'vfs-contains' - Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' }) and distinguish Brainy's VFS Collections from user-created Collections Docs - docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section + Enforcement section. New full reference at the bottom split into Layer 1 (nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement. - docs/api/README.md adds updateRelation(), getRelations({ subtype }), the three verb-side counts methods, requireSubtype(), and the brain-wide constructor option. relate() params include subtype. - docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS - docs/architecture/finite-type-system.md extends Principle 1a to verbs - docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering getRelations and find({connected, subtype}) traversal - README.md "Subtypes" section now shows both noun + verb in one example + the enforcement APIs - RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix Tests - tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests covering V1 round-trips, V1 set membership, updateRelation in place, updateRelation preservation, V2 counts breakdown + point + topN + distinct, V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1 traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both entity kinds, V4 readBoth preservation, V5 per-type required rejection, V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement, V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement, V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause. Verification - Unit suite: 1468/1468 passing - Noun subtype integration (7.29 carryover): 26/26 passing - Verb subtype + enforcement integration: 30/30 passing - Type-check: clean - Build: clean - Public closed-source reference audit: clean Internal 8.0 spec - .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact) documents the contract upgrade Cortex 3.0 implements against: required-by- default subtype, SubtypeRegistry typing hook, native simplification, multi-hop traversal native fast path, brain.fillSubtypes() migration helper. Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
2026-06-05 11:15:52 -07:00
*
* Per-type registrations always compose with the brain-wide flag a type
* registered with `requireSubtype(type, { required: true })` is always
* enforced regardless of this flag.
*/
requireSubtype?: boolean | { except: Array<import('../types/graphTypes.js').NounType | import('../types/graphTypes.js').VerbType> }
/**
* Process role for multi-process safety on filesystem storage.
*
* - `'writer'` (default): acquires an exclusive lock on the storage directory at
* `init()` and refuses to open if another live writer holds the lock. Required
* for any instance that calls `add`/`update`/`remove`/`relate` or any other
* mutation. Released on `close()` and on process exit/SIGINT/SIGTERM.
* - `'reader'`: opens without acquiring the writer lock. Coexists with a live
* writer and with other readers. All mutation methods throw
* `Cannot mutate a read-only Brainy instance`. Use `Brainy.openReadOnly()`
* as a convenience factory.
*
* See `docs/concepts/multi-process.md` for the full coordination model.
*/
mode?: 'writer' | 'reader'
/**
* Bypass the writer-lock check at init even when another writer holds the lock.
* Use only when you know the existing lock is stale and stale-detection
* (PID liveness + heartbeat) cannot prove it. Logs a warning regardless.
*/
force?: boolean
feat(8.0): reserved-field enforcement — reservedFieldPolicy defaults to throw An untyped (JS) caller that smuggles a Brainy-reserved field (confidence, weight, subtype, visibility, service, createdBy, noun/verb, data, createdAt, updatedAt, _rev) inside a write-path metadata bag previously got a silent remap-or-drop — a class of bug where confidence-evolution writes no-oped for weeks before being caught on read-back. 8.0 closes this with no silent failures. - New BrainyConfig.reservedFieldPolicy: 'throw' | 'warn' | 'remap' (default 'throw'). 'throw' rejects the write naming every offending key + its correct write path; 'warn' remaps with a one-shot per-key warning; 'remap' is the legacy silent path. - Central enforceReservedPolicy gate wired into all four remap methods (add, update, relate, updateRelation) so live calls AND their transact()/with() mirrors honor it. Single-source reservedWritePath guidance shared by throw+warn. - 'warn' now warns for EVERY reserved key (closes the gap where only system-managed fields warned). Dead warnDropped* helpers removed. - Import pipeline migrated to route reserved values (confidence/weight/subtype) through dedicated params and strip reserved keys from extractor/customMetadata bags via the canonical split*MetadataRecord helpers — imports no longer trip the default throw. - Tests: new reservedFieldPolicy matrix (throw/warn/remap across every write path + transact); remap-correctness suite reframed as opt-in 'remap'; shared test-factory no longer emits reserved keys in custom metadata.
2026-06-20 15:37:21 -07:00
/**
* How write paths react when an untyped (JavaScript) caller smuggles a
* Brainy-reserved field (`RESERVED_ENTITY_FIELDS` / `RESERVED_RELATION_FIELDS`
* `confidence`, `weight`, `subtype`, `visibility`, `service`, `createdBy`,
* `noun`/`verb`, `data`, `createdAt`, `updatedAt`, `_rev`) **inside the
* `metadata` bag** of `add()` / `update()` / `relate()` / `updateRelation()`
* (and their `transact()` / `with()` mirrors). TypeScript callers can't write
* these shapes at all the compile-time guard on the metadata param types
* (`NoReservedEntityKeys` / `NoReservedRelationKeys`) rejects a literal
* reserved key so this policy only governs untyped callers that slip one
* past the compiler.
*
* - `'throw'` (**default, 8.0**): a reserved key in the bag throws a clear
* `Error` naming the offending key(s) and the correct write path. No silent
* remap, no data loss, no surprise. This is the 8.0 "no silent failures"
* contract.
* - `'warn'`: legacy remapping with a loud, one-shot (per key, per process)
* warning for EVERY reserved key found user-mutable fields are remapped to
* their dedicated top-level param (top-level wins when both are supplied),
* system-managed fields are dropped. Use while migrating untyped call sites.
* - `'remap'`: the pre-8.0 silent remapping, no warning. Last-resort
* compatibility hatch for code that intentionally relies on the bag path.
*
* @default 'throw'
*/
reservedFieldPolicy?: 'throw' | 'warn' | 'remap'
}
// ============= Neural API Types =============
/**
* Neural similarity parameters
*/
export interface NeuralSimilarityParams {
between?: [any, any] // Compare two items
items?: any[] // Compare multiple items
explain?: boolean // Return detailed breakdown
}
/**
* Neural clustering parameters
*/
export interface NeuralClusterParams {
items?: string[] | Entity[] // Items to cluster (or all)
algorithm?: 'hierarchical' | 'kmeans' | 'dbscan' | 'spectral'
params?: {
k?: number // Number of clusters (kmeans)
threshold?: number // Distance threshold (hierarchical)
epsilon?: number // DBSCAN epsilon
minPoints?: number // DBSCAN min points
}
visualize?: boolean // Return visualization data
}
/**
* Neural anomaly detection parameters
*/
export interface NeuralAnomalyParams {
threshold?: number // Standard deviations (default: 2.5)
type?: NounType // Check specific type
method?: 'isolation' | 'lof' | 'statistical' | 'autoencoder'
returnScores?: boolean // Return anomaly scores
}
// ============= Content Extraction Types =============
/**
* Detected content type for smart text extraction
*
*/
export type ContentType = 'plaintext' | 'richtext-json' | 'html' | 'markdown'
/**
* Content category for extracted segments
*
* Universal categories that work across documents, code, and UI:
* - 'title': Names the subject headings, identifiers, labels, JSON keys
* - 'annotation': Human explanation comments, docstrings, captions, alt text
* - 'content': Body substance paragraphs, list items, flowing text
* - 'value': Data literals strings, numbers, form values, error messages
* - 'code': Unparsed code blocks (custom parsers decompose into above)
* - 'structural': Boilerplate keywords, operators, punctuation, formatting
*
* Built-in extractors produce: 'title', 'content', 'code'.
* All 6 categories are available for custom parsers.
*/
export type ContentCategory = 'title' | 'annotation' | 'content' | 'value' | 'code' | 'structural'
/**
* A segment of extracted text with its content category
*
*/
export interface ExtractedSegment {
/** The extracted text content */
text: string
/** What role this text plays: 'title' | 'annotation' | 'content' | 'value' | 'code' | 'structural' */
contentCategory: ContentCategory
}
// ============= Semantic Highlighting Types =============
/**
* Parameters for hybrid highlighting
*
* Zero-config highlighting that returns both text (exact) and semantic (concept) matches.
* Perfect for UI highlighting at different levels.
*
* Added contentType hint and contentExtractor callback for structured text
* (rich-text JSON, HTML, Markdown). Auto-detects format when not specified.
*
* @example
* ```typescript
* // Plain text
* const highlights = await brain.highlight({
* query: "david the warrior",
* text: "David Smith is a brave fighter who battles dragons"
* })
*
* // Rich-text JSON (auto-detected)
* const highlights = await brain.highlight({
* query: "david the warrior",
* text: JSON.stringify(tiptapDocument)
* })
*
* // Custom extractor (for proprietary formats)
* const highlights = await brain.highlight({
* query: "function",
* text: sourceCode,
* contentExtractor: (text) => treeSitterParse(text)
* })
* ```
*/
export interface HighlightParams {
/** The search query to match against */
query: string
/** The text to highlight (e.g., entity.data) */
text: string
/** Granularity of highlighting: 'word' (default), 'phrase', or 'sentence' */
granularity?: 'word' | 'phrase' | 'sentence'
/** Minimum semantic similarity score for semantic matches (default: 0.5) */
threshold?: number
/**
* Optional content type hint to skip auto-detection.
* When omitted, the content type is detected from the text content.
*/
contentType?: ContentType
/**
* Optional custom content extractor function.
* When provided, bypasses built-in detection and extraction entirely.
* Use this to plug in custom parsers (tree-sitter, Monaco, proprietary formats).
*/
contentExtractor?: (text: string) => ExtractedSegment[]
}
/**
* A highlight showing which text matched the query
*
* matchType tells the UI how to style the highlight:
* - 'text': Exact word match (strongest signal, highest confidence)
* - 'semantic': Conceptually similar match (may need softer highlight)
*/
export interface Highlight {
/** The text that matched */
text: string
/** Match score (0-1). For text matches, always 1.0. For semantic, varies by similarity. */
score: number
/** Position in original text [start, end] */
position: [number, number]
/** Match type: 'text' (exact word match) or 'semantic' (concept match) */
matchType: 'text' | 'semantic'
/**
* Content category of the source segment: 'title' | 'annotation' | 'content' | 'value' | 'code' | 'structural'.
* Present when the input text was structured (JSON, HTML, Markdown).
*/
contentCategory?: ContentCategory
}
// ============= Export all types =============
export * from './graphTypes.js' // Re-export NounType, VerbType, etc.