diff --git a/src/errors/brainyError.ts b/src/errors/brainyError.ts index 4301d3f7..2fbdbe8d 100644 --- a/src/errors/brainyError.ts +++ b/src/errors/brainyError.ts @@ -412,18 +412,23 @@ export class MigrationInProgressError extends BrainyError { * embedding parked in the metadata bag would mint 384 postings for one row. * The bound exists to keep that out of the index. * - * 64 is hardcoded on purpose (the zero-config law: no knob). It sits far above + * 256 is hardcoded on purpose (the zero-config law: no knob). It sits far above * every legitimate multi-value field the engine has seen — tags, authors, - * categories, labels, participant lists — and far below any real embedding - * width, so the two populations do not overlap and no caller has to tune it. + * categories, labels, keyword lists, participant lists — and still below the + * narrowest embedding this engine will ever meet (384 dimensions, the smallest + * model it ships), so the two populations do not overlap and no caller has to + * tune it. A vector parked in metadata is refused; a long keyword list is not. * * It replaces a limit of 10 that was applied SILENTLY: a row whose `tags` array * held eleven entries had that field skipped entirely and dropped out of every * filtered search on it, with no error, no warning and no way to tell the * difference from "no row matches". A rule this consequential is a law with a * name and a refusal, not a `continue`. + * + * This is the ONE place the number lives. Every message, warning, doc line and + * pin derives it from here — never a literal. */ -export const MAX_INDEXED_ARRAY_LENGTH = 64 +export const MAX_INDEXED_ARRAY_LENGTH = 256 /** * A metadata field carries an array longer than {@link MAX_INDEXED_ARRAY_LENGTH}. diff --git a/tests/integration/metadata-vector-exclusion.test.ts b/tests/integration/metadata-vector-exclusion.test.ts index 0ca25388..1943b215 100644 --- a/tests/integration/metadata-vector-exclusion.test.ts +++ b/tests/integration/metadata-vector-exclusion.test.ts @@ -161,7 +161,8 @@ describe('Metadata Vector Exclusion Fix', () => { // silence at a bound of 10 — the field simply vanished from the index and // the row dropped out of every `where` on it, indistinguishably from "no // row matches". The bound is now MAX_INDEXED_ARRAY_LENGTH and it REFUSES. - const largeArray = Array.from({ length: 100 }, (_, i) => `item${i}`) + const overTheBound = MAX_INDEXED_ARRAY_LENGTH + 1 + const largeArray = Array.from({ length: overTheBound }, (_, i) => `item${i}`) const err = await brainy .add({ @@ -176,7 +177,7 @@ describe('Metadata Vector Exclusion Fix', () => { expect(err).toBeInstanceOf(MetadataArrayTooLargeError) expect(err.field).toBe('items') - expect(err.length).toBe(100) + expect(err.length).toBe(overTheBound) expect(err.limit).toBe(MAX_INDEXED_ARRAY_LENGTH) // Nothing was indexed from the refused write — no 'items' field, and above diff --git a/tests/unit/utils/metadataIndex-array-bound.test.ts b/tests/unit/utils/metadataIndex-array-bound.test.ts index cbbf6b63..a96ae1d6 100644 --- a/tests/unit/utils/metadataIndex-array-bound.test.ts +++ b/tests/unit/utils/metadataIndex-array-bound.test.ts @@ -15,9 +15,10 @@ * and the caller had no way to tell that from "no row matches". Eleven tags is * not an exotic shape; the eleventh tag made the row invisible. * - * THE LAW. Arrays of scalars index up to {@link MAX_INDEXED_ARRAY_LENGTH} = 64, + * THE LAW. Arrays of scalars index up to {@link MAX_INDEXED_ARRAY_LENGTH}, * hardcoded (the zero-config law: no knob), which clears every legitimate - * multi-value field and stays far below any embedding width. Above it the WRITE + * multi-value field — tags, authors, keyword lists — and stays below the + * narrowest embedding this engine meets (384 dimensions). Above it the WRITE * IS REFUSED by name — `MetadataArrayTooLargeError`, carrying the field, the * length and the bound — at `add`, `update`, `relate` and `updateRelation` * alike. Nothing is skipped in silence. @@ -65,7 +66,7 @@ describe('the indexable-array bound', () => { } }) - it('indexes right up to the bound — all 64 elements', async () => { + it('indexes right up to the bound — every element of it', async () => { await brain.add({ id: 'at-bound', data: 'a row at the bound', @@ -74,8 +75,9 @@ describe('the indexable-array bound', () => { vector: [] }) - // The first, the last, and one in the middle. - for (const tag of ['t0', `t${MAX_INDEXED_ARRAY_LENGTH - 1}`, 't31']) { + // The first, the last, and one in the middle — all derived from the + // bound, so the case follows the constant wherever it moves. + for (const tag of ['t0', `t${MAX_INDEXED_ARRAY_LENGTH - 1}`, `t${Math.floor(MAX_INDEXED_ARRAY_LENGTH / 2)}`]) { const hits = await brain.find({ where: { tags: tag }, limit: 10 } as any) expect(hits.map((r: any) => r.id)).toContain(resolveEntityId('at-bound')) }