From e5f5b500df9d89ee429276178436b92d89ea1d90 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 15:25:26 +0000 Subject: [PATCH] test(objectql): declare the recorded-by fixture's lookup with the canonical `reference` key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `protocol-recorded-by-null.test.ts` declared `recorded_by` as `{ type: 'lookup', referenceTo: 'sys_user', readonly: true }` under a comment claiming it is "the real declaration". `referenceTo` is an alias `FieldSchema` refuses by name, and `referenceTargetOf` — the single arbiter the #4441 write-path referential check resolves through — does not read it, so the lookup presented as target-less. Measured on the fixture's own write path before renaming anything, by counting the guard's target probe: with `referenceTo` spelled, no probe ran even with the `readonly` exemption removed; with `reference` spelled, it runs. So the exemption the file's second half claims to exercise had never admitted these writes — the target-less skip did. Renames the key and adds the two pins that keep the header's claim honest: the declaration resolves through `referenceTargetOf`, and an actor id no `sys_user` row matches is still admitted (which is the exemption, and is now red if the exemption is deleted). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 --- .../src/protocol-recorded-by-null.test.ts | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/objectql/src/protocol-recorded-by-null.test.ts b/packages/objectql/src/protocol-recorded-by-null.test.ts index 27bfc776ee..b3e678d778 100644 --- a/packages/objectql/src/protocol-recorded-by-null.test.ts +++ b/packages/objectql/src/protocol-recorded-by-null.test.ts @@ -19,10 +19,23 @@ * check precisely because this column held a value no `sys_user` row * matched. With the sentinel gone the ordinary authoring paths must still * pass — that is the regression #4441 was bitten by. + * + * [#14535] "The real thing" is a claim about the TARGET KEY too, and it was + * false here until this change. The declaration spelled `referenceTo` — an + * alias `FieldSchema` refuses by name (#11567) and `referenceTargetOf`, the + * single arbiter the write-path guard resolves through, does not read at all. + * So the lookup presented as TARGET-LESS and the guard skipped it at + * `if (!target) continue`, whatever the `readonly` exemption did. Measured on + * this very write path by counting the guard's own target probe: with the + * alias spelled no probe ran even with the exemption deleted; with `reference` + * spelled it runs. The exemption had therefore never been what admitted these + * writes. Two pins below keep both halves honest — the declaration resolves, + * and the exemption is what admits an actor id no `sys_user` row matches. */ import { describe, it, expect, beforeEach } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; +import { referenceTargetOf } from '@objectstack/spec/data'; import { ObjectQL } from './engine.js'; const sysUserObject = { @@ -78,9 +91,11 @@ const sysMetadataHistoryObject = { source: { name: 'source', label: 'Source', type: 'text' as const }, organization_id: { name: 'organization_id', label: 'Org', type: 'text' as const }, // The real declaration, not a `text` stand-in — see the file header. + // The target key is `reference`, the ONLY spelling `referenceTargetOf` + // reads and the one `Field.lookup` emits; the pin below holds it there. recorded_by: { name: 'recorded_by', label: 'Recorded By', - type: 'lookup' as const, referenceTo: 'sys_user', readonly: true, + type: 'lookup' as const, reference: 'sys_user', readonly: true, }, recorded_at: { name: 'recorded_at', label: 'At', type: 'datetime' as const, required: true }, }, @@ -255,4 +270,35 @@ describe('#4556 — protocol write paths store NULL, not the sentinel string', ( expect(v === null || users.has(v)).toBe(true); } }); + + it('[#14535] the declaration resolves to sys_user through the platform arbiter', () => { + // The fidelity claim in the header, as an assertion instead of prose. + // A raw object literal handed to the registry is never parsed by + // `FieldSchema`, so the alias this fixture used to spell could never + // be refused where it was written; `referenceTargetOf` is the reader + // that decides whether the lookup has a target at all, and it is the + // one the write-path guard resolves through. + expect(referenceTargetOf(sysMetadataHistoryObject.fields.recorded_by)).toBe('sys_user'); + }); + + it('[#14535] an actor id with no sys_user row is still admitted — the #4441 readonly exemption', async () => { + // The second half of the file, now that the target resolves. This is + // NOT the #4556 sentinel returning: `'system'` was a string the + // PLATFORM minted for every actor-less write, which is what this suite + // refuses above. An actor the caller named is the caller's own value, + // and #4441 deliberately does not police a `readonly` lookup — the + // value there was minted outside the check's stated scope, and the + // residual is reported by the #4551 audit rather than refused here. + // + // Before the `reference` rename this passed for the wrong reason: the + // field was target-less, so the guard skipped it whether or not the + // exemption existed. Delete the exemption now and this goes red. + await protocol.saveMetaItem({ + type: 'view', name: 'cases', organizationId: 'org_x', item: viewBody('A'), actor: 'usr_not_a_row', + }); + + const rows = await historyRows(); + expect(rows).toHaveLength(1); + expect(rows[0].recorded_by).toBe('usr_not_a_row'); + }); });