88// [#9237] The compound rule moved from `showcase_project` to
99// `showcase_contact`, and the D3 proof got its missing half with it. On
1010// `showcase_project` (OWD `public_read_write`) sharing has nothing to
11- // widen, so every grant the rule reconciled was REFUSED
12- // (`SHARING_NOT_ENABLED`, ADR-0111 D7) — this file could assert the
13- // criteria compiled and matched, and never that anything was ENFORCED,
14- // while its own header claimed both. `showcase_contact` is OWD `private`,
15- // so the grant lands and the enforcement half is now measured rather than
16- // implied.
11+ // widen for the organization's staff, so every grant the rule reconciled
12+ // was REFUSED (`SHARING_NOT_ENABLED`, ADR-0111 D7; since #25549 a share
13+ // there is consulted for an `external` member alone) — this file could
14+ // assert the criteria compiled and matched, and never that anything was
15+ // ENFORCED, while its own header claimed both. `showcase_contact` is OWD
16+ // `private`, so the grant lands and the enforcement half is now measured
17+ // rather than implied.
1718// • ADR-0058 D4 — an RLS `check` clause validates the write POST-IMAGE: a
1819// contributor cannot reassign an invoice they own to a different owner.
1920//
@@ -53,6 +54,8 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
5354 let ql : any ;
5455 let token : string ;
5556 let invId : string ;
57+ let pjId : string ;
58+ let memberId : string ;
5659
5760 beforeAll ( async ( ) => {
5861 stack = await bootShowcase ( {
@@ -77,6 +80,11 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
7780
7881 // An invoice owned by the member (system insert sidesteps authoring rules).
7982 invId = idOf ( await ql . insert ( 'showcase_invoice' , { name : 'INV-D34' , account : acct , owner : MEMBER , status : 'draft' } , SYS ) ) ;
83+
84+ // [#25549] A real project, and the member's user id: the D7 contrast on
85+ // `showcase_project` is judged against a record's organization and a recipient.
86+ pjId = idOf ( await ql . insert ( 'showcase_project' , { name : 'D34 Project' , account : acct , status : 'planned' } , SYS ) ) ;
87+ memberId = String ( ( await ql . findOne ( 'sys_user' , { where : { email : MEMBER } , context : SYS . context } ) ) ?. id ?? '' ) ;
8088 } , 90_000 ) ;
8189
8290 afterAll ( async ( ) => { await stack ?. stop ( ) ; } ) ;
@@ -108,8 +116,9 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
108116 } ) ;
109117
110118 it ( '[#9237] the grant the rule reconciles is one the gates CONSULT — not refused as inert' , async ( ) => {
111- // The half this proof could never carry on `showcase_project`. The verdict
112- // is caller-independent (ADR-0111 D7) and computed from the OWD alone, so
119+ // The half this proof could never carry on `showcase_project`. On a
120+ // private object the verdict is caller-independent (ADR-0111 D7) and
121+ // computed from the OWD alone, so
113122 // it is asserted directly rather than inferred from a reconcile count that
114123 // an empty recipient expansion would leave silently at zero — which is
115124 // exactly how the sibling rule on `showcase_project` produced no boot WARN
@@ -122,17 +131,30 @@ describe('showcase: D3 compound sharing (#1887) + D4 RLS check', () => {
122131 expect ( granted , 'a share row on a private-OWD object is accepted' ) . toBeTruthy ( ) ;
123132
124133 // The contrast that names the retired rules' defect, on the object they
125- // used to sit on: same call, same caller, refused for the OWD alone. The
126- // record id deliberately names no row — the D7 verdict is computed from
127- // the OBJECT before any record is read, which is precisely why it is
128- // caller- and record-independent. Both the code and the object are
129- // asserted, so a refusal arriving for some other reason cannot pass.
134+ // used to sit on: same call, same caller, refused. [#25549] On a public
135+ // object the D7 verdict is no longer the OWD's alone: the external OWD
136+ // floor consults a share there, so the verdict reads one fact about the
137+ // recipient — is it an `external` member of the RECORD's organization?
138+ // The retired rules' `position` recipients expand to that organization's
139+ // staff, so the probe is a real project and a staff member of its
140+ // organization (both preconditions asserted). Both the code and the reason
141+ // are asserted, so a refusal arriving for some other reason cannot pass —
142+ // a record the verdict cannot place answers "that organization could not
143+ // be resolved" instead.
144+ const pjOrg = ( await ql . findOne ( 'showcase_project' , { where : { id : pjId } , fields : [ 'organization_id' ] , context : SYS . context } ) ) ?. organization_id ;
145+ expect ( pjOrg , 'fixture: the project carries an organization' ) . toBeTruthy ( ) ;
146+ const membership = await ql . findOne ( 'sys_member' , {
147+ where : { user_id : memberId , organization_id : pjOrg } , fields : [ 'role' ] , context : { ...SYS . context , tenantId : pjOrg } ,
148+ } ) ;
149+ expect ( membership ?. role , 'fixture: the member is staff of that organization, not external' ) . toBe ( 'member' ) ;
130150 await expect (
131151 sharing . grant (
132- { object : 'showcase_project' , recordId : 'pj_absent' , recipientType : 'user' , recipientId : 'u_d34_probe' , accessLevel : 'read' } ,
152+ { object : 'showcase_project' , recordId : pjId , recipientType : 'user' , recipientId : memberId , accessLevel : 'read' } ,
133153 { isSystem : true } ,
134154 ) ,
135- ) . rejects . toThrow ( / S H A R I N G _ N O T _ E N A B L E D : ' s h o w c a s e _ p r o j e c t ' i s n o t u n d e r r e c o r d - s h a r i n g e n f o r c e m e n t / ) ;
155+ ) . rejects . toThrow (
156+ / S H A R I N G _ N O T _ E N A B L E D : ' s h o w c a s e _ p r o j e c t ' h a s a p u b l i c s h a r i n g m o d e l , s o a s h a r e r o w o n i t i s c o n s u l t e d o n l y f o r a n e x t e r n a l m e m b e r o f t h e r e c o r d ' s o r g a n i z a t i o n , a n d t h e r e c i p i e n t i s n o t o n e / ,
157+ ) ;
136158 } ) ;
137159
138160 // ── ADR-0058 D4 ────────────────────────────────────────────────────────────
0 commit comments