@stapel/workspaces-react 0.6.0 → 0.8.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/api/generated/schema.d.ts +774 -7
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +104 -7
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/api/workspacesApi.d.ts +55 -1
  7. package/dist/api/workspacesApi.d.ts.map +1 -1
  8. package/dist/api/workspacesApi.js +23 -0
  9. package/dist/api/workspacesApi.js.map +1 -1
  10. package/dist/default/InviteAcceptPage.d.ts +28 -0
  11. package/dist/default/InviteAcceptPage.d.ts.map +1 -0
  12. package/dist/default/InviteAcceptPage.js +141 -0
  13. package/dist/default/InviteAcceptPage.js.map +1 -0
  14. package/dist/default/MembersManager.d.ts +4 -4
  15. package/dist/default/MembersManager.d.ts.map +1 -1
  16. package/dist/default/MembersManager.js +61 -56
  17. package/dist/default/MembersManager.js.map +1 -1
  18. package/dist/default/index.d.ts +2 -0
  19. package/dist/default/index.d.ts.map +1 -1
  20. package/dist/default/index.js +1 -0
  21. package/dist/default/index.js.map +1 -1
  22. package/dist/flows/inviteAcceptFlow.d.ts +155 -0
  23. package/dist/flows/inviteAcceptFlow.d.ts.map +1 -0
  24. package/dist/flows/inviteAcceptFlow.js +126 -0
  25. package/dist/flows/inviteAcceptFlow.js.map +1 -0
  26. package/dist/headless/Can.d.ts +40 -0
  27. package/dist/headless/Can.d.ts.map +1 -0
  28. package/dist/headless/Can.js +30 -0
  29. package/dist/headless/Can.js.map +1 -0
  30. package/dist/headless/InviteAcceptFlow.d.ts +44 -0
  31. package/dist/headless/InviteAcceptFlow.d.ts.map +1 -0
  32. package/dist/headless/InviteAcceptFlow.js +67 -0
  33. package/dist/headless/InviteAcceptFlow.js.map +1 -0
  34. package/dist/headless/RoleSelect.d.ts +41 -0
  35. package/dist/headless/RoleSelect.d.ts.map +1 -0
  36. package/dist/headless/RoleSelect.js +34 -0
  37. package/dist/headless/RoleSelect.js.map +1 -0
  38. package/dist/i18n/generated/errors.gen.d.ts +48 -0
  39. package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
  40. package/dist/i18n/generated/errors.gen.js +24 -0
  41. package/dist/i18n/generated/errors.gen.js.map +1 -1
  42. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
  43. package/dist/i18n/generated/errors.ru.gen.js +8 -0
  44. package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
  45. package/dist/i18n/keys.d.ts +28 -0
  46. package/dist/i18n/keys.d.ts.map +1 -1
  47. package/dist/i18n/keys.js +64 -0
  48. package/dist/i18n/keys.js.map +1 -1
  49. package/dist/i18n/ru.d.ts.map +1 -1
  50. package/dist/i18n/ru.js +30 -0
  51. package/dist/i18n/ru.js.map +1 -1
  52. package/dist/index.d.ts +17 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +10 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/model/capabilities.d.ts +21 -0
  57. package/dist/model/capabilities.d.ts.map +1 -0
  58. package/dist/model/capabilities.js +32 -0
  59. package/dist/model/capabilities.js.map +1 -0
  60. package/dist/model/emailMask.d.ts +22 -0
  61. package/dist/model/emailMask.d.ts.map +1 -0
  62. package/dist/model/emailMask.js +36 -0
  63. package/dist/model/emailMask.js.map +1 -0
  64. package/dist/model/mutations.d.ts +107 -3
  65. package/dist/model/mutations.d.ts.map +1 -1
  66. package/dist/model/mutations.js +190 -1
  67. package/dist/model/mutations.js.map +1 -1
  68. package/dist/model/queries.d.ts +108 -2
  69. package/dist/model/queries.d.ts.map +1 -1
  70. package/dist/model/queries.js +139 -1
  71. package/dist/model/queries.js.map +1 -1
  72. package/dist/model/queryKeys.d.ts +12 -1
  73. package/dist/model/queryKeys.d.ts.map +1 -1
  74. package/dist/model/queryKeys.js +25 -0
  75. package/dist/model/queryKeys.js.map +1 -1
  76. package/dist/model/stepUp.d.ts +25 -0
  77. package/dist/model/stepUp.d.ts.map +1 -0
  78. package/dist/model/stepUp.js +79 -0
  79. package/dist/model/stepUp.js.map +1 -0
  80. package/llms.txt +49 -15
  81. package/manifest.json +346 -3
  82. package/package.json +5 -5
  83. package/src/analytics/generated/events.json +1 -1
  84. package/src/api/generated/schema.ts +774 -7
  85. package/src/api/types.ts +113 -7
  86. package/src/api/workspacesApi.ts +137 -0
  87. package/src/default/InviteAcceptPage.tsx +373 -0
  88. package/src/default/MembersManager.tsx +155 -145
  89. package/src/default/index.ts +2 -0
  90. package/src/flows/inviteAcceptFlow.ts +289 -0
  91. package/src/headless/Can.tsx +49 -0
  92. package/src/headless/InviteAcceptFlow.tsx +105 -0
  93. package/src/headless/RoleSelect.tsx +58 -0
  94. package/src/i18n/generated/errors.gen.ts +24 -0
  95. package/src/i18n/generated/errors.json +62 -0
  96. package/src/i18n/generated/errors.ru.gen.ts +8 -0
  97. package/src/i18n/keys.ts +68 -0
  98. package/src/i18n/ru.ts +34 -0
  99. package/src/index.ts +54 -1
  100. package/src/model/capabilities.ts +37 -0
  101. package/src/model/emailMask.ts +37 -0
  102. package/src/model/mutations.ts +248 -4
  103. package/src/model/queries.ts +205 -2
  104. package/src/model/queryKeys.ts +45 -1
  105. package/src/model/stepUp.ts +96 -0
