@@ -25,7 +25,7 @@ import { describe, it, expect, afterEach, beforeEach } from 'vitest';
2525import { ObjectQL } from '@objectstack/objectql' ;
2626import { SqlDriver } from '@objectstack/driver-sql' ;
2727import { resetPlatformAdminEmailMemo } from '@objectstack/core' ;
28- import { SysUser , SysAccount } from '@objectstack/platform-objects/identity' ;
28+ import { SysUser , SysAccount , SysOrganization } from '@objectstack/platform-objects/identity' ;
2929import { bootstrapPlatformAdmin } from './bootstrap-platform-admin.js' ;
3030import { securityObjects } from './manifest.js' ;
3131import { defaultPermissionSets } from './objects/default-permission-sets.js' ;
@@ -53,7 +53,12 @@ afterEach(async () => {
5353 resetPlatformAdminEmailMemo ( ) ;
5454} ) ;
5555
56- /** A fresh `:memory:` database holding the security plugin's own objects and the identity tables. */
56+ /**
57+ * A fresh `:memory:` database holding the security plugin's own objects and the
58+ * identity tables — and the Default Organization a `single` boot creates before
59+ * any `kernel:ready` hook runs (ADR-0131 D3, plugin-auth's boot invariant),
60+ * which owns the promotion's grant (D5).
61+ */
5762async function freshEngine ( ) : Promise < ObjectQL > {
5863 const engine = new ObjectQL ( ) ;
5964 engine . registerDriver (
@@ -67,9 +72,10 @@ async function freshEngine(): Promise<ObjectQL> {
6772 version : '1.0.0' ,
6873 type : 'plugin' ,
6974 scope : 'system' ,
70- objects : [ SysUser , SysAccount , ...securityObjects ] ,
75+ objects : [ SysUser , SysAccount , SysOrganization , ...securityObjects ] ,
7176 } as any ) ;
7277 await engine . syncSchemas ( ) ;
78+ await engine . insert ( 'sys_organization' , { id : 'org_default' , name : 'Default Organization' , slug : 'default' } , SYS ) ;
7379 engines . push ( engine ) ;
7480 return engine ;
7581}
@@ -80,7 +86,7 @@ async function human(engine: ObjectQL, id: string, email: string, createdAt: str
8086}
8187
8288describe ( '[ADR-0131 D3/D4] a fresh single-posture database promotes its first human' , ( ) => {
83- it ( 'promotes the oldest account that can sign in, granting admin_full_access by name' , async ( ) => {
89+ it ( 'promotes the oldest account that can sign in, granting admin_full_access by name, owned by the Default Organization ' , async ( ) => {
8490 const engine = await freshEngine ( ) ;
8591 await human ( engine , 'usr_first' , 'first@fresh.example' , '2026-01-01T00:00:00.000Z' ) ;
8692 await human ( engine , 'usr_second' , 'second@fresh.example' , '2026-02-01T00:00:00.000Z' ) ;
@@ -89,8 +95,9 @@ describe('[ADR-0131 D3/D4] a fresh single-posture database promotes its first hu
8995
9096 expect ( report ) . toMatchObject ( { adminPromoted : true , adminUserId : 'usr_first' } ) ;
9197 const grants = ( await engine . find ( 'sys_user_permission_set' , { where : { } , ...SYS } ) ) as any [ ] ;
98+ // [ADR-0131 D5] The row is the Default Organization's — ⛔ never a NULL organization.
9299 expect ( grants . map ( ( g ) => [ g . user_id , g . permission_set , g . organization_id ?? null ] ) ) . toEqual ( [
93- [ 'usr_first' , 'admin_full_access' , null ] ,
100+ [ 'usr_first' , 'admin_full_access' , 'org_default' ] ,
94101 ] ) ;
95102 } ) ;
96103
@@ -103,5 +110,41 @@ describe('[ADR-0131 D3/D4] a fresh single-posture database promotes its first hu
103110 const again = await bootstrapPlatformAdmin ( engine , defaultPermissionSets ) ;
104111
105112 expect ( again ) . toMatchObject ( { adminPromoted : false , reason : 'already_have_admin' , adminUserId : 'usr_first' } ) ;
113+ const grants = ( await engine . find ( 'sys_user_permission_set' , { where : { } , ...SYS } ) ) as any [ ] ;
114+ expect ( grants ) . toHaveLength ( 1 ) ;
115+ } ) ;
116+
117+ it ( 'an upgraded database whose administrator\'s grant the ceremony gave the Default Organization promotes nobody else' , async ( ) => {
118+ // ADR-0131 D10 attributes a `single` database's organization-less grants to
119+ // the Default Organization. A holder scan reading only the NULL row found
120+ // nobody on such a database and minted a second grant on every boot.
121+ const engine = await freshEngine ( ) ;
122+ await human ( engine , 'usr_admin' , 'admin@fresh.example' , '2026-01-01T00:00:00.000Z' ) ;
123+ await human ( engine , 'usr_older' , 'older@fresh.example' , '2025-01-01T00:00:00.000Z' ) ;
124+ await engine . insert ( 'sys_user_permission_set' , {
125+ id : 'ups_attributed' , user_id : 'usr_admin' , permission_set : 'admin_full_access' , organization_id : 'org_default' ,
126+ } , SYS ) ;
127+
128+ const report = await bootstrapPlatformAdmin ( engine , defaultPermissionSets ) ;
129+
130+ expect ( report ) . toMatchObject ( { adminPromoted : false , reason : 'already_have_admin' , adminUserId : 'usr_admin' } ) ;
131+ const grants = ( await engine . find ( 'sys_user_permission_set' , { where : { } , ...SYS } ) ) as any [ ] ;
132+ expect ( grants . map ( ( g ) => g . id ) ) . toEqual ( [ 'ups_attributed' ] ) ;
133+ } ) ;
134+
135+ it ( '⛔ a database that names no Default Organization is not promoted, says so at error, and writes no grant' , async ( ) => {
136+ const engine = await freshEngine ( ) ;
137+ await engine . insert ( 'sys_organization' , { id : 'org_second' , name : 'Second' , slug : 'second' } , SYS ) ;
138+ await engine . update ( 'sys_organization' , { slug : 'first' } , { where : { id : 'org_default' } , ...SYS } as any ) ;
139+ await human ( engine , 'usr_first' , 'first@fresh.example' , '2026-01-01T00:00:00.000Z' ) ;
140+ const errors : string [ ] = [ ] ;
141+
142+ const report = await bootstrapPlatformAdmin ( engine , defaultPermissionSets , {
143+ logger : { info : ( ) => { } , warn : ( ) => { } , error : ( m : string ) => errors . push ( m ) } ,
144+ } ) ;
145+
146+ expect ( report ) . toMatchObject ( { adminPromoted : false , reason : 'default_organization_unresolved' } ) ;
147+ expect ( errors ) . toHaveLength ( 1 ) ;
148+ expect ( await engine . find ( 'sys_user_permission_set' , { where : { } , ...SYS } ) ) . toEqual ( [ ] ) ;
106149 } ) ;
107150} ) ;
0 commit comments