2025-09-12 14:37:39 -07:00
import { describe , it , expect , beforeEach } from 'vitest'
import {
validateFindParams ,
validateAddParams ,
validateUpdateParams ,
validateRelateParams ,
getValidationConfig ,
recordQueryPerformance
} from '../../../src/utils/paramValidation.js'
import { NounType , VerbType } from '../../../src/types/graphTypes.js'
import { FindParams , AddParams , UpdateParams , RelateParams } from '../../../src/types/brainy.types.js'
describe ( 'Zero-Config Parameter Validation' , ( ) = > {
describe ( 'validateFindParams' , ( ) = > {
it ( 'should accept valid parameters' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
query : 'test query' ,
limit : 10 ,
offset : 0
} ) ) . not . toThrow ( )
expect ( ( ) = > validateFindParams ( {
type : NounType . Document ,
where : { status : 'active' }
} ) ) . not . toThrow ( )
} )
it ( 'should reject negative limit' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
limit : - 1
} ) ) . toThrow ( 'limit must be non-negative' )
} )
it ( 'should reject negative offset' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
offset : - 1
} ) ) . toThrow ( 'offset must be non-negative' )
} )
it ( 'should reject threshold outside 0-1 range' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
near : { id : 'test' , threshold : - 0.1 }
} ) ) . toThrow ( 'threshold must be between 0 and 1' )
expect ( ( ) = > validateFindParams ( {
near : { id : 'test' , threshold : 1.1 }
} ) ) . toThrow ( 'threshold must be between 0 and 1' )
} )
it ( 'should reject both query and vector' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
query : 'test' ,
vector : new Array ( 384 ) . fill ( 0 )
} ) ) . toThrow ( 'cannot specify both query and vector' )
} )
feat(namespace): NO SPECIAL NAMES + storage fidelity — the ruled completion of the field-addressing law
The write side of the law, ruled 2026-08-03: data is either in main space
where developers can use anything, or it is in system.*.
- The reserved-name write door DIES: add/update/relate/updateRelation
metadata bags accept EVERY name (confidence, type, id, data, level,
content, ...) as ordinary user fields — indexed, filterable, sortable,
aggregatable, identical to any other field. The remap/enforce/warn
machinery, the reservedFieldPolicy config (now a typed init refusal),
and the compile-time metadata key bans are all removed. The one write
refusal left: keys spelled 'system.*' (namespace forgery), now enforced
on all four write doors.
- STORED RECORDS GO NESTED (v2): engine fields top-level, the user bag
nested verbatim under 'metadata', sealed by a format stamp — by-name
storage discrimination is unsound once colliders are admitted. Legacy
flat records stay readable forever through the shape-aware splitters
(sound for them: the old door refused colliders). Time travel rides the
same split (generation store snapshots whole records).
- Name-based index exclusions DIE: user frame indexes every name; the
excludeFields/indexedFields knobs and their silent-[] holes are gone;
bulk-payload protection is value-shape only, uniform across names.
- Consumer-sweep findings fixed in the same wave: per-type counts read
the frozen 'system.type' column (addToIndex sort, affinity tracking,
cold-count rehydration, VFS type bitmaps — legacy 'noun' fallback for
pre-rebuild reads); resolveHiddenIds addresses 'system.visibility'
(bare 'visibility' was a silent no-op under the law — VFS/system
entities leaked into default reads).
- Fidelity fallout fixed in the owning layers: readEntityFieldAddress
reads the bag first (colliders were absent-shadowed by its own guard)
and never serves system addresses from the bag; blob history refs read
the bag shape-aware; migration transforms now receive ONE normalized
view (engine fields + nested bag) regardless of stored era, and stray
flat-habit keys refuse with the fix in the message.
- THE REOPEN-COLLIDER CONFORMANCE CASE (required before any RC counts as
gates-green): all ten collider names + plumbing names written as user
fields, verified verbatim + queryable across live reads, flush+reopen,
a forced epoch rebuild, and asOf time travel; relation mirror; forgery
refusals; legacy flat-record compat. 8/8 green.
Gates: unit 1901/1901 (exit 0) · integration 758 (exit 0) · conformance
27/27 (exit 0) · consumer test sweep migrated (10 files).
2026-08-03 16:59:13 -07:00
it ( 'should refuse cursor outright — even paired with offset — as an unimplemented option' , ( ) = > {
// cursor is now a typed, unconditional refusal (UnsupportedFindOptionError):
// it used to be accepted-and-ignored, only conflicting when offset was also
// given. Accepted-and-ignored died as a class — cursor refuses on its own,
// so pairing it with offset refuses too, but with the SAME message.
2025-09-12 14:37:39 -07:00
expect ( ( ) = > validateFindParams ( {
cursor : 'abc123' ,
offset : 10
feat(namespace): NO SPECIAL NAMES + storage fidelity — the ruled completion of the field-addressing law
The write side of the law, ruled 2026-08-03: data is either in main space
where developers can use anything, or it is in system.*.
- The reserved-name write door DIES: add/update/relate/updateRelation
metadata bags accept EVERY name (confidence, type, id, data, level,
content, ...) as ordinary user fields — indexed, filterable, sortable,
aggregatable, identical to any other field. The remap/enforce/warn
machinery, the reservedFieldPolicy config (now a typed init refusal),
and the compile-time metadata key bans are all removed. The one write
refusal left: keys spelled 'system.*' (namespace forgery), now enforced
on all four write doors.
- STORED RECORDS GO NESTED (v2): engine fields top-level, the user bag
nested verbatim under 'metadata', sealed by a format stamp — by-name
storage discrimination is unsound once colliders are admitted. Legacy
flat records stay readable forever through the shape-aware splitters
(sound for them: the old door refused colliders). Time travel rides the
same split (generation store snapshots whole records).
- Name-based index exclusions DIE: user frame indexes every name; the
excludeFields/indexedFields knobs and their silent-[] holes are gone;
bulk-payload protection is value-shape only, uniform across names.
- Consumer-sweep findings fixed in the same wave: per-type counts read
the frozen 'system.type' column (addToIndex sort, affinity tracking,
cold-count rehydration, VFS type bitmaps — legacy 'noun' fallback for
pre-rebuild reads); resolveHiddenIds addresses 'system.visibility'
(bare 'visibility' was a silent no-op under the law — VFS/system
entities leaked into default reads).
- Fidelity fallout fixed in the owning layers: readEntityFieldAddress
reads the bag first (colliders were absent-shadowed by its own guard)
and never serves system addresses from the bag; blob history refs read
the bag shape-aware; migration transforms now receive ONE normalized
view (engine fields + nested bag) regardless of stored era, and stray
flat-habit keys refuse with the fix in the message.
- THE REOPEN-COLLIDER CONFORMANCE CASE (required before any RC counts as
gates-green): all ten collider names + plumbing names written as user
fields, verified verbatim + queryable across live reads, flush+reopen,
a forced epoch rebuild, and asOf time travel; relation mirror; forgery
refusals; legacy flat-record compat. 8/8 green.
Gates: unit 1901/1901 (exit 0) · integration 758 (exit 0) · conformance
27/27 (exit 0) · consumer test sweep migrated (10 files).
2026-08-03 16:59:13 -07:00
} ) ) . toThrow ( "find() option 'cursor' is not implemented" )
2025-09-12 14:37:39 -07:00
} )
it ( 'should validate vector dimensions' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
vector : new Array ( 100 ) . fill ( 0 ) // Wrong dimensions
} ) ) . toThrow ( 'vector must have exactly 384 dimensions' )
expect ( ( ) = > validateFindParams ( {
vector : new Array ( 384 ) . fill ( 0 ) // Correct dimensions
} ) ) . not . toThrow ( )
} )
it ( 'should validate NounType enum' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
type : 'InvalidType' as any
} ) ) . toThrow ( 'invalid NounType: InvalidType' )
expect ( ( ) = > validateFindParams ( {
type : NounType . Document
} ) ) . not . toThrow ( )
} )
it ( 'should validate array of NounTypes' , ( ) = > {
expect ( ( ) = > validateFindParams ( {
type : [ NounType . Document , NounType . Person ]
} ) ) . not . toThrow ( )
expect ( ( ) = > validateFindParams ( {
type : [ NounType . Document , 'InvalidType' as any ]
} ) ) . toThrow ( 'invalid NounType: InvalidType' )
} )
fix: recalibrate find({ limit }) cap + two-tier enforcement + caller location
Brainy 7.30.0 introduced a memory-derived synchronous cap on `find({ limit })`
to prevent OOM. The cap was sound in intent but ~4x too conservative in
calibration: assumed 100 KB per result while typical entity footprint is 7-10 KB
(384-dim float32 vector ≈ 1.5 KB + standard fields + metadata). On a 900 MB
free-memory box the cap derived to 9000 — breaking common safety-cap patterns
like `find({ type, where, limit: 10_000 })` that typically return 10-500
entities. Surfaced as a runtime regression with cascading 500s degrading
production dashboards.
Three concurrent fixes:
A. RECALIBRATE THE FORMULA
- src/utils/paramValidation.ts:175,196,212 — the three memory-derived priorities
(reservedQueryMemory / containerMemory / freeMemory) all divided by
100 * 1024 * 1024 (100 KB per result, ~10-15x over conservative). Replaced
with a new MAX_LIMIT_KB_PER_RESULT = 25 constant that matches observed
entity size.
- Result: 4 GB container cap goes 10_000 → 40_000; 2 GB cap goes 5_000 →
20_000; 900 MB free-memory cap goes 9_000 → ~36_000. 100k hard ceiling
unchanged. `maxQueryLimit` / `reservedQueryMemory` constructor overrides
unchanged in behavior.
B. TWO-TIER ENFORCEMENT (warn-then-throw)
- Below cap (limit <= maxLimit): silent pass, unchanged.
- Soft tier (maxLimit < limit <= 2 * maxLimit): NEW — one-time warning per
call site (dedup keyed on caller stack frame + limit value), query
proceeds. Pre-7.30.2 code that relied on the cap silently allowing typical
safety-cap limits keeps working; the warning teaches the recipe so consumers
can fix it intentionally.
- Hard tier (limit > 2 * maxLimit): throw with the same teaching message
format. Real OOM territory; the cap stops being a recommendation and becomes
a guardrail.
- The 2x soft margin absorbs typical safety-cap patterns (limit: 10_000
against a 9 K-cap box) without disabling OOM protection. Real OOM territory
on a JS in-memory brain is hundreds of thousands of results, not 10x the
safety cap.
C. IMPROVED ERROR / WARNING MESSAGE
- Same shape as the 7.30.1 enforcement-error messages: state the problem,
name the three escape valves (maxQueryLimit / reservedQueryMemory /
pagination), include caller location, link to docs.
- Extracted findCallerLocation() helper from brainy.ts to a new
src/utils/callerLocation.ts so both the subtype enforcement (7.30.1) and
the limit enforcement (7.30.2) share one implementation without circular
imports.
DOCS
- New docs/guides/find-limits.md (public: true) — full reference: why the cap
exists, the four memory sources the auto-config considers, the three escape
valves with when-to-use-which guidance, and an explicit "pagination is the
future-proof pattern" callout (8.0 may tighten the cap further; pagination
keeps working unchanged).
- docs/api/README.md find() entry gets a one-paragraph `limit` tip + pointer
to the new guide.
- RELEASES.md v7.30.2 entry.
TESTS
- New tests/integration/find-limits.test.ts (9 tests): below-cap silent pass;
soft-tier warns once per call site (dedup verified by exercising same vs.
different source lines via wrapper closures); soft-tier message format
(names all three escape valves + docs link); soft-tier message includes
caller location; hard-tier throws; hard-tier message format same as
soft-tier; consumer maxQueryLimit override raises the cap and shifts both
tiers accordingly; pre-7.30.2 regression scenario explicitly covered.
- tests/unit/utils/memoryLimits.test.ts — 4 tests updated for the recalibrated
cap values (hardcoded expected numbers bumped 4x to match new 25 KB/result
assumption).
- tests/unit/utils/paramValidation.test.ts — auto-limit test extended to cover
the three-tier semantics (below-cap pass / soft-tier silent / hard-tier
throw).
- Existing suites unchanged: subtype-and-facets 26/26, verb-subtype-and-
enforcement 30/30, strict-mode-self-test 13/13. Unit 1468/1468.
CORTEX COMPATIBILITY
- Zero Cortex changes required. Every change is JS-side: formula recalibration
runs in ValidationConfig.constructor(), two-tier enforcement runs in
validateFindParams(), both fire before any storage / index / Cortex call.
- The new guide notes that Brainy 8.0's Datomic-style Db.find() may tighten
per-call limits to keep snapshot semantics cheap; pagination remains the
pattern that's guaranteed to keep working.
REPO-WIDE CLEANUP
Brainy is the only Soulcraft project that is open source. This commit also
scrubs closed-source product names and product-specific class/field references
from every tracked file in the repo (src/, docs/, tests/, RELEASES.md,
CHANGELOG.md). Consumer-reported bugs, regression scenarios, and release
notes now refer to "a consumer", "a downstream application", "a production
deployment", or "an internal report" — never to the named product. Two
product-named test files renamed to neutral diagnostic names. CLAUDE.md gains
a project-level guard rule documenting the policy and an example list of the
identifiers that may not appear in tracked code.
Verification
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit
- All four integration subtype + verb + strict + find-limits suites: 78/78
- npm run build: clean
- Closed-source product reference audit: clean
2026-06-08 12:34:05 -07:00
it ( 'should auto-limit based on system memory (two-tier enforcement)' , ( ) = > {
2025-09-12 14:37:39 -07:00
const config = getValidationConfig ( )
fix: recalibrate find({ limit }) cap + two-tier enforcement + caller location
Brainy 7.30.0 introduced a memory-derived synchronous cap on `find({ limit })`
to prevent OOM. The cap was sound in intent but ~4x too conservative in
calibration: assumed 100 KB per result while typical entity footprint is 7-10 KB
(384-dim float32 vector ≈ 1.5 KB + standard fields + metadata). On a 900 MB
free-memory box the cap derived to 9000 — breaking common safety-cap patterns
like `find({ type, where, limit: 10_000 })` that typically return 10-500
entities. Surfaced as a runtime regression with cascading 500s degrading
production dashboards.
Three concurrent fixes:
A. RECALIBRATE THE FORMULA
- src/utils/paramValidation.ts:175,196,212 — the three memory-derived priorities
(reservedQueryMemory / containerMemory / freeMemory) all divided by
100 * 1024 * 1024 (100 KB per result, ~10-15x over conservative). Replaced
with a new MAX_LIMIT_KB_PER_RESULT = 25 constant that matches observed
entity size.
- Result: 4 GB container cap goes 10_000 → 40_000; 2 GB cap goes 5_000 →
20_000; 900 MB free-memory cap goes 9_000 → ~36_000. 100k hard ceiling
unchanged. `maxQueryLimit` / `reservedQueryMemory` constructor overrides
unchanged in behavior.
B. TWO-TIER ENFORCEMENT (warn-then-throw)
- Below cap (limit <= maxLimit): silent pass, unchanged.
- Soft tier (maxLimit < limit <= 2 * maxLimit): NEW — one-time warning per
call site (dedup keyed on caller stack frame + limit value), query
proceeds. Pre-7.30.2 code that relied on the cap silently allowing typical
safety-cap limits keeps working; the warning teaches the recipe so consumers
can fix it intentionally.
- Hard tier (limit > 2 * maxLimit): throw with the same teaching message
format. Real OOM territory; the cap stops being a recommendation and becomes
a guardrail.
- The 2x soft margin absorbs typical safety-cap patterns (limit: 10_000
against a 9 K-cap box) without disabling OOM protection. Real OOM territory
on a JS in-memory brain is hundreds of thousands of results, not 10x the
safety cap.
C. IMPROVED ERROR / WARNING MESSAGE
- Same shape as the 7.30.1 enforcement-error messages: state the problem,
name the three escape valves (maxQueryLimit / reservedQueryMemory /
pagination), include caller location, link to docs.
- Extracted findCallerLocation() helper from brainy.ts to a new
src/utils/callerLocation.ts so both the subtype enforcement (7.30.1) and
the limit enforcement (7.30.2) share one implementation without circular
imports.
DOCS
- New docs/guides/find-limits.md (public: true) — full reference: why the cap
exists, the four memory sources the auto-config considers, the three escape
valves with when-to-use-which guidance, and an explicit "pagination is the
future-proof pattern" callout (8.0 may tighten the cap further; pagination
keeps working unchanged).
- docs/api/README.md find() entry gets a one-paragraph `limit` tip + pointer
to the new guide.
- RELEASES.md v7.30.2 entry.
TESTS
- New tests/integration/find-limits.test.ts (9 tests): below-cap silent pass;
soft-tier warns once per call site (dedup verified by exercising same vs.
different source lines via wrapper closures); soft-tier message format
(names all three escape valves + docs link); soft-tier message includes
caller location; hard-tier throws; hard-tier message format same as
soft-tier; consumer maxQueryLimit override raises the cap and shifts both
tiers accordingly; pre-7.30.2 regression scenario explicitly covered.
- tests/unit/utils/memoryLimits.test.ts — 4 tests updated for the recalibrated
cap values (hardcoded expected numbers bumped 4x to match new 25 KB/result
assumption).
- tests/unit/utils/paramValidation.test.ts — auto-limit test extended to cover
the three-tier semantics (below-cap pass / soft-tier silent / hard-tier
throw).
- Existing suites unchanged: subtype-and-facets 26/26, verb-subtype-and-
enforcement 30/30, strict-mode-self-test 13/13. Unit 1468/1468.
CORTEX COMPATIBILITY
- Zero Cortex changes required. Every change is JS-side: formula recalibration
runs in ValidationConfig.constructor(), two-tier enforcement runs in
validateFindParams(), both fire before any storage / index / Cortex call.
- The new guide notes that Brainy 8.0's Datomic-style Db.find() may tighten
per-call limits to keep snapshot semantics cheap; pagination remains the
pattern that's guaranteed to keep working.
REPO-WIDE CLEANUP
Brainy is the only Soulcraft project that is open source. This commit also
scrubs closed-source product names and product-specific class/field references
from every tracked file in the repo (src/, docs/, tests/, RELEASES.md,
CHANGELOG.md). Consumer-reported bugs, regression scenarios, and release
notes now refer to "a consumer", "a downstream application", "a production
deployment", or "an internal report" — never to the named product. Two
product-named test files renamed to neutral diagnostic names. CLAUDE.md gains
a project-level guard rule documenting the policy and an example list of the
identifiers that may not appear in tracked code.
Verification
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit
- All four integration subtype + verb + strict + find-limits suites: 78/78
- npm run build: clean
- Closed-source product reference audit: clean
2026-06-08 12:34:05 -07:00
// 7.30.2+ design: the cap fires in two tiers.
// - Below cap (`limit <= maxLimit`): silent pass, no signal.
// - Soft tier (`maxLimit < limit <= 2 * maxLimit`): one-time warning
// per call site, query proceeds. No throw.
// - Hard tier (`limit > 2 * maxLimit`): real OOM danger zone, throw.
// Below cap → pass
expect ( ( ) = > validateFindParams ( { limit : config.maxLimit } ) ) . not . toThrow ( )
// Soft tier → no throw (just a warn we don't assert here — proper coverage
// lives in the find-limits integration suite which can intercept the log).
expect ( ( ) = > validateFindParams ( { limit : config.maxLimit + 1 } ) ) . not . toThrow ( )
expect ( ( ) = > validateFindParams ( { limit : config.maxLimit * 2 } ) ) . not . toThrow ( )
// Hard tier → throw with the new message format
2025-09-12 14:37:39 -07:00
expect ( ( ) = > validateFindParams ( {
fix: recalibrate find({ limit }) cap + two-tier enforcement + caller location
Brainy 7.30.0 introduced a memory-derived synchronous cap on `find({ limit })`
to prevent OOM. The cap was sound in intent but ~4x too conservative in
calibration: assumed 100 KB per result while typical entity footprint is 7-10 KB
(384-dim float32 vector ≈ 1.5 KB + standard fields + metadata). On a 900 MB
free-memory box the cap derived to 9000 — breaking common safety-cap patterns
like `find({ type, where, limit: 10_000 })` that typically return 10-500
entities. Surfaced as a runtime regression with cascading 500s degrading
production dashboards.
Three concurrent fixes:
A. RECALIBRATE THE FORMULA
- src/utils/paramValidation.ts:175,196,212 — the three memory-derived priorities
(reservedQueryMemory / containerMemory / freeMemory) all divided by
100 * 1024 * 1024 (100 KB per result, ~10-15x over conservative). Replaced
with a new MAX_LIMIT_KB_PER_RESULT = 25 constant that matches observed
entity size.
- Result: 4 GB container cap goes 10_000 → 40_000; 2 GB cap goes 5_000 →
20_000; 900 MB free-memory cap goes 9_000 → ~36_000. 100k hard ceiling
unchanged. `maxQueryLimit` / `reservedQueryMemory` constructor overrides
unchanged in behavior.
B. TWO-TIER ENFORCEMENT (warn-then-throw)
- Below cap (limit <= maxLimit): silent pass, unchanged.
- Soft tier (maxLimit < limit <= 2 * maxLimit): NEW — one-time warning per
call site (dedup keyed on caller stack frame + limit value), query
proceeds. Pre-7.30.2 code that relied on the cap silently allowing typical
safety-cap limits keeps working; the warning teaches the recipe so consumers
can fix it intentionally.
- Hard tier (limit > 2 * maxLimit): throw with the same teaching message
format. Real OOM territory; the cap stops being a recommendation and becomes
a guardrail.
- The 2x soft margin absorbs typical safety-cap patterns (limit: 10_000
against a 9 K-cap box) without disabling OOM protection. Real OOM territory
on a JS in-memory brain is hundreds of thousands of results, not 10x the
safety cap.
C. IMPROVED ERROR / WARNING MESSAGE
- Same shape as the 7.30.1 enforcement-error messages: state the problem,
name the three escape valves (maxQueryLimit / reservedQueryMemory /
pagination), include caller location, link to docs.
- Extracted findCallerLocation() helper from brainy.ts to a new
src/utils/callerLocation.ts so both the subtype enforcement (7.30.1) and
the limit enforcement (7.30.2) share one implementation without circular
imports.
DOCS
- New docs/guides/find-limits.md (public: true) — full reference: why the cap
exists, the four memory sources the auto-config considers, the three escape
valves with when-to-use-which guidance, and an explicit "pagination is the
future-proof pattern" callout (8.0 may tighten the cap further; pagination
keeps working unchanged).
- docs/api/README.md find() entry gets a one-paragraph `limit` tip + pointer
to the new guide.
- RELEASES.md v7.30.2 entry.
TESTS
- New tests/integration/find-limits.test.ts (9 tests): below-cap silent pass;
soft-tier warns once per call site (dedup verified by exercising same vs.
different source lines via wrapper closures); soft-tier message format
(names all three escape valves + docs link); soft-tier message includes
caller location; hard-tier throws; hard-tier message format same as
soft-tier; consumer maxQueryLimit override raises the cap and shifts both
tiers accordingly; pre-7.30.2 regression scenario explicitly covered.
- tests/unit/utils/memoryLimits.test.ts — 4 tests updated for the recalibrated
cap values (hardcoded expected numbers bumped 4x to match new 25 KB/result
assumption).
- tests/unit/utils/paramValidation.test.ts — auto-limit test extended to cover
the three-tier semantics (below-cap pass / soft-tier silent / hard-tier
throw).
- Existing suites unchanged: subtype-and-facets 26/26, verb-subtype-and-
enforcement 30/30, strict-mode-self-test 13/13. Unit 1468/1468.
CORTEX COMPATIBILITY
- Zero Cortex changes required. Every change is JS-side: formula recalibration
runs in ValidationConfig.constructor(), two-tier enforcement runs in
validateFindParams(), both fire before any storage / index / Cortex call.
- The new guide notes that Brainy 8.0's Datomic-style Db.find() may tighten
per-call limits to keep snapshot semantics cheap; pagination remains the
pattern that's guaranteed to keep working.
REPO-WIDE CLEANUP
Brainy is the only Soulcraft project that is open source. This commit also
scrubs closed-source product names and product-specific class/field references
from every tracked file in the repo (src/, docs/, tests/, RELEASES.md,
CHANGELOG.md). Consumer-reported bugs, regression scenarios, and release
notes now refer to "a consumer", "a downstream application", "a production
deployment", or "an internal report" — never to the named product. Two
product-named test files renamed to neutral diagnostic names. CLAUDE.md gains
a project-level guard rule documenting the policy and an example list of the
identifiers that may not appear in tracked code.
Verification
- npx tsc --noEmit: clean
- npm test: 1468 / 1468 unit
- All four integration subtype + verb + strict + find-limits suites: 78/78
- npm run build: clean
- Closed-source product reference audit: clean
2026-06-08 12:34:05 -07:00
limit : config.maxLimit * 2 + 1
} ) ) . toThrow ( /exceeds the auto-configured query limit/ )
2025-09-12 14:37:39 -07:00
} )
it ( 'should auto-limit query length' , ( ) = > {
const config = getValidationConfig ( )
const longQuery = 'a' . repeat ( config . maxQueryLength + 1 )
expect ( ( ) = > validateFindParams ( {
query : longQuery
} ) ) . toThrow ( ` query exceeds auto-configured maximum length of ${ config . maxQueryLength } ` )
} )
} )
describe ( 'validateAddParams' , ( ) = > {
it ( 'should accept valid add parameters' , ( ) = > {
expect ( ( ) = > validateAddParams ( {
data : 'test content' ,
type : NounType . Document
} ) ) . not . toThrow ( )
expect ( ( ) = > validateAddParams ( {
vector : new Array ( 384 ) . fill ( 0 ) ,
type : NounType . Person
} ) ) . not . toThrow ( )
} )
it ( 'should require either data or vector' , ( ) = > {
expect ( ( ) = > validateAddParams ( {
type : NounType . Document
2025-09-22 15:45:35 -07:00
} as AddParams ) ) . toThrow ( 'Invalid add() parameters: Missing required field \'data\'' )
2025-09-12 14:37:39 -07:00
} )
it ( 'should validate NounType' , ( ) = > {
expect ( ( ) = > validateAddParams ( {
data : 'test' ,
type : 'InvalidType' as any
2025-09-22 15:45:35 -07:00
} ) ) . toThrow ( 'Invalid NounType: \'InvalidType\'' )
2025-09-12 14:37:39 -07:00
} )
it ( 'should validate vector dimensions' , ( ) = > {
expect ( ( ) = > validateAddParams ( {
vector : new Array ( 100 ) . fill ( 0 ) ,
type : NounType . Document
} ) ) . toThrow ( 'vector must have exactly 384 dimensions' )
} )
} )
describe ( 'validateUpdateParams' , ( ) = > {
it ( 'should accept valid update parameters' , ( ) = > {
expect ( ( ) = > validateUpdateParams ( {
id : 'test-id' ,
data : 'new content'
} ) ) . not . toThrow ( )
expect ( ( ) = > validateUpdateParams ( {
id : 'test-id' ,
metadata : { status : 'updated' }
} ) ) . not . toThrow ( )
} )
it ( 'should require an ID' , ( ) = > {
expect ( ( ) = > validateUpdateParams ( {
data : 'new content'
} as UpdateParams ) ) . toThrow ( 'id is required for update' )
} )
it ( 'should require at least one field to update' , ( ) = > {
expect ( ( ) = > validateUpdateParams ( {
id : 'test-id'
} ) ) . toThrow ( 'must specify at least one field to update' )
} )
it ( 'should validate NounType if changing' , ( ) = > {
expect ( ( ) = > validateUpdateParams ( {
id : 'test-id' ,
type : 'InvalidType' as any
} ) ) . toThrow ( 'invalid NounType: InvalidType' )
expect ( ( ) = > validateUpdateParams ( {
id : 'test-id' ,
type : NounType . Event
} ) ) . not . toThrow ( )
} )
} )
describe ( 'validateRelateParams' , ( ) = > {
it ( 'should accept valid relate parameters' , ( ) = > {
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
to : 'entity2' ,
type : VerbType . RelatedTo
} ) ) . not . toThrow ( )
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
to : 'entity2' ,
type : VerbType . Creates ,
weight : 0.8
} ) ) . not . toThrow ( )
} )
it ( 'should require from and to' , ( ) = > {
expect ( ( ) = > validateRelateParams ( {
to : 'entity2' ,
type : VerbType . RelatedTo
} as RelateParams ) ) . toThrow ( 'from entity ID is required' )
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
type : VerbType . RelatedTo
} as RelateParams ) ) . toThrow ( 'to entity ID is required' )
} )
2025-10-09 16:33:08 -07:00
// Self-referential relationships are now allowed (valid in graph systems)
// Previous test "should reject self-referential relationships" removed
2025-09-12 14:37:39 -07:00
it ( 'should validate VerbType' , ( ) = > {
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
to : 'entity2' ,
type : 'InvalidVerb' as any
} ) ) . toThrow ( 'invalid VerbType: InvalidVerb' )
} )
it ( 'should validate weight range' , ( ) = > {
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
to : 'entity2' ,
type : VerbType . RelatedTo ,
weight : - 0.1
} ) ) . toThrow ( 'weight must be between 0 and 1' )
expect ( ( ) = > validateRelateParams ( {
from : 'entity1' ,
to : 'entity2' ,
type : VerbType . RelatedTo ,
weight : 1.1
} ) ) . toThrow ( 'weight must be between 0 and 1' )
} )
} )
describe ( 'Auto-configuration' , ( ) = > {
it ( 'should provide configuration based on system resources' , ( ) = > {
const config = getValidationConfig ( )
expect ( config . maxLimit ) . toBeGreaterThan ( 0 )
expect ( config . maxLimit ) . toBeLessThanOrEqual ( 100000 )
expect ( config . maxQueryLength ) . toBeGreaterThan ( 0 )
expect ( config . maxVectorDimensions ) . toBe ( 384 )
expect ( config . systemMemory ) . toBeGreaterThan ( 0 )
expect ( config . availableMemory ) . toBeGreaterThan ( 0 )
} )
2026-07-17 09:20:09 -07:00
it ( 'never mutates the cap from query timing (telemetry only)' , ( ) = > {
const initialLimit = getValidationConfig ( ) . maxLimit
// Fast queries with large results: no silent growth.
2025-09-12 14:37:39 -07:00
for ( let i = 0 ; i < 10 ; i ++ ) {
recordQueryPerformance ( 50 , initialLimit * 0.9 )
}
2026-07-17 09:20:09 -07:00
expect ( getValidationConfig ( ) . maxLimit ) . toBe ( initialLimit )
// A burst of catastrophically slow queries must not strangle the cap.
// The removed "learning" ratchet shrank it 20% per recorded query down
// to a floor of 1000 — below the documented MIN_AUTO_QUERY_LIMIT — and
// the error message blamed "available free memory" (a production
// incident: every find({ limit: 5000 }) failed on an idle 23GB-free box).
for ( let i = 0 ; i < 50 ; i ++ ) {
recordQueryPerformance ( 90 _000 , 100 )
2025-09-12 14:37:39 -07:00
}
2026-07-17 09:20:09 -07:00
expect ( getValidationConfig ( ) . maxLimit ) . toBe ( initialLimit )
2025-09-12 14:37:39 -07:00
} )
} )
} )