Unified · Split
| @@ -26,6 +26,7 @@ return JSON.stringify(result); · 25 unmodified lines | ||
| 26 | 26 | |
| 27 | 27 | interface MemberRow { |
| 28 | 28 | readonly id: string; |
| 29 | + readonly userId: string; | |
| 29 | 30 | readonly email: string; |
| 30 | 31 | readonly role: string; |
| 31 | 32 | } |
| @@ -52,11 +53,11 @@ scenario( · 20 unmodified lines | ||
| 52 | 53 | const target = yield* Target; |
| 53 | 54 | const api = yield* Api; |
| 54 | 55 | const mcp = yield* Mcp; |
| 55 | − const actor = yield* target.newIdentity(); | |
| 56 | − const controller = yield* Effect.promise(() => | |
| 57 | − createInvitedIdentity(target.baseUrl, actor, { | |
| 56 | + const controller = yield* target.newIdentity(); | |
| 57 | + const actor = yield* Effect.promise(() => | |
| 58 | + createInvitedIdentity(target.baseUrl, controller, { | |
| 58 | 59 | role: "admin", |
| 59 | − emailPrefix: "approval-role-controller", | |
| 60 | + emailPrefix: "approval-role-actor", | |
| 60 | 61 | }), |
| 61 | 62 | ); |
| 62 | 63 | const controllerCookie = controller.headers?.cookie; |
| @@ -81,11 +82,22 @@ scenario( · 18 unmodified lines | ||
| 81 | 82 | ); |
| 82 | 83 | const actorMember = membersBody.members.find((member) => member.email === actor.label); |
| 83 | 84 | expect( |
| 84 | − actorMember?.role === "owner" || actorMember?.role === "admin", | |
| 85 | + actorMember?.role === "admin", | |
| 85 | 86 | "the actor begins with privileged organization membership", |
| 86 | 87 | ).toBe(true); |
| 87 | 88 | if (!actorMember) return yield* Effect.die("the actor membership was not listed"); |
| 88 | 89 | |
| 90 | + const setGlobalRole = (role: "admin" | "user"): Effect.Effect<Response> => | |
| 91 | + Effect.promise(() => | |
| 92 | + accountRequest(target.baseUrl, controllerCookie, "/api/auth/admin/set-role", { | |
| 93 | + method: "POST", | |
| 94 | + headers: { "content-type": "application/json" }, | |
| 95 | + body: JSON.stringify({ userId: actorMember.userId, role }), | |
| 96 | + }), | |
| 97 | + ); | |
| 98 | + const promoted = yield* setGlobalRole("admin"); | |
| 99 | + expect(promoted.status, "the actor has a stale global admin role to distrust").toBe(200); | |
| 100 | + | |
| 89 | 101 | const setRole = (roleSlug: "admin" | "member"): Effect.Effect<Response> => |
| 90 | 102 | Effect.promise(() => |
| 91 | 103 | accountRequest( |
| @@ -134,7 +146,11 @@ scenario( · 42 unmodified lines | ||
| 134 | 146 | ); |
| 135 | 147 | |
| 136 | 148 | expect(decision.status, "the browser decision reaches the paused execution").toBe(200); |
| 137 | − expect(resumed.ok, "the resumed workspace mutation is denied at the sink").toBe(false); | |
| 149 | + expect(resumed.ok, "the resumed sandbox reports the tool result normally").toBe(true); | |
| 150 | + expect( | |
| 151 | + resumed.text, | |
| 152 | + "the resumed workspace mutation preserves the workspace-admin denial", | |
| 153 | + ).toMatch(/OrgWriteDenied|administrator|admin/i); | |
| 138 | 154 | const policies = yield* actorClient.policies.list(); |
| 139 | 155 | expect( |
| 140 | 156 | policies.some((policy) => policy.pattern === CREATED_PATTERN), |
| @@ -149,6 +165,7 @@ scenario( · 8 unmodified lines | ||
| 149 | 165 | payload: { owner: "org" }, |
| 150 | 166 | }), |
| 151 | 167 | ), |
| 168 | + Effect.andThen(setGlobalRole("user")), | |
| 152 | 169 | Effect.ignore, |
| 153 | 170 | ), |
| 154 | 171 | ), |