diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..cdb2ab14
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ node:
+ name: Node ${{ matrix.node-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: ['22', '24']
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: npm
+ - run: npm ci
+ - run: npm run test:unit
+
+ bun:
+ name: Bun (latest)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: npm
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+ - run: npm ci
+ # test:bun imports the built dist/, so build first.
+ - run: npm run build
+ # Bun as a runtime is the supported Bun story (`bun add` / `bun run`).
+ - run: npm run test:bun
diff --git a/.gitignore b/.gitignore
index d7d40cbb..64e235b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -93,9 +93,14 @@ docs/internal/
# Rust/Cargo build artifacts
src/embeddings/candle-wasm/target/
src/embeddings/candle-wasm/Cargo.lock
-src/embeddings/wasm/pkg/
-# But keep the pre-built WASM (committed for users without Rust)
+# Ignore the wasm-pack output dir's CONTENTS (note the `/*`, not `/`, so the
+# re-includes below can take effect — git cannot re-include a file whose parent
+# DIRECTORY is excluded). Keep the pre-built WASM committed: it ships in the npm
+# package anyway, it lets consumers + CI build without a Rust/wasm-pack toolchain,
+# and versioning it makes the shipped artifact reproducible (not "whatever the
+# maintainer last built").
+src/embeddings/wasm/pkg/*
!src/embeddings/wasm/pkg/*.wasm
!src/embeddings/wasm/pkg/*.js
!src/embeddings/wasm/pkg/*.d.ts
diff --git a/.versionrc.json b/.versionrc.json
deleted file mode 100644
index dac6ceca..00000000
--- a/.versionrc.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "types": [
- {"type": "feat", "section": "✨ Features"},
- {"type": "fix", "section": "🐛 Bug Fixes"},
- {"type": "docs", "section": "📚 Documentation"},
- {"type": "refactor", "section": "♻️ Code Refactoring"},
- {"type": "perf", "section": "⚡ Performance Improvements"},
- {"type": "test", "section": "✅ Tests"},
- {"type": "build", "section": "🔧 Build System"},
- {"type": "ci", "section": "🔄 CI/CD"},
- {"type": "style", "hidden": true},
- {"type": "chore", "hidden": true}
- ],
- "compareUrlFormat": "https://github.com/soulcraftlabs/brainy/compare/{{previousTag}}...{{currentTag}}",
- "commitUrlFormat": "https://github.com/soulcraftlabs/brainy/commit/{{hash}}",
- "issueUrlFormat": "https://github.com/soulcraftlabs/brainy/issues/{{id}}",
- "userUrlFormat": "https://github.com/{{user}}",
- "releaseCommitMessageFormat": "chore(release): {{currentTag}}",
- "issuePrefixes": ["#"],
- "header": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n",
- "scripts": {
- "postbump": "echo '✅ Version bumped to' $(node -p \"require('./package.json').version\")"
- }
-}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de64d359..fd0de54e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,329 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [8.9.0](https://github.com/soulcraftlabs/brainy/compare/v8.8.2...v8.9.0) (2026-07-19)
+
+- docs: measured performance envelopes v1 (per-op p50/p95 at 1k and 10k, pure-JS floor) (5cabd78)
+- fix: release drains in-flight writer-lock heartbeat — no phantom lock after unlink (70e4bc8)
+- feat: flush() never compacts — history maintenance moves to close() with bounded passes (300d9f2)
+
+
+### [8.8.2](https://github.com/soulcraftlabs/brainy/compare/v8.8.1...v8.8.2) (2026-07-19)
+
+- fix: one field-resolution law across aggregation hooks, source.where, removeMany, and find() spellings (945d92d)
+- chore: push public docs to the soulcraft.com ingest door on release (42037d0)
+
+
+### [8.8.1](https://github.com/soulcraftlabs/brainy/compare/v8.8.0...v8.8.1) (2026-07-18)
+
+- fix: O(1) adaptive retention accounting + historyStats fleet audit (6207e48)
+- fix: import dedup off-switch honesty + brain-owned lifecycle for the background pass (4fcef7b)
+
+
+### [8.8.0](https://github.com/soulcraftlabs/brainy/compare/v8.7.1...v8.8.0) (2026-07-17)
+
+- feat: OS-limit detection for pool-scale deployments (16a73b8)
+
+
+### [8.7.1](https://github.com/soulcraftlabs/brainy/compare/v8.7.0...v8.7.1) (2026-07-17)
+
+- fix: race-proof writer-lock acquisition + machine-readable conflict through init (01a3b46)
+
+
+### [8.7.0](https://github.com/soulcraftlabs/brainy/compare/v8.6.0...v8.7.0) (2026-07-17)
+
+- feat: scaled transact budgets + labeled timeout diagnostics + envelope docs (6ef9fcb)
+
+
+### [8.6.0](https://github.com/soulcraftlabs/brainy/compare/v8.5.2...v8.6.0) (2026-07-17)
+
+- feat: brain.auditGraph() — read-only graph-truth audit (2a03fae)
+
+
+### [8.5.2](https://github.com/soulcraftlabs/brainy/compare/v8.5.1...v8.5.2) (2026-07-17)
+
+- fix: exception-safe aggregation backfill + generation-verified adoption + loud open-path guards (a77b064)
+
+
+### [8.5.1](https://github.com/soulcraftlabs/brainy/compare/v8.5.0...v8.5.1) (2026-07-17)
+
+- fix: aggregation state adoption on reopen + single-flight backfill + query-cap ratchet removal (da55be7)
+- docs: external-backups/sparse-storage guide + generation fact log concept (593bb8b)
+
+
+### [8.5.0](https://github.com/soulcraftlabs/brainy/compare/v8.4.0...v8.5.0) (2026-07-15)
+
+- test: tolerant timing assertion in the execution-time measure test (4dc0a92)
+- feat: committedGeneration capability + pinned durability/stability contracts (d1ecee1)
+- docs: RELEASES.md entry for 8.5.0 (provider fact-log access + shared verifier) (e4f37cd)
+- feat: provider access to the fact log + shared stamp verifier via internals (352e356)
+
+
+### [8.4.0](https://github.com/soulcraftlabs/brainy/compare/v8.3.3...v8.4.0) (2026-07-15)
+
+- docs: RELEASES.md entry for 8.4.0 (generation fact log + family stamp) (4a60b43)
+- feat: entity-tree family stamp — sourceGeneration + rollup coherence at open (2888ae6)
+- feat: generation fact log — after-image commit records, dual-written at every commit point (38b0041)
+
+
+### [8.3.3](https://github.com/soulcraftlabs/brainy/compare/v8.3.2...v8.3.3) (2026-07-15)
+
+- docs: RELEASES.md entry for 8.3.3 (rename containment fix + repair) (c3feafd)
+- test: lens-consistency regression — combined vs subtype-only vs canonical ground truth (4fb41f9)
+- fix: VFS rename moves the containment edge — no ghost in the old directory (af8c179)
+
+
+### [8.3.2](https://github.com/soulcraftlabs/brainy/compare/v8.3.1...v8.3.2) (2026-07-14)
+
+- docs: RELEASES.md entry for 8.3.2 (honest counters) (0932ecd)
+- fix: honest counters — removal never re-reads the removed record + repairIndex recounts and persists all rollups (2e2ba9c)
+
+
+### [8.3.1](https://github.com/soulcraftlabs/brainy/compare/v8.3.0...v8.3.1) (2026-07-14)
+
+- docs: RELEASES.md entry for 8.3.1 (full-removal deletes + family-scoped gate) (c0c68ac)
+- fix: full-removal canonical deletes + family-scoped migration gate (366f9a9)
+- docs: cite the cross-layer integrity contract generically in comments and notes (1d26988)
+
+
+### [8.3.0](https://github.com/soulcraftlabs/brainy/compare/v8.2.8...v8.3.0) (2026-07-13)
+
+- docs: RELEASES.md entry for 8.3.0 (heal-cost + cross-layer integrity contract) (7692c6f)
+- perf: parallel + id-only canonical enumeration (heal-cost dominant term) (ec5b933)
+- feat: registered-blob family contract — declared index blobs are undeletable (bfa1762)
+- feat: validateIndexConsistency delegates to provider invariants (6bcb54f)
+
+
+### [8.2.8](https://github.com/soulcraftlabs/brainy/compare/v8.2.7...v8.2.8) (2026-07-13)
+
+- fix: honest index readiness — no silently-empty queries on a cold index (d0f69c7)
+
+
+### [8.2.7](https://github.com/soulcraftlabs/brainy/compare/v8.2.6...v8.2.7) (2026-07-13)
+
+- fix: restore loadBinaryBlob fault-propagation (native column-store lockstep) (b6c7039)
+
+
+### [8.2.6](https://github.com/soulcraftlabs/brainy/compare/v8.2.5...v8.2.6) (2026-07-13)
+
+- docs: RELEASES.md entry for 8.2.6 (write/index-spine hardening) (a873852)
+- chore: hold loadBinaryBlob fault-propagation for the cortex column-store lockstep (36c10c1)
+- fix: aggregation surfaces materialize/state-load failures loudly (02eff64)
+- fix: surface a degraded derived index on reads instead of serving it silently (ba958d9)
+- fix: saveBinaryBlob never acks a durable write that stored nothing (7feba49)
+- fix: refuse writes when single-op history cannot be made durable (54c1836)
+- fix: clear() wipes the full native/derived footprint, not a subset (d8301f8)
+- fix: surface segment/entity read faults loudly instead of masking as absent (af5d2f3)
+- fix: spine hardening pass 1 (part) — count symmetry, honest partial-load, flush durability, read-fault propagation (119087a)
+- test: pin the read-your-writes contract under the single writer (eb9c4eb)
+
+
+### [8.2.5](https://github.com/soulcraftlabs/brainy/compare/v8.2.4...v8.2.5) (2026-07-12)
+
+- docs: RELEASES.md entry for 8.2.5 (honest rollback-failure response) (a7c7aa5)
+- fix: honest response when a transaction rollback cannot complete (711d2f0)
+
+
+### [8.2.4](https://github.com/soulcraftlabs/brainy/compare/v8.2.3...v8.2.4) (2026-07-12)
+
+- docs: RELEASES.md entry for 8.2.4 (non-destructive restore) (4574695)
+- fix: non-destructive, crash-resumable restore (a2f4f6a)
+
+
+### [8.2.3](https://github.com/soulcraftlabs/brainy/compare/v8.2.2...v8.2.3) (2026-07-12)
+
+- docs: RELEASES.md entry for 8.2.3 (transact durability barrier) (be5ce0b)
+- fix: transact durability barrier — committed transactions are durable on return (3b8fa51)
+
+
+### [8.2.2](https://github.com/soulcraftlabs/brainy/compare/v8.2.1...v8.2.2) (2026-07-11)
+
+- docs: RELEASES.md entry for 8.2.2 (transaction timeout rollback) (ed97006)
+- fix: transaction timeout rolls back applied operations (no torn state) (508a8e3)
+
+
+### [8.2.1](https://github.com/soulcraftlabs/brainy/compare/v8.2.0...v8.2.1) (2026-07-10)
+
+- test: update graph-index operation constructors to the VerbEndpointInts signature (62a449d)
+- docs: RELEASES.md entry for 8.2.1 (transact forward-ref parity fix) (7089782)
+- fix: transact forward references resolve graph endpoint ints at execute time (a175406)
+
+
+### [8.2.0](https://github.com/soulcraftlabs/brainy/compare/v8.1.0...v8.2.0) (2026-07-10)
+
+- docs: RELEASES.md entry for 8.2.0 (temporal VFS) (98ceadc)
+- feat: temporal VFS — file content joins the Model-B immutability model (a3467e1)
+- docs: pin the write-path invariant in the plugin contract (the onChange change-feed guarantee) (4af8fb3)
+
+
+### [8.1.0](https://github.com/soulcraftlabs/brainy/compare/v8.0.17...v8.1.0) (2026-07-10)
+
+- docs: RELEASES.md entry for 8.1.0 (brain.onChange change feed) (4e9be08)
+- feat: brain.onChange — the in-process change feed for every committed mutation (fd5edb5)
+
+
+### [8.0.17](https://github.com/soulcraftlabs/brainy/compare/v8.0.16...v8.0.17) (2026-07-08)
+
+- docs: RELEASES.md entry for 8.0.17 (canonical count recovery + dead-machinery sweep) (6b8b9cb)
+- fix: count recovery scans the canonical layout; remove the dead 7.x hnsw sharding machinery (352e2da)
+
+
+### [8.0.16](https://github.com/soulcraftlabs/brainy/compare/v8.0.15...v8.0.16) (2026-07-08)
+
+- docs: RELEASES.md entry for 8.0.16 (atomic ifAbsent/upsert + exact blob refCounts) (54e7c0e)
+- fix: atomic ifAbsent/upsert inserts + exact blob reference counts under concurrency (867939e)
+
+
+### [8.0.15](https://github.com/soulcraftlabs/brainy/compare/v8.0.14...v8.0.15) (2026-07-08)
+
+- docs: RELEASES.md entry for 8.0.15 (atomic ifRev CAS) (b1fe25a)
+- fix: ifRev CAS is atomic — the revision check now runs under the commit mutex (9a3d1bd)
+
+
+### [8.0.14](https://github.com/soulcraftlabs/brainy/compare/v8.0.13...v8.0.14) (2026-07-07)
+
+- docs: RELEASES.md entry for 8.0.14 (migration preserves branch-scoped non-entity state) (64188a3)
+- fix: 7→8 migration preserves branch-scoped non-entity state instead of deleting it (a93bb4e)
+
+
+### [8.0.13](https://github.com/soulcraftlabs/brainy/compare/v8.0.12...v8.0.13) (2026-07-07)
+
+- docs: RELEASES.md entry for 8.0.13 (accurate boot log for established stores) (38e8de5)
+- fix: an established store no longer boot-logs "New installation" (3086916)
+
+
+### [8.0.12](https://github.com/soulcraftlabs/brainy/compare/v8.0.11...v8.0.12) (2026-07-07)
+
+- docs: RELEASES.md entry for 8.0.12 (7→8 VFS recovery, zero-rebuild cold open, strict query operators) (d9017e7)
+- fix: recover VFS content blobs stranded by a 7→8 upgrade, in place on open (c0f6ccd)
+- fix: validate where-operators and align the in-memory matcher to the documented set (6821e19)
+- docs: correct rc-era time-travel staleness + record the embedding-model ordering constraint (68da660)
+- fix: cold-open no longer re-derives durable indexes — complete the readiness contract for all three providers (61c247c)
+- docs: RELEASES.md entry for 8.0.11 (exit-hang class closed for every op shape) (4fde94b)
+
+
+### [8.0.11](https://github.com/soulcraftlabs/brainy/compare/v8.0.10...v8.0.11) (2026-07-02)
+
+- fix: no script shape can hang on brainy's internals — unref every maintenance timer + one-shot beforeExit (30eacbd)
+- docs: RELEASES.md entry for 8.0.10 (clean process exit after close) (2da2736)
+
+
+### [8.0.10](https://github.com/soulcraftlabs/brainy/compare/v8.0.9...v8.0.10) (2026-07-02)
+
+- fix: a bare script now exits cleanly after close() — release every process keep-alive (c540d63)
+
+
+### [8.0.9](https://github.com/soulcraftlabs/brainy/compare/v8.0.8...v8.0.9) (2026-07-02)
+
+- feat: guarded plugin auto-detection — installing @soulcraft/cor is the opt-in (588267b)
+
+
+### [8.0.8](https://github.com/soulcraftlabs/brainy/compare/v8.0.7...v8.0.8) (2026-07-02)
+
+- docs: plugins are explicit opt-in — correct the README scale section and plugins config comment (e420369)
+
+
+### [8.0.7](https://github.com/soulcraftlabs/brainy/compare/v8.0.1...v8.0.7) (2026-07-02)
+
+- docs: GA version is 8.0.7 — npm retired 8.0.0-8.0.6 (January dev-cycle unpublishes) (5db2c41)
+- chore(release): 8.0.1 (48bea9e)
+- docs: flagship README for the 8.0 GA; GA version is 8.0.1 (e44620e)
+- docs: rename the native provider to @soulcraft/cor across public docs and JSDoc (bf4a333)
+- chore(release): 8.0.0 (a3c2717)
+- docs: RELEASES.md 8.0.0 GA entry (RC notes become history) (4584d0b)
+- feat: promote the 8.0 u64-id line to main for the 8.0.0 GA (55d57f8)
+- fix(8.0): byte-copy _id_mapper/* in the pre-upgrade backup (cor's write-new nuance) (a30ed72)
+- chore(release): 7.33.5 (29b9d5f)
+- fix: metadata cold-read guard — no more silent [] on cold find({where}) (7.33.5) (9dc4c5e)
+- fix(8.0): metadata cold-read guard — no more silent [] on cold find({where}) (79e8709)
+- docs(8.0): add module JSDoc to typeValidation.ts (the one file missing a module block) (ab53fa0)
+- feat(8.0): auto pre-upgrade backup — hard-link snapshot before the 7.x→8.0 migration (1aad1f6)
+- fix(ci): commit the prebuilt wasm pkg + build before test:bun (green CI on fresh clone) (ed178e2)
+- chore(release): 7.33.4 (2be3d0f)
+- fix: never serve a silent [] from find({connected}) on a cold-loaded graph (fd699d0)
+- chore(release): 7.33.3 (d1665bb)
+- fix: re-validate find() results against the predicate (index-integrity guard) (7b5db0d)
+- chore(release): 7.33.2 (9593a27)
+- fix: graph adjacency cold-load consistency guard — no more silent [] on connected (1694f68)
+- chore(release): 7.33.1 (811c7da)
+- fix: getNouns cursor pagination re-scanned the first page forever (permanent CPU loop) (6721c52)
+- chore(release): 7.33.0 (526aaad)
+- feat: visibility tier (public/internal/system) on nouns + verbs (3a62445)
+- chore(release): 7.32.2 (c53dd61)
+- refactor: rename BackupData → PortableGraph (the type is interchange, not a backup) (89036de)
+- chore(release): 7.32.1 (5e7379d)
+- fix: getNouns().totalCount reports true total, not page size; quiet benign mmap-vector log (edff637)
+- chore(release): 7.32.0 (adec0ba)
+- feat: portable graph export()/import() (BackupData v1) on brain.data() (a408d37)
+- chore(release): 7.31.8 (89c6d04)
+- fix: query-cap memory misread (MemAvailable + floor) + rootDirectory getter for native mmap fast-path (3f8e097)
+- chore(release): 7.31.7 (4f8159c)
+- fix: vfs.rename() issues a metadata-only update + rollback of fresh adds removes them (ac29b0e)
+- chore(release): 7.31.6 (9b52629)
+- fix: remap reserved fields from update() metadata patches to their canonical location (67e5fc8)
+- chore(release): 7.31.5 (e5ec658)
+- fix: feature-detect setVectorBackend before wiring the mmap-vector backend (a537b36)
+- chore(release): 7.31.4 (a8cbab6)
+- fix: feature-detect setConnectionsCodec before wiring the connections codec (747ab97)
+- chore(release): 7.31.3 (cfb051c)
+- fix: mmap-vector backend capacity NaN at the provider FFI boundary (eade6ff)
+
+
+### [8.0.0-rc.9](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.8...v8.0.0-rc.9) (2026-07-01)
+
+- docs(8.0): RELEASES.md — rc.9 (migration LOCK + 6x cosine + ES2023/Node22 floor) (3a33987)
+- chore(8.0): ES2023 target + drop DOM lib + downlevelIteration (config truth-up) (cf74c25)
+- perf(8.0): allocation-free distance loops (6x cosine) — evidence-revised Fork X (b5bc73f)
+- feat(8.0): #18 coordinated migration LOCK — block-and-queue the 7.x→8.0 auto-upgrade (67bbf69)
+- chore(8.0): modernize toolchain + position Bun as a runtime (ca9129a)
+
+
+### [8.0.0-rc.8](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.7...v8.0.0-rc.8) (2026-06-30)
+
+- docs(8.0): RELEASES.md — rc.8 (no-freeze online whole-brain auto-upgrade) (5af48a9)
+- feat(8.0): no-freeze auto-upgrade hooks — isMigrating() deference + stampBrainFormat() + brain-format export (b6b9198)
+
+
+### [8.0.0-rc.7](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.6...v8.0.0-rc.7) (2026-06-30)
+
+- docs(8.0): RELEASES.md — rc.7 (cold-graph self-heal + billion-scale RAM + version handshake) (1ddc786)
+- perf(8.0): bound per-id generation history chains (O(W+L) resident, was O(N)) (a859d6e)
+- feat(8.0): eager graphIndex.init() before the isReady() rebuild gate (8f4787b)
+- feat(8.0): version-handshake marker (formatInfo + indexEpoch) for whole-brain auto-upgrade (fc7f110)
+- fix(8.0): never serve a silent [] from find({connected}) on a cold-loaded graph (229b067)
+- perf(8.0): represent the committed-generation ledger as an interval set (93f61db)
+- perf(8.0): drop O(N)-resident id-keyed storage caches; source counts from the record (b6beb7f)
+
+
+### [8.0.0-rc.6](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.5...v8.0.0-rc.6) (2026-06-29)
+
+- docs(8.0): RELEASES.md — rc.6 (perf + native-provider contract + test hygiene) (6daa70e)
+- feat(8.0): wire the two cor-confirmed metadata-provider contract additions (8b19122)
+- test(8.0): re-home orphaned test files into the gate + guard against recurrence (3f9f140)
+- perf(8.0): negation/absence where-operators via roaring-bitmap difference (5f974ab)
+- perf(8.0): HNSW removeItem is O(in-degree) via a reverse-adjacency index (72df557)
+
+
+### [8.0.0-rc.5](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.4...v8.0.0-rc.5) (2026-06-29)
+
+- docs(8.0): RELEASES.md — rc.5 hardening + the breaking operator removal (6c9a438)
+- refactor(8.0): remove the 4 deprecated query-operator aliases (clean break) (ddcc0c7)
+- refactor(8.0): remove dead/deprecated code (legacy sweep) (b9369f2)
+- refactor(8.0): API-surface + quality polish from the readiness audit (a52dba2)
+- fix(8.0): close GA-blocking correctness gaps from the readiness audit (47e8031)
+- docs(8.0): correct public docs to the real 8.0 API + honest perf claims (40d2cd5)
+- fix(8.0): re-validate find() results against the predicate (index-integrity guard) (3d11619)
+
+
+### [8.0.0-rc.4](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.3...v8.0.0-rc.4) (2026-06-24)
+
+- docs(8.0): drop the DeletedItemsIndex section + pseudo-code from index-architecture (e7b50cf)
+- fix(8.0): gate native graph analytics on the provider readiness flag (d321cf5)
+- refactor(8.0): remove dead, unreachable, and unwired modules (bf0afe8)
+- build(8.0): clean dist before every build so stale artifacts never ship (03d6540)
+- feat(8.0): #35 part-3 — supply at-gen candidate vectors for the native exact-rerank (c9e2169)
+
+
### [8.0.0-rc.3](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.2...v8.0.0-rc.3) (2026-06-23)
- test(8.0): de-flake the VFS path-cache timing assertion (0e8972c)
diff --git a/README.md b/README.md
index 75536c82..d1342f52 100644
--- a/README.md
+++ b/README.md
@@ -1,439 +1,217 @@
-# Brainy
-
-
+
-[](https://www.npmjs.com/package/@soulcraft/brainy)
-[](https://www.npmjs.com/package/@soulcraft/brainy)
-[](https://soulcraft.com/docs)
-[](LICENSE)
-[](https://www.typescriptlang.org/)
+Brainy
-**Three database paradigms. One API. Zero configuration.**
+
+ Three database paradigms. One API. Zero configuration.
+ The in-process knowledge database for TypeScript — vector search, graph traversal,
+ and metadata filtering unified in a single query.
+
-Built because we were tired of stitching together Pinecone + Neo4j + MongoDB and spending weeks on configuration before writing a single line of business logic. Brainy unifies vector search, graph traversal, and metadata filtering so you don't have to choose.
+
+
+
+
+
+
+
+
-**New here?** → **[What is Brainy? — plain-language overview, no jargon](docs/eli5.md)**
+
+ Quick start ·
+ One query ·
+ Features ·
+ Scale with Cor ·
+ Docs
+
---
-## Install
+Built because we were tired of stitching a vector store to a graph database to a document store — and spending weeks on plumbing before writing a line of business logic. Brainy indexes every fact **three ways at once** and lets one call query them together:
+
+| You write | Brainy indexes it as | You query it with |
+|---|---|---|
+| `data: 'Ada wrote the first program'` | a **384-dim vector** (local embedding — no API key) | `find({ query: 'computing pioneers' })` |
+| `metadata: { field: 'CS', year: 1843 }` | **structured fields** (O(1) exact, O(log n) range) | `find({ where: { year: { lessThan: 1900 } } })` |
+| `relate({ from: ada, to: babbage })` | a **typed, directed graph edge** | `find({ connected: { to: babbage, depth: 2 } })` |
+
+It runs **inside your process** — no server, no Docker, nothing to operate — and persists to plain files you can snapshot with a hard link.
+
+**New here?** → **[What is Brainy? — plain-language overview, no jargon](docs/eli5.md)**
+
+## Quick start
```bash
-npm install @soulcraft/brainy
+bun add @soulcraft/brainy # Bun ≥ 1.1 — recommended
+npm install @soulcraft/brainy # Node.js ≥ 22
```
-## Quick Start
-
```javascript
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
-const brain = new Brainy()
+const brain = new Brainy() // in-memory; one line swaps to disk
await brain.init()
-// Add knowledge — text auto-embeds, metadata auto-indexes
-const reactId = await brain.add({
+// Text auto-embeds locally; metadata auto-indexes
+const react = await brain.add({
data: 'React is a JavaScript library for building user interfaces',
type: NounType.Concept,
+ subtype: 'library',
metadata: { category: 'frontend', year: 2013 }
})
-const nextId = await brain.add({
- data: 'Next.js framework for React with server-side rendering',
+const next = await brain.add({
+ data: 'Next.js is a React framework with server-side rendering',
type: NounType.Concept,
- metadata: { category: 'framework', year: 2016 }
+ subtype: 'framework',
+ metadata: { category: 'frontend', year: 2016 }
})
-// Create a relationship
-await brain.relate({ from: nextId, to: reactId, type: VerbType.BuiltOn })
-
-// Query all three paradigms at once
-const results = await brain.find({
- query: 'modern frontend frameworks', // Vector similarity
- where: { year: { greaterThan: 2015 } }, // Metadata filtering
- connected: { to: reactId, depth: 2 } // Graph traversal
-})
+await brain.relate({ from: next, to: react, type: VerbType.DependsOn, subtype: 'runtime' })
```
-**[Full API Reference](docs/api/README.md)** | **[soulcraft.com/docs](https://soulcraft.com/docs)**
-
----
-
-## Three Indexes, One Query
-
-Every piece of knowledge lives in three indexes simultaneously:
-
-- **`data`** → **Vector index** — Content for semantic search. Strings auto-embed into 384-dim vectors. Queried with `find({ query: '...' })`.
-- **`metadata`** → **Metadata index** — Structured fields for filtering. O(1) lookups. Queried with `find({ where: { ... } })`.
-- **`relate()`** → **Graph index** — Typed, directed relationships between entities. Traversed with `find({ connected: { ... } })`.
-
-```javascript
-// Data → vector index (semantic search)
-const articleId = await brain.add({
- data: 'A deep dive into transformer architectures',
- type: NounType.Document,
- metadata: { author: 'Dr. Chen', year: 2024, tags: ['AI'] } // → metadata index
-})
-
-// Relationships → graph index
-await brain.relate({ from: authorId, to: articleId, type: VerbType.Authored })
-
-// Query all three at once
-brain.find({
- query: 'attention mechanisms', // Vector similarity
- where: { year: { greaterThan: 2023 } }, // Metadata filter
- connected: { from: authorId, depth: 1 } // Graph traversal
-})
-```
-
-**[Data Model Reference](docs/DATA_MODEL.md)** | **[Query Operators](docs/QUERY_OPERATORS.md)**
-
----
-
-## Features
-
-### Triple Intelligence
-
-Vector search + graph traversal + metadata filtering in every query. No stitching services together — one `find()` call combines all three.
+## One query, three engines
```javascript
const results = await brain.find({
- query: 'machine learning',
- where: { department: 'engineering', level: 'senior' },
- connected: { from: teamLeadId, via: VerbType.WorksWith, depth: 2 }
+ query: 'modern frontend frameworks', // vector — what it means
+ where: { year: { greaterThan: 2015 } }, // metadata — what it is
+ connected: { to: react, depth: 2 } // graph — what it touches
})
```
-### Hybrid Search
+Every clause is optional; any combination composes. Under the hood Brainy plans the query across an HNSW vector index, a roaring-bitmap field index, and an adjacency graph index — and re-validates every result against your predicate before returning it, so a corrupt index can never hand you a wrong answer.
-Automatically combines keyword (text) and semantic (vector) search. No configuration needed.
+## Feature tour
+
+### The database is a value
+
+Pin it, rewind it, fork it. Snapshot isolation without a server.
```javascript
-await brain.find({ query: 'David Smith' }) // Auto: text + semantic
-await brain.find({ query: 'AI concepts', searchMode: 'semantic' }) // Semantic only
-await brain.find({ query: 'exact id', searchMode: 'text' }) // Text only
+const db = brain.now() // pin current state — O(1)
+
+await brain.transact([ // atomic all-or-nothing, CAS-guarded
+ { op: 'update', id: order, metadata: { status: 'paid' } },
+ { op: 'relate', from: invoice, to: order, type: VerbType.References, subtype: 'billing' }
+], { ifAtGeneration: db.generation })
+
+await db.get(order) // still 'pending' — pinned forever
+await brain.get(order) // 'paid' — live
+
+const lastWeek = await brain.asOf(Date.now() - 7 * 86_400_000) // full query surface, past state
+const whatIf = await db.with([{ op: 'remove', id: order }]) // speculative — never touches disk
+await brain.now().persist('/backups/today') // instant hard-link snapshot
```
-### Query Operators
+**[Consistency model](docs/concepts/consistency-model.md)** · **[Snapshots & time travel](docs/guides/snapshots-and-time-travel.md)**
-Filter metadata with equality, comparison, array, existence, pattern, and logical operators:
+### Local embeddings — no API keys
+
+Strings embed on-device with a bundled MiniLM model (WASM). Semantic search works offline, in CI, and on air-gapped machines, at zero cost per call. Hybrid keyword + semantic ranking is the default:
```javascript
-await brain.find({
- where: {
- status: 'active', // Exact match
- score: { greaterThan: 90 }, // Comparison
- tags: { contains: 'ai' }, // Array
- anyOf: [{ role: 'admin' }, { role: 'owner' }] // Logical OR
- }
-})
+await brain.find({ query: 'David Smith' }) // auto: text + semantic
+await brain.find({ query: 'AI concepts', searchMode: 'semantic' }) // semantic only
```
-**[Query Operators Reference](docs/QUERY_OPERATORS.md)** — all operators with indexed/in-memory matrix
+### A typed graph, not a bag of edges
-### Graph Relationships
-
-Typed, directed edges between entities. Traverse connections at any depth.
+42 entity types × 127 relationship types form a shared vocabulary for any domain — healthcare (`Patient → diagnoses → Condition`), finance (`Account → transfers → Transaction`), yours. Your own taxonomy layers on with `subtype`, enforced at write time:
```javascript
-await brain.relate({ from: personId, to: projectId, type: VerbType.WorksOn })
+await brain.add({ data: 'Avery Brooks', type: NounType.Person, subtype: 'employee' })
-const results = await brain.find({
- connected: { from: personId, via: VerbType.WorksOn, depth: 3 }
-})
+brain.counts.bySubtype(NounType.Person) // O(1) — { employee: 12, customer: 847 }
+brain.requireSubtype(NounType.Person, { values: ['employee', 'customer'], required: true })
```
-### Database as a Value
+**[Type system](docs/architecture/noun-verb-taxonomy.md)** · **[Subtypes & facets](docs/guides/subtypes-and-facets.md)**
-The whole database, pinned as an immutable value. Snapshot isolation, time travel, atomic transactions, instant hard-link snapshots.
+### Graph analytics built in
```javascript
-const db = brain.now() // Pin current state — O(1)
-
-// Atomic multi-write transaction (all-or-nothing, with CAS)
-await brain.transact([
- { op: 'update', id: orderId, metadata: { status: 'paid' } },
- { op: 'relate', from: invoiceId, to: orderId, type: VerbType.References, subtype: 'billing' }
-], { meta: { author: 'billing-service' }, ifAtGeneration: db.generation })
-
-await db.get(orderId) // Still 'pending' — pinned, forever
-await brain.get(orderId) // 'paid' — live
-
-// Time travel: full query surface at any past state
-const yesterday = await brain.asOf(new Date(Date.now() - 86_400_000))
-const past = await yesterday.find({ query: 'unpaid orders' })
-
-// What-if: speculative writes, nothing touches disk
-const whatIf = await db.with([{ op: 'remove', id: orderId }])
-
-// Instant backup: hard-link snapshot, opens read-only with Brainy.load()
-await brain.now().persist('/backups/today')
+await brain.graph.rank() // which entities matter most (centrality)
+await brain.graph.communities() // natural clusters
+await brain.graph.path(a, b) // how two things connect
+await brain.graph.subgraph([seed], { depth: 2 }) // bounded neighborhood → { nodes, edges }
+await brain.graph.export() // whole graph, one O(N+E) streaming pass
```
-**[Consistency Model](docs/concepts/consistency-model.md)** | **[Snapshots & Time Travel](docs/guides/snapshots-and-time-travel.md)**
+### Write-time aggregations
-### Virtual Filesystem
+`SUM` / `COUNT` / `AVG` / `MIN` / `MAX` with `GROUP BY` and time windows, maintained incrementally on every write — reads are O(1) lookups, not scans. **[Aggregation guide](docs/guides/aggregation.md)**
-File operations with semantic search built in.
-
-```javascript
-const vfs = brain.vfs
-
-await vfs.writeFile('/docs/readme.md', 'Project documentation')
-const content = await vfs.readFile('/docs/readme.md')
-const tree = await vfs.getTreeStructure('/docs', { maxDepth: 3 })
-
-// Semantic file search
-const matches = await vfs.search('React components with hooks')
-```
-
-**[VFS Quick Start](docs/vfs/QUICK_START.md)** | **[Common Patterns](docs/vfs/COMMON_PATTERNS.md)**
-
-### Import Anything
-
-CSV, Excel, PDF, URLs — auto-detected format, auto-classified entities.
+### Import anything
```javascript
await brain.import('customers.csv')
-await brain.import('sales-data.xlsx') // all sheets processed
-await brain.import('research-paper.pdf') // tables extracted automatically
+await brain.import('sales.xlsx') // every sheet
+await brain.import('research-paper.pdf') // tables extracted
await brain.import('https://api.example.com/data.json')
-await brain.import('./handbook.md', { vfsPath: '/imports/handbook' }) // preserve in VFS
```
-**[Import Guide](docs/guides/import-anything.md)**
+Entities auto-classify on the way in; `brain.extractEntities(text)` exposes the same NER ensemble directly. **[Import guide](docs/guides/import-anything.md)**
-### Entity Extraction
-
-AI-powered named entity recognition with 4-signal ensemble scoring.
+### A filesystem that understands content
```javascript
-const entities = await brain.extractEntities('John Smith founded Acme Corp in New York')
-// [
-// { text: 'John Smith', type: NounType.Person, confidence: 0.95 },
-// { text: 'Acme Corp', type: NounType.Organization, confidence: 0.92 },
-// { text: 'New York', type: NounType.Location, confidence: 0.88 }
-// ]
+await brain.vfs.writeFile('/docs/readme.md', 'Project documentation')
+await brain.vfs.search('React components with hooks') // semantic file search
```
-**[Neural Extraction Guide](docs/neural-extraction.md)**
+**[VFS quick start](docs/vfs/QUICK_START.md)**
-### Plugin System
+### Operations-grade by default
-Optional native acceleration via `@soulcraft/cortex` — SIMD distance calculations, CRoaring bitmaps, Candle ML embeddings.
+- **Single-writer, many-reader** — an exclusive lock protects the data directory; `Brainy.openReadOnly()` and the `brainy inspect` CLI examine a live brain from another process, safely.
+- **Self-upgrading data files** — a 7.x brain opens under 8.x and migrates itself behind an observable lock (`getIndexStatus().migration`), with an automatic pre-upgrade backup. No migration scripts.
+- **No silent wrong answers** — cold-open guards self-heal or throw typed errors (`MetadataIndexNotReadyError`, `GraphIndexNotReadyError`); they never return `[]` for data that exists.
+
+**[Multi-process model](docs/concepts/multi-process.md)** · **[Inspection guide](docs/guides/inspection.md)**
+
+## From laptop to hundreds of millions
+
+Brainy's TypeScript engines take you a long way. When you outgrow them, add the native engine — **the API doesn't change**:
+
+```bash
+npm install @soulcraft/cor
+```
```javascript
-const brain = new Brainy({ plugins: ['@soulcraft/cortex'] })
-await brain.init()
+const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
+await brain.init() // @soulcraft/cor detected — same code, native engines underneath
```
-Plugins are opt-in. Brainy never auto-imports packages unless listed in `plugins`.
+Installing the package is the opt-in: if `@soulcraft/cor` is present, it loads and announces itself in the init log; if it's present but broken, `init()` **throws** — an installed accelerator never silently vanishes behind the JS engines. Opt out with `plugins: []`, or pin exactly what loads with `plugins: ['@soulcraft/cor']`. [`@soulcraft/cor`](https://www.npmjs.com/package/@soulcraft/cor) (Brainy 8.x ↔ Cor 3.x, version-matched) registers Rust implementations behind every provider seam: SIMD distance kernels, memory-mapped storage, a disk-native vector index that doesn't need your dataset in RAM, durable LSM field/graph indexes that serve cold opens instantly, and native aggregation. Recall@10 measured **0.99 / 0.96 / 0.96 at 1M / 10M / 100M vectors** in Cor's release gate.
-**[Plugin Documentation](docs/PLUGINS.md)**
+Open core, commercial accelerator: Brainy is MIT and complete on its own; Cor is licensed and funds both.
----
+## Performance
-## Type System
+- JS distance kernels: **~6× faster cosine, ~1.4× euclidean** than 7.x (measured: [`tests/benchmarks/distance-microbench.mjs`](tests/benchmarks/distance-microbench.mjs), 384-dim, median of 41).
+- Whole-graph reads are single **O(N + E)** cursor walks — a consumer-measured 19k-edge export dropped from ~27 s of per-node calls to one scan.
+- Full numbers and capacity planning: **[docs/PERFORMANCE.md](docs/PERFORMANCE.md)** · **[docs/SCALING.md](docs/SCALING.md)**
-42 noun types and 127 verb types form a universal knowledge protocol:
+## Use cases
-```
-42 Nouns × 127 Verbs = 5,334 base relationship combinations
-```
-
-Model any domain — healthcare (`Patient → diagnoses → Condition`), finance (`Account → transfers → Transaction`), education (`Student → completes → Course`), or your own.
-
-### Subtypes — sub-classification within a NounType *or* VerbType
-
-Both noun types and verb types are intentionally coarse. Use the top-level `subtype` field to sub-classify entities AND relationships within a type — flat string, no hierarchy, your choice of vocabulary:
-
-```javascript
-// Nouns: sub-classify entities
-await brain.add({
- data: 'Avery Brooks — runs the AI lab',
- type: NounType.Person,
- subtype: 'employee' // 'customer', 'vendor', 'contractor', …
-})
-
-// Verbs: sub-classify relationships
-await brain.relate({
- from: ceoId,
- to: vpId,
- type: VerbType.ReportsTo,
- subtype: 'direct' // 'dotted-line', 'matrix', …
-})
-
-// Filter on the fast path — column-store hit, not metadata fallback:
-const employees = await brain.find({ type: NounType.Person, subtype: 'employee' })
-const directReports = await brain.related({ from: ceoId, subtype: 'direct' })
-
-// O(1) counts via the persisted rollups:
-brain.counts.bySubtype(NounType.Person)
-// → { employee: 12, customer: 847, vendor: 34 }
-
-brain.counts.byRelationshipSubtype(VerbType.ReportsTo)
-// → { direct: 12, 'dotted-line': 3 }
-```
-
-**Enforce the pairing.** Register a vocabulary per type or turn on brain-wide strict mode to ensure every entity AND relationship has both `type` AND `subtype`:
-
-```javascript
-// Per-type rule with a closed vocabulary
-brain.requireSubtype(NounType.Person, { values: ['employee', 'customer'], required: true })
-
-// 8.0 default: every write requires a subtype. Exempt genuine catch-all types…
-const brain = new Brainy({ requireSubtype: { except: [NounType.Thing] } })
-
-// …or opt out while migrating pre-8.0 data, then audit and back-fill:
-const legacy = new Brainy({ requireSubtype: false })
-await legacy.audit() // gaps, grouped by type
-await legacy.fillSubtypes({ [NounType.Person]: 'unspecified' }) // close them
-```
-
-For other facets you want counted (`status`, `source`, `role`), register them with `brain.trackField(name)`. Renaming an existing convention to `subtype`? Use `brain.migrateField({from, to, entityKind: 'both'})` to walk nouns AND verbs in one pass. Full guide: **[Subtypes & Facets](docs/guides/subtypes-and-facets.md)**.
-
-**[Noun-Verb Taxonomy](docs/architecture/noun-verb-taxonomy.md)** | **[Stage 3 Canonical Reference](docs/STAGE3-CANONICAL-TAXONOMY.md)**
-
----
-
-## Storage: Memory and Filesystem
-
-The same API at every scale. Change one config line to go from prototype to production.
-
-### Development — Zero Config
-
-```javascript
-const brain = new Brainy()
-```
-
-### Production — Filesystem (gzip compression on by default)
-
-```javascript
-const brain = new Brainy({
- storage: { type: 'filesystem', path: './data' }
-})
-```
-
-### Backups and Portability — Snapshots
-
-```javascript
-const db = brain.now()
-await db.persist('/backups/2026-06-11') // instant hard-link snapshot
-await db.release()
-
-const snapshot = await Brainy.load('/backups/2026-06-11') // read-only Db
-const hits = await snapshot.find({ query: 'quarterly invoices' })
-await snapshot.release()
-```
-
-A snapshot directory is self-contained — copy it to another machine, open it with `Brainy.load()`, or restore it wholesale with `brain.restore(path, { confirm: true })`.
-
-Performance benchmarks and capacity planning in **[docs/PERFORMANCE.md](docs/PERFORMANCE.md)**.
-
-**[Capacity Planning](docs/operations/capacity-planning.md)**
-
----
-
-## Use Cases
-
-- **AI agents** — Persistent memory with semantic recall and relationship tracking
-- **Knowledge bases** — Auto-linking, semantic search, relationship-aware navigation
-- **Semantic search** — Find by meaning across codebases, documents, or media
-- **Enterprise knowledge** — CRM, product catalogs, institutional memory
-- **Interactive experiences** — Game worlds, NPCs, and characters that remember
-- **Content platforms** — Similarity-based discovery, intelligent tagging
-
----
+**AI agent memory** — persistent semantic recall with relationship tracking · **Knowledge bases** — auto-linking and meaning-aware navigation · **Semantic search** over codebases, documents, media · **Enterprise data** — CRM, catalogs, institutional memory · **Games & simulations** — worlds and characters that remember.
## Documentation
-### Start Here
-
-- **[Brainy explained simply](docs/eli5.md)** — Plain-language overview, no jargon, no code
-
-### Core
-
-- **[API Reference](docs/api/README.md)** — Every method with parameters, returns, and examples
-- **[Data Model](docs/DATA_MODEL.md)** — Entity structure, data vs metadata
-- **[Query Operators](docs/QUERY_OPERATORS.md)** — All BFO operators with examples
-- **[Find System](docs/FIND_SYSTEM.md)** — Natural language find() and hybrid search
-
-### Architecture
-
-- **[Architecture Overview](docs/architecture/overview.md)** — System design and components
-- **[Triple Intelligence](docs/architecture/triple-intelligence.md)** — Vector + graph + metadata unified query
-- **[Noun-Verb Taxonomy](docs/architecture/noun-verb-taxonomy.md)** — Universal type system
-- **[Data Storage Architecture](docs/architecture/data-storage-architecture.md)** — Type-aware indexing and HNSW
-
-### Virtual Filesystem
-
-- **[VFS Quick Start](docs/vfs/QUICK_START.md)** — Build file explorers that never crash
-- **[VFS Core](docs/vfs/VFS_CORE.md)** — Full VFS API reference
-- **[Semantic VFS](docs/vfs/SEMANTIC_VFS.md)** — AI-powered file navigation
-
-### Guides
-
-- **[Import Anything](docs/guides/import-anything.md)** — CSV, Excel, PDF, URLs
-- **[Framework Integration](docs/guides/framework-integration.md)** — React, Vue, Angular, Svelte
-- **[Natural Language Queries](docs/guides/natural-language.md)** — Master the find() method
-
-### Operations
-
-- **[Capacity Planning](docs/operations/capacity-planning.md)** — Memory, storage, and scaling
-- **[Performance](docs/PERFORMANCE.md)** — Benchmarks and architecture details
-
----
+| Start | Core | Going deeper |
+|---|---|---|
+| [Brainy explained simply](docs/eli5.md) | [API reference](docs/api/README.md) | [Architecture overview](docs/architecture/overview.md) |
+| [Installation](docs/guides/installation.md) | [Data model](docs/DATA_MODEL.md) | [Consistency model](docs/concepts/consistency-model.md) |
+| [Natural-language queries](docs/guides/natural-language.md) | [Query operators](docs/QUERY_OPERATORS.md) | [Multi-process model](docs/concepts/multi-process.md) |
+| | [Find system](docs/FIND_SYSTEM.md) | [Scaling](docs/SCALING.md) |
## Requirements
-**Bun 1.0+** (recommended) or **Node.js 22 LTS**
+**Bun ≥ 1.1** (recommended) or **Node.js ≥ 22**. Brainy 8.x is server-only; the 7.x line remains on npm for browser use.
-```bash
-bun install @soulcraft/brainy # Bun — best performance
-npm install @soulcraft/brainy # Node.js — fully supported
-```
+## Contributing & license
-> Brainy 8.0 is server-only. Browser support (OPFS storage, Web Workers, in-browser WASM embeddings) was removed in 8.0 — the 7.x line remains available on npm if you need it.
-
-## Single-Writer Model
-
-Brainy is **single-writer, many-reader** on filesystem storage. One writer
-holds an exclusive lock on the data directory; any number of readers can
-inspect it concurrently. Opening a second writer throws with the PID of the
-existing one.
-
-```typescript
-// Live application — writer mode is the default
-const brain = new Brainy({ storage: { type: 'filesystem', path: '/data/brain' } })
-await brain.init()
-
-// Out-of-band diagnostics from a separate process — safe to run while the
-// writer is live
-const reader = await Brainy.openReadOnly({
- storage: { type: 'filesystem', path: '/data/brain' }
-})
-await reader.requestFlush({ timeoutMs: 5000 })
-const stats = await reader.stats()
-```
-
-For incident debugging, use the `brainy inspect` CLI:
-
-```bash
-brainy inspect stats /data/brain
-brainy inspect find /data/brain --where '{"entityType":"booking"}'
-brainy inspect explain /data/brain --where '{"entityType":"booking"}'
-brainy inspect health /data/brain
-```
-
-See [the multi-process model](docs/concepts/multi-process.md) and the
-[inspection guide](docs/guides/inspection.md) for the full story, including
-stale-lock detection and the cross-process flush RPC.
-
-## Contributing
-
-We welcome contributions! See **[CONTRIBUTING.md](CONTRIBUTING.md)** for guidelines.
-
-## License
-
-MIT © Brainy Contributors
+Contributions welcome — see **[CONTRIBUTING.md](CONTRIBUTING.md)**. MIT © Brainy Contributors.
diff --git a/RELEASES.md b/RELEASES.md
index 2c453af7..7799c6f6 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -8,16 +8,1101 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
- Debugging data, query, or storage behaviour
- A new Brainy feature is available that you want to adopt
+## Removed APIs — 7.x → 8.x (the complete ledger)
+
+Every public API removed at the 8.0 major, with its sanctioned replacement. If your code
+still calls a left-column name on 8.x it throws (or the config key is rejected) — the
+replacement is always a one-line change. (Standing contract from 8.9.0 forward: removals
+happen only at majors, after ≥1 minor of loud runtime deprecation naming the replacement.)
+
+| Removed (7.x) | Replacement (8.x) |
+|---|---|
+| `brain.search(query, k)` | `find({ query })` — semantic; `find({ query, searchMode })` for hybrid |
+| `brain.getRelations({...})` | `related(id, opts)` for adjacency; `find({ connected: {...} })` for scoped traversal |
+| `brain.neural()` clustering | `find({ vector })` + aggregation `GROUP BY` |
+| `Db.search()` | `db.find({ vector })` |
+| Pre-8.0 storage path aliases (`directory`, `basePath`, …) | one `storage.path` key (old aliases throw) |
+| Reserved keys inside `metadata` bags (silently remapped in 7.x) | top-level params (`subtype`, `visibility`, `confidence`, `weight`, …) — reserved-in-bag throws |
+| 7.x COW branches layout (`branches/main/`) | generational MVCC (`asOf()`, `now()`, `db.persist(path)`) — on-disk migration is automatic at first 8.x open |
+
+The fork/snapshot family (`brain.snapshot()`, `createSnapshot()`, `restoreSnapshot()`)
+is sometimes cited as a 7.x removal — those methods never existed on 7.x; the 8.0 Db API
+(`asOf`/`persist`/`restore({confirm})`) is their first real implementation.
+
---
-## v8.0.0 — release candidate (`8.0.0-rc.2` published 2026-06-21 on npm tag `rc`)
+## v8.9.0 — 2026-07-19 (flush is durability-only: history maintenance moves to close())
+
+The write path stops paying maintenance costs — the last structural piece of the
+flush-storm class (a production deployment measured single writes blocked 25–191s behind
+history reclaim running inline on flush under memory pressure):
+
+- **`flush()` never compacts history.** It persists the current window's deltas and
+ nothing else — its cost no longer depends on history backlog or retention mode, in any
+ configuration. **`close()` is the auto-compaction site** (time-bounded per pass, ~5s;
+ an early stop is a consistent prefix and the next pass resumes).
+- **`compactHistory()` gains `timeBudgetMs`** — bound your own maintenance windows; the
+ same resumable-prefix guarantee applies.
+- **The documented trade**: a long-lived writer that never closes accumulates history
+ until its next explicit `compactHistory()`. Predictable writes, explicit maintenance.
+ If you run bounded retention on an always-on service, schedule a periodic
+ `compactHistory({ ...caps, timeBudgetMs })` in your maintenance window.
+- **New public doc: `docs/performance-envelopes.md`** — measured per-op envelopes
+ (p50/p95 at stated scales, hardware, and backend, with the measuring script cited).
+ Refresh rule going forward: any release touching a measured path re-runs that op's
+ benchmark and updates the envelope in the same release.
+- **New in this file: the Removed APIs 7.x→8.x table** (top of this document) — every
+ removal with its sanctioned replacement, one place, per the engine-currency contract.
+ Standing from here: removals only at majors, after ≥1 minor of loud runtime deprecation.
+
+## v8.8.2 — 2026-07-19 (one field-resolution law: reserved-field aggregates stop drifting)
+
+Four fixes from a consumer conformance audit, all rooted in the same disease — two field-resolution
+regimes where there must be one:
+
+- **Aggregates grouped by a RESERVED field (`subtype`, `visibility`, …) now decrement on
+ delete.** The delete/update hooks fed the aggregation engine a partial entity view (type,
+ service, data, metadata only), so a reserved-field `groupBy` resolved to a nonexistent group
+ on the way DOWN — counts drifted upward forever after any delete, and updates that moved an
+ entity between reserved-field groups double-counted it. The hooks now pass the full-fidelity
+ entity view (every reserved field top-level, the same shape the add path uses). If your
+ deployment derives stats from reserved-field aggregates, re-define those aggregates once
+ after upgrading (a changed definition triggers one rescan) or run them fresh — the drifted
+ persisted counts do not self-heal retroactively.
+- **Aggregation `source.where` on reserved fields now filters** instead of silently matching
+ nothing: the matcher resolves fields through the same resolver `groupBy` uses (top-level
+ standard fields + custom metadata), so `where: { subtype: 'note' }` means what it says.
+- **`removeMany()` refuses empty/invalid selectors loudly.** A bare array passed positionally
+ (`removeMany([id])` instead of `removeMany({ ids: [id] })`), an empty params object, or
+ `ids: []` used to resolve successfully having deleted nothing. All three now throw.
+- **`find()` accepts both where-key spellings.** Metadata is flattened at index time
+ (`metadata.entry.title` indexes as `entry.title`); a `metadata.`-prefixed where key now
+ falls back to its flattened spelling when the prefixed one isn't indexed — the
+ "unindexed field(s), returning []" confusion for storage-shaped spellings is gone. (A
+ literal nested custom key named `metadata` still wins when indexed as spelled.)
+
+## v8.8.1 — 2026-07-18 (flush no longer walks the whole generation history + the import dedup off-switch is now honest)
+
+### The flush-storm fix (production incident, reported by a long-running deployment)
+
+Under the default adaptive retention, **every `flush()` re-walked the entire committed
+generation history** to compute total history bytes for the budget check — O(all
+generations) with disk re-reads past the 4,096-entry delta-cache bound. On a brain with
+70,000+ accumulated generations that turned every write into a full-tail scan (60-100s
+writes), even though the budget (free-RAM-based) never tripped and nothing was ever
+reclaimed. Fixed:
+
+- `historyBytes()` now maintains a **running total**: seeded by one walk on first use,
+ then updated incrementally at every commit and reclaim — the adaptive retention check
+ on every flush is O(1). Invariant regression-pinned (running total ≡ fresh walk through
+ both commit paths and compaction).
+- New **`brain.historyStats()`** (read-only, exported `HistoryStats`): generation count,
+ total on-disk bytes, generation/timestamp range, compaction horizon, retention mode,
+ and the effective adaptive budget — the one-call fleet-audit for sizing retention
+ exposure per brain.
+- Interim guidance for keep-everything deployments already affected: `retention: 'all'`
+ skips the adaptive accounting entirely (and is the correct policy if you never want
+ history reclaimed). The accumulated files are harmless at rest; this release removes
+ the per-write cost of their existence.
+
+### The import dedup off-switch (lifecycle honesty)
+
+The post-import background deduplication pass (a merge-DELETE writer that runs ~5 minutes
+after an import, merging entities judged duplicates by id / name / vector similarity) had
+three lifecycle defects, all fixed:
+
+- **`enableDeduplication: false` now actually disables it.** The background pass was
+ scheduled unconditionally — an import that explicitly opted out could still have
+ entities auto-removed 5 minutes later. The flag now gates BOTH the inline merge and
+ the background pass (regression-pinned).
+- **One deduplicator per brain, owned by the brain.** Each `import()` call constructed its
+ own coordinator + deduplicator, so the "debounced" timer never actually debounced across
+ imports (N imports = N delete timers). The brain now owns a single instance — the
+ debounce genuinely spans imports — and `close()` cancels pending work, so a delete pass
+ can never fire against a closed brain.
+- **The 5-minute timer is unref'd** — a pending pass no longer holds the process open
+ (the exit-hang class; this timer had escaped the earlier sweep).
+
+Retention note for keep-everything deployments: with `enableDeduplication: false` on
+import calls and `retention: 'all'` in config, no engine path removes records
+automatically.
+
+## v8.8.0 — 2026-07-17 (OS-limit detection for pool-scale deployments)
+
+Small minor: brains now detect the two OS limits that bite at pool scale and warn **before**
+the incident instead of during it.
+
+- At open (once per process, Linux-only, measurement-only), Brainy reads `RLIMIT_NOFILE`
+ (soft/hard, from `/proc/self/limits`) and `vm.max_map_count`, and warns loudly when either
+ sits below the pool-scale floors (soft NOFILE < 65 536; max_map_count < 262 144) — with the
+ exact raise commands (`ulimit -n` / `LimitNOFILE=` / `sysctl vm.max_map_count`). On stock
+ defaults the failure otherwise arrives as `EMFILE` or a failed mmap deep inside an index
+ open, long after the real cause stopped being visible. An unreadable limit produces **no**
+ warning — no measurement, no claim (non-Linux platforms stay silent).
+- Exported for ops doors: `checkOsLimits()` returns the full `OsLimitsReport`
+ (values + warnings) programmatically, with the floors exported as constants.
+
+## v8.7.1 — 2026-07-17 (writer-lock acquisition is race-proof + machine-readable through init)
+
+Two hardenings of the multi-process writer lock (the `locks/_writer.lock` lease that makes a
+second writer on the same brain directory fail loudly):
+
+- **Lock acquisition claims atomically.** The acquire path used read-then-write, leaving a
+ window where two processes racing an *absent* lock could both "succeed" — and the loser
+ kept running unlocked, silently. The claim is now an atomic create-exclusive write
+ (`O_EXCL`): exactly one racer wins; the loser re-evaluates and either fails loudly with
+ the winner's details or performs a verified stale-takeover. Bounded retries; contention
+ beyond them fails loudly rather than degrading into a lockless open.
+- **`BRAINY_WRITER_LOCKED` survives `init()`.** The conflict error documents a
+ machine-readable contract (`err.code`, `err.lockInfo` with the holder's pid/host/
+ heartbeat), but init's error wrapping silently stripped both, leaving consumers a message
+ to regex against. The error now passes through unwrapped.
+
+Measured while verifying (for operators sizing audits): `brain.auditGraph()` at a
+production-consumer scale of ~2,600 relationships / 800 entities costs ~0.1 s warm and
+~0.5 s cold, with exact scar counting across reopen.
+
+## v8.7.0 — 2026-07-17 (bulk-transact ergonomics: scaled budgets + timeout telemetry)
+
+The bulk-import ergonomics release, from a consumer's measured production incident (a serial
+import on network-attached storage at ~2 s/op met a flat 30 s transaction budget):
+
+- **The transact apply budget now scales with the batch** — `max(30 s, opCount × 2 s)` — or
+ is exactly what you pass as the new `TransactOptions.timeoutMs`. A flat 30 s cap silently
+ limited honest bulk work to ~15 operations on slow disks while looking generous for small
+ batches. Internal batch paths (e.g. `removeMany` chunks) get the same scaling.
+- **`TransactionTimeoutError` is a diagnosis, not just a failure**: it now reports the
+ operation it stopped at as `i/N` with the operation's name, elapsed vs budgeted time, and
+ states the batch rolled back atomically and is retryable. Its `context` carries the same
+ fields programmatically.
+- **The transact envelope is documented** — batch sizing, budget math, chunking with
+ `ifAbsent` idempotency, and the precompute pattern (`embedBatch` + per-op `vector`) that
+ keeps model inference out of the commit path. Guide: `docs/guides/optimistic-concurrency.md`.
+- Note: `brain.embed()` / `brain.embedBatch()` (the precompute APIs) already ship — public,
+ with native-provider passthrough, verified end-to-end (batch and single paths produce
+ bit-identical vectors; a vector-supplied `add` is fully searchable). Honest measurement:
+ on the default WASM engine, batch throughput ≈ sequential (~160 ms/text) — the precompute
+ win is keeping inference out of the budgeted commit path, not raw embedding speed.
+
+## v8.6.0 — 2026-07-17 (brain.auditGraph — the graph-truth verification instrument)
+
+A minor release adding one new public API, from the fleet's graph-trust program: a read-only
+audit that **proves whether relationship reads return stored truth** on a given brain.
+
+- **`brain.auditGraph(options?)`** walks every canonical relationship record, queries the same
+ read path applications use (`related()` / VFS `readdir`) with all visibility tiers included,
+ and classifies every discrepancy into its failure family: `missingFromReads` (records the
+ read path omits — a stale adjacency index), `danglingEndpoints` (relationships whose endpoint
+ entity no longer exists — the historical partial-delete scar class), and `readOnlyVerbIds`
+ (read-path edges with no stored record — ghosts). Design-hidden internal/system edges are
+ counted separately so intentional hiding is never misclassified as loss. Counts are exact;
+ example lists cap at `maxExamples` with an explicit `truncatedExamples` flag; the result is
+ narrated loudly on incoherence. Mutates nothing — safe on a live brain.
+- The operational pairing: audit → if incoherent, `repairIndex()` → audit again. A `coherent`
+ report after the repair is the verified all-clear. Run it after any engine upgrade, restore,
+ or migration. Guide: `docs/guides/inspection.md`.
+- Also: `getNounIds` pagination now refuses an undecodable resume cursor loudly (the same
+ contract `getNouns`/`getVerbs` gained in 8.5.2 — the third and final walk brought under it).
+- Types exported: `GraphAuditReport`, `GraphAuditDiscrepancy`.
+
+## v8.5.2 — 2026-07-17 (aggregation backfill: exception-safe, generation-verified, and loud)
+
+Hardening patch from a migration incident (a byte-copied store on new hardware; the service
+entered a silent full-CPU loop at boot). Four changes, all in the aggregation engine's
+backfill/adoption path:
+
+- **Backfill walks are exception-safe and non-destructive.** A rescan now builds into a
+ staging map and swaps in atomically on completion; a mid-walk failure drops the staging map,
+ keeps the previous live state serving, and surfaces the storage error to the failing query.
+ Previously the walk wiped live state *before* a scan that could throw, never cleared the
+ pending flag on failure, and re-ran a full walk on every subsequent query — a silent
+ wipe/walk/throw loop at the caller's retry rate.
+- **Failed walks are latched.** After a walk fails, retries within a 30-second cooldown rethrow
+ the recorded error instantly instead of re-walking — a tight caller-side retry loop now costs
+ one loud error per query, never a full store walk per query.
+- **Adoption is generation-verified.** Persisted aggregation state is stamped with the store's
+ committed generation at flush; reopen adoption requires the stamp to equal the current
+ watermark. Stale state (unclean shutdown) or over-counting state (a fact-log truncation on a
+ copied store pulled the watermark back) triggers exactly one loud rescan — never a silent
+ adopt. Pre-8.5.2 state on generation-aware stores rescans once after upgrade, then is stamped.
+- **The path narrates.** Adoption decisions, no-adoptable-state outcomes, walk start/finish
+ (entity count + duration), and walk failures all log by default; a non-advancing storage
+ pagination cursor aborts the walk loudly instead of looping forever.
+
+Plus three guards from a full audit of every loop in the open/init path:
+
+- **Invalid pagination cursors fail loudly.** A supplied-but-undecodable resume token to
+ `getNouns`/`getVerbs` used to silently restart the walk at offset 0 — to a `while(hasMore)`
+ caller that re-serves page 1 forever (an unbounded silent CPU loop). It now throws with a
+ clear message instead.
+- **The graph cold-load verb walk has a stall guard.** `hasMore=true` with a missing or
+ non-advancing cursor aborts loudly instead of re-reading the same page forever.
+- **A derived index AHEAD of the store is named at open.** Brainy already surfaced a provider
+ generation *behind* the committed watermark; the *ahead* direction (the signature of a
+ byte-copy of a live service, or a log truncation during crash recovery) now logs a loud
+ warning explaining what happened and that `brain.repairIndex()` forces a heal — instead of
+ passing unnamed into whatever the derived index does next.
+
+## v8.5.1 — 2026-07-17 (aggregation state survives restarts + the query-cap ratchet removed)
+
+Patch release from a production incident (aggregate/count paths taking 40–90 s on an idle box
+while vector search stayed fast, and every `find({ limit: 5000 })` suddenly failing against an
+"auto-configured query limit of 1000"). Three fixes, one cosmetic:
+
+- **Aggregation state is actually adopted on reopen.** The boot pattern `defineAggregate()` →
+ query raced the engine's async state load: the synchronous define always won, flagged a
+ backfill, and the first query then wiped the just-loaded persisted state and re-walked the
+ entire store — every restart, forever. Reopening with an unchanged definition now adopts the
+ persisted state directly (zero scans); a backfill runs only on a real definition change, a
+ missing/failed state load, or a write that landed before adoption (exactness wins). Apps that
+ rely on persisted definitions without re-defining at boot also no longer race a spurious
+ "Aggregate not defined".
+- **Backfills are single-flight and batched.** Concurrent queries on a cold aggregate used to
+ each wipe the others' partial state and start their own full store walk — under steady query
+ arrival the store never converged (the 40–90 s loop). Now all concurrent queries share one
+ walk, and one walk fills every aggregate pending backfill (M aggregates ≠ M scans).
+- **The query-cap "learning" ratchet is removed.** `maxLimit` is a memory-protection bound, but
+ a hidden tuner shrank it 20 % per recorded query while the lifetime-average query time
+ exceeded 1 s — down to a floor of 1000, below the documented 10 000 auto floor, with no
+ practical recovery, and the resulting error blamed "available free memory" (stale basis
+ label). The cap now comes from its construction-time basis (or your explicit
+ `maxQueryLimit` / `reservedQueryMemory`) alone and never changes at runtime; query timing is
+ recorded for diagnostics only.
+- **Cosmetic:** the engine's own persistence keys (`__aggregation_*`, `brainy:entityIdMapper`)
+ no longer log `[Storage] Unknown key format` at boot — they were always routed correctly;
+ they're now recognized before the warning fires.
+
+Operationally: if a host was bitten, upgrading and restarting is the whole fix — no repair
+ritual needed. Setting `maxQueryLimit` explicitly remains the valve that bypasses auto-detection
+entirely.
+
+## v8.5.0 — 2026-07-15 (provider access to the fact log + the shared stamp verifier)
+
+Small additive follow-up to 8.4.0, from the native accelerator's first consumption pass:
+
+- **Index providers can now reach the fact log through the storage adapter** — new optional
+ capability `storage.scanFacts()` / `storage.factLogHeadGeneration()` / `storage.factSegmentPaths()`,
+ wired by the host brain at init as a closure over its live log. Providers hold only `storage` and
+ must never construct their own fact-log reader (the log's open path is writer-side); `null` means
+ "no fact log here — use the enumeration walk."
+- **The family-stamp verifier is shared** via `@soulcraft/brainy/internals`
+ (`readFamilyStamp` / `writeFamilyStamp` / `verifyFamilyStamp` + types) so first-party native
+ providers run literally the same verification function, never a synchronized copy.
+- **Rollup stamp invariants accept strings** (`number | string`) — content fingerprints such as a
+ per-tree SHA-256 are valid invariant values; a type mismatch reads as incoherence, never a pass.
+- **`storage.committedGeneration()`** — the committed watermark as a capability, so a provider
+ compares its stamp's `sourceGeneration` against the store's truth without parsing the private
+ manifest format.
+- **Two durability/stability contracts pinned in the suite:** fsync-before-ack (holds for
+ `transact()` today; the single-op path is pinned as the documented future target — group commit
+ becomes latency batching, never durability skipping) and scan-stability-under-rotation (a scan
+ snapshot yields exactly its facts — no gaps, duplicates, or bleed-in — while segments rotate
+ beneath it).
+
+No behavior change for applications; all additions.
+
+## v8.4.0 — 2026-07-15 (the generation fact log — a sequential, self-verifying commit stream)
+
+A minor release, fully backward-compatible (all additions; no behavior change for existing APIs).
+This is infrastructure: it changes nothing about how you query today, and lays the substrate that
+makes index heals and incremental catch-up sequential-read problems instead of directory walks.
+
+- **Every committed write now also appends a "fact" — an after-image commit record.** Alongside the
+ existing before-image history, each committed generation (single-op and `transact()` alike) appends
+ what each touched entity/relationship *became* — or a body-less tombstone for a removal — to an
+ append-only, checksummed segment log under `_generations/facts/`. Crash-safe by construction: a
+ torn tail is detected and ignored; on open the log is reconciled to committed truth, so an absent
+ generation always means "never committed." `transact()` facts are durable when `transact()`
+ returns; single-op facts ride the same group-commit flush as their history.
+
+- **New: `brain.scanFacts()`** — stream committed facts in commit order, in batches, with heal-grade
+ telemetry (total scope up front; per-batch generation range, byte size, and segment; a summary
+ cross-check at the end; loud abort on any gap — never a silent skip). **`brain.factSegmentPaths()`**
+ hands zero-copy consumers the immutable sealed segment files directly. New exported types:
+ `CommitFact`, `FactOp`, `FactScanBatch`, `FactScanHandle`. Facts accumulate from the first write
+ after upgrading — pre-existing history is not retroactively converted (enumeration remains the
+ fallback for old data).
+
+- **New: the entity-tree family stamp.** At every flush/close, brainy stamps which committed
+ generation the canonical entity files reflect plus the rollup invariants (entity/relationship
+ counts) that verify the tree whole. At open, coherence is a comparison — a genuine divergence is
+ loud and names the failing invariant; `repairIndex()` recounts from canonical and re-stamps. New
+ exports: `readFamilyStamp`, `verifyFamilyStamp`, `ENTITY_TREE_STAMP_PATH`, `FamilyStamp` types.
+
+- **Storage adapters** gain optional binary raw-byte primitives (`appendRawBytes`, `readRawBytes`,
+ `writeRawBytes`, `rawByteSize`) — feature-detected; the filesystem and memory adapters implement
+ them; an adapter without them simply hosts no fact log. The fact-log namespace is registered as a
+ protected family: no sweeper or GC can delete under it.
+
+No API breaks. 24 new tests; the full commit-path regression suite is green.
+
+## v8.3.3 — 2026-07-15 (rename moves the containment edge — no ghost in the old directory)
+
+One production-reported fix plus a repair path, completing the delete/move hygiene arc (8.3.1 fixed
+deletes, 8.3.2 fixed counters, this fixes moves).
+
+- **A cross-directory `vfs.rename()` now MOVES the containment edge instead of accumulating one per
+ parent.** The old parent's `Contains` edge was never removed on a move, leaving the entity a child
+ of **both** directories: `readdir(oldDir)` kept listing it after the move, re-creating the old path
+ showed the same name twice, and any tree-walking consumer (sync engines, file browsers) saw the
+ file in two places. The old edge is now removed by edge id, resolved from the graph's own adjacency
+ — a removal never requires reading the thing being removed. Bonus fix in the same seam: a move **to
+ the root** now gets its containment edge (it was previously skipped, orphaning the file out of
+ `readdir('/')`).
+
+- **`repairIndex()` now also reconciles VFS containment** (new `vfs.repairContainment()`): every VFS
+ entity's containment edges are checked against its canonical `metadata.path` — stale old-parent
+ ghosts and duplicate edges are removed, a missing expected edge is restored, and user
+ knowledge-graph edges are never touched (only `vfs-contains` edges are candidates). Loud per
+ repair. Stores that performed cross-directory renames under ≤8.3.2 should run `brain.repairIndex()`
+ once after upgrading — the same single ritual now heals orphan directories, counters, **and**
+ containment edges.
+
+- Also ships a permanent lens-consistency regression suite (combined type+subtype vs subtype-only vs
+ canonical ground truth, id-for-id, warm and after a cold reopen), ported from the field
+ investigation that closed the historical lens-drop report.
+
+No API changes beyond the new optional `vfs.repairContainment()` (also invoked by `repairIndex()`).
+
+## v8.3.2 — 2026-07-14 (honest counters — the recount + removal-without-re-reading)
+
+Completes 8.3.1's delete-hygiene story at the counter layer, from a production proof chain reported
+by a downstream deployment: persisted entity totals were permanently **inflated** — deletes whose
+count decrement was silently skipped — and because paginated `totalCount` serves
+`Math.max(persistedTotal, scanned)`, the inflated number always won and **no disk cleanup could ever
+lower it**.
+
+- **A removal's count decrement no longer requires re-reading the record being removed.** The
+ decrement was sourced from re-reading the entity's metadata inside the delete; if that read
+ returned `null` (a replace race, or a ghost left by a pre-8.3.1 partial delete) the decrement was
+ silently skipped while the paired add had counted — minting drift on every write→delete→re-create
+ cycle. The caller's pre-delete read now rides through the whole delete path
+ (`remove()`/`removeMany()` → the delete operation → `deleteNoun`/`deleteVerb` →
+ `deleteNounMetadata`/`deleteVerbMetadata`, both sides symmetric): a null internal read falls back
+ to the known prior record instead of skipping. The `StorageAdapter` signatures gain an optional
+ `priorMetadata` parameter (additive; existing adapters unaffected).
+
+- **`repairIndex()` is the sanctioned counter recount — unconditional, and it actually persists.**
+ `rebuildTypeCounts()` previously rebuilt only the type-statistics arrays and computed the total
+ *just to log it* — the persisted scalar (`counts.json`) survived every "rebuild" untouched, so an
+ already-inflated brain could never be corrected. One canonical walk now rebuilds **every** counter
+ rollup — scalar totals, per-type maps, and type statistics — and persists them, and `repairIndex()`
+ runs it unconditionally (not only when orphan directories are found: counters can be inflated over
+ perfectly clean shelves). Brains with delete history should run `brain.repairIndex()` once after
+ upgrading; the correction survives reopen.
+
+No API breaks (optional-parameter additions only). Regression tests cover the drift cycle, the
+null-read decrement fallback, and the persisted recount across reopen.
+
+## v8.3.1 — 2026-07-14 (full-removal deletes + family-scoped migration gate)
+
+Two production-reported fixes in the write/index spine, plus an operator repair path. No API changes;
+all behavior changes make previously-wrong states honest.
+
+- **Deleting an entity now removes it completely — no more "ghost" leftovers on disk.** A canonical
+ noun delete removed the metadata (content) leg but left the entity's `vectors.json` and its `/`
+ directory behind. Consequences observed in a production deployment: deleted rows were
+ indistinguishable on disk from damage scars, enumerated counts inflated monotonically with every
+ delete (the leftovers were counted forever), and locator-style reads hit unreadable ghost rows.
+ `remove()`/`removeMany()`/`deleteNoun`/`deleteVerb` now remove **both legs and the entity
+ container**, with a full two-leg before-image rollback inside the transaction. The generation log
+ still holds the delete's before-image, so `asOf()` time-travel reconstructs deleted entities exactly
+ as before — this is live-HEAD hygiene, not a history change. This also fixes **duplicate `readdir`
+ entries for re-created VFS paths** at the root: with no ghost state, a delete always unposts its
+ index rows, so a delete→recreate cycle lists the path exactly once (regression-tested across
+ repeated cycles).
+
+- **`repairIndex()` prunes ghost/scar directories left by earlier versions.** Stores that deleted
+ entities under ≤8.3.0 may hold orphaned entity directories (a vector-only leg, or an empty dir).
+ `brain.repairIndex()` now sweeps them: it removes only containers with **no metadata content leg**
+ (never a directory that still holds content), logs every removal, and recomputes type/subtype
+ counts afterward so totals stop counting ghosts.
+
+- **Reads no longer hang behind an unrelated index migration (family-scoped gate).** During a native
+ provider's one-time background migration, *every* read — including plain `get()`, VFS
+ `readdir`/`readFile`, and metadata-only `find({ where })` — blocked on the whole-brain migration
+ lock until timeout, even when the migrating index was irrelevant to the read. The gate is now
+ scoped to the index families a read actually consults: canonical reads (`get`, `batchGet`, VFS
+ content) never wait; a `find` waits only on the families its query shape needs (vector for
+ semantic, metadata for `where`/type, graph for `connected`); graph traversals wait only on the
+ graph family. Writes and unclassified operations keep the conservative whole-brain wait. A read
+ that *does* need the migrating family still blocks (bounded by `migrationWaitTimeoutMs`) and
+ surfaces the retryable `MigrationInProgressError` — never a partial result.
+
+No breaking API change. Each fix ships with regression tests.
+
+## v8.3.0 — 2026-07-13 (faster index heals + the cross-layer integrity contract)
+
+Three additive changes. The first is an immediate, standalone performance win; the other two are the
+brainy side of the write/index-spine integrity contract, inert until a native accelerator
+that implements the matching hooks is present — so this release changes nothing for a JS-only brain
+beyond the speedup.
+
+- **Canonical enumeration is up to ~16× faster — the dominant term in an index heal.** The paginated
+ entity walk (`getNounsWithPagination`) hydrated each entity's vector + metadata one-at-a-time; since
+ every index rebuild enumerates canonical storage, that serial per-item latency dominated multi-minute
+ heals. Hydration is now 16-way bounded-concurrency, with the pagination contract (order, cursor
+ resume, filters, totalCount) byte-identical to before. New **`getNounIdsWithPagination()`** returns
+ ids without hydrating anything (zero per-entity reads when unfiltered) for callers that own their own
+ IO schedule.
+
+- **Cross-layer integrity — `validateIndexConsistency()` is no longer blind to native providers.** It
+ only ran the JS metadata index's own check, so a native provider whose manifest/segments/counts had
+ diverged still read as "healthy". It now feature-detects and aggregates each provider's optional
+ `validateInvariants()` self-report, names every failing invariant with its numbers, and exposes the
+ per-provider reports. `repairIndex()` now reconciles native derived state from canonical too
+ (rebuilding any provider whose failing invariant asks for it). New exported types
+ `ProviderInvariantReport` / `InvariantResult` / `InvariantHeal`.
+
+- **Registered-blob families — declared index files are undeletable through the storage layer.** A
+ provider can declare a derived-index blob *family* (a set of members that are load-bearing together);
+ once declared, `deleteBinaryBlob` / `removeRawPrefix` refuse to remove a member (new exported
+ `ProtectedArtifactError`), so a stray in-process sweeper cannot delete a load-bearing index file. The
+ declaration persists across reopen; `checkDerivedFamiliesPresent()` names any member missing on open.
+ New optional `StorageAdapter` surface (`registerDerivedFamily` / `unregisterDerivedFamily` /
+ `listDerivedFamilies` + `DerivedFamilyDeclaration`) and exported `DerivedArtifactMissingError`.
+
+No breaking API change (all additions are optional/new). Each change ships with regression tests.
+
+## v8.2.8 — 2026-07-13 (honest index readiness — no more silently-empty queries on a cold index)
+
+Closes the last of the three spine anti-patterns: the "dishonest readiness proxy," where `size() > 0`
+was treated as "this index actually serves queries." On a cold open (fresh boot, restart, crash
+recovery) a native index can load its **count** before its **serving structure** — so for a brief
+window it has data but cannot answer, and a query returned a silent empty result indistinguishable
+from "no such data." Every fix here asks the index whether it can *actually serve*, and if not,
+self-heals or fails loudly instead of returning `[]`.
+
+- **Semantic search no longer returns a silent `[]` on a cold vector index.** A pure semantic
+ `find({ query })` has no filter, so nothing previously guarded the vector index. A new one-shot
+ guard verifies the vector index serves a known persisted vector on the first semantic/proximity
+ search — preferring the provider's honest `isReady()` signal, else a known-vector self-match probe.
+ It rebuilds from canonical records if the serving structure did not load, and throws the new
+ **`VectorIndexNotReadyError`** only if a rebuild still cannot serve — never a silent empty result.
+
+- **Relationship reads fall back to the canonical scan instead of an empty result on a cold graph
+ index.** `getVerbsBySource`/`getVerbsByTarget` (used by relationship queries and virtual-filesystem
+ traversal) skipped the fast path only on `isInitialized` — which reads true once the manifest loaded
+ even if the source→target adjacency did not. They now consult the honest readiness signal and, when
+ the adjacency is not serving, take the correct-but-slower canonical shard scan. A one-shot self-heal
+ probe covers providers that expose no readiness signal.
+
+- **`getIndexStatus()` tells the truth for readiness probes.** It reported `populated: size>0` only, so
+ a Kubernetes readiness check could route traffic to a brain still warming up. It now folds in the
+ honest per-index `ready` signal (making `populated` honest), plus the degraded states already
+ surfaced by `checkHealth()`/`validateIndexConsistency()` (`rebuildFailed`/`rebuildError` and a
+ `degradedIds` count) — so a probe never reports 200-ready over a known-degraded index.
+
+This completes the write/index-spine hardening end to end. New export: **`VectorIndexNotReadyError`**;
+`getIndexStatus()` gains additive fields (`rebuildFailed`, `rebuildError?`, `degradedIds`, per-index
+`ready?`). No breaking API change. Each fix ships with a dedicated regression test.
+
+## v8.2.7 — 2026-07-13 (loadBinaryBlob fault-propagation — the lockstep completion of 8.2.6)
+
+Completes the Pass-1 spine hardening. `loadBinaryBlob` (the raw-blob read a native accelerator mmaps
+for its index files) previously returned `null` on ANY read error, so a real IO fault
+(EIO/EACCES/EMFILE) on a present-but-unreadable index blob masqueraded as "the blob is absent" —
+driving a needless full rebuild or an empty read. It now distinguishes genuine absence (ENOENT →
+`null`, the documented contract) from a real fault (→ throw), so a transient disk fault surfaces
+loudly instead of silently degrading the index.
+
+This one change was deliberately held out of 8.2.6 and ships now, in lockstep with the native
+accelerator release that hardened its two column-store read sites to handle the throw (a faulted
+segment read marks the field unavailable and throws a named error, instead of relying on
+null-on-error). A consumer on new brainy + an older accelerator was never at risk: 8.2.6 kept the
+prior swallow-on-fault behavior for this method until the accelerator was ready.
+
+No API change. Regression: `tests/unit/storage/blob-save-durability.test.ts` gains the loadBinaryBlob
+leg (absent → null; present → bytes; real fault → throws).
+
+## v8.2.6 — 2026-07-13 (write/index-spine hardening — loud errors, never quiet losses)
+
+Durability + integrity hardening across the write and index paths. Every fix converts a place that
+could *silently* lose data, serve a partial result, or acknowledge a write that did not land into a
+loud, observable failure. No breaking API changes; one new exported error.
+
+- **A durable blob write that stored nothing is no longer acknowledged.** The atomic blob write
+ (`saveBinaryBlob`, used for vector/graph index segments and the native index files) uses a unique
+ per-writer temp file, so a rename `ENOENT` can only mean *our* temp vanished before the rename —
+ the bytes never landed. It previously returned success on that path; it now retries once with a
+ fresh temp and, if the temp vanishes again, throws instead of acknowledging a write that persisted
+ nothing. A downstream deployment that mmaps these files no longer finds a "successfully written"
+ blob missing on the next open.
+
+- **Single-op history durability is enforced, not assumed.** The asynchronous group-commit flush
+ that persists single-op generation history previously swallowed a persist failure as a warning
+ while writes kept succeeding and their history piled up, undurable, in memory. It now tolerates a
+ transient blip (retry with capped backoff) and, after repeated failures, **refuses further writes**
+ with the new **`PendingFlushDurabilityError`** rather than promise a durability it cannot deliver.
+ Live canonical data is untouched; the latch self-heals the moment a flush succeeds. Callers needing
+ hard per-write durability should keep using `transact()` (or `flush()` after a single-op).
+
+- **A degraded derived index is surfaced on reads, not served silently.** When a non-fatal index
+ rebuild fails at open, or a write commits via adopt-forward recovery with an incomplete derived
+ index, `find()` and `get()` now emit one loud warning per degraded window (reads still return —
+ canonical is the source of truth), the state folds into `checkHealth()` /
+ `validateIndexConsistency()`, and `repairIndex()` reconciles and clears it.
+
+- **`clear()` removes the full derived footprint.** It previously left raw index blobs, the native
+ id-mapper, and column-index manifests on disk, so a cleared brain could re-read stale native state
+ on reopen. It now wipes them together as a set.
+
+- **Counts stay honest across deletes.** A delete decremented the per-type breakdown but not the
+ scalar total, so the total inflated permanently (and won pagination). Delete now decrements both;
+ the invariant `total === Σ per-type` holds across any interleaving of add / visibility-flip /
+ delete and across a reopen.
+
+- **Index-maintenance and aggregation failures are loud.** A partial LSM/segment load no longer
+ publishes the manifest's full count as if healthy; an HNSW flush that can't persist a node throws
+ (`HnswFlushError`) instead of returning a lying count and dropping the node; a corrupt or missing
+ manifest-listed column segment throws (`ColumnSegmentLoadError`) instead of silently dropping its
+ entities from every query; and aggregation materialization / state-load failures now warn instead
+ of vanishing into an empty catch.
+
+New export: **`PendingFlushDurabilityError`** (with `.cause` and `.failedAttempts`). No other public
+API change. Each fix ships with a dedicated regression test.
+
+## v8.2.5 — 2026-07-12 (honest response when a transaction rollback can't complete)
+
+Data-integrity fix. When a transaction failed and its rollback then *also* failed to undo a
+canonical write (retries exhausted), the old behavior logged, continued, and threw
+`TransactionRollbackError` — while the record it couldn't undo stayed durable on disk, and the
+transaction even reported its state as `'rolled_back'`. The caller got an error implying the write
+was undone; a read-back showed the record. A failed rollback had no truthful response.
+
+Rollback now tells the truth, with a two-branch contract:
+
+- **Adopt-forward (safe case).** A single-op write (`add`/`update`/…) whose only damage is a
+ durably-present record — the write the caller asked for — is **adopted**: its generation is
+ committed, the write returns success, and a loud warning records that the derived index may be
+ incomplete for that id until the next rebuild/`repairIndex()`. No error, no double-write; the
+ record is immediately durable and retrievable by `get()`.
+- **Fail loud + quarantine (unsafe case).** A multi-operation batch, or *any* case where a record
+ was lost (a remove/update whose restore-undo failed), throws the new **`StoreInconsistentError`**
+ naming every unreconciled record and its disposition (`orphan` vs `loss`), and puts the brain into
+ **write-quarantine**: reads keep working, but writes are refused until `repairIndex()` reconciles
+ the derived indexes against canonical storage and lifts the quarantine. The generation counter is
+ not advanced, and a transaction whose rollback failed is now `'inconsistent'`, never the
+ `'rolled_back'` lie.
+
+The decision is made by *observation*, not guesswork: both commit paths already hold byte-identical
+before-images, so after a failed rollback the store compares current canonical state to them to
+classify exactly which records are orphaned or lost.
+
+New export: `StoreInconsistentError` (with `.records` and `.cause`) and the `UnreconciledRecord` type.
+No other API change; `repairIndex()` gains the quarantine-lift behavior. Regression
+(`tests/integration/rollback-trapdoor.test.ts`) injects the exact failure (index add throws →
+canonical delete-undo fails) and pins adopt-forward (durable, get-able, not quarantined), fail-loud
+(`StoreInconsistentError` + quarantine + reads work + `repairIndex()` lifts it), and the error's
+record naming.
+
+## v8.2.4 — 2026-07-12 (restore can no longer destroy the store it's recovering)
+
+Recovery-safety fix. `restore()` removed the entire live brain directory and THEN copied the
+snapshot in — so any copy failure left the store destroyed with only a partial copy. The sharpest
+edge: the copy (`fs.cp`) materialized the holes of sparse mmap blob files, so a snapshot that fits
+on disk could balloon and `ENOSPC` mid-copy, and the recovery tool would have just destroyed the
+brain it was asked to recover. Reported from a downstream incident's recovery forensics.
+
+Restore is now **non-destructive and crash-resumable**:
+
+- The snapshot is copied into a staging area (`_restore_staging/`) **before any live data is
+ touched**. The copy is **sparse-aware** — all-zero regions are left as holes, so a store of
+ mostly-hole blobs restores at its true allocated size instead of its apparent size.
+- A copy failure (including `ENOSPC`) removes only the half-written staging area and throws; the
+ live store is left **exactly as it was**.
+- Only after the copy succeeds and a completion marker is `fsync`'d does an **atomic per-entry
+ swap** move the staged data into place — same-filesystem renames that cannot fail for disk space.
+- A crash mid-swap is finished **forward** on the next open: startup resumes a committed-but-
+ incomplete swap, or discards an uncommitted staging area (live data still authoritative).
+
+No API change — `restore(path, { confirm: true })` is unchanged. `persist()` was already safe
+(hard-link snapshot). Regression (`tests/integration/restore-nondestructive.test.ts`): a forced
+copy failure leaves live data fully intact, a normal restore round-trips, an interrupted-but-
+committed restore completes on reopen, an uncommitted staging area is discarded, and the sparse
+copy is byte-identical with allocation far below apparent size.
+
+## v8.2.3 — 2026-07-12 (a committed transaction is durable on return)
+
+Durability fix. A `transact()` reported "committed" while its canonical entity writes were still
+only in the OS page cache (written via tmp+rename, not yet `fsync`'d), even though the generation
+counter and manifest WERE fsync'd. A hard kill (power loss, SIGKILL) in that window could leave the
+durable generation counter **ahead of** the persisted entity bytes — so a consumer resuming from the
+counter would see "phantom progress": a generation that claims writes the disk never kept. Reported
+from a downstream migration's crash-lifecycle forensics.
+
+`commitTransaction` now runs a **durability barrier**: it records every canonical write and delete
+the batch's operations make, then `fsync`s that entire footprint (file contents, the rename
+directory entries, and the parent directories of any deletes) **before** advancing the generation
+counter and manifest. A committed transaction is therefore durable the moment `transact()` returns —
+the counter can never outrun the entity bytes.
+
+**Durability contract, now explicit.** `transact()` is durable-on-return (above). A **single-op**
+write (`add`/`update`/`remove`/`relate`/…) is Model-B group-commit: its live bytes are written but
+become durable at the next `flush()` or `close()`, not the instant the call resolves — the counter
+is buffered alongside the data, so a crash loses both together (never a torn counter-ahead-of-state
+store). Need per-write durability? Use `transact()` (even for one op), or `flush()` after the write.
+This deliberately trades single-op fsync latency (a 3-5x write regression) for throughput.
+
+No API change; no accelerator involvement (the barrier is in the filesystem storage adapter). In-memory
+and durable-per-call (cloud object-PUT) adapters treat the barrier as a no-op. Regression:
+`tests/integration/transact-durability-barrier.test.ts` proves entity writes fsync in an earlier
+batch than the manifest, for single-op and multi-op (add+relate) transactions, and that a
+precommit-rejected batch opens no barrier and advances nothing.
+
+## v8.2.2 — 2026-07-11 (P0: a timed-out transaction now rolls back — no torn state)
+
+Data-integrity fix. A transaction that exceeded its time budget **mid-flight** (e.g. a bulk
+`transact()` on slower hardware crossing the 30s ceiling) threw a timeout error WITHOUT rolling
+back the operations it had already applied. The budget check sat outside the per-operation
+rollback path, so only per-operation *failures* rolled back — a timeout stranded the partial
+writes in canonical storage while the generation was never stamped, leaving torn, generation-less
+state. This was caught by a downstream migration that crossed the ceiling on a large batch.
+
+`Transaction.execute()` now has a **single rollback point**: any error that escapes the operation
+loop — an operation failure OR a mid-flight timeout — rolls back every applied operation in
+reverse order, then surfaces the original error (a rollback failure supersedes it, loudly, as
+before). This restores the invariant the generation-store commit path already depended on: a throw
+from execute means the applied operations were undone byte-identically, and the aborted
+transaction leaves `generation()` unchanged and storage byte-identical to its pre-transaction
+state.
+
+No API change. Regression pins the reported requirement — after a mid-flight timeout,
+storage is byte-identical and the transaction ends in the `rolled_back` terminal state
+(`tests/unit/transaction/timeout-rollback.test.ts`), plus the operation-failure and
+rollback-failure paths through the same single rollback point.
+
+**Note on the 30s ceiling itself** (configurable/scaled timeout, batched embedding precompute,
+timeout telemetry) — that ergonomics work is tracked separately; this release fixes only the
+correctness bug (a timeout must never leave partial state), independent of where the ceiling sits.
+
+## v8.2.1 — 2026-07-11 (transact forward references work on the native accelerator)
+
+Parity fix. `transact()` has always promised atomic forward references — `add` an entity and
+`relate` to it in one batch — but the transaction planner resolved relationship endpoint ids at
+**plan** time, before the batch's adds had applied. Asking the id mapper about an entity that
+doesn't exist yet made the accelerator's (correctly strict) native mapper throw in
+`EntityIdMapper.getOrAssign`, so `transact([{op:'add', id:X}, {op:'relate', to:X}])` failed on
+native deployments while the permissive JS mapper masked the bug — and silently leaked an id
+assignment whenever a batch was later rejected by a commit precondition.
+
+Endpoint resolution is now **lazy** — evaluated when the graph operation executes inside the
+commit, after the batch's adds have applied (the same lazy pattern the operation's generation
+stamp already used). Fixed across all transact-planned graph operations: relate (including the
+bidirectional reverse edge), remove's relationship cascade, and unrelate. Single-operation writes
+are unchanged. Also fixed as a byproduct: a rejected batch no longer pollutes the id mapper.
+
+Regression suite covers the exact reported shape, both-endpoints-in-batch, bidirectional,
+add+relate+remove in one batch, and the mapper-cleanliness proof on rejected batches
+(`tests/integration/transact-forward-ref-graph.test.ts`). No API changes; no accelerator version
+pairing required.
+
+## v8.2.0 — 2026-07-10 (temporal VFS — file content joins the immutability model)
+
+Time travel now covers Virtual Filesystem **content**. Previously the temporal model had a hole
+exactly where files were concerned: every entity write was an immutable generation, but the
+content *bytes* lived under an eager reference-count GC — deleting a file could physically destroy
+bytes that in-window history still referenced, while overwriting never released the old content at
+all (an unbounded silent leak that only *accidentally* preserved history). A production consumer's
+recovery from a bad deploy succeeded only because a stale field happened to hold the good value —
+luck, not a guarantee. Both directions are now fixed by making blob reclamation a **history**
+decision instead of a **liveness** decision:
+
+- **Content blobs are retention-protected.** Each blob tracks live references AND history
+ references (one per persisted generation record that carries its hash). Deleting/overwriting a
+ file drops only the live reference; bytes are physically reclaimed in exactly one place —
+ history compaction — once no live reference and no retained generation references the hash.
+ Pinned views are exempt automatically (compaction already respects pins). Crash ordering is
+ over-count-only (never under-count), so a crash can leak until the built-in scrub recounts, but
+ can never reclaim bytes history still needs. Existing stores get a one-time, marker-gated
+ backfill on open; cross-file content dedup is handled exactly.
+- **`vfs.readFile(path, { asOf })`** — the file's exact bytes as of a generation or `Date`,
+ guaranteed present within the retention window.
+- **`vfs.history(path)`** — the file's versions (`{ generation, timestamp, hash, size,
+ mimeType? }`, ascending). Restore = read the old bytes `asOf` and write them back (a new write;
+ history is never rewritten).
+- **Overwrites now refresh the file entity's `data`/embedding text** — previously semantic search
+ and the `data` field served the FIRST version's text forever.
+
+Lifecycle note: deleting a file no longer frees its bytes immediately — old content lives until
+compaction reclaims its generations, under the same `retention` budget as all Model-B history
+(`retention: 'all'` keeps every version forever). Guide: the new "Time travel for files" section in
+`docs/guides/snapshots-and-time-travel.md`. Native accelerator: unaffected (canonical write path
+only) — no version pairing required.
+
+## v8.1.0 — 2026-07-10 (`brain.onChange` — the in-process change feed)
+
+New public API: subscribe to every committed mutation with
+`brain.onChange(cb) → unsubscribe`. One event per affected record, for **every**
+canonical write regardless of origin — direct calls, batch methods, `transact()`,
+imports, and Virtual Filesystem writes all funnel through the same commit point the
+feed is emitted from. This is the authoritative in-process signal for live UIs,
+cache invalidation, and realtime sync layers (downstream SDKs can forward it over
+their own transports to make local and remote brains uniform).
+
+Event shape (`BrainyChangeEvent`, exported): `kind` (`entity`/`relation`/`store`),
+`op` (`add`/`update`/`remove`/`relate`/`unrelate`/`updateRelation`/`clear`/`restore`),
+the post-commit `entity` or `relation` view (type + full custom metadata), the
+committed `generation`, and `timestamp`. Notable properties:
+
+- **Post-commit only** — an aborted write (a losing `ifRev` CAS, a rejected
+ transaction) never emits. If you got the event, the write is durable.
+- **Deletes fully described** — `remove`/`unrelate` carry the record's last
+ committed state (from the commit's own history record), not just an id; batch
+ deletes included.
+- **Batches emit per item**; a `transact()` batch's events share its single
+ generation. Entity deletes also emit `unrelate` for each cascaded relationship.
+- **Commit-ordered, asynchronous, isolated** — events arrive in commit order,
+ dispatched in a microtask so a slow listener never delays a write and a throwing
+ listener never affects the write or other listeners. Zero overhead with no
+ subscribers.
+- **`kind: 'store'`** events for `clear()`/`restore()` mean "refetch everything".
+- Fire-and-forget by design; each event's `generation` composes with
+ `asOf()`/`transactionLog()` for catch-up after a gap.
+
+Guide: `docs/guides/reacting-to-changes.md`. No behavior changes for existing code.
+
+## v8.0.17 — 2026-07-08 (count recovery scans the real layout · ~1,100 lines of dead 7.x machinery removed)
+
+A cleanup of the vestigial 7.x "hnsw sharding" machinery that turned up two real fixes.
+
+**1 — Count recovery now scans the layout the database actually uses.** When `counts.json` is
+lost or corrupted (container restarts, partial copies), the recovery scan rebuilt the entity/
+relationship counters by counting files in `entities/*/hnsw/` — directories the 8.0 write path
+never populates — so an established store recovered to **zero counts** on real data: wrong
+`getNounCount()`/stats, a "New installation"-style boot log, and a mis-sized rebuild-strategy
+decision at open. The scan now counts the canonical `entities////` tree.
+Regression-tested: delete `counts.json` from a populated store → reopen → exact counts.
+
+**2 — A latent init-time deadlock removed.** The recovery scan's type-distribution sampler called
+a guarded accessor (`getNounMetadata` → `ensureInitialized`) from **inside** `init()`, which
+re-enters `init()` and hangs the open. It was unreachable before only because the old scan never
+found anything to sample; the fix reads the sampled metadata files directly.
+
+**3 — The dead machinery itself is gone (−1,138 lines).** Twenty-three methods with zero callers:
+the 7.x hnsw-layout entity/edge CRUD, the sharding-depth prober + depth-migration engine, and an
+orphaned streaming paginator. Verified by reachability analysis before deletion; no public API
+touched; the full suite is green. New stores no longer carry the always-empty legacy directories'
+scan cost, and the boot log keeps the truthful new-vs-established wording from v8.0.13.
+
+## v8.0.16 — 2026-07-08 (concurrency fast-follow: atomic `ifAbsent`/`upsert` + exact blob reference counts)
+
+Closes the two remaining check-then-act races found in the sweep that followed v8.0.15's CAS fix
+(disclosed in that release's notes). Same bug class — a check performed before the serialization
+point that guards the apply — same cure.
+
+**1 — `add({ ifAbsent })` and `add({ upsert })` are now atomic.** The absence check ran before the
+commit mutex, so N concurrent same-id creates could all pass it and all write — the second
+silently overwriting the first, violating ifAbsent's "returns the existing id **without writing**"
+contract and upsert's "merge, never clobber" contract. The insert leg now carries a must-be-absent
+precondition (the same conditional-commit primitive as `ifRev`), verified under the commit mutex:
+exactly one concurrent create wins; every other caller takes its documented resolution — ifAbsent
+returns the existing id with zero writes, upsert merges into the now-existing entity (with a
+bounded retry if a concurrent delete intervenes). Verified: 8 concurrent `ifAbsent` creates
+advance the store by exactly ONE generation; 8 concurrent upserts on an absent id produce one
+create + seven merges (`_rev` lands at exactly 8). Note: `transact()`'s batch-level
+ifAbsent/upsert keep planning-time semantics (the batch converges to a valid entity; the window is
+documented, not silent).
+
+**2 — Blob reference counts are exact under concurrency.** The content-addressed blob store's
+`write()` (dedup: exists → add a reference, absent → create) and `delete()` (decrement → remove at
+zero) were unserialized read-modify-writes over the blob's metadata. Concurrent writes of
+identical content could lose references — making a later delete remove bytes **another file still
+referenced** (data loss), or leak unreferenced blobs. All reference-count-bearing mutations are
+now serialized per content hash (distinct content never contends): N concurrent identical writes
+yield exactly N references, and a blob is physically removed only when the true last reference
+drops. Verified with concurrent write/delete storms.
+
+Both fixes are in-process complete by construction — storage enforces single-writer-per-directory,
+so the process is the whole concurrency domain. No API changes.
+
+## v8.0.15 — 2026-07-08 (`ifRev` CAS is now atomic — exactly one winner under concurrency)
+
+Correctness fix for optimistic concurrency, reported from a production cutover rehearsal. N
+**concurrent** `update({ ifRev })` calls carrying the same expected revision ALL succeeded — zero
+`RevisionConflictError`s, last-writer-wins, the other N−1 writes silently lost. (Sequential calls
+conflicted correctly.) The revision check ran before the commit mutex, so interleaved callers all
+passed it before any apply landed — breaking the exactly-one-winner semantics the
+[optimistic-concurrency guide](docs/guides/optimistic-concurrency.md) promises, which advisory
+locks and per-entity ledgers/counters build on.
+
+The fix makes the check-and-apply a **conditional commit**: the generation store's commit paths
+accept a precondition that runs under the commit mutex against the just-read authoritative
+before-images — the per-record analogue of `ifAtGeneration`, which always ran there. `update()`
+and `transact()` per-op `ifRev` both re-verify at that point (the earlier check remains as a cheap
+fast-fail). A conflict aborts atomically: nothing staged, nothing applied, the same
+`RevisionConflictError` as before. Two adjacent behaviors also became honest:
+
+- **`_rev` is now monotonic under concurrency.** The winner's stamp derives from the
+ authoritative before-image, so N concurrent plain updates advance `_rev` by N (previously they
+ could all stamp the same stale value).
+- **CAS against a concurrently-deleted entity** now throws `EntityNotFoundError` instead of
+ silently resurrecting it (plain updates keep their last-writer-wins re-create semantics).
+
+Verified with a concurrency regression suite: 8 parallel same-rev updates → exactly 1 winner + 7
+conflicts; the documented read→CAS→retry ledger loop converges exactly (8 workers, 8 decrements,
+0 lost) — `tests/integration/ifrev-concurrent-cas.test.ts`. If you serialized writes in your own
+adapter as a workaround, it can come out after this upgrade.
+
+## v8.0.14 — 2026-07-07 (7→8 migration preserves branch-scoped non-entity state instead of deleting it)
+
+Defense-in-depth for the one-time 7→8 layout migration. The migration rescues the head branch's
+entities (`branches//entities/*` → `entities/*`) and then drained the whole `branches//`
+directory. If a 7.x engine had written durable state under the head branch *outside* `entities/`
+(a branch-scoped index, blob area, or field registry), that drain would have silently deleted it —
+the same failure class as the VFS content blobs a 7.x store kept in `_cow/`. The migration now drains
+the branch only when nothing but the moved entities remains; if any non-entity object survives, it
+**preserves the branch** (no delete) and logs a loud warning naming the leftover keys, so the state is
+recoverable rather than lost. No effect on a normal migration (a clean branch still drains); purely a
+guard against silent data loss.
+
+Cosmetic boot-log fix. Every persisted 8.0 store logged `📁 New installation: using depth 1
+sharding` on **every** open — even established brains holding thousands of entities — which is
+alarming to read during a restart or incident. Cause: 8.0 stores nouns in the canonical
+`entities/nouns///vectors.json` layout, but the legacy sharding probe inspected the
+`entities/nouns/hnsw/` directory, which the 8.0 write path never populates, so it always concluded
+"new". The new-vs-existing decision now consults the layout the database actually reads and writes
+(plus the known noun count), so an established store logs `📁 Using depth 1 sharding (N entities)`
+and only a genuinely empty store reports a new installation. No behavior change beyond the log line —
+it never triggered a rebuild or migration; entities were always read correctly.
+
+## v8.0.12 — 2026-07-07 (7→8 VFS-content recovery · zero-rebuild cold open · strict query operators)
+
+Three consumer-facing fixes.
+
+**1 — A 7→8 upgrade recovers Virtual Filesystem content automatically (data-integrity).**
+7.x stored VFS file content as blobs in the branch system's copy-on-write area (`_cow/`). 8.0
+removed that system and stores content blobs in the content-addressed store (`_cas/`), but the
+one-time layout migration only moved entities — it never adopted the `_cow/` content blobs. On a
+store that used the VFS (for example, a CMS with published pages), that left every VFS-backed read
+throwing `Blob metadata not found` and the pages 500ing. **8.0.12 adds an on-open recovery pass**
+that adopts every orphaned `_cow/` blob into `_cas/` — copying both the bytes and the metadata,
+idempotently and **non-destructively** (the `_cow/` originals are never deleted). It heals both a
+fresh 7→8 upgrade and a store **already** upgraded by an earlier 8.0.x that stranded them, with no
+operator action: just open the store under 8.0.12. An explicit force path is exposed as
+`brain.vfs.adoptOrphanedBlobs()` (returns `{ cowBlobs, adopted, alreadyPresent, incomplete }`). The
+automatic pre-upgrade backup is now **retained** if any blob can't be fully adopted
+(`incomplete > 0`) instead of being removed on entity-migration "success" alone. Affects any 7.x
+store that used the VFS; native-8.0 and non-VFS stores no-op on a cheap existence check. Full guide:
+`docs/guides/upgrading-7-to-8.md`.
+
+**2 — A cold open no longer re-derives durable indexes it can just load.**
+Opening a persisted store rebuilt the vector, graph, and metadata indexes from the canonical
+records even when the durable index state was present and loadable — an O(N) cost paid on every
+boot (measured at ~48 s on an ~11k-entity store). The rebuild gate now consults an honest
+per-provider durability signal (`init()` / `isReady()`) instead of an in-memory size heuristic, so
+a provider that has loaded (or can cheaply demand-load) its persisted index is not rebuilt. The
+built-in JS indexes keep today's behavior (a rebuild *is* their load path); the live query-time
+guards that self-heal a genuinely lost index are unchanged. **The zero-rebuild boot activates when
+the accelerator exposes the durability signal (`@soulcraft/cor@3.0.5`);** on earlier accelerator
+versions 8.0.12 falls back to the previous behavior safely — no regression, just no speedup yet.
+
+**3 — Unknown query operators now throw instead of silently returning nothing.**
+`find({ where })` had two gaps: the in-memory matcher (used for egress re-validation and historical
+reads) was missing several documented operators (`in`, `greaterThanOrEqual`, `lessThanOrEqual`), so
+it silently disagreed with the index path; and an unknown operator key (a typo, or `notIn` written
+where `not: { in }` was meant) was treated as a nested-object field and silently matched nothing.
+8.0.12 aligns the matcher to the full documented operator set and **validates the `where` filter
+up front**, throwing a typed `BrainyError('INVALID_QUERY')` naming the bad operator. Dotted paths
+remain the supported form for nested fields. If you relied on an unknown key silently returning `[]`,
+it now throws — the fix is to use the documented operator or dot-notation.
+
+## v8.0.11 — 2026-07-02 (no script shape can hang on brainy's internals)
+
+Completes v8.0.10's exit fix for every operation class. Two further mechanisms found and fixed:
+the `beforeExit` auto-flush hook looped forever on any script that never reaches `close()` (Node
+re-emits `beforeExit` after each event-loop drain and the async flush schedules new work — it now
+self-deregisters before its single flush, which still lands your buffered data before exit), and
+every background-maintenance interval (graph auto-flush, LSM compaction, metadata write-buffer,
+VFS/path-cache maintenance, statistics debounce) is now unref'd at creation. Verified against the
+published package: an `add + relate` script exits cleanly both with `close()` (~0.5 s) and with no
+teardown at all — with the data confirmed durable on reopen. A per-operation-class sweep test now
+asserts no ref'd timer survives `close()`, so this bug class stays closed.
+
+## v8.0.10 — 2026-07-02 (a bare script exits cleanly after `close()`)
+
+A minimal `init → add → close` script used to hang forever after `close()` returned — brainy held
+four process keep-alives it never released: the global SIGTERM/SIGINT shutdown hooks (never
+removed; now deregistered when the last live instance closes), an internal cache-fairness interval
+(unclearable by construction; now unref'd), and the VFS/PathResolver maintenance intervals
+(`close()` never shut the VFS down; now wired). Verified against the published package: the same
+script now exits ~1 ms after `close()` resolves. No API change; servers and long-running processes
+are unaffected.
+
+## v8.0.9 — 2026-07-02 (guarded plugin auto-detection — the "install it and it's on" contract)
+
+With the default config (`plugins` unset), brainy now **auto-detects the first-party accelerator**:
+installing `@soulcraft/cor` is the opt-in. The detection is guarded — everything except "not
+installed" fails loud:
+
+- Not installed → plain brainy, silently (the free path — zero noise, zero cost).
+- Installed and healthy → it loads and announces itself (`[brainy] Plugin activated`).
+- Installed but broken (unresolvable, invalid shape, failed activation, version mismatch) →
+ **`init()` throws.** An installed accelerator never silently vanishes behind the JS engines.
+- `plugins: []` / `false` = explicit opt-out; `plugins: ['@soulcraft/cor']` pins the exact list
+ (unchanged semantics).
+
+Also new: if a plugin activates but registers **zero** native providers (e.g. a licensing gate
+declining to engage), brainy warns loudly instead of leaving you to discover every query is running
+on the JS engines.
+
+> This supersedes v8.0.8's "plugins are explicit opt-in" wording, which documented the pre-GA
+> loader accurately but contradicted the published product contract ("add the package and it
+> activates"). 8.0.9 makes the contract true — with the loud-failure guarantees intact.
+
+## v8.0.8 — 2026-07-02 (docs-only patch on the GA)
+
+Corrects the README's scale-up section: the native provider is **not** auto-detected — plugins are
+**explicit opt-in** (`new Brainy({ plugins: ['@soulcraft/cor'] })`; brainy never auto-imports a
+package you didn't list, and a listed plugin that fails to load throws rather than silently falling
+back to the JS engines). Also fixes the stale `plugins` config comment in the public types and one
+phrase in the plugin-author guide. No code change.
+
+## v8.0.7 — 2026-07-02 (GA, npm tag `latest`)
+
+> **Why 8.0.7:** npm permanently retires unpublished version numbers, and `8.0.0`–`8.0.6` were
+> consumed by a January development cycle (published and immediately unpublished). `8.0.7` is
+> the first stable release of the 8.x line; there are no earlier stable 8.0.x releases.
+
+**8.0.7 is the first stable major on the u64-id core.** It ships in lockstep with the optional
+native provider's `3.0` (the billion-scale path). Everything below works standalone on the open-core
+JS engine — a native provider is feature-detected and only changes the scale ceiling, never the API.
+
+**Upgrading from 7.x: nothing to script.** The first time 8.0 opens a 7.x brain it upgrades itself —
+an automatic, observable, **coordinated migration lock** rebuilds every derived index from your
+canonical records while Brainy **blocks and queues** reads and writes, so no operation ever touches a
+half-built index and no write is ever lost. Budget seconds-to-minutes per brain at large sizes; small
+brains rebuild inline on open. A pre-upgrade hard-link **backup** is taken automatically (removed on
+success, kept on failure for rollback). Observe it via `getIndexStatus().migration`; a caller that
+hits the window gets a typed, retryable **`MigrationInProgressError`** (catch → HTTP 503 +
+`Retry-After`). Bounded by `migrationWaitTimeoutMs` (default 30 s — it bounds *your wait*, not the
+rebuild). Opt out of the backup with `migrationBackup: false`.
+
+### Headline changes
+
+- **The cold-open silent-`[]` class is gone.** On a cold reopen, filtered finds (`find({ where })`)
+ and graph reads (`find({ connected })` / `neighbors()` / `related()`) never silently return `[]`
+ for data that is actually present. With a native provider the durable indexes cold-serve every
+ filter and edge with zero rebuild; the open-core engine adds belt-and-suspenders guards that
+ self-heal from canonical data or throw a loud, typed error — never a silent empty result. Two new
+ exported errors: **`MetadataIndexNotReadyError`**, **`GraphIndexNotReadyError`**.
+
+- **Faster vector search (open-core).** The JS distance functions are allocation-free loops instead
+ of `reduce`: **~6× cosine, ~1.4× euclidean** (MEASURED — `tests/benchmarks/distance-microbench.mjs`,
+ 384-dim, median of 41). Numerically identical, so recall is unchanged. Exact metadata-filter
+ pushdown and scale-aware search width keep `find()` recall-correct as data grows.
+
+- **Per-write immutable history.** Every write is generation-stamped; `now()` / `asOf()` /
+ `transact()` read a consistent point in time, and a retention knob bounds history growth. Single
+ writes participate in the same immutable timeline as transactions.
+
+- **Billion-scale resident memory.** Nothing is O(N)-resident on the write or time-travel path —
+ per-id caches removed, the committed-generation ledger is an interval set, per-id history chains
+ are bounded (hot-window + LRU). Resident memory is independent of entity count.
+
+- **Deterministic, round-trip-free writes.** Supply your own ids, forward-reference not-yet-written
+ entities inside a `transact()`, `ifAbsent` upsert, and `brain.newId()` (uuidv7) — write graphs
+ without a write→wait→get round-trip.
+
+### Breaking changes (summary — the full migration guide follows below)
+
+- **Runtime floor: Node ≥ 22 / Bun ≥ 1.1** (Bun recommended). Compiler target ES2023; the DOM lib is
+ dropped — 8.0 is a Node/Bun/Deno engine with no browser path.
+- **`neural()` removed** and **`Db.search` removed** — use `find()` on the brain.
+- **Storage config is one `path` key.** Removed aliases now throw with a message pointing at `path`.
+- **`get()` omits the vector by default** — pass `{ includeVectors: true }` when you need it.
+- **Export/import type is `PortableGraph`** (was `BackupData`; wire tag `brainy-backup` →
+ `brainy-portable-graph`). Re-export any snapshots you version outside Brainy.
+- **Reserved `visibility` field** (`public` / `internal` / `system`) on nouns and verbs; `internal`
+ and `system` are excluded from normal reads by default.
+- **4 deprecated query operators removed** (`is`/`isNot`/`greaterEqual`/`lessEqual`) and reserved
+ keys in a `metadata` bag now **throw** by default — details in the rc notes below.
+
+> Post-rc.9 hardening folded into GA (no on-disk or provider-contract change vs `8.0.0-rc.9`):
+> the metadata cold-read guard, the auto pre-upgrade backup (with an `_id_mapper/*` mmap byte-copy
+> correctness fix), and a CI fix so a fresh clone builds green.
+
+### RC history (rc.1 → rc.9, npm tag `rc`)
+
+> **rc.9 changes (2026-07-01):**
+> - **Whole-brain auto-upgrade is now a coordinated, observable LOCK — supersedes rc.8's no-freeze
+> approach.** When a large brain's derived-index format changes (7.x→8.0), the native provider
+> rebuilds the indexes from the canonical records **in place** while Brainy **blocks and queues**
+> reads and writes — so no operation ever touches a half-built index. This is a clean *blocking
+> upgrade to a known-good state* (vs rc.8's online background swap): unknown/halfway states are
+> more dangerous than a bounded wait. Small brains still rebuild inline on open. New surface, all
+> additive: a typed, exported, retryable **`MigrationInProgressError`** (catch it → HTTP 503 +
+> `Retry-After`); `getIndexStatus()` gains **`migrating` + `migration`** progress (never gated —
+> the readiness-probe signal); a **`migrationWaitTimeoutMs`** config (default 30 s — it bounds the
+> *caller's wait*, NOT the rebuild, which is unbounded); `health()` / `checkHealth()` report the
+> upgrade without blocking. No effect without a native provider.
+> - **Faster vector search (open-core).** The JS distance functions are rewritten from `reduce` to
+> allocation-free loops — **~6× cosine, ~1.4× euclidean** (MEASURED, `tests/benchmarks/distance-microbench.mjs`,
+> 384-dim, median of 41). Numerically identical → recall unchanged.
+> - **Runtime floor + Bun.** Engines are now **Node ≥22 / Bun ≥1.1** (fixes a Node-24 `EBADENGINE`);
+> compiler target ES2023 with DOM dropped from the type lib (8.0 is Node/Bun/Deno-only, no browser
+> path). **Bun is recommended as a runtime** (`bun add` / `bun run`); the single-binary
+> `bun build --compile` is not a supported target (native addon can't embed + a Bun 1.3.10 codegen
+> regression). `.d.ts` stays TS-5.x-parseable.
+
+> **rc.8 additions (2026-06-30) — additive, no breaking change:**
+> - **No-freeze (online) whole-brain auto-upgrade.** When a derived-index format changes, a large
+> brain upgrades **without blocking** — the native provider rebuilds the new indexes in the
+> background and serves correct reads from the canonical records meanwhile, then atomically swaps;
+> no minutes-long freeze on the first open/query. (Small brains still auto-rebuild inline on open.)
+> New surface: an optional provider `isMigrating()` deference signal, a public `brain.stampBrainFormat()`
+> the provider calls once its swap verifies, and a `@soulcraft/brainy/brain-format` export so the
+> provider shares the `indexEpoch` constant. No effect without a native provider.
+
+> **rc.7 additions (2026-06-30) — additive, no breaking change:**
+> - **8.0 cold-graph self-heal.** `find({ connected })` / `neighbors()` / `related()` never
+> silently return `[]` on a cold open where the graph adjacency loaded its membership but not
+> its source→target edges — it self-heals (rebuild from storage) or throws a loud
+> `GraphIndexNotReadyError`, never empty-for-persisted-data. (The 8.0 equivalent of the 7.33.4
+> fix 7.x consumers already have; gates on the native provider's honest `isReady()` edge-readiness
+> signal.)
+> - **Billion-scale RAM — nothing O(N)-resident on the write/time-travel path.** Eliminated the
+> per-id storage caches (per-type counts now sourced from the record), made the
+> committed-generation ledger an interval set, and bounded the per-id time-travel history chains
+> (hot-window + LRU, with lock-light reconstruction so historical reads never stall writers).
+> Resident memory is now independent of entity count.
+> - **Whole-brain version handshake.** A `_system/brain-format.json` marker + `brain.formatInfo()`
+> + a shared, lockstep `indexEpoch`: a future derived-index format change auto-rebuilds the
+> indexes from the canonical records on open (non-destructively) — the foundation for
+> whole-brain auto-upgrade with the native provider. No effect on an unchanged brain.
+
+> **rc.6 additions (2026-06-29) — additive, no breaking change:**
+> - **Open-core perf**: HNSW delete is now O(in-degree) (was O(N²) for bulk delete) via a
+> reverse-adjacency index; the negation/absence operators (`ne`/`exists:false`/`missing:true`)
+> are served as a roaring-bitmap difference instead of materializing the whole corpus.
+> - **Native provider contract** (cor lockstep, optional/feature-detected — no effect without a
+> native provider): a cold-open `probeConsistency()` self-heal hook, and a `getIdsForFilter`
+> page bound so the native index can early-stop the unsorted `find({ type, where, limit })` path.
+> - **Test hygiene**: re-homed previously-unrun test suites into CI + a guard so a test file can
+> never silently fall outside every config again. No source/API change from these.
+
**Affected products:** every consumer — this is a major release with removed
surfaces, hard renames (no aliases, no deprecation period), and one flipped
default. This entry **is** the migration guide: the find/replace pairs, the
sed snippet, and the upgrade checklist below are the complete story — there is
-no separate migration doc. **`8.0.0-rc.2` is live** — install with
-`npm i @soulcraft/brainy@rc` to try it; the `latest` tag stays on 7.x until GA.
+no separate migration doc. **`8.0.7` is GA on `latest`** — install with
+`npm i @soulcraft/brainy@latest`.
+
+> **rc.3–rc.5 additions (2026-06-24):**
+> - **Showcase-quality GA hardening (rc.5).** A full readiness audit closed a cold-init
+> version-coupling bug (a matched native provider could be rejected on first open), turned
+> silent storage-read failures into named `BrainyError`s (no more empty-result-on-error),
+> stopped the JS graph LSM from orphaning compacted SSTables, corrected the public docs to
+> the real API, and documented the two headline methods. Plus a dead/deprecated-code sweep.
+> - **BREAKING — 4 deprecated query operators removed.** `is`→`eq`, `isNot`→`ne`,
+> `greaterEqual`→`gte`, `lessEqual`→`lte`. The canonical operators and their clean long-form
+> aliases (`equals`/`notEquals`/`greaterThan`/`greaterThanOrEqual`/`lessThan`/`lessThanOrEqual`)
+> are unchanged — only the four redundant spellings are gone. Find/replace if you used them.
+> - **`find()` search mode** is now the single `searchMode: SearchMode` option (the redundant,
+> silently-ignored `mode` alias and the unwired `explain` flag were removed from `FindParams`).
+> - The phantom index-integrity guard (find() re-validates every result against its predicate)
+> shipped in rc.3; rc.4 added the `accel.isInitialized` gate + #35 at-gen candidate vectors.
> **rc.1 additions (this entry predates them — full writeup lands at GA):** entity-id
> normalization — `brain.newId()` (UUID v7) + v7 default ids, and non-UUID string ids are
diff --git a/docs/BATCHING.md b/docs/BATCHING.md
index 96239ba6..e70a9e18 100644
--- a/docs/BATCHING.md
+++ b/docs/BATCHING.md
@@ -78,10 +78,10 @@ for (const [id, metadata] of metadataMap) {
- ✅ Direct O(1) path construction from ID (no type lookup needed!)
- ✅ Sharding preservation (all paths include `{shard}/{id}`)
- ✅ Write-cache coherent (read-after-write consistency)
-- ✅ 40x faster than v5.x type-first architecture
+- ✅ O(1) path construction — eliminates the per-entity type search of the old type-first layout
**Performance:**
-- ~1ms per 100 entities (consistent, no cache misses!)
+- Constant-time path construction per ID — no type-cache misses
- Filesystem: parallel reads bounded by IOPS
- No type search delays — every ID maps directly to storage path
@@ -121,7 +121,7 @@ for (const [sourceId, verbs] of results) {
- Bulk export of relationship data
**Performance:**
-- Memory storage: <10ms for 1000 relationships
+- Memory storage: single in-memory pass over the metadata index
- Filesystem storage: parallel reads through the metadata index
---
@@ -171,7 +171,7 @@ entities/verbs/{SHARD}/{ID}/metadata.json
**Direct O(1) Path Construction:**
```typescript
-// Every ID maps directly to exactly ONE path - 40x faster!
+// Every ID maps directly to exactly ONE path - O(1), no type search
const id = 'abc-123'
const shard = getShardIdFromUuid(id) // → 'ab' (first 2 hex chars)
const path = `entities/nouns/${shard}/${id}/metadata.json`
@@ -183,9 +183,9 @@ const path = `entities/nouns/${shard}/${id}/metadata.json`
```
**Benefits:**
-- **40x faster** path lookups (eliminates 42-type sequential search)
+- **O(1)** path lookups (eliminates the 42-type sequential search the old type-first layout required)
- **Simpler code** - removed 500+ lines of type cache complexity
-- **Scalable** - works at billion-scale without type tracking overhead
+- **Scalable** - works at large scale without type tracking overhead
---
@@ -220,28 +220,22 @@ await db.release()
---
-## Performance Benchmarks
+## Why Batching Is Faster
-### VFS Operations (12 Files)
+Batching's advantage is structural, not a fixed multiplier (the actual speedup
+depends on storage backend, IOPS, and batch size):
-| Storage | Before optimization | After optimization | Improvement |
-|---------|---------------------|--------------------|-------------|
-| **Memory** | 150ms | 50ms | **67% faster** |
-| **Filesystem** | ~500ms | ~80ms | **84% faster** |
+- **N+1 elimination** — N sequential reads collapse into a single parallel pass
+ (`Promise.all` over the batch).
+- **O(1) path construction** — every ID maps directly to one storage path, with
+ no per-type cache lookup.
+- **One metadata round-trip** — relationship batches fetch all sources' metadata
+ in a single pass instead of one query per source.
-### Entity Batch Retrieval (100 Entities)
-
-| Storage | Individual Gets | Batch Get | Improvement |
-|---------|-----------------|-----------|-------------|
-| **Memory** | 180ms | 15ms | **92% faster** |
-| **Filesystem** | ~1.2s | ~120ms | **90% faster** |
-
-### Throughput (Entities/Second)
-
-| Storage | Individual | Batch | Improvement |
-|---------|------------|-------|-------------|
-| **Memory** | 556 ent/s | 6667 ent/s | **12x** |
-| **Filesystem** | ~80 ent/s | ~800 ent/s | **10x** |
+The integration test `tests/integration/storage-batch-operations.test.ts`
+exercises batch vs. individual reads and asserts that batch retrieval is not
+slower than the per-entity loop for large batches; it does not pin a specific
+multiplier, since that is hardware- and IOPS-dependent.
---
@@ -306,7 +300,7 @@ const results = await brain.batchGet(ids)
const entities = Array.from(results.values())
```
-**Performance Gain:** 10-20x faster on filesystem storage.
+**Performance Gain:** Replaces N sequential reads with a single batched pass — no fixed multiplier, it scales with storage IOPS.
---
@@ -332,7 +326,7 @@ for (const verbs of results.values()) {
}
```
-**Performance Gain:** 5-10x faster due to batched metadata fetches.
+**Performance Gain:** One batched metadata fetch instead of one query per source entity.
---
@@ -450,8 +444,8 @@ return await Promise.all(resolvedPaths.map(path => this.read(path)))
- `storage.getVerbsBySourceBatch(sourceIds, verbType?)` - Batch relationship queries
**Performance Improvements:**
-- VFS operations: 90%+ faster than the naive per-entity loop
-- Entity retrieval: 10-20x throughput improvement
+- VFS operations: single batched pass instead of N sequential reads
+- Entity retrieval: N+1 reads collapsed into one batched pass
- Zero N+1 query patterns
**Compatibility:**
diff --git a/docs/FIND_SYSTEM.md b/docs/FIND_SYSTEM.md
index 5bf8ef1a..1cc38ce9 100644
--- a/docs/FIND_SYSTEM.md
+++ b/docs/FIND_SYSTEM.md
@@ -22,7 +22,7 @@ Brainy's `find()` method is the most advanced query system in any vector databas
### 1. Vector Intelligence (HNSW Index)
- **Purpose**: Semantic similarity search using embeddings
- **Algorithm**: Hierarchical Navigable Small World (HNSW)
-- **Performance**: O(log n) search, ~1.8ms typical
+- **Performance**: O(log n) search
- **Data Structure**: Multi-layer graph with 16 connections per node
- **Use Cases**: "Find similar documents", "Content like this"
@@ -36,14 +36,14 @@ Brainy's `find()` method is the most advanced query system in any vector databas
### 3. Metadata Intelligence (Incremental Indices)
- **Purpose**: Fast filtering on structured data
- **Algorithm**: HashMap for exact matches, Sorted arrays for ranges
-- **Performance**: O(1) exact, O(log n) ranges, <1ms typical
+- **Performance**: O(1) exact, O(log n) ranges
- **Data Structure**: `Map>` + sorted value arrays
- **Use Cases**: "Documents from 2023", "Status equals active"
### 4. Graph Intelligence (Adjacency Maps)
- **Purpose**: Relationship traversal and connection analysis
- **Algorithm**: Pure O(1) neighbor lookups via Map operations
-- **Performance**: O(1) per hop, ~0.1ms typical
+- **Performance**: O(1) per hop (measured <1 ms per neighbor lookup, validated up to 1M relationships — `tests/performance/graph-scale-performance.test.ts:238`)
- **Data Structure**: `Map>`
- **Use Cases**: "Papers connected to MIT", "Authors who collaborated"
@@ -373,36 +373,40 @@ return results.slice(offset, offset + limit)
### Query Performance by Type
-| Query Type | Index Used | Performance | Example |
-|------------|------------|-------------|---------|
-| **Semantic Search** | HNSW Vector | O(log n), ~1.8ms | `"AI research papers"` |
-| **Exact Metadata** | HashMap | O(1), ~0.8ms | `{status: "published"}` |
-| **Range Metadata** | Sorted Array | O(log n), ~0.6ms | `{year: {greaterThan: 2020}}` |
-| **Graph Traversal** | Adjacency Map | O(1), ~0.1ms | `{connected: {to: "mit"}}` |
-| **Type Detection** | Pre-embedded Types | O(t), ~0.3ms | `"documents"` → `NounType.Document` |
-| **Field Matching** | Field Embeddings | O(f), ~0.1ms | `"by"` → `"author"` |
-| **Combined Query** | All Indices | O(log n), ~1.8ms | NLP + filters + graph |
+| Query Type | Index Used | Complexity | Example |
+|------------|------------|------------|---------|
+| **Semantic Search** | HNSW Vector | O(log n) | `"AI research papers"` |
+| **Exact Metadata** | HashMap | O(1) | `{status: "published"}` |
+| **Range Metadata** | Sorted Array | O(log n) | `{year: {greaterThan: 2020}}` |
+| **Graph Traversal** | Adjacency Map | O(1) | `{connected: {to: "mit"}}` |
+| **Type Detection** | Pre-embedded Types | O(t) | `"documents"` → `NounType.Document` |
+| **Field Matching** | Field Embeddings | O(f) | `"by"` → `"author"` |
+| **Combined Query** | All Indices | O(log n) | NLP + filters + graph |
Where:
- n = number of entities in database
- t = number of types (169 total: 42 noun + 127 verb)
- f = number of fields for detected entity type (typically 5-15)
+Absolute latencies depend on hardware, embedding model, and storage backend; the columns above describe how each stage scales. The graph stage is the one path with a committed scale benchmark (see [Scalability](#scalability)).
+
### Scalability
-| Database Size | Vector Search | Metadata Filter | Graph Query | Combined |
-|---------------|---------------|-----------------|-------------|----------|
-| **1K entities** | 0.8ms | 0.3ms | 0.05ms | 1.1ms |
-| **10K entities** | 1.2ms | 0.5ms | 0.08ms | 1.5ms |
-| **100K entities** | 1.8ms | 0.8ms | 0.1ms | 2.1ms |
-| **1M entities** | 2.5ms | 1.2ms | 0.1ms | 2.8ms |
-| **10M entities** | 3.8ms | 1.8ms | 0.1ms | 4.2ms |
+The cost of each query stage is governed by its algorithmic complexity, not a fixed millisecond figure — absolute latency depends on hardware, embedding model, and storage backend. Only the graph adjacency index carries a committed scale assertion:
+
+| Query stage | Complexity | Scaling behavior |
+|-------------|------------|------------------|
+| Metadata filter (exact) | O(1) | Constant — independent of dataset size |
+| Metadata filter (range) | O(log n) + O(k) | Sub-linear; k = matching results |
+| Vector search (HNSW) | O(log n) | Degrades gracefully via hierarchical layers |
+| Graph hop | O(1) | Measured <1 ms per neighbor lookup, validated up to 1M relationships (`tests/performance/graph-scale-performance.test.ts:238`) |
+| Combined query | O(log n) | Bounded by the vector stage; metadata and graph stages stay O(1)/O(log n) |
**Key Performance Notes:**
- Graph queries stay O(1) regardless of scale
- Metadata ranges scale as O(log n), not O(n)
- Vector search degrades gracefully due to HNSW
-- Type-aware NLP adds minimal overhead (~0.4ms)
+- Type-aware NLP adds minimal overhead (single embedding pass, no full scan)
## Example Query Flows
@@ -455,7 +459,7 @@ await brain.find({
},
limit: 10
})
-// Total performance: ~1.2ms for 100K entities
+// Executes as O(1) type filter + O(1)/O(log n) metadata + O(1) graph lookup — no full scan
```
## Filter Syntax Reference
@@ -1001,7 +1005,7 @@ const results = await brain.find({
})
```
-**Performance**: O(log n) vector search + O(log n) metadata filters + O(1) graph traversal = ~2-3ms total.
+**Performance**: O(log n) vector search + O(log n) metadata filters + O(1) graph traversal — bounded by the vector stage.
### Excluding Soft-Deleted Entities
diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md
index 0caa123d..248a2c70 100644
--- a/docs/PERFORMANCE.md
+++ b/docs/PERFORMANCE.md
@@ -2,11 +2,11 @@
## Performance Characteristics
-Brainy achieves industry-leading performance through carefully optimized data structures and algorithms. All performance claims are verified through actual benchmarks on production code.
+Brainy achieves high performance through carefully optimized data structures and algorithms. The tables below describe each component by its **algorithmic complexity** — the durable, defensible guarantee. The example latencies are figures from a single 100-item run on one machine (see [Benchmarks](#benchmarks)); they are illustrative, not a committed benchmark, and vary with hardware, embedding model, and storage backend. The one component with a committed scale assertion is the graph adjacency index (`tests/performance/graph-scale-performance.test.ts:238`).
### Core Performance Summary
-| Component | Operation | Time Complexity | Measured Performance | Data Structure |
+| Component | Operation | Time Complexity | Example latency (100-item run)\* | Data Structure |
|-----------|-----------|-----------------|---------------------|----------------|
| **Metadata Index** | Exact match | **O(1)** | 0.8ms | `Map>` |
| **Metadata Index** | Range query | **O(log n) + O(k)** | 0.6ms | Sorted array + binary search |
@@ -17,6 +17,8 @@ Brainy achieves industry-leading performance through carefully optimized data st
| **Type Detection** | Noun/Verb matching | **O(t)** | 0.3ms | Pre-embedded type vectors |
| **Triple Intelligence** | Combined query | **O(1) to O(log n)** | 1.8ms | Parallel execution |
+\* Illustrative single-run figures at 100 items on one machine — not a committed benchmark. Only the graph index carries an asserted scale bound (measured <1 ms per neighbor lookup up to 1M relationships, `tests/performance/graph-scale-performance.test.ts:238`).
+
Where:
- `n` = number of items in index
- `k` = number of results returned
@@ -26,25 +28,32 @@ Where:
### brain.get() Metadata-Only Optimization
-✨ **Massive Performance Improvement**: `brain.get()` is now **76-81% faster** by default!
+`brain.get()` returns **metadata only by default**, skipping the 384-dimensional
+embedding — the bulk of an entity's payload. Callers that need the vector opt in
+with `{ includeVectors: true }`.
-| Operation | Before | After | Speedup | Use Case |
-|-----------|------------------|-----------------|---------|----------|
-| **brain.get() (metadata-only)** | 43ms, 6KB | **10ms, 300 bytes** | **76-81%** | VFS, existence checks, metadata |
-| **brain.get({ includeVectors: true })** | 43ms, 6KB | 43ms, 6KB | 0% | Similarity calculations |
-| **VFS readFile()** | 53ms | **~13ms** | **75%** | File operations |
-| **VFS readdir(100 files)** | 5.3s | **~1.3s** | **75%** | Directory listings |
+| Operation | Default (metadata-only) | With `includeVectors: true` | Use Case |
+|-----------|-------------------------|-----------------------------|----------|
+| **brain.get()** | Skips vector load | Loads full vector | VFS, existence checks, metadata |
+| **VFS readFile() / readdir()** | Inherits metadata-only path | n/a | File operations, directory listings |
-**Key Innovation**: Lazy vector loading - only load 384-dimensional embeddings when explicitly needed.
+**Key Innovation**: Lazy vector loading — only load the 384-dimensional embedding when explicitly needed.
+
+The integration test `tests/integration/metadata-only-comprehensive.test.ts:306`
+asserts metadata-only `get()` is faster than the full-entity `get()`
+(`metadataTime < fullTime`). The *magnitude* of the speedup is
+environment-dependent (the percentage assertion in
+`tests/integration/vfs-performance-v5.11.1.test.ts` is intentionally skipped on
+CI for that reason), so no fixed percentage is quoted here.
**Why this matters**:
-- **94% of brain.get() calls** don't need vectors (VFS, admin tools, import utilities, data APIs)
-- **Vector data is 95% of entity size** (6KB vectors vs 300 bytes metadata)
-- **Zero code changes** for most applications - automatic speedup!
+- Most `brain.get()` calls don't need vectors (VFS, admin tools, import utilities, data APIs)
+- The embedding dominates an entity's serialized size, so skipping it is the largest win
+- **Zero code changes** for most applications — automatic by default
**When to use what**:
```typescript
-// DEFAULT: Metadata-only (76-81% faster) - use for:
+// DEFAULT: Metadata-only (skips the vector load) - use for:
const entity = await brain.get(id)
// - VFS operations (readFile, stat, readdir)
// - Existence checks: if (await brain.get(id)) ...
@@ -252,28 +261,34 @@ const results = await Promise.all(searchPromises)
## Benchmarks
-### Real-world Performance Test (100 items)
+### Illustrative Single Run (100 items, one machine)
+
+Example output from a single 100-item run — illustrative only, not a committed
+benchmark; absolute numbers vary by hardware. The values feed the
+[Core Performance Summary](#core-performance-summary) example-latency column.
```
-📊 Metadata exact match: 0.818ms (50 items matched)
-📊 Metadata range query: 0.631ms (40 items in range)
-🔗 Graph neighbor lookup: 0.092ms (2 connections)
-🎯 Vector k-NN search: 1.773ms (10 nearest neighbors)
-🧠 NLP query parsing: 8.906ms (full natural language)
-⚡ Triple Intelligence: 1.830ms (combined query)
+Metadata exact match: 0.818ms (50 items matched)
+Metadata range query: 0.631ms (40 items in range)
+Graph neighbor lookup: 0.092ms (2 connections)
+Vector k-NN search: 1.773ms (10 nearest neighbors)
+NLP query parsing: 8.906ms (full natural language)
+Triple Intelligence: 1.830ms (combined query)
```
### Scaling Characteristics
-| Items | Metadata O(1) | Range O(log n) | Graph O(1) | Vector O(log n) |
-|-------|---------------|----------------|------------|-----------------|
-| 100 | 0.8ms | 0.6ms | 0.09ms | 1.8ms |
-| 1,000 | 0.8ms | 0.9ms | 0.09ms | 2.5ms |
-| 10,000 | 0.8ms | 1.2ms | 0.09ms | 3.2ms |
-| 100,000 | 0.8ms | 1.5ms | 0.09ms | 4.1ms |
-| 1,000,000 | 0.8ms | 1.8ms | 0.09ms | 5.0ms |
+Each stage scales by its algorithmic complexity, not a fixed millisecond figure
+— absolute latency depends on hardware, embedding model, and storage backend.
+Only the graph adjacency index carries a committed scale assertion:
-*Note: O(1) operations maintain constant time regardless of scale*
+| Query stage | Complexity | Scaling behavior |
+|-------------|------------|------------------|
+| Metadata filter (exact) | O(1) | Constant — independent of dataset size |
+| Metadata filter (range) | O(log n) + O(k) | Sub-linear; k = matching results |
+| Vector search (HNSW) | O(log n) | Degrades gracefully via hierarchical layers |
+| Graph hop | O(1) | Measured <1 ms per neighbor lookup, validated up to 1M relationships (`tests/performance/graph-scale-performance.test.ts:238`) |
+| Combined query | O(log n) | Bounded by the vector stage; metadata and graph stages stay O(1)/O(log n) |
## Comparison with Other Systems
@@ -402,7 +417,7 @@ const brain = new Brainy({
})
```
-The default JS index is `JsHnswVectorIndex`. An optional native acceleration package (`@soulcraft/cortex`) can replace it with a higher-performing implementation; the public knobs stay the same.
+The default JS index is `JsHnswVectorIndex`. An optional native acceleration package (`@soulcraft/cor`) can replace it with a higher-performing implementation; the public knobs stay the same.
### Scale Scenarios
@@ -470,8 +485,8 @@ For off-site replication, snapshot `path` from your scheduler (`gsutil rsync`, `
- **Auto-configuration system** with environment detection
- **Zero-config operation** with intelligent defaults
- **Auto-flush and auto-optimize** in indices
-- **Sub-2ms response times** for complex queries
+- **Low-latency Triple Intelligence queries** (O(log n) vector + O(1) metadata/graph)
## Conclusion
-Brainy delivers on its promise of **production-ready Triple Intelligence** with measured, verified performance characteristics. All listed features are fully implemented, tested, and benchmarked. No stubs, no mocks, no theoretical claims - just real, working code with measured performance.
\ No newline at end of file
+Brainy delivers on its promise of **production-ready Triple Intelligence** with documented algorithmic-complexity guarantees and a committed graph-scale benchmark (`tests/performance/graph-scale-performance.test.ts`). All listed features are fully implemented and tested. No stubs, no mocks — just real, working code with characterized performance.
\ No newline at end of file
diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md
index 8e82b646..d9a4d3e7 100644
--- a/docs/PLUGINS.md
+++ b/docs/PLUGINS.md
@@ -12,7 +12,7 @@ next:
# Plugin Development Guide
-Brainy has a plugin system that allows third-party packages to replace internal subsystems with custom implementations. This is how `@soulcraft/cortex` provides optional native acceleration, and it's the same system available to any developer.
+Brainy has a plugin system that allows third-party packages to replace internal subsystems with custom implementations. This is how `@soulcraft/cor` provides optional native acceleration, and it's the same system available to any developer.
## Architecture Overview
@@ -23,20 +23,19 @@ Brainy's plugin system uses **named providers** — string keys mapped to implem
3. The plugin calls `context.registerProvider(key, implementation)` for each subsystem it provides
4. Brainy checks each provider key and wires the implementation into its internal pipeline
-Plugins are **opt-in** — brainy never auto-imports packages. You must explicitly list plugins in the config:
+Installing the first-party accelerator is the opt-in: with the default config, brainy probes for `@soulcraft/cor` and loads it when present. Everything except "not installed" fails **loud** — a present-but-broken accelerator makes `init()` throw rather than silently degrading to the JS engines.
```typescript
-const brain = new Brainy({
- plugins: ['@soulcraft/cortex'] // explicitly load the native acceleration package
-})
+const brain = new Brainy() // @soulcraft/cor auto-detected when installed
+const pinned = new Brainy({ plugins: ['@soulcraft/cor'] }) // or pin exactly what loads
+const plain = new Brainy({ plugins: [] }) // or opt out of detection entirely
```
| `plugins` value | Behavior |
|---|---|
-| `undefined` (default) | No plugins loaded |
-| `false` | No plugins loaded |
-| `[]` | No plugins loaded |
-| `['@soulcraft/cortex']` | Load only the listed packages |
+| `undefined` (default) | Guarded auto-detection of `@soulcraft/cor`: not installed → no plugins, silently; installed → loads + announces; installed-but-broken → `init()` throws |
+| `false` / `[]` | No plugins, no detection (explicit opt-out) |
+| `['@soulcraft/cor']` | Load only the listed packages; a listed plugin that fails to load throws |
Plugins registered programmatically via `brain.use(plugin)` are always activated regardless of the `plugins` config.
@@ -70,7 +69,7 @@ export default myPlugin
### 2. Package exports
-Your package must export the plugin as the default export so brainy's auto-detection works:
+Your package must export the plugin as the default export so brainy's plugin loader can resolve it:
```typescript
// index.ts
@@ -150,6 +149,17 @@ context.registerProvider('embedBatch', async (texts: string[]) => {
### Index Providers
+> **Write-path invariant (the change-feed contract).** Every canonical
+> mutation flows through Brainy's generation-store commit points — index
+> providers are invoked *inside* that commit and never originate canonical
+> writes of their own. The `brain.onChange` change feed is emitted from those
+> commit points and relies on this: **a plugin must never introduce a write
+> path that bypasses the generation-store commit.** If a future provider ever
+> needs a direct native ingest path, it must either route through the commit
+> or emit equivalent change events — otherwise every `onChange` consumer
+> (live UIs, cache invalidation, realtime sync) silently develops a blind
+> spot.
+
#### `vector`
**Type:** `(config: object, distanceFunction: Function, options: object) => VectorIndexProvider-compatible`
@@ -182,6 +192,27 @@ context.registerProvider('vector', (config, distanceFn, options) => {
})
```
+#### The readiness contract (all three index providers)
+
+A provider that **persists its derived index** should implement the optional readiness
+members so a warm reopen never pays a redundant rebuild-from-canonical:
+
+- **`init?(): Promise`** — eager cold-load. Brainy awaits it once during
+ `brain.init()`, after the metadata provider's `init()` (the id-mapper hydrates first)
+ and **before the rebuild gate**.
+- **`isReady?(): boolean`** — honest durability signal. `true` ⇔ the persisted index is
+ loaded (or cheaply demand-loadable) and consistent with what was last persisted. When
+ exposed, the rebuild gate defers to this signal **instead of** the `size() === 0` /
+ `totalEntries === 0` heuristics — a disk-native index may report 0 resident entries
+ while fully durable. Never return `true` if the durable state failed to load: the
+ signal is honest in both directions, and a not-ready provider gets its rebuild even
+ when `size() > 0`.
+- **`isMigrating?(): boolean`** — while `true`, the provider owns its index (background
+ migration); brainy skips its rebuild entirely.
+
+Providers that implement none of these keep the size/count heuristics — correct for
+engines whose `rebuild()` *is* their load path (like brainy's built-in JS vector index).
+
#### `metadataIndex`
**Type:** `(storage: StorageAdapter) => MetadataIndexManager-compatible`
@@ -215,7 +246,7 @@ context.registerProvider('aggregation', (storage) => {
})
```
-When provided by an optional native acceleration plugin (such as `@soulcraft/cortex`), this enables:
+When provided by an optional native acceleration plugin (such as `@soulcraft/cor`), this enables:
- Compiled source filters (vs per-entity JS object traversal)
- Precise MIN/MAX via sorted data structures (vs lazy recompute)
- Parallel aggregate rebuild across CPU cores
@@ -251,7 +282,7 @@ Native msgpack encode/decode for SSTable serialization.
### Analytics Providers (Native-Only)
-These provider keys have **no JavaScript fallback** — they represent capabilities that require native code (SIMD, mmap, sub-microsecond latency). They are available when an optional native acceleration plugin (such as `@soulcraft/cortex`) is installed.
+These provider keys have **no JavaScript fallback** — they represent capabilities that require native code (SIMD, mmap, sub-microsecond latency). They are available when an optional native acceleration plugin (such as `@soulcraft/cor`) is installed.
Use `brain.getProvider('analytics:hyperloglog')` to check availability. Returns `undefined` if no plugin provides it.
@@ -359,8 +390,8 @@ brainy diagnostics
When a plugin is active, brainy automatically logs a provider summary after `init()`:
```
-[brainy] Plugin activated: @soulcraft/cortex
-[brainy] Providers: 8/10 native (@soulcraft/cortex) | default: vector, cache
+[brainy] Plugin activated: @soulcraft/cor
+[brainy] Providers: 8/10 native (@soulcraft/cor) | default: vector, cache
```
This tells you at a glance how many subsystems are accelerated and which ones are falling back to JavaScript. The log respects `config.silent`.
@@ -381,7 +412,7 @@ If a required provider is missing, the error message tells you exactly what's wr
```
[brainy] Required providers using JS fallback: graphIndex.
-Active plugins: @soulcraft/cortex.
+Active plugins: @soulcraft/cor.
These providers must be supplied by a plugin for this deployment.
Check plugin installation, license, and native module availability.
```
diff --git a/docs/PRODUCTION_SERVICE_ARCHITECTURE.md b/docs/PRODUCTION_SERVICE_ARCHITECTURE.md
index a7e088f4..4568cd31 100644
--- a/docs/PRODUCTION_SERVICE_ARCHITECTURE.md
+++ b/docs/PRODUCTION_SERVICE_ARCHITECTURE.md
@@ -215,9 +215,9 @@ console.log('Server running on http://localhost:3000')
```
**Benefits:**
-- ✅ Native Bun performance (~2x faster than Node.js)
+- ✅ Native Bun runtime performance
- ✅ No framework dependencies
-- ✅ Works with `bun --compile` for single-binary deployment
+- ✅ Pure WASM — no native binaries, bundler-friendly
- ✅ Built-in TypeScript support
### Pattern 4: Express/Node.js Middleware (Legacy)
diff --git a/docs/QUERY_OPERATORS.md b/docs/QUERY_OPERATORS.md
index 16482de1..f4ac04e6 100644
--- a/docs/QUERY_OPERATORS.md
+++ b/docs/QUERY_OPERATORS.md
@@ -10,8 +10,8 @@ All operators work with `find({ where: { ... } })` and filter on **metadata fiel
| Operator | Alias | Description | Example |
|----------|-------|-------------|---------|
-| `equals` | `eq`, `is` | Exact match | `{ status: { equals: 'active' } }` |
-| `notEquals` | `ne`, `isNot` | Not equal | `{ status: { notEquals: 'deleted' } }` |
+| `eq` | `equals` | Exact match | `{ status: { eq: 'active' } }` |
+| `ne` | `notEquals` | Not equal | `{ status: { ne: 'deleted' } }` |
**Shorthand:** A bare value is treated as `equals`:
@@ -27,10 +27,10 @@ brain.find({ where: { status: { equals: 'active' } } })
| Operator | Alias | Description | Example |
|----------|-------|-------------|---------|
-| `greaterThan` | `gt` | Greater than | `{ age: { greaterThan: 18 } }` |
-| `greaterEqual` | `gte` | Greater or equal | `{ score: { greaterEqual: 90 } }` |
-| `lessThan` | `lt` | Less than | `{ price: { lessThan: 100 } }` |
-| `lessEqual` | `lte` | Less or equal | `{ rating: { lessEqual: 3 } }` |
+| `gt` | `greaterThan` | Greater than | `{ age: { gt: 18 } }` |
+| `gte` | `greaterThanOrEqual` | Greater or equal | `{ score: { gte: 90 } }` |
+| `lt` | `lessThan` | Less than | `{ price: { lt: 100 } }` |
+| `lte` | `lessThanOrEqual` | Less or equal | `{ rating: { lte: 3 } }` |
| `between` | — | Inclusive range `[min, max]` | `{ year: { between: [2020, 2025] } }` |
```typescript
@@ -160,9 +160,9 @@ Brainy's MetadataIndex supports a subset of operators natively for O(1) field lo
| `equals` / `eq` | Yes | Yes |
| `notEquals` / `ne` | — | Yes |
| `greaterThan` / `gt` | Yes | Yes |
-| `greaterEqual` / `gte` | Yes | Yes |
+| `greaterThanOrEqual` / `gte` | Yes | Yes |
| `lessThan` / `lt` | Yes | Yes |
-| `lessEqual` / `lte` | Yes | Yes |
+| `lessThanOrEqual` / `lte` | Yes | Yes |
| `between` | Yes | Yes |
| `oneOf` / `in` | Yes | Yes |
| `noneOf` | — | Yes |
@@ -234,7 +234,7 @@ const all = await brain.related({
})
// Graph traversal — subtype filters traversal edges (depth-1 in 7.30 JS path;
-// multi-hop subtype filtering lands on Cortex native)
+// multi-hop subtype filtering lands on Cor native)
const reports = await brain.find({
connected: {
from: ceoId,
diff --git a/docs/RELEASE-GUIDE.md b/docs/RELEASE-GUIDE.md
index ce7d32c5..94c6a6cb 100644
--- a/docs/RELEASE-GUIDE.md
+++ b/docs/RELEASE-GUIDE.md
@@ -119,4 +119,13 @@ gh release create vY.Y.Y --generate-notes
- **Most releases should be MINOR or PATCH**
- **Major versions should be RARE**
- **When in doubt, it's probably MINOR**
-- **NEVER use "BREAKING CHANGE" for internal changes**
\ No newline at end of file
+- **NEVER use "BREAKING CHANGE" for internal changes**
+## Hard Ordering Constraints (check before EVERY release)
+
+- **Embedding model changes are SEQUENCED, not free.** No release may change the
+ embedding model (or its quantization/dimensions) before **vector model-version
+ stamping + hard-error-on-mismatch** ships. Stored vectors carry no model version
+ today; mixing vectors from two models silently corrupts every similarity
+ comparison. If a model bump is ever proposed, the stamping work moves ahead of
+ it in the schedule — coordinate with the native provider so both engines stamp
+ and enforce identically. (Registered with the native-provider team 2026-07-07.)
diff --git a/docs/SCALING.md b/docs/SCALING.md
index c1e00987..e9ae1136 100644
--- a/docs/SCALING.md
+++ b/docs/SCALING.md
@@ -37,7 +37,7 @@ The three knobs that matter most:
1. **`config.vector.recall`** — `'fast'`, `'balanced'`, or `'accurate'` (default `'balanced'`)
2. **`config.vector.persistMode`** — `'immediate'` for durability, `'deferred'` for throughput
-The native vector provider (via the optional `@soulcraft/cortex` package) extends this with a higher-performing index — and its own at-scale acceleration such as on-disk compressed indexing — when installed.
+The native vector provider (via the optional `@soulcraft/cor` package) extends this with a higher-performing index — and its own at-scale acceleration such as on-disk compressed indexing — when installed.
## Measured Performance
@@ -77,7 +77,7 @@ graph, metadata index, and 100k edges).
**Scale ceiling (open-core).** The pure-JS HNSW *build* cost (~100 inserts/s at 384-dim on
one core) makes the in-process open-core path most appropriate up to ~10⁵–10⁶ entities.
*Query* latency stays low well beyond that, but for the 10⁸–10¹⁰ regime install the native
-provider (`@soulcraft/cortex`, on-disk DiskANN) — same API, no code change. _Projected from
+provider (`@soulcraft/cor`, on-disk DiskANN) — same API, no code change. _Projected from
the two measured points, vector p50 at 1M is ~2 ms; metadata-heavy composition grows with
match-set size and is the path to move onto the native provider first._
@@ -213,12 +213,12 @@ const stats = await brain.stats()
### Issue: Slow queries
1. Switch to `vector.recall: 'fast'`
2. Increase the read cache (`storage.cache.maxSize`)
-3. Consider the optional native vector provider via `@soulcraft/cortex`
+3. Consider the optional native vector provider via `@soulcraft/cor`
### Issue: Memory pressure
1. Reduce `storage.cache.maxSize`
2. Move to `vector.persistMode: 'deferred'` to batch writes
-3. Consider the optional native vector provider via `@soulcraft/cortex` for at-scale index acceleration
+3. Consider the optional native vector provider via `@soulcraft/cor` for at-scale index acceleration
### Issue: Slow startup after a crash
1. Use `vector.persistMode: 'immediate'` so the index file stays in sync with storage
diff --git a/docs/api/README.md b/docs/api/README.md
index 5af84dec..4ca84364 100644
--- a/docs/api/README.md
+++ b/docs/api/README.md
@@ -413,9 +413,9 @@ Brainy uses clean, readable operators (BFO — Brainy Field Operators):
| `equals` / `eq` | Exact match | `{age: {equals: 25}}` |
| `notEquals` / `ne` | Not equal | `{status: {notEquals: 'deleted'}}` |
| `greaterThan` / `gt` | Greater than | `{age: {greaterThan: 18}}` |
-| `greaterEqual` / `gte` | Greater or equal | `{score: {greaterEqual: 90}}` |
+| `gte` / `greaterThanOrEqual` | Greater or equal | `{score: {gte: 90}}` |
| `lessThan` / `lt` | Less than | `{price: {lessThan: 100}}` |
-| `lessEqual` / `lte` | Less or equal | `{rating: {lessEqual: 3}}` |
+| `lte` / `lessThanOrEqual` | Less or equal | `{rating: {lte: 3}}` |
| `between` | Inclusive range | `{year: {between: [2020, 2025]}}` |
| `oneOf` / `in` | In array | `{color: {oneOf: ['red', 'blue']}}` |
| `noneOf` | Not in array | `{status: {noneOf: ['deleted']}}` |
diff --git a/docs/architecture/aggregation.md b/docs/architecture/aggregation.md
index 10b55689..443ee727 100644
--- a/docs/architecture/aggregation.md
+++ b/docs/architecture/aggregation.md
@@ -33,7 +33,7 @@
│ ▼ │
│ ┌──────────────────────┐ │
│ │ AggregationProvider │ (optional, registered by │
-│ │ ├─ incrementalUpdate│ plugin like @soulcraft/cortex)│
+│ │ ├─ incrementalUpdate│ plugin like @soulcraft/cor) │
│ │ ├─ rebuildAggregate │ │
│ │ ├─ queryAggregate │ │
│ │ └─ serialize/restore│ │
@@ -135,7 +135,7 @@ M2 is clamped to zero on remove to prevent floating-point drift from producing n
The TypeScript engine uses simple comparison for add operations and marks MIN/MAX as potentially stale on delete (since removing the current min/max value requires a rescan). Stale values are lazily recomputed on the next query.
-The Cortex native engine uses a `BTreeMap, u64>` that tracks the exact frequency of every value, providing precise MIN/MAX after any sequence of operations without rescanning.
+The Cor native engine uses a `BTreeMap, u64>` that tracks the exact frequency of every value, providing precise MIN/MAX after any sequence of operations without rescanning.
### Time Window Bucketing
diff --git a/docs/architecture/data-storage-architecture.md b/docs/architecture/data-storage-architecture.md
index a51ebf05..48064757 100644
--- a/docs/architecture/data-storage-architecture.md
+++ b/docs/architecture/data-storage-architecture.md
@@ -369,7 +369,10 @@ await brain.restore('/backups/today', { confirm: true }) // replace store stat
through the metadata index, not the filesystem.
- **`_system/`** holds singletons plus 256 hash buckets of index state.
- **`_generations/` + `manifest.json` + `tx-log.jsonl`** implement
- generational MVCC; `transact()` is the unit of history.
+ generational MVCC. History is per-write: every `add()`/`update()`/`remove()`/
+ `relate()` gets its own generation, and `transact()` groups several ops into
+ one atomic generation. (Single-op retention has been the model since 8.0;
+ you never need to route a write through `transact()` just to keep its history.)
- **`_column_index/` + `_blobs/`** hold the columnar metadata runs and binary
blobs (VFS content included).
- **`locks/`** coordinates the single writer and reader flush requests, and
diff --git a/docs/architecture/index-architecture.md b/docs/architecture/index-architecture.md
index 993072e9..8b3dc540 100644
--- a/docs/architecture/index-architecture.md
+++ b/docs/architecture/index-architecture.md
@@ -183,7 +183,7 @@ async getIdsForMultipleFields(pairs: [{field, value}, ...]): Promise {
- Memory usage: **90% reduction** (17.17 MB → 2.01 MB for 100K entities)
- Hardware acceleration: SIMD instructions make bitmap operations nearly free
-**Benchmark Results** (1,000 queries on various dataset sizes):
+**Benchmark Results** — example output from a single run of `tests/performance/roaring-bitmap-benchmark.ts` (1,000 queries per size, one machine; absolute times vary by hardware, the relative speedup and memory savings are the durable signal):
| Dataset Size | Operation | Set Time | Roaring Time | Speedup | Memory Savings |
|--------------|-----------|----------|--------------|---------|----------------|
| 10,000 entities | 3-field intersection | 3.74ms | 1.14ms | **3.3x faster** | 90% |
@@ -402,7 +402,7 @@ const DEFAULT_EXCLUDE_FIELDS = [
**Purpose**: O(log n) semantic similarity search using vector embeddings.
-The default JS implementation is `JsHnswVectorIndex`; an optional native acceleration package (`@soulcraft/cortex`) can register a higher-performing `VectorIndexProvider` through the plugin system. The public API stays the same either way.
+The default JS implementation is `JsHnswVectorIndex`; an optional native acceleration package (`@soulcraft/cor`) can register a higher-performing `VectorIndexProvider` through the plugin system. The public API stays the same either way.
### Internal Architecture
@@ -578,16 +578,6 @@ const reachable = await this.graphIndex.traverse({
// Complexity: O(V + E) breadth-first search, but each neighbor lookup is O(1)
```
-## Notes on Other Indexes
-
-### DeletedItemsIndex (Not Currently Used)
-
-**Status**: Utility class exists in `src/utils/deletedItemsIndex.ts` but is **not instantiated** in the Brainy class.
-
-**Purpose** (if used): O(1) tracking of soft-deleted items without removing data.
-
-**Current Behavior**: Brainy uses hard deletes via `storage.deleteNoun()` and `storage.deleteVerb()`. Soft-delete functionality is not currently integrated.
-
## Shared Memory Management: UnifiedCache
All three main indexes share a single **UnifiedCache** instance for coordinated memory management.
@@ -684,9 +674,6 @@ async find(query: FindQuery): Promise {
results = results.filter(r => connectedIds.includes(r.id))
}
- // Step 4: Filter deleted items
- results = results.filter(r => !this.deletedItemsIndex.isDeleted(r.id))
-
return results
}
```
@@ -728,9 +715,6 @@ async stats(): Promise {
relationships: this.graphIndex.getTotalRelationshipCount(),
relationshipTypes: this.graphIndex.getRelationshipCountsByType(),
- // From deleted items index
- deletedItems: this.deletedItemsIndex.getDeletedCount(),
-
// From vector index
vectorIndexSize: this.index.getSize()
}
@@ -858,14 +842,15 @@ Where:
### Scalability
-All indexes scale gracefully:
+All indexes scale gracefully. The cost of each stage is governed by its algorithmic complexity, not a fixed millisecond figure — absolute latency depends on hardware, embedding model, and storage backend. Only the graph adjacency index carries a committed scale assertion:
-| Database Size | Metadata Filter | Vector Search | Graph Hop | Combined Query |
-|---------------|----------------|---------------|-----------|----------------|
-| **1K entities** | 0.3ms | 0.8ms | 0.05ms | 1.1ms |
-| **10K entities** | 0.5ms | 1.2ms | 0.08ms | 1.5ms |
-| **100K entities** | 0.8ms | 1.8ms | 0.1ms | 2.1ms |
-| **1M entities** | 1.2ms | 2.5ms | 0.1ms | 2.8ms |
+| Query stage | Complexity | Scaling behavior |
+|-------------|------------|------------------|
+| Metadata filter (exact) | O(1) | Constant — independent of dataset size |
+| Metadata filter (range) | O(log n) + O(k) | Sub-linear; k = matching results |
+| Vector search (HNSW) | O(log n) | Degrades gracefully via hierarchical layers |
+| Graph hop | O(1) | Measured <1 ms per neighbor lookup, validated up to 1M relationships (`tests/performance/graph-scale-performance.test.ts:238`) |
+| Combined query | O(log n) | Bounded by the vector stage; metadata and graph stages stay O(1)/O(log n) |
**Key observations**:
- Graph queries stay O(1) regardless of scale
diff --git a/docs/architecture/multiprocess-storage-mixin.md b/docs/architecture/multiprocess-storage-mixin.md
index 7b53fa33..46f98398 100644
--- a/docs/architecture/multiprocess-storage-mixin.md
+++ b/docs/architecture/multiprocess-storage-mixin.md
@@ -22,7 +22,7 @@ requestFlushOverFilesystem(timeoutMs)
They live on `BaseStorage` as no-op defaults and are overridden on
`FileSystemStorage` with real implementations. Any adapter extending
-`FileSystemStorage` (e.g. Cortex's `MmapFileSystemStorage`) inherits the
+`FileSystemStorage` (e.g. Cor's `MmapFileSystemStorage`) inherits the
real ones for free.
This works correctly today. The question is whether the methods *belong*
@@ -83,7 +83,7 @@ Benefits:
## The case against doing it now
- Breaking change for any adapter that already overrides these methods.
- `FileSystemStorage` is the only one in-tree, but Cortex's
+ `FileSystemStorage` is the only one in-tree, but Cor's
`MmapFileSystemStorage` inherits from it — interface relocation would
ripple through the plugin ecosystem.
- The current state works. The real failure modes seen in the field
diff --git a/docs/architecture/noun-verb-taxonomy.md b/docs/architecture/noun-verb-taxonomy.md
index d896016d..286464be 100644
--- a/docs/architecture/noun-verb-taxonomy.md
+++ b/docs/architecture/noun-verb-taxonomy.md
@@ -5,7 +5,7 @@ public: true
category: concepts
template: concept
order: 2
-description: 42 NounTypes and 127 VerbTypes cover 96-97% of all human knowledge. The universal vocabulary for structuring anything from people and documents to events and relationships.
+description: 42 NounTypes and 127 VerbTypes cover ~95% of all domains. The universal vocabulary for structuring anything from people and documents to events and relationships.
next:
- concepts/triple-intelligence
- api/reference
@@ -14,114 +14,141 @@ next:
# The Universal Knowledge Protocol: Noun-Verb Taxonomy
> **Brainy is the Universal Knowledge Protocol™ powered by Triple Intelligence™**
->
-> We're the world's first to unify vector, graph, and document search in one magical API. This breakthrough—Triple Intelligence—enables us to create a universal language for knowledge that all tools, augmentations, and AI models can speak.
+>
+> Brainy unifies vector, graph, and document search behind one API. That unification — Triple Intelligence — rests on a shared, standardized vocabulary for knowledge: a fixed set of entity types (nouns) and relationship types (verbs) that every tool, integration, and model can speak.
+
+Every example on this page is written against the real Brainy 8.0 API. The setup is always the same:
+
+```typescript
+import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
+
+const brain = new Brainy()
+await brain.init()
+```
+
+- `brain.add({ data, type, subtype?, metadata? })` creates a noun and returns its `string` id.
+- `brain.relate({ from, to, type, metadata? })` creates a verb (relationship) between two nouns.
+- `brain.find({ query?, type?, where?, connected? })` runs Triple Intelligence search.
+- `brain.related({ from })` / `brain.neighbors(id)` read a noun's relationships.
## Universal & Infinite Expressiveness
-Brainy's **Noun-Verb Taxonomy** achieves **universal coverage** of all human knowledge through **infinite expressiveness**:
+Brainy's **Noun-Verb Taxonomy** achieves broad coverage of human knowledge through composable expressiveness:
- **42 Noun Types × 127 Verb Types = 5,334 Base Combinations**
-- **Unlimited Metadata Fields = ∞ Domain Specificity**
-- **Multi-hop Graph Traversals = ∞ Relationship Complexity**
-- **Result: Can Model ANY Data in ANY Industry**
+- **Unlimited Metadata Fields = Domain Specificity**
+- **Multi-hop Graph Traversals = Relationship Complexity**
+- **Result: Model data across virtually any industry**
-This isn't marketing—it's mathematically provable. Every piece of information that exists can be represented as entities (nouns) connected by relationships (verbs) with properties (metadata).
+Every piece of information can be represented as entities (nouns) connected by relationships (verbs) carrying properties (metadata). The standardized type system from `@soulcraft/brainy` (`NounType`, `VerbType`) gives those nouns and verbs a stable, shared name.
## The Power of Standardization: Universal Interoperability
### Why Standardized Types = Seamless Integration
-The standardized noun-verb taxonomy creates a **universal language** that enables:
+Because every entity is classified with a `NounType` and every relationship with a `VerbType`, the same data is legible to any code that imports the same enums.
+
+#### 1. Tool Interoperability
-#### 1. **Tool Interoperability**
```typescript
-// Any tool that understands Brainy types can work with any other
-const analyticsAugmentation = await brain.augment('analytics')
-const visualizationAugmentation = await brain.augment('visualization')
+// Any tool that understands Brainy's NounType/VerbType can read the same graph.
+// One module writes; another reads — no schema translation in between.
+const authors = await brain.find({ type: NounType.Person })
-// Both understand "person", "document", "creates" without translation
-const authors = await analyticsAugmentation.findTopAuthors()
-await visualizationAugmentation.graphRelationships(authors)
+for (const author of authors) {
+ const authored = await brain.related({
+ from: author.id,
+ type: VerbType.Creates
+ })
+ console.log(`${author.data} → ${authored.length} document(s)`)
+}
```
-#### 2. **Data Portability**
+#### 2. Data Portability
+
```typescript
-// Snapshot from one Brainy instance, restore into another —
-// types are universally understood
+// Snapshot one brain and open it as another — the noun/verb vocabulary
+// travels with the data, so the types line up exactly.
const pin = brain1.now()
-await pin.persist('/snapshots/brain1')
-await pin.release()
+try {
+ await pin.persist('/snapshots/brain-1')
+} finally {
+ await pin.release()
+}
-const brain2 = await Brainy.load('/snapshots/brain1') // Types match perfectly
+const brain2 = await Brainy.load('/snapshots/brain-1') // same NounType/VerbType vocabulary
```
-#### 3. **AI Model Compatibility**
+#### 3. Model & Agent Compatibility
+
```typescript
-// Different AI models can share the same knowledge graph
-const gptBrain = await brain.connectModel('gpt-4')
-const claudeBrain = await brain.connectModel('claude-3')
-const llamaBrain = await brain.connectModel('llama-2')
-
-// All models understand the same noun-verb structure
-const knowledge = await brain.add("Quantum Computer", { type: "thing" })
-// Any model can now reason about this knowledge
-```
-
-#### 4. **Augmentation Ecosystem**
-```typescript
-// Augmentations build on standard types, ensuring compatibility
-await brain.augment.install('medical-records') // Extends "person" type
-await brain.augment.install('financial-analysis') // Extends "transaction" events
-await brain.augment.install('social-graph') // Uses "follows", "likes" verbs
-
-// All augmentations work together seamlessly
-const patient = await brain.find("patient with financial transactions who follows Dr. Smith")
-```
-
-#### 5. **Cross-Platform Integration**
-```typescript
-// Standard types enable integration with external systems
-// CRM understands "person" and "organization"
-await brain.sync.salesforce({
- mapping: {
- Contact: "person",
- Account: "organization",
- Opportunity: "event"
- }
+// Different models and agents reason over the SAME typed structure.
+// Whatever produced the entity, every consumer reads the same NounType.
+const conceptId = await brain.add({
+ data: 'Quantum Computer',
+ type: NounType.Thing,
+ subtype: 'computing-hardware'
})
-// Project management understands "task" and "project"
-await brain.sync.jira({
- mapping: {
- Issue: "task",
- Epic: "project",
- Sprint: "event"
- }
-})
+// Any downstream consumer can now retrieve and reason about this entity.
+const concept = await brain.get(conceptId)
+console.log(concept?.type) // 'thing'
+```
+
+#### 4. Extensibility Without Forking the Schema
+
+```typescript
+// Subtypes extend a standard NounType for a specific domain — no schema
+// migration, no new noun type. The base type stays universally understood.
+await brain.add({ data: 'Patient #12345', type: NounType.Person, subtype: 'patient' })
+await brain.add({ data: 'Invoice #4471', type: NounType.Document, subtype: 'invoice' })
+await brain.add({ data: 'Follower edge', type: NounType.Relationship, subtype: 'social-graph' })
+
+// Domains that share the base types interoperate even with custom subtypes.
+const patients = await brain.find({ type: NounType.Person, subtype: 'patient' })
+```
+
+#### 5. Cross-Platform Integration
+
+```typescript
+// Map external systems onto the standard taxonomy. A CRM's Contact/Account/
+// Opportunity become Person/Organization/Event — the same vocabulary everywhere.
+const externalRecords = [
+ { kind: 'Contact', name: 'Dana Lee', type: NounType.Person },
+ { kind: 'Account', name: 'Acme Corp', type: NounType.Organization },
+ { kind: 'Opportunity', name: 'Q3 Renewal', type: NounType.Event }
+]
+
+for (const record of externalRecords) {
+ await brain.add({
+ data: record.name,
+ type: record.type,
+ metadata: { source: 'crm', externalKind: record.kind }
+ })
+}
```
### The Network Effect: Brainy as the Universal Knowledge Protocol
-Like **HTTP** became the protocol for the web and **TCP/IP** for the internet, Brainy's noun-verb taxonomy is becoming the **Universal Knowledge Protocol**:
+Like **HTTP** became the protocol for the web and **TCP/IP** for the internet, Brainy's noun-verb taxonomy aims to be a **Universal Knowledge Protocol**:
-- **Learn Once**: Developers learn 42 nouns + 127 verbs, not 1000s of schemas
+- **Learn Once**: Developers learn 42 nouns + 127 verbs, not thousands of bespoke schemas
- **Build Anywhere**: Tools built for one domain work in others
- **Share Everything**: Knowledge graphs are universally shareable
-- **Compose Freely**: Augmentations compose without conflicts
+- **Compose Freely**: Subtypes and metadata extend types without schema migrations
-This isn't just a database—it's a **protocol for how humanity represents knowledge**.
+This isn't just a database — it's a **shared model for how knowledge is represented**.
## Overview
-Brainy 2.0 introduces a revolutionary **Noun-Verb Taxonomy** that models data as entities (nouns) and relationships (verbs), creating a semantic knowledge graph that mirrors how humans naturally think about information.
+Brainy's **Noun-Verb Taxonomy** models data as entities (nouns) and relationships (verbs), creating a semantic knowledge graph that mirrors how humans naturally think about information.
## Why Noun-Verb?
Traditional databases force you to think in tables, documents, or nodes. Brainy lets you think naturally:
- **Nouns**: Things that exist (people, documents, products, concepts)
-- **Verbs**: How things relate (creates, owns, references, similar-to)
+- **Verbs**: How things relate (creates, owns, references, related-to)
This simple mental model scales from basic storage to complex knowledge graphs while remaining intuitive.
@@ -129,70 +156,78 @@ This simple mental model scales from basic storage to complex knowledge graphs w
### Nouns (Entities)
-Nouns represent any entity in your system:
+Nouns represent any entity in your system. `add()` takes a single object and returns the new entity's id:
```typescript
// Add any entity as a noun
-const personId = await brain.add("John Smith, Senior Engineer", {
- type: "person",
- department: "engineering",
- skills: ["TypeScript", "React", "Node.js"]
+const personId = await brain.add({
+ data: 'John Smith, Senior Engineer',
+ type: NounType.Person,
+ subtype: 'employee',
+ metadata: { department: 'engineering', skills: ['TypeScript', 'React', 'Node.js'] }
})
-const documentId = await brain.add("Q3 2024 Financial Report", {
- type: "document",
- category: "financial",
- confidential: true,
- created: "2024-10-01"
+const documentId = await brain.add({
+ data: 'Q3 2024 Financial Report',
+ type: NounType.Document,
+ subtype: 'report',
+ metadata: { category: 'financial', confidential: true, created: '2024-10-01' }
})
-const conceptId = await brain.add("Machine Learning", {
- type: "concept",
- domain: "technology",
- complexity: "advanced"
+const conceptId = await brain.add({
+ data: 'Machine Learning',
+ type: NounType.Concept,
+ metadata: { domain: 'technology', complexity: 'advanced' }
})
```
#### Noun Properties
Every noun automatically gets:
-- **Unique ID**: System-generated or custom
-- **Vector Embedding**: For semantic similarity
-- **Metadata**: Flexible JSON properties
-- **Timestamps**: Created/updated tracking
-- **Indexing**: Automatic field indexing
+- **Unique ID**: System-generated UUID, or supply your own via `id`
+- **Vector Embedding**: `data` is embedded for semantic similarity
+- **Metadata**: Flexible, queryable JSON properties
+- **Timestamps**: `createdAt` / `updatedAt` tracking
+- **Indexing**: Automatic field indexing for `where` filters
### Verbs (Relationships)
-Verbs define how nouns relate to each other:
+Verbs define how nouns relate to each other. `relate()` also takes a single object:
```typescript
// Create relationships between entities
-await brain.relate(personId, documentId, "authored", {
- role: "primary_author",
- contribution: "80%"
+await brain.relate({
+ from: personId,
+ to: documentId,
+ type: VerbType.Creates,
+ metadata: { role: 'primary_author', contribution: '80%' }
})
-await brain.relate(documentId, conceptId, "discusses", {
- sections: ["methodology", "results"],
- depth: "detailed"
+await brain.relate({
+ from: documentId,
+ to: conceptId,
+ type: VerbType.Describes,
+ metadata: { sections: ['methodology', 'results'], depth: 'detailed' }
})
-await brain.relate(personId, conceptId, "expert_in", {
- years_experience: 5,
- certification: "Advanced ML Certification"
+await brain.relate({
+ from: personId,
+ to: conceptId,
+ type: VerbType.RelatedTo,
+ subtype: 'expertise',
+ metadata: { yearsExperience: 5, certification: 'Advanced ML Certification' }
})
```
#### Verb Properties
Every verb includes:
-- **Source**: The noun initiating the relationship
-- **Target**: The noun receiving the relationship
-- **Type**: The relationship type/name
-- **Direction**: Directional or bidirectional
-- **Metadata**: Relationship-specific data
-- **Strength**: Optional relationship weight
+- **Source** (`from`): The noun initiating the relationship
+- **Target** (`to`): The noun receiving the relationship
+- **Type**: The `VerbType` classification
+- **Subtype**: Optional per-product sub-classification (fast-path indexed)
+- **Metadata**: Relationship-specific queryable data
+- **Weight**: Optional relationship strength (0–1)
## Benefits
@@ -200,92 +235,104 @@ Every verb includes:
```typescript
// Think naturally about your data
-const taskId = await brain.add("Implement payment system")
-const userId = await brain.add("Alice Johnson")
-const projectId = await brain.add("E-commerce Platform")
+const taskId = await brain.add({ data: 'Implement payment system', type: NounType.Task })
+const userId = await brain.add({ data: 'Alice Johnson', type: NounType.Person })
+const projectId = await brain.add({ data: 'E-commerce Platform', type: NounType.Project })
// Express relationships clearly
-await brain.relate(userId, taskId, "assigned_to")
-await brain.relate(taskId, projectId, "part_of")
-await brain.relate(userId, projectId, "manages")
+await brain.relate({ from: userId, to: taskId, type: VerbType.ParticipatesIn, subtype: 'assignee' })
+await brain.relate({ from: taskId, to: projectId, type: VerbType.PartOf })
+await brain.relate({ from: userId, to: projectId, type: VerbType.ParticipatesIn, subtype: 'manager' })
```
### 2. Semantic Understanding
-The noun-verb model preserves meaning:
+The noun-verb model preserves meaning. `find()` accepts a natural-language string or a structured query:
```typescript
-// The system understands semantic relationships
-const results = await brain.find("tasks assigned to Alice")
-// Automatically understands: assigned_to verb + Alice noun
+// Natural language — embedded and matched semantically
+const results = await brain.find({ query: 'tasks for the payment system' })
-const related = await brain.find("people who manage projects with payment tasks")
-// Traverses: person -> manages -> project -> contains -> task
+// Structured — type + graph traversal in one call
+const aliceTasks = await brain.find({
+ type: NounType.Task,
+ connected: { from: userId, via: VerbType.ParticipatesIn }
+})
```
### 3. Flexible Schema
-No rigid schema requirements:
+No rigid schema requirements — add any type, extend with a `subtype`:
```typescript
// Add any noun type without schema changes
-await brain.add("New IoT Sensor", {
- type: "device",
- protocol: "MQTT",
- location: "Building A"
+const sensorId = await brain.add({
+ data: 'New IoT Sensor',
+ type: NounType.Thing,
+ subtype: 'iot-device',
+ metadata: { protocol: 'MQTT', location: 'Building A' }
})
-// Create new relationship types on the fly
-await brain.relate(sensorId, buildingId, "monitors", {
- metrics: ["temperature", "humidity"],
- interval: "5 minutes"
+const buildingId = await brain.add({ data: 'Building A', type: NounType.Location })
+
+// Relationships carry their own structured metadata
+await brain.relate({
+ from: sensorId,
+ to: buildingId,
+ type: VerbType.Measures,
+ metadata: { metrics: ['temperature', 'humidity'], interval: '5 minutes' }
})
```
### 4. Graph Traversal
-Navigate relationships naturally:
+Navigate relationships naturally with `connected`:
```typescript
-// Find all documents authored by team members
+// Find documents reachable from a team via two relationship hops
const teamDocs = await brain.find({
+ type: NounType.Document,
connected: {
from: teamId,
- through: ["member_of", "authored"],
+ via: [VerbType.MemberOf, VerbType.Creates],
depth: 2
}
})
-// Find similar products purchased by similar users
+// Find products two hops out from a user
const recommendations = await brain.find({
+ type: NounType.Product,
connected: {
from: userId,
- through: ["similar_to", "purchased"],
- depth: 2,
- type: "product"
+ via: VerbType.Owns,
+ depth: 2
}
})
```
### 5. Temporal Relationships
-Track how relationships change over time:
+Track how relationships change over time by storing dates in edge metadata:
```typescript
-// Relationships with temporal data
-await brain.relate(employeeId, companyId, "worked_at", {
- from: "2020-01-01",
- to: "2023-12-31",
- position: "Senior Developer"
+await brain.relate({
+ from: employeeId,
+ to: companyId,
+ type: VerbType.MemberOf,
+ subtype: 'past-employment',
+ metadata: { from: '2020-01-01', to: '2023-12-31', position: 'Senior Developer' }
})
-await brain.relate(employeeId, newCompanyId, "works_at", {
- from: "2024-01-01",
- position: "Tech Lead"
+await brain.relate({
+ from: employeeId,
+ to: newCompanyId,
+ type: VerbType.MemberOf,
+ subtype: 'current-employment',
+ metadata: { from: '2024-01-01', position: 'Tech Lead' }
})
-// Query historical relationships
-const employment = await brain.find("where did John work in 2022")
+// Query with natural language
+const employment = await brain.find({ query: 'where did this person work in 2022' })
```
## Real-World Use Cases
@@ -294,97 +341,108 @@ const employment = await brain.find("where did John work in 2022")
```typescript
// Documents and their relationships
-const paperId = await brain.add("Neural Networks Paper", {
- type: "research_paper",
- year: 2024
+const paperId = await brain.add({
+ data: 'Neural Networks Paper',
+ type: NounType.Document,
+ subtype: 'research-paper',
+ metadata: { year: 2024 }
})
-const authorId = await brain.add("Dr. Sarah Chen", {
- type: "researcher"
+const authorId = await brain.add({
+ data: 'Dr. Sarah Chen',
+ type: NounType.Person,
+ subtype: 'researcher'
})
-const topicId = await brain.add("Deep Learning", {
- type: "topic"
-})
+const topicId = await brain.add({ data: 'Deep Learning', type: NounType.Concept })
+const otherPaperId = await brain.add({ data: 'Backpropagation Survey', type: NounType.Document })
// Rich relationship network
-await brain.relate(authorId, paperId, "authored")
-await brain.relate(paperId, topicId, "covers")
-await brain.relate(paperId, otherPaperId, "cites")
-await brain.relate(authorId, topicId, "researches")
+await brain.relate({ from: authorId, to: paperId, type: VerbType.Creates })
+await brain.relate({ from: paperId, to: topicId, type: VerbType.Describes })
+await brain.relate({ from: paperId, to: otherPaperId, type: VerbType.References })
+await brain.relate({ from: authorId, to: topicId, type: VerbType.RelatedTo, subtype: 'research-focus' })
// Query the knowledge graph
-const related = await brain.find("papers about deep learning by Sarah Chen")
+const related = await brain.find({ query: 'papers about deep learning by Sarah Chen' })
```
### Social Networks
```typescript
// Users and connections
-const user1 = await brain.add("Alice", { type: "user" })
-const user2 = await brain.add("Bob", { type: "user" })
-const post = await brain.add("Great article on AI!", { type: "post" })
+const user1 = await brain.add({ data: 'Alice', type: NounType.Person })
+const user2 = await brain.add({ data: 'Bob', type: NounType.Person })
+const post = await brain.add({ data: 'Great article on AI!', type: NounType.Message, subtype: 'post' })
// Social interactions
-await brain.relate(user1, user2, "follows")
-await brain.relate(user2, user1, "follows") // Mutual
-await brain.relate(user1, post, "created")
-await brain.relate(user2, post, "liked")
-await brain.relate(user2, post, "shared")
+await brain.relate({ from: user1, to: user2, type: VerbType.Follows })
+await brain.relate({ from: user2, to: user1, type: VerbType.Follows }) // mutual
+await brain.relate({ from: user1, to: post, type: VerbType.Creates })
+await brain.relate({ from: user2, to: post, type: VerbType.Likes })
+await brain.relate({ from: user2, to: post, type: VerbType.Communicates, subtype: 'share' })
// Find social patterns
-const influencers = await brain.find("users with most followers who post about AI")
+const influencers = await brain.find({ query: 'people who post about AI with many followers' })
```
### E-commerce
```typescript
// Products and purchases
-const product = await brain.add("Wireless Headphones", {
- type: "product",
- price: 99.99,
- category: "electronics"
+const product = await brain.add({
+ data: 'Wireless Headphones',
+ type: NounType.Product,
+ metadata: { price: 99.99, category: 'electronics' }
})
-const customer = await brain.add("Customer #12345", {
- type: "customer",
- tier: "premium"
+const customer = await brain.add({
+ data: 'Customer #12345',
+ type: NounType.Person,
+ subtype: 'customer',
+ metadata: { tier: 'premium' }
})
-// Purchase relationships
-await brain.relate(customer, product, "purchased", {
- date: "2024-01-15",
- quantity: 1,
- price: 99.99
+// Purchase and review relationships
+await brain.relate({
+ from: customer,
+ to: product,
+ type: VerbType.Owns,
+ subtype: 'purchase',
+ metadata: { date: '2024-01-15', quantity: 1, price: 99.99 }
})
-await brain.relate(customer, product, "reviewed", {
- rating: 5,
- text: "Excellent sound quality!"
+await brain.relate({
+ from: customer,
+ to: product,
+ type: VerbType.Evaluates,
+ subtype: 'review',
+ metadata: { rating: 5, text: 'Excellent sound quality!' }
})
// Recommendation queries
-const recs = await brain.find("products purchased by customers who bought headphones")
+const recs = await brain.find({ query: 'products bought by customers who bought headphones' })
```
### Project Management
```typescript
// Projects, tasks, and teams
-const project = await brain.add("Website Redesign", { type: "project" })
-const task = await brain.add("Update homepage", { type: "task" })
-const developer = await brain.add("Jane Developer", { type: "person" })
-const designer = await brain.add("John Designer", { type: "person" })
+const project = await brain.add({ data: 'Website Redesign', type: NounType.Project })
+const task = await brain.add({ data: 'Update homepage', type: NounType.Task })
+const otherTask = await brain.add({ data: 'Design system audit', type: NounType.Task })
+const developer = await brain.add({ data: 'Jane Developer', type: NounType.Person, subtype: 'employee' })
+const designer = await brain.add({ data: 'John Designer', type: NounType.Person, subtype: 'employee' })
// Work relationships
-await brain.relate(task, project, "belongs_to")
-await brain.relate(developer, task, "assigned_to")
-await brain.relate(designer, developer, "collaborates_with")
-await brain.relate(task, otherTask, "depends_on")
+await brain.relate({ from: task, to: project, type: VerbType.PartOf })
+await brain.relate({ from: developer, to: task, type: VerbType.ParticipatesIn, subtype: 'assignee' })
+await brain.relate({ from: designer, to: developer, type: VerbType.WorksWith })
+await brain.relate({ from: task, to: otherTask, type: VerbType.DependsOn })
// Project queries
-const blockers = await brain.find("tasks that depend on incomplete tasks")
-const workload = await brain.find("people assigned to multiple active projects")
+const blockers = await brain.find({ query: 'tasks blocked by incomplete work' })
+const workload = await brain.find({ query: 'people assigned to multiple active projects' })
```
## Advanced Patterns
@@ -392,54 +450,56 @@ const workload = await brain.find("people assigned to multiple active projects")
### Bidirectional Relationships
```typescript
-// Some relationships are naturally bidirectional
-await brain.relate(user1, user2, "friend_of", { bidirectional: true })
-// Automatically creates inverse relationship
+// Symmetric relationships create the inverse edge automatically
+await brain.relate({ from: user1, to: user2, type: VerbType.FriendOf, bidirectional: true })
```
### Weighted Relationships
```typescript
-// Add strength/weight to relationships
-await brain.relate(doc1, doc2, "similar_to", {
- similarity_score: 0.95,
- algorithm: "cosine"
+// Add strength/weight to relationships (top-level weight, 0–1)
+await brain.relate({
+ from: doc1,
+ to: doc2,
+ type: VerbType.SimilarityDegree,
+ weight: 0.95,
+ metadata: { algorithm: 'cosine' }
})
-// Use weights in queries
-const stronglyRelated = await brain.find({
- connected: {
- type: "similar_to",
- minWeight: 0.8
- }
-})
+// Weights come back on the Relation, so you can filter on them
+const edges = await brain.related({ from: doc1, type: VerbType.SimilarityDegree })
+const stronglyRelated = edges.filter((edge) => (edge.weight ?? 0) >= 0.8)
```
-### Relationship Chains
+### Relationship Chains (Multi-hop)
```typescript
-// Follow relationship chains
+// Follow a chain of relationship types out to a fixed depth.
+// `via` accepts an array of VerbTypes; `depth` bounds the traversal.
const results = await brain.find({
+ type: NounType.Thing,
connected: {
from: userId,
- chain: [
- { type: "owns", to: "company" },
- { type: "produces", to: "product" },
- { type: "uses", to: "technology" }
- ]
+ via: [VerbType.Owns, VerbType.Creates, VerbType.Uses],
+ depth: 3
}
})
-// Finds: technologies used by products made by companies owned by user
+// Finds: things used by products made by companies owned by the user
```
### Meta-Relationships
+Relationships can themselves be reasoned about. The `Relationship` NounType reifies an edge as a first-class entity, and the meta-level verbs (`Endorses`, `Supports`, `Contradicts`, `Supersedes`) express second-order claims between entities:
+
```typescript
-// Relationships about relationships
-const verbId = await brain.relate(user1, user2, "recommends")
-await brain.relate(user3, verbId, "endorses", {
- reason: "Accurate recommendation",
- trust_score: 0.9
+// A second person endorses a claim, and a third supports it with evidence.
+const claim = await brain.add({ data: 'X improves retention', type: NounType.Proposition })
+await brain.relate({ from: user2, to: claim, type: VerbType.Endorses })
+await brain.relate({
+ from: user3,
+ to: claim,
+ type: VerbType.Supports,
+ metadata: { reason: 'Matches the A/B test', trustScore: 0.9 }
})
```
@@ -449,1121 +509,1048 @@ await brain.relate(user3, verbId, "endorses", {
```typescript
// By type
-const people = await brain.find({ where: { type: "person" } })
+const people = await brain.find({ type: NounType.Person })
-// By properties
+// By type + metadata filters (bare operators — no `$` prefixes)
const documents = await brain.find({
+ type: NounType.Document,
where: {
- type: "document",
confidential: false,
- created: { $gte: "2024-01-01" }
+ created: { gte: '2024-01-01' }
}
})
-// By similarity
+// By semantic similarity — use `query`, optionally narrowed by type
const similar = await brain.find({
- like: "machine learning research",
- where: { type: "document" }
+ query: 'machine learning research',
+ type: NounType.Document
})
```
-### Finding Verbs
+> **`where` operators** are bare (never dollar-prefixed): `eq`/`equals`/`is`, `ne`/`notEquals`, `in`/`oneOf`, `gt`/`greaterThan`, `gte`/`greaterThanOrEqual`, `lt`/`lessThan`, `lte`/`lessThanOrEqual`, `between`, `contains`, `exists`, `missing`, plus the logical combinators `allOf`/`anyOf`/`not`.
+
+### Finding Verbs (Relationships)
```typescript
-// Get all relationships for a noun
-const relationships = await brain.getVerbs(nounId)
+// All relationships originating from a noun
+const outgoing = await brain.related({ from: nounId })
-// Find specific relationship types
-const authorships = await brain.find({
- verb: {
- type: "authored",
- from: authorId
- }
-})
+// Every edge touching a noun, in either direction
+const incident = await brain.related({ node: nounId })
-// Find by relationship properties
-const recentPurchases = await brain.find({
- verb: {
- type: "purchased",
- where: {
- date: { $gte: "2024-01-01" }
- }
- }
-})
+// Filter by relationship type
+const authorships = await brain.related({ from: authorId, type: VerbType.Creates })
+
+// Filter returned relationships by their metadata (Relation carries `.metadata`)
+const purchases = await brain.related({ from: customerId, type: VerbType.Owns, subtype: 'purchase' })
+const recentPurchases = purchases.filter((edge) => edge.metadata?.date >= '2024-01-01')
+
+// Just the count of relationships in the graph
+const totalEdges = await brain.getVerbCount()
```
-### Combined Queries
+### Combined Queries (Query → Expand)
```typescript
-// Find nouns through relationships
+// Start from a semantic query, then expand along the graph.
+// Vector + graph in a single find() call.
const results = await brain.find({
- // Start with similar documents
- like: "AI research",
- // That are authored by
+ query: 'AI research',
connected: {
- through: "authored",
- // People who work at
- where: {
- connected: {
- to: "Stanford",
- type: "works_at"
- }
- }
+ via: VerbType.Creates,
+ depth: 2
}
})
```
-## Performance Optimizations
+## The Complete Noun Taxonomy (42 Types)
-### Noun Indexing
-- Automatic vector indexing for similarity
-- Field indexing for metadata queries
-- Full-text indexing for content search
+`NounType` is the stable, exported vocabulary for classifying entities. Every value is a plain string, so you can write `NounType.Person` or the literal `'person'`. Pick the closest standard type and refine with `subtype` and `metadata`.
-### Verb Indexing
-- Relationship type indexing
-- Source/target indexing
-- Temporal indexing for time-based queries
-
-### Query Optimization
-- Automatic query plan optimization
-- Parallel execution of independent operations
-- Result caching for repeated queries
-
-## Best Practices
-
-1. **Use Descriptive Types**: Make noun and verb types self-documenting
-2. **Rich Metadata**: Include relevant metadata for better querying
-3. **Consistent Naming**: Use consistent verb names across your application
-4. **Temporal Data**: Include timestamps for time-based analysis
-5. **Bidirectional When Appropriate**: Mark symmetric relationships as bidirectional
-
-## Migration from Traditional Models
-
-### From Relational (SQL)
```typescript
-// Instead of JOIN queries
-// SELECT * FROM users JOIN orders ON users.id = orders.user_id
-
-// Use noun-verb relationships
-const userId = await brain.add("User", userData)
-const orderId = await brain.add("Order", orderData)
-await brain.relate(userId, orderId, "placed")
-
-// Query naturally
-const userOrders = await brain.find({
- connected: { from: userId, type: "placed" }
+const physicistId = await brain.add({
+ data: 'Albert Einstein',
+ type: NounType.Person,
+ metadata: { role: 'physicist', born: '1879-03-14' }
})
```
-### From Document (NoSQL)
-```typescript
-// Instead of embedded documents
-// { user: { orders: [...] } }
-
-// Use explicit relationships
-const userId = await brain.add("User", userData)
-for (const order of orders) {
- const orderId = await brain.add("Order", order)
- await brain.relate(userId, orderId, "has_order")
-}
-```
+### Core Entity Types (7)
-### From Graph Databases
-```typescript
-// Similar to graph databases but with added benefits:
-// 1. Automatic vector embeddings for similarity
-// 2. Natural language querying
-// 3. Unified with metadata filtering
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Person` | `'person'` | Individual human entities |
+| `Organization` | `'organization'` | Companies, institutions, collectives |
+| `Location` | `'location'` | Geographic and named spatial entities |
+| `Thing` | `'thing'` | Discrete physical objects and artifacts |
+| `Concept` | `'concept'` | Abstract ideas, principles, intangibles |
+| `Event` | `'event'` | Temporal occurrences and happenings |
+| `Agent` | `'agent'` | Non-human autonomous actors (AI agents, bots) |
-// Enhanced graph queries
-const results = await brain.find("similar users who purchased similar products")
-```
+### Biological & Material Types (2)
-## Universal Knowledge Coverage
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Organism` | `'organism'` | Living biological entities (animals, plants, bacteria) |
+| `Substance` | `'substance'` | Physical materials and matter (water, iron, DNA) |
-The Noun-Verb taxonomy is designed to represent **all human knowledge** through a comprehensive set of types that can be combined infinitely.
+### Property, Temporal & Functional Types (3)
-### Complete Noun Types (31 Types)
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Quality` | `'quality'` | Properties and attributes that inhere in entities |
+| `TimeInterval` | `'timeInterval'` | Temporal regions, periods, durations |
+| `Function` | `'function'` | Purposes, capabilities, functional roles |
-#### Core Entity Types (6)
+### Informational Type (1)
-##### 1. **Person** - Individual human entities
-```typescript
-await brain.add("Albert Einstein", {
- type: "person",
- role: "physicist",
- born: "1879-03-14"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Proposition` | `'proposition'` | Statements, claims, assertions, declarative content |
-##### 2. **Organization** - Collective entities
-```typescript
-await brain.add("OpenAI", {
- type: "organization",
- industry: "AI research",
- founded: 2015
-})
-```
+### Digital/Content Types (4)
-##### 3. **Location** - Geographic and spatial entities
-```typescript
-await brain.add("San Francisco", {
- type: "location",
- category: "city",
- coordinates: [37.7749, -122.4194]
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Document` | `'document'` | Text-based files and written content |
+| `Media` | `'media'` | Non-text media (audio, video, images) |
+| `File` | `'file'` | Generic digital files and data blobs |
+| `Message` | `'message'` | Communication content and correspondence |
-##### 4. **Thing** - Physical objects
-```typescript
-await brain.add("Tesla Model 3", {
- type: "thing",
- category: "vehicle",
- manufacturer: "Tesla"
-})
-```
+### Collection Types (2)
-##### 5. **Concept** - Abstract ideas and intangibles
-```typescript
-await brain.add("Machine Learning", {
- type: "concept",
- domain: "technology",
- complexity: "advanced"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Collection` | `'collection'` | Groups and sets of items |
+| `Dataset` | `'dataset'` | Structured data collections and databases |
-##### 6. **Event** - Temporal occurrences
-```typescript
-await brain.add("Product Launch 2024", {
- type: "event",
- date: "2024-09-15",
- attendees: 500
-})
-```
+### Business/Application Types (4)
-#### Digital/Content Types (5)
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Product` | `'product'` | Commercial products and offerings |
+| `Service` | `'service'` | Service offerings and intangible products |
+| `Task` | `'task'` | Actions, todos, work items |
+| `Project` | `'project'` | Organized initiatives and programs |
-##### 7. **Document** - Text-based files
-```typescript
-await brain.add("Quarterly Report", {
- type: "document",
- format: "PDF",
- pages: 47
-})
-```
+### Descriptive Types (6)
-##### 8. **Media** - Non-text media files
-```typescript
-await brain.add("Product Demo Video", {
- type: "media",
- format: "MP4",
- duration: "5:30"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Process` | `'process'` | Workflows, procedures, ongoing activities |
+| `State` | `'state'` | Conditions, status, situational contexts |
+| `Role` | `'role'` | Positions, responsibilities, classifications |
+| `Language` | `'language'` | Natural and formal languages |
+| `Currency` | `'currency'` | Monetary units and exchange mediums |
+| `Measurement` | `'measurement'` | Metrics, quantities, measured values |
-##### 9. **File** - Generic digital files
-```typescript
-await brain.add("config.json", {
- type: "file",
- size: "2KB",
- modified: Date.now()
-})
-```
+### Scientific & Legal Types (4)
-##### 10. **Message** - Communication content
-```typescript
-await brain.add("Support ticket #1234", {
- type: "message",
- priority: "high",
- channel: "email"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Hypothesis` | `'hypothesis'` | Scientific theories, research hypotheses |
+| `Experiment` | `'experiment'` | Controlled studies, trials, methodologies |
+| `Contract` | `'contract'` | Legal agreements, terms, binding documents |
+| `Regulation` | `'regulation'` | Laws, rules, compliance requirements |
-##### 11. **Content** - Generic content
-```typescript
-await brain.add("Landing page copy", {
- type: "content",
- category: "marketing",
- language: "en"
-})
-```
-
-#### Collection Types (2)
+### Technical Infrastructure Types (2)
-##### 12. **Collection** - Groups of items
-```typescript
-await brain.add("Premium Features", {
- type: "collection",
- items: 25,
- category: "features"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Interface` | `'interface'` | APIs, protocols, specifications, endpoints |
+| `Resource` | `'resource'` | Compute, bandwidth, storage, infrastructure assets |
-##### 13. **Dataset** - Structured data collections
-```typescript
-await brain.add("Customer Analytics", {
- type: "dataset",
- records: 10000,
- schema: "v2"
-})
-```
+### Social Structure Types (3)
-#### Business/Application Types (5)
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `SocialGroup` | `'socialGroup'` | Informal social groups and collectives |
+| `Institution` | `'institution'` | Formal social structures and practices |
+| `Norm` | `'norm'` | Social norms, conventions, expectations |
-##### 14. **Product** - Commercial offerings
-```typescript
-await brain.add("Pro Subscription", {
- type: "product",
- price: 99.99,
- tier: "premium"
-})
-```
+### Information Theory Types (2)
-##### 15. **Service** - Service offerings
-```typescript
-await brain.add("Cloud Hosting", {
- type: "service",
- sla: "99.9%",
- region: "us-west"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `InformationContent` | `'informationContent'` | Abstract information (stories, ideas, schemas) |
+| `InformationBearer` | `'informationBearer'` | Physical or digital carrier of information |
-##### 16. **User** - User accounts
-```typescript
-await brain.add("user@example.com", {
- type: "user",
- tier: "enterprise",
- created: Date.now()
-})
-```
+### Meta-Level & Extensible Types (2)
-##### 17. **Task** - Actions and todos
-```typescript
-await brain.add("Deploy v2.0", {
- type: "task",
- priority: "high",
- assignee: "devops"
-})
-```
+| NounType | Value | Use for |
+|----------|-------|---------|
+| `Relationship` | `'relationship'` | Relationships reified as first-class entities |
+| `Custom` | `'custom'` | Domain-specific entities outside the standard set |
-##### 18. **Project** - Organized initiatives
-```typescript
-await brain.add("Website Redesign", {
- type: "project",
- deadline: "2024-12-31",
- status: "active"
-})
-```
+## The Complete Verb Taxonomy (127 Types)
-#### Descriptive Types (7)
+`VerbType` is the exported vocabulary for classifying relationships. As with nouns, every value is a plain string — write `VerbType.Creates` or `'creates'`. Where no verb is an exact fit, choose the closest base verb and refine it with `subtype` and `metadata` (see [Coverage Completeness](#coverage-completeness-analysis)).
-##### 19. **Process** - Workflows and procedures
```typescript
-await brain.add("CI/CD Pipeline", {
- type: "process",
- steps: 7,
- automated: true
-})
+await brain.relate({ from: authorId, to: documentId, type: VerbType.Creates })
```
-##### 20. **State** - Conditions or status
-```typescript
-await brain.add("System Health", {
- type: "state",
- status: "operational",
- uptime: "99.99%"
-})
-```
+### Foundational Ontological (3)
-##### 21. **Role** - Positions or responsibilities
-```typescript
-await brain.add("Admin Role", {
- type: "role",
- permissions: ["read", "write", "delete"],
- level: "superuser"
-})
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `InstanceOf` | `'instanceOf'` | Individual to class (Fido instanceOf Dog) |
+| `SubclassOf` | `'subclassOf'` | Taxonomic hierarchy (Dog subclassOf Mammal) |
+| `ParticipatesIn` | `'participatesIn'` | Entity participation in events/processes |
-##### 22. **Topic** - Subjects or themes
-```typescript
-await brain.add("Machine Learning", {
- type: "topic",
- field: "AI",
- popularity: "high"
-})
-```
+### Core Relationships (4)
-##### 23. **Language** - Languages or linguistic entities
-```typescript
-await brain.add("English", {
- type: "language",
- iso_code: "en",
- speakers_millions: 1500
-})
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `RelatedTo` | `'relatedTo'` | Generic relationship (fallback) |
+| `Contains` | `'contains'` | Containment relationship |
+| `PartOf` | `'partOf'` | Part-whole (mereological) relationship |
+| `References` | `'references'` | Citation and referential relationship |
-##### 24. **Currency** - Monetary units
-```typescript
-await brain.add("US Dollar", {
- type: "currency",
- symbol: "$",
- code: "USD"
-})
-```
-
-##### 25. **Measurement** - Metrics or quantities
-```typescript
-await brain.add("Temperature Reading", {
- type: "measurement",
- value: 23.5,
- unit: "celsius"
-})
-```
-
-#### Scientific/Research Types (2)
-
-##### 26. **Hypothesis** - Scientific theories and propositions
-```typescript
-await brain.add("String Theory", {
- type: "hypothesis",
- field: "physics",
- status: "unproven"
-})
-```
-
-##### 27. **Experiment** - Studies and research trials
-```typescript
-await brain.add("Clinical Trial XYZ", {
- type: "experiment",
- phase: 3,
- participants: 1000
-})
-```
-
-#### Legal/Regulatory Types (2)
-
-##### 28. **Contract** - Legal agreements and terms
-```typescript
-await brain.add("Service Agreement", {
- type: "contract",
- duration: "2 years",
- value: 100000
-})
-```
-
-##### 29. **Regulation** - Laws and compliance requirements
-```typescript
-await brain.add("GDPR", {
- type: "regulation",
- jurisdiction: "EU",
- category: "data protection"
-})
-```
-
-#### Technical Infrastructure Types (2)
-
-##### 30. **Interface** - APIs and protocols
-```typescript
-await brain.add("REST API", {
- type: "interface",
- version: "v2",
- endpoints: 45
-})
-```
-
-##### 31. **Resource** - Infrastructure and compute assets
-```typescript
-await brain.add("Database Server", {
- type: "resource",
- capacity: "1TB",
- availability: "99.9%"
-})
-```
-
-### Complete Verb Types (40 Types)
-
-#### Core Relationship Types (5)
-
-##### 1. **RelatedTo** - Generic relationship (default)
-```typescript
-await brain.relate(entityA, entityB, "relatedTo")
-```
+### Spatial (2)
-##### 2. **Contains** - Containment relationship
-```typescript
-await brain.relate(folderId, fileId, "contains")
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `LocatedAt` | `'locatedAt'` | Spatial location relationship |
+| `AdjacentTo` | `'adjacentTo'` | Spatial proximity relationship |
-##### 3. **PartOf** - Part-whole relationship
-```typescript
-await brain.relate(componentId, systemId, "partOf")
-```
+### Temporal (3)
-##### 4. **LocatedAt** - Spatial relationship
-```typescript
-await brain.relate(deviceId, locationId, "locatedAt")
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Precedes` | `'precedes'` | Temporal sequence (before) |
+| `During` | `'during'` | Temporal containment |
+| `OccursAt` | `'occursAt'` | Temporal location |
-##### 5. **References** - Citation relationship
-```typescript
-await brain.relate(paperId, sourceId, "references")
-```
+### Causal & Dependency (5)
-#### Temporal/Causal Types (5)
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Causes` | `'causes'` | Direct causal relationship |
+| `Enables` | `'enables'` | Enablement without direct causation |
+| `Prevents` | `'prevents'` | Prevention relationship |
+| `DependsOn` | `'dependsOn'` | Dependency relationship |
+| `Requires` | `'requires'` | Necessity relationship |
+
+### Creation & Transformation (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Creates` | `'creates'` | Creation relationship |
+| `Transforms` | `'transforms'` | Transformation relationship |
+| `Becomes` | `'becomes'` | State change relationship |
+| `Modifies` | `'modifies'` | Modification relationship |
+| `Consumes` | `'consumes'` | Consumption relationship |
-##### 6. **Precedes** - Temporal sequence (before)
-```typescript
-await brain.relate(event1Id, event2Id, "precedes")
-```
+### Lifecycle (1)
-##### 7. **Succeeds** - Temporal sequence (after)
-```typescript
-await brain.relate(event2Id, event1Id, "succeeds")
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Destroys` | `'destroys'` | Termination and destruction relationship |
+
+### Ownership & Attribution (2)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Owns` | `'owns'` | Ownership relationship |
+| `AttributedTo` | `'attributedTo'` | Attribution relationship |
-##### 8. **Causes** - Causal relationship
-```typescript
-await brain.relate(actionId, effectId, "causes")
-```
+### Property & Quality (2)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `HasQuality` | `'hasQuality'` | Entity to quality attribution |
+| `Realizes` | `'realizes'` | Function realization relationship |
+
+### Effects & Experience (1)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Affects` | `'affects'` | Patient/experiencer relationship |
-##### 9. **DependsOn** - Dependency relationship
-```typescript
-await brain.relate(moduleId, libraryId, "dependsOn")
-```
+### Composition (2)
-##### 10. **Requires** - Necessity relationship
-```typescript
-await brain.relate(taskId, resourceId, "requires")
-```
-
-#### Creation/Transformation Types (5)
-
-##### 11. **Creates** - Creation relationship
-```typescript
-await brain.relate(authorId, documentId, "creates")
-```
-
-##### 12. **Transforms** - Transformation relationship
-```typescript
-await brain.relate(processId, dataId, "transforms")
-```
-
-##### 13. **Becomes** - State change relationship
-```typescript
-await brain.relate(caterpillarId, butterflyId, "becomes")
-```
-
-##### 14. **Modifies** - Modification relationship
-```typescript
-await brain.relate(editorId, fileId, "modifies")
-```
-
-##### 15. **Consumes** - Consumption relationship
-```typescript
-await brain.relate(processId, resourceId, "consumes")
-```
-
-#### Ownership/Attribution Types (4)
-
-##### 16. **Owns** - Ownership relationship
-```typescript
-await brain.relate(userId, assetId, "owns")
-```
-
-##### 17. **AttributedTo** - Attribution relationship
-```typescript
-await brain.relate(quoteId, authorId, "attributedTo")
-```
-
-##### 18. **CreatedBy** - Creation attribution
-```typescript
-await brain.relate(productId, teamId, "createdBy")
-```
-
-##### 19. **BelongsTo** - Belonging relationship
-```typescript
-await brain.relate(itemId, collectionId, "belongsTo")
-```
-
-#### Social/Organizational Types (9)
-
-##### 20. **MemberOf** - Membership relationship
-```typescript
-await brain.relate(userId, organizationId, "memberOf")
-```
-
-##### 21. **WorksWith** - Professional relationship
-```typescript
-await brain.relate(employee1Id, employee2Id, "worksWith")
-```
-
-##### 22. **FriendOf** - Friendship relationship
-```typescript
-await brain.relate(user1Id, user2Id, "friendOf")
-```
-
-##### 23. **Follows** - Following relationship
-```typescript
-await brain.relate(followerId, influencerId, "follows")
-```
-
-##### 24. **Likes** - Liking relationship
-```typescript
-await brain.relate(userId, postId, "likes")
-```
-
-##### 25. **ReportsTo** - Reporting relationship
-```typescript
-await brain.relate(employeeId, managerId, "reportsTo")
-```
-
-##### 26. **Supervises** - Supervisory relationship
-```typescript
-await brain.relate(managerId, employeeId, "supervises")
-```
-
-##### 27. **Mentors** - Mentorship relationship
-```typescript
-await brain.relate(seniorId, juniorId, "mentors")
-```
-
-##### 28. **Communicates** - Communication relationship
-```typescript
-await brain.relate(sender, receiver, "communicates")
-```
-
-#### Descriptive/Functional Types (8)
-
-##### 29. **Describes** - Descriptive relationship
-```typescript
-await brain.relate(documentId, conceptId, "describes")
-```
-
-##### 30. **Defines** - Definition relationship
-```typescript
-await brain.relate(glossaryId, termId, "defines")
-```
-
-##### 31. **Categorizes** - Categorization relationship
-```typescript
-await brain.relate(taxonomyId, itemId, "categorizes")
-```
-
-##### 32. **Measures** - Measurement relationship
-```typescript
-await brain.relate(sensorId, metricId, "measures")
-```
-
-##### 33. **Evaluates** - Evaluation relationship
-```typescript
-await brain.relate(reviewerId, proposalId, "evaluates")
-```
-
-##### 34. **Uses** - Utilization relationship
-```typescript
-await brain.relate(applicationId, libraryId, "uses")
-```
-
-##### 35. **Implements** - Implementation relationship
-```typescript
-await brain.relate(classId, interfaceId, "implements")
-```
-
-##### 36. **Extends** - Extension relationship
-```typescript
-await brain.relate(childClassId, parentClassId, "extends")
-```
-
-#### Enhanced Relationships (4)
-
-##### 37. **Inherits** - Inheritance relationship
-```typescript
-await brain.relate(childId, parentId, "inherits")
-```
-
-##### 38. **Conflicts** - Conflict relationship
-```typescript
-await brain.relate(policy1Id, policy2Id, "conflicts")
-```
-
-##### 39. **Synchronizes** - Synchronization relationship
-```typescript
-await brain.relate(service1Id, service2Id, "synchronizes")
-```
-
-##### 40. **Competes** - Competition relationship
-```typescript
-await brain.relate(company1Id, company2Id, "competes")
-```
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `ComposedOf` | `'composedOf'` | Material composition (distinct from partOf) |
+| `Inherits` | `'inherits'` | Inheritance relationship |
+
+### Social & Organizational (8)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `MemberOf` | `'memberOf'` | Membership relationship |
+| `WorksWith` | `'worksWith'` | Professional collaboration |
+| `FriendOf` | `'friendOf'` | Friendship relationship |
+| `Follows` | `'follows'` | Following/subscription relationship |
+| `Likes` | `'likes'` | Liking/favoriting relationship |
+| `ReportsTo` | `'reportsTo'` | Hierarchical reporting relationship |
+| `Mentors` | `'mentors'` | Mentorship relationship |
+| `Communicates` | `'communicates'` | Communication relationship |
+
+### Descriptive & Functional (8)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Describes` | `'describes'` | Descriptive relationship |
+| `Defines` | `'defines'` | Definition relationship |
+| `Categorizes` | `'categorizes'` | Categorization relationship |
+| `Measures` | `'measures'` | Measurement relationship |
+| `Evaluates` | `'evaluates'` | Evaluation relationship |
+| `Uses` | `'uses'` | Utilization relationship |
+| `Implements` | `'implements'` | Implementation relationship |
+| `Extends` | `'extends'` | Extension relationship |
+
+### Advanced Relationships (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `EquivalentTo` | `'equivalentTo'` | Equivalence/identity relationship |
+| `Believes` | `'believes'` | Epistemic relationship (cognitive state) |
+| `Conflicts` | `'conflicts'` | Conflict relationship |
+| `Synchronizes` | `'synchronizes'` | Synchronization relationship |
+| `Competes` | `'competes'` | Competition relationship |
+
+### Modal (6)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `CanCause` | `'canCause'` | Potential causation (possibility) |
+| `MustCause` | `'mustCause'` | Necessary causation (necessity) |
+| `WouldCauseIf` | `'wouldCauseIf'` | Counterfactual causation |
+| `CouldBe` | `'couldBe'` | Possible states |
+| `MustBe` | `'mustBe'` | Necessary identity |
+| `Counterfactual` | `'counterfactual'` | General counterfactual relationship |
+
+### Epistemic States (9)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Knows` | `'knows'` | Knowledge (justified true belief) |
+| `Doubts` | `'doubts'` | Uncertainty/skepticism |
+| `Desires` | `'desires'` | Want/preference |
+| `Intends` | `'intends'` | Intentionality |
+| `Fears` | `'fears'` | Fear/anxiety |
+| `Loves` | `'loves'` | Strong positive emotional attitude |
+| `Hates` | `'hates'` | Strong negative emotional attitude |
+| `Hopes` | `'hopes'` | Hopeful expectation |
+| `Perceives` | `'perceives'` | Sensory perception |
+
+### Learning & Cognition (1)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Learns` | `'learns'` | Cognitive acquisition and learning |
+
+### Uncertainty & Probability (4)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `ProbablyCauses` | `'probablyCauses'` | Probabilistic causation |
+| `UncertainRelation` | `'uncertainRelation'` | Unknown relationship with confidence bounds |
+| `CorrelatesWith` | `'correlatesWith'` | Statistical correlation (not causation) |
+| `ApproximatelyEquals` | `'approximatelyEquals'` | Fuzzy equivalence |
+
+### Scalar Properties (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `GreaterThan` | `'greaterThan'` | Scalar comparison |
+| `SimilarityDegree` | `'similarityDegree'` | Graded similarity |
+| `MoreXThan` | `'moreXThan'` | Comparative property |
+| `HasDegree` | `'hasDegree'` | Scalar property assignment |
+| `PartiallyHas` | `'partiallyHas'` | Graded possession |
+
+### Information Theory (2)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Carries` | `'carries'` | Bearer carries content |
+| `Encodes` | `'encodes'` | Encoding relationship |
+
+### Deontic (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `ObligatedTo` | `'obligatedTo'` | Moral/legal obligation |
+| `PermittedTo` | `'permittedTo'` | Permission/authorization |
+| `ProhibitedFrom` | `'prohibitedFrom'` | Prohibition/forbidden |
+| `ShouldDo` | `'shouldDo'` | Normative expectation |
+| `MustNotDo` | `'mustNotDo'` | Strong prohibition |
+
+### Context & Perspective (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `TrueInContext` | `'trueInContext'` | Context-dependent truth |
+| `PerceivedAs` | `'perceivedAs'` | Subjective perception |
+| `InterpretedAs` | `'interpretedAs'` | Interpretation relationship |
+| `ValidInFrame` | `'validInFrame'` | Frame-dependent validity |
+| `TrueFrom` | `'trueFrom'` | Perspective-dependent truth |
+
+### Advanced Temporal (6)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Overlaps` | `'overlaps'` | Partial temporal overlap |
+| `ImmediatelyAfter` | `'immediatelyAfter'` | Direct temporal succession |
+| `EventuallyLeadsTo` | `'eventuallyLeadsTo'` | Long-term consequence |
+| `SimultaneousWith` | `'simultaneousWith'` | Exact temporal alignment |
+| `HasDuration` | `'hasDuration'` | Temporal extent |
+| `RecurringWith` | `'recurringWith'` | Cyclic temporal relationship |
+
+### Advanced Spatial (9)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `ContainsSpatially` | `'containsSpatially'` | Spatial containment |
+| `OverlapsSpatially` | `'overlapsSpatially'` | Spatial overlap |
+| `Surrounds` | `'surrounds'` | Encirclement |
+| `ConnectedTo` | `'connectedTo'` | Topological connection |
+| `Above` | `'above'` | Vertical (superior position) |
+| `Below` | `'below'` | Vertical (inferior position) |
+| `Inside` | `'inside'` | Within containment boundaries |
+| `Outside` | `'outside'` | Beyond containment boundaries |
+| `Facing` | `'facing'` | Directional orientation |
+
+### Social Structures (5)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Represents` | `'represents'` | Representative relationship |
+| `Embodies` | `'embodies'` | Exemplification or personification |
+| `Opposes` | `'opposes'` | Opposition relationship |
+| `AlliesWith` | `'alliesWith'` | Alliance relationship |
+| `ConformsTo` | `'conformsTo'` | Norm conformity |
+
+### Measurement (4)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `MeasuredIn` | `'measuredIn'` | Unit relationship |
+| `ConvertsTo` | `'convertsTo'` | Unit conversion |
+| `HasMagnitude` | `'hasMagnitude'` | Quantitative value |
+| `DimensionallyEquals` | `'dimensionallyEquals'` | Dimensional analysis |
+
+### Change & Persistence (4)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `PersistsThrough` | `'persistsThrough'` | Persistence through change |
+| `GainsProperty` | `'gainsProperty'` | Property acquisition |
+| `LosesProperty` | `'losesProperty'` | Property loss |
+| `RemainsSame` | `'remainsSame'` | Identity through time |
+
+### Parthood Variations (4)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `FunctionalPartOf` | `'functionalPartOf'` | Functional component |
+| `TopologicalPartOf` | `'topologicalPartOf'` | Spatial part |
+| `TemporalPartOf` | `'temporalPartOf'` | Temporal slice |
+| `ConceptualPartOf` | `'conceptualPartOf'` | Abstract decomposition |
+
+### Dependency Variations (3)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `RigidlyDependsOn` | `'rigidlyDependsOn'` | Necessary dependency |
+| `FunctionallyDependsOn` | `'functionallyDependsOn'` | Operational dependency |
+| `HistoricallyDependsOn` | `'historicallyDependsOn'` | Causal history dependency |
+
+### Meta-Level (4)
+
+| VerbType | Value | Meaning |
+|----------|-------|---------|
+| `Endorses` | `'endorses'` | Second-order validation |
+| `Contradicts` | `'contradicts'` | Logical contradiction |
+| `Supports` | `'supports'` | Evidential support |
+| `Supersedes` | `'supersedes'` | Replacement relationship |
## Coverage Completeness Analysis
-### Is Anything Missing?
+### Is Anything Missing?
-While we could add more specific verb types (like "approves", "delegates", "shares"), our current taxonomy is **mathematically complete** for several reasons:
+The taxonomy is intentionally bounded. When no type is an exact fit, three escape hatches keep it complete:
#### 1. Generic Fallbacks
-- **`Custom` noun type**: For any entity that doesn't fit standard types
-- **`RelatedTo` verb type**: For any relationship not explicitly defined
-- **Unlimited metadata**: Any additional semantics via properties
-#### 2. Semantic Flexibility Through Metadata
+- **`Custom` noun type**: For any entity that doesn't fit a standard type
+- **`RelatedTo` verb type**: For any relationship not explicitly named
+- **`subtype` + metadata**: Refine a base type with domain-specific semantics
-Instead of adding dozens more verb types, we use metadata for specificity:
+#### 2. Semantic Flexibility Through Subtype & Metadata
+
+Instead of adding ever more verb types, refine a base verb with a `subtype` and structured metadata:
```typescript
-// Instead of adding "approves" verb:
-await brain.relate(managerId, requestId, "evaluates", {
- result: "approved",
- timestamp: Date.now()
+// "approves" → Evaluates with a result
+await brain.relate({
+ from: managerId,
+ to: requestId,
+ type: VerbType.Evaluates,
+ subtype: 'approval',
+ metadata: { result: 'approved', timestamp: Date.now() }
})
-// Instead of adding "shares" verb:
-await brain.relate(userId, documentId, "communicates", {
- action: "shared",
- permissions: "read-only"
+// "shares" → Communicates with an action
+await brain.relate({
+ from: userId,
+ to: documentId,
+ type: VerbType.Communicates,
+ subtype: 'share',
+ metadata: { permissions: 'read-only' }
})
-// Instead of adding "delegates" verb:
-await brain.relate(managerId, taskId, "creates", {
- delegatedTo: employeeId,
- authority: "full"
+// "delegates" → ParticipatesIn with a role + delegation metadata
+await brain.relate({
+ from: employeeId,
+ to: taskId,
+ type: VerbType.ParticipatesIn,
+ subtype: 'delegate',
+ metadata: { delegatedBy: managerId, authority: 'full' }
})
```
#### 3. Edge Cases Are Covered
-Even exotic scenarios work with our current types:
+Even exotic scenarios fit the standard types:
```typescript
// Quantum computing
-const qubitId = await brain.add("Qubit-1", {
- type: "thing",
- subtype: "quantum_bit",
- superposition: [0.707, 0.707]
+const qubitId = await brain.add({
+ data: 'Qubit-1',
+ type: NounType.Thing,
+ subtype: 'quantum-bit',
+ metadata: { superposition: [0.707, 0.707] }
})
// Cryptocurrency transactions
-const txId = await brain.add("Bitcoin Transfer", {
- type: "event",
- subtype: "blockchain_transaction",
- hash: "1A2B3C..."
+const txId = await brain.add({
+ data: 'Bitcoin Transfer',
+ type: NounType.Event,
+ subtype: 'blockchain-transaction',
+ metadata: { hash: '1A2B3C...' }
})
// AI model training
-const modelId = await brain.add("Neural Network", {
- type: "process",
- subtype: "ml_model",
- architecture: "transformer"
+const modelId = await brain.add({
+ data: 'Neural Network',
+ type: NounType.Process,
+ subtype: 'ml-model',
+ metadata: { architecture: 'transformer' }
})
```
### The Philosophy: Simplicity Over Specificity
-We intentionally keep the type system minimal because:
-1. **Fewer types = easier to learn**
-2. **Metadata provides infinite extensibility**
-3. **Consistent patterns across domains**
-4. **Avoids taxonomy explosion**
+A bounded type system stays learnable:
+1. **A fixed vocabulary is easier to learn** than thousands of bespoke schemas
+2. **Subtype + metadata provide open-ended extensibility**
+3. **Consistent patterns** carry across domains
+4. **No taxonomy explosion** as new use cases appear
## Industry-Specific Coverage Analysis
-### Why 42 Nouns + 127 Verbs = Universal Coverage
+### Why 42 Nouns + 127 Verbs = Broad Coverage
-The combination of **31 noun types** and **40 verb types** creates **1,240 basic combinations**, but with metadata and multi-hop relationships, this expands to **infinite expressiveness**. Here's how it covers every industry:
+The combination of **42 noun types** and **127 verb types** yields **5,334 base combinations**, and with subtypes, metadata, and multi-hop relationships that expands to cover essentially any domain. Here's how it maps onto common industries.
### Healthcare & Medical
+
```typescript
-// Patient records with medical history
-const patientId = await brain.add("John Doe", {
- type: "person",
- subtype: "patient",
- mrn: "12345"
+const patientId = await brain.add({
+ data: 'John Doe',
+ type: NounType.Person,
+ subtype: 'patient',
+ metadata: { mrn: '12345' }
})
-const diagnosisId = await brain.add("Type 2 Diabetes", {
- type: "state",
- subtype: "diagnosis",
- icd10: "E11.9"
+const diagnosisId = await brain.add({
+ data: 'Type 2 Diabetes',
+ type: NounType.State,
+ subtype: 'diagnosis',
+ metadata: { icd10: 'E11.9' }
})
-const medicationId = await brain.add("Metformin", {
- type: "thing",
- subtype: "medication",
- dosage: "500mg"
+const medicationId = await brain.add({
+ data: 'Metformin',
+ type: NounType.Substance,
+ subtype: 'medication',
+ metadata: { dosage: '500mg' }
})
+const doctorId = await brain.add({ data: 'Dr. Reyes', type: NounType.Person, subtype: 'physician' })
+
// Medical relationships
-await brain.relate(patientId, diagnosisId, "diagnoses")
-await brain.relate(medicationId, diagnosisId, "treats")
-await brain.relate(doctorId, patientId, "treats")
+await brain.relate({ from: patientId, to: diagnosisId, type: VerbType.HasQuality, subtype: 'diagnosis' })
+await brain.relate({ from: medicationId, to: diagnosisId, type: VerbType.Affects, subtype: 'treats' })
+await brain.relate({ from: doctorId, to: patientId, type: VerbType.RelatedTo, subtype: 'treats' })
```
### Finance & Banking
+
```typescript
-// Financial instruments and transactions
-const accountId = await brain.add("Checking Account", {
- type: "thing",
- subtype: "account",
- balance: 10000
+const accountId = await brain.add({
+ data: 'Checking Account',
+ type: NounType.Thing,
+ subtype: 'account',
+ metadata: { balance: 10000 }
})
-const transactionId = await brain.add("Wire Transfer", {
- type: "event",
- subtype: "transaction",
- amount: 5000
+const transactionId = await brain.add({
+ data: 'Wire Transfer',
+ type: NounType.Event,
+ subtype: 'transaction',
+ metadata: { amount: 5000 }
})
-const regulationId = await brain.add("GDPR Compliance", {
- type: "concept",
- subtype: "regulation"
+const regulationId = await brain.add({
+ data: 'KYC Requirement',
+ type: NounType.Regulation,
+ subtype: 'compliance'
})
+const customerId = await brain.add({ data: 'Account Holder', type: NounType.Person, subtype: 'customer' })
+
// Financial relationships
-await brain.relate(customerId, accountId, "owns")
-await brain.relate(transactionId, accountId, "modifies")
-await brain.relate(accountId, regulationId, "compliesWith")
+await brain.relate({ from: customerId, to: accountId, type: VerbType.Owns })
+await brain.relate({ from: transactionId, to: accountId, type: VerbType.Modifies })
+await brain.relate({ from: accountId, to: regulationId, type: VerbType.ConformsTo })
```
### Manufacturing & Supply Chain
+
```typescript
-// Production and logistics
-const factoryId = await brain.add("Plant #3", {
- type: "location",
- subtype: "facility"
+const factoryId = await brain.add({
+ data: 'Plant #3',
+ type: NounType.Location,
+ subtype: 'facility'
})
-const assemblyLineId = await brain.add("Assembly Line A", {
- type: "process",
- subtype: "production"
+const assemblyLineId = await brain.add({
+ data: 'Assembly Line A',
+ type: NounType.Process,
+ subtype: 'production'
})
-const componentId = await brain.add("Circuit Board v2", {
- type: "thing",
- subtype: "component"
+const componentId = await brain.add({
+ data: 'Circuit Board v2',
+ type: NounType.Thing,
+ subtype: 'component'
})
+const productId = await brain.add({ data: 'Controller Unit', type: NounType.Product })
+const supplierId = await brain.add({ data: 'Acme Components', type: NounType.Organization, subtype: 'supplier' })
+
// Manufacturing relationships
-await brain.relate(assemblyLineId, componentId, "produces")
-await brain.relate(componentId, productId, "partOf")
-await brain.relate(supplierId, componentId, "supplies")
+await brain.relate({ from: assemblyLineId, to: componentId, type: VerbType.Creates })
+await brain.relate({ from: componentId, to: productId, type: VerbType.PartOf })
+await brain.relate({ from: supplierId, to: componentId, type: VerbType.RelatedTo, subtype: 'supplies' })
```
### Education & Learning
+
```typescript
-// Educational content and progress
-const courseId = await brain.add("Machine Learning 101", {
- type: "collection",
- subtype: "course"
+const courseId = await brain.add({
+ data: 'Machine Learning 101',
+ type: NounType.Collection,
+ subtype: 'course'
})
-const lessonId = await brain.add("Neural Networks", {
- type: "content",
- subtype: "lesson"
+const lessonId = await brain.add({
+ data: 'Neural Networks',
+ type: NounType.Document,
+ subtype: 'lesson'
})
-const assessmentId = await brain.add("Final Exam", {
- type: "event",
- subtype: "assessment"
+const assessmentId = await brain.add({
+ data: 'Final Exam',
+ type: NounType.Event,
+ subtype: 'assessment'
})
+const studentId = await brain.add({ data: 'Student #88', type: NounType.Person, subtype: 'student' })
+
// Educational relationships
-await brain.relate(studentId, courseId, "enrolledIn")
-await brain.relate(courseId, lessonId, "contains")
-await brain.relate(studentId, assessmentId, "completed")
+await brain.relate({ from: studentId, to: courseId, type: VerbType.MemberOf, subtype: 'enrolled' })
+await brain.relate({ from: courseId, to: lessonId, type: VerbType.Contains })
+await brain.relate({ from: studentId, to: assessmentId, type: VerbType.ParticipatesIn, subtype: 'completed' })
```
### Legal & Compliance
+
```typescript
-// Legal documents and cases
-const contractId = await brain.add("Service Agreement", {
- type: "document",
- subtype: "contract"
+const contractId = await brain.add({
+ data: 'Service Agreement',
+ type: NounType.Contract,
+ subtype: 'service-agreement'
})
-const clauseId = await brain.add("Liability Clause", {
- type: "content",
- subtype: "clause"
+const clauseId = await brain.add({
+ data: 'Liability Clause',
+ type: NounType.Document,
+ subtype: 'clause'
})
-const caseId = await brain.add("Case #2024-1234", {
- type: "event",
- subtype: "legal_case"
+const caseId = await brain.add({
+ data: 'Case #2024-1234',
+ type: NounType.Event,
+ subtype: 'legal-case'
})
+const partyId = await brain.add({ data: 'Counterparty LLC', type: NounType.Organization })
+
// Legal relationships
-await brain.relate(contractId, clauseId, "contains")
-await brain.relate(party1Id, contractId, "signedBy")
-await brain.relate(caseId, contractId, "references")
+await brain.relate({ from: contractId, to: clauseId, type: VerbType.Contains })
+await brain.relate({ from: partyId, to: contractId, type: VerbType.RelatedTo, subtype: 'signatory' })
+await brain.relate({ from: caseId, to: contractId, type: VerbType.References })
```
### Retail & E-commerce
+
```typescript
-// Products and customer behavior
-const productId = await brain.add("iPhone 15", {
- type: "product",
- sku: "IP15-128-BLK"
+const productId = await brain.add({
+ data: 'Wireless Earbuds',
+ type: NounType.Product,
+ metadata: { sku: 'WE-128-BLK' }
})
-const cartId = await brain.add("Shopping Cart", {
- type: "collection",
- subtype: "cart"
+const cartId = await brain.add({
+ data: 'Shopping Cart',
+ type: NounType.Collection,
+ subtype: 'cart'
})
-const promotionId = await brain.add("Black Friday Sale", {
- type: "event",
- subtype: "promotion"
+const promotionId = await brain.add({
+ data: 'Holiday Sale',
+ type: NounType.Event,
+ subtype: 'promotion'
})
+const customerId = await brain.add({ data: 'Customer #5521', type: NounType.Person, subtype: 'customer' })
+
// Retail relationships
-await brain.relate(customerId, productId, "views")
-await brain.relate(cartId, productId, "contains")
-await brain.relate(promotionId, productId, "applies")
+await brain.relate({ from: customerId, to: productId, type: VerbType.RelatedTo, subtype: 'view' })
+await brain.relate({ from: cartId, to: productId, type: VerbType.Contains })
+await brain.relate({ from: promotionId, to: productId, type: VerbType.Affects, subtype: 'applies' })
```
### Real Estate
+
```typescript
-// Properties and transactions
-const propertyId = await brain.add("123 Main St", {
- type: "location",
- subtype: "property"
+const propertyId = await brain.add({
+ data: '123 Main St',
+ type: NounType.Location,
+ subtype: 'property'
})
-const listingId = await brain.add("MLS #789", {
- type: "document",
- subtype: "listing"
+const listingId = await brain.add({
+ data: 'MLS #789',
+ type: NounType.Document,
+ subtype: 'listing'
})
-const inspectionId = await brain.add("Home Inspection", {
- type: "event",
- subtype: "inspection"
+const inspectionId = await brain.add({
+ data: 'Home Inspection',
+ type: NounType.Event,
+ subtype: 'inspection'
})
+const ownerId = await brain.add({ data: 'Property Owner', type: NounType.Person })
+
// Real estate relationships
-await brain.relate(ownerId, propertyId, "owns")
-await brain.relate(listingId, propertyId, "describes")
-await brain.relate(inspectionId, propertyId, "evaluates")
+await brain.relate({ from: ownerId, to: propertyId, type: VerbType.Owns })
+await brain.relate({ from: listingId, to: propertyId, type: VerbType.Describes })
+await brain.relate({ from: inspectionId, to: propertyId, type: VerbType.Evaluates })
```
### Government & Public Sector
+
```typescript
-// Civic data and services
-const citizenId = await brain.add("Citizen #123", {
- type: "person",
- subtype: "citizen"
+const citizenId = await brain.add({
+ data: 'Citizen #123',
+ type: NounType.Person,
+ subtype: 'citizen'
})
-const permitId = await brain.add("Building Permit", {
- type: "document",
- subtype: "permit"
+const permitId = await brain.add({
+ data: 'Building Permit',
+ type: NounType.Document,
+ subtype: 'permit'
})
-const departmentId = await brain.add("Planning Dept", {
- type: "organization",
- subtype: "government"
+const departmentId = await brain.add({
+ data: 'Planning Dept',
+ type: NounType.Organization,
+ subtype: 'government'
})
+const propertyId = await brain.add({ data: '500 Oak Ave', type: NounType.Location, subtype: 'property' })
+
// Government relationships
-await brain.relate(citizenId, permitId, "requests")
-await brain.relate(departmentId, permitId, "issues")
-await brain.relate(permitId, propertyId, "authorizes")
+await brain.relate({ from: citizenId, to: permitId, type: VerbType.RelatedTo, subtype: 'request' })
+await brain.relate({ from: departmentId, to: permitId, type: VerbType.Creates, subtype: 'issues' })
+await brain.relate({ from: permitId, to: propertyId, type: VerbType.PermittedTo, subtype: 'authorizes' })
```
-### Why This Covers All Knowledge
+### Why This Covers Most Knowledge
-#### 1. **Mathematical Completeness**
-The noun-verb model forms a **complete graph structure** where:
+#### 1. Structural Completeness
+
+The noun-verb model forms a **graph structure** where:
- Any entity can be represented as a noun
- Any relationship can be represented as a verb
- Complex knowledge emerges from simple combinations
-#### 2. **Semantic Completeness**
-Every piece of human knowledge falls into one of these categories:
+#### 2. Semantic Coverage
+
+Most information falls into one of these categories:
- **Entities** (who, what, where) → Nouns
-- **Actions** (how, when, why) → Verbs
+- **Actions/relations** (how, when, why) → Verbs
- **Attributes** (properties) → Metadata
- **Context** (conditions) → Graph structure
-#### 3. **Compositional Power**
+#### 3. Compositional Power
+
Simple types combine to represent complex knowledge:
+
```typescript
-// Complex knowledge from simple building blocks
-const researchPaper = await brain.add("AI Ethics Study", {
- type: "document"
-})
+const researchPaper = await brain.add({ data: 'AI Ethics Study', type: NounType.Document })
+const researcher = await brain.add({ data: 'Dr. Smith', type: NounType.Person })
+const institution = await brain.add({ data: 'MIT', type: NounType.Organization })
+const concept = await brain.add({ data: 'AI Ethics', type: NounType.Concept })
-const researcher = await brain.add("Dr. Smith", {
- type: "person"
-})
-
-const institution = await brain.add("MIT", {
- type: "organization"
-})
-
-const concept = await brain.add("AI Ethics", {
- type: "concept"
-})
-
-// Rich knowledge graph emerges
-await brain.relate(researcher, researchPaper, "authors")
-await brain.relate(researcher, institution, "affiliated")
-await brain.relate(researchPaper, concept, "explores")
-await brain.relate(institution, researchPaper, "publishes")
+// A rich knowledge graph emerges from a handful of typed edges
+await brain.relate({ from: researcher, to: researchPaper, type: VerbType.Creates })
+await brain.relate({ from: researcher, to: institution, type: VerbType.MemberOf })
+await brain.relate({ from: researchPaper, to: concept, type: VerbType.Describes })
+await brain.relate({ from: institution, to: researchPaper, type: VerbType.Creates, subtype: 'publishes' })
```
-#### 4. **Domain Independence**
-The same types work across all domains:
+#### 4. Domain Independence
+
+The same types work across domains:
**Science:**
```typescript
-await brain.add("H2O", { type: "thing", category: "molecule" })
-await brain.add("Photosynthesis", { type: "process" })
-await brain.relate(moleculeId, processId, "participates")
+const moleculeId = await brain.add({ data: 'H2O', type: NounType.Substance, metadata: { category: 'molecule' } })
+const processId = await brain.add({ data: 'Photosynthesis', type: NounType.Process })
+await brain.relate({ from: moleculeId, to: processId, type: VerbType.ParticipatesIn })
```
**Business:**
```typescript
-await brain.add("Q3 Revenue", { type: "metric", value: 10000000 })
-await brain.add("Sales Team", { type: "organization" })
-await brain.relate(teamId, metricId, "achieves")
+const metricId = await brain.add({ data: 'Q3 Revenue', type: NounType.Measurement, metadata: { value: 10_000_000 } })
+const teamId = await brain.add({ data: 'Sales Team', type: NounType.Organization })
+await brain.relate({ from: teamId, to: metricId, type: VerbType.RelatedTo, subtype: 'achieves' })
```
**Social:**
```typescript
-await brain.add("John", { type: "person" })
-await brain.add("Community Group", { type: "organization" })
-await brain.relate(personId, groupId, "joins")
+const personId = await brain.add({ data: 'John', type: NounType.Person })
+const groupId = await brain.add({ data: 'Community Group', type: NounType.SocialGroup })
+await brain.relate({ from: personId, to: groupId, type: VerbType.MemberOf })
```
-#### 5. **Temporal Coverage**
-Handles all temporal aspects:
+#### 5. Temporal Coverage
+
+Time lives in edge metadata, so past, present, and future all fit:
+
```typescript
// Past
-await brain.relate(personId, companyId, "worked", {
- from: "2010", to: "2020"
+await brain.relate({
+ from: personId,
+ to: companyId,
+ type: VerbType.MemberOf,
+ subtype: 'past-employment',
+ metadata: { from: '2010', to: '2020' }
})
// Present
-await brain.relate(personId, projectId, "manages", {
- since: "2024-01-01"
+await brain.relate({
+ from: personId,
+ to: projectId,
+ type: VerbType.ParticipatesIn,
+ subtype: 'manager',
+ metadata: { since: '2024-01-01' }
})
// Future
-await brain.relate(eventId, venueId, "scheduled", {
- date: "2025-06-15"
+await brain.relate({
+ from: eventId,
+ to: venueId,
+ type: VerbType.LocatedAt,
+ metadata: { scheduledFor: '2025-06-15' }
})
```
-#### 6. **Hierarchical Representation**
-Supports all levels of abstraction:
+#### 6. Hierarchical Representation
+
+Every level of abstraction fits:
+
```typescript
// Micro level
-await brain.add("Electron", { type: "thing", scale: "quantum" })
+await brain.add({ data: 'Electron', type: NounType.Thing, metadata: { scale: 'quantum' } })
// Macro level
-await brain.add("Solar System", { type: "place", scale: "astronomical" })
+await brain.add({ data: 'Solar System', type: NounType.Location, metadata: { scale: 'astronomical' } })
// Abstract level
-await brain.add("Justice", { type: "concept", domain: "philosophy" })
+await brain.add({ data: 'Justice', type: NounType.Concept, metadata: { domain: 'philosophy' } })
```
### Extensibility
-While the core types cover all knowledge, you can extend with domain-specific subtypes:
+While the core types cover most domains, you extend with `subtype` (and metadata) — never a schema migration:
```typescript
-// Extend person for medical domain
-await brain.add("Patient #12345", {
- type: "person",
- subtype: "patient",
- medicalRecord: "MR-12345"
+// Extend Person for the medical domain
+await brain.add({
+ data: 'Patient #12345',
+ type: NounType.Person,
+ subtype: 'patient',
+ metadata: { medicalRecord: 'MR-12345' }
})
-// Extend document for legal domain
-await brain.add("Contract ABC", {
- type: "document",
- subtype: "contract",
- jurisdiction: "California"
+// Extend Document for the legal domain
+await brain.add({
+ data: 'Contract ABC',
+ type: NounType.Document,
+ subtype: 'contract',
+ metadata: { jurisdiction: 'California' }
})
-// Custom verb for specific domain
-await brain.relate(lawyerId, contractId, "negotiates", {
- verbSubtype: "legal-action",
- billableHours: 10
+// Extend a verb with a domain-specific subtype + billing metadata
+await brain.relate({
+ from: lawyerId,
+ to: contractId,
+ type: VerbType.ParticipatesIn,
+ subtype: 'negotiator',
+ metadata: { billableHours: 10 }
})
```
-### Mathematical Proof of Universal Coverage
+### How the Taxonomy Stays Complete
-The noun-verb taxonomy achieves **Turing completeness** for knowledge representation:
+The noun-verb model is designed to represent any knowledge that can be expressed as entities and relations:
-1. **Storage Completeness**: Any data can be stored as nouns
-2. **Relational Completeness**: Any relationship can be expressed as verbs
-3. **Property Completeness**: Unlimited metadata captures all attributes
-4. **Graph Completeness**: Multi-hop traversals express any complexity
-5. **Temporal Completeness**: Time metadata handles all temporal aspects
-6. **Semantic Completeness**: Vector embeddings capture meaning and similarity
+1. **Storage**: Any data can be stored as nouns
+2. **Relational**: Any relationship can be expressed as verbs
+3. **Property**: Open-ended metadata captures all attributes
+4. **Graph**: Multi-hop traversals express arbitrary complexity
+5. **Temporal**: Date metadata handles all temporal aspects
+6. **Semantic**: Vector embeddings capture meaning and similarity
-#### The Infinity Formula
+#### The Composition Formula
```
-Expressiveness = (31 nouns × 40 verbs) × ∞ metadata × ∞ graph depth
- = 1,240 × ∞ × ∞
- = ∞ (Infinite Expressiveness)
+Expressiveness = (42 nouns × 127 verbs) × metadata × graph depth
+ = 5,334 base combinations × open-ended refinement
```
-This mathematical infinity means Brainy can represent:
-- **All Scientific Knowledge**: From quantum physics to molecular biology
-- **All Business Data**: From transactions to supply chains
-- **All Social Graphs**: From friendships to organizational hierarchies
-- **All Historical Records**: From events to archaeological findings
-- **All Creative Works**: From art metadata to story relationships
-- **All Technical Systems**: From software architecture to network topology
-- **All Personal Information**: From memories to preferences
-- **Literally ANY Information That Can Exist**
+That composition lets Brainy represent:
+- **Scientific Knowledge**: From quantum physics to molecular biology
+- **Business Data**: From transactions to supply chains
+- **Social Graphs**: From friendships to organizational hierarchies
+- **Historical Records**: From events to archaeological findings
+- **Creative Works**: From media metadata to story relationships
+- **Technical Systems**: From software architecture to network topology
+- **Personal Information**: From memories to preferences
### Real-World Proof: Unmappable Becomes Mappable
Even the most complex scenarios map naturally:
```typescript
-// String Theory - 11-dimensional physics
-const braneId = await brain.add("D3-Brane", {
- type: "concept",
- dimensions: 11,
- vibrational_modes: ["0,1", "1,0", "2,1"]
+// String Theory — high-dimensional physics
+const braneId = await brain.add({
+ data: 'D3-Brane',
+ type: NounType.Concept,
+ metadata: { dimensions: 11, vibrationalModes: ['0,1', '1,0', '2,1'] }
})
-// Consciousness - The "hard problem" of philosophy
-const qualiaId = await brain.add("Red Qualia", {
- type: "concept",
- subtype: "phenomenal_experience",
- ineffable: true
+// Consciousness — the "hard problem" of philosophy
+const qualiaId = await brain.add({
+ data: 'Red Qualia',
+ type: NounType.Concept,
+ subtype: 'phenomenal-experience',
+ metadata: { ineffable: true }
})
-// Time Travel Paradoxes
-const futureEvent = await brain.add("Future Effect", {
- type: "event",
- temporal_position: "future"
+// Causal paradoxes
+const futureEvent = await brain.add({
+ data: 'Future Effect',
+ type: NounType.Event,
+ metadata: { temporalPosition: 'future' }
})
-const pastCause = await brain.add("Past Cause", {
- type: "event",
- temporal_position: "past"
+const pastCause = await brain.add({
+ data: 'Past Cause',
+ type: NounType.Event,
+ metadata: { temporalPosition: 'past' }
})
-await brain.relate(futureEvent, pastCause, "causes", {
- paradox_type: "bootstrap"
+await brain.relate({
+ from: futureEvent,
+ to: pastCause,
+ type: VerbType.Causes,
+ metadata: { paradoxType: 'bootstrap' }
})
```
-If it exists, thinks, happens, or can be imagined—Brainy can model it.
+If it exists, thinks, happens, or can be imagined — Brainy can model it.
+
+## Migration from Traditional Models
+
+### From Relational (SQL)
+
+```typescript
+// Instead of JOIN queries:
+// SELECT * FROM users JOIN orders ON users.id = orders.user_id
+
+// Use noun-verb relationships
+const userId = await brain.add({ data: 'User', type: NounType.Person, metadata: { email: 'u@example.com' } })
+const orderId = await brain.add({ data: 'Order #1', type: NounType.Event, subtype: 'order' })
+await brain.relate({ from: userId, to: orderId, type: VerbType.Creates, subtype: 'placed' })
+
+// Query naturally via the graph
+const userOrders = await brain.find({
+ type: NounType.Event,
+ connected: { from: userId, via: VerbType.Creates }
+})
+```
+
+### From Document (NoSQL)
+
+```typescript
+// Instead of embedded documents: { user: { orders: [...] } }
+
+// Use explicit relationships
+const userId = await brain.add({ data: 'User', type: NounType.Person })
+for (const order of orders) {
+ const orderId = await brain.add({ data: order.summary, type: NounType.Event, subtype: 'order' })
+ await brain.relate({ from: userId, to: orderId, type: VerbType.Creates, subtype: 'placed' })
+}
+```
+
+### From Graph Databases
+
+```typescript
+// Similar to a graph database, with added benefits:
+// 1. Automatic vector embeddings for similarity
+// 2. Natural language querying
+// 3. Unified with metadata filtering
+
+// Vector + graph in one query
+const results = await brain.find({ query: 'users who bought similar products' })
+```
## Conclusion
-The Noun-Verb taxonomy in Brainy 2.0 provides a natural, flexible, and powerful way to model any domain. By thinking in terms of entities and their relationships, you can build everything from simple data stores to complex knowledge graphs while maintaining code clarity and query simplicity.
+The Noun-Verb Taxonomy gives Brainy a natural, flexible, and powerful way to model any domain. By thinking in terms of entities (42 `NounType`s) and their relationships (127 `VerbType`s) — refined with `subtype` and metadata — you can build everything from simple data stores to complex knowledge graphs while keeping code clear and queries simple.
## See Also
-- [Triple Intelligence](./triple-intelligence.md)
-- [Natural Language Queries](../guides/natural-language.md)
-- [API Reference](../api/README.md)
\ No newline at end of file
+- [Triple Intelligence](/docs/concepts/triple-intelligence)
+- [Subtypes & Facets](/docs/guides/subtypes-and-facets)
+- [The Find System](/docs/guides/find-system)
+- [API Reference](/docs/api/reference)
diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md
index 3f77e133..1bc6e66c 100644
--- a/docs/architecture/overview.md
+++ b/docs/architecture/overview.md
@@ -48,7 +48,7 @@ Pluggable vector index (`VectorIndexProvider`) for efficient nearest-neighbor se
- **Configurable recall**: `fast` / `balanced` / `accurate` presets trade recall for latency
- **Scalable**: Handles millions of vectors per process
- **Persistent**: Serializable to storage
-- **Swappable**: Replace with a native implementation (such as `@soulcraft/cortex`) via the plugin system without changing application code
+- **Swappable**: Replace with a native implementation (such as `@soulcraft/cor`) via the plugin system without changing application code
### Metadata Index Manager
High-performance field indexing system:
diff --git a/docs/architecture/triple-intelligence.md b/docs/architecture/triple-intelligence.md
index 0751444a..fbec56a9 100644
--- a/docs/architecture/triple-intelligence.md
+++ b/docs/architecture/triple-intelligence.md
@@ -23,29 +23,37 @@ Traditional databases force you to choose between vector search, graph traversal
### Unified Query Structure
+`find()` accepts a single `FindParams` object (or a natural-language string). One
+object combines all three intelligences:
+
```typescript
-interface TripleQuery {
- // Vector/Semantic search
- like?: string | Vector | any
- similar?: string | Vector | any
-
- // Graph/Relationship search
+interface FindParams {
+ // Vector intelligence — semantic similarity
+ query?: string // Natural-language / semantic query (embedded, matched via HNSW + text index)
+ vector?: number[] // Pre-computed embedding for direct vector search
+
+ // Metadata intelligence — structured field filters
+ type?: NounType | NounType[] // Filter by entity type
+ subtype?: string | string[] // Filter by per-product subtype
+ where?: Record // Field predicates with bare operators (gte, lt, in, contains, exists…)
+
+ // Graph intelligence — relationship traversal
connected?: {
- to?: string | string[]
- from?: string | string[]
- type?: string | string[]
- depth?: number
+ to?: string // Reachable to this entity
+ from?: string // Reachable from this entity
+ via?: VerbType | VerbType[] // Relationship type(s) to traverse (alias: type)
+ depth?: number // Max traversal depth (default: 1)
direction?: 'in' | 'out' | 'both'
}
-
- // Field/Attribute search
- where?: Record
-
- // Advanced options
- limit?: number
- boost?: 'recent' | 'popular' | 'verified' | string
- explain?: boolean
- threshold?: number
+
+ // Proximity — nearest neighbours of a known entity
+ near?: { id: string; threshold?: number }
+
+ // Control
+ limit?: number // Max results (default: 10)
+ offset?: number // Skip N results
+ orderBy?: string // Field to sort by (e.g. 'createdAt')
+ order?: 'asc' | 'desc' // Sort direction
}
```
@@ -74,10 +82,10 @@ const results = await brain.find("machine learning concepts")
#### Combined Intelligence Query
```typescript
const results = await brain.find({
- like: "neural networks",
+ query: "neural networks",
where: {
category: "research",
- year: { $gte: 2023 }
+ year: { gte: 2023 }
},
connected: {
to: "deep-learning-team",
@@ -109,8 +117,8 @@ All three search types execute simultaneously:
```typescript
// Parallel execution for balanced query
const results = await brain.find({
- like: "AI research", // ~1000 potential matches
- where: { type: "paper" }, // ~500 potential matches
+ query: "AI research", // ~1000 potential matches
+ where: { kind: "paper" }, // ~500 potential matches
connected: { to: "stanford" } // ~200 potential matches
})
// All three execute in parallel, results fused
@@ -126,7 +134,7 @@ Operations chain for maximum efficiency:
// Progressive execution for selective query
const results = await brain.find({
where: { userId: "user123" }, // Very selective (1-10 matches)
- like: "recent posts", // Applied to filtered set
+ query: "recent posts", // Applied to filtered set
limit: 5
})
// Metadata filter first, then vector search on results
@@ -166,10 +174,10 @@ const results = await brain.find(
)
// Automatically converts to:
// {
-// like: "AI papers",
-// where: {
+// query: "AI papers",
+// where: {
// institution: "Stanford",
-// published: { $gte: "2024-01-01" }
+// published: { gte: "2024-01-01" }
// }
// }
```
@@ -188,10 +196,10 @@ The NLP processor identifies query intent:
Successful execution plans are cached:
```typescript
-// First query: 50ms (plan generation + execution)
+// First call parses the natural-language query and builds an execution plan
await brain.find("machine learning papers")
-// Subsequent similar queries: 10ms (cached plan)
+// A structurally similar query reuses that plan, skipping plan generation
await brain.find("deep learning papers")
```
@@ -213,50 +221,45 @@ Triple Intelligence leverages all available indexes:
### Explain Mode
-Understand how your query was executed:
+Diagnose how a query's `where` fields map to the index. Run `brain.explain()`
+first whenever `find()` returns surprising or empty results:
```typescript
-const results = await brain.find({
- like: "quantum computing",
- where: { category: "research" },
- explain: true
+const plan = await brain.explain({
+ query: "quantum computing",
+ where: { category: "research" }
})
-console.log(results[0].explanation)
-// {
-// plan: "field-first-progressive",
-// timing: {
-// fieldFilter: 2,
-// vectorSearch: 8,
-// fusion: 1
-// },
-// selectivity: {
-// field: 0.1,
-// vector: 0.3
-// }
-// }
+console.log(plan.fieldPlan)
+// [
+// { field: 'category', path: 'column-store', notes: '...' }
+// ]
+
+console.log(plan.warnings)
+// e.g. ['Field "category" has no index entries. find() will return [] silently...']
```
-### Boosting
+### Result Ordering
-Apply custom ranking boosts:
+Sort results by any stored field with `orderBy` / `order`:
```typescript
const results = await brain.find({
- like: "news articles",
- boost: 'recent', // Boost recent items
- where: { verified: true }
+ query: "news articles",
+ where: { verified: true },
+ orderBy: 'createdAt', // Newest first
+ order: 'desc'
})
```
-### Threshold Control
+### Similarity Threshold
-Set minimum similarity thresholds:
+Find the nearest neighbours of a known entity and keep only close matches with
+`near`:
```typescript
const results = await brain.find({
- like: "exact match needed",
- threshold: 0.9, // Only very similar results
+ near: { id: anchorId, threshold: 0.9 }, // Only results >= 0.9 similarity
limit: 10
})
```
@@ -283,8 +286,8 @@ const results = await brain.find({
```typescript
// Find similar content with constraints
const results = await brain.find({
- like: query,
- where: {
+ query: searchText,
+ where: {
status: 'published',
language: 'en'
}
@@ -295,10 +298,10 @@ const results = await brain.find({
```typescript
// Find items related to a specific item
const results = await brain.find({
- connected: {
+ connected: {
to: itemId,
depth: 2,
- type: 'similar'
+ via: VerbType.RelatedTo
},
limit: 20
})
@@ -309,10 +312,11 @@ const results = await brain.find({
// Recent items matching criteria
const results = await brain.find({
where: {
- timestamp: { $gte: Date.now() - 86400000 }
+ timestamp: { gte: Date.now() - 86400000 }
},
- like: "trending topics",
- boost: 'recent'
+ query: "trending topics",
+ orderBy: 'timestamp',
+ order: 'desc'
})
```
diff --git a/docs/architecture/zero-config.md b/docs/architecture/zero-config.md
index 64a10461..d42d6784 100644
--- a/docs/architecture/zero-config.md
+++ b/docs/architecture/zero-config.md
@@ -84,7 +84,7 @@ const brain = new Brainy({
```
The default JS index is `JsHnswVectorIndex`. An optional native acceleration
-provider (the `@soulcraft/cortex` package) can replace it with a
+provider (the `@soulcraft/cor` package) can replace it with a
higher-performing implementation; the public knobs stay the same. Quantization
and other index-internal acceleration are the native provider's concern, not a
Brainy configuration option.
diff --git a/docs/concepts/generation-fact-log.md b/docs/concepts/generation-fact-log.md
new file mode 100644
index 00000000..f9b3e974
--- /dev/null
+++ b/docs/concepts/generation-fact-log.md
@@ -0,0 +1,117 @@
+---
+title: The Generation Fact Log
+slug: concepts/generation-fact-log
+public: true
+category: concepts
+template: concept
+order: 6
+description: Every committed write also appends a self-verifying "fact" — an after-image commit record — to an append-only log. What facts are, the crash-safety model, the scanFacts() streaming surface, family stamps, and how index providers consume the log for sequential heals.
+next:
+ - concepts/consistency-model
+ - guides/snapshots-and-time-travel
+---
+
+# The Generation Fact Log
+
+Since 8.4.0, every committed generation also appends a **fact** — a compact record of what each
+touched entity or relationship *became* — to an append-only, checksummed log under
+`_generations/facts/`. Where the generational history answers *"what did things look like
+before?"* (before-images, powering `asOf()` and rollback), the fact log answers *"what happened,
+in order?"* — one sequential, self-verifying stream of the store's present being written.
+
+Nothing about querying changes. The fact log exists for three consumers:
+
+1. **Index heals and rebuilds** — one sequential read in commit order replaces a per-entity
+ directory walk over millions of files.
+2. **Incremental catch-up** — a derived index that knows which generation it reflects reads *just
+ the gap*, instead of rebuilding from scratch.
+3. **Replay and audit tooling** — anything that wants the store's committed timeline as a stream.
+
+## What a fact is
+
+One fact per committed generation:
+
+- **`generation`** and **`timestamp`** — which commit, when.
+- **`ops`** — every write in that commit: `{ kind: 'noun' | 'verb', id, record }` where `record`
+ holds the entity's full after-image (both stored legs), or **`null` for a tombstone** — a
+ removal carries no body, by design.
+- **`meta`** — the transaction metadata `transact()` was submitted with, when present.
+- **`blobHashes`** — content-blob references, for exact reclamation accounting.
+
+Facts accumulate **from the first write after upgrading** — pre-existing history is not
+retroactively converted, and consumers fall back to the enumeration walk when no log exists.
+
+## Crash safety, in one paragraph
+
+Facts are appended and fsynced **inside the same durability window as the commit itself**, before
+the commit point — so after a crash, the log can only ever be *ahead* of committed truth, never
+behind it with a hole. On open, the store reconciles the log back to the committed watermark:
+torn tails are detected by per-record checksums and cut; whole records beyond the watermark are
+truncated. The invariant every reader can rely on: **an absent generation was never committed; a
+present fact was.** `transact()` facts are durable the moment `transact()` returns; single-op
+facts share the same group-commit flush as the rest of their generation, so a hard kill loses the
+fact and the generation *together* — never a torn state.
+
+## Reading the log
+
+```typescript
+const scan = brain.scanFacts({ fromGeneration: 1 })
+if (scan) {
+ // Telemetry up front — progress bars get a denominator from second zero.
+ console.log(scan.headGeneration, scan.segmentCount, scan.approxFactCount)
+
+ for await (const batch of scan.batches()) {
+ // Each batch: { facts, firstGeneration, lastGeneration, factCount, byteSize, segmentId }
+ for (const fact of batch.facts) {
+ for (const op of fact.ops) {
+ if (op.record === null) {
+ // a tombstone: op.id was removed in this generation
+ }
+ }
+ }
+ }
+
+ console.log(scan.summary()) // { factsYielded, segmentsRead } — the cross-check
+}
+```
+
+- `scanFacts()` returns `null` when the store hosts no fact log (older store, or a storage adapter
+ without binary append support) — fall back to enumerating entities.
+- Scans run against a **snapshot**: facts appended after the scan opens never bleed in, each fact
+ is yielded exactly once, and a detected gap aborts loudly — never a silent skip.
+- `brain.factSegmentPaths()` returns the immutable, *sealed* segment files for zero-copy consumers
+ (the append-mutable tail is excluded — read it through `scanFacts()`).
+
+## Family stamps: how a projection proves it's current
+
+Anything derived from the store — an index, the entity file tree itself — carries a **family
+stamp**: a small JSON record of *which committed generation the projection reflects*
+(`sourceGeneration`) plus the invariants that verify it whole (exact per-file byte sizes for
+bounded families; rollup invariants like entity counts for unbounded ones). At open, coherence is
+a **comparison**, not a walk:
+
+- stamp equals the committed watermark and invariants hold → serve;
+- stamp is behind → the projection reads just the gap from the fact log;
+- invariants fail → loud, named divergence — `brain.repairIndex()` rebuilds from canonical and
+ re-stamps.
+
+The verifier is exported (`verifyFamilyStamp`) so every projection — TypeScript or native — runs
+literally the same check.
+
+## For plugin authors: the storage capability
+
+Index providers receive the storage adapter, not the brain — so the host wires the log onto it.
+Feature-detect and prefer the stream; fall back to enumeration:
+
+```typescript
+const scan = storage.scanFacts?.({ fromGeneration: stamp.sourceGeneration + 1 })
+if (scan) {
+ // sequential catch-up from the log
+} else {
+ // enumeration walk (older store or adapter)
+}
+const committed = storage.committedGeneration?.() // the watermark stamps compare against
+```
+
+Providers must never construct their own reader over the log's files — the open path belongs to
+the single writer (it reconciles the log at open); the capability is the sanctioned seam.
diff --git a/docs/concepts/multi-process.md b/docs/concepts/multi-process.md
index cd65594b..8fda315f 100644
--- a/docs/concepts/multi-process.md
+++ b/docs/concepts/multi-process.md
@@ -55,20 +55,20 @@ process opening the same directory:
The fix is the lock: refuse to open a second writer. SQLite has done the same
since the late 1990s (`SQLITE_BUSY`).
-## What about Cortex?
+## What about Cor?
-Brainy + Cortex compose cleanly under this model:
+Brainy + Cor compose cleanly under this model:
-- Cortex stores its column-index segments inside the same `rootDir` (under
+- Cor stores its column-index segments inside the same `rootDir` (under
`indexes/_column_index/{field}/`).
-- Segments (`*.cidx` files) are **immutable** once written. Cortex mmaps them
+- Segments (`*.cidx` files) are **immutable** once written. Cor mmaps them
read-only.
- The `MANIFEST.json` per field is updated via atomic rename — readers see
either the old or new manifest, never a torn file.
-A reader process can safely mmap Cortex segments alongside a live writer
+A reader process can safely mmap Cor segments alongside a live writer
without coordination. The single Brainy writer lock at
-`/locks/_writer.lock` covers Cortex too, because Cortex segment
+`/locks/_writer.lock` covers Cor too, because Cor segment
writes happen on the writer's side.
## Stale-lock detection
@@ -141,7 +141,7 @@ The CLI `brainy inspect` subcommands all do this for you by default
processes can both succeed at `init()` in writer mode and clobber each
other's writes. A best-effort warning is logged in writer mode against a
non-filesystem backend.
-- **Long-running readers** do not automatically pick up new Cortex segments
+- **Long-running readers** do not automatically pick up new Cor segments
the writer publishes. One-shot inspector calls re-open the store and see
fresh segments; a reader that stays open for hours sees its column store
as-of the time it opened.
@@ -150,4 +150,4 @@ The CLI `brainy inspect` subcommands all do this for you by default
- `Brainy.openReadOnly()` — [API reference](../api/brainy.md)
- `brainy inspect` — [inspection guide](../guides/inspection.md)
-- Cortex columnar storage — see `node_modules/@soulcraft/cortex/README.md`
+- Cor columnar storage — see `node_modules/@soulcraft/cor/README.md`
diff --git a/docs/concepts/storage-adapters.md b/docs/concepts/storage-adapters.md
index e0f9b70d..af6d068f 100644
--- a/docs/concepts/storage-adapters.md
+++ b/docs/concepts/storage-adapters.md
@@ -33,7 +33,7 @@ BaseStorage (type-statistics, lifecycle helpers, generation
FileSystemStorage (real filesystem I/O, writer-lock implementation,
flush-request watcher, atomic writes)
↑
- (Cortex's MmapFileSystemStorage, etc.)
+ (Cor's MmapFileSystemStorage, etc.)
```
When you `extend FileSystemStorage`, your adapter inherits every method on
diff --git a/docs/eli5.md b/docs/eli5.md
index 303de040..e0bb9a19 100644
--- a/docs/eli5.md
+++ b/docs/eli5.md
@@ -11,7 +11,7 @@ next:
- getting-started/quick-start
---
-# Brainy and Cortex — Explained Simply
+# Brainy and Cor — Explained Simply
*A plain-language guide for anyone who wants to understand what this thing actually does.*
@@ -65,13 +65,13 @@ Brainy can narrow any result set down by exact labels or ranges in the same brea
---
-## What is Cortex?
+## What is Cor?
-Cortex is a turbocharger for Brainy.
+Cor is a turbocharger for Brainy.
Same car. Same controls. Same fuel. You just swap in a faster engine under the hood, and everything that used to take a moment now happens instantly.
-Technically, Cortex is an optional plugin written in Rust — a lower-level language that runs much closer to the raw metal of your processor. It plugs into Brainy and takes over the most compute-intensive work: the distance calculations that power meaning search, the number-crunching behind live aggregates, and the set operations that drive label filtering.
+Technically, Cor is an optional plugin written in Rust — a lower-level language that runs much closer to the raw metal of your processor. It plugs into Brainy and takes over the most compute-intensive work: the distance calculations that power meaning search, the number-crunching behind live aggregates, and the set operations that drive label filtering.
You install it with one line, register it with one call, and Brainy automatically uses it everywhere it can help.
@@ -83,9 +83,9 @@ Plain language:
- **Searches** go from "the blink of an eye" to "faster than a blink." The overall speedup is **5.2× on average** across all operations.
- **Live aggregates** are rebuilt using all CPU cores in parallel, so re-indexing large datasets takes a fraction of the time.
-- **Analytics** that aren't even possible in pure JavaScript — real-time anomaly detection, streaming percentile estimates, approximate unique counts — become available because Cortex brings the native capabilities required to run them efficiently.
+- **Analytics** that aren't even possible in pure JavaScript — real-time anomaly detection, streaming percentile estimates, approximate unique counts — become available because Cor brings the native capabilities required to run them efficiently.
-If Brainy is what makes knowledge fast, Cortex is what makes Brainy feel instant.
+If Brainy is what makes knowledge fast, Cor is what makes Brainy feel instant.
---
@@ -135,7 +135,7 @@ Brainy is the only row with every box checked. And it runs all of them in a sing
Brainy scales from a quick experiment to serious production datasets without changing a line of code. Small datasets live entirely in memory. Larger ones spill to disk, where Brainy shards and compresses everything automatically. Need a backup or a copy? Snapshots are instant — the same API the whole way.
-Add Cortex and you also unlock memory-mapped storage — aggregate state lives directly in the operating system's memory with zero serialization overhead, as fast as the hardware allows.
+Add Cor and you also unlock memory-mapped storage — aggregate state lives directly in the operating system's memory with zero serialization overhead, as fast as the hardware allows.
---
diff --git a/docs/guides/aggregation.md b/docs/guides/aggregation.md
index 1ae2c98e..11d86ec8 100644
--- a/docs/guides/aggregation.md
+++ b/docs/guides/aggregation.md
@@ -11,7 +11,13 @@ No batch jobs. No scheduled recalculations. Aggregates stay current with every w
**Defining over existing data:** if you define an aggregate on a store that already holds
matching entities, Brainy backfills it from those entities on the first query (a one-time scan,
then purely incremental). So `defineAggregate()` behaves the same whether you define it before
-or after the data exists — including when a persisted brain reopens already populated.
+or after the data exists.
+
+**Reopening a persisted brain:** aggregate state persists across restarts. Re-defining the
+same aggregate at boot (the normal declarative pattern) adopts the persisted state directly —
+no rescan. A backfill scan runs only when the definition actually changed, when no persisted
+state exists, or when the state failed to load; and however many aggregates need backfilling,
+they share a single scan.
## Quick Start
@@ -487,7 +493,7 @@ Aggregate definitions and running state are automatically persisted:
## Native Acceleration
-When [Cortex](https://github.com/soulcraftlabs/cortex) is installed as a plugin, the aggregation engine automatically uses Rust-accelerated computation:
+When [Cor](https://www.npmjs.com/package/@soulcraft/cor) is installed as a plugin, the aggregation engine automatically uses Rust-accelerated computation:
- Incremental updates run in Rust with BTreeMap-backed precise MIN/MAX
- Welford's online stddev/variance computed natively
@@ -496,7 +502,7 @@ When [Cortex](https://github.com/soulcraftlabs/cortex) is installed as a plugin,
```typescript
const brain = new Brainy({
- plugins: ['@soulcraft/cortex']
+ plugins: ['@soulcraft/cor']
})
await brain.init()
@@ -577,7 +583,7 @@ brain.defineAggregate({
Aggregation complexity per write is O(A x G x M) where A = matching aggregates, G = groupBy dimensions, M = metrics. For typical configurations (2-5 aggregates, 1-3 dimensions, 3-5 metrics), this is effectively O(1).
-With Cortex native acceleration:
+With Cor native acceleration:
| Operation | Throughput | Latency |
|-----------|-----------|---------|
diff --git a/docs/guides/enterprise-for-everyone.md b/docs/guides/enterprise-for-everyone.md
index de099d04..b79844e9 100644
--- a/docs/guides/enterprise-for-everyone.md
+++ b/docs/guides/enterprise-for-everyone.md
@@ -174,13 +174,13 @@ await brain.syncWith({
const brain = new Brainy()
// 1 → ~1M vectors: pure-JS HNSW, zero extra setup
-// 1M → 10B+ vectors: install @soulcraft/cortex for the native DiskANN provider
+// 1M → 10B+ vectors: install @soulcraft/cor for the native DiskANN provider
```
**Scaling model:**
- **Single process, no cluster**: Brainy runs in one process — no coordinator,
no peer discovery, no consensus to operate
-- **Optional native provider**: install `@soulcraft/cortex` to back the index with
+- **Optional native provider**: install `@soulcraft/cor` to back the index with
on-disk DiskANN that scales to 10B+ vectors on one machine
- **Per-tenant pools**: isolate tenants by giving each its own Brainy instance and
storage directory
diff --git a/docs/guides/external-backups-and-sparse-storage.md b/docs/guides/external-backups-and-sparse-storage.md
new file mode 100644
index 00000000..f28dcfd7
--- /dev/null
+++ b/docs/guides/external-backups-and-sparse-storage.md
@@ -0,0 +1,99 @@
+---
+title: External Backups & Sparse Storage
+slug: guides/external-backups
+public: true
+category: guides
+template: guide
+order: 10
+description: How to back up a brain directory with external tools (tar, rsync, cp) without exploding sparse files — why a store can show 100+ GB "apparent" size on a small disk, which files are sparse, and how persist()/restore() handle it for you.
+next:
+ - guides/snapshots-and-time-travel
+ - concepts/storage-adapters
+---
+
+# External Backups & Sparse Storage
+
+The built-in snapshot path — [`db.persist()` and `brain.restore()`](/docs/guides/snapshots-and-time-travel) —
+already handles everything on this page for you. Read this when you back up a brain directory with
+**external tools**: `tar`, `rsync`, `cp`, `scp`, or a filesystem-level backup agent.
+
+## The one-sentence rule
+
+> **Always use the sparse-aware flag**: `tar czSf` (capital `S`), `rsync --sparse`,
+> `cp --sparse=always`. A naive copy can turn a 2 GB store into a 100+ GB one — or fail
+> the disk entirely.
+
+## Why: some files are sparse
+
+When a native accelerator plugin is active, parts of the index live in **memory-mapped files**
+created at a large fixed virtual size — the file's *apparent* size — while the filesystem only
+allocates blocks that were actually written. A brand-new id-mapper file can report tens of
+gigabytes in `ls -l` while occupying a few megabytes on disk.
+
+Check the difference yourself:
+
+```bash
+ls -lh brain-data/_id_mapper/ # APPARENT size (can be huge)
+du -sh brain-data/ # ALLOCATED size (the real footprint)
+```
+
+The sparse candidates in a brain directory:
+
+| Path | What it is |
+|---|---|
+| `_id_mapper/` | The native id-mapper's mmap files (large fixed virtual size) |
+| `_blobs/` | Native index files (vector base, segments) — may be mmap-backed |
+
+Everything else (entities, `_system`, `_generations`, `_cas` content blobs) is ordinary dense data.
+
+## Doing it right
+
+**tar** — the `S` flag detects holes and stores only real data:
+
+```bash
+tar czSf brain-backup.tgz /data/brain
+# restore preserves the holes:
+tar xzSf brain-backup.tgz -C /data/
+```
+
+**rsync**:
+
+```bash
+rsync -a --sparse /data/brain/ backup-host:/backups/brain/
+```
+
+**cp**:
+
+```bash
+cp -a --sparse=always /data/brain /backups/brain
+```
+
+**What goes wrong without the flag:** the copy *materializes* every hole as real zero bytes.
+A store whose apparent size exceeds the target disk fails with `ENOSPC` partway through — and a
+copy that *does* fit silently costs the full apparent size in storage and transfer time.
+
+## What the built-in paths do (so you don't have to)
+
+- **`db.persist(path)`** snapshots via **hard links** — instant and space-shared, since every data
+ file is immutable-by-rename. The handful of append-in-place files (the transaction log, the
+ commit fact log's tail segment) and mmap-mutated directories (`_id_mapper/`) are **byte-copied**
+ instead, so a post-snapshot write can never reach through a shared inode into your backup.
+- **`brain.restore(path, { confirm: true })`** is **non-destructive and sparse-aware**: the snapshot
+ is copied into a staging area *before* any live data is touched (all-zero blocks stay holes), and
+ only after the copy fully succeeds does an atomic swap move it into place. A failed copy —
+ including `ENOSPC` — leaves the live store exactly as it was. A crash mid-swap completes forward
+ on the next open.
+
+## Live-store caveats for external tools
+
+1. **Prefer snapshotting a `persist()` output, not the live directory.** `persist()` produces a
+ crash-consistent, immutable snapshot; running `tar` against a live, actively-written directory
+ can capture a torn mid-write state. If you must archive live, stop writes first (or accept that
+ the archive is only as consistent as the moment's flush state).
+2. **Never prune or "clean up" files inside a brain directory.** Index files that look stale or
+ redundant are load-bearing; the store protects its declared index families from in-process
+ deletion, but an external `rm` bypasses that fence. If space is the concern, `du -sh` first —
+ the allocated size is usually far smaller than it looks.
+3. **Verify restores by opening them.** `Brainy.load(path)` opens any snapshot or restored
+ directory read-only — the store verifies its own coherence at open and reports loudly if
+ anything is missing or torn.
diff --git a/docs/guides/find-limits.md b/docs/guides/find-limits.md
index 66418b80..4c7fd252 100644
--- a/docs/guides/find-limits.md
+++ b/docs/guides/find-limits.md
@@ -40,6 +40,10 @@ Brainy picks `maxLimit` from the first of these that's available:
Worked example: a 4 GB Cloud Run container picks priority 3 → `floor(4 GB × 0.25 / 25 KB) = floor(40 960) = 40 000` results. A 900 MB free-memory box on priority 4 gets `floor(900 MB / 25 KB) = ~36 000`.
+The cap is fixed at construction and never changes at runtime. Query timing is recorded
+for diagnostics only — a burst of slow queries cannot silently shrink the cap, and the
+auto-detected tiers (3 and 4) never go below a floor of 10 000.
+
> **Calibration note.** Pre-7.30.2 used 100 KB per result instead of 25 KB, which produced caps that were 4× too tight for typical workloads (an 8 KB / result reality). 7.30.2 recalibrated to match observed entity sizes; existing `limit: 10_000` safety patterns now pass silently on any reasonably-sized box.
## What happens when you exceed the cap
diff --git a/docs/guides/import-anything.md b/docs/guides/import-anything.md
index e0cd94ed..b1bb15ef 100644
--- a/docs/guides/import-anything.md
+++ b/docs/guides/import-anything.md
@@ -300,7 +300,10 @@ await brain.import(data, {
// Deduplication
enableDeduplication: true, // Check for duplicate entities (default: true)
deduplicationThreshold: 0.85, // Similarity threshold for duplicates (0-1, default: 0.85)
- // Note: Auto-disabled for imports >100 entities
+ // Notes: false disables BOTH the inline merge and the background pass that
+ // runs ~5 min after the last import (merged duplicates are deleted).
+ // The inline pass auto-disables for imports >100 entities (O(n²) cost);
+ // the background pass still covers those unless the flag is false.
// Performance
chunkSize: 100, // Batch size for processing (default: varies by operation)
diff --git a/docs/guides/import-quick-reference.md b/docs/guides/import-quick-reference.md
index 5a850a86..7837d49e 100644
--- a/docs/guides/import-quick-reference.md
+++ b/docs/guides/import-quick-reference.md
@@ -86,11 +86,22 @@ await brain.import(file, {
```typescript
await brain.import(file, {
- enableDeduplication: true, // Check for duplicates (default: false)
+ enableDeduplication: true, // Check for duplicates (default: true)
deduplicationThreshold: 0.85 // Similarity threshold (default: 0.85)
})
```
+Deduplication merges entities judged duplicates — the non-primary records are
+**deleted**. Set `enableDeduplication: false` to disable it entirely: the flag
+gates both the inline merge during import and the background pass that runs
+about 5 minutes after the last import.
+
+```typescript
+await brain.import(file, {
+ enableDeduplication: false // No merging, inline or background
+})
+```
+
### Import Tracking
Track and organize imports by project:
diff --git a/docs/guides/inspection.md b/docs/guides/inspection.md
index 015c5118..240e81ae 100644
--- a/docs/guides/inspection.md
+++ b/docs/guides/inspection.md
@@ -166,6 +166,34 @@ brainy inspect diff /data/brain-prod /data/brain-staging
Sample-based — for a full diff, dump both with `inspect dump` and compare
the JSONL.
+## Auditing graph-read truth
+
+`brain.auditGraph()` (8.6.0+) proves — or disproves — that relationship reads
+return canonical truth on a given brain, without mutating anything. It walks
+every stored relationship record, asks the same read path your application
+uses (`related()`, VFS `readdir`) with every visibility tier included, and
+classifies every discrepancy:
+
+```typescript
+const report = await brain.auditGraph()
+
+report.coherent // true = related()/readdir can be trusted on this brain
+report.missingFromReadsCount // records the read path omits — stale index
+report.danglingEndpointsCount // relationships whose endpoint entity is gone
+report.readOnlyCount // read-path edges with NO stored record — ghosts
+report.visibilityHiddenCount // internal/system edges hidden by design (not a fault)
+```
+
+Counts are always exact; the example lists (`missingFromReads`,
+`danglingEndpoints`, `readOnlyVerbIds`) are capped at `maxExamples`
+(default 100) and `truncatedExamples` says so when they are.
+
+Run it after any engine upgrade, restore, or migration. If it reports
+discrepancies, run `brain.repairIndex()` and audit again — a `coherent`
+report after the repair is the verified statement that the heal worked.
+Cost: one relationship-record walk plus one indexed read per distinct
+source entity — safe on a live brain.
+
## Repairing a corrupted store
If invariants fail and you suspect index corruption, `inspect repair`
diff --git a/docs/guides/installation.md b/docs/guides/installation.md
index e1bfb2b6..0a36f632 100644
--- a/docs/guides/installation.md
+++ b/docs/guides/installation.md
@@ -45,20 +45,20 @@ console.log('Brainy ready.')
## Native Acceleration (Optional)
-For production workloads, add Cortex for Rust-accelerated SIMD distance calculations and native embeddings:
+For production workloads, add Cor for Rust-accelerated SIMD distance calculations and native embeddings:
```bash
-npm install @soulcraft/cortex
+npm install @soulcraft/cor
```
```typescript
import { Brainy } from '@soulcraft/brainy'
-const brain = new Brainy({ plugins: ['@soulcraft/cortex'] })
+const brain = new Brainy({ plugins: ['@soulcraft/cor'] })
await brain.init() // native providers registered during init
```
-Cortex registers native (Rust/SIMD) vector, metadata, and graph engines behind the same Brainy `find()` API — no code changes, an optional dependency for production-scale workloads.
+Cor registers native (Rust/SIMD) vector, metadata, and graph engines behind the same Brainy `find()` API — no code changes, an optional dependency for production-scale workloads.
## Server-only since 8.0
diff --git a/docs/guides/model-loading.md b/docs/guides/model-loading.md
index 415fe2e3..e5b7b1d6 100644
--- a/docs/guides/model-loading.md
+++ b/docs/guides/model-loading.md
@@ -35,15 +35,18 @@ This single WASM file contains everything needed for sentence embeddings.
### Bun (Recommended)
-```typescript
-// Works with Bun runtime
+```bash
+# Bun as a runtime — supported and recommended
+bun add @soulcraft/brainy
bun run server.ts
-
-// Works with bun --compile (single binary deployment!)
-bun build --compile --target=bun server.ts
-./server // Self-contained binary with embedded model
```
+Brainy is pure WebAssembly with no native binaries, so the module graph stays
+bundler-friendly. Single-binary `bun build --compile` is **not a supported
+target** at present: Bun 1.3.10 has a `--compile` codegen regression
+(`__promiseAll is not defined`) triggered by top-level `await` in the bundled
+graph. Run Brainy under the Bun runtime (above) instead.
+
### Node.js
```typescript
@@ -164,7 +167,7 @@ await brain.init()
**What's new:**
- Faster initialization
-- Works with `bun --compile`
+- Bundler-friendly (pure WASM, no native binaries)
- No network requirements
### From Custom Embedding Functions
@@ -216,9 +219,8 @@ export { brain }
### Deployment
```bash
-# Option 1: Bun compile (single binary)
-bun build --compile server.ts
-./server # Contains everything
+# Option 1: Bun runtime
+bun run server.ts
# Option 2: Docker
docker build -t my-app .
diff --git a/docs/guides/optimistic-concurrency.md b/docs/guides/optimistic-concurrency.md
index a21a8af0..268bc5fa 100644
--- a/docs/guides/optimistic-concurrency.md
+++ b/docs/guides/optimistic-concurrency.md
@@ -180,3 +180,35 @@ Brainy 8.0 has exactly two write-coordination counters, at two granularities:
They compose: a `transact()` batch can carry per-entity `ifRev` checks *and* a whole-store `ifAtGeneration`; any failed check rejects the entire batch before anything is staged. Generations also power snapshots and time travel (`brain.now()`, `brain.asOf()`, `db.persist()`) — see the [consistency model](../concepts/consistency-model.md) and [Snapshots & Time Travel](./snapshots-and-time-travel.md).
A snapshot or historical view captures each entity *including* its `_rev` at that moment, so reading the past and writing back with `ifRev` against the live state works exactly as you'd hope: the write fails if the entity moved since the state you copied from.
+
+## The transact envelope: batch size, budget, and bulk imports
+
+`transact()` applies its batch atomically under one commit — which means the whole batch
+shares one **apply budget**. Since 8.7.0 the budget scales with the batch:
+`max(30 s, opCount × 2 s)`, or exactly what you pass as `timeoutMs`. A tripped budget rolls
+the entire batch back (nothing partial survives) and throws a retryable
+`TransactionTimeoutError` that names the operation it stopped at, the batch size, and the
+elapsed vs budgeted time — a diagnosis, not just a failure:
+
+```
+Transaction timed out at operation 41/120 ('add') — 246012ms elapsed, budget 240000ms.
+The batch rolled back atomically; retry with a higher timeoutMs or a smaller batch.
+```
+
+Practical envelope guidance for bulk work:
+
+1. **Precompute embeddings outside the commit path.** Embedding inside `transact()` spends
+ the budget on model inference. Use `brain.embedBatch(texts)` and pass each vector via
+ the op's `vector` field — the commit then pays only storage costs, and a retried batch
+ never re-pays inference. (The win is *where* the inference happens, not raw embedding
+ throughput: on the default WASM engine, batch and sequential embedding measure
+ comparably, ~160 ms/text; native embedding providers may batch faster.)
+2. **Chunk very large imports** into batches of a few hundred ops with one `transact()`
+ each. You lose whole-import atomicity but keep per-chunk atomicity, bounded memory, and
+ resumability — pair with `ifAbsent` upserts so a retried chunk is idempotent.
+3. **Slow disks change the math, not the contract.** On network-attached storage a single
+ op can cost ~2 s (canonical write + fsync + index maintenance). The scaled default
+ absorbs that; pass an explicit `timeoutMs` only when you know better than the scale.
+4. **`addMany`/`relateMany` are the convenience tier** — they chunk and batch-embed for
+ you, with per-item error reporting instead of batch atomicity. Choose by what you need:
+ atomic-all-or-nothing → `transact()`; resilient bulk load → `addMany`.
diff --git a/docs/guides/quick-start.md b/docs/guides/quick-start.md
index 2a689bd3..097c55fe 100644
--- a/docs/guides/quick-start.md
+++ b/docs/guides/quick-start.md
@@ -60,17 +60,17 @@ const nextId: string = await brain.add({
await brain.relate({
from: nextId,
to: reactId,
- type: VerbType.BuiltOn
+ type: VerbType.DependsOn
})
```
## 5. Query with Triple Intelligence
```typescript
-import type { FindResult } from '@soulcraft/brainy'
+import type { Result } from '@soulcraft/brainy'
// All three search paradigms in one call
-const results: FindResult[] = await brain.find({
+const results: Result[] = await brain.find({
query: 'modern frontend frameworks', // Vector similarity search
where: { year: { greaterThan: 2015 } }, // Metadata filtering
connected: { to: reactId, depth: 2 } // Graph traversal
diff --git a/docs/guides/reacting-to-changes.md b/docs/guides/reacting-to-changes.md
new file mode 100644
index 00000000..7367a5e3
--- /dev/null
+++ b/docs/guides/reacting-to-changes.md
@@ -0,0 +1,117 @@
+---
+title: Reacting to Changes
+slug: guides/reacting-to-changes
+public: true
+category: guides
+template: guide
+order: 11
+description: Subscribe to every committed mutation with `brain.onChange` — the in-process change feed behind live UIs, cache invalidation, and realtime sync. Covers the event shape, delivery guarantees, and catch-up patterns.
+next:
+ - guides/optimistic-concurrency
+ - guides/snapshots-and-time-travel
+---
+
+# Reacting to Changes
+
+`brain.onChange(cb)` is Brainy's in-process change feed: subscribe once and
+receive one event per committed mutation — **every** mutation, regardless of
+how it happened. Direct calls, batch methods, `transact()`, imports, and
+Virtual Filesystem writes all funnel through the same commit point the feed is
+emitted from, so nothing slips past it.
+
+```ts
+const off = brain.onChange((e) => {
+ if (e.kind === 'entity') {
+ console.log(`${e.op} ${e.entity?.type} ${e.id} @ generation ${e.generation}`)
+ }
+})
+
+await brain.add({ data: 'Ada Lovelace', type: 'person' })
+// → "add person 0198... @ generation 42"
+
+off() // unsubscribe when done
+```
+
+## The event
+
+```ts
+interface BrainyChangeEvent {
+ kind: 'entity' | 'relation' | 'store'
+ op: 'add' | 'update' | 'remove' | 'relate' | 'unrelate' | 'updateRelation'
+ | 'clear' | 'restore'
+ id?: string
+ entity?: { id: string; type: string; subtype?: string;
+ metadata: Record; service?: string }
+ relation?: { id: string; from: string; to: string; type: string;
+ metadata?: Record }
+ generation?: number
+ timestamp: number
+}
+```
+
+- **Entity events** (`add` / `update` / `remove`) carry the post-commit indexed
+ view — `type`, `subtype`, and the full custom `metadata`, so you can match
+ your own `where`-style filters against events without a read.
+- **Deletes are fully described.** A `remove` or `unrelate` event carries the
+ record's *last committed state* (sourced from the commit's own history
+ record), not just an id.
+- **Batches emit per item.** `addMany` / `updateMany` / `relateMany` /
+ `removeMany` emit one event per affected record; a `transact()` batch emits
+ one event per item, all sharing the batch's single `generation`.
+- **Cascades are visible.** Removing an entity also emits `unrelate` for each
+ relationship the delete cascaded to.
+- **Store-level events** (`kind: 'store'`) fire for the two wholesale
+ operations — `clear()` and `restore()` — and mean *"everything may have
+ changed; refetch what you care about."*
+
+## Delivery guarantees
+
+- **Post-commit only.** An aborted write — a losing
+ [`ifRev` compare-and-swap](optimistic-concurrency.md), a rejected
+ transaction — never emits. If you received the event, the write is durable.
+- **Commit-ordered.** Events arrive in the order writes committed;
+ `generation` is monotonic.
+- **Asynchronous, never blocking.** Delivery happens in a microtask after the
+ write completes. A slow listener cannot delay a write; a throwing listener
+ is logged and isolated from other listeners.
+- **Zero overhead when unused.** With no subscribers, the write path does no
+ event work at all.
+- **Fire-and-forget.** There is no replay or backpressure. For catch-up after
+ a disconnect, use the `generation` on each event together with
+ [`asOf()` / the transaction log](snapshots-and-time-travel.md): record the
+ last generation you processed, and on reconnect diff from there. For file
+ content specifically, `vfs.readFile(path, { asOf })` and
+ `vfs.history(path)` are the temporal read — see
+ [Snapshots & Time Travel](snapshots-and-time-travel.md).
+
+## Patterns
+
+**Cache invalidation** — drop cached reads for whatever changed:
+
+```ts
+brain.onChange((e) => {
+ if (e.kind === 'store') return cache.clear()
+ if (e.id) cache.delete(e.id)
+})
+```
+
+**Live queries (notify-and-refetch)** — re-run a query when a relevant change
+lands, rather than diffing incrementally:
+
+```ts
+brain.onChange((e) => {
+ if (e.kind === 'entity' && e.entity?.type === 'order') {
+ refreshOpenOrdersView() // debounce as needed
+ }
+})
+```
+
+**Forwarding to other processes** — the feed is in-process by design. To push
+changes to browsers or other services, forward events through your own
+transport (WebSocket, SSE) from the process that owns the brain.
+
+## Lifecycle
+
+`onChange` returns an unsubscribe function — call it when tearing down a
+subscriber (for example, when evicting a pooled instance). `brain.close()`
+drops all listeners; no events are delivered for or after `close()`.
diff --git a/docs/guides/snapshots-and-time-travel.md b/docs/guides/snapshots-and-time-travel.md
index d9f0e264..490aecab 100644
--- a/docs/guides/snapshots-and-time-travel.md
+++ b/docs/guides/snapshots-and-time-travel.md
@@ -9,6 +9,7 @@ description: Recipes for the Db API — instant backups with persist(), restore,
next:
- concepts/consistency-model
- guides/optimistic-concurrency
+ - guides/external-backups
---
# Snapshots & Time Travel
@@ -41,6 +42,10 @@ bytes. Cross-device targets fall back to per-file byte copies, and
persisting an in-memory brain serializes it to the same directory layout —
a real, durable store.
+> Archiving a brain directory with **external tools** (`tar`, `rsync`, `cp`)?
+> Some index files are sparse and can explode to their apparent size under a
+> naive copy — see [External Backups & Sparse Storage](/docs/guides/external-backups).
+
Two things to know:
- `persist()` requires the view to still be the store's **latest**
@@ -339,8 +344,12 @@ For per-entity write coordination (rather than whole-store history), the
## Keeping history bounded
Under Model-B every write is a generation, so history can grow quickly —
-Brainy auto-compacts on every `flush()`/`close()` under the **`retention`**
-knob (configured on the constructor):
+Brainy auto-compacts at `close()` (time-bounded per pass) under the
+**`retention`** knob (configured on the constructor). Since 8.9.0, `flush()`
+never compacts: flushing is durability work and costs only what the current
+window's writes cost, regardless of history backlog. A long-lived writer that
+never closes keeps its history until its next explicit `compactHistory()` —
+schedule one in your maintenance window if you run bounded retention:
```typescript
// Zero-config: ADAPTIVE — keep as much history as free disk/RAM allows,
@@ -354,10 +363,13 @@ new Brainy({ retention: 'all' })
new Brainy({ retention: { maxGenerations: 1000, maxAge: 7 * 86_400_000, maxBytes: 512 * 1024 ** 2 } })
```
-Reclaim manually at any time (the same caps):
+Reclaim manually at any time (the same caps, plus an optional per-pass time
+budget for maintenance windows — an early stop is a consistent prefix and the
+next pass resumes):
```typescript
await brain.compactHistory({ maxGenerations: 100, maxAge: 7 * 24 * 60 * 60 * 1000 })
+await brain.compactHistory({ maxBytes: 512 * 1024 ** 2, timeBudgetMs: 10_000 })
```
Compaction never breaks a pinned read — record-sets are reclaimed only when
@@ -366,6 +378,61 @@ are done with (including the ones `transact()` returns), and `persist()` any
generation you want to keep beyond the retention window: snapshots are
self-contained and unaffected by compaction.
+## Time travel for files (the VFS)
+
+Since 8.2.0, time travel covers Virtual Filesystem **content**, not just
+entity records. File bytes are retention-protected: a content blob referenced
+by any generation inside the retention window is never reclaimed, so reading
+the past always returns the exact bytes — never a stale field or a
+dangling hash.
+
+**`vfs.readFile(path, { asOf })`** takes a generation number or a `Date` and
+returns the file's exact bytes as they stood then. It resolves the path's
+current entity, then materializes its state at the target generation — so it
+answers *"what did the file at this path hold at that point?"* It bypasses
+the content cache; the `encoding` option still applies. Asking about a
+generation before the file existed throws the usual not-found error, and
+asking past the retention window's compaction horizon throws a
+compacted-generation error.
+
+**`vfs.history(path)`** returns the file's versions inside the retention
+window, oldest first — one `FileVersion` per generation that wrote the file,
+the newest entry being the current state:
+
+```typescript
+// A CMS page evolves…
+await brain.vfs.writeFile('/pages/home.json', '{"title":"Launch"}')
+await brain.vfs.writeFile('/pages/home.json', '{"title":"Launch v2"}')
+await brain.vfs.writeFile('/pages/home.json', '{"title":""}') // bad deploy!
+
+// Every version is listed and readable:
+const versions = await brain.vfs.history('/pages/home.json')
+// → [{ generation, timestamp, hash, size, mimeType? }, …] ascending
+
+const good = versions[versions.length - 2]
+const bytes = await brain.vfs.readFile('/pages/home.json', {
+ asOf: good.generation
+})
+
+// Restore = write the old bytes back. This is a NEW write (a new
+// generation) — history is never rewritten, so the bad version stays
+// visible in the audit trail.
+await brain.vfs.writeFile('/pages/home.json', bytes)
+```
+
+Two lifecycle consequences worth stating plainly:
+
+- **Deleting or overwriting a file no longer frees its bytes immediately.**
+ Old content lives until history compaction reclaims the generations that
+ reference it — the same `retention` budget that bounds all Model-B history
+ (and pinned views are exempt, exactly as above). Size your `retention` for
+ the file-version depth you want; `retention: 'all'` keeps every version of
+ every file forever.
+- **After `compactHistory()` reclaims a generation, its file versions are
+ gone** and their bytes are physically reclaimed. (This also fixed a
+ pre-8.2.0 defect where overwritten content was never reclaimed at all — an
+ unbounded silent leak.)
+
## From branches to values
If you used the pre-8.0 `fork`/`checkout`/`commit`/`versions` surface, every
diff --git a/docs/guides/subtypes-and-facets.md b/docs/guides/subtypes-and-facets.md
index 0b38b146..ff5de320 100644
--- a/docs/guides/subtypes-and-facets.md
+++ b/docs/guides/subtypes-and-facets.md
@@ -332,7 +332,7 @@ await brain.updateRelation({ id: relationId, weight: 0.5, confidence: 0.9 })
`find({ connected, subtype })` filters traversal edges by their subtype. Composes with `via` (verb-type filter):
```typescript
-// All direct reports two hops deep (will be supported in Cortex; for now depth-1)
+// All direct reports two hops deep (will be supported in Cor; for now depth-1)
const directChain = await brain.find({
connected: {
from: ceoId,
@@ -343,7 +343,7 @@ const directChain = await brain.find({
})
```
-Multi-hop subtype filtering (`depth > 1`) lights up on the Cortex native path; the JS path throws today rather than return incorrect partial results.
+Multi-hop subtype filtering (`depth > 1`) lights up on the Cor native path; the JS path throws today rather than return incorrect partial results.
### Counts
diff --git a/docs/guides/upgrading-7-to-8.md b/docs/guides/upgrading-7-to-8.md
new file mode 100644
index 00000000..a3c64fb9
--- /dev/null
+++ b/docs/guides/upgrading-7-to-8.md
@@ -0,0 +1,186 @@
+---
+title: Upgrading from 7.x to 8.0
+slug: guides/upgrading-7-to-8
+public: true
+category: guides
+template: guide
+order: 10
+description: What the one-time 7→8 on-disk migration does, how 8.0 automatically recovers Virtual Filesystem content that older layouts stored in the removed copy-on-write area, and how to verify (or force) that recovery.
+next:
+ - guides/storage-adapters
+ - guides/inspection
+---
+
+# Upgrading from 7.x to 8.0
+
+Opening a 7.x on-disk store with Brainy 8.0 runs a **one-time, in-place layout
+migration** the first time the store is opened. It is automatic (`autoMigrate`
+defaults to `true`), it runs once, and it stamps a marker so every later open is
+a no-op.
+
+Almost everything about the upgrade is transparent: your entities, relationships,
+metadata, and indexes migrate and rebuild without any action on your part. This
+guide covers the **one case that needs attention** — Virtual Filesystem (VFS)
+content — and how 8.0 recovers it for you.
+
+## TL;DR
+
+- **Just upgrade to `@soulcraft/brainy@8.0.12` (or later) and open the store.**
+ If a previous upgrade left VFS content stranded, 8.0.12 **heals it on open**,
+ with no operator action.
+- Want to force or script it? Call **`await brain.vfs.adoptOrphanedBlobs()`**.
+- The recovery is **non-destructive and idempotent** — it copies, never moves,
+ and running it twice is a no-op.
+
+## What the migration does
+
+The 7.x on-disk layout stored entities under a per-branch path
+(`branches//entities/...`). 8.0 uses a flat layout (`entities/...`). On
+first open, Brainy:
+
+1. Collapses `branches//entities/*` into the flat `entities/*` layout.
+2. Rebuilds the derived indexes (vector, graph, metadata) and count rollups from
+ the canonical entities.
+3. Stamps `_system/migration-layout.json` so re-opening is a no-op.
+4. Takes an automatic **pre-upgrade backup** of the directory before it starts,
+ and removes it once the upgrade is verified complete (see
+ [The safety net](#the-safety-net-pre-upgrade-backup) below).
+
+The log line to expect (once per store):
+
+```
+[brainy] Migrating a 7.x branch layout (branches/main) to the 8.0 flat layout
+ in place — 13175 entity files. This runs once; back up the directory first if
+ you need a rollback (8.0 does not keep the old layout).
+```
+
+## The one thing that needs recovery: VFS content
+
+If your application uses the Virtual Filesystem (VFS) to store file content (for
+example, a CMS that keeps page documents at paths like
+`/pages/homepage/page.json`), that content is held as **content blobs**.
+
+7.x kept those blobs in the branch system's **copy-on-write area** (`_cow/`).
+8.0 removed the branch/copy-on-write system, and its content blobs live in the
+content-addressed store (`_cas/`). The layout migration moves entities — it does
+**not** move the VFS content blobs. Left alone, a 7.x store's VFS blobs would
+remain in `_cow/`, and a read of one would throw:
+
+```
+VFS: Cannot read blob for /pages/homepage/page.json:
+ Blob metadata not found: 6b87cfb71ad2f04602a5c157214dc42000...
+```
+
+### 8.0.12 recovers them automatically
+
+Brainy 8.0.12 adds an **on-open recovery pass** that runs right after the layout
+migration. It scans `_cow/`, and for every content blob whose `blob:` +
+`blob-meta:` pair is not already in `_cas/`, it copies **both** across into the
+8.0 store. It:
+
+- **Heals a fresh 7→8 upgrade** (where `_cow/` still holds the blobs), **and**
+- **Heals a store already upgraded by an earlier 8.0.x** that stranded them —
+ the recovery is gated on the presence of `_cow/` and its own marker, not on
+ the layout-migration marker, so an already-migrated store is still healed.
+- Is a **cheap no-op** on a native-8.0 or fresh store (there is no `_cow/`), and
+ on a store already healed (a marker records completion so later opens skip the
+ scan).
+
+So the operator action for a stranded store is simply: **upgrade to 8.0.12 and
+open it.**
+
+```ts
+import { Brainy } from '@soulcraft/brainy'
+
+// Opening the store is all that is required — recovery runs during init().
+const brain = new Brainy({ storage: { type: 'filesystem', path: '/data/my-store' } })
+await brain.init()
+
+// The previously-failing read now succeeds.
+const page = await brain.vfs.readFile('/pages/homepage/page.json')
+```
+
+On a store that needed recovery you will see:
+
+```
+[brainy] Recovered 337 VFS content blob(s) stranded by a 7→8 upgrade
+ (adopted _cow/ → _cas/ in place).
+```
+
+### Forcing recovery explicitly
+
+If you would rather run the recovery deliberately (for example, in an upgrade
+script that asserts a clean result before flipping traffic), call it directly:
+
+```ts
+const result = await brain.vfs.adoptOrphanedBlobs()
+// → { cowBlobs, adopted, alreadyPresent, incomplete }
+console.log(`adopted ${result.adopted}, already present ${result.alreadyPresent}`)
+if (result.incomplete > 0) {
+ // One or more _cow/ blobs are missing their bytes or metadata — investigate
+ // _cow/ before discarding your own backup. See "The safety net" below.
+}
+```
+
+`adoptOrphanedBlobs()` self-initializes the brain, so it is safe to call
+immediately after construction. It returns:
+
+| Field | Meaning |
+| ---------------- | ---------------------------------------------------------------- |
+| `cowBlobs` | Distinct content-blob hashes found in `_cow/`. |
+| `adopted` | Newly copied into `_cas/` on this call. |
+| `alreadyPresent` | Already in `_cas/` (a prior open or run adopted them). |
+| `incomplete` | A `_cow/` blob missing its bytes *or* its metadata — **skipped** rather than half-adopted. |
+
+## Verifying recovery
+
+After opening under 8.0.12, confirm a previously-failing path reads:
+
+```ts
+const content = await brain.vfs.readFile('/pages/homepage/page.json')
+console.log(content.toString().slice(0, 80))
+```
+
+If you scripted it with `adoptOrphanedBlobs()`, a clean result is
+`incomplete === 0` and `adopted + alreadyPresent === cowBlobs`.
+
+## The safety net: pre-upgrade backup
+
+Brainy takes an automatic pre-upgrade backup and removes it once the upgrade is
+**verified complete**. In 8.0.12 that verification includes VFS content: if the
+blob recovery reports `incomplete > 0`, the completion marker is **not** stamped
+(the next open retries) and the **pre-upgrade backup is retained** so you still
+have a rollback while blobs remain unaccounted for. You will see:
+
+```
+[brainy] VFS blob recovery adopted N blob(s) but M orphaned _cow/ blob(s) are
+ missing their bytes or metadata and were left in place. The pre-upgrade backup
+ is being retained; inspect _cow/ before discarding it.
+```
+
+The recovery never deletes anything from `_cow/`, so the original blobs stay in
+place for inspection or a manual rollback regardless.
+
+## Who is affected
+
+**Any 7.x store that used the VFS to store file content** (so it has a `_cow/`
+area) is a candidate for stranded blobs on a 7→8 upgrade. Stores that never used
+the VFS have no `_cow/` content blobs and are unaffected.
+
+Because the recovery is **gated on the presence of `_cow/`**, it is
+self-selecting and safe to roll out everywhere:
+
+- On a store with stranded blobs → it adopts them.
+- On a native-8.0, fresh, or non-VFS store → it is a no-op on the existence
+ check.
+
+There is no configuration to set and nothing to opt into. Upgrading to 8.0.12
+and opening each store is sufficient.
+
+## Rollback
+
+The recovery is copy-only, so no rollback of the recovery itself is ever needed.
+If you need to roll back the **whole** 7→8 upgrade, restore the directory from
+your pre-upgrade backup (retained automatically while recovery is incomplete, or
+your own snapshot) and pin `@soulcraft/brainy@7.x`. 8.0 does not keep the old
+branch layout in place, so a directory-level restore is the rollback path.
diff --git a/docs/performance-envelopes.md b/docs/performance-envelopes.md
new file mode 100644
index 00000000..d29677e3
--- /dev/null
+++ b/docs/performance-envelopes.md
@@ -0,0 +1,83 @@
+---
+title: Performance Envelopes
+slug: guides/performance-envelopes
+public: true
+category: guides
+template: guide
+order: 40
+description: Measured per-operation latency envelopes at stated scales — what to expect, on what hardware, and exactly how each number was produced.
+next:
+ - guides/find-limits
+---
+
+# Performance Envelopes
+
+Every number on this page is **measured, never projected** — produced by the script
+cited at the bottom, against the built package (the artifact you install), on the stated
+hardware. Each entry says what was measured, at what scale, on which storage backend.
+When a release touches a measured path, that operation is re-measured and this page
+updates in the same release.
+
+Two scopes to keep straight:
+
+- **These envelopes are the pure-JS engine** (no native accelerator registered) on
+ filesystem storage. This is the floor every deployment gets from `npm install` alone.
+- **Accelerated deployments** (the optional native provider) publish their own numbers —
+ this page never claims them.
+
+## Read operations
+
+Reads are where the architecture pays off: after the write path has done its indexing
+work, queries answer from purpose-built indexes without scanning.
+
+| Operation | 1,000 entities | 10,000 entities | Notes |
+|---|---|---|---|
+| `get(id)` (warm) | p50 < 0.1ms | p50 < 0.1ms | served from cache/metadata index |
+| `find` (metadata: indexed equality + range, limit 100) | p50 1.0ms · p95 1.8ms | p50 7.0ms · p95 8.9ms | column-store bitmap paths |
+| `related(id)` (per-node adjacency) | p50 < 0.1ms · p95 0.2ms | p50 < 0.1ms | LSM adjacency index — O(degree), scale-independent |
+| `find` (semantic: embed + HNSW, 1k docs) | p50 178ms · p95 393ms | — | dominated by WASM query embedding (measured on a machine under concurrent load — treat the p95 as an upper bound); the vector search itself is single-digit ms |
+
+## Write operations
+
+Under Model-B **every write is its own durable generation** — a single-op `add` pays
+serialization, before-image staging, and fsync before it acks. That durability is priced
+into the write path visibly, by design:
+
+| Operation | 1,000 entities | 10,000 entities | Notes |
+|---|---|---|---|
+| `add` (single-op) | p50 167ms · p95 171ms | p50 165ms · p95 172ms | full durable generation per write — flat across scale |
+| `addMany` (bulk) | ~163ms/entity | ~187ms/entity | **currently per-item commits** — see the honest note below |
+| `relateMany` | ~0.8ms/edge | ~0.9ms/edge | edges batch efficiently today |
+| `flush` (steady-state, 1 pending write) | p50 8ms · p95 10ms | p50 45ms · p95 52ms | durability-only since 8.9.0 — cost no longer depends on history backlog or retention mode |
+
+**The honest note on bulk writes:** `addMany` today commits each item as its own
+generation (the same durability as single-op `add`, serialized by the single-writer
+lock), so bulk-load cost is N × single-op cost. Batched chunk commits (one generation
+and one fsync window per chunk, as `removeMany` already does) are designed into the
+unified-commit work on the current roadmap. Until that ships, size bulk imports
+accordingly — 10k entities is minutes, not seconds, on filesystem storage.
+
+## Open / close
+
+| Operation | 1,000 entities | 10,000 entities | Notes |
+|---|---|---|---|
+| `open` (empty store) | ~560ms | ~190ms | includes embedder initialization |
+| `open` (warm, populated, clean shutdown) | 763ms | 4.9s | pure-JS vector index load dominates and grows with entity count; the native accelerator exists precisely to remove this |
+| `close` | bounded | bounded | auto-compaction pass is time-bounded (~5s max) since 8.9.0 |
+
+A store that was NOT cleanly closed pays index rebuilds on top of the warm-open
+number (tens of seconds at 10k) — clean shutdown is worth engineering for.
+
+## How these were produced
+
+- **Hardware**: Intel Core i9-14900HX (32 threads), 62GB RAM, NVMe, Linux, Node v22.
+- **Backend**: `storage: { type: 'filesystem' }`, pure JS (no native providers).
+- **Embeddings**: deterministic stub for non-semantic ops (isolates engine cost);
+ the real WASM embedder for the semantic row (that's what you'll run).
+- **Method**: p50/p95 over 50–200 samples per op against the built `dist/`;
+ the measuring script ships in the repo history and re-runs per release.
+
+Numbers on different hardware will differ; the *shape* (sub-2ms indexed reads,
+~160ms embedding-bound semantic queries, durability-priced writes) is the envelope
+you should hold your deployment against. If your measurements diverge from these
+shapes by an order of magnitude, something is wrong — file it.
diff --git a/docs/vfs/PROJECTION_STRATEGY_API.md b/docs/vfs/PROJECTION_STRATEGY_API.md
index ab66e7c7..380862e1 100644
--- a/docs/vfs/PROJECTION_STRATEGY_API.md
+++ b/docs/vfs/PROJECTION_STRATEGY_API.md
@@ -289,7 +289,7 @@ export class SizeProjection extends BaseProjectionStrategy {
where: {
vfsType: 'file',
size: {
- greaterEqual: min,
+ gte: min,
lessThan: max
}
},
@@ -305,7 +305,7 @@ export class SizeProjection extends BaseProjectionStrategy {
where: {
vfsType: 'file',
size: {
- greaterEqual: min,
+ gte: min,
lessThan: max
}
},
@@ -359,7 +359,7 @@ export class StatusProjection extends BaseProjectionStrategy {
const results = await brain.find({
where: {
vfsType: 'file',
- modified: { greaterEqual: oneDayAgo },
+ modified: { gte: oneDayAgo },
reviewStatus: { missing: true } // No review status set
},
limit: 1000
@@ -387,7 +387,7 @@ export class StatusProjection extends BaseProjectionStrategy {
vfsType: 'file',
anyOf: [
{ reviewStatus: { exists: true } },
- { modified: { greaterEqual: Date.now() - 86400000 } }
+ { modified: { gte: Date.now() - 86400000 } }
]
},
limit
@@ -415,7 +415,7 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op
{ size: { $gte: 1000, $lte: 5000 } }
// ✅ BFO style (CORRECT)
-{ size: { greaterEqual: 1000, lessEqual: 5000 } }
+{ size: { gte: 1000, lte: 5000 } }
```
### Logical Operators
@@ -451,9 +451,9 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op
// Comparison
{ field: value } // Exact match
{ field: { greaterThan: 10 } } // >
-{ field: { greaterEqual: 10 } } // >=
+{ field: { gte: 10 } } // >=
{ field: { lessThan: 10 } } // <
-{ field: { lessEqual: 10 } } // <=
+{ field: { lte: 10 } } // <=
{ field: { not: value } } // !=
// Logical
@@ -485,7 +485,7 @@ All metadata fields are automatically indexed. Use direct equality or range quer
```typescript
// ✅ Fast: Direct index lookup (O(log n))
{ priority: 'high' }
-{ size: { greaterEqual: 1000 } }
+{ size: { gte: 1000 } }
// ⚠️ Slower: Must scan results
{ path: { matches: /complex-regex/ } }
@@ -668,7 +668,7 @@ async resolve(brain, vfs, period: string) {
const results = await brain.find({
where: {
- modified: { greaterEqual: since }
+ modified: { gte: since }
}
})
return this.extractIds(results)
diff --git a/docs/vfs/SEMANTIC_VFS.md b/docs/vfs/SEMANTIC_VFS.md
index 40807d3c..9298c822 100644
--- a/docs/vfs/SEMANTIC_VFS.md
+++ b/docs/vfs/SEMANTIC_VFS.md
@@ -127,7 +127,7 @@ await vfs.readFile('/as-of/2024-03-15/auth.ts')
// the path only resolves if auth.ts was modified that day
```
-**How it works:** Tracks the `modified` timestamp on every file and runs a range query (`greaterEqual`/`lessEqual`) over one 24-hour window for O(log n) performance. The VFS does not store historical file contents — `/as-of/` filters by *when a file last changed*; reads return the current bytes. For point-in-time state, use the Db API (`brain.asOf(generation)`).
+**How it works:** Tracks the `modified` timestamp on every file and runs a range query (`gte`/`lte`) over one 24-hour window for O(log n) performance. The VFS does not store historical file contents — `/as-of/` filters by *when a file last changed*; reads return the current bytes. For point-in-time state, use the Db API (`brain.asOf(generation)`).
**Status:** ✅ Fully implemented and tested at 10K file scale
diff --git a/examples/directory-import-with-caching.ts b/examples/directory-import-with-caching.ts
deleted file mode 100644
index d3395b5a..00000000
--- a/examples/directory-import-with-caching.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-/**
- * Directory Import with Entity Extraction Caching Example
- *
- * Demonstrates:
- * - Importing directories with progress tracking
- * - Entity extraction caching for performance
- * - Relationship detection with confidence scores
- * - Cache statistics monitoring
- */
-
-import { Brainy, NounType, VerbType } from '../src/brainy.js'
-import { DirectoryImporter } from '../src/vfs/importers/DirectoryImporter.js'
-import { ProgressTracker, formatProgress } from '../src/types/progress.types.js'
-import { detectRelationshipsWithConfidence } from '../src/neural/relationshipConfidence.js'
-import { NeuralEntityExtractor } from '../src/neural/entityExtractor.js'
-
-async function main() {
- console.log('🧠 Brainy 3.21.0 - Directory Import with Caching Example\n')
-
- // Initialize Brainy
- const brain = new Brainy({ verbose: false })
- await brain.init()
-
- console.log('✅ Brainy initialized\n')
-
- // The entity extractor (and its extraction cache) is constructed directly.
- const extractor = new NeuralEntityExtractor(brain)
-
- // Example 1: Import directory with entity extraction caching
- console.log('📁 Example 1: Import Directory with Caching\n')
-
- const vfs = brain.vfs
- const importer = new DirectoryImporter(vfs, brain)
-
- // Progress tracking
- const tracker = ProgressTracker.create(100)
- tracker.start()
-
- try {
- // Import with progress (using async generator)
- console.log('Importing directory...')
-
- let filesProcessed = 0
- for await (const progress of importer.importStream('./examples', {
- batchSize: 10,
- recursive: true,
- generateEmbeddings: true,
- extractMetadata: true
- })) {
- if (progress.type === 'progress') {
- filesProcessed = progress.processed
- const trackedProgress = tracker.update(progress.processed, progress.current)
- console.log(` ${formatProgress(trackedProgress)}`)
- } else if (progress.type === 'complete') {
- console.log(`\n✅ Import complete! Processed ${progress.processed} files\n`)
- } else if (progress.type === 'error') {
- console.error(`❌ Error: ${progress.error?.message}`)
- }
- }
-
- tracker.complete({ filesProcessed })
-
- } catch (error) {
- console.error('Import failed:', error)
- }
-
- // Example 2: Entity extraction with caching
- console.log('\n📝 Example 2: Entity Extraction with Caching\n')
-
- const sampleText = `
- John Smith created the user authentication system for the application.
- The authentication system uses JWT tokens and bcrypt for password hashing.
- Mary Johnson manages the backend team that maintains the system.
- The system was built using Node.js and PostgreSQL database.
- `
-
- console.log('First extraction (cache miss):')
- const startTime1 = Date.now()
- const entities1 = await extractor.extract(sampleText, {
- types: [NounType.Person, NounType.Service, NounType.Technology],
- confidence: 0.7,
- cache: {
- enabled: true,
- ttl: 7 * 24 * 60 * 60 * 1000, // 7 days
- invalidateOn: 'hash'
- }
- })
- const time1 = Date.now() - startTime1
- console.log(` Extracted ${entities1.length} entities in ${time1}ms`)
- console.log(` Entities: ${entities1.map(e => e.text).join(', ')}\n`)
-
- console.log('Second extraction (cache hit):')
- const startTime2 = Date.now()
- const entities2 = await extractor.extract(sampleText, {
- types: [NounType.Person, NounType.Service, NounType.Technology],
- confidence: 0.7,
- cache: {
- enabled: true,
- invalidateOn: 'hash'
- }
- })
- const time2 = Date.now() - startTime2
- console.log(` Extracted ${entities2.length} entities in ${time2}ms`)
- console.log(` Speedup: ${Math.round(time1 / time2)}x faster!\n`)
-
- // Show cache statistics
- const cacheStats = extractor.getCacheStats()
- console.log('📊 Cache Statistics:')
- console.log(` Hits: ${cacheStats.hits}`)
- console.log(` Misses: ${cacheStats.misses}`)
- console.log(` Hit Rate: ${(cacheStats.hitRate * 100).toFixed(1)}%`)
- console.log(` Total Entries: ${cacheStats.totalEntries}`)
- console.log(` Avg Entities per Entry: ${cacheStats.averageEntitiesPerEntry}\n`)
-
- // Example 3: Relationship detection with confidence
- console.log('🔗 Example 3: Relationship Detection with Confidence\n')
-
- const relationships = detectRelationshipsWithConfidence(
- entities1,
- sampleText,
- {
- minConfidence: 0.6,
- maxDistance: 100,
- useProximityBoost: true,
- usePatternMatching: true,
- useStructuralAnalysis: true
- }
- )
-
- console.log(`Detected ${relationships.length} relationships:\n`)
- for (const rel of relationships.slice(0, 5)) { // Show top 5
- console.log(` ${rel.sourceEntity.text} --[${rel.verbType}]--> ${rel.targetEntity.text}`)
- console.log(` Confidence: ${(rel.confidence * 100).toFixed(1)}%`)
- console.log(` Evidence: ${rel.evidence.reasoning}`)
- console.log(` Method: ${rel.evidence.method}`)
- console.log(` Source: "${rel.evidence.sourceText?.substring(0, 60)}..."\n`)
- }
-
- // Example 4: Create relationships in graph with confidence
- console.log('📊 Example 4: Creating Relationships in Graph\n')
-
- const createdRelations = []
- for (const rel of relationships.slice(0, 3)) { // Create top 3
- try {
- // Add entities to brain
- const sourceId = await brain.add({
- data: rel.sourceEntity.text,
- type: rel.sourceEntity.type,
- metadata: {
- confidence: rel.sourceEntity.confidence,
- extractedFrom: 'sample text'
- }
- })
-
- const targetId = await brain.add({
- data: rel.targetEntity.text,
- type: rel.targetEntity.type,
- metadata: {
- confidence: rel.targetEntity.confidence,
- extractedFrom: 'sample text'
- }
- })
-
- // Create relationship with confidence
- const relationId = await brain.relate({
- from: sourceId,
- to: targetId,
- type: rel.verbType,
- confidence: rel.confidence,
- evidence: rel.evidence,
- metadata: {
- autoDetected: true,
- detectedAt: new Date().toISOString()
- }
- })
-
- createdRelations.push(relationId)
- console.log(` ✅ Created: ${rel.sourceEntity.text} → ${rel.targetEntity.text}`)
- } catch (error) {
- console.error(` ❌ Failed to create relationship:`, error)
- }
- }
-
- console.log(`\n✅ Created ${createdRelations.length} relationships in knowledge graph`)
-
- // Example 5: Query relationships by confidence
- console.log('\n🔍 Example 5: Query High-Confidence Relationships\n')
-
- const allRelations = await brain.getRelations({
- limit: 100
- })
-
- const highConfidence = allRelations.filter(r => (r.confidence || 0) >= 0.7)
- console.log(`Found ${highConfidence.length} high-confidence relationships (≥70%):\n`)
-
- for (const rel of highConfidence.slice(0, 5)) {
- console.log(` ${rel.from} → ${rel.to} (${rel.type})`)
- console.log(` Confidence: ${((rel.confidence || 0) * 100).toFixed(1)}%`)
- if (rel.evidence) {
- console.log(` Method: ${rel.evidence.method}`)
- console.log(` Reasoning: ${rel.evidence.reasoning}\n`)
- }
- }
-
- // Example 6: Cache management
- console.log('🧹 Example 6: Cache Management\n')
-
- console.log('Cache operations:')
-
- // Cleanup expired entries
- const cleaned = extractor.cleanupCache()
- console.log(` Cleaned ${cleaned} expired entries`)
-
- // Invalidate specific cache entry
- const invalidated = extractor.invalidateCache('hash:abc123')
- console.log(` Invalidated entry: ${invalidated}`)
-
- // Get final stats
- const finalStats = extractor.getCacheStats()
- console.log(` Final cache size: ${finalStats.totalEntries} entries`)
- console.log(` Memory used: ~${Math.round(finalStats.cacheSize / 1024)}KB`)
-
- // Clear all cache (optional)
- // extractor.clearCache()
- // console.log(' Cleared entire cache')
-
- console.log('\n✨ Example complete!')
- console.log('\n📚 Key Takeaways:')
- console.log(' • Entity extraction caching provides 10-100x speedup on repeated content')
- console.log(' • Progress tracking gives real-time feedback for long operations')
- console.log(' • Relationship confidence helps filter low-quality connections')
- console.log(' • Evidence tracking makes relationships explainable and debuggable')
- console.log(' • All features are opt-in and backward compatible')
-}
-
-// Run example
-main().catch(console.error)
diff --git a/package-lock.json b/package-lock.json
index bf8feba9..fb9262e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@soulcraft/brainy",
- "version": "8.0.0-rc.3",
+ "version": "8.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@soulcraft/brainy",
- "version": "8.0.0-rc.3",
+ "version": "8.9.0",
"license": "MIT",
"dependencies": {
"@msgpack/msgpack": "^3.1.2",
@@ -31,14 +31,10 @@
"brainy": "bin/brainy.js"
},
"devDependencies": {
- "@rollup/plugin-commonjs": "^28.0.6",
- "@rollup/plugin-node-resolve": "^16.0.1",
- "@rollup/plugin-replace": "^6.0.2",
- "@rollup/plugin-terser": "^0.4.4",
"@testcontainers/redis": "^11.5.1",
"@types/js-yaml": "^4.0.9",
"@types/mime": "^3.0.4",
- "@types/node": "^20.11.30",
+ "@types/node": "^22",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -46,7 +42,7 @@
"@vitest/coverage-v8": "^3.2.4",
"jspdf": "^3.0.3",
"minio": "^8.0.5",
- "standard-version": "^9.5.0",
+ "prettier": "^3.9.4",
"testcontainers": "^11.5.1",
"tsx": "^4.19.2",
"typescript": "^5.4.5",
@@ -54,8 +50,8 @@
"vitest": "^3.2.4"
},
"engines": {
- "bun": ">=1.0.0",
- "node": "22.x"
+ "bun": ">=1.1.0",
+ "node": ">=22"
}
},
"node_modules/@ampproject/remapping": {
@@ -72,28 +68,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/code-frame/node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -1178,16 +1152,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@hutson/parse-repository-url": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz",
- "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@inquirer/ansi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
@@ -1690,6 +1654,8 @@
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -2003,126 +1969,6 @@
"dev": true,
"license": "BSD-3-Clause"
},
- "node_modules/@rollup/plugin-commonjs": {
- "version": "28.0.9",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz",
- "integrity": "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rollup/pluginutils": "^5.0.1",
- "commondir": "^1.0.1",
- "estree-walker": "^2.0.2",
- "fdir": "^6.2.0",
- "is-reference": "1.2.1",
- "magic-string": "^0.30.3",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=16.0.0 || 14 >= 14.17"
- },
- "peerDependencies": {
- "rollup": "^2.68.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/plugin-node-resolve": {
- "version": "16.0.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
- "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rollup/pluginutils": "^5.0.1",
- "@types/resolve": "1.20.2",
- "deepmerge": "^4.2.2",
- "is-module": "^1.0.0",
- "resolve": "^1.22.1"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^2.78.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/plugin-replace": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz",
- "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rollup/pluginutils": "^5.0.1",
- "magic-string": "^0.30.3"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/plugin-terser": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
- "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "serialize-javascript": "^6.0.1",
- "smob": "^1.0.0",
- "terser": "^5.17.4"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
- "node_modules/@rollup/pluginutils": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
- "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^2.0.2",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
@@ -2511,30 +2357,16 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/node": {
- "version": "20.19.27",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
- "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
+ "version": "22.20.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz",
+ "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
- "node_modules/@types/normalize-package-data": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
- "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
@@ -2550,13 +2382,6 @@
"license": "MIT",
"optional": true
},
- "node_modules/@types/resolve": {
- "version": "1.20.2",
- "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
- "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/ssh2": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
@@ -3047,6 +2872,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3065,13 +2891,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/add-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz",
- "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
@@ -3251,13 +3070,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
- "node_modules/array-ify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
- "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -3622,7 +3434,9 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
"node_modules/buildcheck": {
"version": "0.0.7",
@@ -3727,34 +3541,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/camelcase-keys": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
- "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "^5.3.1",
- "map-obj": "^4.0.0",
- "quick-lru": "^4.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/camelcase-keys/node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
@@ -3945,97 +3731,6 @@
"node": ">= 12"
}
},
- "node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "node_modules/cliui/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/cliui/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cliui/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/codepage": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
@@ -4072,24 +3767,6 @@
"node": ">=16"
}
},
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/compare-func": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
- "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-ify": "^1.0.0",
- "dot-prop": "^5.1.0"
- }
- },
"node_modules/compress-commons": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
@@ -4129,302 +3806,8 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/concat-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
- "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
- "dev": true,
- "engines": [
- "node >= 6.0"
- ],
"license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.0.2",
- "typedarray": "^0.0.6"
- }
- },
- "node_modules/conventional-changelog": {
- "version": "3.1.25",
- "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz",
- "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "conventional-changelog-angular": "^5.0.12",
- "conventional-changelog-atom": "^2.0.8",
- "conventional-changelog-codemirror": "^2.0.8",
- "conventional-changelog-conventionalcommits": "^4.5.0",
- "conventional-changelog-core": "^4.2.1",
- "conventional-changelog-ember": "^2.0.9",
- "conventional-changelog-eslint": "^3.0.9",
- "conventional-changelog-express": "^2.0.6",
- "conventional-changelog-jquery": "^3.0.11",
- "conventional-changelog-jshint": "^2.0.9",
- "conventional-changelog-preset-loader": "^2.3.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-angular": {
- "version": "5.0.13",
- "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz",
- "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "compare-func": "^2.0.0",
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-atom": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz",
- "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-codemirror": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz",
- "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-config-spec": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz",
- "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/conventional-changelog-conventionalcommits": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz",
- "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "compare-func": "^2.0.0",
- "lodash": "^4.17.15",
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-core": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz",
- "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "add-stream": "^1.0.0",
- "conventional-changelog-writer": "^5.0.0",
- "conventional-commits-parser": "^3.2.0",
- "dateformat": "^3.0.0",
- "get-pkg-repo": "^4.0.0",
- "git-raw-commits": "^2.0.8",
- "git-remote-origin-url": "^2.0.0",
- "git-semver-tags": "^4.1.1",
- "lodash": "^4.17.15",
- "normalize-package-data": "^3.0.0",
- "q": "^1.5.1",
- "read-pkg": "^3.0.0",
- "read-pkg-up": "^3.0.0",
- "through2": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-ember": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz",
- "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-eslint": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz",
- "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-express": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz",
- "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-jquery": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz",
- "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-jshint": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz",
- "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "compare-func": "^2.0.0",
- "q": "^1.5.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-preset-loader": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz",
- "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-writer": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz",
- "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "conventional-commits-filter": "^2.0.7",
- "dateformat": "^3.0.0",
- "handlebars": "^4.7.7",
- "json-stringify-safe": "^5.0.1",
- "lodash": "^4.17.15",
- "meow": "^8.0.0",
- "semver": "^6.0.0",
- "split": "^1.0.0",
- "through2": "^4.0.0"
- },
- "bin": {
- "conventional-changelog-writer": "cli.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-changelog-writer/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/conventional-commits-filter": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz",
- "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lodash.ismatch": "^4.4.0",
- "modify-values": "^1.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-commits-parser": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz",
- "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-text-path": "^1.0.1",
- "JSONStream": "^1.0.4",
- "lodash": "^4.17.15",
- "meow": "^8.0.0",
- "split2": "^3.0.0",
- "through2": "^4.0.0"
- },
- "bin": {
- "conventional-commits-parser": "cli.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/conventional-recommended-bump": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz",
- "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "concat-stream": "^2.0.0",
- "conventional-changelog-preset-loader": "^2.3.4",
- "conventional-commits-filter": "^2.0.7",
- "conventional-commits-parser": "^3.2.0",
- "git-raw-commits": "^2.0.8",
- "git-semver-tags": "^4.1.1",
- "meow": "^8.0.0",
- "q": "^1.5.1"
- },
- "bin": {
- "conventional-recommended-bump": "cli.js"
- },
- "engines": {
- "node": ">=10"
- }
+ "peer": true
},
"node_modules/core-js": {
"version": "3.47.0",
@@ -4535,26 +3918,6 @@
"integrity": "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw==",
"license": "MIT"
},
- "node_modules/dargs": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
- "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dateformat": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
- "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -4573,43 +3936,6 @@
}
}
},
- "node_modules/decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/decamelize-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
- "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "decamelize": "^1.1.0",
- "map-obj": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/decamelize-keys/node_modules/map-obj": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
- "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
@@ -4638,16 +3964,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -4666,26 +3982,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/detect-indent": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
- "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/detect-newline": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dingbat-to-unicode": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
@@ -4795,123 +4091,6 @@
"@types/trusted-types": "^2.0.7"
}
},
- "node_modules/dot-prop": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
- "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-obj": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dotgitignore": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz",
- "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "find-up": "^3.0.0",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/dotgitignore/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/dotgitignore/node_modules/find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/dotgitignore/node_modules/locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/dotgitignore/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/dotgitignore/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/dotgitignore/node_modules/p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/dotgitignore/node_modules/path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/duck": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
@@ -4959,16 +4138,6 @@
"once": "^1.4.0"
}
},
- "node_modules/error-ex": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
- "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.2.1"
- }
- },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -5325,13 +4494,6 @@
"node": ">=4.0"
}
},
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5458,32 +4620,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/figures": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
- "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "escape-string-regexp": "^1.0.5"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/figures/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -5514,6 +4650,7 @@
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -5679,69 +4816,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-pkg-repo": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz",
- "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@hutson/parse-repository-url": "^3.0.0",
- "hosted-git-info": "^4.0.0",
- "through2": "^2.0.0",
- "yargs": "^16.2.0"
- },
- "bin": {
- "get-pkg-repo": "src/cli.js"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-pkg-repo/node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/get-pkg-repo/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/get-pkg-repo/node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/get-pkg-repo/node_modules/through2": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
- "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
- }
- },
"node_modules/get-port": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz",
@@ -5782,77 +4856,6 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
- "node_modules/git-raw-commits": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz",
- "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dargs": "^7.0.0",
- "lodash": "^4.17.15",
- "meow": "^8.0.0",
- "split2": "^3.0.0",
- "through2": "^4.0.0"
- },
- "bin": {
- "git-raw-commits": "cli.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/git-remote-origin-url": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz",
- "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "gitconfiglocal": "^1.0.0",
- "pify": "^2.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/git-semver-tags": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz",
- "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "meow": "^8.0.0",
- "semver": "^6.0.0"
- },
- "bin": {
- "git-semver-tags": "cli.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/git-semver-tags/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/gitconfiglocal": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz",
- "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==",
- "dev": true,
- "license": "BSD",
- "dependencies": {
- "ini": "^1.3.2"
- }
- },
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
@@ -5922,38 +4925,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/handlebars": {
- "version": "4.7.8",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
- "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.5",
- "neo-async": "^2.6.2",
- "source-map": "^0.6.1",
- "wordwrap": "^1.0.0"
- },
- "bin": {
- "handlebars": "bin/handlebars"
- },
- "engines": {
- "node": ">=0.4.7"
- },
- "optionalDependencies": {
- "uglify-js": "^3.1.4"
- }
- },
- "node_modules/hard-rejection": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
- "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -6019,19 +4990,6 @@
"node": ">= 0.4"
}
},
- "node_modules/hosted-git-info": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
- "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -6136,29 +5094,12 @@
"node": ">=0.8.19"
}
},
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
- "node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/inquirer": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz",
@@ -6219,13 +5160,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6239,22 +5173,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6321,43 +5239,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-module": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
- "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-obj": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
- "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-plain-obj": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
- "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-reference": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
- "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "*"
- }
- },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -6390,19 +5271,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-text-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
- "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "text-extensions": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
@@ -6541,20 +5409,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/json-parse-better-errors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
- "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6571,40 +5425,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/json-stringify-safe": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jsonparse": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
- "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
- "dev": true,
- "engines": [
- "node >= 0.2.0"
- ],
- "license": "MIT"
- },
- "node_modules/JSONStream": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
- "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
- "dev": true,
- "license": "(MIT OR Apache-2.0)",
- "dependencies": {
- "jsonparse": "^1.2.0",
- "through": ">=2.2.7 <3"
- },
- "bin": {
- "JSONStream": "bin.js"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/jspdf": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
@@ -6682,16 +5502,6 @@
"json-buffer": "3.0.1"
}
},
- "node_modules/kind-of": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
- "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -6771,45 +5581,13 @@
"immediate": "~3.0.5"
}
},
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/load-json-file": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
- "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "parse-json": "^4.0.0",
- "pify": "^3.0.0",
- "strip-bom": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/load-json-file/node_modules/pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -6834,13 +5612,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.ismatch": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
- "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -6902,19 +5673,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -6986,19 +5744,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/map-obj": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
- "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7009,204 +5754,6 @@
"node": ">= 0.4"
}
},
- "node_modules/meow": {
- "version": "8.1.2",
- "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
- "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/minimist": "^1.2.0",
- "camelcase-keys": "^6.2.2",
- "decamelize-keys": "^1.1.0",
- "hard-rejection": "^2.1.0",
- "minimist-options": "4.1.0",
- "normalize-package-data": "^3.0.0",
- "read-pkg-up": "^7.0.1",
- "redent": "^3.0.0",
- "trim-newlines": "^3.0.0",
- "type-fest": "^0.18.0",
- "yargs-parser": "^20.2.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/meow/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/hosted-git-info": {
- "version": "2.8.9",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
- "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/meow/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/meow/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/meow/node_modules/read-pkg": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
- "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/normalize-package-data": "^2.4.0",
- "normalize-package-data": "^2.5.0",
- "parse-json": "^5.0.0",
- "type-fest": "^0.6.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/read-pkg-up": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
- "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^4.1.0",
- "read-pkg": "^5.2.0",
- "type-fest": "^0.8.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
- "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
- "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "hosted-git-info": "^2.1.4",
- "resolve": "^1.10.0",
- "semver": "2 || 3 || 4 || 5",
- "validate-npm-package-license": "^3.0.1"
- }
- },
- "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
- "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/meow/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/meow/node_modules/type-fest": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
- "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/mime": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
@@ -7257,16 +5804,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/min-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -7283,41 +5820,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minimist-options": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
- "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arrify": "^1.0.1",
- "is-plain-obj": "^1.1.0",
- "kind-of": "^6.0.3"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/minimist-options/node_modules/arrify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
- "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/minio": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/minio/-/minio-8.0.6.tgz",
@@ -7406,16 +5908,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/modify-values": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
- "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -7466,29 +5958,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/neo-async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
- "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/normalize-package-data": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
- "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "hosted-git-info": "^4.0.1",
- "is-core-module": "^2.5.0",
- "semver": "^7.3.4",
- "validate-npm-package-license": "^3.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -7578,6 +6047,7 @@
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -7594,6 +6064,7 @@
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -7604,16 +6075,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -7642,26 +6103,13 @@
"node": ">=6"
}
},
- "node_modules/parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "error-ex": "^1.3.1",
- "json-parse-better-errors": "^1.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -7685,13 +6133,6 @@
"node": ">=8"
}
},
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
@@ -7716,29 +6157,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/path-type": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
- "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/path-type/node_modules/pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -7796,16 +6214,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -7856,6 +6264,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.9.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz",
+ "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -7978,18 +6402,6 @@
"node": ">=6"
}
},
- "node_modules/q": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
- "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
- "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.6.0",
- "teleport": ">=0.2.0"
- }
- },
"node_modules/query-string": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
@@ -8009,16 +6421,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/quick-lru": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
- "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
@@ -8030,148 +6432,6 @@
"performance-now": "^2.1.0"
}
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
- "node_modules/read-pkg": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
- "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "load-json-file": "^4.0.0",
- "normalize-package-data": "^2.3.2",
- "path-type": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
- "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^2.0.0",
- "read-pkg": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^1.1.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg-up/node_modules/path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/read-pkg/node_modules/hosted-git-info": {
- "version": "2.8.9",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
- "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/read-pkg/node_modules/normalize-package-data": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
- "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "hosted-git-info": "^2.1.4",
- "resolve": "^1.10.0",
- "semver": "2 || 3 || 4 || 5",
- "validate-npm-package-license": "^3.0.1"
- }
- },
- "node_modules/read-pkg/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver"
- }
- },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -8210,20 +6470,6 @@
"node": ">=10"
}
},
- "node_modules/redent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
- "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "indent-string": "^4.0.0",
- "strip-indent": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@@ -8242,27 +6488,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/resolve": {
- "version": "1.22.11",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
- "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -8445,16 +6670,6 @@
"node": ">=10"
}
},
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -8527,19 +6742,14 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
- "node_modules/smob": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
- "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
+ "optional": true,
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8560,60 +6770,13 @@
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
- "node_modules/spdx-correct": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
- "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "spdx-expression-parse": "^3.0.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "node_modules/spdx-exceptions": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
- "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
- "dev": true,
- "license": "CC-BY-3.0"
- },
- "node_modules/spdx-expression-parse": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
- "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "spdx-exceptions": "^2.1.0",
- "spdx-license-ids": "^3.0.0"
- }
- },
- "node_modules/spdx-license-ids": {
- "version": "3.0.22",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz",
- "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/split": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
- "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "through": "2"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/split-ca": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
@@ -8631,16 +6794,6 @@
"node": ">=6"
}
},
- "node_modules/split2": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
- "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "readable-stream": "^3.0.0"
- }
- },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -8717,113 +6870,6 @@
"node": ">=0.1.14"
}
},
- "node_modules/standard-version": {
- "version": "9.5.0",
- "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz",
- "integrity": "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "chalk": "^2.4.2",
- "conventional-changelog": "3.1.25",
- "conventional-changelog-config-spec": "2.1.0",
- "conventional-changelog-conventionalcommits": "4.6.3",
- "conventional-recommended-bump": "6.1.0",
- "detect-indent": "^6.0.0",
- "detect-newline": "^3.1.0",
- "dotgitignore": "^2.1.0",
- "figures": "^3.1.0",
- "find-up": "^5.0.0",
- "git-semver-tags": "^4.0.0",
- "semver": "^7.1.1",
- "stringify-package": "^1.0.1",
- "yargs": "^16.0.0"
- },
- "bin": {
- "standard-version": "bin/cli.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/standard-version/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/standard-version/node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/standard-version/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/standard-version/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/standard-version/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/standard-version/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/standard-version/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
@@ -8955,14 +7001,6 @@
"node": ">=8"
}
},
- "node_modules/stringify-package": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz",
- "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==",
- "deprecated": "This module is not used anymore, and has been replaced by @npmcli/package-json",
- "dev": true,
- "license": "ISC"
- },
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
@@ -9002,29 +7040,6 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-indent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
- "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "min-indent": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -9065,19 +7080,6 @@
"node": ">=8"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
@@ -9122,6 +7124,8 @@
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
"dev": true,
"license": "BSD-2-Clause",
+ "optional": true,
+ "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -9140,7 +7144,9 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
"node_modules/test-exclude": {
"version": "7.0.1",
@@ -9191,16 +7197,6 @@
"b4a": "^1.6.4"
}
},
- "node_modules/text-extensions": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
- "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
@@ -9212,13 +7208,6 @@
"utrie": "^1.0.2"
}
},
- "node_modules/through": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/through2": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
@@ -9300,16 +7289,6 @@
"node": ">=14.14"
}
},
- "node_modules/trim-newlines": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
- "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -9382,13 +7361,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -9403,20 +7375,6 @@
"node": ">=14.17"
}
},
- "node_modules/uglify-js": {
- "version": "3.19.3",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
- "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "optional": true,
- "bin": {
- "uglifyjs": "bin/uglifyjs"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/underscore": {
"version": "1.13.7",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
@@ -9496,17 +7454,6 @@
"uuid": "dist/bin/uuid"
}
},
- "node_modules/validate-npm-package-license": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
- "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "spdx-correct": "^3.0.0",
- "spdx-expression-parse": "^3.0.0"
- }
- },
"node_modules/vite": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
@@ -9790,13 +7737,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/wordwrap": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
- "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
@@ -9976,16 +7916,6 @@
"node": ">=4.0"
}
},
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4"
- }
- },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -9996,13 +7926,6 @@
"node": ">=10"
}
},
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
@@ -10019,86 +7942,13 @@
"url": "https://github.com/sponsors/eemeli"
}
},
- "node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/yargs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
diff --git a/package.json b/package.json
index 0f312ba9..7366ce98 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@soulcraft/brainy",
- "version": "8.0.0-rc.3",
+ "version": "8.9.0",
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
"main": "dist/index.js",
"module": "dist/index.js",
@@ -56,16 +56,22 @@
"import": "./dist/internals.js",
"types": "./dist/internals.d.ts"
},
+ "./brain-format": {
+ "import": "./dist/storage/brainFormat.js",
+ "types": "./dist/storage/brainFormat.d.ts"
+ },
"./embeddings/wasm": {
"import": "./dist/embeddings/wasm/index.js",
"types": "./dist/embeddings/wasm/index.d.ts"
}
},
"engines": {
- "node": "22.x",
- "bun": ">=1.0.0"
+ "node": ">=22",
+ "bun": ">=1.1.0"
},
"scripts": {
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
+ "prebuild": "npm run clean",
"build": "npm run build:types:if-needed && npm run build:patterns:if-needed && tsc && tsc -p tsconfig.cli.json && npm run build:copy-wasm",
"build:copy-wasm": "node -e \"const fs=require('fs');const src='src/embeddings/wasm/pkg';const dst='dist/embeddings/wasm/pkg';const skip=new Set(['package.json','.gitignore']);if(fs.existsSync(src)){fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).filter(f=>!skip.has(f)).forEach(f=>fs.copyFileSync(src+'/'+f,dst+'/'+f));console.log('Copied WASM pkg to dist')}\"",
"build:types": "tsx scripts/buildTypeEmbeddings.ts",
@@ -88,8 +94,7 @@
"test:ci-unit": "CI=true vitest run --config tests/configs/vitest.unit.config.ts",
"test:ci-integration": "NODE_OPTIONS='--max-old-space-size=16384' CI=true vitest run --config tests/configs/vitest.integration.config.ts",
"test:ci": "npm run test:ci-unit && npm run test:ci-integration",
- "test:bun": "bun tests/integration/bun-compile-test.ts",
- "test:bun:compile": "bun build tests/integration/bun-compile-test.ts --compile --outfile /tmp/brainy-bun-test && /tmp/brainy-bun-test",
+ "test:bun": "bun tests/integration/bun-runtime-test.ts",
"test:wasm": "npx vitest run tests/integration/wasm-embeddings.test.ts",
"typecheck": "tsc --noEmit",
"lint": "eslint --ext .ts,.js src/",
@@ -100,11 +105,7 @@
"release:patch": "./scripts/release.sh patch",
"release:minor": "./scripts/release.sh minor",
"release:major": "./scripts/release.sh major",
- "release:dry": "./scripts/release.sh patch --dry-run",
- "release:standard-version": "standard-version",
- "release:standard-version:patch": "standard-version --release-as patch",
- "release:standard-version:minor": "standard-version --release-as minor",
- "release:standard-version:major": "standard-version --release-as major"
+ "release:dry": "./scripts/release.sh patch --dry-run"
},
"keywords": [
"ai-database",
@@ -151,14 +152,10 @@
"boolean": "3.2.0"
},
"devDependencies": {
- "@rollup/plugin-commonjs": "^28.0.6",
- "@rollup/plugin-node-resolve": "^16.0.1",
- "@rollup/plugin-replace": "^6.0.2",
- "@rollup/plugin-terser": "^0.4.4",
"@testcontainers/redis": "^11.5.1",
"@types/js-yaml": "^4.0.9",
"@types/mime": "^3.0.4",
- "@types/node": "^20.11.30",
+ "@types/node": "^22",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -166,7 +163,7 @@
"@vitest/coverage-v8": "^3.2.4",
"jspdf": "^3.0.3",
"minio": "^8.0.5",
- "standard-version": "^9.5.0",
+ "prettier": "^3.9.4",
"testcontainers": "^11.5.1",
"tsx": "^4.19.2",
"typescript": "^5.4.5",
@@ -204,33 +201,5 @@
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
- },
- "eslintConfig": {
- "root": true,
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended"
- ],
- "parser": "@typescript-eslint/parser",
- "plugins": [
- "@typescript-eslint"
- ],
- "rules": {
- "@typescript-eslint/no-explicit-any": "off",
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": [
- "warn",
- {
- "args": "after-used",
- "argsIgnorePattern": "^_"
- }
- ],
- "semi": "off",
- "@typescript-eslint/semi": [
- "error",
- "never"
- ],
- "no-extra-semi": "off"
- }
}
}
diff --git a/scripts/push-docs.js b/scripts/push-docs.js
new file mode 100644
index 00000000..699d332b
--- /dev/null
+++ b/scripts/push-docs.js
@@ -0,0 +1,116 @@
+#!/usr/bin/env node
+/**
+ * @module scripts/push-docs
+ * @description Push this repo's PUBLIC docs to the soulcraft.com docs ingest
+ * door after an npm publish (VENUE-DOCS-RELEASE-PUSH — retires the old
+ * build-time docs sync).
+ *
+ * Contract (mirrors the reference implementation on the serving side):
+ * POST {base}/api/docs/ingest
+ * headers: x-service-secret: $DOCS_INGEST_SECRET, Content-Type: application/json
+ * body: { docs: [{ slug, title, markdown, nav: { order, section } }] }
+ * batches of 10, idempotent per slug.
+ *
+ * A doc is public iff its frontmatter has `public: true` AND a `slug`. The
+ * frontmatter is stripped; `category` → nav.section, `order` → nav.order.
+ *
+ * Deliberately NOT pushed: the combined /docs landing index. It spans BOTH
+ * engine corpora (this repo's and the native accelerator's), so a per-repo
+ * push would clobber the union — the index is authored on the serving side.
+ *
+ * Env: DOCS_INGEST_SECRET (required), DOCS_INGEST_BASE (default
+ * https://soulcraft.com). Exits 0 with a LOUD warning when the secret is
+ * absent (the npm publish has already happened; the serving side runs its
+ * interim sync on request) and exits 1 when a push actually fails — the docs
+ * site would silently trail npm otherwise, and that must be visible.
+ */
+import * as fs from 'node:fs'
+import * as path from 'node:path'
+
+const BASE = (process.env.DOCS_INGEST_BASE || 'https://soulcraft.com').replace(/\/+$/, '')
+const SECRET = process.env.DOCS_INGEST_SECRET
+const DOCS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'docs')
+const BATCH = 10
+
+if (!SECRET) {
+ console.warn(
+ '⚠️ DOCS PUSH SKIPPED: DOCS_INGEST_SECRET is not set.\n' +
+ ' soulcraft.com/docs now TRAILS this npm release until docs are pushed.\n' +
+ ' Either export DOCS_INGEST_SECRET and re-run `node scripts/push-docs.js`,\n' +
+ ' or ping venue on VENUE-DOCS-RELEASE-PUSH for the interim sync.'
+ )
+ process.exit(0)
+}
+
+/** Minimal frontmatter split — returns [meta, body] or [null, raw]. */
+function parseFrontmatter(raw) {
+ const m = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
+ if (!m) return [null, raw]
+ const meta = {}
+ for (const line of m[1].split('\n')) {
+ const kv = line.match(/^(\w[\w-]*):\s*(.*)$/)
+ if (kv) meta[kv[1]] = kv[2].trim().replace(/^["']|["']$/g, '')
+ }
+ return [meta, m[2]]
+}
+
+const docs = []
+;(function walk(dir) {
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name)
+ if (entry.isDirectory()) walk(full)
+ else if (entry.name.endsWith('.md')) {
+ const [meta, body] = parseFrontmatter(fs.readFileSync(full, 'utf-8'))
+ if (!meta || meta.public !== 'true' || !meta.slug) continue
+ docs.push({
+ slug: meta.slug,
+ title: meta.title || meta.slug,
+ markdown: body.trim(),
+ nav: {
+ order: Number.parseInt(meta.order || '99', 10) || 99,
+ section: meta.category || 'guides'
+ }
+ })
+ }
+ }
+})(DOCS_DIR)
+
+if (docs.length === 0) {
+ console.error('❌ DOCS PUSH FAILED: zero public docs collected — refusing to push an empty corpus.')
+ process.exit(1)
+}
+docs.sort((a, b) => a.slug.localeCompare(b.slug))
+console.log(`Pushing ${docs.length} public docs to ${BASE}/api/docs/ingest …`)
+
+let failed = false
+for (let i = 0; i < docs.length; i += BATCH) {
+ const batch = docs.slice(i, i + BATCH)
+ try {
+ const res = await fetch(`${BASE}/api/docs/ingest`, {
+ method: 'POST',
+ headers: {
+ 'x-service-secret': SECRET,
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'brainy-docs-push/1.0'
+ },
+ body: JSON.stringify({ docs: batch }),
+ signal: AbortSignal.timeout(120_000)
+ })
+ if (!res.ok) {
+ throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
+ }
+ console.log(` batch ${i / BATCH + 1}: ${batch.map((d) => d.slug).join(', ')} → ok`)
+ } catch (err) {
+ failed = true
+ console.error(` batch ${i / BATCH + 1} FAILED: ${err instanceof Error ? err.message : err}`)
+ }
+}
+
+if (failed) {
+ console.error(
+ '❌ DOCS PUSH INCOMPLETE — soulcraft.com/docs may trail npm. ' +
+ 'Re-run `node scripts/push-docs.js` or ping venue on VENUE-DOCS-RELEASE-PUSH.'
+ )
+ process.exit(1)
+}
+console.log('✅ Docs pushed.')
diff --git a/scripts/release.sh b/scripts/release.sh
index 0e6a9c43..7d860564 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -196,6 +196,18 @@ else
fi
echo -e "${GREEN}✅ GitHub release created${NC}\n"
+# Step 12: Push public docs to the soulcraft.com docs ingest door
+# (VENUE-DOCS-RELEASE-PUSH). Skips with a loud warning when
+# DOCS_INGEST_SECRET is unset; fails loudly (without undoing the publish —
+# that already happened) when a push errors, so the docs site never
+# silently trails npm.
+echo -e "${BLUE}1️⃣2️⃣ Pushing public docs to soulcraft.com/docs...${NC}"
+if node scripts/push-docs.js; then
+ echo -e "${GREEN}✅ Docs push step done${NC}\n"
+else
+ echo -e "${RED}❌ Docs push FAILED — soulcraft.com/docs trails npm until re-run or interim sync${NC}\n"
+fi
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}🎉 Release ${NEW_VERSION} complete!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
diff --git a/src/aggregation/AggregationIndex.ts b/src/aggregation/AggregationIndex.ts
index bf9dc5de..f9382218 100644
--- a/src/aggregation/AggregationIndex.ts
+++ b/src/aggregation/AggregationIndex.ts
@@ -29,6 +29,7 @@ import { matchesMetadataFilter } from '../utils/metadataFilter.js'
import { compareCodePoints } from '../utils/collation.js'
import { bucketTimestamp } from './timeWindows.js'
import { NounType } from '../types/graphTypes.js'
+import { prodLog } from '../utils/logger.js'
/** Persistence key for aggregate definitions */
const DEFINITIONS_KEY = '__aggregation_definitions__'
@@ -87,10 +88,18 @@ function matchesSource(entity: Record, source: AggregateDefinit
if (entity.service !== source.service) return false
}
- // Metadata where filter — match against the entity's metadata sub-object
+ // Where filter — resolve each filtered field through resolveEntityField,
+ // the SAME single source of truth groupBy uses (top-level standard fields
+ // + custom metadata). Matching only the metadata sub-object made
+ // where:{subtype}/{visibility}/… a silent no-op: reserved fields never
+ // live in the custom bag, so those filters could never match anything.
if (source.where && Object.keys(source.where).length > 0) {
- const metadata = (entity.metadata ?? entity) as Record
- if (!matchesMetadataFilter(metadata, source.where)) return false
+ const e = entity as unknown as HNSWNounWithMetadata
+ const resolved: Record = {}
+ for (const key of Object.keys(source.where)) {
+ resolved[key] = resolveEntityField(e, key)
+ }
+ if (!matchesMetadataFilter(resolved, source.where)) return false
}
return true
@@ -269,7 +278,7 @@ function recomputeMinMaxFromCounts(state: MetricState): void {
/**
* Exact percentile over a value multiset, using linear interpolation between closest ranks
* (numpy 'linear' / type-7): `rank = p·(n−1)`, interpolating between the floor and ceil
- * positions of the sorted expansion. Mirrors Cortex's `MetricState::percentile` bit-for-bit.
+ * positions of the sorted expansion. Mirrors Cor's `MetricState::percentile` bit-for-bit.
*/
function computePercentile(valueCounts: Record, count: number, p: number): number {
if (count === 0) return 0
@@ -327,6 +336,30 @@ export class AggregationIndex {
/** Track aggregates with stale MIN/MAX (need lazy recompute) */
private staleMinMax = new Map>()
+ /** Resolves when init() has finished loading persisted definitions/state. */
+ private initPromise: Promise | null = null
+
+ /** True once init() has settled (success or failure). */
+ private initDone = false
+
+ /**
+ * Aggregates registered by the app before init() finished loading persisted
+ * state, awaiting reconciliation: init() adopts the persisted state when the
+ * definition hash matches; anything left unadopted when init settles resolves
+ * to a backfill. Deciding backfill eagerly at define time was the boot-order
+ * bug that wiped valid persisted state on every restart — the synchronous
+ * defineAggregate() always beats the async init().
+ */
+ private pendingAdopt = new Set()
+
+ /**
+ * In-flight rescan targets. While a name has a staging map, ALL
+ * contributions (the walk's and concurrent write hooks') land there instead
+ * of the live map; the live map keeps serving until {@link finishBackfill}
+ * swaps the staging map in atomically.
+ */
+ private backfillStaging = new Map>()
+
constructor(storage: StorageAdapter, nativeProvider?: AggregationProvider) {
this.storage = storage
this.nativeProvider = nativeProvider
@@ -336,21 +369,128 @@ export class AggregationIndex {
/**
* Initialize: load persisted definitions and state, detect changes, rebuild stale.
+ *
+ * Idempotent — repeated calls return the same promise. Definitions registered
+ * *before* this completes (the normal boot order: `defineAggregate()` is
+ * synchronous and always beats this async load) are reconciled rather than
+ * clobbered: the app's definition wins, and its persisted state is adopted
+ * when the definition hash matches — backfill happens only on a real change.
*/
- async init(): Promise {
+ init(): Promise {
+ if (!this.initPromise) {
+ this.initPromise = this.loadPersisted().finally(() => {
+ this.resolvePendingAdoptToBackfill()
+ this.initDone = true
+ })
+ }
+ return this.initPromise
+ }
+
+ /**
+ * Await the persisted-state load (if one was started) and settle every
+ * pending adoption decision. After this resolves, `getPendingBackfills()`
+ * is authoritative: a name is listed iff it genuinely needs a rescan.
+ * Query paths must await this before consulting backfill state.
+ */
+ async ready(): Promise {
+ if (this.initPromise) {
+ try {
+ await this.initPromise
+ } catch {
+ // The owner already surfaced the load failure loudly; backfill covers.
+ }
+ }
+ this.resolvePendingAdoptToBackfill()
+ }
+
+ /**
+ * Any definition still awaiting state adoption has no persisted state to
+ * adopt (or init never ran / failed) — it must backfill.
+ */
+ private resolvePendingAdoptToBackfill(): void {
+ if (this.pendingAdopt.size > 0) {
+ prodLog.info(
+ `[Aggregation] no adoptable persisted state for: ${Array.from(this.pendingAdopt).join(', ')} — flagged for backfill`
+ )
+ }
+ for (const name of this.pendingAdopt) this.needsBackfill.add(name)
+ this.pendingAdopt.clear()
+ }
+
+ /**
+ * May this persisted state be ADOPTED? When the store exposes its committed
+ * watermark, the state's `sourceGeneration` must EQUAL it: behind means
+ * later writes are missing from the state (unclean shutdown); ahead means
+ * it counts writes that no longer exist (e.g. a fact-log truncation on a
+ * copied store pulled the watermark back). Either way: one exact rescan,
+ * said out loud — never a silent adopt. Stores without the capability (and
+ * pre-stamp state on them) fall back to hash-only adoption.
+ */
+ private stateGenerationAdoptable(name: string, stateData: unknown): boolean {
+ const committed = this.storage.committedGeneration?.() ?? null
+ if (committed === null) return true
+ const raw = (stateData as Record).sourceGeneration
+ const stamped = typeof raw === 'number' ? raw : null
+ if (stamped === committed) return true
+ prodLog.warn(
+ `[Aggregation] '${name}': persisted state is at generation ${stamped ?? 'unstamped'} ` +
+ `but the store's committed generation is ${committed} — rescanning instead of adopting`
+ )
+ return false
+ }
+
+ private async loadPersisted(): Promise {
// Load persisted definitions
const savedDefs = await this.storage.getMetadata(DEFINITIONS_KEY)
if (savedDefs && typeof savedDefs === 'object' && savedDefs.definitions) {
const defs = savedDefs.definitions as Array
for (const def of defs) {
- this.definitions.set(def.name, def)
- const currentHash = hashDefinition(def)
const savedHash = def._hash || ''
- // Load persisted state
+ if (this.definitions.has(def.name)) {
+ // The app re-registered this aggregate before the load finished.
+ // The app's definition wins — never clobber it with the persisted
+ // copy. Adopt the persisted state when the definition is unchanged
+ // AND no write has landed for it yet (a landed write would be lost
+ // by adoption; the hook flips such names to backfill).
+ const appHash = this.definitionHashes.get(def.name) || ''
+ if (appHash === savedHash && this.pendingAdopt.has(def.name)) {
+ const stateData = await this.storage.getMetadata(`${STATE_KEY_PREFIX}${def.name}__`)
+ if (
+ stateData &&
+ stateData.groups &&
+ this.stateGenerationAdoptable(def.name, stateData)
+ ) {
+ const groupMap = new Map()
+ for (const group of stateData.groups as AggregateGroupState[]) {
+ groupMap.set(serializeGroupKey(group.groupKey), group)
+ }
+ this.states.set(def.name, groupMap)
+ this.pendingAdopt.delete(def.name)
+ this.needsBackfill.delete(def.name)
+ prodLog.info(
+ `[Aggregation] '${def.name}': adopted persisted state (${groupMap.size} groups) — no rescan`
+ )
+ }
+ // No/invalid persisted state: stays in pendingAdopt and resolves
+ // to backfill when init settles.
+ }
+ continue
+ }
+
+ // Not registered this session — restore definition + state from
+ // persistence.
+ this.definitions.set(def.name, def)
+ const currentHash = hashDefinition(def)
+
const stateData = await this.storage.getMetadata(`${STATE_KEY_PREFIX}${def.name}__`)
- if (stateData && stateData.groups && savedHash === currentHash) {
+ if (
+ stateData &&
+ stateData.groups &&
+ savedHash === currentHash &&
+ this.stateGenerationAdoptable(def.name, stateData)
+ ) {
// Definition unchanged — load state
const groupMap = new Map()
for (const group of stateData.groups as AggregateGroupState[]) {
@@ -358,6 +498,10 @@ export class AggregationIndex {
groupMap.set(serialized, group)
}
this.states.set(def.name, groupMap)
+ this.needsBackfill.delete(def.name)
+ prodLog.info(
+ `[Aggregation] '${def.name}': restored definition + adopted persisted state (${groupMap.size} groups)`
+ )
} else {
// Definition changed or no saved state — start fresh and backfill from
// existing entities (the owner drains needsBackfill on first query).
@@ -398,14 +542,22 @@ export class AggregationIndex {
}))
await this.storage.saveMetadata(DEFINITIONS_KEY, { definitions: defsToSave })
- // Persist dirty states
+ // Persist dirty states, stamped with the committed generation they
+ // reflect. The stamp is what makes reopen-adoption verifiable: state at a
+ // different generation than the store's committed watermark is stale (an
+ // unclean shutdown after later writes) or over-counts (a fact-log
+ // truncation on a copied store pulled the watermark BACK below the
+ // stamp) — either way the answer is one exact rescan, never a silent
+ // adopt. Read the generation after collecting groups so any racing
+ // commit resolves toward rescan, not wrong-adopt.
for (const name of this.dirty) {
const stateMap = this.states.get(name)
if (stateMap) {
const groups = Array.from(stateMap.values())
+ const sourceGeneration = this.storage.committedGeneration?.() ?? null
await this.storage.saveMetadata(
`${STATE_KEY_PREFIX}${name}__`,
- { groups }
+ sourceGeneration === null ? { groups } : { groups, sourceGeneration }
)
}
}
@@ -452,10 +604,19 @@ export class AggregationIndex {
this.definitions.set(def.name, def)
this.definitionHashes.set(def.name, newHash)
+ // First sight this session, before init() settled: defer the backfill
+ // decision — init() adopts the persisted state on hash match, and anything
+ // left unadopted resolves to backfill. Deciding eagerly here wiped valid
+ // persisted state on every restart.
+ if (!this.states.has(def.name) && !this.initDone) {
+ this.states.set(def.name, new Map())
+ this.pendingAdopt.add(def.name)
+ }
// Reset state if definition changed or doesn't exist yet, and flag it for
// backfill so already-stored entities are counted (write-time hooks only see
// future writes). The owner drains this on the next query via getPendingBackfills().
- if (!this.states.has(def.name) || (oldHash && oldHash !== newHash)) {
+ else if (!this.states.has(def.name) || (oldHash && oldHash !== newHash)) {
+ this.pendingAdopt.delete(def.name)
this.states.set(def.name, new Map())
this.needsBackfill.add(def.name)
}
@@ -476,6 +637,8 @@ export class AggregationIndex {
this.definitionHashes.delete(name)
this.states.delete(name)
this.staleMinMax.delete(name)
+ this.pendingAdopt.delete(name)
+ this.needsBackfill.delete(name)
// Notify native provider
if (this.nativeProvider?.removeAggregate) {
@@ -513,9 +676,17 @@ export class AggregationIndex {
return Array.from(this.needsBackfill)
}
- /** Clear an aggregate's state so a full rescan cannot double-count. */
+ /**
+ * Begin a rescan into a STAGING map. The live state is not touched — it
+ * keeps serving (possibly stale, but flagged pending) until the rescan
+ * completes and swaps in atomically. A mid-walk failure drops the staging
+ * map via {@link abortBackfill} and loses nothing: wiping live state before
+ * a scan that could throw was the destructive-before-durable defect.
+ * Contributions (walk + concurrent write hooks) land in staging while it
+ * exists, so the swapped-in result reflects writes that raced the walk.
+ */
beginBackfill(name: string): void {
- this.states.set(name, new Map())
+ this.backfillStaging.set(name, new Map())
// Reset native provider state for this aggregate too, if present.
const def = this.definitions.get(name)
if (def && this.nativeProvider?.removeAggregate && this.nativeProvider?.defineAggregate) {
@@ -524,6 +695,15 @@ export class AggregationIndex {
}
}
+ /**
+ * Abandon an in-flight rescan after a failure: drop the staging map, keep
+ * the live state serving, leave the aggregate flagged as pending so a later
+ * attempt rescans. The failure itself must be surfaced loudly by the owner.
+ */
+ abortBackfill(name: string): void {
+ this.backfillStaging.delete(name)
+ }
+
/** Feed one already-stored entity into a single aggregate during backfill. */
backfillEntity(name: string, entity: Record): void {
if (isAggregateEntity(entity)) return
@@ -537,14 +717,33 @@ export class AggregationIndex {
}
}
- /** Mark an aggregate's backfill complete; rebuilt state persists on next flush(). */
+ /** Swap the rebuilt staging state in atomically; persists on next flush(). */
finishBackfill(name: string): void {
+ const staged = this.backfillStaging.get(name)
+ if (staged) {
+ this.states.set(name, staged)
+ this.backfillStaging.delete(name)
+ }
this.needsBackfill.delete(name)
this.dirty.add(name)
}
// ============= Write-Time Hooks =============
+ /**
+ * A write is landing for an aggregate whose persisted-state adoption is still
+ * pending — adopting after this write would lose its contribution. Settle the
+ * decision now: an exact rescan instead of adoption. The window is the few
+ * milliseconds between a boot-time defineAggregate() and init() completing,
+ * so this rarely fires; when it does, correctness wins over the walk.
+ */
+ private resolveAdoptOnWrite(name: string): void {
+ if (this.pendingAdopt.has(name)) {
+ this.pendingAdopt.delete(name)
+ this.needsBackfill.add(name)
+ }
+ }
+
/**
* Called when an entity is added. Updates all matching aggregates.
*/
@@ -553,6 +752,7 @@ export class AggregationIndex {
for (const [name, def] of this.definitions) {
if (!matchesSource(entity, def.source)) continue
+ this.resolveAdoptOnWrite(name)
if (this.nativeProvider) {
const results = this.nativeProvider.incrementalUpdate(name, def, entity, 'add')
@@ -579,6 +779,10 @@ export class AggregationIndex {
const oldMatches = matchesSource(oldEntity, def.source)
const newMatches = matchesSource(newEntity, def.source)
+ if (oldMatches || newMatches) {
+ this.resolveAdoptOnWrite(name)
+ }
+
if (this.nativeProvider && (oldMatches || newMatches)) {
const results = this.nativeProvider.incrementalUpdate(name, def, newEntity, 'update', oldEntity)
this.applyNativeResults(name, results)
@@ -605,6 +809,7 @@ export class AggregationIndex {
for (const [name, def] of this.definitions) {
if (!matchesSource(entity, def.source)) continue
+ this.resolveAdoptOnWrite(name)
if (this.nativeProvider) {
const results = this.nativeProvider.incrementalUpdate(name, def, entity, 'delete')
@@ -757,7 +962,7 @@ export class AggregationIndex {
def: AggregateDefinition,
entity: Record
): void {
- const stateMap = this.states.get(aggName)!
+ const stateMap = (this.backfillStaging.get(aggName) ?? this.states.get(aggName))!
// Fan out: an unnest dimension makes one entity contribute to several groups.
for (const groupKey of computeGroupKeys(entity, def.groupBy)) {
@@ -815,7 +1020,7 @@ export class AggregationIndex {
def: AggregateDefinition,
entity: Record
): void {
- const stateMap = this.states.get(aggName)!
+ const stateMap = (this.backfillStaging.get(aggName) ?? this.states.get(aggName))!
// Fan out: reverse the entity's contribution from every group it joined.
for (const groupKey of computeGroupKeys(entity, def.groupBy)) {
@@ -871,7 +1076,7 @@ export class AggregationIndex {
* Apply results from native provider back into the state maps.
*/
private applyNativeResults(aggName: string, results: AggregateGroupState[]): void {
- const stateMap = this.states.get(aggName)!
+ const stateMap = (this.backfillStaging.get(aggName) ?? this.states.get(aggName))!
for (const group of results) {
const serialized = serializeGroupKey(group.groupKey)
stateMap.set(serialized, group)
diff --git a/src/aggregation/materializer.ts b/src/aggregation/materializer.ts
index 3a8dcb3f..f752648a 100644
--- a/src/aggregation/materializer.ts
+++ b/src/aggregation/materializer.ts
@@ -14,6 +14,7 @@ import type {
AggregateMetricDef
} from '../types/brainy.types.js'
import { serializeGroupKey } from './AggregationIndex.js'
+import { prodLog } from '../utils/logger.js'
/**
* Callback interface for the materializer to create/update Brainy entities.
@@ -86,8 +87,15 @@ export class AggregateMaterializer {
if (existing) clearTimeout(existing)
this.debounceTimers.set(key, setTimeout(() => {
- this.materializeOne(key).catch(() => {
- // Non-fatal — materialization is derived data
+ this.materializeOne(key).catch((err) => {
+ // Materialization is derived data (rebuildable via backfill-on-query), so
+ // a failure is non-fatal — but it leaves the materialized Measurement
+ // entity STALE. Surface it loudly rather than swallow it silently.
+ prodLog.warn(
+ `[Aggregation] Failed to materialize aggregate group '${key}': ` +
+ `${(err as Error).message}. The materialized value is stale until the ` +
+ `next successful materialization or a backfill-on-query.`
+ )
})
}, debounceMs))
}
diff --git a/src/brainy.ts b/src/brainy.ts
index e2e58962..9ab3acee 100644
--- a/src/brainy.ts
+++ b/src/brainy.ts
@@ -1,8 +1,10 @@
/**
- * 🧠 Brainy 3.0 - The Future of Neural Databases
- *
- * Beautiful, Professional, Planet-Scale, Fun to Use
- * NO STUBS, NO MOCKS, REAL IMPLEMENTATION
+ * @module brainy
+ * @description The `Brainy` class — the Triple-Intelligence database that unifies
+ * vector similarity, graph traversal, and metadata filtering behind a single API
+ * (`add`/`find`/`relate`/`related`/`get`/`similar`/`graph.*`/`now`/`asOf`/`transact`/
+ * `export`/`import`). Native acceleration is feature-detected through the provider
+ * boundary; when no provider is registered, the built-in JS engines serve everything.
*/
import { v4 as uuidv4, v7 as uuidv7 } from './universal/uuid.js'
@@ -15,6 +17,13 @@ import type { StorageOptions } from './storage/storageFactory.js'
import { rebuildCounts } from './utils/rebuildCounts.js'
import type { MetadataWriteBuffer } from './utils/metadataWriteBuffer.js'
import { BaseStorage } from './storage/baseStorage.js'
+import {
+ CURRENT_DATA_FORMAT,
+ EXPECTED_INDEX_EPOCH,
+ readBrainFormat,
+ writeBrainFormat
+} from './storage/brainFormat.js'
+import type { BrainFormat } from './storage/brainFormat.js'
import { StorageAdapter, Vector, DistanceFunction, EmbeddingFunction, GraphVerb, STANDARD_ENTITY_FIELDS } from './coreTypes.js'
import type { HNSWNounWithMetadata, HNSWVerbWithMetadata, EntityVisibility } from './coreTypes.js'
import {
@@ -23,7 +32,7 @@ import {
getBrainyVersion
} from './utils/index.js'
import { embeddingManager } from './embeddings/EmbeddingManager.js'
-import { matchesMetadataFilter } from './utils/metadataFilter.js'
+import { matchesMetadataFilter, validateWhereFilter } from './utils/metadataFilter.js'
import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js'
import { NeuralEntityExtractor, ExtractedEntity } from './neural/entityExtractor.js'
import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js'
@@ -37,8 +46,10 @@ import {
pageRank,
MinHeap
} from './graph/analyticsFallback.js'
+import { runGraphAudit, type GraphAuditReport } from './graph/graphAudit.js'
import { createPipeline } from './streaming/pipeline.js'
import { configureLogger, LogLevel, prodLog } from './utils/logger.js'
+import { warnOnLowOsLimits } from './utils/osLimits.js'
import { setGlobalCache } from './utils/unifiedCache.js'
import type { UnifiedCache } from './utils/unifiedCache.js'
import { rankIndicesByScore, reorderByIndices } from './utils/resultRanking.js'
@@ -51,7 +62,8 @@ import type {
CommunitiesOptions,
PathOptions,
MetadataIndexProvider,
- OpaqueIdSet
+ OpaqueIdSet,
+ AtGenerationVectors
} from './plugin.js'
import type {
BrainyPlugin,
@@ -60,6 +72,7 @@ import type {
} from './plugin.js'
import { ConnectionsCodec } from './hnsw/connectionsCodec.js'
import { TransactionManager } from './transaction/TransactionManager.js'
+import { transactTimeoutBudget } from './transaction/Transaction.js'
import { RevisionConflictError } from './transaction/RevisionConflictError.js'
import { EntityNotFoundError, RelationNotFoundError } from './errors/notFound.js'
import {
@@ -141,10 +154,12 @@ import type { MigrationPreview, MigrationResult, MigrateOptions } from './migrat
import { AggregationIndex } from './aggregation/AggregationIndex.js'
import { AggregateMaterializer } from './aggregation/materializer.js'
import type { AggregateDefinition, AggregateQueryParams, AggregateResult } from './types/brainy.types.js'
+import type { MigrationProgress } from './types/brainy.types.js'
import { resolveJsHnswConfig, DEFAULT_RECALL } from './utils/recallPreset.js'
import * as fs from 'node:fs'
import * as os from 'node:os'
import { Db, type DbHost, type HistoricalQueryHandle } from './db/db.js'
+import { entityMatchesFind } from './db/whereMatcher.js'
import {
importGraph,
isPortableGraph,
@@ -154,13 +169,30 @@ import {
type ImportOptions,
type ImportResult
} from './db/portableGraph.js'
-import { GenerationStore } from './db/generationStore.js'
+import { GenerationStore, type CommitBeforeImages } from './db/generationStore.js'
+import type { FactScanHandle } from './db/factLog.js'
+import {
+ ENTITY_TREE_STAMP_PATH,
+ readFamilyStamp,
+ verifyFamilyStamp,
+ writeFamilyStamp,
+ type FamilyStamp
+} from './db/familyStamp.js'
+import {
+ ChangeFeed,
+ type BrainyChangeEvent,
+ type ChangeListener,
+ type PendingChangeEvent
+} from './events/changeFeed.js'
import { isDeterministicEmbedMode } from './embeddings/deterministicEmbedMode.js'
-import { GenerationConflictError } from './db/errors.js'
+import { GenerationConflictError, StoreInconsistentError } from './db/errors.js'
+import { BrainyError, GraphIndexNotReadyError, MetadataIndexNotReadyError, MigrationInProgressError, VectorIndexNotReadyError } from './errors/brainyError.js'
+import { assessIndexReadiness } from './utils/indexReadiness.js'
import { MemoryStorage } from './storage/adapters/memoryStorage.js'
import type {
CompactHistoryOptions,
CompactHistoryResult,
+ HistoryStats,
TransactOptions,
TransactReceipt,
TxLogEntry,
@@ -170,7 +202,7 @@ import type {
HistoryVersion
} from './db/types.js'
import { stableDeepEqual } from './db/stableEqual.js'
-import type { VersionedIndexProvider } from './plugin.js'
+import type { VersionedIndexProvider, ProviderInvariantReport } from './plugin.js'
import type { Operation, TransactionFunction } from './transaction/types.js'
/**
@@ -233,6 +265,7 @@ type ResolvedBrainyConfig = Required<
| 'integrations'
| 'retention'
| 'eagerEmbeddings'
+ | 'migrationWaitTimeoutMs'
>
> &
Pick<
@@ -244,6 +277,7 @@ type ResolvedBrainyConfig = Required<
| 'integrations'
| 'retention'
| 'eagerEmbeddings'
+ | 'migrationWaitTimeoutMs'
>
/**
@@ -299,8 +333,78 @@ interface PlannedTransact {
touchedVerbs: string[]
/** Aggregation-index hooks to run after the commit point. */
postCommit: Array<() => void>
+ /**
+ * One entry per staged `{ op: 'update' }`, re-verified UNDER the commit
+ * mutex (the generation store's `precommit`) so per-op `ifRev` CAS is
+ * atomic with the apply — planning-time checks can interleave with
+ * concurrent writers. `updatedMetadata` is the staged metadata object (held
+ * by reference by the staged operation), re-stamped there with the
+ * authoritative `_rev`. A conflict rejects the WHOLE batch.
+ */
+ casUpdates: Array<{
+ id: string
+ ifRev?: number
+ updatedMetadata: { _rev?: number }
+ }>
+ /**
+ * Ids whose revision baseline THIS batch resets via an `add` op (a fresh
+ * create, or add's overwrite semantics which restamp `_rev: 1`). Updates to
+ * these ids sequence against in-batch state no concurrent writer can touch,
+ * so their plan-time CAS checks and rev stamps are already exact — the
+ * commit precondition leaves them untouched.
+ */
+ createdNouns: Set
+ /**
+ * Change-feed events, one per affected record in op order, populated by the
+ * planners ONLY when a listener is subscribed. Stamped with the batch's
+ * committed generation and emitted after `commitTransaction` returns — a
+ * rejected batch (CAS conflict, failed apply) emits nothing.
+ */
+ changeEvents: PendingChangeEvent[]
}
+/**
+ * Internal control-flow signal: an insert guarded by a must-be-absent
+ * precondition (`add({ ifAbsent })` / `add({ upsert })`) found the entity
+ * already present in the authoritative before-image under the commit mutex —
+ * a concurrent writer created it after the planning-time absence check.
+ * Never escapes `add()`: the caller converts it into the documented
+ * resolution (ifAbsent → return the existing id without writing; upsert →
+ * merge into the now-existing entity via `update()`).
+ */
+class InsertPreconditionExistsSignal extends Error {
+ constructor(readonly id: string) {
+ super(`insert precondition: entity ${id} already exists`)
+ this.name = 'InsertPreconditionExistsSignal'
+ }
+}
+
+/**
+ * @description The derived-index families a read may depend on. A read that
+ * consults none of them (a canonical-storage read: `get`, an entity
+ * enumeration, a VFS content/dir read) is index-independent and must never
+ * block on another family's one-time migration. Used by the family-scoped
+ * migration gate ({@link Brainy.awaitMigrationLock}).
+ */
+export type IndexFamily = 'vector' | 'metadata' | 'graph'
+
+/**
+ * How long a failed aggregation-backfill walk suppresses fresh walk attempts.
+ * Within the window, queries rethrow the recorded failure instantly (loud,
+ * cheap); after it, one new attempt is allowed. Bounds the damage of a
+ * caller-side tight retry loop against a deterministically-failing store.
+ */
+const AGGREGATION_BACKFILL_RETRY_COOLDOWN_MS = 30_000
+
+/**
+ * Time budget for the auto-compaction pass at close() (8.9.0). Bounds how long
+ * a clean shutdown spends reclaiming history backlog — an early stop is a
+ * consistent prefix and the next close/explicit pass resumes. Explicit
+ * `compactHistory()` calls are unbounded unless the caller passes their own
+ * `timeBudgetMs` (maintenance windows choose their own budgets).
+ */
+const CLOSE_COMPACTION_BUDGET_MS = 5_000
+
/**
* The main Brainy class - Clean, Beautiful, Powerful
* REAL IMPLEMENTATION - No stubs, no mocks
@@ -312,6 +416,23 @@ export class Brainy implements BrainyInterface {
private static shutdownHooksRegisteredGlobally = false
private static instances: Brainy[] = []
+ /** The globally-registered shutdown listeners, kept so the LAST close() can
+ * deregister them. A process.on('SIGINT'/'SIGTERM') listener holds a ref'd
+ * signal handle that keeps the Node event loop alive — a library that never
+ * removes its listeners makes every bare script hang after close().
+ * See {@link registerShutdownHooks} / {@link deregisterShutdownHooksIfIdle}. */
+ private static sigtermListener?: () => void
+ private static sigintListener?: () => void
+ private static beforeExitListener?: () => void
+
+ /** Poll cadence (ms) for the migration LOCK when a provider exposes no
+ * event-driven `whenMigrationComplete()` signal. See {@link awaitMigrationLock}. */
+ private static readonly MIGRATION_POLL_INTERVAL_MS = 250
+
+ /** First-party accelerator packages probed by guarded auto-detection when
+ * `plugins` is undefined (installing one IS the opt-in). See {@link loadPlugins}. */
+ private static readonly AUTO_DETECT_PLUGIN_PACKAGES = ['@soulcraft/cor']
+
// Core components
private index!: JsHnswVectorIndex
private storage!: BaseStorage
@@ -388,6 +509,89 @@ export class Brainy implements BrainyInterface {
* (use the TS fallback); otherwise the provider instance.
*/
private _graphAccelProvider: GraphAccelerationProvider | null | undefined = undefined
+ /**
+ * Set when a non-fatal index rebuild fails at init() (the brain is allowed to
+ * start, but in a degraded state where queries may be incomplete). Surfaced
+ * through {@link checkHealth} so consumers can observe the failure
+ * programmatically rather than only via the console. A storage *read* failure
+ * during rebuild is NOT recorded here — it re-throws and aborts init() loudly.
+ */
+ private _indexRebuildFailed: Error | null = null
+ /**
+ * Ids of records that committed via an adopt-forward failed-rollback recovery
+ * ({@link GenerationStore.commitSingleOp} `degraded`): the canonical record is
+ * durable but its derived index entry may be incomplete until
+ * {@link repairIndex}. Non-empty = a queryable degraded state, folded into
+ * {@link checkHealth}/{@link validateIndexConsistency} and warned on the read
+ * paths. Cleared by {@link repairIndex}.
+ */
+ private _indexDegradedIds: Set = new Set()
+ /** One-shot guard so the degraded-reads warning fires once per degraded window
+ * (reset when the degraded state clears). See {@link warnIfReadsDegraded}. */
+ private _degradedReadWarned = false
+ /** One-shot guard so the metadata cold-open consistency probe runs once per brain. */
+ private _metadataConsistencyProbed = false
+ /** Graph-adjacency cold-load consistency: verified-live this session (one-shot). */
+ private _graphAdjacencyVerified = false
+ /** Re-entrancy guard: a verify (rebuild → reads) is in flight. */
+ private _graphAdjacencyVerifying = false
+ /** Metadata field-index cold-read guard: verified-serving this session (one-shot). */
+ private _metadataVerified = false
+ /** Re-entrancy guard for {@link verifyMetadataLive}. */
+ private _metadataVerifying = false
+ /** Vector-index cold-read guard: verified-serving this session (one-shot). */
+ private _vectorVerified = false
+ /** Re-entrancy guard for {@link verifyVectorLive}. */
+ private _vectorVerifying = false
+ /**
+ * Coordinated migration LOCK (#18): dedup guards so the "upgrading, blocking"
+ * and "upgrade complete, resumed" lines each log once per migration window,
+ * not once per blocked operation. See {@link awaitMigrationLock}.
+ */
+ private _migrationBlockLogged = false
+ private _migrationReleaseLogged = false
+ /** Epoch ms when brainy first observed the migration LOCK held, or `null` when
+ * not migrating — the fallback `elapsedMs` a provider that omits `migrationStatus()`
+ * timing still gets in `getIndexStatus().migration`. See {@link migrationSnapshot}. */
+ private _migrationObservedAt: number | null = null
+ /** Path of the pre-upgrade backup taken this open (default-on, opt-out via
+ * `migrationBackup: false`), or `null` if none. Removed once the 7.x→8.0
+ * upgrade verifies + stamps; retained on failure. See {@link createMigrationBackupIfNeeded}. */
+ private _migrationBackupPath: string | null = null
+
+ /**
+ * The in-process change feed behind {@link onChange}. Emitted from the
+ * commit seam ({@link persistSingleOp} / {@link transact}), so every
+ * canonical mutation — any origin — produces exactly one post-commit event
+ * per affected record. See src/events/changeFeed.ts for the delivery
+ * contract.
+ */
+ private readonly _changeFeed = new ChangeFeed()
+
+ /** Set when the on-open VFS-blob adoption left one or more `_cow/` blobs it
+ * could not fully adopt (bytes or metadata missing). While true, the
+ * pre-upgrade backup is NOT auto-removed — the upgrade is not verifiably
+ * complete for the VFS. See {@link autoAdoptLegacyVfsBlobsIfNeeded}. */
+ private _vfsBlobAdoptionIncomplete = false
+
+ /**
+ * 8.0 ⇄ native-provider version handshake — the on-disk {@link BrainFormat}
+ * marker (`_system/brain-format.json`) as read during the store-open phase,
+ * or `null` for a pre-handshake / brand-new brain. Populated BEFORE any
+ * derived index or native provider is constructed, so {@link formatInfo}
+ * answers synchronously when the provider reads it at its own init().
+ * Refreshed to the current marker once it is (re)stamped.
+ */
+ private _brainFormat: BrainFormat | null = null
+ /**
+ * True when the on-disk {@link _brainFormat} is absent OR carries an
+ * `indexEpoch` different from {@link EXPECTED_INDEX_EPOCH} — i.e. the derived
+ * JS indexes on disk predate this build and must be rebuilt from the
+ * canonical records (the epoch-drift rebuild trigger that closes the gap
+ * where JS indexes rebuilt only on `size()===0`, never on a format change).
+ * Cleared once the rebuild verifies and the marker is re-stamped.
+ */
+ private _indexEpochStale = false
// Sub-APIs (lazy-loaded)
private _nlp?: NaturalLanguageProcessor
@@ -415,6 +619,11 @@ export class Brainy implements BrainyInterface {
private _hub?: IntegrationHub // Integration Hub for external tools
private _pendingMigrationRunner?: MigrationRunner // Deferred migration runner for large datasets
private _aggregationIndex?: AggregationIndex // Incremental aggregation engine
+ private _aggregationBackfillFlight: Promise | null = null // Single-flight backfill walk
+ // A failed walk latches its error: retries within the cooldown rethrow it
+ // instantly instead of re-walking, so a tight caller-side retry loop costs
+ // one loud error per query, never a full store walk per query.
+ private _aggregationBackfillFailure: { at: number; error: Error } | null = null
private _materializer?: AggregateMaterializer // Debounced materialization of aggregate results
/**
* Fields registered via `brain.trackField()` — drives optional value validation on
@@ -444,6 +653,13 @@ export class Brainy implements BrainyInterface {
// set this to ReaderMode so historical instances are protected the same way.
private operationalMode: BaseOperationalMode
+ // Write-quarantine after a failed transaction rollback left the store
+ // inconsistent (see StoreInconsistentError). Set at the moment the failure is
+ // surfaced; every subsequent mutation is refused via assertWritable until
+ // repairIndex() reconciles canonical vs derived state and clears it. Reads are
+ // never affected. null = healthy.
+ private storeInconsistency: StoreInconsistentError | null = null
+
// Ready Promise state (Unified readiness API)
// Allows consumers to await brain.ready for initialization completion
private _readyPromise: Promise | null = null
@@ -565,13 +781,13 @@ export class Brainy implements BrainyInterface {
* Whether the active storage adapter (anywhere in its prototype chain)
* implements a given optional method.
*
- * Storage-adapter plugins (e.g. `@soulcraft/cortex`'s `MmapFileSystemStorage
+ * Storage-adapter plugins (e.g. `@soulcraft/cor`'s `MmapFileSystemStorage
* extends FileSystemStorage`) inherit new methods Brainy adds to
* `FileSystemStorage` / `BaseStorage` automatically — `typeof` walks the
* prototype chain, so there's no in-package version skew to worry about as
* long as the plugin's own dist resolves `@soulcraft/brainy` dynamically
* (which Cortex 2.2.x onward does — see
- * `node_modules/@soulcraft/cortex/dist/storage/mmapFileSystemStorage.js`).
+ * `node_modules/@soulcraft/cor/dist/storage/mmapFileSystemStorage.js`).
*
* This helper exists for the **build/install** failure modes the import
* resolution can't catch:
@@ -609,6 +825,17 @@ export class Brainy implements BrainyInterface {
`Open in writer mode to modify data.`
)
}
+ // Write-quarantine: a prior transaction's rollback failed and left the store
+ // inconsistent. Refuse further writes (which would compound the damage) until
+ // repairIndex() reconciles and lifts the quarantine. Reads still work.
+ if (this.storeInconsistency) {
+ throw new Error(
+ `Cannot call ${method}() — the store is WRITE-QUARANTINED after a failed ` +
+ `transaction rollback left it inconsistent. Reads still work; run repairIndex() ` +
+ `to reconcile the derived indexes against canonical storage and lift the ` +
+ `quarantine. Original inconsistency: ${this.storeInconsistency.message}`
+ )
+ }
}
/**
@@ -706,7 +933,7 @@ export class Brainy implements BrainyInterface {
try {
// Auto-detect and activate plugins BEFORE storage setup
- // so plugin-provided storage factories (e.g., filesystem override from cortex) are available
+ // so plugin-provided storage factories (e.g., filesystem override from cor) are available
await this.loadPlugins()
// 7.x → 8.0 layout migration, BEFORE storage setup: collapse a legacy
@@ -719,11 +946,18 @@ export class Brainy implements BrainyInterface {
this.storage = await this.setupStorage()
await this.storage.init()
+ // OS-limit detection (once per process, Linux-only, measurement-only):
+ // warn NOW about RLIMIT_NOFILE / vm.max_map_count values that will bite
+ // at pool scale, instead of letting the operator meet them as EMFILE or
+ // a failed mmap deep inside an index open. Fire-and-forget — the check
+ // never affects open.
+ void warnOnLowOsLimits()
+
// Acquire the writer lock for filesystem (and other locking-capable) backends.
// Skipped in reader mode and on backends that don't support multi-process locking.
// Throws if another live writer holds the directory (unless force: true).
//
- // Defensive call: older storage adapters (e.g. `@soulcraft/cortex@2.2.0`
+ // Defensive call: older storage adapters (e.g. `@soulcraft/cor@2.2.0`
// and earlier) bundle a pre-7.21 `BaseStorage` that doesn't define these
// methods. We feature-detect each one rather than fail boot.
if (this.config.mode !== 'reader') {
@@ -780,6 +1014,68 @@ export class Brainy implements BrainyInterface {
readOnly: this.config.mode === 'reader'
})
+ // The generation fact log is CANONICAL state, not a derived index — no
+ // sweeper, GC, or blob-lifecycle path may ever delete under it. Declare
+ // its namespace as a protected family (rebuildable: false — a lost fact
+ // segment is NOT reconstructable) so the storage layer REFUSES such
+ // deletes; refusal beats trust. Feature-detected + idempotent per name.
+ if (
+ this.config.mode !== 'reader' &&
+ this.generationStore.getFactLog() &&
+ typeof this.storage.registerDerivedFamily === 'function'
+ ) {
+ await this.storage.registerDerivedFamily({
+ name: 'generation-facts',
+ members: ['_generations/facts/'],
+ namespace: true,
+ rebuildable: false
+ })
+ }
+
+ // Fact-scan capability: wire the storage seam through which index
+ // providers (which hold only `storage`) reach the fact log. A closure
+ // over the LIVE log — restore/reopen swaps the instance transparently —
+ // so a provider's heal can switch from the enumeration walk to one
+ // sequential fact scan whenever the log exists.
+ if (typeof (this.storage as BaseStorage).setFactScanSource === 'function') {
+ ;(this.storage as BaseStorage).setFactScanSource({
+ factLog: () => this.generationStore?.getFactLog() ?? null,
+ // The committed watermark, exposed as a capability so providers
+ // never parse the store's private manifest format.
+ committedGeneration: () => this.generationStore?.committedGeneration() ?? 0
+ })
+ }
+
+ // Entity-tree stamp coherence: compare the stamped sourceGeneration +
+ // rollup invariants against the log head + live counters. Loud on
+ // genuine incoherence (repairIndex heals), silent on absent/coherent,
+ // benign-behind refreshes at the next flush. Never blocks open.
+ await this.verifyEntityTreeStamp()
+
+ // 8.0 ⇄ native-provider version handshake: load the on-disk brain-format
+ // marker (`_system/brain-format.json`) into an in-memory field NOW —
+ // after the store-open phase, but BEFORE any derived index or native
+ // provider is constructed below — so `formatInfo()` is populated when the
+ // provider reads it synchronously at its own init(). A drifted or absent
+ // `indexEpoch` means the on-disk derived-index format predates this build
+ // (the derived JS indexes are stale); `rebuildIndexesIfNeeded()` rebuilds
+ // them from the canonical records and then re-stamps the marker AFTER the
+ // rebuild verifies (non-destructive: a crash mid-rebuild leaves the old /
+ // absent marker, so the next open idempotently re-rebuilds).
+ this._brainFormat = await readBrainFormat(this.storage)
+ this._indexEpochStale =
+ this._brainFormat === null || this._brainFormat.indexEpoch !== EXPECTED_INDEX_EPOCH
+
+ // Pre-upgrade backup (default-on, opt-out via `migrationBackup: false`): the
+ // on-disk format is stale, so a one-time 7.x → 8.0 rebuild will run below.
+ // Snapshot the brain dir NOW — before any provider (native or JS) rebuilds a
+ // derived index — so a migration bug can be rolled back. Removed once the
+ // upgrade verifies + stamps; retained on failure. No-op for a reader, for
+ // non-filesystem storage, or for a brain with no persisted data.
+ if (this._indexEpochStale && this.config.migrationBackup && !this.isReadOnly) {
+ await this.createMigrationBackupIfNeeded()
+ }
+
// Provider: embeddings (reassign embedder if plugin provides one)
const embeddingProvider = this.pluginRegistry.getProvider('embeddings')
if (embeddingProvider) {
@@ -806,7 +1102,7 @@ export class Brainy implements BrainyInterface {
setMsgpackImplementation(msgpackProvider)
}
- // Provider: sort:topK (e.g. cortex's native partial-sort / heap-select) — swaps the
+ // Provider: sort:topK (e.g. cor's native partial-sort / heap-select) — swaps the
// JS result-ranking used by find() to pick the top `offset + limit` rows. The provider
// returns indices into a scores array, ordered descending with stable ties, identical
// to the JS sortTopKIndicesJs. rankIndicesByScore validates the provider's output and
@@ -833,7 +1129,7 @@ export class Brainy implements BrainyInterface {
if (metadataFactory) {
this.metadataIndex = metadataFactory(this.storage)
} else {
- // JS fallback — inject native EntityIdMapper if cortex provides one
+ // JS fallback — inject native EntityIdMapper if cor provides one
const entityIdMapperFactory = this.pluginRegistry.getProvider<(storage: StorageAdapter) => any>('entityIdMapper')
this.metadataIndex = new MetadataIndexManager(this.storage, {}, {
entityIdMapper: entityIdMapperFactory ? entityIdMapperFactory(this.storage) : undefined,
@@ -854,6 +1150,26 @@ export class Brainy implements BrainyInterface {
this.graphIndex = graphIndex
}
+ // Eager cold-load (readiness contract). A provider that persists its
+ // derived state exposes init?(): trigger the load NOW — AFTER
+ // metadataIndex.init() above (the id-mapper is hydrated first, so a
+ // native int-keyed index resolves endpoints/slots through it — the
+ // CTX-BR-RESTORE-REBUILD order) and BEFORE the rebuild gate — so a
+ // durable index reports its real size()/isReady() at the gate instead
+ // of eating a spurious rebuild-from-canonical on every open (§7.1
+ // rebuild()==0). Vector first, then graph. JS engines: the JS vector
+ // index has no init() (rebuild() IS its load path); the JS graph's
+ // init() already cold-loaded its LSM inside storage.getGraphIndex()
+ // (idempotent here).
+ const vectorWithInit = this.index as { init?: () => Promise }
+ if (typeof vectorWithInit.init === 'function') {
+ await vectorWithInit.init()
+ }
+ const graphWithInit = this.graphIndex as { init?: () => Promise }
+ if (typeof graphWithInit.init === 'function') {
+ await graphWithInit.init()
+ }
+
// 8.0 u64 contract: thread the shared UUID ↔ int resolver into the JS
// graph index and the storage layer's verb read paths.
this.wireGraphIdResolver()
@@ -898,9 +1214,46 @@ export class Brainy implements BrainyInterface {
`(storage committed: ${committed}) — provider replays the gap per ` +
`the post-commit applier contract`
)
+ } else if (providerGen > committed) {
+ // The AHEAD direction is incoherence, not a replay gap: the provider's
+ // persisted index claims writes the store no longer has — the signature
+ // of a torn copy or a log truncation that pulled the committed
+ // watermark back (crash recovery, byte-copy of a live store). A replay
+ // can never converge on it and index answers may reference vanished
+ // writes. Name it loudly at open so it is never diagnosed from a
+ // silent journal; the provider's own coherence check / heal walk (or
+ // brain.repairIndex()) is the cure.
+ prodLog.warn(
+ `[Brainy] Versioned index provider is AHEAD of the store: provider ` +
+ `generation ${providerGen} vs committed ${committed}. This store was ` +
+ `likely copied from a live service or truncated during crash recovery. ` +
+ `Derived-index answers may reference rolled-back writes until the ` +
+ `provider heals from canonical (brain.repairIndex() forces it).`
+ )
}
}
+ // Recover VFS content blobs a 7→8 upgrade left stranded in the removed
+ // branch system's copy-on-write area (`_cow/`). Runs BEFORE the rebuild
+ // (whose success stamp removes the pre-upgrade backup) so the backup still
+ // covers this step, and works on the raw object primitives (no blob store
+ // needed yet). A cheap no-op on native-8.0 / fresh brains and on a brain
+ // already healed. See adoptLegacyCowBlobs.
+ await this.autoAdoptLegacyVfsBlobsIfNeeded()
+
+ // Temporal-blob contract: one-time (marker-gated) backfill of blob
+ // history reference counts for stores whose generation history predates
+ // the contract — after it, compaction reclaims blob bytes exactly (zero
+ // live AND zero history references). On a scrub failure the store runs
+ // leak-safe (no blob reclamation) rather than risk a premature delete.
+ if (typeof (this.storage as unknown as {
+ backfillBlobHistoryRefCountsIfNeeded?: () => Promise
+ }).backfillBlobHistoryRefCountsIfNeeded === 'function') {
+ await (this.storage as unknown as {
+ backfillBlobHistoryRefCountsIfNeeded: () => Promise
+ }).backfillBlobHistoryRefCountsIfNeeded()
+ }
+
// Rebuild indexes if needed for existing data
await this.rebuildIndexesIfNeeded()
@@ -934,12 +1287,34 @@ export class Brainy implements BrainyInterface {
} else {
console.log(`[brainy] Providers: ${native.length}/${wellKnownKeys.length} native (${plugins}) | default: ${fallback.join(', ')}`)
}
+ // An activated accelerator that registered NOTHING is running as pure
+ // decoration — every query silently serves from the JS engines. Most
+ // often a licensing gate declining to engage. Say so, loudly, so an
+ // evaluator never concludes the accelerator "does nothing".
+ if (native.length === 0) {
+ console.warn(
+ `[brainy] ⚠ ${plugins} activated but registered 0 native providers — all queries are ` +
+ `running on the default JS engines. Check the plugin's requirements (e.g. a license ` +
+ `key) and its logs; see docs/PLUGINS.md.`
+ )
+ }
}
// Mark as initialized BEFORE VFS init
// VFS.init() needs brain to be marked initialized to call brain methods
this.initialized = true
+ // Migration LOCK (#18): if a native provider is running the one-time
+ // 7.x → 8.0 rebuild, WAIT for it to finish HERE — before VFS bootstrap and
+ // before the brain serves — so init-internal reads/writes (the VFS root
+ // get/add/find below) run against the rebuilt indexes, never a half-built
+ // one. This is the "not ready until upgraded" contract: a blocking upgrade
+ // to a known-good state. Bounded by `migrationWaitTimeoutMs`: a brain whose
+ // upgrade outlives the budget surfaces MigrationInProgressError here (raise
+ // the budget, or run the offline migrator) instead of bootstrapping the VFS
+ // against a partial index. A non-migrating brain returns instantly.
+ await this.awaitMigrationLock()
+
// Initialize VFS: Ensure VFS is ready when accessed as property
// This eliminates need for separate vfs.init() calls - zero additional complexity
this._vfs = new VirtualFileSystem(this)
@@ -1016,6 +1391,13 @@ export class Brainy implements BrainyInterface {
if (this._readyReject) {
this._readyReject(error instanceof Error ? error : new Error(String(error)))
}
+ // Machine-readable init failures pass through UNWRAPPED — the writer-lock
+ // conflict documents an err.code/err.lockInfo contract ("callers detect
+ // this case via err.code"), and wrapping in a fresh Error silently
+ // stripped both, leaving consumers only a message to regex against.
+ if (error instanceof Error && (error as Error & { code?: string }).code === 'BRAINY_WRITER_LOCKED') {
+ throw error
+ }
throw new Error(`Failed to initialize Brainy: ${error}`)
}
}
@@ -1107,20 +1489,52 @@ export class Brainy implements BrainyInterface {
}
}
- // Graceful shutdown signals (registered once globally)
- process.on('SIGTERM', async () => {
+ // Graceful shutdown signals (registered once globally). The listeners are
+ // kept as statics so the last live instance's close() can deregister them
+ // — the signal handles they hold are ref'd and would otherwise keep the
+ // process alive forever after every brain is closed.
+ Brainy.sigtermListener = async () => {
await flushOnShutdown()
process.exit(0)
- })
-
- process.on('SIGINT', async () => {
+ }
+ Brainy.sigintListener = async () => {
await flushOnShutdown()
process.exit(0)
- })
-
- process.on('beforeExit', async () => {
+ }
+ Brainy.beforeExitListener = async () => {
+ // Self-deregister FIRST: Node re-emits 'beforeExit' after every event-
+ // loop drain, and this flush schedules new async work — with the
+ // listener still attached, a script that never calls close() would spin
+ // flush → drain → flush forever and never exit. One flush, then the
+ // next drain finds no listener and the process exits.
+ if (Brainy.beforeExitListener) {
+ process.off('beforeExit', Brainy.beforeExitListener)
+ Brainy.beforeExitListener = undefined
+ }
await flushOnShutdown()
- })
+ }
+ process.on('SIGTERM', Brainy.sigtermListener)
+ process.on('SIGINT', Brainy.sigintListener)
+ process.on('beforeExit', Brainy.beforeExitListener)
+ }
+
+ /**
+ * Deregister the global shutdown hooks once NO live instance remains, so a
+ * script that closed every brain exits on its own — a library must never
+ * keep its host process alive. Re-initializing later re-registers them
+ * (the `shutdownHooksRegisteredGlobally` flag resets here).
+ */
+ private static deregisterShutdownHooksIfIdle(): void {
+ if (Brainy.instances.length > 0 || !Brainy.shutdownHooksRegisteredGlobally) {
+ return
+ }
+ if (Brainy.sigtermListener) process.off('SIGTERM', Brainy.sigtermListener)
+ if (Brainy.sigintListener) process.off('SIGINT', Brainy.sigintListener)
+ if (Brainy.beforeExitListener) process.off('beforeExit', Brainy.beforeExitListener)
+ Brainy.sigtermListener = undefined
+ Brainy.sigintListener = undefined
+ Brainy.beforeExitListener = undefined
+ Brainy.shutdownHooksRegisteredGlobally = false
}
/**
@@ -1136,13 +1550,31 @@ export class Brainy implements BrainyInterface {
* re-initializing — using a closed Brainy is a consumer bug, not a lazy-init
* opportunity.
*/
- private async ensureInitialized(): Promise {
+ private async ensureInitialized(opts?: {
+ bypassMigrationLock?: boolean
+ needs?: IndexFamily[]
+ }): Promise {
if (this.closed) {
throw new Error('Brainy instance is not initialized: it was closed via close(). Create a new instance.')
}
if (!this.initialized) {
await this.init()
}
+ // Coordinated migration LOCK (#18): every data-plane read and write funnels
+ // through here, so this is the single choke point that holds operations while
+ // a native provider runs its one-time 7.x → 8.0 rebuild-from-canonical — no
+ // op touches a half-built index. The gate is FAMILY-SCOPED: `needs` names the
+ // derived-index families this operation actually consults, so a read served
+ // entirely from canonical storage (`needs: []`) or from a healthy family
+ // never blocks on an UNRELATED family's migration. `needs` omitted = the
+ // conservative whole-brain wait (writes, and any read not yet classified).
+ // Observability (`health`/`checkHealth`) and the lock-clearing path
+ // (`stampBrainFormat`, which does not route through here) opt out entirely so
+ // an operator can always watch progress and a native provider can stamp.
+ // A brain that never migrates pays one boolean check (see awaitMigrationLock).
+ if (!opts?.bypassMigrationLock) {
+ await this.awaitMigrationLock(opts?.needs)
+ }
}
/**
@@ -1289,9 +1721,11 @@ export class Brainy implements BrainyInterface {
/**
* @description Persist one single-operation write (`add`/`update`/`remove`/
- * `relate`/`updateRelation`/`unrelate` and the per-item calls of their `*Many`
- * variants) as its OWN immutable generation — the Model-B "every write
- * versioned" contract. Wraps the write's existing `executeTransaction` batch in
+ * `relate`/`updateRelation`/`unrelate` and the per-item calls of `addMany`/
+ * `updateMany`/`relateMany`) as its OWN immutable generation — the Model-B
+ * "every write versioned" contract. (`removeMany` is the one exception: it
+ * commits each delete *chunk* as a single generation for bulk-delete efficiency,
+ * not one generation per removed id.) Wraps the write's existing `executeTransaction` batch in
* {@link GenerationStore.commitSingleOp}, which captures byte-identical
* before-images of `touched`, runs the batch with the generation watermark
* pinned to the reserved generation (so this write's graph-index ops stamp cor
@@ -1307,23 +1741,200 @@ export class Brainy implements BrainyInterface {
*/
private async persistSingleOp(
touched: { nouns?: string[]; verbs?: string[] },
- run: TransactionFunction
- ): Promise {
+ run: TransactionFunction,
+ precommit?: (before: CommitBeforeImages) => void,
+ pendingEvents?: PendingChangeEvent[]
+ ): Promise<{ generation?: number; timestamp: number; degraded?: string[] }> {
+ // Change-feed capture: when this write will emit, hold a reference to the
+ // commit's before-images so `remove` events can carry the record's last
+ // committed state (free — the commit reads them anyway for Model B).
+ let capturedBefore: CommitBeforeImages | undefined
+ const captureAndCheck =
+ pendingEvents && pendingEvents.length > 0
+ ? (before: CommitBeforeImages): void => {
+ capturedBefore = before
+ precommit?.(before)
+ }
+ : precommit
+
if (!this._generationStampingActive) {
// Init-time / infrastructure baseline write (e.g. the VFS root): apply
// WITHOUT creating a generation. Generation 0 is the freshly-materialized
// brain (bootstrap included); the first USER write is generation 1.
+ // Bootstrap writes are single-threaded, so a supplied precondition is
+ // honored against directly-read before-images (no commit mutex exists
+ // on this path — nothing to race).
+ if (captureAndCheck) {
+ const nouns = new Map()
+ for (const id of touched.nouns ?? []) {
+ const prev = await this.storage.readNounRaw(id)
+ nouns.set(id, { kind: 'noun', metadata: prev.metadata, vector: prev.vector })
+ }
+ const verbs = new Map()
+ for (const id of touched.verbs ?? []) {
+ const prev = await this.storage.readVerbRaw(id)
+ verbs.set(id, { kind: 'verb', metadata: prev.metadata, vector: prev.vector })
+ }
+ captureAndCheck({ nouns, verbs } as CommitBeforeImages)
+ }
await this.generationStore.runWithoutGeneration(() =>
- this.transactionManager.executeTransaction(run)
+ this.transactionManager.executeTransaction(run, {
+ timeout: transactTimeoutBudget(
+ (touched.nouns?.length ?? 0) + (touched.verbs?.length ?? 0)
+ )
+ })
)
- return
+ const timestamp = Date.now()
+ // Bootstrap writes are not generation-stamped; emit without one.
+ this.emitCommitted(pendingEvents, capturedBefore, undefined, timestamp)
+ return { timestamp }
}
- await this.generationStore.commitSingleOp({
- touched,
- execute: () => this.transactionManager.executeTransaction(run)
- })
+ let receipt
+ try {
+ receipt = await this.generationStore.commitSingleOp({
+ touched,
+ precommit: captureAndCheck,
+ execute: () =>
+ this.transactionManager.executeTransaction(run, {
+ timeout: transactTimeoutBudget(
+ (touched.nouns?.length ?? 0) + (touched.verbs?.length ?? 0)
+ )
+ })
+ })
+ } catch (err) {
+ // A failed rollback that left the store inconsistent (a remove/update
+ // whose restore-undo failed) quarantines writes until repairIndex().
+ if (err instanceof StoreInconsistentError) this.storeInconsistency = err
+ throw err
+ }
+ // POST-COMMIT ONLY: an aborted commit (CAS conflict, failed apply) throws
+ // above and never reaches this line — the feed cannot announce a write
+ // that did not become durable. (A degraded adopt-forward write DID commit —
+ // it carries a generation and emits normally.)
+ this.emitCommitted(pendingEvents, capturedBefore, receipt.generation, receipt.timestamp)
+ // An adopt-forward failed-rollback recovery committed the record but may have
+ // left its derived index incomplete (generationStore already warned once).
+ // Record the ids so reads and checkHealth() surface the incompleteness rather
+ // than silently returning partial data; repairIndex() clears them.
+ if (receipt.degraded && receipt.degraded.length > 0) {
+ for (const degradedId of receipt.degraded) this._indexDegradedIds.add(degradedId)
+ this._degradedReadWarned = false // re-arm the read-path warning
+ if (!this.config.silent) {
+ prodLog.warn(
+ `[Brainy] A single-op write committed in a DEGRADED state — the derived ` +
+ `index may be incomplete for id(s) ${receipt.degraded.join(', ')}. ` +
+ `Reads may return partial results until repairIndex() reconciles them.`
+ )
+ }
+ }
+ return receipt
}
+ /**
+ * @description Stamp pending change events with their commit receipt,
+ * enrich entity `remove` events with the record's last committed state
+ * (from the commit's before-images), and hand them to the change feed for
+ * post-mutex dispatch. No-op when the write produced no events.
+ * @param pending - Events the mutation method constructed pre-commit
+ * (only when a listener is subscribed — see {@link ChangeFeed.hasListeners}).
+ * @param before - The commit's before-images (delete-payload source).
+ * @param generation - The committed generation (absent for bootstrap writes).
+ * @param timestamp - The commit timestamp.
+ */
+ private emitCommitted(
+ pending: PendingChangeEvent[] | undefined,
+ before: CommitBeforeImages | undefined,
+ generation: number | undefined,
+ timestamp: number
+ ): void {
+ if (!pending || pending.length === 0) return
+ const events: BrainyChangeEvent[] = pending.map((p) => {
+ let entity = p.entity
+ if (!entity && p.kind === 'entity' && p.op === 'remove' && p.id && before) {
+ const record = before.nouns.get(p.id)?.metadata as
+ | Record
+ | null
+ | undefined
+ if (record) {
+ entity = this.entityViewFromRawRecord(p.id, record)
+ }
+ }
+ return {
+ ...p,
+ ...(entity && { entity }),
+ ...(generation !== undefined && { generation }),
+ timestamp
+ }
+ })
+ this._changeFeed.emit(events)
+ }
+
+ /**
+ * @description Build the change-event entity view from a stored flat
+ * metadata record (the shape before-images and pre-delete reads carry),
+ * using THE canonical reserved/custom split so the view can never drift
+ * from the live read paths.
+ * @param id - The entity's canonical UUID.
+ * @param record - The stored flat metadata record.
+ * @returns The `ChangeEventEntity` view (type, subtype, service, custom metadata).
+ */
+ private entityViewFromRawRecord(
+ id: string,
+ record: Record
+ ): NonNullable {
+ const { reserved, custom } = splitNounMetadataRecord(record)
+ return {
+ id,
+ type: String(reserved.noun ?? 'unknown'),
+ ...(reserved.subtype !== undefined && { subtype: String(reserved.subtype) }),
+ metadata: custom,
+ ...(reserved.service !== undefined && { service: String(reserved.service) })
+ }
+ }
+
+ /**
+ * @description Build the AGGREGATION view of an entity from a stored flat
+ * metadata record — EVERY reserved field mapped to its top-level entity
+ * name (stored `noun` → `type`), custom metadata in `metadata`. This must
+ * mirror the add-path `entityForIndexing` shape exactly: the aggregation
+ * engine resolves groupBy/where fields via `resolveEntityField`
+ * (top-level standard fields + custom metadata), so a view that drops a
+ * reserved field makes every aggregate grouped by that field decrement a
+ * group that does not exist — counts then drift upward forever after
+ * deletes (SELF-AGGREGATE-DELETE-DRIFT). Do not hand-roll subsets of this.
+ * @param record - The stored flat metadata record (before-image or pre-delete read).
+ * @returns The full-fidelity entity view for aggregation hooks.
+ */
+ private entityForAggFromRawRecord(record: Record): Record {
+ const { reserved, custom } = splitNounMetadataRecord(record)
+ const { noun, ...rest } = reserved
+ return {
+ type: noun,
+ ...rest,
+ metadata: custom
+ }
+ }
+
+ /**
+ * @description Add an entity (noun) to the brain. Embeds `data` into a vector and
+ * indexes the entity across all three intelligences — vector similarity, graph
+ * adjacency, and metadata — then returns its id. When no `id` is supplied a fresh
+ * time-ordered **UUID v7** is generated; a supplied natural-key string is
+ * normalized to a stable **UUID v5**. Under Model-B every add is its own immutable
+ * generation, so it is time-travelable via {@link asOf}.
+ * @param params - The entity to add. `data` (embedded for similarity) and `type`
+ * (a {@link NounType}) are the essentials; `subtype`, `metadata`, an explicit
+ * `id`, `visibility`, `confidence`, and `weight` are optional.
+ * @returns The entity's id — the supplied/normalized id, or a freshly generated UUID v7.
+ * @throws If the brain is read-only (`mode: 'reader'`), or brain-wide `requireSubtype`
+ * is on and the entity's type has no subtype.
+ * @example
+ * const id = await brain.add({
+ * data: 'Ada Lovelace',
+ * type: NounType.Person,
+ * metadata: { role: 'mathematician' }
+ * })
+ */
async add(params: AddParams): Promise {
this.assertWritable('add')
await this.ensureInitialized()
@@ -1372,7 +1983,10 @@ export class Brainy implements BrainyInterface {
// ifAbsent (7.31.0) — by-ID idempotent insert. Only meaningful when a custom id
// is supplied; a freshly generated UUID can never collide. Returns the existing
// (canonical) id without writing if the entity is already present (no throw,
- // no overwrite).
+ // no overwrite). This check is a FAST-FAIL (skips the embedding cost); the
+ // authoritative absence check runs in the insert's commit precondition
+ // below, atomic with the apply, so a concurrent same-id create cannot make
+ // both callers write.
if (params.id && params.ifAbsent) {
const existing = await this.storage.getNounMetadata(id)
if (existing) return id
@@ -1383,21 +1997,12 @@ export class Brainy implements BrainyInterface {
// delegate to update() so the supplied fields MERGE into the existing entity
// (metadata merge, re-embed on changed data, _rev bump, createdAt preserved)
// instead of the default destructive overwrite. When the id is absent, fall
- // through to the normal insert below.
+ // through to the guarded insert below (whose commit precondition converts a
+ // concurrent same-id create into this same merge — never an overwrite).
if (params.id && params.upsert) {
const existing = await this.storage.getNounMetadata(id)
if (existing) {
- await this.update({
- id,
- ...(params.data !== undefined && { data: params.data }),
- ...(params.type !== undefined && { type: params.type }),
- ...(params.subtype !== undefined && { subtype: params.subtype }),
- ...(params.visibility !== undefined && { visibility: params.visibility }),
- ...(params.metadata !== undefined && { metadata: params.metadata }),
- ...(params.vector !== undefined && { vector: params.vector }),
- ...(params.confidence !== undefined && { confidence: params.confidence }),
- ...(params.weight !== undefined && { weight: params.weight })
- })
+ await this.update(this.upsertMergeParams(id, params))
return id
}
}
@@ -1469,7 +2074,22 @@ export class Brainy implements BrainyInterface {
// Execute atomically with transaction system, generation-stamped as one
// immutable Model-B generation (before-image = the absent/create sentinel).
// All operations succeed or all rollback - prevents partial failures
- await this.persistSingleOp({ nouns: [id] }, async (tx) => {
+ // ifAbsent/upsert insert leg: must-be-absent commit precondition, run
+ // under the commit mutex against the authoritative before-image — the
+ // planning-time absence checks above are fast-fails that can interleave
+ // with a concurrent same-id create. Exactly one concurrent create wins;
+ // every other caller takes its documented resolution instead of
+ // silently overwriting the winner.
+ const requireAbsent = Boolean(params.id && (params.ifAbsent || params.upsert))
+ const insertPrecommit = requireAbsent
+ ? (before: CommitBeforeImages): void => {
+ if (before.nouns.get(id)?.metadata) {
+ throw new InsertPreconditionExistsSignal(id)
+ }
+ }
+ : undefined
+
+ const runInsert: TransactionFunction = async (tx) => {
// Operation 1: Save metadata FIRST (TypeAwareStorage caching)
// isNew=true: skip pre-read for rollback (entity doesn't exist yet)
tx.addOperation(
@@ -1496,7 +2116,62 @@ export class Brainy implements BrainyInterface {
tx.addOperation(
new AddToMetadataIndexOperation(this.metadataIndex, id, entityForIndexing)
)
- })
+ }
+
+ // Bounded retry closes the remaining interleavings without ever holding
+ // a lock across the loop: a lost insert race resolves to skip (ifAbsent)
+ // or merge (upsert); a merge that then hits a concurrent delete retries
+ // the insert. Each persistSingleOp call constructs fresh operations, so
+ // re-running the callback is safe.
+ // Change feed: built only when someone is listening (zero-cost gate).
+ const addEvents: PendingChangeEvent[] | undefined = this._changeFeed.hasListeners
+ ? [
+ {
+ kind: 'entity',
+ op: 'add',
+ id,
+ entity: {
+ id,
+ type: String(params.type),
+ ...(params.subtype !== undefined && { subtype: String(params.subtype) }),
+ metadata: (params.metadata as Record) ?? {},
+ ...(params.service !== undefined && { service: String(params.service) })
+ }
+ }
+ ]
+ : undefined
+
+ const MAX_UPSERT_ATTEMPTS = 10
+ for (let attempt = 0; ; attempt++) {
+ try {
+ await this.persistSingleOp({ nouns: [id] }, runInsert, insertPrecommit, addEvents)
+ break
+ } catch (err) {
+ if (!(err instanceof InsertPreconditionExistsSignal)) {
+ throw err
+ }
+ // A concurrent writer created the entity between our absence check
+ // and the commit.
+ if (params.ifAbsent) {
+ // Documented contract: return the existing id without writing.
+ return id
+ }
+ // upsert: merge into the now-existing entity. A concurrent delete
+ // between this conflict and the merge throws EntityNotFoundError —
+ // loop back and retry the insert.
+ try {
+ await this.update(this.upsertMergeParams(id, params))
+ return id
+ } catch (mergeErr) {
+ if (
+ !(mergeErr instanceof EntityNotFoundError) ||
+ attempt >= MAX_UPSERT_ATTEMPTS
+ ) {
+ throw mergeErr
+ }
+ }
+ }
+ }
// Aggregation hook (outside transaction — derived data, can be reconstructed)
if (this._aggregationIndex) {
@@ -1506,6 +2181,31 @@ export class Brainy implements BrainyInterface {
return id
}
+ /**
+ * @description The `update()` param mapping `add({ upsert })` delegates to
+ * when the target id already exists: every supplied field merges into the
+ * existing entity (metadata merge, re-embed on changed data, `_rev` bump,
+ * `createdAt` preserved); absent fields are left untouched. Shared by the
+ * planning-time upsert branch and the commit-conflict retry so the two
+ * paths can never drift.
+ * @param id - The canonical entity id the upsert resolved to.
+ * @param params - The original `add()` params carrying the fields to merge.
+ * @returns The `UpdateParams` for the merging `update()` call.
+ */
+ private upsertMergeParams(id: string, params: AddParams): UpdateParams {
+ return {
+ id,
+ ...(params.data !== undefined && { data: params.data }),
+ ...(params.type !== undefined && { type: params.type }),
+ ...(params.subtype !== undefined && { subtype: params.subtype }),
+ ...(params.visibility !== undefined && { visibility: params.visibility }),
+ ...(params.metadata !== undefined && { metadata: params.metadata }),
+ ...(params.vector !== undefined && { vector: params.vector }),
+ ...(params.confidence !== undefined && { confidence: params.confidence }),
+ ...(params.weight !== undefined && { weight: params.weight })
+ } as UpdateParams
+ }
+
/**
* Get an entity by ID
*
@@ -1639,7 +2339,10 @@ export class Brainy implements BrainyInterface {
*
*/
async get(id: string, options?: GetOptions): Promise | null> {
- await this.ensureInitialized()
+ // Canonical read: a get resolves an entity by id straight from storage and
+ // consults no derived index — it must not wait on any family's migration.
+ await this.ensureInitialized({ needs: [] })
+ this.warnIfReadsDegraded('get')
// Id normalization (8.0): a caller may read by their natural key — resolve
// it to the same canonical UUID add() stored. A real UUID passes through.
@@ -1697,7 +2400,8 @@ export class Brainy implements BrainyInterface {
* ```
*/
async batchGet(ids: string[], options?: GetOptions): Promise