Skip to content

Commit 57f738d

Browse files
committed
fix(services): the user-reference refusal is built by the shared validationFailure constructor
The producer-side refusal assigned its code in each package, which made both packages new stamp sites of a registered code with no provenance row. It is now built by `validationFailure` from `@objectstack/types` (already a runtime dependency of both), the constructor that already holds that provenance. The envelope is unchanged: same name, code, message and findings. The settings pin's find double applies the caller's bound before copying rows, and the package's vitest config aliases `platform-objects/identity` to source. Claude-Session: https://claude.ai/code/session_011K3zqE8Pv1Evw5hc8tZCnN Co-authored-by: Claude <noreply@anthropic.com>
1 parent a3c2209 commit 57f738d

4 files changed

Lines changed: 25 additions & 17 deletions

File tree

‎packages/services/service-messaging/src/actor-reference.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type { IDataEngine } from '@objectstack/spec/contracts';
44
import { renderValidationMessage } from '@objectstack/spec/system';
5+
import { validationFailure } from '@objectstack/types';
56
import { FAN_OUT_SYSTEM_CONTEXT } from './fan-out-system-context.js';
67
import { USER_OBJECT } from './recipient-locale.js';
78

@@ -29,8 +30,11 @@ const ACTOR_FIELD = 'actor_id';
2930
* write through, as the engine's fail-open does;
3031
* - the same answer — `VALIDATION_FAILED` carrying one `reference_not_found`
3132
* finding on `actor_id`, whose message is rendered from the same catalog
32-
* entry and the field's declared label. The differential pin holds it
33-
* equal to the engine's refusal over a real engine.
33+
* entry and the field's declared label. It is built by `validationFailure`
34+
* (`@objectstack/types`), the shared constructor for the shape every door
35+
* serves as `400 VALIDATION_FAILED`, so this package stamps no code of its
36+
* own. The differential pin holds it equal to the engine's refusal over a
37+
* real engine.
3438
*
3539
* A write that names no actor (`null`, `undefined`, `''`) is unchanged: the
3640
* engine never checked an empty reference either.
@@ -68,9 +72,5 @@ function actorReferenceNotFound(data: IDataEngine, object: string, value: string
6872
field: ACTOR_FIELD,
6973
params: { ...constraint, value },
7074
});
71-
const error = new Error(message) as Error & { code: string; fields: unknown[] };
72-
error.name = 'ValidationError';
73-
error.code = 'VALIDATION_FAILED';
74-
error.fields = [{ field: ACTOR_FIELD, code: 'reference_not_found', message, label, constraint, value }];
75-
return error;
75+
return validationFailure(message, [{ field: ACTOR_FIELD, code: 'reference_not_found', message, label, constraint, value }]);
7676
}

‎packages/services/service-settings/src/actor-reference.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { renderValidationMessage } from '@objectstack/spec/system';
4+
import { validationFailure } from '@objectstack/types';
45

56
/** The object every user reference below targets. */
67
export const USER_OBJECT = 'sys_user';
@@ -40,8 +41,11 @@ export interface UserReferenceField {
4041
* does;
4142
* - the same answer: `VALIDATION_FAILED` carrying one `reference_not_found`
4243
* finding on the field, rendered from the same catalog entry and the
43-
* field's label. The differential pin holds it equal to the engine's
44-
* refusal over a real engine.
44+
* field's label. It is built by `validationFailure` (`@objectstack/types`),
45+
* the shared constructor for the shape every door serves as
46+
* `400 VALIDATION_FAILED`, so this package stamps no code of its own. The
47+
* differential pin holds it equal to the engine's refusal over a real
48+
* engine.
4549
*
4650
* A write that names no user (`null`, `undefined`, `''`) is unchanged: the
4751
* engine never checked an empty reference either.
@@ -66,13 +70,9 @@ export async function assertUserReferenceResolves(
6670
field: ref.field,
6771
params: { ...constraint, value: String(value) },
6872
});
69-
const error = new Error(message) as Error & { code: string; fields: unknown[] };
70-
error.name = 'ValidationError';
71-
error.code = 'VALIDATION_FAILED';
72-
error.fields = [
73+
throw validationFailure(message, [
7374
{ field: ref.field, code: 'reference_not_found', message, label: ref.label, constraint, value: String(value) },
74-
];
75-
throw error;
75+
]);
7676
}
7777

7878
/**

‎packages/services/service-settings/src/settings-user-reference.pin.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ function makeMemoryDriver() {
4646
async connect() {}, async disconnect() {}, async checkHealth() { return true; },
4747
async execute() { return null; },
4848
async find(object: string, ast: any) {
49-
const hits = [...rowsOf(object).values()].filter((r) => matches(r, ast?.where)).map((r) => ({ ...r }));
50-
return typeof ast?.limit === 'number' ? hits.slice(0, ast.limit) : hits;
49+
const hits = [...rowsOf(object).values()].filter((r) => matches(r, ast?.where));
50+
const page = typeof ast?.limit === 'number' ? hits.slice(0, ast.limit) : hits;
51+
return page.map((r) => ({ ...r }));
5152
},
5253
async findOne(object: string, ast: any) {
5354
for (const r of rowsOf(object).values()) if (matches(r, ast?.where)) return { ...r };

‎packages/services/service-settings/vitest.config.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ export default defineConfig({
7373
find: /^@objectstack\/platform-objects\/system$/,
7474
replacement: path.resolve(__dirname, '../../platform-objects/src/system/index.ts'),
7575
},
76+
// `platform-objects/identity` arrived with the user-reference pin
77+
// (`settings-user-reference.pin.test.ts`), which registers `sys_user` on a
78+
// real engine; same explicit-subpath shape as `system` above.
79+
{
80+
find: /^@objectstack\/platform-objects\/identity$/,
81+
replacement: path.resolve(__dirname, '../../platform-objects/src/identity/index.ts'),
82+
},
7683
{
7784
find: /^@objectstack\/platform-objects$/,
7885
replacement: path.resolve(__dirname, '../../platform-objects/src/index.ts'),

0 commit comments

Comments
 (0)