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).
8 KiB
Query Operators (BFO)
Brainy Field Operators — the complete reference for
wherefilters infind().
All operators work with find({ where: { ... } }) and filter on metadata fields (not data).
Equality
| Operator | Alias | Description | Example |
|---|---|---|---|
equals |
eq, is |
Exact match | { status: { equals: 'active' } } |
notEquals |
ne, isNot |
Not equal | { status: { notEquals: 'deleted' } } |
Shorthand: A bare value is treated as equals:
// These are equivalent:
brain.find({ where: { status: 'active' } })
brain.find({ where: { status: { equals: 'active' } } })
Comparison
| 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 } } |
between |
— | Inclusive range [min, max] |
{ year: { between: [2020, 2025] } } |
// Range query
const recent = await brain.find({
where: {
createdAt: { between: [Date.now() - 86400000, Date.now()] }
}
})
Array / Set
| Operator | Alias | Description | Example |
|---|---|---|---|
oneOf |
in |
Value is one of the given options | { color: { oneOf: ['red', 'blue'] } } |
noneOf |
— | Value is NOT one of the given options | { status: { noneOf: ['deleted', 'archived'] } } |
contains |
— | Array field contains value | { tags: { contains: 'ai' } } |
excludes |
— | Array field does NOT contain value | { tags: { excludes: 'spam' } } |
hasAll |
— | Array field contains ALL listed values | { skills: { hasAll: ['js', 'ts'] } } |
// Find entities tagged with 'ai'
const aiEntities = await brain.find({
where: { tags: { contains: 'ai' } }
})
// Find entities of specific types
const people = await brain.find({
where: { noun: { oneOf: ['Person', 'Agent'] } }
})
Existence
| Operator | Description | Example |
|---|---|---|
exists: true |
Field exists (has any value) | { email: { exists: true } } |
exists: false |
Field does NOT exist | { email: { exists: false } } |
missing: true |
Field does NOT exist (alias for exists: false) |
{ email: { missing: true } } |
missing: false |
Field exists (alias for exists: true) |
{ email: { missing: false } } |
// Find entities that have an email field
const withEmail = await brain.find({
where: { email: { exists: true } }
})
Pattern (In-Memory Only)
These operators work via the in-memory filter path. They are applied after the indexed query, so use them with other indexed operators for best performance.
| Operator | Description | Example |
|---|---|---|
matches |
Regex or string pattern match | { name: { matches: /^Dr\./ } } |
startsWith |
String prefix | { name: { startsWith: 'John' } } |
endsWith |
String suffix | { email: { endsWith: '@gmail.com' } } |
const doctors = await brain.find({
where: {
type: NounType.Person, // Indexed — fast
name: { startsWith: 'Dr.' } // In-memory — applied after
}
})
Logical
Combine multiple conditions:
| Operator | Description | Example |
|---|---|---|
allOf |
ALL sub-filters must match (AND) | { allOf: [{ status: 'active' }, { role: 'admin' }] } |
anyOf |
ANY sub-filter must match (OR) | { anyOf: [{ role: 'admin' }, { role: 'owner' }] } |
not |
Invert a filter | { not: { status: 'deleted' } } |
// Complex OR query
const adminsOrOwners = await brain.find({
where: {
anyOf: [
{ role: 'admin' },
{ role: 'owner' }
]
}
})
// NOT query
const notDeleted = await brain.find({
where: {
not: { status: 'deleted' }
}
})
// Combined AND + OR
const results = await brain.find({
where: {
allOf: [
{ department: 'engineering' },
{ anyOf: [
{ level: 'senior' },
{ yearsExperience: { greaterThan: 5 } }
]}
]
}
})
Indexed vs In-Memory Operators
Brainy's MetadataIndex supports a subset of operators natively for O(1) field lookups. Other operators fall back to in-memory filtering.
| Operator | MetadataIndex (Indexed) | In-Memory Fallback |
|---|---|---|
equals / eq |
Yes | Yes |
notEquals / ne |
— | Yes |
greaterThan / gt |
Yes | Yes |
greaterEqual / gte |
Yes | Yes |
lessThan / lt |
Yes | Yes |
lessEqual / lte |
Yes | Yes |
between |
Yes | Yes |
oneOf / in |
Yes | Yes |
noneOf |
— | Yes |
contains |
Yes | Yes |
exists / missing |
Yes | Yes |
matches |
— | Yes |
startsWith |
— | Yes |
endsWith |
— | Yes |
allOf |
Partial | Yes |
anyOf |
Partial | Yes |
not |
— | Yes |
Performance tip: Combine indexed operators (equals, greaterThan, oneOf, between, contains, exists) with pattern operators for optimal speed — the index narrows results first, then patterns filter in memory.
Practical Examples
Filter by entity type
// Using the type shorthand (recommended)
brain.find({ type: NounType.Person })
// Using where.noun directly
brain.find({ where: { noun: NounType.Person } })
// Multiple types
brain.find({ type: [NounType.Person, NounType.Agent] })
Filter by subtype
subtype is a top-level standard field — takes the column-store fast path, not the metadata fallback. Pair with type for the typical "Person who is an employee" query:
// Equality on subtype:
brain.find({ type: NounType.Person, subtype: 'employee' })
// Set membership:
brain.find({ type: NounType.Person, subtype: ['employee', 'contractor'] })
// Operator-form predicates use `where`:
brain.find({
type: NounType.Person,
where: { subtype: { exists: true } }
})
See the Subtypes & Facets guide for the full surface.
Filter relationships by subtype (7.30+)
Verbs are first-class peers — getRelations() and graph traversal both honor subtype filters on the fast path:
// Filter relationships by VerbType subtype
const direct = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
// Set membership on verb subtype
const all = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line']
})
// Graph traversal — subtype filters traversal edges (depth-1 in 7.30 JS path;
// multi-hop subtype filtering lands on Cortex native)
const reports = await brain.find({
connected: {
from: ceoId,
via: VerbType.ReportsTo,
subtype: 'direct',
depth: 1
}
})
Combine semantic search with filters
const results = await brain.find({
query: 'machine learning engineer', // Semantic search (on data)
type: NounType.Person, // Type filter (indexed)
where: {
department: 'engineering', // Exact match (indexed)
yearsExperience: { greaterThan: 3 } // Range filter (indexed)
},
limit: 10
})
Temporal queries
const lastWeek = Date.now() - 7 * 24 * 60 * 60 * 1000
const recentEntities = await brain.find({
where: {
createdAt: { greaterThan: lastWeek }
},
orderBy: 'createdAt',
order: 'desc',
limit: 50
})
Graph + metadata combination
const results = await brain.find({
connected: {
from: teamLeadId,
via: VerbType.WorksWith,
depth: 2
},
where: {
role: { oneOf: ['engineer', 'designer'] },
active: true
}
})
See Also
- Data Model — Entity structure, data vs metadata
- API Reference — Complete API documentation
- Find System — Natural language find() details