@sema-agent/settings-schema 13.0.0 → 13.0.1

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.
@@ -0,0 +1,357 @@
1
+ # OpenAPI 3 contract for the registry /api/v1/auth/* face ONLY (REGISTRY-REBUILD-DESIGN §2, M1 shipped).
2
+ # Source of truth for the shapes is src/api/auth.ts (zod) — this document mirrors it for docs/third-party
3
+ # integration. When design §3's scripts/gen-openapi.mjs lands, this file becomes generated; until then it
4
+ # is maintained by hand next to the zod module. Registry JWKS (the auth-bridge key source) is included
5
+ # because the §5 bridge depends on it; the rest of the registry API is deliberately NOT documented here.
6
+ openapi: 3.0.3
7
+ info:
8
+ title: web-admin auth API
9
+ description: >
10
+ RFC 8628 device authorization grant + rotating refresh tokens + logout + browser consent,
11
+ plus the public JWKS a worker uses to verify the minted access JWTs (auth bridge, design §5).
12
+ Contract frozen in @sema-agent/settings-schema `api/auth` / `api/auth-bridge`.
13
+ version: 0.2.0
14
+ paths:
15
+ /api/v1/auth/device/code:
16
+ post:
17
+ operationId: deviceCode
18
+ summary: Open an RFC 8628 device-authorization handshake
19
+ description: >
20
+ Unauthenticated by design — the client has no credentials yet. Returns the polling secret
21
+ (device_code, hash-persisted server-side, 15min TTL) and the human approval code (user_code).
22
+ requestBody:
23
+ required: false
24
+ content:
25
+ application/json:
26
+ schema: { $ref: "#/components/schemas/DeviceCodeRequest" }
27
+ application/x-www-form-urlencoded:
28
+ schema: { $ref: "#/components/schemas/DeviceCodeRequest" }
29
+ responses:
30
+ "200":
31
+ description: Handshake opened.
32
+ content:
33
+ application/json:
34
+ schema: { $ref: "#/components/schemas/DeviceCodeResponse" }
35
+ "400":
36
+ $ref: "#/components/responses/OAuthError"
37
+ "503":
38
+ description: SSO not configured on this registry (error=server_error).
39
+ content:
40
+ application/json:
41
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
42
+ /api/v1/auth/device/token:
43
+ post:
44
+ operationId: deviceToken
45
+ summary: Poll for tokens (RFC 8628 §3.4)
46
+ description: >
47
+ The client polls every `interval` seconds. 400 error codes while pending:
48
+ authorization_pending (keep polling) / slow_down (add 5s to the interval) /
49
+ expired_token (restart the flow) / access_denied (user denied — stop).
50
+ On approval the handshake is consumed atomically — the 200 is returned exactly once.
51
+ requestBody:
52
+ required: true
53
+ content:
54
+ application/json:
55
+ schema: { $ref: "#/components/schemas/DeviceTokenRequest" }
56
+ application/x-www-form-urlencoded:
57
+ schema: { $ref: "#/components/schemas/DeviceTokenRequest" }
58
+ responses:
59
+ "200":
60
+ description: Approved — tokens minted (once).
61
+ content:
62
+ application/json:
63
+ schema: { $ref: "#/components/schemas/TokenGrantResponse" }
64
+ "400":
65
+ $ref: "#/components/responses/OAuthError"
66
+ "503":
67
+ description: SSO not configured (error=server_error).
68
+ content:
69
+ application/json:
70
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
71
+ /api/v1/auth/token/refresh:
72
+ post:
73
+ operationId: tokenRefresh
74
+ summary: Rotate a refresh token (silent access-token renewal)
75
+ description: >
76
+ Strict single-use rotation — verify, revoke the old grant, issue a new grant + new access JWT.
77
+ A replayed or raced old refresh token answers invalid_grant. Clients MUST persist the returned
78
+ refresh_token before discarding the old one.
79
+ requestBody:
80
+ required: true
81
+ content:
82
+ application/json:
83
+ schema: { $ref: "#/components/schemas/TokenRefreshRequest" }
84
+ application/x-www-form-urlencoded:
85
+ schema: { $ref: "#/components/schemas/TokenRefreshRequest" }
86
+ responses:
87
+ "200":
88
+ description: Rotated — same shape as the device token success.
89
+ content:
90
+ application/json:
91
+ schema: { $ref: "#/components/schemas/TokenGrantResponse" }
92
+ "400":
93
+ description: invalid_grant (unknown/expired/revoked/replayed — no detail) or invalid_request.
94
+ content:
95
+ application/json:
96
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
97
+ "503":
98
+ description: SSO not configured (error=server_error).
99
+ content:
100
+ application/json:
101
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
102
+ /api/v1/auth/logout:
103
+ post:
104
+ operationId: logout
105
+ summary: Revoke a refresh grant (CLI logout)
106
+ description: >
107
+ Always 200 {ok:true} whether or not the token matched a live grant (RFC 7009 — no token-existence
108
+ disclosure). The already-minted access JWT stays valid until its 1h exp; revocation kills the
109
+ renewal path. 400 only for a malformed request.
110
+ requestBody:
111
+ required: true
112
+ content:
113
+ application/json:
114
+ schema: { $ref: "#/components/schemas/LogoutRequest" }
115
+ application/x-www-form-urlencoded:
116
+ schema: { $ref: "#/components/schemas/LogoutRequest" }
117
+ responses:
118
+ "200":
119
+ description: Revoked (or no-op).
120
+ content:
121
+ application/json:
122
+ schema: { $ref: "#/components/schemas/LogoutResponse" }
123
+ "400":
124
+ $ref: "#/components/responses/OAuthError"
125
+ /api/v1/auth/device/approve:
126
+ get:
127
+ operationId: deviceApproveLookup
128
+ summary: Consent-screen lookup (does this user_code name a live handshake?)
129
+ description: Session-cookie-gated (the /activate page). Does NOT approve.
130
+ security:
131
+ - sessionCookie: []
132
+ parameters:
133
+ - name: user_code
134
+ in: query
135
+ required: true
136
+ schema: { type: string }
137
+ description: Case-insensitive; dashes/spaces optional (normalized server-side to XXXX-XXXX).
138
+ responses:
139
+ "200":
140
+ description: Live handshake found.
141
+ content:
142
+ application/json:
143
+ schema: { $ref: "#/components/schemas/DeviceApproveLookupResponse" }
144
+ "400":
145
+ description: invalid_request (implausible code) or invalid_grant (unknown/expired).
146
+ content:
147
+ application/json:
148
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
149
+ "401":
150
+ description: Not signed in (no valid session cookie).
151
+ post:
152
+ operationId: deviceApprove
153
+ summary: Approve a pending handshake with the SESSION actor's identity
154
+ description: >
155
+ JSON-only content type (cross-site-request defence — a cross-site form cannot send
156
+ application/json without a CORS preflight). The approving principal/role come from the session
157
+ cookie exclusively, never from the body.
158
+ security:
159
+ - sessionCookie: []
160
+ requestBody:
161
+ required: true
162
+ content:
163
+ application/json:
164
+ schema: { $ref: "#/components/schemas/DeviceApproveRequest" }
165
+ responses:
166
+ "200":
167
+ description: Approved — the polling client's next device/token call succeeds.
168
+ content:
169
+ application/json:
170
+ schema: { $ref: "#/components/schemas/DeviceApproveResponse" }
171
+ "400":
172
+ description: invalid_request (bad body/content-type) or invalid_grant (unknown/expired/already-approved).
173
+ content:
174
+ application/json:
175
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
176
+ "401":
177
+ description: Not signed in.
178
+ /api/auth/sso/jwks:
179
+ get:
180
+ operationId: jwks
181
+ summary: Public JWKS — the auth-bridge key source (design §5)
182
+ description: >
183
+ Public verification key(s) ONLY (RFC 7517 §5 — no auth). Workers verify registry-minted user
184
+ access JWTs against these keys per the auth-bridge rules (RS256 only, kid match, kid-miss single
185
+ forced refresh with ≥60s throttle, 24h offline grace then fail-closed). `{keys: []}` is a valid
186
+ response (SSO disabled) — every token then fails verification.
187
+ responses:
188
+ "200":
189
+ description: The key set (may be empty).
190
+ content:
191
+ application/json:
192
+ schema: { $ref: "#/components/schemas/WebAdminJwks" }
193
+ components:
194
+ securitySchemes:
195
+ sessionCookie:
196
+ type: apiKey
197
+ in: cookie
198
+ name: session
199
+ description: The registry's existing HMAC-signed browser session (web login).
200
+ responses:
201
+ OAuthError:
202
+ description: RFC 6749/8628 error envelope — branch on `error`; `error_description` is diagnostic only.
203
+ content:
204
+ application/json:
205
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
206
+ schemas:
207
+ OAuthErrorResponse:
208
+ type: object
209
+ required: [error]
210
+ properties:
211
+ error:
212
+ type: string
213
+ enum:
214
+ - authorization_pending
215
+ - slow_down
216
+ - expired_token
217
+ - access_denied
218
+ - invalid_request
219
+ - unsupported_grant_type
220
+ - invalid_grant
221
+ - server_error
222
+ error_description:
223
+ type: string
224
+ DeviceCodeRequest:
225
+ type: object
226
+ properties:
227
+ client_name:
228
+ type: string
229
+ maxLength: 200
230
+ description: NON-secret display hint for the consent screen; falls back to the User-Agent.
231
+ DeviceCodeResponse:
232
+ type: object
233
+ required: [device_code, user_code, verification_uri, expires_in, interval]
234
+ properties:
235
+ device_code:
236
+ type: string
237
+ description: ≥256-bit opaque polling secret — returned once, hash-persisted server-side. Never log.
238
+ user_code:
239
+ type: string
240
+ description: Human approval code, "XXXX-XXXX" (base-20, no vowels/lookalikes).
241
+ verification_uri:
242
+ type: string
243
+ description: The /activate consent page.
244
+ verification_uri_complete:
245
+ type: string
246
+ description: verification_uri with ?user_code= pre-filled. OPTIONAL (RFC 8628 §3.2) — absent ⇒ show verification_uri + user_code; never an empty string.
247
+ expires_in:
248
+ type: integer
249
+ description: Seconds until the handshake expires (900).
250
+ interval:
251
+ type: integer
252
+ description: Minimum seconds between polls (5).
253
+ DeviceTokenRequest:
254
+ type: object
255
+ required: [grant_type, device_code]
256
+ properties:
257
+ grant_type:
258
+ type: string
259
+ enum: ["urn:ietf:params:oauth:grant-type:device_code"]
260
+ device_code:
261
+ type: string
262
+ TokenGrantResponse:
263
+ type: object
264
+ required: [access_token, refresh_token, token_type, expires_in]
265
+ properties:
266
+ access_token:
267
+ type: string
268
+ description: "RS256 JWT — claims: sub (principal), iss, iat, exp, role; optional aud. 1h TTL."
269
+ refresh_token:
270
+ type: string
271
+ description: NEW opaque rotating refresh token (30d sliding) — replaces the previous one.
272
+ token_type:
273
+ type: string
274
+ enum: [Bearer]
275
+ expires_in:
276
+ type: integer
277
+ description: Access-token lifetime in seconds (3600).
278
+ TokenRefreshRequest:
279
+ type: object
280
+ required: [refresh_token]
281
+ properties:
282
+ refresh_token:
283
+ type: string
284
+ LogoutRequest:
285
+ type: object
286
+ required: [refresh_token]
287
+ properties:
288
+ refresh_token:
289
+ type: string
290
+ LogoutResponse:
291
+ type: object
292
+ required: [ok]
293
+ properties:
294
+ ok:
295
+ type: boolean
296
+ enum: [true]
297
+ DeviceApproveLookupResponse:
298
+ type: object
299
+ required: [user_code, status, client, expires_at]
300
+ properties:
301
+ user_code:
302
+ type: string
303
+ status:
304
+ type: string
305
+ enum: [pending, approved, denied]
306
+ description: denied is contract-reserved (explicit deny button); M1 ships pending|approved.
307
+ client:
308
+ type: string
309
+ nullable: true
310
+ description: The client_name/User-Agent hint from device/code; null when none.
311
+ expires_at:
312
+ type: number
313
+ description: Handshake expiry, epoch MILLISECONDS.
314
+ DeviceApproveRequest:
315
+ type: object
316
+ required: [user_code]
317
+ properties:
318
+ user_code:
319
+ type: string
320
+ description: Case-insensitive; dashes/spaces optional.
321
+ DeviceApproveResponse:
322
+ type: object
323
+ required: [ok, user_code]
324
+ properties:
325
+ ok:
326
+ type: boolean
327
+ enum: [true]
328
+ user_code:
329
+ type: string
330
+ WebAdminJwks:
331
+ type: object
332
+ required: [keys]
333
+ properties:
334
+ keys:
335
+ type: array
336
+ items:
337
+ type: object
338
+ required: [kty, n, e, kid]
339
+ properties:
340
+ kty:
341
+ type: string
342
+ enum: [RSA]
343
+ n:
344
+ type: string
345
+ description: modulus, base64url
346
+ e:
347
+ type: string
348
+ description: exponent, base64url
349
+ kid:
350
+ type: string
351
+ description: RFC 7638 thumbprint — matches the JWT header kid.
352
+ use:
353
+ type: string
354
+ enum: [sig]
355
+ alg:
356
+ type: string
357
+ enum: [RS256]
@@ -0,0 +1,281 @@
1
+ # OpenAPI 3 contract for the registry multi-tenant SCOPE face ONLY (/api/v1/scopes* + /api/v1/auth/scope).
2
+ # Source of truth for the shapes AND the decision semantics is src/api/scopes.ts (zod + pure functions:
3
+ # resolveTokenScope / canSwitchScope / effectiveScopeRole / readTokenScope) — this document mirrors it for
4
+ # docs/third-party integration, exactly like docs/openapi-auth.yaml mirrors src/api/auth.ts. Maintained by
5
+ # hand next to the zod module until gen-openapi lands. The auth face lives in openapi-auth.yaml.
6
+ openapi: 3.0.3
7
+ info:
8
+ title: web-admin scopes API
9
+ description: >
10
+ Multi-tenant scopes: a scope is a tenant boundary {id,name,createdAt} with per-scope memberships
11
+ {scopeId,principal,role} (the existing viewer<editor<publisher<admin ladder — the same principal may
12
+ hold different roles in different scopes). The reserved scope "global" always exists and is the
13
+ default; every access JWT carries a `scope` claim, and a token without one is refused (401) — it is not read as "global".
14
+ Refresh tokens are NOT scope-bound (a refresh re-mints an access token with the claim).
15
+ Instance-level users.role is unchanged; an instance admin administers all scopes and their effective
16
+ role in any scope is always "admin". Contract frozen in @sema-agent/settings-schema `api/scopes`.
17
+ version: 0.3.0
18
+ paths:
19
+ /api/v1/scopes:
20
+ get:
21
+ operationId: listScopes
22
+ summary: List the caller's scopes with their per-scope effective role
23
+ description: >
24
+ Auth: bearer access JWT (any scope). Returns the scopes the caller can act in, with the caller's
25
+ EFFECTIVE role in each (effectiveScopeRole — an instance admin sees ALL scopes with role "admin").
26
+ security:
27
+ - bearerJwt: []
28
+ responses:
29
+ "200":
30
+ description: The caller's scopes.
31
+ content:
32
+ application/json:
33
+ schema: { $ref: "#/components/schemas/ScopesListResponse" }
34
+ "401":
35
+ description: Missing/invalid bearer token.
36
+ post:
37
+ operationId: createScope
38
+ summary: Create a scope (instance admin only)
39
+ description: >
40
+ The reserved id "global" is rejected as invalid_request — the default scope pre-exists and is
41
+ neither creatable nor deletable. Duplicate id → invalid_request.
42
+ security:
43
+ - bearerJwt: []
44
+ requestBody:
45
+ required: true
46
+ content:
47
+ application/json:
48
+ schema: { $ref: "#/components/schemas/ScopeCreateRequest" }
49
+ responses:
50
+ "200":
51
+ description: Created.
52
+ content:
53
+ application/json:
54
+ schema: { $ref: "#/components/schemas/Scope" }
55
+ "400":
56
+ $ref: "#/components/responses/OAuthError"
57
+ "401":
58
+ description: Missing/invalid bearer token.
59
+ "403":
60
+ description: Caller is not an instance admin (error=access_denied).
61
+ content:
62
+ application/json:
63
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
64
+ /api/v1/auth/scope:
65
+ post:
66
+ operationId: switchScope
67
+ summary: Mint a new access token bound to another scope
68
+ description: >
69
+ Auth: bearer access JWT (any scope). Allowed iff canSwitchScope — the caller is a MEMBER of the
70
+ target scope (any role) or an instance admin (any scope, effective role "admin"). "global" gets no
71
+ special case: a non-member non-admin cannot switch to it. Success mints a FRESH access JWT whose
72
+ `scope` claim = the requested scope and `role` claim = the caller's effective role there
73
+ (effectiveScopeRole); the refresh grant is untouched and NO refresh_token is returned — on refresh
74
+ the scope re-resolves via resolveTokenScope and the client re-switches if needed. Errors are
75
+ OAuth-style: invalid_request (malformed) / access_denied (not a member, not an admin).
76
+ security:
77
+ - bearerJwt: []
78
+ requestBody:
79
+ required: true
80
+ content:
81
+ application/json:
82
+ schema: { $ref: "#/components/schemas/ScopeSwitchRequest" }
83
+ responses:
84
+ "200":
85
+ description: New scoped access token minted.
86
+ content:
87
+ application/json:
88
+ schema: { $ref: "#/components/schemas/ScopeSwitchResponse" }
89
+ "400":
90
+ $ref: "#/components/responses/OAuthError"
91
+ "401":
92
+ description: Missing/invalid bearer token.
93
+ "403":
94
+ description: Not a member of the target scope and not an instance admin (error=access_denied).
95
+ content:
96
+ application/json:
97
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
98
+ /api/v1/scopes/{id}/members:
99
+ parameters:
100
+ - name: id
101
+ in: path
102
+ required: true
103
+ schema: { $ref: "#/components/schemas/ScopeIdString" }
104
+ description: The scope id (slug).
105
+ put:
106
+ operationId: upsertScopeMember
107
+ summary: Upsert a member's role in a scope (instance admin only)
108
+ description: Creates or replaces the {scopeId, principal} membership row with the given role.
109
+ security:
110
+ - bearerJwt: []
111
+ requestBody:
112
+ required: true
113
+ content:
114
+ application/json:
115
+ schema: { $ref: "#/components/schemas/ScopeMemberUpsertRequest" }
116
+ responses:
117
+ "200":
118
+ description: The stored membership row.
119
+ content:
120
+ application/json:
121
+ schema: { $ref: "#/components/schemas/ScopeMember" }
122
+ "400":
123
+ $ref: "#/components/responses/OAuthError"
124
+ "401":
125
+ description: Missing/invalid bearer token.
126
+ "403":
127
+ description: Caller is not an instance admin (error=access_denied).
128
+ content:
129
+ application/json:
130
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
131
+ "404":
132
+ description: Unknown scope id.
133
+ delete:
134
+ operationId: deleteScopeMember
135
+ summary: Remove a member from a scope (instance admin only)
136
+ description: Idempotent — removing a non-member still answers 200 {ok:true}.
137
+ security:
138
+ - bearerJwt: []
139
+ requestBody:
140
+ required: true
141
+ content:
142
+ application/json:
143
+ schema: { $ref: "#/components/schemas/ScopeMemberDeleteRequest" }
144
+ responses:
145
+ "200":
146
+ description: Removed (or no-op).
147
+ content:
148
+ application/json:
149
+ schema: { $ref: "#/components/schemas/ScopeMemberDeleteResponse" }
150
+ "400":
151
+ $ref: "#/components/responses/OAuthError"
152
+ "401":
153
+ description: Missing/invalid bearer token.
154
+ "403":
155
+ description: Caller is not an instance admin (error=access_denied).
156
+ content:
157
+ application/json:
158
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
159
+ "404":
160
+ description: Unknown scope id.
161
+ components:
162
+ securitySchemes:
163
+ bearerJwt:
164
+ type: http
165
+ scheme: bearer
166
+ bearerFormat: JWT
167
+ description: >
168
+ web-admin-minted RS256 access JWT (api/auth). Carries the `scope` claim (api/scopes); a token
169
+ without it is refused (readTokenScope ⇒ scope_claim_missing).
170
+ responses:
171
+ OAuthError:
172
+ description: OAuth-style error envelope — branch on `error`; `error_description` is diagnostic only.
173
+ content:
174
+ application/json:
175
+ schema: { $ref: "#/components/schemas/OAuthErrorResponse" }
176
+ schemas:
177
+ OAuthErrorResponse:
178
+ type: object
179
+ required: [error]
180
+ properties:
181
+ error:
182
+ type: string
183
+ enum:
184
+ - invalid_request
185
+ - access_denied
186
+ - invalid_grant
187
+ - server_error
188
+ error_description:
189
+ type: string
190
+ ScopeIdString:
191
+ type: string
192
+ pattern: "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
193
+ description: Slug, 1-64 chars, lowercase alnum + inner dashes. "global" is the reserved default scope.
194
+ ScopeRole:
195
+ type: string
196
+ enum: [viewer, editor, publisher, admin]
197
+ description: The existing 4-level RBAC ladder, applied PER SCOPE.
198
+ Scope:
199
+ type: object
200
+ required: [id, name, createdAt]
201
+ properties:
202
+ id: { $ref: "#/components/schemas/ScopeIdString" }
203
+ name:
204
+ type: string
205
+ maxLength: 200
206
+ createdAt:
207
+ type: number
208
+ description: Creation time, epoch milliseconds.
209
+ ScopeMember:
210
+ type: object
211
+ required: [scopeId, principal, role]
212
+ properties:
213
+ scopeId: { $ref: "#/components/schemas/ScopeIdString" }
214
+ principal:
215
+ type: string
216
+ description: Same principal string as the JWT sub / x-agent-principal pipeline.
217
+ role: { $ref: "#/components/schemas/ScopeRole" }
218
+ ScopesListResponse:
219
+ type: object
220
+ required: [scopes]
221
+ properties:
222
+ scopes:
223
+ type: array
224
+ items:
225
+ type: object
226
+ required: [id, name, role]
227
+ properties:
228
+ id: { $ref: "#/components/schemas/ScopeIdString" }
229
+ name: { type: string }
230
+ role:
231
+ allOf: [{ $ref: "#/components/schemas/ScopeRole" }]
232
+ description: The CALLER's effective role in this scope (instance admin ⇒ "admin").
233
+ ScopeSwitchRequest:
234
+ type: object
235
+ required: [scope]
236
+ properties:
237
+ scope: { $ref: "#/components/schemas/ScopeIdString" }
238
+ ScopeSwitchResponse:
239
+ type: object
240
+ required: [access_token, token_type, expires_in, scope, role]
241
+ properties:
242
+ access_token:
243
+ type: string
244
+ description: Fresh RS256 JWT with the `scope` claim set. Never log.
245
+ token_type:
246
+ type: string
247
+ enum: ["Bearer"]
248
+ expires_in:
249
+ type: integer
250
+ description: Access-token lifetime in seconds (3600 — same mint as api/auth).
251
+ scope: { $ref: "#/components/schemas/ScopeIdString" }
252
+ role: { $ref: "#/components/schemas/ScopeRole" }
253
+ ScopeCreateRequest:
254
+ type: object
255
+ required: [id, name]
256
+ properties:
257
+ id:
258
+ allOf: [{ $ref: "#/components/schemas/ScopeIdString" }]
259
+ description: Must not be a reserved id ("global").
260
+ name:
261
+ type: string
262
+ minLength: 1
263
+ maxLength: 200
264
+ ScopeMemberUpsertRequest:
265
+ type: object
266
+ required: [principal, role]
267
+ properties:
268
+ principal: { type: string }
269
+ role: { $ref: "#/components/schemas/ScopeRole" }
270
+ ScopeMemberDeleteRequest:
271
+ type: object
272
+ required: [principal]
273
+ properties:
274
+ principal: { type: string }
275
+ ScopeMemberDeleteResponse:
276
+ type: object
277
+ required: [ok]
278
+ properties:
279
+ ok:
280
+ type: boolean
281
+ enum: [true]