Skip to content

Commit dee412b

Browse files
os-warrenclaude
andauthored
test(plugin-auth): register sys_position / sys_user_position in the sso-register harness (#16305)
`bootEngine()` in `sso-register-platform-admin-gate.test.ts` registered the auth identity objects plus hand-declared `sys_permission_set` and `sys_user_permission_set`, but never the position pair the platform-admin standing resolver also reads. `resolveAuthzContext` reads `sys_user_position` on every resolution, and `sys_position` whenever the principal holds any position — always, since every authenticated member implicitly holds the ADR-0090 D5 `everyone` anchor. With no such tables, both reads were refused by the driver and `tryFind`'s loud-failure arm logged them, while the resolver went on treating each refusal as "this principal holds no positions". The suite reported `Tests 4 passed (4)` over eight `[sql-driver] DATABASE_ERROR ... no such table` lines, four per table, and no assertion could notice. That is a latent false green on a security-adjacent resolver: a change making platform-admin standing genuinely depend on a position row would have been measured against an engine that can never return one, and this file would have stayed green. Same shape #14756 exists to remove — a harness whose registered object set is narrower than the code path it drives. Registers both objects the way the file already hand-declares the permission-set pair, with only the columns the judge reads: `active` (the ADR-0049 predicate `isRowActive` applies in §6a) and `organization_id` (what the driver's `applyTenantScope` filters the organization-scoped `sys_position` read on, and what §4 tests to place a `sys_user_position` row). Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y Co-authored-by: Claude <noreply@anthropic.com>
1 parent ac6213e commit dee412b

1 file changed

Lines changed: 62 additions & 6 deletions

File tree

packages/plugins/plugin-auth/src/sso-register-platform-admin-gate.test.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,33 @@
4646
*
4747
* ## Fixture note
4848
*
49-
* The RBAC objects (`sys_permission_set`, `sys_user_permission_set`) live in
50-
* `@objectstack/plugin-security`. They are declared locally here — the
51-
* `last-admin-guard.test.ts` precedent — with only the columns the judge reads,
52-
* so a fixture does not add a dependency edge to plugin-auth. Everything else is
53-
* real: a real ObjectQL engine over real better-sqlite3, the real `AuthManager`
54-
* with the real `sso()` plugin, real sign-up and real session cookies.
49+
* The RBAC objects (`sys_permission_set`, `sys_user_permission_set`,
50+
* `sys_position`, `sys_user_position`) live in `@objectstack/plugin-security`.
51+
* They are declared locally here — the `last-admin-guard.test.ts` precedent —
52+
* with only the columns the judge reads, so a fixture does not add a dependency
53+
* edge to plugin-auth. Everything else is real: a real ObjectQL engine over real
54+
* better-sqlite3, the real `AuthManager` with the real `sso()` plugin, real
55+
* sign-up and real session cookies.
56+
*
57+
* ## ⚠️ Why the POSITION pair is registered, when no assertion mentions it
58+
*
59+
* [#14846] `resolveAuthzContext` reads `sys_user_position` on every resolution,
60+
* and `sys_position` whenever the principal holds any position — which is
61+
* always, because every authenticated member implicitly holds the ADR-0090 D5
62+
* `everyone` anchor. Until those two objects were registered here the tables did
63+
* not exist, so both reads were REFUSED by the driver and `tryFind`'s
64+
* loud-failure arm logged a `[sql-driver] DATABASE_ERROR … no such table` line
65+
* while the resolver carried on treating the refusal as "this principal holds no
66+
* positions". Measured on this file alone: `Tests 4 passed (4)` sitting on top of
67+
* EIGHT refused reads, four per table, and not one assertion able to notice.
68+
*
69+
* That is a latent false green on a security-adjacent resolver, not untidy log
70+
* output: a future change that made platform-admin standing genuinely depend on
71+
* a position row would have been measured against an engine that can never
72+
* return one, and this file would have stayed green. It is the same shape
73+
* #14756 exists to remove — a harness whose registered object set is narrower
74+
* than the code path it drives — kept invisible because the errors are logged
75+
* rather than thrown.
5576
*/
5677

5778
import { describe, it, expect, afterEach, vi } from 'vitest';
@@ -98,6 +119,39 @@ const sysUserPermissionSet = {
98119
},
99120
};
100121

122+
/**
123+
* [#14846] The ADR-0057 D4 position pair, in the same shape and for the same
124+
* reason as the permission-set pair above — these are read by the resolver this
125+
* file drives, so the harness has to be able to answer them.
126+
*
127+
* `active` is the ADR-0049 predicate the resolver applies to `sys_position`
128+
* (`isRowActive`, §6a). `organization_id` is what the driver's
129+
* `applyTenantScope` filters the organization-scoped `sys_position` read on, and
130+
* what §4 tests to decide whether a `sys_user_position` row is global or belongs
131+
* to another tenant.
132+
*/
133+
const sysPosition = {
134+
name: 'sys_position',
135+
label: 'Position',
136+
fields: {
137+
id: { name: 'id', type: 'text' as const, primaryKey: true },
138+
name: { name: 'name', type: 'text' as const },
139+
active: { name: 'active', type: 'boolean' as const },
140+
organization_id: { name: 'organization_id', type: 'text' as const },
141+
},
142+
};
143+
144+
const sysUserPosition = {
145+
name: 'sys_user_position',
146+
label: 'User Position',
147+
fields: {
148+
id: { name: 'id', type: 'text' as const, primaryKey: true },
149+
user_id: { name: 'user_id', type: 'text' as const },
150+
position: { name: 'position', type: 'text' as const },
151+
organization_id: { name: 'organization_id', type: 'text' as const },
152+
},
153+
};
154+
101155
const engines: ObjectQL[] = [];
102156
afterEach(async () => {
103157
while (engines.length) {
@@ -127,6 +181,8 @@ async function bootEngine(): Promise<ObjectQL> {
127181
}
128182
engine.registry.registerObject(sysPermissionSet as never, '@objectstack/plugin-auth');
129183
engine.registry.registerObject(sysUserPermissionSet as never, '@objectstack/plugin-auth');
184+
engine.registry.registerObject(sysPosition as never, '@objectstack/plugin-auth');
185+
engine.registry.registerObject(sysUserPosition as never, '@objectstack/plugin-auth');
130186
await engine.syncSchemas();
131187
return engine;
132188
}

0 commit comments

Comments
 (0)