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
|
|
@ -68,6 +68,15 @@ export interface Relation<T = any> {
|
|||
to: string
|
||||
/** Relationship type classification (VerbType enum) */
|
||||
type: VerbType
|
||||
/**
|
||||
* Per-product sub-classification within the VerbType (e.g. a `Manages`
|
||||
* relationship might have `subtype: 'direct'` vs `'dotted-line'`; a `RelatedTo`
|
||||
* edge might carry `'spouse'` / `'sibling'` / `'colleague'`). Flat string, no
|
||||
* hierarchy. Top-level standard field — indexed on the fast path and rolled into
|
||||
* per-VerbType statistics so queries like `getRelations({ verb, subtype })` hit
|
||||
* the column-store directly.
|
||||
*/
|
||||
subtype?: string
|
||||
/** Connection strength (0-1, default: 1.0) */
|
||||
weight?: number
|
||||
/** Opaque content for the relationship (overrides auto-computed vector if provided) */
|
||||
|
|
@ -220,6 +229,14 @@ export interface RelateParams<T = any> {
|
|||
to: string
|
||||
/** Relationship type classification (required) */
|
||||
type: VerbType
|
||||
/**
|
||||
* Per-product sub-classification within the VerbType (e.g. a `Manages`
|
||||
* relationship might have `subtype: 'direct'` or `'dotted-line'`; a `RelatedTo`
|
||||
* edge might carry `'spouse'` or `'colleague'`). Flat string, no hierarchy.
|
||||
* Indexed and rolled up into per-VerbType statistics for fast filtering
|
||||
* (`getRelations({ verb, subtype })`) and aggregation (`groupBy:['subtype']`).
|
||||
*/
|
||||
subtype?: string
|
||||
/** Connection strength (0-1, default: 1.0) */
|
||||
weight?: number
|
||||
/** Content for the relationship (optional — overrides auto-computed vector) */
|
||||
|
|
@ -241,7 +258,10 @@ export interface RelateParams<T = any> {
|
|||
*/
|
||||
export interface UpdateRelationParams<T = any> {
|
||||
id: string // Relation to update
|
||||
type?: VerbType // Change verb type
|
||||
subtype?: string // Change sub-classification (omit to preserve existing)
|
||||
weight?: number // New weight
|
||||
confidence?: number // New confidence (0-1)
|
||||
data?: any // New content
|
||||
metadata?: Partial<T> // Metadata to update
|
||||
merge?: boolean // Merge or replace metadata
|
||||
|
|
@ -334,6 +354,13 @@ export interface GraphConstraints {
|
|||
from?: string // Connected from this entity
|
||||
via?: VerbType | VerbType[] // Via these relationship types
|
||||
type?: VerbType | VerbType[] // Alias for via
|
||||
/**
|
||||
* Filter traversal edges by VerbType subtype. Single string for equality,
|
||||
* array for set membership. Composes with `via` — `{ via: 'manages', subtype:
|
||||
* 'direct' }` traverses only direct-management edges, not dotted-line. Edges
|
||||
* without a subtype value are excluded when this filter is set.
|
||||
*/
|
||||
subtype?: string | string[]
|
||||
depth?: number // Max traversal depth (default: 1)
|
||||
direction?: 'in' | 'out' | 'both' // Direction of traversal (default: 'both')
|
||||
bidirectional?: boolean // Consider both directions
|
||||
|
|
@ -423,6 +450,15 @@ export interface GetRelationsParams {
|
|||
*/
|
||||
type?: VerbType | VerbType[]
|
||||
|
||||
/**
|
||||
* Filter by VerbType subtype
|
||||
*
|
||||
* Top-level standard field — uses the fast path, not the metadata fallback.
|
||||
* Pass a single string for equality (e.g. `subtype: 'direct'`), or an array
|
||||
* for set membership (e.g. `subtype: ['direct', 'dotted-line']`).
|
||||
*/
|
||||
subtype?: string | string[]
|
||||
|
||||
/**
|
||||
* Maximum number of results to return
|
||||
*
|
||||
|
|
@ -1099,6 +1135,26 @@ export interface BrainyConfig {
|
|||
// - true: Automatically run pending migrations during init() for small datasets (<10K entities)
|
||||
autoMigrate?: boolean
|
||||
|
||||
/**
|
||||
* Brain-wide subtype enforcement mode.
|
||||
*
|
||||
* Opt-in in 7.30.0 (default: `false`); becomes the default in 8.0.0.
|
||||
*
|
||||
* - `false` / `undefined` (default): no brain-wide check. Per-type rules
|
||||
* registered via `brain.requireSubtype(type, options)` still apply.
|
||||
* - `true`: every `add()` / `addMany()` / `update()` / `relate()` /
|
||||
* `relateMany()` / `updateRelation()` rejects writes where the entity's
|
||||
* NounType (or relationship's VerbType) has no non-empty `subtype` value.
|
||||
* - `{ except: [NounType.Thing, NounType.Custom] }`: same as `true`, but
|
||||
* the listed types are allowed through without a subtype. Use for genuine
|
||||
* catch-all types where no subtype makes sense.
|
||||
*
|
||||
* Per-type registrations always compose with the brain-wide flag — a type
|
||||
* registered with `requireSubtype(type, { required: true })` is always
|
||||
* enforced regardless of this flag.
|
||||
*/
|
||||
requireSubtype?: boolean | { except: Array<import('../types/graphTypes.js').NounType | import('../types/graphTypes.js').VerbType> }
|
||||
|
||||
/**
|
||||
* Process role for multi-process safety on filesystem storage.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue