@fuzdev/fuz_app 0.42.0 → 0.44.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 (34) hide show
  1. package/dist/actions/CLAUDE.md +77 -0
  2. package/dist/actions/action_rpc.d.ts.map +1 -1
  3. package/dist/actions/action_rpc.js +14 -7
  4. package/dist/actions/frontend_rpc_client.d.ts +74 -0
  5. package/dist/actions/frontend_rpc_client.d.ts.map +1 -0
  6. package/dist/actions/frontend_rpc_client.js +61 -0
  7. package/dist/actions/rpc_client.d.ts +64 -0
  8. package/dist/actions/rpc_client.d.ts.map +1 -1
  9. package/dist/actions/rpc_client.js +77 -0
  10. package/dist/auth/CLAUDE.md +32 -21
  11. package/dist/auth/account_action_specs.d.ts +8 -8
  12. package/dist/auth/account_action_specs.js +4 -4
  13. package/dist/auth/admin_action_specs.d.ts +8 -8
  14. package/dist/auth/admin_action_specs.js +4 -4
  15. package/dist/auth/self_service_role_action_specs.d.ts +20 -48
  16. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  17. package/dist/auth/self_service_role_action_specs.js +22 -44
  18. package/dist/auth/self_service_role_actions.d.ts +9 -9
  19. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  20. package/dist/auth/self_service_role_actions.js +48 -53
  21. package/dist/auth/standard_action_specs.d.ts +31 -0
  22. package/dist/auth/standard_action_specs.d.ts.map +1 -0
  23. package/dist/auth/standard_action_specs.js +36 -0
  24. package/dist/http/schema_helpers.d.ts +9 -0
  25. package/dist/http/schema_helpers.d.ts.map +1 -1
  26. package/dist/http/schema_helpers.js +9 -0
  27. package/dist/testing/admin_integration.js +9 -9
  28. package/dist/testing/audit_completeness.js +3 -3
  29. package/dist/testing/integration.js +36 -36
  30. package/dist/testing/rpc_helpers.d.ts +14 -6
  31. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  32. package/dist/testing/rpc_helpers.js +8 -5
  33. package/dist/ui/admin_rpc_adapters.js +4 -4
  34. package/package.json +1 -1
@@ -13,9 +13,9 @@ import { AuthSessionJson, ClientApiTokenJson, SessionAccountJson } from './accou
13
13
  import { ApiTokenId } from './api_token.js';
14
14
  // -- Input/output schemas ---------------------------------------------------
15
15
  /** Input for `account_verify`. No parameters — the caller is the subject. */
16
- export const VerifyInput = z.null();
16
+ export const VerifyInput = z.void();
17
17
  /** Input for `account_session_list`. No parameters. */
18
- export const SessionListInput = z.null();
18
+ export const SessionListInput = z.void();
19
19
  /** Output for `account_session_list`. */
