@foir/sdk 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -108,6 +108,57 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
108
108
 
109
109
  Changes on `dev` that have not been published to npm.
110
110
 
111
+ ## [0.23.0] — 2026-10-16
112
+
113
+ ### Added
114
+
115
+ - **`signInRefusalOf(error)`** (`@foir/sdk/auth`) reads why a sign-in your app's
116
+ own form sent was refused, as a stable code: `password_sign_in_off` from
117
+ `login` or `register` where the project, or the app the sign-in came through,
118
+ takes no password, and `code_sign_in_off` from `requestOTP` or `loginWithOTP`
119
+ where it takes no code. Null for any other failure.
120
+ - **`hostedSignInNoticeOf` reads what the hosted login sent back** (`@foir/sdk/auth`).
121
+ A refused sign-in's `foir_error` code, or the provider a sign-in connected to
122
+ the account (`foir_connected`), each with a sentence your app can show as it
123
+ is. A code it has no words for reads as a general sentence, never the code.
124
+ - **`myProviderToken` says what it did about the token** (`@foir/sdk/auth`).
125
+ `refresh` is `REFRESHED`, `FAILED` (with `refreshFailure` saying where),
126
+ `HELD_BACK` or `NOT_ATTEMPTED`; `reauthenticate` is true when only signing in
127
+ with the provider again mends the connection; and `linked: false` answers a
128
+ signed-in user with nothing stored for the provider. Pass `forceRefresh`
129
+ after the provider refused the token it was given. A token whose provider
130
+ stated no expiry is now renewed when read, and two reads at once renew it
131
+ once. New type: `ProviderTokenRefresh`.
132
+ - **`requestPasswordReset` takes a `clientId`** (`@foir/sdk/auth`). Where the
133
+ project has no App base URL, the reset email links to Foir's hosted reset
134
+ page, and naming your app sends the user to its initiate login URL to sign in
135
+ once the password is changed.
136
+ - **`registerOrganization` takes an `appBaseUrl`** (`@foir/sdk/auth`), so an
137
+ organization a user founds has its address from the first request and an app
138
+ that finds its organization by `orgUrl` finds this one at once. The address
139
+ sits under the project's own app address or a domain it verified, and one
140
+ another organization holds is refused.
141
+
142
+ ### Changed
143
+
144
+ - **Breaking: `myOrganizations` pages from an entry's `cursor`** (`@foir/sdk/auth`).
145
+ Each `MyOrganization` it lists carries `cursor`; pass the last one's as
146
+ `after` for the next page. `after` no longer takes an organization's id. A
147
+ user whose role holds `organizations:read` in the project is listed every
148
+ organization of it, suspended ones included.
149
+
150
+ - **`ProviderToken.accessToken` may be `null`**, when `linked` is false.
151
+ - **`holds` reads a grant that names one field** (`@foir/sdk/auth`), as
152
+ `records:read:<model>.<field>` or `records:write:<model>.<field>`, the way the
153
+ platform does. A guarded field is covered by the grant that names it and by
154
+ one over every owner's records of every model, and not by its model's grant,
155
+ so `holds(['records:read'], 'records:read:order.taxId')` is `false`.
156
+ - **Breaking: a key in an older format is refused.** Foir answers `401` to a
157
+ key that begins `pk_`, `sk_` or `tc_` and says it no longer reads that format,
158
+ and the SDK reads a kind only from `foir_pub_`, `foir_srv_` and `foir_adm_`.
159
+ Make a key of its kind in its place: see
160
+ [Credentials](https://docs.foir.dev/credentials).
161
+
111
162
  ## [0.22.0] — 2026-10-15
112
163
 
113
164
  ### Added
package/README.md CHANGED
@@ -104,11 +104,11 @@ present — there is no partial-`select` footgun. `include` adds a relation key,
104
104
  `__typename` that narrow under an exhaustive `switch` with no casts.
105
105
 
106
106
  **The client is shaped by your credential.** The generated `FoirSchema` contains
107
- only what that key's manifest lists, so a read-only `pk_` key has no `create` to
107
+ only what that key's manifest lists, so a read-only publishable key has no `create` to
108
108
  call:
109
109
 
110
110
  ```ts
111
- foir.post.create({ data }); // ✗ compile error under a pk_ key — not in its manifest
111
+ foir.post.create({ data }); // ✗ compile error under a publishable key: not in its manifest
112
112
  ```
113
113
 
114
114
  `generated` is optional. Without it — and without `selections` — the client
@@ -119,7 +119,7 @@ that way; silence it with `announceManifestFallback: false`.
119
119
 
120
120
  Reads that can be shared are sent to `GET /cdn/graphql` with only the
121
121
  persisted-query hash in the URL, so a CDN in front of you can serve the next
122
- visitor without reaching origin. That is a query, on a `pk_` key, with no
122
+ visitor without reaching origin. That is a query, on a publishable key, with no
123
123
  user bearer, on the published channel; everything else is a `POST /graphql`.
124
124
 
125
125
  `foir.raw()` is the escape hatch, and it takes a **GraphQL document** — it
@@ -144,7 +144,7 @@ const foir = createFoirClient({
144
144
 
145
145
  Already have `PLATFORM_API_URL` / `PLATFORM_API_KEY` / `FOIR_PREVIEW` wired for
146
146
  the GraphQL path? `createFoirClientFromEnv(env, { generated })` reuses them,
147
- including the preview `sk_` key and the draft schema channel.
147
+ including the preview key and the draft schema channel.
148
148
 
149
149
  A product built on organizations names the one a request acts in with `orgId`,
150
150
  which the client sends as `X-Org-Id` on every request. This is for the PROJECT
@@ -207,7 +207,7 @@ import { FoirProvider, SignIn, UserButton, useFoirSession } from '@foir/sdk/reac
207
207
 
208
208
  const env = {
209
209
  PLATFORM_API_URL: process.env.NEXT_PUBLIC_PLATFORM_API_URL,
210
- PLATFORM_API_KEY: process.env.NEXT_PUBLIC_PLATFORM_API_KEY, // a publishable pk_ key
210
+ PLATFORM_API_KEY: process.env.NEXT_PUBLIC_PLATFORM_API_KEY, // a publishable key
211
211
  };
212
212
 
213
213
  export function App() {
@@ -241,7 +241,7 @@ backend.
241
241
 
242
242
  ```tsx
243
243
  <FoirProvider
244
- env={{ PLATFORM_API_URL: 'https://api.foir.dev/graphql', PLATFORM_API_KEY: 'pk_…' }}
244
+ env={{ PLATFORM_API_URL: 'https://api.foir.dev/graphql', PLATFORM_API_KEY: 'foir_pub_…' }}
245
245
  hostedSession={{ loginOrigin: 'https://login.acme.com' }}
246
246
  >
247
247
  <App />
@@ -97,10 +97,11 @@ export interface AppInstallConfig {
97
97
  sources: AppBinding[];
98
98
  sinks: AppBinding[];
99
99
  /**
100
- * Why the project cannot send a user an email at the level this install
101
- * sits at, or null when it can. While it is set, a notification that carries
102
- * an email and an opt-in to a category confirmed by email are refused
103
- * (`appUserEmailUnavailableOf`), so say so before one is.
100
+ * Why the app's emails to users cannot link into the app at the level this
101
+ * install sits at, or null when they can. While it is set, a notification
102
+ * whose email links into the app with `{base}` is refused
103
+ * (`appUserEmailUnavailableOf`), so say so before one is. Every other email
104
+ * is sent.
104
105
  */
105
106
  userEmailUnavailable: AppUserEmailUnavailableReason | null;
106
107
  /**
@@ -35,18 +35,20 @@ export declare function appUserUnlinkedOf(error: unknown): {
35
35
  identityProvider: string;
36
36
  } | null;
37
37
  /**
38
- * Why a project cannot send a user an email. `NO_APP_BASE_URL`: no App base
39
- * URL is set for the project, or for the user's organization. A reason
40
- * this build does not know arrives as the platform spelled it.
38
+ * Why a project cannot send a user an email. `NO_APP_BASE_URL`: the email
39
+ * links into the app with `{base}`, and no App base URL is set for the project
40
+ * or for the user's organization. A reason this build does not know arrives as
41
+ * the platform spelled it.
41
42
  */
42
43
  export type AppUserEmailUnavailableReason = 'NO_APP_BASE_URL' | (string & {});
43
44
  /** The reason as the platform sent it, or null when it sent none. */
44
45
  export declare function userEmailUnavailableReasonOf(value: unknown): AppUserEmailUnavailableReason | null;
45
46
  /**
46
47
  * A request refused because it would send a user an email the project
47
- * cannot send, or null for any other failure. Nothing was written and nothing
48
- * was queued. An opt-in to a category confirmed by email is refused this way,
49
- * and so is a notification that carries an email.
48
+ * cannot build, or null for any other failure. Nothing was written and nothing
49
+ * was queued. A notification whose email links into the app with `{base}` is
50
+ * refused this way where no App base URL is set, and so is an opt-in whose
51
+ * confirmation email the project's copy links into the app.
50
52
  *
51
53
  * It is about the project, not the user: the project's team fixes it,
52
54
  * and the same request goes once they have.
@@ -79,6 +79,16 @@ export declare function providerSignInRefusalOf(error: unknown): {
79
79
  code: string;
80
80
  pending?: string;
81
81
  } | null;
82
+ /**
83
+ * Why a sign-in your app's own form sent was refused, as the platform said it:
84
+ * a stable code your app shows its own words for. `password_sign_in_off` from
85
+ * {@link login} or {@link register} where the project, or the app the sign-in
86
+ * came through, takes no password; `code_sign_in_off` from {@link requestOTP}
87
+ * or {@link loginWithOTP} where it takes no code. Null for any other failure.
88
+ */
89
+ export declare function signInRefusalOf(error: unknown): {
90
+ code: string;
91
+ } | null;
82
92
  /**
83
93
  * Finish a provider sign-in that is waiting for the code sent to its address,
84
94
  * connecting the provider to the account that holds the address. See
@@ -121,8 +131,14 @@ export declare function joinWaitlist(env: FoirEnv, email: string): Promise<Waitl
121
131
  * Send a password-reset email. Resolves `true` whether or not the address has
122
132
  * an account: the server answers identically either way, on purpose, so this
123
133
  * endpoint cannot be used to test whether someone has signed up.
134
+ *
135
+ * `clientId` names your app. Where the email's link goes to Foir's hosted
136
+ * reset page (the project has no App base URL), the page then sends the user
137
+ * to your app's initiate login URL to sign in.
124
138
  */
125
- export declare function requestPasswordReset(env: FoirEnv, email: string): Promise<boolean>;
139
+ export declare function requestPasswordReset(env: FoirEnv, email: string, options?: {
140
+ clientId?: string;
141
+ }): Promise<boolean>;
126
142
  /**
127
143
  * Redeem an emailed reset token and set a new password. Returns a fresh session
128
144
  * (resetting a password signs the user in), so pair it with
@@ -148,11 +164,19 @@ export declare function updatePassword(env: FoirEnv, vars: {
148
164
  */
149
165
  export declare function currentUser(env: FoirEnv, options?: AuthOpOptions): Promise<CurrentUser | null>;
150
166
  /**
151
- * The stored upstream provider token for the calling user, refreshed
152
- * transparently by the server when it has expired. `null` when there is no
153
- * user token on the request or none stored for that provider.
167
+ * The stored upstream provider token for the calling user, renewed by the
168
+ * server when it is due: near its stated expiry, or with no expiry stated.
169
+ * `null` when there is no user token on the request; `linked: false` when the
170
+ * user has none stored for that provider.
171
+ *
172
+ * Pass `forceRefresh` after the provider refused the token it was last given:
173
+ * the server renews it then, at most once a minute for one user and provider.
174
+ * `refresh` says what the read did, and `reauthenticate` says when only the
175
+ * user signing in with the provider again mends the connection.
154
176
  */
155
- export declare function myProviderToken(env: FoirEnv, providerKey: string, options?: AuthOpOptions): Promise<ProviderToken | null>;
177
+ export declare function myProviderToken(env: FoirEnv, providerKey: string, options?: AuthOpOptions & {
178
+ forceRefresh?: boolean;
179
+ }): Promise<ProviderToken | null>;
156
180
  /**
157
181
  * End the session server-side, revoking stored provider tokens. This does NOT
158
182
  * clear local session state — {@link signOut} is the pairing that does both.
@@ -275,8 +299,9 @@ export declare function makeRecoveryCodes(env: FoirEnv, elevatedHandle: string,
275
299
  * Answered from the calling user's own memberships: each organization they
276
300
  * hold one in and every organization beneath it, so the list is exactly the
277
301
  * set a request of theirs may name, and a user with no memberships sees an
278
- * empty list rather than a refusal. Requires the user's bearer; it is not the
279
- * project's inventory and cannot be used as one.
302
+ * empty list rather than a refusal. Requires the user's bearer. A user whose
303
+ * role holds `organizations:read` in the project is listed every organization
304
+ * of it instead, suspended ones included.
280
305
  *
281
306
  * A MEMBERSHIP REACHES DOWNWARD. Someone who is a member of an organization may
282
307
  * stand in any organization beneath it without holding a row of their own
@@ -295,8 +320,8 @@ export declare function myOrganizations(env: FoirEnv, vars?: {
295
320
  /**
296
321
  * Found an organization, as its first member.
297
322
  *
298
- * The caller decides the NAME and, optionally, where it hangs — and nothing
299
- * else. The founder is the user behind the bearer, the id is minted
323
+ * The caller decides the NAME and, optionally, where it hangs and where it
324
+ * lives — and nothing else. The founder is the user behind the bearer, the id is minted
300
325
  * server-side the way a project's and a tenant's are, and the role they hold in
301
326
  * the new organization is the one the PROJECT named in its settings
302
327
  * (`organizationSignupRole`). The server creates the organization and that one
@@ -310,6 +335,12 @@ export declare function myOrganizations(env: FoirEnv, vars?: {
310
335
  * not-found. Omit it to found a root, which is what every registration did
311
336
  * before organizations nested.
312
337
  *
338
+ * `appBaseUrl` is where the organization's app lives, so an app that finds its
339
+ * organization from the page address (`orgUrl`) finds this one at once. It is
340
+ * held to the rule every organization address is: under the project's own app
341
+ * address or a domain the project verified, and not one another organization
342
+ * of the project holds.
343
+ *
313
344
  * Exists on the wire only while the project's `organizationSignupEnabled` setting
314
345
  * is on; a project that never opted in has no such field, and this refuses
315
346
  * naming it. Returns the same entry `myOrganizations` would then list, role
@@ -318,6 +349,7 @@ export declare function myOrganizations(env: FoirEnv, vars?: {
318
349
  export declare function registerOrganization(env: FoirEnv, vars: {
319
350
  name: string;
320
351
  parentId?: string;
352
+ appBaseUrl?: string;
321
353
  }, options?: AuthOpOptions): Promise<MyOrganization>;
322
354
  /**
323
355
  * Persist an auth result to the session; returns the signed-in user.
@@ -1,12 +1,13 @@
1
1
  export type GrantReach = 'own' | 'named' | 'all';
2
+ export type GrantTarget = 'none' | 'model' | 'operation';
2
3
  /**
3
- * One permission: its name, whether a grant of it may name a target, the
4
- * reaches it may be granted at (none: only its bare form exists), and the
5
- * grants a holder of it also holds.
4
+ * One permission: its name, what a grant of it may name, the reaches it may be
5
+ * granted at (none: only its bare form exists), and the grants a holder of it
6
+ * also holds.
6
7
  */
7
8
  export type GrantTableRow = readonly [
8
9
  name: string,
9
- takesTarget: boolean,
10
+ target: GrantTarget,
10
11
  reaches: readonly GrantReach[],
11
12
  implies: readonly string[]
12
13
  ];
@@ -127,3 +127,27 @@ export type HostedSessionResult =
127
127
  * returning `null` for everything that is not a success.
128
128
  */
129
129
  export declare function fetchHostedSession(request: Request, config: HostedLoginConfig): Promise<HostedSessionResult>;
130
+ /**
131
+ * What the hosted login said beside the code when it sent someone back to your
132
+ * app. A sign-in it refused carries a stable code (`foir_error`); one that
133
+ * connected a provider to the person's account on the way in names the
134
+ * provider (`foir_connected`). Each comes with a sentence your app can show as
135
+ * it is, so an app that does nothing more still shows words and never a code.
136
+ * Show your own words for a code by switching on `code`.
137
+ */
138
+ export type HostedSignInNotice = {
139
+ kind: 'refused';
140
+ code: string;
141
+ message: string;
142
+ } | {
143
+ kind: 'connected';
144
+ provider: string;
145
+ message: string;
146
+ };
147
+ /**
148
+ * Reads the notice off the address the hosted login sent the browser back to:
149
+ * your callback URL, its search string, or its parameters. `names` maps a
150
+ * provider's key to the name to show (from `getAuthProviders`); without it the
151
+ * key is shown. Null when the hosted login said nothing.
152
+ */
153
+ export declare function hostedSignInNoticeOf(location: string | URL | URLSearchParams, names?: Record<string, string>): HostedSignInNotice | null;
@@ -10,15 +10,15 @@
10
10
  * the caller's own manifest advertises, through the same op transport
11
11
  * `@foir/sdk/data` uses, so `graphql` is not on this module's path at all.
12
12
  */
13
- export { login, register, requestOTP, loginWithOTP, refreshTokens, loginWithProvider, providerCallback, providerVerifyOTP, providerSignInRefusalOf, confirmProviderLink, resendProviderLinkCode, getAuthProviders, getAuthConfig, getDefaultAuthProvider, signupContext, joinWaitlist, requestPasswordReset, resetPassword, updatePassword, verifyEmail, currentUser, myProviderToken, logout, deleteMyAccount, updateMyProfile, myAuthorizedApps, revokeAuthorizedApp, mySessions, endMySession, signOutEverywhere, createPersonalAccessToken, mySignInMethods, connectSignInMethod, disconnectSignInMethod, myFactors, beginStepUp, completeStepUp, beginFactorEnrollment, completeFactorEnrollment, removeFactor, makeRecoveryCodes, myOrganizations, registerOrganization, saveSession, clearSession, getSessionToken, getSessionUser, signInWithPassword, signUpWithPassword, signInWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, } from './auth';
13
+ export { login, register, requestOTP, loginWithOTP, refreshTokens, loginWithProvider, providerCallback, providerVerifyOTP, providerSignInRefusalOf, signInRefusalOf, confirmProviderLink, resendProviderLinkCode, getAuthProviders, getAuthConfig, getDefaultAuthProvider, signupContext, joinWaitlist, requestPasswordReset, resetPassword, updatePassword, verifyEmail, currentUser, myProviderToken, logout, deleteMyAccount, updateMyProfile, myAuthorizedApps, revokeAuthorizedApp, mySessions, endMySession, signOutEverywhere, createPersonalAccessToken, mySignInMethods, connectSignInMethod, disconnectSignInMethod, myFactors, beginStepUp, completeStepUp, beginFactorEnrollment, completeFactorEnrollment, removeFactor, makeRecoveryCodes, myOrganizations, registerOrganization, saveSession, clearSession, getSessionToken, getSessionUser, signInWithPassword, signUpWithPassword, signInWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, } from './auth';
14
14
  export type { FoirAuthTokens, FoirUser, SessionAdapter, RequireAuthOptions, } from './auth';
15
15
  export { userAccess, getSessionAccess, hasRole, hasScope, requireRole, requireScope, FoirAccessDeniedError, } from './access';
16
16
  export type { FoirAccess, FoirAccessDenial } from './access';
17
17
  export { FoirAuthDowngradedError, FoirSessionExpiredError } from '../graphql/foir-request';
18
18
  export { AUTH_OPS } from './ops';
19
19
  export type { AuthOpName, AuthOpOptions } from './ops';
20
- export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession } from './hosted';
21
- export type { HostedLoginConfig, HostedSession, HostedSessionResult } from './hosted';
20
+ export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession, hostedSignInNoticeOf } from './hosted';
21
+ export type { HostedLoginConfig, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
22
22
  export { createFoirSessionClient } from './session-client';
23
23
  export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
24
- export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, WaitlistJoinResult, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, DeleteAccountResult, MyOrganization, MyProfile, MyProfileChange, AuthorizedApp, MySession, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
24
+ export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, WaitlistJoinResult, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, ProviderTokenRefresh, DeleteAccountResult, MyOrganization, MyProfile, MyProfileChange, AuthorizedApp, MySession, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
@@ -123,18 +123,33 @@ export interface CurrentUser {
123
123
  /** ISO-8601. */
124
124
  createdAt: string;
125
125
  }
126
+ /** What a provider token read did about the token's freshness. */
127
+ export type ProviderTokenRefresh = 'NOT_ATTEMPTED' | 'REFRESHED' | 'FAILED' | 'HELD_BACK';
126
128
  /**
127
- * A stored upstream provider token for the calling user. `null` when there
128
- * is no user token on the request, or none stored for that provider.
129
+ * A stored upstream provider token for the calling user. `myProviderToken`
130
+ * answers `null` when there is no user token on the request, and
131
+ * `linked: false` when the user has none stored for that provider.
129
132
  */
130
133
  export interface ProviderToken {
131
134
  providerKey: string;
132
- accessToken: string;
135
+ /** False when the user has no token for the provider; every token field is then null. */
136
+ linked: boolean;
137
+ accessToken: string | null;
133
138
  refreshToken: string | null;
134
139
  /** ISO-8601. */
135
140
  expiresAt: string | null;
136
141
  /** Space-separated provider scopes, as the provider issued them. */
137
142
  scopes: string | null;
143
+ /**
144
+ * What the read did: `REFRESHED` renewed it, `FAILED` tried and served the
145
+ * stored token, `HELD_BACK` had tried within the last minute, and
146
+ * `NOT_ATTEMPTED` found it fresh or had nothing to renew it with.
147
+ */
148
+ refresh: ProviderTokenRefresh | null;
149
+ /** Where a failed refresh failed, such as `"refresh grant"`. */
150
+ refreshFailure: string | null;
151
+ /** Only the user signing in with the provider again mends the connection. */
152
+ reauthenticate: boolean;
138
153
  }
139
154
  /** What a self-serve account deletion removed. */
140
155
  export interface DeleteAccountResult {
@@ -205,7 +220,10 @@ export interface MyFactor {
205
220
  kind: string;
206
221
  /** What {@link removeFactor} takes. */
207
222
  id: string;
208
- /** A security key's or passkey's name. */
223
+ /**
224
+ * A security key's or passkey's name. A passkey made on the project's own
225
+ * login domain is named by that domain, the only one it signs in on.
226
+ */
209
227
  name: string | null;
210
228
  /** ISO-8601. */
211
229
  addedAt: string | null;
@@ -314,6 +332,12 @@ export interface MyOrganization {
314
332
  * sees part of what the next request is allowed and not the whole of it.
315
333
  */
316
334
  role: MyOrganizationRole | null;
335
+ /**
336
+ * Where the list goes on from: pass the last entry's cursor as `after` to
337
+ * read the next page. Null on an entry no list gave, such as the one
338
+ * `registerOrganization` returns.
339
+ */
340
+ cursor: string | null;
317
341
  }
318
342
  /** A role as `myOrganizations` reports it: enough to match a policy and to render. */
319
343
  export interface MyOrganizationRole {