feat: visibility tier (public/internal/system) on nouns + verbs

Adds a reserved, top-level `visibility` field (mirrors the subtype rollout):
'public' (default, surfaced) | 'internal' (a consumer's app-internal data — hidden
from default find()/getRelations()/counts/stats, opt-in via includeInternal) |
'system' (Brainy plumbing, library-set only; the add()/relate() param narrows to
'public' | 'internal').

Fixes a real leak: the VFS root entity counted in getNounCount() and appeared in
find() (a fresh brain reported 1 entity). It is now visibility:'system' → excluded
from every user-facing surface (fresh brain reports 0).

- Reserved (STANDARD_ENTITY_FIELDS / STANDARD_VERB_FIELDS) — surfaced top-level on
  reads, never in the custom metadata bag; a 'public'/'internal' value smuggled
  through metadata is lifted to the field, 'system' dropped with a one-shot warning.
- Threaded through add/relate/update/updateRelation + their internal mirrors;
  stored only when not 'public' so the common case stays lean.
- Default exclusion in counts (baseStorage, isCountedVisibility), find()/getRelations()
  (hard candidate filter via excludeVisibility — keeps top-K/limit/offset correct),
  and rebuildCounts; includeInternal/includeSystem opt-ins.
- VFS root marked 'system'.

Same on-disk shape as the 8.0 line, so it carries forward unchanged. Backward-compatible:
absent === 'public', so all existing data stays counted + returned.

Tests: tests/unit/brainy/visibility.test.ts 17/17 (fresh-brain getNounCount()===0,
internal hidden + opt-in, verb symmetry, top-level surfacing, metadata-spoof rejection).
1522 unit green; count-synchronization integration green.
This commit is contained in:
David Snelling 2026-06-19 16:40:35 -07:00
parent c53dd61f96
commit 3a62445465
8 changed files with 778 additions and 36 deletions

View file

@ -5,6 +5,7 @@
*/
import { Vector } from '../coreTypes.js'
import type { EntityVisibility } from '../coreTypes.js'
import { NounType, VerbType } from './graphTypes.js'
// ============= Core Types =============
@ -33,6 +34,13 @@ export interface Entity<T = any> {
* rolled into per-NounType statistics.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field). Absent === `'public'` (counted and
* returned everywhere). `'internal'` hides the entity from default `find()` / counts /
* `stats()` (opt back in with `find({ includeInternal: true })`); `'system'` is Brainy
* plumbing, hidden unless `find({ includeSystem: true })`. See {@link EntityVisibility}.
*/
visibility?: EntityVisibility
/** Opaque content — used for embeddings and semantic search. Not indexed by MetadataIndex. */
data?: any
/** User-defined structured fields — indexed and queryable via `where` filters. */
@ -84,6 +92,14 @@ export interface Relation<T = any> {
* the column-store directly.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field), the verb mirror of `Entity.visibility`.
* Absent === `'public'` (counted and returned everywhere). `'internal'` hides the edge
* from default `related()` / counts / `stats()` (opt back in with
* `related({ includeInternal: true })`); `'system'` is Brainy plumbing. See
* {@link EntityVisibility}.
*/
visibility?: EntityVisibility
/** Connection strength (0-1, default: 1.0) */
weight?: number
/** Opaque content for the relationship (overrides auto-computed vector if provided) */
@ -129,6 +145,7 @@ export interface Result<T = any> {
// Convenience: Common entity fields flattened to top level
type?: NounType // Entity type (from entity.type)
subtype?: string // Per-product sub-classification (from entity.subtype)
visibility?: EntityVisibility // Visibility tier (from entity.visibility); absent === 'public'
metadata?: T // Entity metadata (from entity.metadata)
data?: any // Entity data (from entity.data)
confidence?: number // Type classification confidence (from entity.confidence)
@ -191,6 +208,15 @@ export interface AddParams<T = any> {
* aggregation on the standard-field fast path.
*/
subtype?: string
/**
* Visibility tier (reserved top-level field). Omit (or pass `'public'`) for normal
* data that is counted and returned everywhere. Pass `'internal'` for app-internal
* data that should be hidden from default `find()` / counts / `stats()` but stay
* retrievable via `find({ includeInternal: true })`. The `'system'` tier is reserved
* for Brainy's own plumbing and is intentionally not accepted here. Stored only when
* not `'public'`.
*/
visibility?: 'public' | 'internal'
/** Structured queryable fields — indexed by MetadataIndex, used in `where` filters */
metadata?: T
/** Custom entity ID (auto-generated UUID v4 if not provided) */
@ -222,6 +248,13 @@ export interface UpdateParams<T = any> {
data?: any // New content to re-embed
type?: NounType // Change type
subtype?: string // Change subtype (set to '' or null-equivalent via dedicated unset is future work)
/**
* Change the visibility tier. Omit to preserve the existing value. Toggling between
* `'public'` and `'internal'` moves the entity in/out of the default-visible counts and
* `find()` results. The `'system'` tier is Brainy-internal (e.g. re-asserted on the VFS
* root during repair) consumer code should only ever use `'public'` / `'internal'`.
*/
visibility?: EntityVisibility
metadata?: Partial<T> // Metadata to update
merge?: boolean // Merge or replace metadata (default: true)
vector?: Vector // New pre-computed vector
@ -259,6 +292,15 @@ export interface RelateParams<T = any> {
* (`getRelations({ verb, subtype })`) and aggregation (`groupBy:['subtype']`).
*/
subtype?: string
/**
* Visibility tier (reserved top-level field), the verb mirror of `AddParams.visibility`.
* Omit (or pass `'public'`) for normal edges that are counted and returned everywhere.
* Pass `'internal'` for app-internal edges hidden from default `related()` / counts /
* `stats()` but retrievable via `related({ includeInternal: true })`. The `'system'`
* tier is reserved for Brainy's own plumbing and is not accepted here. Stored only when
* not `'public'`.
*/
visibility?: 'public' | 'internal'
/** Connection strength (0-1, default: 1.0) */
weight?: number
/** Content for the relationship (optional — overrides auto-computed vector) */
@ -282,6 +324,12 @@ export interface UpdateRelationParams<T = any> {
id: string // Relation to update
type?: VerbType // Change verb type
subtype?: string // Change sub-classification (omit to preserve existing)
/**
* Change the visibility tier. Omit to preserve the existing value. The verb mirror of
* `UpdateParams.visibility`; `'system'` is Brainy-internal consumer code should only
* use `'public'` / `'internal'`.
*/
visibility?: EntityVisibility
weight?: number // New weight
confidence?: number // New confidence (0-1)
data?: any // New content
@ -320,7 +368,24 @@ export interface FindParams<T = any> {
subtype?: string | string[]
/** Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`) */
where?: Partial<T>
// Visibility
/**
* Also return `visibility: 'internal'` entities. By default `find()` returns ONLY
* public entities (those with no `visibility` field, or `visibility: 'public'`);
* app-internal entities are hidden. Set `true` to include them. Applied as a hard
* candidate filter, so `limit` / `offset` stay correct. Does NOT include `'system'`
* entities use `includeSystem` for those.
*/
includeInternal?: boolean
/**
* Also return `visibility: 'system'` entities (Brainy's own plumbing, e.g. the VFS
* root). Hidden by default and even when `includeInternal` is set. Set `true` only
* when you specifically need to see system entities. Applied as a hard candidate
* filter, so `limit` / `offset` stay correct.
*/
includeSystem?: boolean
// Graph Intelligence
connected?: GraphConstraints
@ -481,6 +546,25 @@ export interface GetRelationsParams {
*/
subtype?: string | string[]
/**
* Also return `visibility: 'internal'` relationships.
*
* By default `related()` returns ONLY public relationships (those with no
* `visibility` field, or `visibility: 'public'`); app-internal edges are hidden.
* Set `true` to include them. Applied as a hard candidate filter so `limit` /
* `offset` stay correct. Does NOT include `'system'` edges use `includeSystem`.
*/
includeInternal?: boolean
/**
* Also return `visibility: 'system'` relationships (Brainy's own plumbing).
*
* Hidden by default and even when `includeInternal` is set. Set `true` only when
* you specifically need system edges. Applied as a hard candidate filter so
* `limit` / `offset` stay correct.
*/
includeSystem?: boolean
/**
* Maximum number of results to return
*