20
20
  export const SessionListOutput = z.strictObject({
21
21
  sessions: z.array(AuthSessionJson),
@@ -30,7 +30,7 @@ export const SessionRevokeOutput = z.strictObject({
30
30
  revoked: z.boolean(),
31
31
  });
32
32
  /** Input for `account_session_revoke_all`. No parameters. */
33
- export const SessionRevokeAllInput = z.null();
33
+ export const SessionRevokeAllInput = z.void();
34
34
  /** Output for `account_session_revoke_all`. */
35
35
  export const SessionRevokeAllOutput = z.strictObject({
36
36
  ok: z.literal(true),
@@ -51,7 +51,7 @@ export const TokenCreateOutput = z.strictObject({
51
51
  name: z.string(),
52
52
  });
53
53
  /** Input for `account_token_list`. No parameters. */
54
- export const TokenListInput = z.null();
54
+ export const TokenListInput = z.void();
55
55
  /** Output for `account_token_list`. Hashes are excluded. */
56
56
  export const TokenListOutput = z.strictObject({
57
57
  tokens: z.array(ClientApiTokenJson),
@@ -20,7 +20,7 @@ import type { RequestResponseActionSpec } from '../actions/action_spec.js';
20
20
  /** Max audit-log page size. Mirrors the former REST route's clamp. */
21
21
  export declare const AUDIT_LOG_LIST_LIMIT_MAX = 200;
22
22
  /** Input for `admin_account_list`. No parameters — the caller is the subject. */
23
- export declare const AdminAccountListInput: z.ZodNull;
23
+ export declare const AdminAccountListInput: z.ZodVoid;
24
24
  export type AdminAccountListInput = z.infer<typeof AdminAccountListInput>;
25
25
  /** Output for `admin_account_list`. */
26
26
  export declare const AdminAccountListOutput: z.ZodObject<{
@@ -60,7 +60,7 @@ export declare const AdminAccountListOutput: z.ZodObject<{
60
60
  }, z.core.$strict>;
61
61
  export type AdminAccountListOutput = z.infer<typeof AdminAccountListOutput>;
62
62
  /** Input for `admin_session_list`. No parameters — reads every active session. */
63
- export declare const AdminSessionListInput: z.ZodNull;
63
+ export declare const AdminSessionListInput: z.ZodVoid;
64
64
  export type AdminSessionListInput = z.infer<typeof AdminSessionListInput>;
65
65
  /** Output for `admin_session_list`. Cross-account listing; fan-out already scoped by role auth. */
66
66
  export declare const AdminSessionListOutput: z.ZodObject<{
@@ -183,7 +183,7 @@ export declare const InviteCreateOutput: z.ZodObject<{
183
183
  }, z.core.$strict>;
184
184
  export type InviteCreateOutput = z.infer<typeof InviteCreateOutput>;
185
185
  /** Input for `invite_list`. */
186
- export declare const InviteListInput: z.ZodNull;
186
+ export declare const InviteListInput: z.ZodVoid;
187
187
  export type InviteListInput = z.infer<typeof InviteListInput>;
188
188
  /** Output for `invite_list`. Uses the enriched row including creator/claimer usernames. */
189
189
  export declare const InviteListOutput: z.ZodObject<{
@@ -211,7 +211,7 @@ export declare const InviteDeleteOutput: z.ZodObject<{
211
211
  }, z.core.$strict>;
212
212
  export type InviteDeleteOutput = z.infer<typeof InviteDeleteOutput>;
213
213
  /** Input for `app_settings_get`. No parameters. */
214
- export declare const AppSettingsGetInput: z.ZodNull;
214
+ export declare const AppSettingsGetInput: z.ZodVoid;
215
215
  export type AppSettingsGetInput = z.infer<typeof AppSettingsGetInput>;
216
216
  /** Output for `app_settings_get`. */
217
217
  export declare const AppSettingsGetOutput: z.ZodObject<{
@@ -247,7 +247,7 @@ export declare const admin_account_list_action_spec: {
247
247
  role: string;
248
248
  };
249
249
  side_effects: false;
250
- input: z.ZodNull;
250
+ input: z.ZodVoid;
251
251
  output: z.ZodObject<{
252
252
  accounts: z.ZodArray<z.ZodObject<{
253
253
  account: z.ZodObject<{
@@ -294,7 +294,7 @@ export declare const admin_session_list_action_spec: {
294
294
  role: string;
295
295
  };
296
296
  side_effects: false;
297
- input: z.ZodNull;
297
+ input: z.ZodVoid;
298
298
  output: z.ZodObject<{
299
299
  sessions: z.ZodArray<z.ZodObject<{
300
300
  id: z.ZodString;
@@ -454,7 +454,7 @@ export declare const invite_list_action_spec: {
454
454
  role: string;
455
455
  };
456
456
  side_effects: false;
457
- input: z.ZodNull;
457
+ input: z.ZodVoid;
458
458
  output: z.ZodObject<{
459
459
  invites: z.ZodArray<z.ZodObject<{
460
460
  id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
@@ -496,7 +496,7 @@ export declare const app_settings_get_action_spec: {
496
496
  role: string;
497
497
  };
498
498
  side_effects: false;
499
- input: z.ZodNull;
499
+ input: z.ZodVoid;
500
500
  output: z.ZodObject<{
501
501
  settings: z.ZodObject<{
502
502
  open_signup: z.ZodBoolean;
@@ -26,14 +26,14 @@ import { AppSettingsWithUsernameJson } from './app_settings_schema.js';
26
26
  export const AUDIT_LOG_LIST_LIMIT_MAX = 200;
27
27
  // -- Input/output schemas ---------------------------------------------------
28
28
  /** Input for `admin_account_list`. No parameters — the caller is the subject. */
29
- export const AdminAccountListInput = z.null();
29
+ export const AdminAccountListInput = z.void();
30
30
  /** Output for `admin_account_list`. */
31
31
  export const AdminAccountListOutput = z.strictObject({
32
32
  accounts: z.array(AdminAccountEntryJson),
33
33
  grantable_roles: z.array(RoleName),
34
34
  });
35
35
  /** Input for `admin_session_list`. No parameters — reads every active session. */
36
- export const AdminSessionListInput = z.null();
36
+ export const AdminSessionListInput = z.void();
37
37
  /** Output for `admin_session_list`. Cross-account listing; fan-out already scoped by role auth. */
38
38
  export const AdminSessionListOutput = z.strictObject({
39
39
  sessions: z.array(AdminSessionJson),
@@ -116,7 +116,7 @@ export const InviteCreateOutput = z.strictObject({
116
116
  invite: InviteJson,
117
117
  });
118
118
  /** Input for `invite_list`. */
119
- export const InviteListInput = z.null();
119
+ export const InviteListInput = z.void();
120
120
  /** Output for `invite_list`. Uses the enriched row including creator/claimer usernames. */
121
121
  export const InviteListOutput = z.strictObject({
122
122
  invites: z.array(InviteWithUsernamesJson),
@@ -130,7 +130,7 @@ export const InviteDeleteOutput = z.strictObject({
130
130
  ok: z.literal(true),
131
131
  });
132
132
  /** Input for `app_settings_get`. No parameters. */
133
- export const AppSettingsGetInput = z.null();
133
+ export const AppSettingsGetInput = z.void();
134
134
  /** Output for `app_settings_get`. */
135
135
  export const AppSettingsGetOutput = z.strictObject({
136
136
  settings: AppSettingsWithUsernameJson,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Self-service role grant/revoke action specs — schemas, error reasons,
2
+ * Unified self-service role toggle action spec — schemas, error reasons,
3
3
  * and the codegen-ready registry.
4
4
  *
5
5
  * Client-safe: no query-layer or audit-write imports. Handler factory
@@ -11,54 +11,24 @@ import { z } from 'zod';
11
11
  import type { RequestResponseActionSpec } from '../actions/action_spec.js';
12
12
  /** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
13
13
  export declare const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE: "role_not_self_service_eligible";
14
- /** Input for `self_service_role_grant`. */
15
- export declare const SelfServiceRoleGrantInput: z.ZodObject<{
14
+ /** Input for `self_service_role_set`. */
15
+ export declare const SelfServiceRoleSetInput: z.ZodObject<{
16
16
  role: z.ZodString;
17
+ enabled: z.ZodBoolean;
17
18
  }, z.core.$strict>;
18
- export type SelfServiceRoleGrantInput = z.infer<typeof SelfServiceRoleGrantInput>;
19
+ export type SelfServiceRoleSetInput = z.infer<typeof SelfServiceRoleSetInput>;
19
20
  /**
20
- * Output for `self_service_role_grant`. `granted` is `false` on idempotent
21
- * re-grant (caller already held the role globally); `permit_id` is set on
22
- * new grants only.
21
+ * Output for `self_service_role_set`. `enabled` echoes the post-call state
22
+ * (always equals the input `enabled` on success). `changed` is `true` only
23
+ * when the call mutated — re-grants / re-revokes return `false`.
23
24
  */
24
- export declare const SelfServiceRoleGrantOutput: z.ZodObject<{
25
+ export declare const SelfServiceRoleSetOutput: z.ZodObject<{
25
26
  ok: z.ZodLiteral<true>;
26
- granted: z.ZodBoolean;
27
- permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
27
+ enabled: z.ZodBoolean;
28
+ changed: z.ZodBoolean;
28
29
  }, z.core.$strict>;
29
- export type SelfServiceRoleGrantOutput = z.infer<typeof SelfServiceRoleGrantOutput>;
30
- /** Input for `self_service_role_revoke`. */
31
- export declare const SelfServiceRoleRevokeInput: z.ZodObject<{
32
- role: z.ZodString;
33
- }, z.core.$strict>;
34
- export type SelfServiceRoleRevokeInput = z.infer<typeof SelfServiceRoleRevokeInput>;
35
- /**
36
- * Output for `self_service_role_revoke`. `revoked` is `false` when the
37
- * caller held no active global permit for the role (idempotent).
38
- */
39
- export declare const SelfServiceRoleRevokeOutput: z.ZodObject<{
40
- ok: z.ZodLiteral<true>;
41
- revoked: z.ZodBoolean;
42
- }, z.core.$strict>;
43
- export type SelfServiceRoleRevokeOutput = z.infer<typeof SelfServiceRoleRevokeOutput>;
44
- export declare const self_service_role_grant_action_spec: {
45
- method: string;
46
- kind: "request_response";
47
- initiator: "frontend";
48
- auth: "authenticated";
49
- side_effects: true;
50
- input: z.ZodObject<{
51
- role: z.ZodString;
52
- }, z.core.$strict>;
53
- output: z.ZodObject<{
54
- ok: z.ZodLiteral<true>;
55
- granted: z.ZodBoolean;
56
- permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
57
- }, z.core.$strict>;
58
- async: true;
59
- description: string;
60
- };
61
- export declare const self_service_role_revoke_action_spec: {
30
+ export type SelfServiceRoleSetOutput = z.infer<typeof SelfServiceRoleSetOutput>;
31
+ export declare const self_service_role_set_action_spec: {
62
32
  method: string;
63
33
  kind: "request_response";
64
34
  initiator: "frontend";
@@ -66,18 +36,20 @@ export declare const self_service_role_revoke_action_spec: {
66
36
  side_effects: true;
67
37
  input: z.ZodObject<{
68
38
  role: z.ZodString;
39
+ enabled: z.ZodBoolean;
69
40
  }, z.core.$strict>;
70
41
  output: z.ZodObject<{
71
42
  ok: z.ZodLiteral<true>;
72
- revoked: z.ZodBoolean;
43
+ enabled: z.ZodBoolean;
44
+ changed: z.ZodBoolean;
73
45
  }, z.core.$strict>;
74
46
  async: true;
75
47
  description: string;
76
48
  };
77
49
  /**
78
- * All self-service role action specs — a codegen-ready registry. Method
79
- * names are static, so consumer typed-client codegen picks them up the
80
- * same way it picks up `account_*_action_specs`.
50
+ * All self-service role action specs — a codegen-ready registry. Single-element
51
+ * post-unification, kept for symmetry with the other `all_*_action_specs`
52
+ * exports so codegen and frontend bundles import the same shape.
81
53
  */
82
- export declare const all_self_service_role_action_specs: Array<RequestResponseActionSpec>;
54
+ export declare const all_self_service_role_action_specs: ReadonlyArray<RequestResponseActionSpec>;
83
55
  //# sourceMappingURL=self_service_role_action_specs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAGzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB;;kBAEpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;kBAIrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;kBAErC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;kBAGtC,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEtF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;CAWX,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,KAAK,CAAC,yBAAyB,CAG/E,CAAC"}
1
+ {"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAGzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,yCAAyC;AACzC,eAAO,MAAM,uBAAuB;;;kBAMlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;kBAInC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;CAWT,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,aAAa,CAAC,yBAAyB,CAEvF,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Self-service role grant/revoke action specs — schemas, error reasons,
2
+ * Unified self-service role toggle action spec — schemas, error reasons,
3
3
  * and the codegen-ready registry.
4
4
  *
5
5
  * Client-safe: no query-layer or audit-write imports. Handler factory
@@ -8,64 +8,42 @@
8
8
  * @module
9
9
  */
10
10
  import { z } from 'zod';
11
- import { Uuid } from '@fuzdev/fuz_util/id.js';
12
11
  import { RoleName } from './role_schema.js';
13
12
  /** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
14
13
  export const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE = 'role_not_self_service_eligible';
15
- /** Input for `self_service_role_grant`. */
16
- export const SelfServiceRoleGrantInput = z.strictObject({
17
- role: RoleName.meta({ description: 'Role to self-grant. Must be in the configured allowlist.' }),
14
+ /** Input for `self_service_role_set`. */
15
+ export const SelfServiceRoleSetInput = z.strictObject({
16
+ role: RoleName.meta({ description: 'Role to toggle. Must be in the configured allowlist.' }),
17
+ enabled: z.boolean().meta({
18
+ description: 'Desired post-call state. `true` grants if not held; `false` revokes if held. Idempotent in both directions.',
19
+ }),
18
20
  });
19
21
  /**
20
- * Output for `self_service_role_grant`. `granted` is `false` on idempotent
21
- * re-grant (caller already held the role globally); `permit_id` is set on
22
- * new grants only.
22
+ * Output for `self_service_role_set`. `enabled` echoes the post-call state
23
+ * (always equals the input `enabled` on success). `changed` is `true` only
24
+ * when the call mutated — re-grants / re-revokes return `false`.
23
25
  */
24
- export const SelfServiceRoleGrantOutput = z.strictObject({
26
+ export const SelfServiceRoleSetOutput = z.strictObject({
25
27
  ok: z.literal(true),
26
- granted: z.boolean(),
27
- permit_id: Uuid.optional(),
28
+ enabled: z.boolean(),
29
+ changed: z.boolean(),
28
30
  });
29
- /** Input for `self_service_role_revoke`. */
30
- export const SelfServiceRoleRevokeInput = z.strictObject({
31
- role: RoleName.meta({ description: 'Role to self-revoke. Must be in the configured allowlist.' }),
32
- });
33
- /**
34
- * Output for `self_service_role_revoke`. `revoked` is `false` when the
35
- * caller held no active global permit for the role (idempotent).
36
- */
37
- export const SelfServiceRoleRevokeOutput = z.strictObject({
38
- ok: z.literal(true),
39
- revoked: z.boolean(),
40
- });
41
- export const self_service_role_grant_action_spec = {
42
- method: 'self_service_role_grant',
43
- kind: 'request_response',
44
- initiator: 'frontend',
45
- auth: 'authenticated',
46
- side_effects: true,
47
- input: SelfServiceRoleGrantInput,
48
- output: SelfServiceRoleGrantOutput,
49
- async: true,
50
- description: 'Self-grant an active permit for an allowlisted role. Idempotent — already-granted callers receive `granted: false`.',
51
- };
52
- export const self_service_role_revoke_action_spec = {
53
- method: 'self_service_role_revoke',
31
+ export const self_service_role_set_action_spec = {
32
+ method: 'self_service_role_set',
54
33
  kind: 'request_response',
55
34
  initiator: 'frontend',
56
35
  auth: 'authenticated',
57
36
  side_effects: true,
58
- input: SelfServiceRoleRevokeInput,
59
- output: SelfServiceRoleRevokeOutput,
37
+ input: SelfServiceRoleSetInput,
38
+ output: SelfServiceRoleSetOutput,
60
39
  async: true,
61
- description: 'Self-revoke an active global permit for an allowlisted role. Idempotent callers without an active permit receive `revoked: false`.',
40
+ description: 'Toggle a self-service role. Idempotent in both directions `changed: false` when post-call state already matched the request.',
62
41
  };
63
42
  /**
64
- * All self-service role action specs — a codegen-ready registry. Method
65
- * names are static, so consumer typed-client codegen picks them up the
66
- * same way it picks up `account_*_action_specs`.
43
+ * All self-service role action specs — a codegen-ready registry. Single-element
44
+ * post-unification, kept for symmetry with the other `all_*_action_specs`
45
+ * exports so codegen and frontend bundles import the same shape.
67
46
  */
68
47
  export const all_self_service_role_action_specs = [
69
- self_service_role_grant_action_spec,
70
- self_service_role_revoke_action_spec,
48
+ self_service_role_set_action_spec,
71
49
  ];
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Self-service role grant/revoke RPC actions.
2
+ * Unified self-service role toggle RPC action.
3
3
  *
4
- * Two static `request_response` actions — `self_service_role_grant` and
5
- * `self_service_role_revoke` — that take `{role}` as input and toggle a
6
- * permit on the caller for an allowlisted role. Idempotent in both
7
- * directions: re-granting an already-held role returns `granted: false`;
8
- * revoking a role the caller doesn't hold returns `revoked: false`.
4
+ * One static `request_response` action — `self_service_role_set` — that
5
+ * takes `{role, enabled}` and toggles a global permit on the caller for an
6
+ * allowlisted role. Idempotent in both directions: re-enabling an
7
+ * already-held role returns `changed: false`; disabling a role the caller
8
+ * doesn't hold returns `changed: false`.
9
9
  *
10
10
  * The factory takes an `eligible_roles` allowlist (validated against the
11
11
  * supplied `roles.role_options` at factory time so typos surface at startup
@@ -19,8 +19,8 @@
19
19
  * part of the documented schema surface and is round-trip-validated by
20
20
  * `query_audit_log`.
21
21
  *
22
- * Static method names — `role` lives in the input, not the method name —
23
- * so specs are codegen-compatible (`satisfies RequestResponseActionSpec`)
22
+ * Static method name — `role` lives in the input, not the method name —
23
+ * so the spec is codegen-compatible (`satisfies RequestResponseActionSpec`)
24
24
  * and the surface stays constant as consumers add eligible roles. Mirrors
25
25
  * the existing `permit_offer_create({role})` precedent rather than
26
26
  * generating per-role methods.
@@ -57,7 +57,7 @@ export interface SelfServiceRoleActionsOptions {
57
57
  */
58
58
  export type SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
59
59
  /**
60
- * Build the self-service role grant/revoke RPC actions.
60
+ * Build the unified self-service role toggle RPC action.
61
61
  *
62
62
  * @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
63
63
  * @param options - eligible-role allowlist plus optional role schema for typo-checking
@@ -1 +1 @@
1
- {"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAmBhD,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC3C,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C,CAAC;AAOF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CAqHjB,CAAC"}
1
+ {"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAgBhD,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC3C,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C,CAAC;AAOF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CA4GjB,CAAC"}
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Self-service role grant/revoke RPC actions.
2
+ * Unified self-service role toggle RPC action.
3
3
  *
4
- * Two static `request_response` actions — `self_service_role_grant` and
5
- * `self_service_role_revoke` — that take `{role}` as input and toggle a
6
- * permit on the caller for an allowlisted role. Idempotent in both
7
- * directions: re-granting an already-held role returns `granted: false`;
8
- * revoking a role the caller doesn't hold returns `revoked: false`.
4
+ * One static `request_response` action — `self_service_role_set` — that
5
+ * takes `{role, enabled}` and toggles a global permit on the caller for an
6
+ * allowlisted role. Idempotent in both directions: re-enabling an
7
+ * already-held role returns `changed: false`; disabling a role the caller
8
+ * doesn't hold returns `changed: false`.
9
9
  *
10
10
  * The factory takes an `eligible_roles` allowlist (validated against the
11
11
  * supplied `roles.role_options` at factory time so typos surface at startup
@@ -19,8 +19,8 @@
19
19
  * part of the documented schema surface and is round-trip-validated by
20
20
  * `query_audit_log`.
21
21
  *
22
- * Static method names — `role` lives in the input, not the method name —
23
- * so specs are codegen-compatible (`satisfies RequestResponseActionSpec`)
22
+ * Static method name — `role` lives in the input, not the method name —
23
+ * so the spec is codegen-compatible (`satisfies RequestResponseActionSpec`)
24
24
  * and the surface stays constant as consumers add eligible roles. Mirrors
25
25
  * the existing `permit_offer_create({role})` precedent rather than
26
26
  * generating per-role methods.
@@ -35,14 +35,14 @@ import { rpc_action } from '../actions/action_rpc.js';
35
35
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
36
36
  import { query_grant_permit, query_permit_find_active_for_actor, query_permit_has_role, query_revoke_permit, } from './permit_queries.js';
37
37
  import { audit_log_fire_and_forget } from './audit_log_queries.js';
38
- import { ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE, self_service_role_grant_action_spec, self_service_role_revoke_action_spec, } from './self_service_role_action_specs.js';
38
+ import { ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE, self_service_role_set_action_spec, } from './self_service_role_action_specs.js';
39
39
  const require_request_auth = (auth) => {
40
40
  if (!auth)
41
41
  throw new Error('unreachable: action auth guard did not enforce authentication');
42
42
  return auth;
43
43
  };
44
44
  /**
45
- * Build the self-service role grant/revoke RPC actions.
45
+ * Build the unified self-service role toggle RPC action.
46
46
  *
47
47
  * @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
48
48
  * @param options - eligible-role allowlist plus optional role schema for typo-checking
@@ -65,45 +65,43 @@ export const create_self_service_role_actions = (deps, options) => {
65
65
  });
66
66
  }
67
67
  };
68
- const grant_handler = async (input, ctx) => {
68
+ const handler = async (input, ctx) => {
69
69
  const auth = require_request_auth(ctx.auth);
70
70
  reject_if_ineligible(input.role);
71
- // Pre-check for idempotent re-grant. `query_grant_permit` is itself
72
- // idempotent (returns the existing permit instead of inserting), but
73
- // it doesn't signal "already existed" vs "newly inserted" — so we
74
- // peek first. The TOCTOU window is benign for self-service: two
75
- // concurrent grants both observe "no permit", both call
76
- // `query_grant_permit`, and one collapses onto the other inside the
77
- // query's `ON CONFLICT DO NOTHING`. Worst case both responses report
78
- // `granted: true`; the DB still ends up with exactly one permit.
79
- const already = await query_permit_has_role(ctx, auth.actor.id, input.role);
80
- if (already) {
81
- return { ok: true, granted: false };
71
+ if (input.enabled) {
72
+ // Pre-check for idempotent re-grant. `query_grant_permit` is itself
73
+ // idempotent (returns the existing permit instead of inserting), but
74
+ // it doesn't signal "already existed" vs "newly inserted" so we
75
+ // peek first. The TOCTOU window is benign for self-service: two
76
+ // concurrent grants both observe "no permit", both call
77
+ // `query_grant_permit`, and one collapses onto the other inside the
78
+ // query's `ON CONFLICT DO NOTHING`. Worst case both responses report
79
+ // `changed: true`; the DB still ends up with exactly one permit.
80
+ const already = await query_permit_has_role(ctx, auth.actor.id, input.role);
81
+ if (already) {
82
+ return { ok: true, enabled: true, changed: false };
83
+ }
84
+ const permit = await query_grant_permit(ctx, {
85
+ actor_id: auth.actor.id,
86
+ role: input.role,
87
+ scope_id: null,
88
+ expires_at: null,
89
+ granted_by: auth.actor.id,
90
+ });
91
+ void audit_log_fire_and_forget(ctx, {
92
+ event_type: 'permit_grant',
93
+ actor_id: auth.actor.id,
94
+ account_id: auth.account.id,
95
+ ip: ctx.client_ip,
96
+ metadata: {
97
+ role: permit.role,
98
+ permit_id: permit.id,
99
+ scope_id: permit.scope_id,
100
+ self_service: true,
101
+ },
102
+ }, deps);
103
+ return { ok: true, enabled: true, changed: true };
82
104
  }
83
- const permit = await query_grant_permit(ctx, {
84
- actor_id: auth.actor.id,
85
- role: input.role,
86
- scope_id: null,
87
- expires_at: null,
88
- granted_by: auth.actor.id,
89
- });
90
- void audit_log_fire_and_forget(ctx, {
91
- event_type: 'permit_grant',
92
- actor_id: auth.actor.id,
93
- account_id: auth.account.id,
94
- ip: ctx.client_ip,
95
- metadata: {
96
- role: permit.role,
97
- permit_id: permit.id,
98
- scope_id: permit.scope_id,
99
- self_service: true,
100
- },
101
- }, deps);
102
- return { ok: true, granted: true, permit_id: permit.id };
103
- };
104
- const revoke_handler = async (input, ctx) => {
105
- const auth = require_request_auth(ctx.auth);
106
- reject_if_ineligible(input.role);
107
105
  // Find an active global permit for this (actor, role). No dedicated
108
106
  // query exists, but `query_permit_find_active_for_actor` returns the
109
107
  // short list of every active permit and we filter in JS — fewer
@@ -111,12 +109,12 @@ export const create_self_service_role_actions = (deps, options) => {
111
109
  const active = await query_permit_find_active_for_actor(ctx, auth.actor.id);
112
110
  const target = active.find((p) => p.role === input.role && p.scope_id === null);
113
111
  if (!target) {
114
- return { ok: true, revoked: false };
112
+ return { ok: true, enabled: false, changed: false };
115
113
  }
116
114
  const result = await query_revoke_permit(ctx, target.id, auth.actor.id, auth.actor.id);
117
115
  if (!result) {
118
116
  // Raced with another revoker — treat as already revoked.
119
- return { ok: true, revoked: false };
117
+ return { ok: true, enabled: false, changed: false };
120
118
  }
121
119
  void audit_log_fire_and_forget(ctx, {
122
120
  event_type: 'permit_revoke',
@@ -130,10 +128,7 @@ export const create_self_service_role_actions = (deps, options) => {
130
128
  self_service: true,
131
129
  },
132
130
  }, deps);
133
- return { ok: true, revoked: true };
131
+ return { ok: true, enabled: false, changed: true };
134
132
  };
135
- return [
136
- rpc_action(self_service_role_grant_action_spec, grant_handler),
137
- rpc_action(self_service_role_revoke_action_spec, revoke_handler),
138
- ];
133
+ return [rpc_action(self_service_role_set_action_spec, handler)];
139
134
  };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Aggregate spec list mirroring `create_standard_rpc_actions` on the backend.
3
+ *
4
+ * `create_standard_rpc_actions` (in `./standard_rpc_actions.js`) bundles three
5
+ * action registries into one mounted RPC surface: admin + permit_offer +
6
+ * account. Frontends mounting that surface need the matching spec list to
7
+ * feed `create_rpc_client` so the typed Proxy knows about every standard
8
+ * method.
9
+ *
10
+ * Without this aggregate, every consumer spreads three (or four with
11
+ * self-service roles) `all_*_action_specs` imports at the typed-client
12
+ * site, the codegen-sources table, and any other registry construction —
13
+ * a triplicate that drifts silently on either side.
14
+ *
15
+ * Self-service role specs are **not** included — they're opt-in (require
16
+ * `eligible_roles` configuration) and not bundled into
17
+ * `create_standard_rpc_actions`. Consumers that mount them spread
18
+ * `all_self_service_role_action_specs` separately.
19
+ *
20
+ * @module
21
+ */
22
+ import type { RequestResponseActionSpec } from '../actions/action_spec.js';
23
+ /**
24
+ * Combined spec registry for the standard RPC surface (admin +
25
+ * permit_offer + account). Symmetric with `create_standard_rpc_actions`.
26
+ *
27
+ * Spec count is the sum of the three sub-registries. Adding a method to
28
+ * any sub-registry surfaces here automatically.
29
+ */
30
+ export declare const all_standard_action_specs: ReadonlyArray<RequestResponseActionSpec>;
31
+ //# sourceMappingURL=standard_action_specs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAKzE;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,yBAAyB,CAI9E,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Aggregate spec list mirroring `create_standard_rpc_actions` on the backend.
3
+ *
4
+ * `create_standard_rpc_actions` (in `./standard_rpc_actions.js`) bundles three
5
+ * action registries into one mounted RPC surface: admin + permit_offer +
6
+ * account. Frontends mounting that surface need the matching spec list to
7
+ * feed `create_rpc_client` so the typed Proxy knows about every standard
8
+ * method.
9
+ *
10
+ * Without this aggregate, every consumer spreads three (or four with
11
+ * self-service roles) `all_*_action_specs` imports at the typed-client
12
+ * site, the codegen-sources table, and any other registry construction —
13
+ * a triplicate that drifts silently on either side.
14
+ *
15
+ * Self-service role specs are **not** included — they're opt-in (require
16
+ * `eligible_roles` configuration) and not bundled into
17
+ * `create_standard_rpc_actions`. Consumers that mount them spread
18
+ * `all_self_service_role_action_specs` separately.
19
+ *
20
+ * @module
21
+ */
22
+ import { all_admin_action_specs } from './admin_action_specs.js';
23
+ import { all_permit_offer_action_specs } from './permit_offer_action_specs.js';
24
+ import { all_account_action_specs } from './account_action_specs.js';
25
+ /**
26
+ * Combined spec registry for the standard RPC surface (admin +
27
+ * permit_offer + account). Symmetric with `create_standard_rpc_actions`.
28
+ *
29
+ * Spec count is the sum of the three sub-registries. Adding a method to
30
+ * any sub-registry surfaces here automatically.
31
+ */
32
+ export const all_standard_action_specs = [
33
+ ...all_admin_action_specs,
34
+ ...all_permit_offer_action_specs,
35
+ ...all_account_action_specs,
36
+ ];
@@ -18,6 +18,15 @@ import { type RateLimitKey, type RouteErrorSchemas } from './error_schemas.js';
18
18
  * but also accept other values.
19
19
  */
20
20
  export declare const is_null_schema: (schema: z.ZodType) => boolean;
21
+ /**
22
+ * Check if a schema is exactly `z.void()`.
23
+ *
24
+ * RPC action specs use `z.void()` to declare a parameterless method —
25
+ * JSON-RPC 2.0 forbids `params: null` (params must be omitted or be a
26
+ * Structured value), so `z.void()` is the correct schema for "no params"
27
+ * and the dispatcher maps absent params to `undefined` for these specs.
28
+ */
29
+ export declare const is_void_schema: (schema: z.ZodType) => boolean;
21
30
  /**
22
31
  * Check if a schema is a strict object (`z.strictObject()`).
23
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAuB,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAEnG;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAUtB,CAAC"}
1
+ {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAuB,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAEnG;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAUtB,CAAC"}