feat(namespace): write-door forgery refusal (user metadata keys may never start 'system.') + refusal messages name both spellings in every branch (the non-colliding case marks system.<f> honestly as NOT valid) — cross-engine message pin alignment
This commit is contained in:
parent
8e962dabda
commit
48a6130a50
3 changed files with 30 additions and 3 deletions
|
|
@ -247,7 +247,8 @@ export function buildUnresolvableMessage(
|
||||||
return (
|
return (
|
||||||
`no metadata field '${raw}' on this store — nothing carries it, so an ordered or ` +
|
`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 ` +
|
`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).`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -518,7 +518,28 @@ export function validateFindParams(params: FindParams): void {
|
||||||
/**
|
/**
|
||||||
* Validate add parameters
|
* Validate add parameters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace cannot be forged: a USER metadata key literally spelled
|
||||||
|
* 'system.<anything>' 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<string, unknown> | 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 {
|
export function validateAddParams(params: AddParams): void {
|
||||||
|
rejectForgedSystemKeys(params.metadata as Record<string, unknown> | undefined, 'add()')
|
||||||
// Universal truth: must have data or vector
|
// Universal truth: must have data or vector
|
||||||
if (!params.data && !params.vector) {
|
if (!params.data && !params.vector) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -559,6 +580,7 @@ export function validateAddParams(params: AddParams): void {
|
||||||
* Validate update parameters
|
* Validate update parameters
|
||||||
*/
|
*/
|
||||||
export function validateUpdateParams(params: UpdateParams): void {
|
export function validateUpdateParams(params: UpdateParams): void {
|
||||||
|
rejectForgedSystemKeys(params.metadata as Record<string, unknown> | undefined, 'update()')
|
||||||
// Universal truth: must have an ID
|
// Universal truth: must have an ID
|
||||||
if (!params.id) {
|
if (!params.id) {
|
||||||
throw new Error('id is required for update')
|
throw new Error('id is required for update')
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,13 @@ describe('field-addressing law — pure module pins', () => {
|
||||||
expect(msg).toContain('metadata.createdAt')
|
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.<f> 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')
|
const msg = buildUnresolvableMessage('scoore', 'entity')
|
||||||
expect(msg).not.toContain('system.scoore')
|
|
||||||
expect(msg).toContain('metadata.scoore')
|
expect(msg).toContain('metadata.scoore')
|
||||||
|
expect(msg).toContain('system.scoore')
|
||||||
|
expect(msg).toContain('NOT valid')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue