@@ -42,6 +42,7 @@ const ORG = 'org_30621';
4242const ENFORCED = 'os30621_deal' ;
4343const OWNERLESS = 'os30621_session' ;
4444const PUBLIC_OWNED = 'os30621_notice' ;
45+ const PUBLIC_FEDERATED = 'os30621_ext_customer' ;
4546
4647const SYSTEM : ExecutionContext = { isSystem : true , positions : [ ] , permissions : [ ] } ;
4748const ADMIN = {
@@ -88,6 +89,12 @@ async function boot() {
8889 name : PUBLIC_OWNED , label : 'Notice' , sharingModel : 'public_read_write' ,
8990 fields : { ...recordFields , owner_id : text ( 'owner_id' ) } ,
9091 } as never ) ;
92+ // A PUBLIC federated object (ADR-0015): the registry injects `owner_id` as an
93+ // anchor the remote table does not have. Registered only, never written to.
94+ ql . registerObject ( {
95+ name : PUBLIC_FEDERATED , label : 'External customer' , sharingModel : 'public_read_write' ,
96+ external : { remoteName : 'customers' } , fields : { id : { ...text ( 'id' ) , primary : true } , name : text ( 'name' ) } ,
97+ } as never ) ;
9198 ql . registerObject ( {
9299 name : 'sys_sharing_rule' , label : 'Sharing Rule' , isSystem : true ,
93100 fields : {
@@ -102,7 +109,7 @@ async function boot() {
102109 const sharing = new SharingService ( { engine : ql as never } ) ;
103110 const rules = new SharingRuleService ( { engine : ql as never , sharing } ) ;
104111 bindRuleObjectEnforcementGuard ( ql as never , sharing ) ;
105- return { ql, rules } ;
112+ return { ql, rules, sharing } ;
106113}
107114
108115const rowFor = ( object : string , name : string ) => ( {
@@ -169,6 +176,24 @@ describe('[#30621] the /data door refuses a sharing rule on an object without re
169176 } ) ;
170177} ) ;
171178
179+ describe ( '[#30621] the refusal sentence names the remedy for the arm that refused' , ( ) => {
180+ it ( 'a PUBLIC federated object with the injected owner anchor is told to declare a real remote owner_id' , async ( ) => {
181+ const { ql, sharing } = await boot ( ) ;
182+ expect ( ( ql as any ) . getSchema ( PUBLIC_FEDERATED ) ?. fields ?. owner_id , 'the registry injected the anchor' ) . toBeTruthy ( ) ;
183+ const refusal = sharing . sharingRuleObjectRefusal ( PUBLIC_FEDERATED ) ;
184+ expect ( refusal ) . toContain ( `'${ PUBLIC_FEDERATED } '` ) ;
185+ expect ( refusal ) . toMatch ( / d e c l a r e a r e a l r e m o t e ' o w n e r _ i d ' c o l u m n / ) ;
186+ // Not the generic remedy: the object already has an owner_id, and a
187+ // private model would still meet the phantom-anchor refusal.
188+ expect ( refusal ) . not . toMatch ( / s h a r i n g M o d e l : ' p r i v a t e ' / ) ;
189+ const err = await ql . insert ( 'sys_sharing_rule' , rowFor ( PUBLIC_FEDERATED , 'ext_rule' ) , { context : ADMIN } as never )
190+ . catch ( ( x : unknown ) => x as any ) ;
191+ expect ( err ?. code ) . toBe ( 'SHARING_NOT_ENABLED' ) ;
192+ expect ( err ?. status ) . toBe ( 422 ) ;
193+ expect ( await storedRules ( ql ) ) . toHaveLength ( 0 ) ;
194+ } ) ;
195+ } ) ;
196+
172197describe ( '[#30621] defineRule (POST /api/v1/sharing/rules) refuses the same rule' , ( ) => {
173198 const input = ( object : string , name : string ) => ( {
174199 name, label : name , object, recipientType : 'position' , recipientId : 'sales_rep' , criteria : { stage : 'won' } ,
0 commit comments