From 7222252f839d4dd9fd37c6919beccf7d22f15139 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 13 Sep 2026 08:11:54 +0000 Subject: [PATCH] test(plugin-auth): register the authz objects two sign-in fixtures drive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `account-issuer-upgrade-path.test.ts` and `signup-existing-address-refusal.test.ts` boot a real ObjectQL over a real SqlDriver and register only plugin-auth's own `authIdentityObjects`. Both then drive reads against plugin-security-owned tables that were never provisioned: - real sign-ins reach core's `resolveUserAuthzGrants`, whose `tryFind` reads `sys_user_position`, `sys_user_permission_set` and `sys_position`; - `settleSelfRegistrationGrant` reads `sys_user_permission_set` before inserting the declared self-registration grant. The driver refused every one of them. `tryFind` classifies a missing table as "not provisioned" and answers `[]`, so nothing went red — the resolver leg and the grant-write leg simply went unexercised while the suites reported green. Declares the missing objects locally with only the columns those paths read, following the `find-envelope-limb-removal` precedent, so no dependency edge from plugin-auth to plugin-security is added. No product code changes; the refused reads now succeed. Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj Co-authored-by: Claude --- .../src/account-issuer-upgrade-path.test.ts | 70 +++++++++++++++++++ .../signup-existing-address-refusal.test.ts | 29 ++++++++ 2 files changed, 99 insertions(+) diff --git a/packages/plugins/plugin-auth/src/account-issuer-upgrade-path.test.ts b/packages/plugins/plugin-auth/src/account-issuer-upgrade-path.test.ts index 2de292d6e8..1a9f9f03c6 100644 --- a/packages/plugins/plugin-auth/src/account-issuer-upgrade-path.test.ts +++ b/packages/plugins/plugin-auth/src/account-issuer-upgrade-path.test.ts @@ -84,6 +84,70 @@ function newDbFile(): string { return join(dir, 'identity.sqlite'); } +/** + * [#17897] The three authorization objects a REAL sign-in makes core's + * `resolveUserAuthzGrants` read, which the auth manifest does not declare — + * they belong to `@objectstack/plugin-security`. Spelled locally with only the + * columns the resolver reads (the `find-envelope-limb-removal` / + * `signup-existing-address-refusal` precedent), so a fixture adds no dependency + * edge from plugin-auth to plugin-security. + * + * Why they belong in a file about a column on `sys_account`: the cases below + * sign in through the real auth route, and `AuthManager`'s session-payload + * callback resolves the principal's grants on the way out. Against a fixture + * that registered only `authIdentityObjects`, all three of those reads were + * REFUSED by the driver — measured at `225197cdb`: two sign-in cases x + * `sys_user_position` / `sys_user_permission_set` / `sys_position` = six + * `DATABASE_ERROR` lines per run. `tryFind` classifies a missing table as "not + * provisioned" and answers `[]`, so nothing went red: the resolver leg of a + * sign-in was never actually exercised here, and the suite reported a green it + * had not earned. Registering the tables is what makes those reads SUCCEED — + * ⛔ the fix is not to quieten the line. + * + * ⛔ `sys_position_permission_set` and `sys_permission_set` are deliberately + * NOT here. The resolver reaches them only once a `sys_position` row resolves + * and a permission-set id is collected; these cases seed neither, so both reads + * are skipped and registering the objects would add fixture surface no read in + * this file touches. + * + * The ADR-0091 validity columns (`valid_from` / `valid_until`) are absent for + * the same reason `sys_member` lacks them today: `isGrantActive` reads an + * absent bound as unbounded, so declaring them would change no verdict here. + */ +const sysUserPosition = { + name: 'sys_user_position', + label: 'User Position', + fields: { + id: { name: 'id', type: 'text' as const, primaryKey: true }, + user_id: { name: 'user_id', type: 'text' as const }, + position: { name: 'position', type: 'text' as const }, + organization_id: { name: 'organization_id', type: 'text' as const }, + }, +}; + +const sysUserPermissionSet = { + name: 'sys_user_permission_set', + label: 'User Permission Set', + fields: { + id: { name: 'id', type: 'text' as const, primaryKey: true }, + user_id: { name: 'user_id', type: 'text' as const }, + permission_set_id: { name: 'permission_set_id', type: 'text' as const }, + organization_id: { name: 'organization_id', type: 'text' as const }, + }, +}; + +const sysPosition = { + name: 'sys_position', + label: 'Position', + fields: { + id: { name: 'id', type: 'text' as const, primaryKey: true }, + name: { name: 'name', type: 'text' as const }, + label: { name: 'label', type: 'text' as const }, + active: { name: 'active', type: 'boolean' as const }, + organization_id: { name: 'organization_id', type: 'text' as const }, + }, +}; + async function bootEngine(filename: string, objects: unknown[]): Promise { const engine = new ObjectQL(); engines.push(engine); @@ -95,6 +159,12 @@ async function bootEngine(filename: string, objects: unknown[]): Promise { while (engines.length) { @@ -122,6 +150,7 @@ async function bootEngine(): Promise { engine.registry.registerObject(object as never, '@objectstack/plugin-auth'); } engine.registry.registerObject(sysPermissionSet as never, '@objectstack/plugin-security'); + engine.registry.registerObject(sysUserPermissionSet as never, '@objectstack/plugin-security'); await engine.syncSchemas(); return engine; }