@@ -89,6 +89,113 @@ export interface paths {
89
89
  patch: operations["workspaces_api_v1_partial_update"];
90
90
  trace?: never;
91
91
  };
92
+ "/workspaces/api/v1/{workspace_id}/invitations": {
93
+ parameters: {
94
+ query?: never;
95
+ header?: never;
96
+ path?: never;
97
+ cookie?: never;
98
+ };
99
+ /**
100
+ * @description Who was invited and has not accepted (#109).
101
+ *
102
+ * Before this endpoint an admin could send invitations and never see
103
+ * them again: acceptance produced a member row, and everything else —
104
+ * the unopened mail, the wrong address, the person who declined — was
105
+ * invisible from the product. The roster answered "who is in"; nothing
106
+ * answered "who is still out, and since when".
107
+ *
108
+ * Gated on ``members.invite``: the mandate that creates invitations is
109
+ * the mandate that sees and manages them. Reading the list is strictly
110
+ * less than sending one, so no separate capability is minted — a role
111
+ * that may invite but may not audit its own invitations would be a
112
+ * distinction without a use.
113
+ *
114
+ * The invite token is never in the response. It is a bearer credential;
115
+ * a list endpoint that carried it would hand every admin a working login
116
+ * link for every invited address.
117
+ *
118
+ * **Permissions:** `IsAuthenticated`
119
+ */
120
+ get: operations["workspaces_api_v1_invitations_list"];
121
+ put?: never;
122
+ post?: never;
123
+ delete?: never;
124
+ options?: never;
125
+ head?: never;
126
+ patch?: never;
127
+ trace?: never;
128
+ };
129
+ "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/resend": {
130
+ parameters: {
131
+ query?: never;
132
+ header?: never;
133
+ path?: never;
134
+ cookie?: never;
135
+ };
136
+ get?: never;
137
+ put?: never;
138
+ /**
139
+ * @description Send the invitation email again (#109).
140
+ *
141
+ * Accepts an **expired** invitation on purpose — a dead TTL is the most
142
+ * common reason to resend — and refuses the three stored terminal states,
143
+ * which are decisions rather than delivery failures. The token is
144
+ * rotated and the TTL restarts, so the fresh letter carries a fresh link
145
+ * and any stale copy of the old one stops working.
146
+ *
147
+ * Reviving an expired invitation re-reserves a seat, so the plan ceiling
148
+ * is re-checked here exactly as it is on invite: capability first ("may
149
+ * YOU", 403), then the org's plan ("may the ORG", 402). An invitation
150
+ * that is already pending costs no additional seat and is never blocked
151
+ * by that check.
152
+ *
153
+ * **Permissions:** `IsAuthenticated`
154
+ */
155
+ post: operations["workspaces_api_v1_invitations_resend_create"];
156
+ delete?: never;
157
+ options?: never;
158
+ head?: never;
159
+ patch?: never;
160
+ trace?: never;
161
+ };
162
+ "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/revoke": {
163
+ parameters: {
164
+ query?: never;
165
+ header?: never;
166
+ path?: never;
167
+ cookie?: never;
168
+ };
169
+ get?: never;
170
+ put?: never;
171
+ /**
172
+ * @description Withdraw a live invitation (#109).
173
+ *
174
+ * The workspace's terminal "no", the mirror of the invitee's decline —
175
+ * the two stay distinguishable in ``status`` forever. The seat the
176
+ * invitation reserved is freed on commit.
177
+ *
178
+ * Only a **pending** invitation is revocable: an accepted one produced a
179
+ * membership (remove the member instead), a declined or already-revoked
180
+ * one is terminal, and an expired one is already harmless. Each of those
181
+ * gets its own error key rather than a shrug.
182
+ *
183
+ * The transition is a compare-and-set under a row lock, not a blind
184
+ * write: an accept committing between this view's state check and the
185
+ * lock wins, and the revocation then fails honestly with
186
+ * ``error.400.invitation_already_used``. The reverse — a revocation
187
+ * committing between an accept's check and its lock — is the race fixed
188
+ * in 0.10.0, and it is the same predicate holding both ends.
189
+ *
190
+ * **Permissions:** `IsAuthenticated`
191
+ */
192
+ post: operations["workspaces_api_v1_invitations_revoke_create"];
193
+ delete?: never;
194
+ options?: never;
195
+ head?: never;
196
+ patch?: never;
197
+ trace?: never;
198
+ };
92
199
  "/workspaces/api/v1/{workspace_id}/members": {
93
200
  parameters: {
94
201
  query?: never;
@@ -154,6 +261,78 @@ export interface paths {
154
261
  patch: operations["workspaces_api_v1_members_partial_update"];
155
262
  trace?: never;
156
263
  };
264
+ "/workspaces/api/v1/{workspace_id}/members/{user_id}/password/reset": {
265
+ parameters: {
266
+ query?: never;
267
+ header?: never;
268
+ path?: never;
269
+ cookie?: never;
270
+ };
271
+ get?: never;
272
+ put?: never;
273
+ /**
274
+ * @description Reset a member's password on the organization's order (#110).
275
+ *
276
+ * Five questions this endpoint has to answer out loud, because a
277
+ * password reset is an account takeover performed on purpose and every
278
+ * one of them is a way to get it wrong.
279
+ *
280
+ * **Who may do it.** A mandate, not a session: capability
281
+ * ``members.password.reset`` (builtin ``admin`` and ``owner``), declared
282
+ * ``high``, so ``@requires_verification(scope="sensitive")`` demands a
283
+ * fresh step-up on top — an ambient cookie is not enough to hand
284
+ * somebody else's account over. Only an owner may reset an OWNER's
285
+ * password, the same hardcoded owner protection role changes and
286
+ * removals carry: otherwise an admin resets the owner and inherits the
287
+ * organization. And auth refuses a staff/superuser target outright
288
+ * (``error.403.privileged_account``) — org admin is a role inside one
289
+ * workspace, deployment staff is a role above every workspace, and the
290
+ * first must never be a route to the second.
291
+ *
292
+ * **Whether the user finds out.** Always. A
293
+ * ``workspace.member_password_reset`` letter names the workspace and the
294
+ * admin who did it — a reset is indistinguishable from a takeover
295
+ * unless the account holder is told which one it was. ``notified`` in
296
+ * the response says honestly whether a channel existed; the letter
297
+ * never carries the new password (see
298
+ * :func:`~stapel_workspaces.services.reset_member_password`).
299
+ *
300
+ * **Whether the new password is temporary.** Yes — auth raises the
301
+ * workspace's ``provisioned_user_policies`` (#90), defaulting to
302
+ * ``password_change``. A password the admin knows must stop working the
303
+ * first time it is used, and since auth 0.15.0 that demand holds on all
304
+ * 19 session-issuance paths rather than only the password form. An org
305
+ * may pass an explicit ``[]`` to suppress it, and that lands in auth's
306
+ * audit row.
307
+ *
308
+ * **Whether it is an existence oracle.** No: a target that is not a
309
+ * resettable member of THIS workspace — an unknown UUID, a real account
310
+ * that is not a member, a member of a different workspace, or the
311
+ * caller's own id — all get one byte-identical 404. And the capability
312
+ * check runs before any target lookup, so a caller without the mandate
313
+ * learns nothing at all about anybody. ``tests/
314
+ * test_api_member_password_reset.py`` compares those responses byte for
315
+ * byte.
316
+ *
317
+ * **Whether it is logged with the actor.** Twice, on purpose:
318
+ * ``workspace.member_password_reset`` through the transactional outbox
319
+ * (the org's activity log) and auth's own ``AuthAuditLog`` row carrying
320
+ * ``actor_id`` and ``via=admin_reset`` (the deployment's security
321
+ * journal). Neither carries credential material.
322
+ *
323
+ * Own password: use auth's ``POST /password/change/``. This endpoint is
324
+ * for acting on somebody else, and answers about yourself with the same
325
+ * 404 as about a stranger.
326
+ *
327
+ * **Permissions:** `IsAuthenticated`
328
+ */
329
+ post: operations["workspaces_api_v1_members_password_reset_create"];
330
+ delete?: never;
331
+ options?: never;
332
+ head?: never;
333
+ patch?: never;
334
+ trace?: never;
335
+ };
157
336
  "/workspaces/api/v1/{workspace_id}/members/invite": {
158
337
  parameters: {
159
338
  query?: never;
@@ -181,6 +360,49 @@ export interface paths {
181
360
  patch?: never;
182
361
  trace?: never;
183
362
  };
363
+ "/workspaces/api/v1/{workspace_id}/members/provision": {
364
+ parameters: {
365
+ query?: never;
366
+ header?: never;
367
+ path?: never;
368
+ cookie?: never;
369
+ };
370
+ get?: never;
371
+ put?: never;
372
+ /**
373
+ * @description Provision an org-created (synthetic) member (org-program spec §C1).
374
+ *
375
+ * The org mints its own login/password account: full username is
376
+ * ``{workspace_slug}/{username_local}`` (namespaced — the slug is
377
+ * globally unique, so orgs cannot collide), the account is created by
378
+ * ``auth.provision_user`` with the workspace's first-login policy
379
+ * (``settings.security.provisioned_user_policy``: forced password change
380
+ * or mandatory MFA enrollment) and joins immediately
381
+ * (``accepted_at=now``, ``provisioned=True``).
382
+ *
383
+ * Gate stack, in order: HIGH step-up (``@requires_verification``, scope
384
+ * ``sensitive`` — the same store as admin step-up) → capability
385
+ * ``members.provision`` (403) → entitlement ``workspaces.provision_user``
386
+ * (402; degrades to allow without billing) → optional per-user debit
387
+ * (``STAPEL_WORKSPACES["PROVISION_USER_CREDITS"]`` > 0).
388
+ *
389
+ * Credentials: a synthetic account normally has no email — when the
390
+ * request omits ``email``, the server-generated password comes back in
391
+ * the response (``generated_password``) exactly once and no letter is
392
+ * sent. With ``email``, the ``workspace.provisioned_account`` letter
393
+ * carries the credentials as well. Auth's structured failures pass
394
+ * through keyed (``error.409.username_taken`` / auth's 400s); auth not
395
+ * wired → honest 503, this seam never degrades to allow.
396
+ *
397
+ * **Permissions:** `IsAuthenticated`
398
+ */
399
+ post: operations["workspaces_api_v1_members_provision_create"];
400
+ delete?: never;
401
+ options?: never;
402
+ head?: never;
403
+ patch?: never;
404
+ trace?: never;
405
+ };
184
406
  "/workspaces/api/v1/internal/{workspace_id}/members/{user_id}": {
185
407
  parameters: {
186
408
  query?: never;
@@ -223,6 +445,90 @@ export interface paths {
223
445
  patch?: never;
224
446
  trace?: never;
225
447
  };
448
+ "/workspaces/api/v1/invitations/{token}": {
449
+ parameters: {
450
+ query?: never;
451
+ header?: never;
452
+ path?: never;
453
+ cookie?: never;
454
+ };
455
+ /**
456
+ * @description Public invitation preview — what the /invite/{token} page renders.
457
+ *
458
+ * AllowAny by design (org-program spec §B2): the invitee has no session
459
+ * yet; the token in the URL is the bearer secret. The response leaks
460
+ * nothing harvestable — the email is masked, and ``status`` /
461
+ * ``email_registered`` are exactly what the frontend flow machine needs
462
+ * to route (login vs claim vs terminal-state screen). Throttled as an
463
+ * enumeration backstop. The token is never logged.
464
+ *
465
+ * **Permissions:** `AllowAny`
466
+ */
467
+ get: operations["workspaces_api_v1_invitations_retrieve"];
468
+ put?: never;
469
+ post?: never;
470
+ delete?: never;
471
+ options?: never;
472
+ head?: never;
473
+ patch?: never;
474
+ trace?: never;
475
+ };
476
+ "/workspaces/api/v1/invitations/{token}/claim": {
477
+ parameters: {
478
+ query?: never;
479
+ header?: never;
480
+ path?: never;
481
+ cookie?: never;
482
+ };
483
+ get?: never;
484
+ put?: never;
485
+ /**
486
+ * @description Mint a login grant for a not-yet-registered invitee (spec §B2-B3).
487
+ *
488
+ * AllowAny — the whole point is that no account exists yet. Only valid
489
+ * for ``email_registered == false``: an existing account gets 409 and the
490
+ * frontend switches to login. The grant comes from auth's
491
+ * ``auth.issue_login_grant`` comm Function (``create_if_missing`` — the
492
+ * verified account materializes on exchange); if that Function is not
493
+ * wired, the answer is an honest 503 — an invite flow without auth is
494
+ * meaningless, so this seam never degrades to allow. The invitation is
495
+ * NOT consumed here: accept stays a separate, deliberate step after
496
+ * setup. Neither the invite token nor the grant token is ever logged.
497
+ *
498
+ * **Permissions:** `AllowAny`
499
+ */
500
+ post: operations["workspaces_api_v1_invitations_claim_create"];
501
+ delete?: never;
502
+ options?: never;
503
+ head?: never;
504
+ patch?: never;
505
+ trace?: never;
506
+ };
507
+ "/workspaces/api/v1/invitations/{token}/decline": {
508
+ parameters: {
509
+ query?: never;
510
+ header?: never;
511
+ path?: never;
512
+ cookie?: never;
513
+ };
514
+ get?: never;
515
+ put?: never;
516
+ /**
517
+ * @description Decline an invitation — the invitee's terminal "no" (spec §B2).
518
+ *
519
+ * Authenticated + email-match, exactly like accept: only the invited
520
+ * account may resolve the invitation, in either direction. Decline ≠
521
+ * revoke — both states stay distinguishable in the preview ``status``.
522
+ *
523
+ * **Permissions:** `IsAuthenticated`
524
+ */
525
+ post: operations["workspaces_api_v1_invitations_decline_create"];
526
+ delete?: never;
527
+ options?: never;
528
+ head?: never;
529
+ patch?: never;
530
+ trace?: never;
531
+ };
226
532
  "/workspaces/api/v1/invitations/accept": {
227
533
  parameters: {
228
534
  query?: never;
@@ -250,6 +556,31 @@ export interface paths {
250
556
  patch?: never;
251
557
  trace?: never;
252
558
  };
559
+ "/workspaces/api/v1/roles": {
560
+ parameters: {
561
+ query?: never;
562
+ header?: never;
563
+ path?: never;
564
+ cookie?: never;
565
+ };
566
+ /**
567
+ * @description The effective role registry — metadata for frontends (spec §A2).
568
+ *
569
+ * Lets a RoleSelect stop hardcoding the builtin four: builtin roles plus
570
+ * the deployment's ``STAPEL_WORKSPACES["ROLES"]`` overlay, capability
571
+ * strings verbatim (wildcards included), ordered by descending rank.
572
+ *
573
+ * **Permissions:** `IsAuthenticated`
574
+ */
575
+ get: operations["workspaces_api_v1_roles_retrieve"];
576
+ put?: never;
577
+ post?: never;
578
+ delete?: never;
579
+ options?: never;
580
+ head?: never;
581
+ patch?: never;
582
+ trace?: never;
583
+ };
253
584
  }
254
585
  export type webhooks = Record<string, never>;
255
586
  export interface components {
@@ -267,18 +598,100 @@ export interface components {
267
598
  /** @description Invite token from the email link */
268
599
  token: string;
269
600
  };
270
- /** @description InvitationResponse(id: uuid.UUID, workspace_id: uuid.UUID, email: str, role: str, expires_at: str, accepted_at: Optional[str], revoked_at: Optional[str], created_at: str) */
601
+ /** @description Login-grant mint for an unregistered invitee (claim step). */
602
+ InvitationClaimResponse: {
603
+ /** @description Single-use, short-lived login grant token — exchange it at auth's POST /grant/exchange/ for a session (creates the verified account). A credential: never log it */
604
+ grant_token: string;
605
+ };
606
+ /** @description Public (AllowAny) invitation preview — what the /invite/{token} page renders before any auth decision. */
607
+ InvitationPreviewResponse: {
608
+ /**
609
+ * @description Display name of the inviting workspace
610
+ * @example Acme Engineering
611
+ */
612
+ workspace_name: string;
613
+ /**
614
+ * @description Role granted on acceptance
615
+ * @example member
616
+ */
617
+ role: string;
618
+ /**
619
+ * @description Invited email, masked for the public page
620
+ * @example m***@e***.com
621
+ */
622
+ email_masked: string;
623
+ /**
624
+ * @description Invitation state. One of pending / expired / revoked / accepted / declined
625
+ * @example pending
626
+ */
627
+ status: string;
628
+ /** @description Whether an account already exists for the invited email — steers the frontend to login vs claim */
629
+ email_registered: boolean;
630
+ /**
631
+ * @description ISO 8601 expiry time
632
+ * @example 2026-07-31T10:00:00Z
633
+ */
634
+ expires_at: string;
635
+ };
636
+ /**
637
+ * @description One invitation row — the admin's "who has not accepted yet" table (#109).
638
+ *
639
+ * ``status`` is the derived label (``WorkspaceInvitation.status``), not a
640
+ * stored column: it is what the table renders and what the frontend
641
+ * routes the revoke/resend affordances on. The raw timestamps travel
642
+ * alongside it because "expires in 2 days" and "declined last Tuesday"
643
+ * are different rows on the same screen; the label alone cannot say
644
+ * when.
645
+ *
646
+ * The invite token is deliberately absent — it is a bearer secret that
647
+ * only ever leaves this service inside the invitation email.
648
+ */
271
649
  InvitationResponse: {
272
- /** Format: uuid */
650
+ /**
651
+ * Format: uuid
652
+ * @description Invitation UUID
653
+ * @example 0192f...
654
+ */
273
655
  id: string;
274
- /** Format: uuid */
656
+ /**
657
+ * Format: uuid
658
+ * @description Workspace UUID
659
+ */
275
660
  workspace_id: string;
661
+ /**
662
+ * @description Invited email address (normalized lowercase)
663
+ * @example alice@example.com
664
+ */
276
665
  email: string;
666
+ /**
667
+ * @description Role granted on acceptance
668
+ * @example member
669
+ */
277
670
  role: string;
671
+ /**
672
+ * @description Derived state. One of pending / accepted / declined / revoked / expired
673
+ * @example pending
674
+ */
675
+ status: string;
676
+ /**
677
+ * @description ISO 8601 expiry time
678
+ * @example 2026-07-31T10:00:00Z
679
+ */
278
680
  expires_at: string;
681
+ /** @description ISO 8601 acceptance time; null unless accepted */
279
682
  accepted_at: string | null;
683
+ /** @description ISO 8601 decline time (the invitee said no); null unless declined */
684
+ declined_at: string | null;
685
+ /** @description ISO 8601 revocation time (the workspace withdrew it); null unless revoked */
280
686
  revoked_at: string | null;
687
+ /** @description ISO 8601 creation time */
281
688
  created_at: string;
689
+ /**
690
+ * Format: uuid
691
+ * @description UUID of the admin who sent it; null when that account is gone
692
+ * @example 0192a...
693
+ */
694
+ invited_by_id?: string | null;
282
695
  };
283
696
  /** @description Invite payload. */
284
697
  MemberInviteRequest: {
@@ -299,6 +712,50 @@ export interface components {
299
712
  MemberInviteResponse: {
300
713
  invitations?: components["schemas"]["InvitationResponse"][];
301
714
  };
715
+ /** @description Reset a member's password on the organization's order (#110). */
716
+ MemberPasswordResetRequest: {
717
+ /** @description New password chosen by the admin. Omitted: the server generates a crypto-strong one, returned once as generated_password */
718
+ password?: string | null;
719
+ /**
720
+ * @description First-login steps to demand of the member afterwards — any subset of password_change / mfa_enroll. Omitted: the workspace's own security policies, falling back to password_change. A password somebody else chose has to stop working at its first use, so an EMPTY list is a deliberate, auditable choice, not a shortcut
721
+ * @example [
722
+ * "password_change"
723
+ * ]
724
+ */
725
+ first_login_policies?: string[] | null;
726
+ /**
727
+ * @description Free-text note recorded on auth's audit row (e.g. the ticket the request came in on)
728
+ * @example SUP-42
729
+ */
730
+ reason?: string | null;
731
+ };
732
+ /** @description Result of an administrative password reset (#110). */
733
+ MemberPasswordResetResponse: {
734
+ /**
735
+ * Format: uuid
736
+ * @description UUID of the member whose password was reset
737
+ */
738
+ user_id: string;
739
+ /**
740
+ * @description How many live sessions the reset ended. A reset that left them standing would not recover the account
741
+ * @example 2
742
+ */
743
+ sessions_revoked?: number;
744
+ /** @description Server-generated password — returned exactly ONCE, only when the request omitted password. Hand it to the member out of band; it cannot be re-fetched. A credential: never log it */
745
+ generated_password?: string | null;
746
+ /**
747
+ * @description The steps now demanded of the member before their next session
748
+ * @example [
749
+ * "password_change"
750
+ * ]
751
+ */
752
+ first_login_policies_applied?: string[];
753
+ /**
754
+ * @description Whether the member was told their password was reset. False means the account has no contact channel — the admin is the only one who can tell them
755
+ * @example true
756
+ */
757
+ notified?: boolean;
758
+ };
302
759
  /** @description Workspace member. */
303
760
  MemberResponse: {
304
761
  /**
@@ -330,6 +787,25 @@ export interface components {
330
787
  accepted_at: string | null;
331
788
  /** @description ISO 8601 last access; null if never accessed */
332
789
  last_accessed_at: string | null;
790
+ /** @description Whether this is an org-created (synthetic) member joined via members/provision */
791
+ provisioned?: boolean;
792
+ /** @description ISO 8601 suspension timestamp; null while active. Suspension is not removal — the role stays but access is closed */
793
+ suspended_at?: string | null;
794
+ /** @description Why the membership is suspended (canonical value no_mfa); null while active */
795
+ suspension_reason?: string | null;
796
+ };
797
+ PaginatedInvitationResponseList: {
798
+ items: components["schemas"]["InvitationResponse"][];
799
+ /** @description Anchor value for next page */
800
+ next_anchor?: string | null;
801
+ /** @description Anchor value for previous page */
802
+ prev_anchor?: string | null;
803
+ /** @description Whether there are more items after this page */
804
+ has_next: boolean;
805
+ /** @description Whether there are items before this page */
806
+ has_prev: boolean;
807
+ /** @description Number of items in current page */
808
+ count: number;
333
809
  };
334
810
  PaginatedMemberResponseList: {
335
811
  items: components["schemas"]["MemberResponse"][];
@@ -356,6 +832,78 @@ export interface components {
356
832
  [key: string]: unknown;
357
833
  } | null;
358
834
  };
835
+ /** @description Provision an org-created (synthetic) member (org-program spec §C1). */
836
+ ProvisionMemberRequest: {
837
+ /**
838
+ * @description Local part of the login; the full username becomes "{workspace_slug}/{username_local}". Stock username alphabet, no slash
839
+ * @example jdoe
840
+ */
841
+ username_local: string;
842
+ /**
843
+ * @description Role to grant (effective registry; owner is not provisionable)
844
+ * @example member
845
+ */
846
+ role?: string;
847
+ /** @description Initial password chosen by the admin. Omitted: the server generates a crypto-strong one, returned once as generated_password */
848
+ password?: string | null;
849
+ /**
850
+ * @description Display-name hint forwarded to auth/profiles
851
+ * @example Jane Doe
852
+ */
853
+ display_name?: string | null;
854
+ /** @description Optional email anchor (stored UNVERIFIED by auth). When present, the provisioned-account email with the credentials is sent there. Normally omitted — synthetic accounts have no email */
855
+ email?: string | null;
856
+ };
857
+ /** @description Result of provisioning an org member. */
858
+ ProvisionMemberResponse: {
859
+ /**
860
+ * Format: uuid
861
+ * @description UUID of the created account
862
+ */
863
+ user_id: string;
864
+ /**
865
+ * @description Full namespaced login
866
+ * @example acme-eng/jdoe
867
+ */
868
+ username: string;
869
+ /**
870
+ * @description Granted role
871
+ * @example member
872
+ */
873
+ role: string;
874
+ /** @description Server-generated initial password — returned exactly ONCE, only when the request omitted password. Store it or hand it to the user now; it cannot be re-fetched. A credential: never log it */
875
+ generated_password?: string | null;
876
+ };
877
+ /** @description RoleListResponse(roles: List[stapel_workspaces.dto.RoleResponse] = <factory>) */
878
+ RoleListResponse: {
879
+ roles?: components["schemas"]["RoleResponse"][];
880
+ };
881
+ /** @description One role of the effective registry (builtin + STAPEL_WORKSPACES overlay). */
882
+ RoleResponse: {
883
+ /**
884
+ * @description Role key
885
+ * @example admin
886
+ */
887
+ role: string;
888
+ /**
889
+ * @description Ordering weight; higher = more powerful
890
+ * @example 300
891
+ */
892
+ rank: number;
893
+ /**
894
+ * @description Granted capability strings, verbatim (wildcards like * included)
895
+ * @example [
896
+ * "workspace.view",
897
+ * "members.invite"
898
+ * ]
899
+ */
900
+ capabilities: string[];
901
+ /**
902
+ * @description Whether the role key is one of the builtin four
903
+ * @example true
904
+ */
905
+ builtin: boolean;
906
+ };
359
907
  /** @description Structured error returned by all Stapel API endpoints. */
360
908
  StapelError: {
361
909
  /**
@@ -363,10 +911,7 @@ export interface components {
363
911
  * @example error.404.not_found
364
912
  */
365
913
  localizable_error: string;
366
- /**
367
- * @description Human-readable message in English
368
- * @example Requested resource not found
369
- */
914
+ /** @description Human-readable fallback/debug message — not reliably English, */
370
915
  error: string;
371
916
  /**
372
917
  * @description Context values for template placeholders
@@ -377,6 +922,8 @@ export interface components {
377
922
  params?: {
378
923
  [key: string]: unknown;
379
924
  };
925
+ /** @description Active Django locale `error` was rendered in (e.g */
926
+ error_language?: string;
380
927
  };
381
928
  /** @description Create-workspace payload. */
382
929
  WorkspaceCreateRequest: {
@@ -454,6 +1001,14 @@ export interface components {
454
1001
  * @example 2026-05-20T10:00:00Z
455
1002
  */
456
1003
  updated_at: string;
1004
+ /**
1005
+ * @description Granted capability strings of the requesting user's role, verbatim from the registry (wildcards like * included)
1006
+ * @example [
1007
+ * "workspace.view",
1008
+ * "members.view"
1009
+ * ]
1010
+ */
1011
+ my_capabilities?: string[];
457
1012
  };
458
1013
  };
459
1014
  responses: never;
@@ -576,6 +1131,82 @@ export interface operations {
576
1131
  };
577
1132
  };
578
1133
  };
1134
+ workspaces_api_v1_invitations_list: {
1135
+ parameters: {
1136
+ query?: {
1137
+ /** @description Anchor value to paginate from (exclusive) */
1138
+ anchor?: string;
1139
+ /** @description Pagination direction */
1140
+ direction?: "next" | "prev" | "center";
1141
+ /** @description Number of items (default 100, max 500) */
1142
+ limit?: number;
1143
+ /** @description Case-insensitive substring filter on the invited email address. */
1144
+ search?: string;
1145
+ /** @description Which invitations to return. `pending` (default) — live, actionable, seat-reserving ones, i.e. who has not accepted yet. `never_accepted` — those plus the declined, revoked and expired ones. `all` — the full history, accepted rows included. */
1146
+ status?: "all" | "never_accepted" | "pending";
1147
+ };
1148
+ header?: never;
1149
+ path: {
1150
+ workspace_id: string;
1151
+ };
1152
+ cookie?: never;
1153
+ };
1154
+ requestBody?: never;
1155
+ responses: {
1156
+ 200: {
1157
+ headers: {
1158
+ [name: string]: unknown;
1159
+ };
1160
+ content: {
1161
+ "application/json": components["schemas"]["PaginatedInvitationResponseList"];
1162
+ };
1163
+ };
1164
+ };
1165
+ };
1166
+ workspaces_api_v1_invitations_resend_create: {
1167
+ parameters: {
1168
+ query?: never;
1169
+ header?: never;
1170
+ path: {
1171
+ invitation_id: string;
1172
+ workspace_id: string;
1173
+ };
1174
+ cookie?: never;
1175
+ };
1176
+ requestBody?: never;
1177
+ responses: {
1178
+ 200: {
1179
+ headers: {
1180
+ [name: string]: unknown;
1181
+ };
1182
+ content: {
1183
+ "application/json": components["schemas"]["InvitationResponse"];
1184
+ };
1185
+ };
1186
+ };
1187
+ };
1188
+ workspaces_api_v1_invitations_revoke_create: {
1189
+ parameters: {
1190
+ query?: never;
1191
+ header?: never;
1192
+ path: {
1193
+ invitation_id: string;
1194
+ workspace_id: string;
1195
+ };
1196
+ cookie?: never;
1197
+ };
1198
+ requestBody?: never;
1199
+ responses: {
1200
+ 200: {
1201
+ headers: {
1202
+ [name: string]: unknown;
1203
+ };
1204
+ content: {
1205
+ "application/json": components["schemas"]["InvitationResponse"];
1206
+ };
1207
+ };
1208
+ };
1209
+ };
579
1210
  workspaces_api_v1_members_list: {
580
1211
  parameters: {
581
1212
  query?: {
@@ -655,6 +1286,34 @@ export interface operations {
655
1286
  };
656
1287
  };
657
1288
  };
1289
+ workspaces_api_v1_members_password_reset_create: {
1290
+ parameters: {
1291
+ query?: never;
1292
+ header?: never;
1293
+ path: {
1294
+ user_id: string;
1295
+ workspace_id: string;
1296
+ };
1297
+ cookie?: never;
1298
+ };
1299
+ requestBody?: {
1300
+ content: {
1301
+ "application/json": components["schemas"]["MemberPasswordResetRequest"];
1302
+ "application/x-www-form-urlencoded": components["schemas"]["MemberPasswordResetRequest"];
1303
+ "multipart/form-data": components["schemas"]["MemberPasswordResetRequest"];
1304
+ };
1305
+ };
1306
+ responses: {
1307
+ 200: {
1308
+ headers: {
1309
+ [name: string]: unknown;
1310
+ };
1311
+ content: {
1312
+ "application/json": components["schemas"]["MemberPasswordResetResponse"];
1313
+ };
1314
+ };
1315
+ };
1316
+ };
658
1317
  workspaces_api_v1_members_invite_create: {
659
1318
  parameters: {
660
1319
  query?: never;
@@ -682,6 +1341,33 @@ export interface operations {
682
1341
  };
683
1342
  };
684
1343
  };
1344
+ workspaces_api_v1_members_provision_create: {
1345
+ parameters: {
1346
+ query?: never;
1347
+ header?: never;
1348
+ path: {
1349
+ workspace_id: string;
1350
+ };
1351
+ cookie?: never;
1352
+ };
1353
+ requestBody: {
1354
+ content: {
1355
+ "application/json": components["schemas"]["ProvisionMemberRequest"];
1356
+ "application/x-www-form-urlencoded": components["schemas"]["ProvisionMemberRequest"];
1357
+ "multipart/form-data": components["schemas"]["ProvisionMemberRequest"];
1358
+ };
1359
+ };
1360
+ responses: {
1361
+ 201: {
1362
+ headers: {
1363
+ [name: string]: unknown;
1364
+ };
1365
+ content: {
1366
+ "application/json": components["schemas"]["ProvisionMemberResponse"];
1367
+ };
1368
+ };
1369
+ };
1370
+ };
685
1371
  workspaces_api_v1_internal_members_retrieve: {
686
1372
  parameters: {
687
1373
  query?: never;
@@ -733,6 +1419,68 @@ export interface operations {
733
1419
  };
734
1420
  };
735
1421
  };
1422
+ workspaces_api_v1_invitations_retrieve: {
1423
+ parameters: {
1424
+ query?: never;
1425
+ header?: never;
1426
+ path: {
1427
+ token: string;
1428
+ };
1429
+ cookie?: never;
1430
+ };
1431
+ requestBody?: never;
1432
+ responses: {
1433
+ 200: {
1434
+ headers: {
1435
+ [name: string]: unknown;
1436
+ };
1437
+ content: {
1438
+ "application/json": components["schemas"]["InvitationPreviewResponse"];
1439
+ };
1440
+ };
1441
+ };
1442
+ };
1443
+ workspaces_api_v1_invitations_claim_create: {
1444
+ parameters: {
1445
+ query?: never;
1446
+ header?: never;
1447
+ path: {
1448
+ token: string;
1449
+ };
1450
+ cookie?: never;
1451
+ };
1452
+ requestBody?: never;
1453
+ responses: {
1454
+ 200: {
1455
+ headers: {
1456
+ [name: string]: unknown;
1457
+ };
1458
+ content: {
1459
+ "application/json": components["schemas"]["InvitationClaimResponse"];
1460
+ };
1461
+ };
1462
+ };
1463
+ };
1464
+ workspaces_api_v1_invitations_decline_create: {
1465
+ parameters: {
1466
+ query?: never;
1467
+ header?: never;
1468
+ path: {
1469
+ token: string;
1470
+ };
1471
+ cookie?: never;
1472
+ };
1473
+ requestBody?: never;
1474
+ responses: {
1475
+ /** @description No response body */
1476
+ 204: {
1477
+ headers: {
1478
+ [name: string]: unknown;
1479
+ };
1480
+ content?: never;
1481
+ };
1482
+ };
1483
+ };
736
1484
  workspaces_api_v1_invitations_accept_create: {
737
1485
  parameters: {
738
1486
  query?: never;
@@ -758,4 +1506,23 @@ export interface operations {
758
1506
  };
759
1507
  };
760
1508
  };
1509
+ workspaces_api_v1_roles_retrieve: {
1510
+ parameters: {
1511
+ query?: never;
1512
+ header?: never;
1513
+ path?: never;
1514
+ cookie?: never;
1515
+ };
1516
+ requestBody?: never;
1517
+ responses: {
1518
+ 200: {
1519
+ headers: {
1520
+ [name: string]: unknown;
1521
+ };
1522
+ content: {
1523
+ "application/json": components["schemas"]["RoleListResponse"];
1524
+ };
1525
+ };
1526
+ };
1527
+ };
761
1528
  }