@stapel/workspaces-react 0.7.0 → 0.9.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 (53) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/api/generated/schema.d.ts +497 -5
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +91 -2
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/api/workspacesApi.d.ts +35 -1
  7. package/dist/api/workspacesApi.d.ts.map +1 -1
  8. package/dist/api/workspacesApi.js +20 -0
  9. package/dist/api/workspacesApi.js.map +1 -1
  10. package/dist/default/MembersManager.js +2 -2
  11. package/dist/i18n/generated/errors.gen.d.ts +6 -0
  12. package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
  13. package/dist/i18n/generated/errors.gen.js +3 -0
  14. package/dist/i18n/generated/errors.gen.js.map +1 -1
  15. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
  16. package/dist/i18n/generated/errors.ru.gen.js +1 -0
  17. package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
  18. package/dist/index.d.ts +7 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/model/mutations.d.ts +86 -2
  23. package/dist/model/mutations.d.ts.map +1 -1
  24. package/dist/model/mutations.js +152 -1
  25. package/dist/model/mutations.js.map +1 -1
  26. package/dist/model/queries.d.ts +90 -2
  27. package/dist/model/queries.d.ts.map +1 -1
  28. package/dist/model/queries.js +115 -1
  29. package/dist/model/queries.js.map +1 -1
  30. package/dist/model/queryKeys.d.ts +11 -1
  31. package/dist/model/queryKeys.d.ts.map +1 -1
  32. package/dist/model/queryKeys.js +21 -1
  33. package/dist/model/queryKeys.js.map +1 -1
  34. package/dist/model/stepUp.d.ts +25 -0
  35. package/dist/model/stepUp.d.ts.map +1 -0
  36. package/dist/model/stepUp.js +79 -0
  37. package/dist/model/stepUp.js.map +1 -0
  38. package/llms.txt +15 -3
  39. package/manifest.json +147 -2
  40. package/package.json +4 -4
  41. package/src/analytics/generated/events.json +1 -1
  42. package/src/api/generated/schema.ts +497 -5
  43. package/src/api/types.ts +101 -2
  44. package/src/api/workspacesApi.ts +93 -0
  45. package/src/default/MembersManager.tsx +2 -2
  46. package/src/i18n/generated/errors.gen.ts +3 -0
  47. package/src/i18n/generated/errors.json +9 -0
  48. package/src/i18n/generated/errors.ru.gen.ts +1 -0
  49. package/src/index.ts +30 -2
  50. package/src/model/mutations.ts +193 -1
  51. package/src/model/queries.ts +162 -2
  52. package/src/model/queryKeys.ts +38 -2
  53. package/src/model/stepUp.ts +96 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @stapel/workspaces-react
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f9c04aa: Ручки вчерашних релизов бэкенда стали вызываемыми с фронта.
8
+
9
+ `@stapel/workspaces-react` (контракт stapel-workspaces `>=0.14 <0.15`):
10
+
11
+ - `useInvitations` / `useInfiniteInvitations` — админская таблица приглашений
12
+ (`GET /{ws}/invitations`) с фильтрами `status` (`pending` / `never_accepted`
13
+ / `all`) и `search`. Пагинация **якорная**, как у `useMembers`: страница
14
+ адресуется непрозрачным `next_anchor` предыдущей, номера страницы нет —
15
+ оффсет поехал бы ровно в тот момент, когда приглашение отзывают у админа
16
+ под руками.
17
+ - `useRevokeInvitation` / `useResendInvitation` — отзыв и повторная отправка;
18
+ обе возвращают обновлённый DTO. Ресенд ротирует токен и перезапускает TTL,
19
+ поэтому таблица инвалидируется: старый `expires_at` на экране врал бы про
20
+ живую креденцию.
21
+ - `useResetMemberPassword` — админский сброс пароля участнику.
22
+ `generated_password` приходит ровно один раз и **не попадает в кэш
23
+ запросов** (ничего не пишется через `setQueryData`, `gcTime: 0`): рантайм
24
+ ядра персистит весь пользовательский query-кэш в localStorage, так что
25
+ запись туда означала бы живой пароль на диске и в девтулзах.
26
+ - `useCapabilityGate` + порт `BUILTIN_CAPABILITY_LEVELS` — уровень `high` и
27
+ скоуп `sensitive` известны **до** кнопки, а не после 403.
28
+ `readVerificationEnrollment` отличает конверт «заведи фактор» (его ядро не
29
+ перехватывает — перехватывать нечего) от обычного челленджа.
30
+ - `useUpdateSecuritySettings` — `provisioned_user_policies` теперь список
31
+ независимых требований (#90), пустой список отправляется явно. Мердж
32
+ делается на клиенте: бэкенд присваивает `settings` целиком, и голый
33
+ `{security: …}` стёр бы остальные ключи.
34
+
35
+ `@stapel/profiles-react` (контракт stapel-profiles `>=0.9 <0.10`):
36
+
37
+ - `useProfilesBatch` — `POST /profiles/api/v1/batch`, один запрос вместо N.
38
+ `profileBatchEntry` отвечает четырьмя состояниями (`found` / `missing` /
39
+ `not_requested` / `unknown`): «профиля нет» — нормальное состояние и
40
+ плейсхолдер, «не спрашивали» — другое дело, и схлопывать их в `undefined`
41
+ значило бы вернуть тот самый дефект, ради которого батч и делался.
42
+ Найденные профили засеваются в кэш `useProfile`; для `missing` не
43
+ выдумывается ничего.
44
+
3
45
  ## 0.7.0
4
46
 
5
47
  ### Minor Changes
@@ -86,6 +86,113 @@ export interface paths {
86
86
  patch: operations["workspaces_api_v1_partial_update"];
87
87
  trace?: never;
88
88
  };
89
+ "/workspaces/api/v1/{workspace_id}/invitations": {
90
+ parameters: {
91
+ query?: never;
92
+ header?: never;
93
+ path?: never;
94
+ cookie?: never;
95
+ };
96
+ /**
97
+ * @description Who was invited and has not accepted (#109).
98
+ *
99
+ * Before this endpoint an admin could send invitations and never see
100
+ * them again: acceptance produced a member row, and everything else —
101
+ * the unopened mail, the wrong address, the person who declined — was
102
+ * invisible from the product. The roster answered "who is in"; nothing
103
+ * answered "who is still out, and since when".
104
+ *
105
+ * Gated on ``members.invite``: the mandate that creates invitations is
106
+ * the mandate that sees and manages them. Reading the list is strictly
107
+ * less than sending one, so no separate capability is minted — a role
108
+ * that may invite but may not audit its own invitations would be a
109
+ * distinction without a use.
110
+ *
111
+ * The invite token is never in the response. It is a bearer credential;
112
+ * a list endpoint that carried it would hand every admin a working login
113
+ * link for every invited address.
114
+ *
115
+ * **Permissions:** `IsAuthenticated`
116
+ */
117
+ get: operations["workspaces_api_v1_invitations_list"];
118
+ put?: never;
119
+ post?: never;
120
+ delete?: never;
121
+ options?: never;
122
+ head?: never;
123
+ patch?: never;
124
+ trace?: never;
125
+ };
126
+ "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/resend": {
127
+ parameters: {
128
+ query?: never;
129
+ header?: never;
130
+ path?: never;
131
+ cookie?: never;
132
+ };
133
+ get?: never;
134
+ put?: never;
135
+ /**
136
+ * @description Send the invitation email again (#109).
137
+ *
138
+ * Accepts an **expired** invitation on purpose — a dead TTL is the most
139
+ * common reason to resend — and refuses the three stored terminal states,
140
+ * which are decisions rather than delivery failures. The token is
141
+ * rotated and the TTL restarts, so the fresh letter carries a fresh link
142
+ * and any stale copy of the old one stops working.
143
+ *
144
+ * Reviving an expired invitation re-reserves a seat, so the plan ceiling
145
+ * is re-checked here exactly as it is on invite: capability first ("may
146
+ * YOU", 403), then the org's plan ("may the ORG", 402). An invitation
147
+ * that is already pending costs no additional seat and is never blocked
148
+ * by that check.
149
+ *
150
+ * **Permissions:** `IsAuthenticated`
151
+ */
152
+ post: operations["workspaces_api_v1_invitations_resend_create"];
153
+ delete?: never;
154
+ options?: never;
155
+ head?: never;
156
+ patch?: never;
157
+ trace?: never;
158
+ };
159
+ "/workspaces/api/v1/{workspace_id}/invitations/{invitation_id}/revoke": {
160
+ parameters: {
161
+ query?: never;
162
+ header?: never;
163
+ path?: never;
164
+ cookie?: never;
165
+ };
166
+ get?: never;
167
+ put?: never;
168
+ /**
169
+ * @description Withdraw a live invitation (#109).
170
+ *
171
+ * The workspace's terminal "no", the mirror of the invitee's decline —
172
+ * the two stay distinguishable in ``status`` forever. The seat the
173
+ * invitation reserved is freed on commit.
174
+ *
175
+ * Only a **pending** invitation is revocable: an accepted one produced a
176
+ * membership (remove the member instead), a declined or already-revoked
177
+ * one is terminal, and an expired one is already harmless. Each of those
178
+ * gets its own error key rather than a shrug.
179
+ *
180
+ * The transition is a compare-and-set under a row lock, not a blind
181
+ * write: an accept committing between this view's state check and the
182
+ * lock wins, and the revocation then fails honestly with
183
+ * ``error.400.invitation_already_used``. The reverse — a revocation
184
+ * committing between an accept's check and its lock — is the race fixed
185
+ * in 0.10.0, and it is the same predicate holding both ends.
186
+ *
187
+ * **Permissions:** `IsAuthenticated`
188
+ */
189
+ post: operations["workspaces_api_v1_invitations_revoke_create"];
190
+ delete?: never;
191
+ options?: never;
192
+ head?: never;
193
+ patch?: never;
194
+ trace?: never;
195
+ };
89
196
  "/workspaces/api/v1/{workspace_id}/members": {
90
197
  parameters: {
91
198
  query?: never;
@@ -151,6 +258,78 @@ export interface paths {
151
258
  patch: operations["workspaces_api_v1_members_partial_update"];
152
259
  trace?: never;
153
260
  };
261
+ "/workspaces/api/v1/{workspace_id}/members/{user_id}/password/reset": {
262
+ parameters: {
263
+ query?: never;
264
+ header?: never;
265
+ path?: never;
266
+ cookie?: never;
267
+ };
268
+ get?: never;
269
+ put?: never;
270
+ /**
271
+ * @description Reset a member's password on the organization's order (#110).
272
+ *
273
+ * Five questions this endpoint has to answer out loud, because a
274
+ * password reset is an account takeover performed on purpose and every
275
+ * one of them is a way to get it wrong.
276
+ *
277
+ * **Who may do it.** A mandate, not a session: capability
278
+ * ``members.password.reset`` (builtin ``admin`` and ``owner``), declared
279
+ * ``high``, so ``@requires_verification(scope="sensitive")`` demands a
280
+ * fresh step-up on top — an ambient cookie is not enough to hand
281
+ * somebody else's account over. Only an owner may reset an OWNER's
282
+ * password, the same hardcoded owner protection role changes and
283
+ * removals carry: otherwise an admin resets the owner and inherits the
284
+ * organization. And auth refuses a staff/superuser target outright
285
+ * (``error.403.privileged_account``) — org admin is a role inside one
286
+ * workspace, deployment staff is a role above every workspace, and the
287
+ * first must never be a route to the second.
288
+ *
289
+ * **Whether the user finds out.** Always. A
290
+ * ``workspace.member_password_reset`` letter names the workspace and the
291
+ * admin who did it — a reset is indistinguishable from a takeover
292
+ * unless the account holder is told which one it was. ``notified`` in
293
+ * the response says honestly whether a channel existed; the letter
294
+ * never carries the new password (see
295
+ * :func:`~stapel_workspaces.services.reset_member_password`).
296
+ *
297
+ * **Whether the new password is temporary.** Yes — auth raises the
298
+ * workspace's ``provisioned_user_policies`` (#90), defaulting to
299
+ * ``password_change``. A password the admin knows must stop working the
300
+ * first time it is used, and since auth 0.15.0 that demand holds on all
301
+ * 19 session-issuance paths rather than only the password form. An org
302
+ * may pass an explicit ``[]`` to suppress it, and that lands in auth's
303
+ * audit row.
304
+ *
305
+ * **Whether it is an existence oracle.** No: a target that is not a
306
+ * resettable member of THIS workspace — an unknown UUID, a real account
307
+ * that is not a member, a member of a different workspace, or the
308
+ * caller's own id — all get one byte-identical 404. And the capability
309
+ * check runs before any target lookup, so a caller without the mandate
310
+ * learns nothing at all about anybody. ``tests/
311
+ * test_api_member_password_reset.py`` compares those responses byte for
312
+ * byte.
313
+ *
314
+ * **Whether it is logged with the actor.** Twice, on purpose:
315
+ * ``workspace.member_password_reset`` through the transactional outbox
316
+ * (the org's activity log) and auth's own ``AuthAuditLog`` row carrying
317
+ * ``actor_id`` and ``via=admin_reset`` (the deployment's security
318
+ * journal). Neither carries credential material.
319
+ *
320
+ * Own password: use auth's ``POST /password/change/``. This endpoint is
321
+ * for acting on somebody else, and answers about yourself with the same
322
+ * 404 as about a stranger.
323
+ *
324
+ * **Permissions:** `IsAuthenticated`
325
+ */
326
+ post: operations["workspaces_api_v1_members_password_reset_create"];
327
+ delete?: never;
328
+ options?: never;
329
+ head?: never;
330
+ patch?: never;
331
+ trace?: never;
332
+ };
154
333
  "/workspaces/api/v1/{workspace_id}/members/invite": {
155
334
  parameters: {
156
335
  query?: never;
@@ -221,6 +400,27 @@ export interface paths {
221
400
  patch?: never;
222
401
  trace?: never;
223
402
  };
403
+ "/workspaces/api/v1/instance": {
404
+ parameters: {
405
+ query?: never;
406
+ header?: never;
407
+ path?: never;
408
+ cookie?: never;
409
+ };
410
+ /**
411
+ * @description Instance shape: how a street signup lands (personal | none) and whether self-serve registration is open. Public, unauthenticated — the screen a kicked/left member sees depends on it.
412
+ *
413
+ * **Permissions:** `AllowAny`
414
+ */
415
+ get: operations["workspaces_api_v1_instance_retrieve"];
416
+ put?: never;
417
+ post?: never;
418
+ delete?: never;
419
+ options?: never;
420
+ head?: never;
421
+ patch?: never;
422
+ trace?: never;
423
+ };
224
424
  "/workspaces/api/v1/internal/{workspace_id}/members/{user_id}": {
225
425
  parameters: {
226
426
  query?: never;
@@ -403,6 +603,38 @@ export interface paths {
403
603
  export type webhooks = Record<string, never>;
404
604
  export interface components {
405
605
  schemas: {
606
+ /**
607
+ * @description Как развёрнут ЭТОТ инстанс — то, что клиенту нужно знать ДО того, как
608
+ * он решит, что показать человеку без пространства.
609
+ *
610
+ * Ось ``landing`` (``STREET_LANDING_MODE``) существовала с 03.08.2026, но
611
+ * жила только в окружении бэкенда: наружу не отдавалась ничем, и отличить
612
+ * закрытый контур от публичного облака на клиенте было НЕЧЕМ.
613
+ *
614
+ * Почему это не косметика. Экран, который видит человек, выброшенный из
615
+ * Спейса (или вышедший сам), в этих двух мирах разный по существу:
616
+ *
617
+ * * ``none`` — закрытый контур: своего пространства у человека нет и
618
+ * взяться неоткуда. Честный экран говорит «вы не участник, попросите
619
+ * приглашение» и никуда не зовёт;
620
+ * * ``personal`` — публичное облако: у него есть собственное
621
+ * пространство, и туда можно вести.
622
+ *
623
+ * Показать «создайте встречу» тому, кому инстанс её создать не даст, —
624
+ * это тупик, нарисованный кнопкой.
625
+ */
626
+ InstanceShapeResponse: {
627
+ /**
628
+ * @description С чем приземляется человек «с улицы»
629
+ * @example personal
630
+ */
631
+ landing: string;
632
+ /**
633
+ * @description Можно ли завести учётку самому
634
+ * @example True
635
+ */
636
+ registration_open: boolean;
637
+ };
406
638
  /** @description Get-or-create result for a user's personal workspace (service-to-service). */
407
639
  InternalPersonalWorkspaceResponse: {
408
640
  /**
@@ -451,21 +683,78 @@ export interface components {
451
683
  */
452
684
  expires_at: string;
453
685
  };
454
- /** @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) */
686
+ /**
687
+ * @description One invitation row — the admin's "who has not accepted yet" table (#109).
688
+ *
689
+ * ``status`` is the derived label (``WorkspaceInvitation.status``), not a
690
+ * stored column: it is what the table renders and what the frontend
691
+ * routes the revoke/resend affordances on. The raw timestamps travel
692
+ * alongside it because "expires in 2 days" and "declined last Tuesday"
693
+ * are different rows on the same screen; the label alone cannot say
694
+ * when.
695
+ *
696
+ * The invite token is deliberately absent — it is a bearer secret that
697
+ * only ever leaves this service inside the invitation email.
698
+ */
455
699
  InvitationResponse: {
456
- /** Format: uuid */
700
+ /**
701
+ * Format: uuid
702
+ * @description Invitation UUID
703
+ * @example 0192f...
704
+ */
457
705
  id: string;
458
- /** Format: uuid */
706
+ /**
707
+ * Format: uuid
708
+ * @description Workspace UUID
709
+ */
459
710
  workspace_id: string;
711
+ /**
712
+ * @description Invited email address (normalized lowercase)
713
+ * @example alice@example.com
714
+ */
460
715
  email: string;
716
+ /**
717
+ * @description Role granted on acceptance
718
+ * @example member
719
+ */
461
720
  role: string;
721
+ /**
722
+ * @description Derived state. One of pending / accepted / declined / revoked / expired
723
+ * @example pending
724
+ */
725
+ status: string;
726
+ /**
727
+ * @description ISO 8601 expiry time
728
+ * @example 2026-07-31T10:00:00Z
729
+ */
462
730
  expires_at: string;
731
+ /** @description ISO 8601 acceptance time; null unless accepted */
463
732
  accepted_at: string | null;
733
+ /** @description ISO 8601 decline time (the invitee said no); null unless declined */
734
+ declined_at: string | null;
735
+ /** @description ISO 8601 revocation time (the workspace withdrew it); null unless revoked */
464
736
  revoked_at: string | null;
737
+ /** @description ISO 8601 creation time */
465
738
  created_at: string;
739
+ /**
740
+ * Format: uuid
741
+ * @description UUID of the admin who sent it; null when that account is gone
742
+ * @example 0192a...
743
+ */
744
+ invited_by_id?: string | null;
745
+ /**
746
+ * @description Name hint typed for this invitee at invite time, if any; null otherwise
747
+ * @example Ada Lovelace
748
+ */
749
+ display_name?: string | null;
466
750
  };
467
751
  /** @description Invite payload. */
468
752
  MemberInviteRequest: {
753
+ /**
754
+ * @description Name hint for the invitee (this invite's "Имя" field), applied to every email in this request. Optional — an invite without one behaves exactly as before. NOT written into stapel-profiles directly (that store is the invitee's own): held on the invitation, copied onto the member at accept time, and shown only until stapel-profiles has a real name for the person
755
+ * @example Ada Lovelace
756
+ */
757
+ display_name?: string | null;
469
758
  /**
470
759
  * @description One or more emails to invite
471
760
  * @example [
@@ -483,6 +772,50 @@ export interface components {
483
772
  MemberInviteResponse: {
484
773
  invitations?: components["schemas"]["InvitationResponse"][];
485
774
  };
775
+ /** @description Reset a member's password on the organization's order (#110). */
776
+ MemberPasswordResetRequest: {
777
+ /** @description New password chosen by the admin. Omitted: the server generates a crypto-strong one, returned once as generated_password */
778
+ password?: string | null;
779
+ /**
780
+ * @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
781
+ * @example [
782
+ * "password_change"
783
+ * ]
784
+ */
785
+ first_login_policies?: string[] | null;
786
+ /**
787
+ * @description Free-text note recorded on auth's audit row (e.g. the ticket the request came in on)
788
+ * @example SUP-42
789
+ */
790
+ reason?: string | null;
791
+ };
792
+ /** @description Result of an administrative password reset (#110). */
793
+ MemberPasswordResetResponse: {
794
+ /**
795
+ * Format: uuid
796
+ * @description UUID of the member whose password was reset
797
+ */
798
+ user_id: string;
799
+ /**
800
+ * @description How many live sessions the reset ended. A reset that left them standing would not recover the account
801
+ * @example 2
802
+ */
803
+ sessions_revoked?: number;
804
+ /** @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 */
805
+ generated_password?: string | null;
806
+ /**
807
+ * @description The steps now demanded of the member before their next session
808
+ * @example [
809
+ * "password_change"
810
+ * ]
811
+ */
812
+ first_login_policies_applied?: string[];
813
+ /**
814
+ * @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
815
+ * @example true
816
+ */
817
+ notified?: boolean;
818
+ };
486
819
  /** @description Workspace member. */
487
820
  MemberResponse: {
488
821
  /**
@@ -520,6 +853,24 @@ export interface components {
520
853
  suspended_at?: string | null;
521
854
  /** @description Why the membership is suspended (canonical value no_mfa); null while active */
522
855
  suspension_reason?: string | null;
856
+ /**
857
+ * @description Best-effort display name — a live lookup in stapel-profiles when it is installed and has one, else the name typed at invite/provision time (never both, never invented when neither exists). Null when nobody has one yet
858
+ * @example Ada Lovelace
859
+ */
860
+ display_name?: string | null;
861
+ };
862
+ PaginatedInvitationResponseList: {
863
+ items: components["schemas"]["InvitationResponse"][];
864
+ /** @description Anchor value for next page */
865
+ next_anchor?: string | null;
866
+ /** @description Anchor value for previous page */
867
+ prev_anchor?: string | null;
868
+ /** @description Whether there are more items after this page */
869
+ has_next: boolean;
870
+ /** @description Whether there are items before this page */
871
+ has_prev: boolean;
872
+ /** @description Number of items in current page */
873
+ count: number;
523
874
  };
524
875
  PaginatedMemberResponseList: {
525
876
  items: components["schemas"]["MemberResponse"][];
@@ -588,9 +939,19 @@ export interface components {
588
939
  /** @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 */
589
940
  generated_password?: string | null;
590
941
  };
591
- /** @description RoleListResponse(roles: List[stapel_workspaces.dto.RoleResponse] = <factory>) */
942
+ /** @description The effective role registry (builtin four + STAPEL_WORKSPACES overlay). */
592
943
  RoleListResponse: {
944
+ /** @description One entry per effective role, descending rank */
593
945
  roles?: components["schemas"]["RoleResponse"][];
946
+ /**
947
+ * @description Step-up level per namespaced capability string, builtin ``members.provision`` / ``members.password.reset`` / ``workspace.security.manage`` plus the deployment's ``STAPEL_WORKSPACES["CAPABILITY_LEVELS"]`` overlay. A capability absent from this map is ``"standard"`` — the client-side default. Lets a frontend stop hardcoding its own copy of this registry
948
+ * @example {
949
+ * "members.provision": "high"
950
+ * }
951
+ */
952
+ capability_levels?: {
953
+ [key: string]: string;
954
+ };
594
955
  };
595
956
  /** @description One role of the effective registry (builtin + STAPEL_WORKSPACES overlay). */
596
957
  RoleResponse: {
@@ -657,9 +1018,17 @@ export interface components {
657
1018
  */
658
1019
  type?: string;
659
1020
  };
660
- /** @description WorkspaceListResponse(workspaces: List[stapel_workspaces.dto.WorkspaceResponse] = <factory>) */
1021
+ /** @description The caller's own workspace list. */
661
1022
  WorkspaceListResponse: {
1023
+ /** @description The caller's active memberships (empty for a guest — see is_guest) */
662
1024
  workspaces?: components["schemas"]["WorkspaceResponse"][];
1025
+ /** @description True when the caller holds NO active mandate anywhere (permissions.is_guest) — the wire form of the mandate-model vardict's guest predicate (2026-08-03), so a frontend can render the "incomplete dashboard" without re-deriving it from workspaces == [] */
1026
+ is_guest?: boolean;
1027
+ /**
1028
+ * @description Which workspace a client should open when the person has expressed no choice — the instance's STAPEL_WORKSPACES["DEFAULT_WORKSPACE_ID"], echoed back ONLY when the caller actually holds an active membership in it, else "". Exists because clients that had to guess guessed badly: meettoday took `workspaces[0]` off a list ordered by last access, so a person in two spaces landed wherever they happened to have been last (measured 2026-08-06 as "the owner cannot see his own invitations" — they were in the OTHER space). A default, not a cage: an explicit choice still wins
1029
+ * @example a8bba7ae-5d2e-43e5-9911-0553e7df50b3
1030
+ */
1031
+ default_workspace_id?: string;
663
1032
  };
664
1033
  /** @description Workspace details. */
665
1034
  WorkspaceResponse: {
@@ -845,6 +1214,82 @@ export interface operations {
845
1214
  };
846
1215
  };
847
1216
  };
1217
+ workspaces_api_v1_invitations_list: {
1218
+ parameters: {
1219
+ query?: {
1220
+ /** @description Anchor value to paginate from (exclusive) */
1221
+ anchor?: string;
1222
+ /** @description Pagination direction */
1223
+ direction?: "next" | "prev" | "center";
1224
+ /** @description Number of items (default 100, max 500) */
1225
+ limit?: number;
1226
+ /** @description Case-insensitive substring filter on the invited email address. */
1227
+ search?: string;
1228
+ /** @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. */
1229
+ status?: "all" | "never_accepted" | "pending";
1230
+ };
1231
+ header?: never;
1232
+ path: {
1233
+ workspace_id: string;
1234
+ };
1235
+ cookie?: never;
1236
+ };
1237
+ requestBody?: never;
1238
+ responses: {
1239
+ 200: {
1240
+ headers: {
1241
+ [name: string]: unknown;
1242
+ };
1243
+ content: {
1244
+ "application/json": components["schemas"]["PaginatedInvitationResponseList"];
1245
+ };
1246
+ };
1247
+ };
1248
+ };
1249
+ workspaces_api_v1_invitations_resend_create: {
1250
+ parameters: {
1251
+ query?: never;
1252
+ header?: never;
1253
+ path: {
1254
+ invitation_id: string;
1255
+ workspace_id: string;
1256
+ };
1257
+ cookie?: never;
1258
+ };
1259
+ requestBody?: never;
1260
+ responses: {
1261
+ 200: {
1262
+ headers: {
1263
+ [name: string]: unknown;
1264
+ };
1265
+ content: {
1266
+ "application/json": components["schemas"]["InvitationResponse"];
1267
+ };
1268
+ };
1269
+ };
1270
+ };
1271
+ workspaces_api_v1_invitations_revoke_create: {
1272
+ parameters: {
1273
+ query?: never;
1274
+ header?: never;
1275
+ path: {
1276
+ invitation_id: string;
1277
+ workspace_id: string;
1278
+ };
1279
+ cookie?: never;
1280
+ };
1281
+ requestBody?: never;
1282
+ responses: {
1283
+ 200: {
1284
+ headers: {
1285
+ [name: string]: unknown;
1286
+ };
1287
+ content: {
1288
+ "application/json": components["schemas"]["InvitationResponse"];
1289
+ };
1290
+ };
1291
+ };
1292
+ };
848
1293
  workspaces_api_v1_members_list: {
849
1294
  parameters: {
850
1295
  query?: {
@@ -924,6 +1369,34 @@ export interface operations {
924
1369
  };
925
1370
  };
926
1371
  };
1372
+ workspaces_api_v1_members_password_reset_create: {
1373
+ parameters: {
1374
+ query?: never;
1375
+ header?: never;
1376
+ path: {
1377
+ user_id: string;
1378
+ workspace_id: string;
1379
+ };
1380
+ cookie?: never;
1381
+ };
1382
+ requestBody?: {
1383
+ content: {
1384
+ "application/json": components["schemas"]["MemberPasswordResetRequest"];
1385
+ "application/x-www-form-urlencoded": components["schemas"]["MemberPasswordResetRequest"];
1386
+ "multipart/form-data": components["schemas"]["MemberPasswordResetRequest"];
1387
+ };
1388
+ };
1389
+ responses: {
1390
+ 200: {
1391
+ headers: {
1392
+ [name: string]: unknown;
1393
+ };
1394
+ content: {
1395
+ "application/json": components["schemas"]["MemberPasswordResetResponse"];
1396
+ };
1397
+ };
1398
+ };
1399
+ };
927
1400
  workspaces_api_v1_members_invite_create: {
928
1401
  parameters: {
929
1402
  query?: never;
@@ -978,6 +1451,25 @@ export interface operations {
978
1451
  };
979
1452
  };
980
1453
  };
1454
+ workspaces_api_v1_instance_retrieve: {
1455
+ parameters: {
1456
+ query?: never;
1457
+ header?: never;
1458
+ path?: never;
1459
+ cookie?: never;
1460
+ };
1461
+ requestBody?: never;
1462
+ responses: {
1463
+ 200: {
1464
+ headers: {
1465
+ [name: string]: unknown;
1466
+ };
1467
+ content: {
1468
+ "application/json": components["schemas"]["InstanceShapeResponse"];
1469
+ };
1470
+ };
1471
+ };
1472
+ };
981
1473
  workspaces_api_v1_internal_members_retrieve: {
982
1474
  parameters: {
983
1475
  query?: never;