From 48a6130a50251be464dda201ee85d40758efb63e Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 3 Aug 2026 16:07:27 -0700 Subject: [PATCH] =?UTF-8?q?feat(namespace):=20write-door=20forgery=20refus?= =?UTF-8?q?al=20(user=20metadata=20keys=20may=20never=20start=20'system.')?= =?UTF-8?q?=20+=20refusal=20messages=20name=20both=20spellings=20in=20ever?= =?UTF-8?q?y=20branch=20(the=20non-colliding=20case=20marks=20system.?= =?UTF-8?q?=20honestly=20as=20NOT=20valid)=20=E2=80=94=20cross-engine=20me?= =?UTF-8?q?ssage=20pin=20alignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/fieldAddressing.ts | 3 ++- src/utils/paramValidation.ts | 22 ++++++++++++++++++++++ tests/unit/db/fieldAddressing.test.ts | 8 ++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/db/fieldAddressing.ts b/src/db/fieldAddressing.ts index 98c81e5f..ae83ea18 100644 --- a/src/db/fieldAddressing.ts +++ b/src/db/fieldAddressing.ts @@ -247,7 +247,8 @@ export function buildUnresolvableMessage( return ( `no metadata field '${raw}' on this store — nothing carries it, so an ordered or ` + `filtered read against it cannot mean anything. Spell it metadata.${raw} once the ` + - `field exists, or check the field name.` + `field exists, or check the field name (system.${raw} is NOT valid — '${raw}' is ` + + `not one of the engine's system scalars).` ) } diff --git a/src/utils/paramValidation.ts b/src/utils/paramValidation.ts index fd018a04..749849b3 100644 --- a/src/utils/paramValidation.ts +++ b/src/utils/paramValidation.ts @@ -518,7 +518,28 @@ export function validateFindParams(params: FindParams): void { /** * Validate add parameters */ + +/** + * The namespace cannot be forged: a USER metadata key literally spelled + * 'system.' would collide with the engine's explicit address + * namespace at read time — refuse it at the write door, loudly, with the + * fix in the message (sealed 2026-08-03). + */ +function rejectForgedSystemKeys(metadata: Record | undefined, site: string): void { + if (!metadata) return + for (const key of Object.keys(metadata)) { + if (key.startsWith('system.')) { + throw new Error( + `${site}: metadata key '${key}' is not allowed — the 'system.' prefix is the ` + + `engine's explicit address namespace and cannot be used as a user field name. ` + + `Rename the field (e.g. '${key.slice('system.'.length)}').` + ) + } + } +} + export function validateAddParams(params: AddParams): void { + rejectForgedSystemKeys(params.metadata as Record | undefined, 'add()') // Universal truth: must have data or vector if (!params.data && !params.vector) { throw new Error( @@ -559,6 +580,7 @@ export function validateAddParams(params: AddParams): void { * Validate update parameters */ export function validateUpdateParams(params: UpdateParams): void { + rejectForgedSystemKeys(params.metadata as Record | undefined, 'update()') // Universal truth: must have an ID if (!params.id) { throw new Error('id is required for update') diff --git a/tests/unit/db/fieldAddressing.test.ts b/tests/unit/db/fieldAddressing.test.ts index f7ca1cbe..04110992 100644 --- a/tests/unit/db/fieldAddressing.test.ts +++ b/tests/unit/db/fieldAddressing.test.ts @@ -133,9 +133,13 @@ describe('field-addressing law — pure module pins', () => { expect(msg).toContain('metadata.createdAt') }) - it('a non-colliding unknown bare name gets the single-candidate refusal', () => { + it('a non-colliding unknown bare name names both spellings — system. explicitly as NOT valid', () => { + // Cross-engine pin (cor's suite greps for both spellings in every + // refusal): the metadata candidate is the fix; the system spelling is + // named but HONESTLY marked invalid, never offered as a candidate. const msg = buildUnresolvableMessage('scoore', 'entity') - expect(msg).not.toContain('system.scoore') expect(msg).toContain('metadata.scoore') + expect(msg).toContain('system.scoore') + expect(msg).toContain('NOT valid') }) })