Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive
shipped for entities only; this release ships the symmetric verb mirror plus
the enforcement layer for ensuring every entity AND every relationship has
both type AND subtype.
Layer V1 — verb subtype mirror
- HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField()
- Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended,
GetRelationsParams.subtype, GraphConstraints.subtype (for find connected)
- relate() persists subtype on verbMetadata + GraphVerb + transaction ops
- getRelations({ type, subtype }) fast-path filter with set membership
- find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on
the JS path; explicit error on depth > 1 pointing at Cortex native)
- verbsToRelations + storage destructure sites surface subtype to top-level
- All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich
with subtype from metadata
Layer V2 — updateRelation() closes a pre-7.30 gap
- New first-class verb update method (parallel to update() for nouns)
- Changes subtype/type/weight/confidence/data/metadata in place
- Re-indexes in graph adjacency when verb type changes; id preserved
- validateUpdateRelationParams enforces id + at-least-one-field-to-update
Layer V3 — verb subtype storage rollup
- verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage
- verbSubtypeByIdCache for self-heal during update/delete
- incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state
- loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to
_system/verb-subtype-statistics.json (mirrors noun-side shape)
- rebuildVerbSubtypeCounts for poison recovery / explicit repair
- getVerbSubtypeCountsByType accessor for the public counts API
- Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata
Layer V4 — verb counts API + relationshipSubtypesOf
- brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point
- brain.counts.topRelationshipSubtypes(verb, n) — top N by count
- brain.relationshipSubtypesOf(verb) — sorted distinct subtypes
Layer V5 — migrateField extended to verbs
- New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun')
- Mirror verb iteration via storage.getVerbs() with same path semantics
- verbToRelationLike + buildRelationMigrationUpdate helpers project the
storage verb shape onto the Entity<T>-shaped surface readPath understands
- Routes through new updateRelation() for the verb-side rewrite
Enforcement (opt-in in 7.30, default in 8.0)
- brain.requireSubtype(type, options) — unified API for NounType OR VerbType.
Registers per-type rules with optional values whitelist; composes with the
brain-wide flag.
- new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public
write path validates the pairing guarantee.
- { except: [NounType.Thing, ...] } form for catch-all type exemptions
- Atomic-fail semantics on addMany / relateMany — pre-validate every item
before any storage write, throw on first failure with item index
- Per-type rules + brain-wide flag both throw with descriptive messages
- VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so
brain's own VFS writes don't get rejected when strict mode is on
VFS labeling — concrete subtypes for infrastructure entities
- VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection)
- VFS directories: subtype: 'vfs-directory'
- VFS files: subtype: 'vfs-file' (NounType still mime-based)
- VFS containment edges: VerbType.Contains + subtype: 'vfs-contains'
- Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' })
and distinguish Brainy's VFS Collections from user-created Collections
Docs
- docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section +
Enforcement section. New full reference at the bottom split into Layer 1
(nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement.
- docs/api/README.md adds updateRelation(), getRelations({ subtype }), the
three verb-side counts methods, requireSubtype(), and the brain-wide
constructor option. relate() params include subtype.
- docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS
- docs/architecture/finite-type-system.md extends Principle 1a to verbs
- docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering
getRelations and find({connected, subtype}) traversal
- README.md "Subtypes" section now shows both noun + verb in one example +
the enforcement APIs
- RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix
Tests
- tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests
covering V1 round-trips, V1 set membership, updateRelation in place,
updateRelation preservation, V2 counts breakdown + point + topN + distinct,
V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1
traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both
entity kinds, V4 readBoth preservation, V5 per-type required rejection,
V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement,
V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement,
V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause.
Verification
- Unit suite: 1468/1468 passing
- Noun subtype integration (7.29 carryover): 26/26 passing
- Verb subtype + enforcement integration: 30/30 passing
- Type-check: clean
- Build: clean
- Public closed-source reference audit: clean
Internal 8.0 spec
- .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact)
documents the contract upgrade Cortex 3.0 implements against: required-by-
default subtype, SubtypeRegistry typing hook, native simplification,
multi-hop traversal native fast path, brain.fillSubtypes() migration helper.
Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel
work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
10 KiB
Data Model
How Brainy stores entities and relationships, and the critical distinction between
dataandmetadata.
Entity (Noun)
An entity is the fundamental data unit in Brainy. Every entity has:
| Field | Type | Indexed | Description |
|---|---|---|---|
id |
string |
Primary key | UUID v4 (auto-generated or custom) |
data |
any |
HNSW vector index | Content used for semantic/hybrid search. Strings auto-embed. |
metadata |
object |
MetadataIndex | Structured queryable fields (tags, dates, flags, etc.) |
type |
NounType |
MetadataIndex (as noun) |
Entity type classification |
vector |
number[] |
HNSW | 384-dim embedding (auto-computed from data or user-provided) |
confidence |
number |
MetadataIndex | Type classification confidence (0-1) |
weight |
number |
MetadataIndex | Entity importance/salience (0-1) |
service |
string |
MetadataIndex | Multi-tenancy identifier |
createdAt |
number |
MetadataIndex | Creation timestamp (ms since epoch) |
updatedAt |
number |
MetadataIndex | Last update timestamp (ms since epoch) |
createdBy |
object |
MetadataIndex | Source augmentation info |
Example
const id = await brain.add({
data: 'John Smith is a software engineer at Acme Corp', // → embedded into vector
type: NounType.Person,
metadata: { // → indexed, queryable via where filters
role: 'engineer',
department: 'backend',
yearsExperience: 8
},
confidence: 0.95,
weight: 0.7
})
Relationship (Verb)
A relationship is a typed, directed edge connecting two entities.
| Field | Type | Indexed | Description |
|---|---|---|---|
id |
string |
Primary key | UUID v4 (auto-generated) |
from |
string |
GraphAdjacencyIndex | Source entity ID |
to |
string |
GraphAdjacencyIndex | Target entity ID |
type |
VerbType |
GraphAdjacencyIndex (as verb) |
Relationship type classification |
data |
any |
— | Opaque content (overrides auto-computed vector if provided) |
metadata |
object |
— | Structured fields on the edge |
weight |
number |
— | Connection strength (0-1, default: 1.0) |
confidence |
number |
— | Relationship certainty (0-1) |
evidence |
RelationEvidence |
— | Why this relationship was detected |
createdAt |
number |
— | Creation timestamp (ms since epoch) |
updatedAt |
number |
— | Last update timestamp (ms since epoch) |
service |
string |
— | Multi-tenancy identifier |
Example
const relId = await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn,
data: 'Lead engineer on the AI module', // Optional: content for this edge
metadata: { // Optional: queryable edge fields
role: 'lead',
startDate: '2024-01-15'
},
weight: 0.9
})
Data vs Metadata
This is the most important concept in Brainy's storage model:
data — Content for Semantic Search
- Embedded into a 384-dimensional vector via the WASM embedding engine
- Searchable via semantic similarity (HNSW vector index) and hybrid text+semantic search
- Queried by passing
querytofind():brain.find({ query: 'machine learning algorithms' }) - NOT indexed by MetadataIndex — you cannot use
wherefilters ondata - Stored opaquely: strings, objects, numbers — anything goes
metadata — Structured Queryable Fields
- Indexed by MetadataIndex with O(1) lookups per field
- Queryable via
wherefilters using BFO operators:brain.find({ where: { department: 'engineering', yearsExperience: { greaterThan: 5 }, tags: { contains: 'senior' } } }) - NOT used for vector/semantic search
- Must be a flat or lightly nested object
Quick Reference
data |
metadata |
|
|---|---|---|
| Purpose | Content for embedding / semantic search | Structured fields for filtering |
| Searched by | find({ query }) — vector similarity, hybrid text+semantic |
find({ where }) — exact, range, set operators |
| Indexed by | HNSW vector index | MetadataIndex |
| Queryable with operators? | No | Yes (equals, greaterThan, oneOf, etc.) |
| Auto-embedded? | Yes (strings → 384-dim vectors) | No |
| Typical content | Text descriptions, document content | Tags, dates, status flags, categories, numeric fields |
Common Pattern
// Add an article
await brain.add({
data: 'A deep dive into transformer architectures and attention mechanisms',
type: NounType.Document,
metadata: {
title: 'Transformer Deep Dive',
author: 'Dr. Chen',
publishedYear: 2024,
tags: ['AI', 'transformers', 'NLP'],
status: 'published'
}
})
// Search by content (semantic — searches data)
const results = await brain.find({ query: 'neural network attention' })
// Filter by fields (exact — queries metadata)
const recent = await brain.find({
where: {
publishedYear: { greaterThan: 2023 },
status: 'published'
}
})
// Combine both (Triple Intelligence)
const precise = await brain.find({
query: 'attention mechanisms', // Semantic search on data
where: { author: 'Dr. Chen' }, // Metadata filter
connected: { from: authorId, depth: 1 } // Graph traversal
})
Storage Field Naming
Internally, Brainy uses different field names in storage vs the public API:
| Public API (Entity/Relation) | Storage (metadata object) | Notes |
|---|---|---|
type |
noun |
Entity type stored as noun |
from |
sourceId |
Relationship source |
to |
targetId |
Relationship target |
type (on Relation) |
verb |
Relationship type stored as verb |
When querying with find(), you can use:
typeparameter (convenience alias, equivalent towhere.noun)where.noundirectly
// These are equivalent:
brain.find({ type: NounType.Person })
brain.find({ where: { noun: NounType.Person } })
Standard Metadata Fields
When you add an entity, Brainy stores these standard fields in the metadata object alongside your custom fields:
| Field | Set By | Description |
|---|---|---|
noun |
System | Entity type (NounType enum value) |
subtype |
User | Per-NounType sub-classification (e.g. 'employee', 'invoice', 'milestone'). Flat string, no hierarchy. Indexed on the fast path and rolled into per-NounType statistics. |
data |
System | The raw data value (stored opaquely) |
createdAt |
System | Creation timestamp |
updatedAt |
System | Last update timestamp |
confidence |
User | Type classification confidence |
weight |
User | Entity importance |
service |
User | Multi-tenancy identifier |
createdBy |
User/System | Source augmentation |
On read, these standard fields are extracted to top-level Entity properties. The metadata field on the returned Entity contains only your custom fields.
Subtype — sub-classification within a NounType
type (NounType) is a stable 42-value enum. subtype is the consumer-chosen string vocabulary within a type:
// A Person who is an employee:
await brain.add({
data: 'Avery Brooks — runs the AI lab',
type: NounType.Person,
subtype: 'employee',
metadata: { department: 'ai-lab' }
})
// A Document that is an invoice:
await brain.add({
data: 'INV-2026-001',
type: NounType.Document,
subtype: 'invoice',
metadata: { amount: 1500 }
})
subtype lives at the top level — NOT inside metadata, NOT inside data. That's how find({ type, subtype }) routes through the standard-field fast path (column-store hit) instead of the metadata fallback. See Subtypes & Facets for the full guide including trackField() and migrateField().
Subtype — sub-classification within a VerbType (7.30+)
Relationships are first-class citizens too. Every verb (VerbType) gets the same subtype primitive — a ReportsTo relationship might carry subtype: 'direct' vs 'dotted-line'; a RelatedTo edge might carry 'spouse' / 'sibling' / 'colleague'. Same shape as the noun side: flat string, no hierarchy, top-level standard field on HNSWVerbWithMetadata and on the public Relation<T>:
await brain.relate({
from: ceoId,
to: vpId,
type: VerbType.ReportsTo,
subtype: 'direct', // top-level standard field
metadata: { since: '2025-Q1' } // user-custom fields stay in metadata
})
Fast-path filter on the verb side:
const direct = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
The verb-side rollup at _system/verb-subtype-statistics.json mirrors the noun-side _system/subtype-statistics.json — same shape, same self-heal machinery. Per-VerbType-per-subtype counts are O(1) via brain.counts.byRelationshipSubtype().
Verbs and nouns now have full capability parity — every API on the noun side has a verb-side mirror, including the new brain.updateRelation() (which closed a pre-7.30 gap where relationships had no update path).
Standard verb fields
The verb-side equivalent of STANDARD_ENTITY_FIELDS is STANDARD_VERB_FIELDS, exported from src/coreTypes.ts. Verb-specific standard fields:
| Field | Description |
|---|---|
verb |
The VerbType enum value |
sourceId / targetId |
The two endpoints of the relationship |
subtype |
Sub-classification within the VerbType (7.30+) |
confidence, weight, createdAt, updatedAt, service, createdBy, data |
Same semantics as the noun-side standard fields |
The companion resolveVerbField(verb, field) helper resolves field paths the same way resolveEntityField does for nouns: standard fields first, metadata fallback for everything else.
See Also
- API Reference — Complete API documentation
- Query Operators — All BFO operators with examples
- Find System — Natural language find() details