feat: verb subtype + updateRelation + requireSubtype enforcement
Brings verbs to first-class parity with nouns. The 7.29.0 subtype primitive
shipped for entities only; this release ships the symmetric verb mirror plus
the enforcement layer for ensuring every entity AND every relationship has
both type AND subtype.
Layer V1 — verb subtype mirror
- HNSWVerbWithMetadata.subtype + STANDARD_VERB_FIELDS set + resolveVerbField()
- Relation<T>.subtype, RelateParams<T>.subtype, UpdateRelationParams<T> extended,
GetRelationsParams.subtype, GraphConstraints.subtype (for find connected)
- relate() persists subtype on verbMetadata + GraphVerb + transaction ops
- getRelations({ type, subtype }) fast-path filter with set membership
- find({ connected: { via, subtype, depth } }) traversal filter (depth-1 on
the JS path; explicit error on depth > 1 pointing at Cortex native)
- verbsToRelations + storage destructure sites surface subtype to top-level
- All three graph-index fast-path queries (getVerbsBySource/ByTarget) enrich
with subtype from metadata
Layer V2 — updateRelation() closes a pre-7.30 gap
- New first-class verb update method (parallel to update() for nouns)
- Changes subtype/type/weight/confidence/data/metadata in place
- Re-indexes in graph adjacency when verb type changes; id preserved
- validateUpdateRelationParams enforces id + at-least-one-field-to-update
Layer V3 — verb subtype storage rollup
- verbSubtypeCountsByType: Map<number, Map<string, number>> on BaseStorage
- verbSubtypeByIdCache for self-heal during update/delete
- incrementVerbSubtypeCount + decrementVerbSubtypeCount maintain state
- loadVerbSubtypeStatistics + saveVerbSubtypeStatistics persist to
_system/verb-subtype-statistics.json (mirrors noun-side shape)
- rebuildVerbSubtypeCounts for poison recovery / explicit repair
- getVerbSubtypeCountsByType accessor for the public counts API
- Wired into init() / flushCounts() / saveVerbMetadata / deleteVerbMetadata
Layer V4 — verb counts API + relationshipSubtypesOf
- brain.counts.byRelationshipSubtype(verb, subtype?) — O(1) breakdown or point
- brain.counts.topRelationshipSubtypes(verb, n) — top N by count
- brain.relationshipSubtypesOf(verb) — sorted distinct subtypes
Layer V5 — migrateField extended to verbs
- New entityKind?: 'noun' | 'verb' | 'both' option (default 'noun')
- Mirror verb iteration via storage.getVerbs() with same path semantics
- verbToRelationLike + buildRelationMigrationUpdate helpers project the
storage verb shape onto the Entity<T>-shaped surface readPath understands
- Routes through new updateRelation() for the verb-side rewrite
Enforcement (opt-in in 7.30, default in 8.0)
- brain.requireSubtype(type, options) — unified API for NounType OR VerbType.
Registers per-type rules with optional values whitelist; composes with the
brain-wide flag.
- new Brainy({ requireSubtype: true }) — brain-wide strict mode. Every public
write path validates the pairing guarantee.
- { except: [NounType.Thing, ...] } form for catch-all type exemptions
- Atomic-fail semantics on addMany / relateMany — pre-validate every item
before any storage write, throw on first failure with item index
- Per-type rules + brain-wide flag both throw with descriptive messages
- VFS infrastructure bypass via metadata.isVFSEntity / isVFS markers so
brain's own VFS writes don't get rejected when strict mode is on
VFS labeling — concrete subtypes for infrastructure entities
- VFS root: NounType.Collection + subtype: 'vfs-root' (was bare Collection)
- VFS directories: subtype: 'vfs-directory'
- VFS files: subtype: 'vfs-file' (NounType still mime-based)
- VFS containment edges: VerbType.Contains + subtype: 'vfs-contains'
- Lets consumers cleanly enumerate VFS state via find({ subtype: 'vfs-file' })
and distinguish Brainy's VFS Collections from user-created Collections
Docs
- docs/guides/subtypes-and-facets.md extended with Layer V (Verbs) section +
Enforcement section. New full reference at the bottom split into Layer 1
(nouns), Layer V (verbs), Layer 2 (facets), Layer 3 (migration), Enforcement.
- docs/api/README.md adds updateRelation(), getRelations({ subtype }), the
three verb-side counts methods, requireSubtype(), and the brain-wide
constructor option. relate() params include subtype.
- docs/DATA_MODEL.md adds a Subtype-for-VerbType section + STANDARD_VERB_FIELDS
- docs/architecture/finite-type-system.md extends Principle 1a to verbs
- docs/QUERY_OPERATORS.md adds a verb-subtype filter section covering
getRelations and find({connected, subtype}) traversal
- README.md "Subtypes" section now shows both noun + verb in one example +
the enforcement APIs
- RELEASES.md v7.30.0 entry with the full noun/verb capability parity matrix
Tests
- tests/integration/verb-subtype-and-enforcement.test.ts — 30 new tests
covering V1 round-trips, V1 set membership, updateRelation in place,
updateRelation preservation, V2 counts breakdown + point + topN + distinct,
V2 decrements on unrelate, V2 re-routes on updateRelation, V3 depth-1
traversal filter, V3 depth>1 explicit error, V4 verb migration, V4 both
entity kinds, V4 readBoth preservation, V5 per-type required rejection,
V5 vocabulary rejection, V5 on-vocab acceptance, V5 verb-side enforcement,
V5 addMany atomic-fail, V5 relateMany atomic-fail, V5 update enforcement,
V5 updateRelation enforcement, V5 brain-wide strict mode, V5 except clause.
Verification
- Unit suite: 1468/1468 passing
- Noun subtype integration (7.29 carryover): 26/26 passing
- Verb subtype + enforcement integration: 30/30 passing
- Type-check: clean
- Build: clean
- Public closed-source reference audit: clean
Internal 8.0 spec
- .strategy/BRAINY-8.0-SUBTYPE-CONTRACT.md (gitignored, not in npm artifact)
documents the contract upgrade Cortex 3.0 implements against: required-by-
default subtype, SubtypeRegistry typing hook, native simplification,
multi-hop traversal native fast path, brain.fillSubtypes() migration helper.
Coordinated via PLATFORM-HANDOFF rows CTX-SUBTYPE-PARITY-V2 (7.30 parallel
work) and CTX-SUBTYPE-8.0-CONTRACT (8.0 spec).
This commit is contained in:
parent
8c68396e71
commit
c0d326b36d
14 changed files with 2180 additions and 68 deletions
184
RELEASES.md
184
RELEASES.md
|
|
@ -11,6 +11,190 @@ Collective. The SDK wraps it — most products never call Brainy directly. Read
|
|||
|
||||
---
|
||||
|
||||
## v7.30.0 — 2026-06-05
|
||||
|
||||
**Affected products:** consumers modeling typed relationships with sub-classification
|
||||
(direct vs dotted-line management; spouse / sibling / colleague; collaborator vs competitor;
|
||||
etc.), and anyone wanting to enforce the pairing of `type` + `subtype` on every write.
|
||||
Additive; drop-in from 7.29.x. No deprecations.
|
||||
|
||||
### Symmetric — `subtype` on relationships (parity with 7.29.0 nouns)
|
||||
|
||||
`subtype?: string` is now a first-class standard field on every relationship, mirroring the
|
||||
noun-side work shipped in 7.29.0. Verbs and nouns are now first-class peers — every API
|
||||
available on the noun side has a verb-side mirror.
|
||||
|
||||
```typescript
|
||||
const ceoId = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'Avery' })
|
||||
const vpId = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'Jordan' })
|
||||
|
||||
await brain.relate({
|
||||
from: ceoId,
|
||||
to: vpId,
|
||||
type: VerbType.ReportsTo,
|
||||
subtype: 'direct' // sub-classification on the edge
|
||||
})
|
||||
```
|
||||
|
||||
**Read & filter:**
|
||||
|
||||
```typescript
|
||||
// Fast-path filter — column-store hit, not metadata fallback
|
||||
const direct = await brain.getRelations({
|
||||
from: ceoId,
|
||||
type: VerbType.ReportsTo,
|
||||
subtype: 'direct'
|
||||
})
|
||||
|
||||
// Set membership
|
||||
const all = await brain.getRelations({
|
||||
from: ceoId,
|
||||
type: VerbType.ReportsTo,
|
||||
subtype: ['direct', 'dotted-line']
|
||||
})
|
||||
|
||||
// Traversal filter (depth-1 in JS; multi-hop lands on Cortex native)
|
||||
const reports = await brain.find({
|
||||
connected: { from: ceoId, via: VerbType.ReportsTo, subtype: 'direct', depth: 1 }
|
||||
})
|
||||
```
|
||||
|
||||
### New — `updateRelation()` closes a long-standing gap
|
||||
|
||||
Verbs previously had no update method — the only way to change a relationship was
|
||||
delete-then-recreate, which lost the relation id. 7.30 ships `brain.updateRelation()`:
|
||||
|
||||
```typescript
|
||||
await brain.updateRelation({ id: relId, subtype: 'dotted-line' })
|
||||
await brain.updateRelation({ id: relId, weight: 0.5, confidence: 0.9 })
|
||||
|
||||
// Change verb type — re-indexes in graph adjacency, id preserved
|
||||
await brain.updateRelation({ id: relId, type: VerbType.WorksWith })
|
||||
```
|
||||
|
||||
### New — O(1) verb subtype counts via the persisted rollup
|
||||
|
||||
`_system/verb-subtype-statistics.json` mirrors the noun-side rollup shipped in 7.29.0.
|
||||
Per-VerbType-per-subtype counts are maintained incrementally and persisted; reads are O(1):
|
||||
|
||||
```typescript
|
||||
brain.counts.byRelationshipSubtype(VerbType.ReportsTo)
|
||||
// → { direct: 12, 'dotted-line': 3 }
|
||||
|
||||
brain.counts.byRelationshipSubtype(VerbType.ReportsTo, 'direct') // O(1) point
|
||||
// → 12
|
||||
|
||||
brain.counts.topRelationshipSubtypes(VerbType.ReportsTo, 3)
|
||||
// → [['direct', 12], ['dotted-line', 3]]
|
||||
|
||||
brain.relationshipSubtypesOf(VerbType.ReportsTo)
|
||||
// → ['direct', 'dotted-line']
|
||||
```
|
||||
|
||||
### New — `brain.requireSubtype(type, options)` per-type enforcement
|
||||
|
||||
Unified API for noun OR verb types — register specific types as requiring a subtype,
|
||||
optionally with a fixed vocabulary:
|
||||
|
||||
```typescript
|
||||
brain.requireSubtype(NounType.Person, {
|
||||
values: ['employee', 'customer', 'vendor'],
|
||||
required: true
|
||||
})
|
||||
|
||||
brain.requireSubtype(VerbType.ReportsTo, {
|
||||
values: ['direct', 'dotted-line'],
|
||||
required: true
|
||||
})
|
||||
|
||||
// Now this throws — Person requires subtype:
|
||||
await brain.add({ type: NounType.Person, data: 'no subtype' })
|
||||
|
||||
// And this throws — 'matrix' isn't in the registered vocabulary:
|
||||
await brain.relate({ from: a, to: b, type: VerbType.ReportsTo, subtype: 'matrix' })
|
||||
```
|
||||
|
||||
### New — Brain-wide strict mode
|
||||
|
||||
`new Brainy({ requireSubtype: true })` enforces subtype on every public write across the
|
||||
whole brain. Composes with per-type rules; per-type rules win when both apply.
|
||||
|
||||
```typescript
|
||||
// Every write must include subtype
|
||||
const brain = new Brainy({ requireSubtype: true })
|
||||
|
||||
// Exempt specific types (e.g. catch-all Thing)
|
||||
const brain2 = new Brainy({
|
||||
requireSubtype: { except: [NounType.Thing, NounType.Custom] }
|
||||
})
|
||||
```
|
||||
|
||||
When strict mode is on:
|
||||
- Every `add()` / `addMany()` / `update()` / `relate()` / `relateMany()` / `updateRelation()`
|
||||
rejects writes missing a subtype on a non-exempt type.
|
||||
- `addMany()` and `relateMany()` validate every item BEFORE any storage write —
|
||||
atomic-fail semantics, no partial writes.
|
||||
- Brainy's own infrastructure writes (VFS root, directories, files) bypass via the
|
||||
`metadata.isVFSEntity: true` marker so existing consumers' VFS usage continues to work.
|
||||
|
||||
The brain-wide flag becomes the default in 8.0.0; the type-level `subtype` field becomes
|
||||
required at the type system level. The full 8.0 contract upgrade is coordinated through the
|
||||
internal platform handoff (`CTX-SUBTYPE-8.0-CONTRACT`).
|
||||
|
||||
### `migrateField()` extended to verbs
|
||||
|
||||
The migration helper shipped in 7.29.0 now walks verbs too via the new `entityKind` option:
|
||||
|
||||
```typescript
|
||||
// Migrate verb-side metadata.kind → top-level subtype
|
||||
await brain.migrateField({
|
||||
from: 'metadata.kind',
|
||||
to: 'subtype',
|
||||
entityKind: 'verb'
|
||||
})
|
||||
|
||||
// Or walk nouns and verbs in one pass
|
||||
await brain.migrateField({
|
||||
from: 'metadata.kind',
|
||||
to: 'subtype',
|
||||
entityKind: 'both'
|
||||
})
|
||||
```
|
||||
|
||||
Default is `entityKind: 'noun'` (backward-compatible).
|
||||
|
||||
### Symmetry — noun + verb capability matrix
|
||||
|
||||
7.30 closes every gap between nouns and verbs. Every capability available on the noun side
|
||||
has a verb-side mirror:
|
||||
|
||||
| Capability | Nouns | Verbs |
|
||||
|---|---|---|
|
||||
| `subtype` top-level field | ✓ (7.29) | ✓ (7.30 new) |
|
||||
| Standard-field set | `STANDARD_ENTITY_FIELDS` | `STANDARD_VERB_FIELDS` (new) |
|
||||
| Field resolver helper | `resolveEntityField` | `resolveVerbField` (new) |
|
||||
| Statistics rollup | `_system/subtype-statistics.json` | `_system/verb-subtype-statistics.json` (new) |
|
||||
| Counts breakdown | `counts.bySubtype` / `topSubtypes` / `subtypesOf` | `counts.byRelationshipSubtype` / `topRelationshipSubtypes` / `relationshipSubtypesOf` (new) |
|
||||
| Fast-path filter | `find({type, subtype})` | `getRelations({type, subtype})` + `find({connected, subtype})` (new) |
|
||||
| Update method | `update()` | `updateRelation()` (new — closed pre-7.30 gap) |
|
||||
| Migration helper | `migrateField()` | `migrateField({entityKind: 'verb'\|'both'})` (new) |
|
||||
| Enforcement | `requireSubtype(NounType, ...)` | `requireSubtype(VerbType, ...)` — one unified API |
|
||||
| Brain-wide strict mode | `new Brainy({ requireSubtype })` covers both | same |
|
||||
|
||||
### Cortex compatibility
|
||||
|
||||
Verb subtype works under Cortex out of the box via auto-field-indexing. Cortex parity items
|
||||
for the verb side (native query planner recognition, `verbSubtypeCountsByType` native rollup,
|
||||
per-edge subtype on native graph adjacency for multi-hop traversal filtering) ship in the
|
||||
next Cortex release. Not a Brainy blocker.
|
||||
|
||||
### Docs
|
||||
|
||||
Full guide: `docs/guides/subtypes-and-facets.md` (extended with Layer V + Enforcement
|
||||
sections). The verb subtype + enforcement APIs are documented in `docs/api/README.md`.
|
||||
|
||||
---
|
||||
|
||||
## v7.29.0 — 2026-06-04
|
||||
|
||||
**Affected products:** anyone modeling entities with per-product sub-classification — every
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue