@fuzdev/fuz_app 0.101.1 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/CLAUDE.md +29 -17
- package/dist/auth/cell_action_specs.d.ts +35 -34
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +23 -8
- package/dist/auth/cell_actions.d.ts +28 -0
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +52 -20
- package/dist/auth/cell_data_schema.d.ts +14 -16
- package/dist/auth/cell_data_schema.d.ts.map +1 -1
- package/dist/auth/cell_data_schema.js +14 -16
- package/dist/auth/role_grant_offer_action_specs.d.ts +56 -3
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +48 -3
- package/dist/auth/role_grant_offer_actions.d.ts +9 -19
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +91 -36
- package/dist/db/CLAUDE.md +8 -5
- package/dist/db/cell_ddl.d.ts +11 -2
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +13 -1
- package/dist/db/cell_queries.d.ts +14 -8
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +12 -11
- package/dist/testing/CLAUDE.md +66 -7
- package/dist/testing/cross_backend/capabilities.d.ts +14 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +3 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +55 -13
- package/dist/testing/cross_backend/cell_gated_create.d.ts +4 -0
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_gated_create.js +71 -0
- package/dist/testing/cross_backend/cell_grant_role.js +4 -4
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +30 -24
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +7 -0
- package/dist/testing/cross_backend/default_spine_surface.d.ts +9 -5
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +12 -6
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/fact_serving.js +2 -2
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +6 -1
- package/dist/testing/cross_backend/role_grant_participation.d.ts +11 -0
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_participation.js +73 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +12 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +12 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +17 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +36 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +5 -2
- package/package.json +1 -1
|
@@ -44,7 +44,7 @@ import { jsonrpc_errors, dev_only } from '../http/jsonrpc_errors.js';
|
|
|
44
44
|
import { is_pg_unique_violation } from '../db/pg_error.js';
|
|
45
45
|
import { has_role } from './request_context.js';
|
|
46
46
|
import { ROLE_ADMIN } from './role_schema.js';
|
|
47
|
-
import { cell_create_action_spec, cell_get_action_spec, cell_update_action_spec, cell_delete_action_spec, cell_list_action_spec, cell_clone_action_spec, ERROR_CELL_NOT_FOUND, ERROR_CELL_PATH_ADMIN_ONLY, ERROR_CELL_PATH_TAKEN, ERROR_CELL_VISIBILITY_MANAGE_ONLY, ERROR_CELL_GET_REQUIRES_ID_OR_PATH,
|
|
47
|
+
import { cell_create_action_spec, cell_get_action_spec, cell_update_action_spec, cell_delete_action_spec, cell_list_action_spec, cell_clone_action_spec, ERROR_CELL_NOT_FOUND, ERROR_CELL_PATH_ADMIN_ONLY, ERROR_CELL_PATH_TAKEN, ERROR_CELL_VISIBILITY_MANAGE_ONLY, ERROR_CELL_GET_REQUIRES_ID_OR_PATH, ERROR_CELL_KIND_IN_DATA, ERROR_CELL_KIND_EMPTY, ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH, ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH, CELL_LIST_LIMIT_DEFAULT, CELL_RELATIONS_BUNDLE_LIMIT, } from './cell_action_specs.js';
|
|
48
48
|
import { query_cell_create, query_cell_get, query_cell_get_by_path, query_cell_update, query_cell_delete, query_cell_list, query_cell_load_many, } from '../db/cell_queries.js';
|
|
49
49
|
import { query_cell_grant_list_for_cell, query_cell_grants_for_caller_in_cells, } from '../db/cell_grant_queries.js';
|
|
50
50
|
import { query_cell_field_list_for_source, query_cell_field_set } from '../db/cell_field_queries.js';
|
|
@@ -66,10 +66,25 @@ const to_iso_nullable = (value) => {
|
|
|
66
66
|
* write is unambiguously a path collision.
|
|
67
67
|
*/
|
|
68
68
|
const path_taken_error = () => jsonrpc_errors.conflict('cell.path is already taken', { reason: ERROR_CELL_PATH_TAKEN });
|
|
69
|
+
/**
|
|
70
|
+
* Reject a `kind` key supplied inside `data`. `kind` is the top-level
|
|
71
|
+
* `cell.kind` column (write-once capability axis), so a copy inside the
|
|
72
|
+
* loose `data` blob would be a second source of truth that silently
|
|
73
|
+
* diverges on update. Fail-loud `invalid_params` at every wire-`data`
|
|
74
|
+
* boundary (create / update / clone-patch).
|
|
75
|
+
*/
|
|
76
|
+
const reject_kind_in_data = (data) => {
|
|
77
|
+
if (Object.hasOwn(data, 'kind')) {
|
|
78
|
+
throw jsonrpc_errors.invalid_params('cell.data must not contain `kind` (it is a top-level field)', {
|
|
79
|
+
reason: ERROR_CELL_KIND_IN_DATA,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
69
83
|
export const to_cell_json = (row) => ({
|
|
70
84
|
id: row.id,
|
|
71
85
|
path: row.path,
|
|
72
86
|
data: row.data,
|
|
87
|
+
kind: row.kind,
|
|
73
88
|
visibility: row.visibility,
|
|
74
89
|
refs: row.refs,
|
|
75
90
|
created_by: row.created_by,
|
|
@@ -92,14 +107,14 @@ const emit_cell_audit = (ctx, event_type, row, deps, auth) => {
|
|
|
92
107
|
ip: ctx.client_ip,
|
|
93
108
|
metadata: {
|
|
94
109
|
cell_id: row.id,
|
|
95
|
-
kind: row.
|
|
110
|
+
kind: row.kind ?? undefined,
|
|
96
111
|
path: row.path,
|
|
97
112
|
},
|
|
98
113
|
});
|
|
99
114
|
};
|
|
100
115
|
/** Create the six generic cell RPC actions. */
|
|
101
116
|
export const create_cell_actions = (deps) => {
|
|
102
|
-
const { validate_data } = deps;
|
|
117
|
+
const { validate_data, authorize_create } = deps;
|
|
103
118
|
/**
|
|
104
119
|
* Run the optional `validate_data` deps callback and convert any thrown
|
|
105
120
|
* `ZodError` into the standard `invalid_params` JSON-RPC error so per-
|
|
@@ -129,9 +144,28 @@ export const create_cell_actions = (deps) => {
|
|
|
129
144
|
reason: ERROR_CELL_PATH_ADMIN_ONLY,
|
|
130
145
|
});
|
|
131
146
|
}
|
|
147
|
+
// `kind` is the top-level column — reject a stray copy inside `data`,
|
|
148
|
+
// and reject an empty `kind` (a tag tags nothing — `null` is the typeless
|
|
149
|
+
// state), so `kind` stays a clean `null | non-empty-string`. Both run
|
|
150
|
+
// after the path-admin gate so an unauthorized caller sees that first.
|
|
151
|
+
reject_kind_in_data(input.data);
|
|
152
|
+
if (input.kind === '') {
|
|
153
|
+
throw jsonrpc_errors.invalid_params('cell.kind must not be empty (use null for a typeless cell)', { reason: ERROR_CELL_KIND_EMPTY });
|
|
154
|
+
}
|
|
132
155
|
// Per-kind shape validation (sub-API). Unknown kinds pass through;
|
|
133
156
|
// known kinds with malformed payloads surface as invalid_params.
|
|
134
157
|
const validated_data = validate_data_or_throw(input.data);
|
|
158
|
+
const kind = input.kind ?? null;
|
|
159
|
+
// Creation authorizer (opt-in): may this actor create this kind? Runs
|
|
160
|
+
// after shape validation, before the insert. Deny → 404 IDOR mask (the
|
|
161
|
+
// established cell-layer deny shape — a gated kind is indistinguishable
|
|
162
|
+
// from a missing resource). Receives the **raw** `input.data` (not the
|
|
163
|
+
// `validate_data`-normalized value) to match the Rust twin, so a
|
|
164
|
+
// consumer with a transforming validator can't diverge TS↔Rust.
|
|
165
|
+
if (authorize_create &&
|
|
166
|
+
!(await authorize_create(auth, { kind, data: input.data, scope_id: null }))) {
|
|
167
|
+
throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
|
|
168
|
+
}
|
|
135
169
|
let row;
|
|
136
170
|
try {
|
|
137
171
|
row = await query_cell_create(ctx, {
|
|
@@ -141,6 +175,7 @@ export const create_cell_actions = (deps) => {
|
|
|
141
175
|
// extend `Json`. The DB column is JSONB; the cast trusts the
|
|
142
176
|
// schema-validated runtime shape.
|
|
143
177
|
data: validated_data,
|
|
178
|
+
kind,
|
|
144
179
|
visibility: input.visibility,
|
|
145
180
|
path: input.path ?? null,
|
|
146
181
|
created_by: auth.actor.id,
|
|
@@ -254,7 +289,11 @@ export const create_cell_actions = (deps) => {
|
|
|
254
289
|
// Per-kind shape validation when `data` is supplied. Patch-only —
|
|
255
290
|
// we don't validate the existing row's data on update (validation
|
|
256
291
|
// is for incoming patches). `data` writes fully replace, so the
|
|
257
|
-
// patch IS the post-update state.
|
|
292
|
+
// patch IS the post-update state. A stray `kind` inside it is rejected
|
|
293
|
+
// (kind is the write-once top-level column — `cell_update` cannot
|
|
294
|
+
// change it, structurally: it is not even a field on `CellUpdateInput`).
|
|
295
|
+
if (input.data !== undefined)
|
|
296
|
+
reject_kind_in_data(input.data);
|
|
258
297
|
const validated_data = input.data !== undefined ? validate_data_or_throw(input.data) : undefined;
|
|
259
298
|
let updated;
|
|
260
299
|
try {
|
|
@@ -312,23 +351,13 @@ export const create_cell_actions = (deps) => {
|
|
|
312
351
|
* server-stamped provenance fields.
|
|
313
352
|
*/
|
|
314
353
|
const clone_one_cell_row = async (ctx, source, auth, options) => {
|
|
354
|
+
// A patch is wire-supplied `data` — reject a stray `kind` (kind is the
|
|
355
|
+
// immutable top-level column; the clone inherits `source.kind`).
|
|
356
|
+
if (options.patch_data !== undefined)
|
|
357
|
+
reject_kind_in_data(options.patch_data);
|
|
315
358
|
// Both `source.data` and `options.patch_data` are CellData (loose
|
|
316
359
|
// objects). Patch-last shallow merge composes cleanly.
|
|
317
360
|
const merged_data = options.patch_data !== undefined ? { ...source.data, ...options.patch_data } : source.data;
|
|
318
|
-
// Block cross-kind patches before per-kind shape validation: a
|
|
319
|
-
// kind-A → kind-B patch can pass `validate_data` coincidentally
|
|
320
|
-
// (loose shapes accept overlapping fields) and produce an
|
|
321
|
-
// incoherent result. Reject when the merged kind diverges from the
|
|
322
|
-
// source's kind. Both undefined or equal kinds pass through;
|
|
323
|
-
// one-sided undefined is permissive (caller patching unrelated
|
|
324
|
-
// fields on a typeless source).
|
|
325
|
-
if (source.data.kind !== undefined &&
|
|
326
|
-
merged_data.kind !== undefined &&
|
|
327
|
-
source.data.kind !== merged_data.kind) {
|
|
328
|
-
throw jsonrpc_errors.invalid_params('cell_clone cannot change kind via with_data_patch', {
|
|
329
|
-
reason: ERROR_CELL_CLONE_KIND_MISMATCH,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
361
|
// Per-kind shape validation runs on the merged result (sub-API).
|
|
333
362
|
// Source rows are validated on their original create; the patch
|
|
334
363
|
// could violate the kind shape (e.g., remove a required field).
|
|
@@ -336,6 +365,9 @@ export const create_cell_actions = (deps) => {
|
|
|
336
365
|
return query_cell_create(ctx, {
|
|
337
366
|
// Boundary cast (see `create_handler`).
|
|
338
367
|
data: validated_data,
|
|
368
|
+
// Clone inherits the source's kind verbatim — kind is fixed at
|
|
369
|
+
// birth and a `with_data_patch` can't reach the column.
|
|
370
|
+
kind: source.kind,
|
|
339
371
|
visibility: source.visibility,
|
|
340
372
|
path: null, // admin-only paths cannot auto-clone
|
|
341
373
|
created_by: auth.actor.id,
|
|
@@ -463,7 +495,7 @@ export const create_cell_actions = (deps) => {
|
|
|
463
495
|
// — emit directly rather than threading it through `emit_cell_audit`.
|
|
464
496
|
// `kind` is read from `source.data` (not the cloned row) so the
|
|
465
497
|
// audit trail attributes the clone to the source's shape.
|
|
466
|
-
const source_kind = source.
|
|
498
|
+
const source_kind = source.kind ?? undefined;
|
|
467
499
|
const cloned_child_count = deep ? cloneable_items.length : 0;
|
|
468
500
|
deps.audit.emit(ctx, {
|
|
469
501
|
event_type: 'cell_clone',
|
|
@@ -505,7 +537,7 @@ export const create_cell_actions = (deps) => {
|
|
|
505
537
|
const caller_actor_id = auth?.actor?.id ?? null;
|
|
506
538
|
const rows = await query_cell_list(ctx, {
|
|
507
539
|
ids: input.ids,
|
|
508
|
-
|
|
540
|
+
kind: input.kind,
|
|
509
541
|
visibility: input.visibility,
|
|
510
542
|
ref: input.ref,
|
|
511
543
|
created_by: input.created_by,
|
|
@@ -11,17 +11,19 @@
|
|
|
11
11
|
* `auth/cell_actions.ts`.
|
|
12
12
|
*
|
|
13
13
|
* **Discipline**: a field joins `CellData` only when at least two
|
|
14
|
-
* consumers in different domains read it generically. `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* stay per-kind.
|
|
14
|
+
* consumers in different domains read it generically. `label` (list/index
|
|
15
|
+
* rendering) and `summary` (card subtitle + share-target description) meet
|
|
16
|
+
* this bar. Future candidates require evidence of generic usage — otherwise
|
|
17
|
+
* they stay per-kind.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* **`kind` is not in here, and is rejected if present.** Like `visibility`,
|
|
20
|
+
* a cell's `kind` is a top-level column (`cell.kind` on `CellJson` /
|
|
21
|
+
* `CellRow`), not content metadata — it is the capability / identity axis a
|
|
22
|
+
* creation authorizer gates on, write-once at birth. A stray `kind` key
|
|
23
|
+
* inside `data` is a fail-loud `ERROR_CELL_KIND_IN_DATA` at the create /
|
|
24
|
+
* update / clone-patch boundary (single source of truth). Access control
|
|
25
|
+
* (`cell.visibility`) is likewise a peer column, enforced by `can_view_cell`
|
|
26
|
+
* reading the column directly (no JSON dive).
|
|
25
27
|
*
|
|
26
28
|
* @module
|
|
27
29
|
*/
|
|
@@ -29,14 +31,10 @@ import { z } from 'zod';
|
|
|
29
31
|
/**
|
|
30
32
|
* Base cell-data shape. All fields optional; loose mode admits arbitrary
|
|
31
33
|
* additional keys so apps can attach metadata or stage new kinds without
|
|
32
|
-
* touching the wire schema.
|
|
33
|
-
*
|
|
34
|
-
* `kind` is optional because cells without a registered kind are valid —
|
|
35
|
-
* admin-curated content, in-development types, or unknown shapes pass
|
|
36
|
-
* through. Known kinds get richer validation via the per-kind sub-API.
|
|
34
|
+
* touching the wire schema. `kind` lives on the top-level `cell.kind`
|
|
35
|
+
* column, not here (see the module doc).
|
|
37
36
|
*/
|
|
38
37
|
export declare const CellData: z.ZodObject<{
|
|
39
|
-
kind: z.ZodOptional<z.ZodString>;
|
|
40
38
|
label: z.ZodOptional<z.ZodString>;
|
|
41
39
|
summary: z.ZodOptional<z.ZodString>;
|
|
42
40
|
}, z.core.$loose>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_data_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_data_schema.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cell_data_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_data_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;GAKG;AACH,eAAO,MAAM,QAAQ;;;iBAGnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC"}
|
|
@@ -11,17 +11,19 @@
|
|
|
11
11
|
* `auth/cell_actions.ts`.
|
|
12
12
|
*
|
|
13
13
|
* **Discipline**: a field joins `CellData` only when at least two
|
|
14
|
-
* consumers in different domains read it generically. `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* stay per-kind.
|
|
14
|
+
* consumers in different domains read it generically. `label` (list/index
|
|
15
|
+
* rendering) and `summary` (card subtitle + share-target description) meet
|
|
16
|
+
* this bar. Future candidates require evidence of generic usage — otherwise
|
|
17
|
+
* they stay per-kind.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* **`kind` is not in here, and is rejected if present.** Like `visibility`,
|
|
20
|
+
* a cell's `kind` is a top-level column (`cell.kind` on `CellJson` /
|
|
21
|
+
* `CellRow`), not content metadata — it is the capability / identity axis a
|
|
22
|
+
* creation authorizer gates on, write-once at birth. A stray `kind` key
|
|
23
|
+
* inside `data` is a fail-loud `ERROR_CELL_KIND_IN_DATA` at the create /
|
|
24
|
+
* update / clone-patch boundary (single source of truth). Access control
|
|
25
|
+
* (`cell.visibility`) is likewise a peer column, enforced by `can_view_cell`
|
|
26
|
+
* reading the column directly (no JSON dive).
|
|
25
27
|
*
|
|
26
28
|
* @module
|
|
27
29
|
*/
|
|
@@ -29,14 +31,10 @@ import { z } from 'zod';
|
|
|
29
31
|
/**
|
|
30
32
|
* Base cell-data shape. All fields optional; loose mode admits arbitrary
|
|
31
33
|
* additional keys so apps can attach metadata or stage new kinds without
|
|
32
|
-
* touching the wire schema.
|
|
33
|
-
*
|
|
34
|
-
* `kind` is optional because cells without a registered kind are valid —
|
|
35
|
-
* admin-curated content, in-development types, or unknown shapes pass
|
|
36
|
-
* through. Known kinds get richer validation via the per-kind sub-API.
|
|
34
|
+
* touching the wire schema. `kind` lives on the top-level `cell.kind`
|
|
35
|
+
* column, not here (see the module doc).
|
|
37
36
|
*/
|
|
38
37
|
export const CellData = z.looseObject({
|
|
39
|
-
kind: z.string().optional(),
|
|
40
38
|
label: z.string().optional(),
|
|
41
39
|
summary: z.string().optional(),
|
|
42
40
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Role grant offer RPC action specs — declarative contract for the
|
|
3
|
-
* consentful-role-grants surface (offer lifecycle + admin revoke
|
|
3
|
+
* consentful-role-grants surface (offer lifecycle + admin revoke + the
|
|
4
|
+
* immediate admin-only `role_grant_assign`).
|
|
4
5
|
*
|
|
5
6
|
* Import this module for the specs, Input/Output schemas, `ERROR_ROLE_GRANT_OFFER_*`
|
|
6
7
|
* reason constants, and the `all_role_grant_offer_action_specs` registry.
|
|
@@ -209,6 +210,27 @@ export declare const RoleGrantRevokeOutput: z.ZodObject<{
|
|
|
209
210
|
revoked: z.ZodLiteral<true>;
|
|
210
211
|
}, z.core.$strict>;
|
|
211
212
|
export type RoleGrantRevokeOutput = z.infer<typeof RoleGrantRevokeOutput>;
|
|
213
|
+
/**
|
|
214
|
+
* Input for `role_grant_assign` — the immediate admin-only conferral path, the
|
|
215
|
+
* consent-free sibling of `role_grant_offer_create`. An admin assigns a
|
|
216
|
+
* role_grant straight onto the target actor; there is no offer for a grantee to
|
|
217
|
+
* accept. No `message` (no offer to carry it on) and no `scope_kind` (`scope_id`
|
|
218
|
+
* alone is the v1 scope discriminator — the grant row's `scope_kind` stays null).
|
|
219
|
+
*/
|
|
220
|
+
export declare const RoleGrantAssignInput: z.ZodObject<{
|
|
221
|
+
to_account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
222
|
+
to_actor_id: z.ZodOptional<z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>>;
|
|
223
|
+
role: z.ZodString;
|
|
224
|
+
scope_id: z.ZodOptional<z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>>;
|
|
225
|
+
acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
226
|
+
}, z.core.$strict>;
|
|
227
|
+
export type RoleGrantAssignInput = z.infer<typeof RoleGrantAssignInput>;
|
|
228
|
+
/** Output for `role_grant_assign`. */
|
|
229
|
+
export declare const RoleGrantAssignOutput: z.ZodObject<{
|
|
230
|
+
ok: z.ZodLiteral<true>;
|
|
231
|
+
role_grant_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
232
|
+
}, z.core.$strict>;
|
|
233
|
+
export type RoleGrantAssignOutput = z.infer<typeof RoleGrantAssignOutput>;
|
|
212
234
|
/**
|
|
213
235
|
* `rate_limit: 'account'` throttles offer-spam at the authenticated
|
|
214
236
|
* grantor and bounds the account-existence oracle on `to_account_id` —
|
|
@@ -447,10 +469,41 @@ export declare const role_grant_revoke_action_spec: {
|
|
|
447
469
|
error_reasons: ("role_not_web_grantable" | "role_grant_not_found")[];
|
|
448
470
|
rate_limit: "account";
|
|
449
471
|
};
|
|
472
|
+
/**
|
|
473
|
+
* `rate_limit: 'account'` bounds admin-side burn of `role_grant_assign` — the
|
|
474
|
+
* action is admin-gated and audit-trailed, but the per-account cap matches
|
|
475
|
+
* `role_grant_revoke` so a single admin script can't churn grants in a loop.
|
|
476
|
+
*/
|
|
477
|
+
export declare const role_grant_assign_action_spec: {
|
|
478
|
+
method: string;
|
|
479
|
+
kind: "request_response";
|
|
480
|
+
initiator: "frontend";
|
|
481
|
+
auth: {
|
|
482
|
+
account: "required";
|
|
483
|
+
actor: "required";
|
|
484
|
+
roles: string[];
|
|
485
|
+
};
|
|
486
|
+
side_effects: true;
|
|
487
|
+
input: z.ZodObject<{
|
|
488
|
+
to_account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
489
|
+
to_actor_id: z.ZodOptional<z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>>;
|
|
490
|
+
role: z.ZodString;
|
|
491
|
+
scope_id: z.ZodOptional<z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>>;
|
|
492
|
+
acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
493
|
+
}, z.core.$strict>;
|
|
494
|
+
output: z.ZodObject<{
|
|
495
|
+
ok: z.ZodLiteral<true>;
|
|
496
|
+
role_grant_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
497
|
+
}, z.core.$strict>;
|
|
498
|
+
async: true;
|
|
499
|
+
description: string;
|
|
500
|
+
error_reasons: ("role_not_web_grantable" | "role_grant_offer_actor_account_mismatch")[];
|
|
501
|
+
rate_limit: "account";
|
|
502
|
+
};
|
|
450
503
|
/**
|
|
451
504
|
* All role-grant-offer action specs — a codegen-ready registry. Consumers spread
|
|
452
|
-
* this into their own action-spec array to include offer lifecycle + revoke
|
|
453
|
-
* methods in a typed client surface.
|
|
505
|
+
* this into their own action-spec array to include offer lifecycle + revoke +
|
|
506
|
+
* assign methods in a typed client surface.
|
|
454
507
|
*/
|
|
455
508
|
export declare const all_role_grant_offer_action_specs: Array<RequestResponseActionSpec>;
|
|
456
509
|
//# sourceMappingURL=role_grant_offer_action_specs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Role grant offer RPC action specs — declarative contract for the
|
|
3
|
-
* consentful-role-grants surface (offer lifecycle + admin revoke
|
|
3
|
+
* consentful-role-grants surface (offer lifecycle + admin revoke + the
|
|
4
|
+
* immediate admin-only `role_grant_assign`).
|
|
4
5
|
*
|
|
5
6
|
* Import this module for the specs, Input/Output schemas, `ERROR_ROLE_GRANT_OFFER_*`
|
|
6
7
|
* reason constants, and the `all_role_grant_offer_action_specs` registry.
|
|
@@ -156,6 +157,31 @@ export const RoleGrantRevokeOutput = z.strictObject({
|
|
|
156
157
|
ok: z.literal(true),
|
|
157
158
|
revoked: z.literal(true),
|
|
158
159
|
});
|
|
160
|
+
/**
|
|
161
|
+
* Input for `role_grant_assign` — the immediate admin-only conferral path, the
|
|
162
|
+
* consent-free sibling of `role_grant_offer_create`. An admin assigns a
|
|
163
|
+
* role_grant straight onto the target actor; there is no offer for a grantee to
|
|
164
|
+
* accept. No `message` (no offer to carry it on) and no `scope_kind` (`scope_id`
|
|
165
|
+
* alone is the v1 scope discriminator — the grant row's `scope_kind` stays null).
|
|
166
|
+
*/
|
|
167
|
+
export const RoleGrantAssignInput = z.strictObject({
|
|
168
|
+
to_account_id: Uuid.meta({ description: 'Account id of the grantee.' }),
|
|
169
|
+
to_actor_id: Uuid.nullish().meta({
|
|
170
|
+
description: "Optional actor-grain target on the grantee account. When set, must belong to `to_account_id`. Omit to resolve the account's sole active actor — a multi-actor account must name one.",
|
|
171
|
+
}),
|
|
172
|
+
role: RoleName.meta({
|
|
173
|
+
description: "Role to assign. Must be admin-grantable (its `grant_paths` includes `'admin'`).",
|
|
174
|
+
}),
|
|
175
|
+
scope_id: Uuid.nullish().meta({
|
|
176
|
+
description: 'Scope id for resource-scoped grants. `null` for a global role_grant.',
|
|
177
|
+
}),
|
|
178
|
+
acting: ActingActor,
|
|
179
|
+
});
|
|
180
|
+
/** Output for `role_grant_assign`. */
|
|
181
|
+
export const RoleGrantAssignOutput = z.strictObject({
|
|
182
|
+
ok: z.literal(true),
|
|
183
|
+
role_grant_id: Uuid,
|
|
184
|
+
});
|
|
159
185
|
// -- Action specs -----------------------------------------------------------
|
|
160
186
|
/**
|
|
161
187
|
* `rate_limit: 'account'` throttles offer-spam at the authenticated
|
|
@@ -267,10 +293,28 @@ export const role_grant_revoke_action_spec = {
|
|
|
267
293
|
error_reasons: [ERROR_ROLE_GRANT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE],
|
|
268
294
|
rate_limit: 'account',
|
|
269
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* `rate_limit: 'account'` bounds admin-side burn of `role_grant_assign` — the
|
|
298
|
+
* action is admin-gated and audit-trailed, but the per-account cap matches
|
|
299
|
+
* `role_grant_revoke` so a single admin script can't churn grants in a loop.
|
|
300
|
+
*/
|
|
301
|
+
export const role_grant_assign_action_spec = {
|
|
302
|
+
method: 'role_grant_assign',
|
|
303
|
+
kind: 'request_response',
|
|
304
|
+
initiator: 'frontend',
|
|
305
|
+
auth: { account: 'required', actor: 'required', roles: ['admin'] },
|
|
306
|
+
side_effects: true,
|
|
307
|
+
input: RoleGrantAssignInput,
|
|
308
|
+
output: RoleGrantAssignOutput,
|
|
309
|
+
async: true,
|
|
310
|
+
description: "Immediately assign a role_grant to a target account — admin-only, no consent step (the capability-unlock UX). The role's `grant_paths` must include `'admin'`. Idempotent: re-assigning an active grant returns it. Emits a role_grant_create audit event.",
|
|
311
|
+
error_reasons: [ERROR_ROLE_NOT_WEB_GRANTABLE, ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH],
|
|
312
|
+
rate_limit: 'account',
|
|
313
|
+
};
|
|
270
314
|
/**
|
|
271
315
|
* All role-grant-offer action specs — a codegen-ready registry. Consumers spread
|
|
272
|
-
* this into their own action-spec array to include offer lifecycle + revoke
|
|
273
|
-
* methods in a typed client surface.
|
|
316
|
+
* this into their own action-spec array to include offer lifecycle + revoke +
|
|
317
|
+
* assign methods in a typed client surface.
|
|
274
318
|
*/
|
|
275
319
|
export const all_role_grant_offer_action_specs = [
|
|
276
320
|
role_grant_offer_create_action_spec,
|
|
@@ -280,4 +324,5 @@ export const all_role_grant_offer_action_specs = [
|
|
|
280
324
|
role_grant_offer_list_action_spec,
|
|
281
325
|
role_grant_offer_history_action_spec,
|
|
282
326
|
role_grant_revoke_action_spec,
|
|
327
|
+
role_grant_assign_action_spec,
|
|
283
328
|
];
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Role grant offer RPC action handlers — the consentful-role-grants action surface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* retract / list / history) plus `role_grant_revoke`
|
|
4
|
+
* Eight actions: six offer-lifecycle methods (create / accept / decline /
|
|
5
|
+
* retract / list / history) plus `role_grant_revoke` and `role_grant_assign`
|
|
6
|
+
* (both admin-only). All mount
|
|
6
7
|
* on a consumer's JSON-RPC endpoint via `create_rpc_endpoint`. The action
|
|
7
8
|
* specs themselves live in `auth/role_grant_offer_action_specs.ts`. Mutations
|
|
8
9
|
* declare `side_effects: true` so the RPC dispatcher wraps the handler in
|
|
@@ -23,6 +24,10 @@
|
|
|
23
24
|
* dispatcher rejects non-admin callers before the handler runs.
|
|
24
25
|
* The admin-grant-path gate prevents revoking keeper / daemon-scoped
|
|
25
26
|
* roles via this surface. Keys on `actor_id` to survive multi-actor accounts.
|
|
27
|
+
* - `role_grant_assign` — spec-level `auth: {role: 'admin'}`; the immediate
|
|
28
|
+
* consent-free conferral path. The admin-grant-path gate prevents assigning
|
|
29
|
+
* keeper / daemon-scoped roles via this surface. No holder-propagation — only
|
|
30
|
+
* admins confer (see participation-gates.md Decision 7).
|
|
26
31
|
*
|
|
27
32
|
* Audit events are emitted in-transaction by the query layer (atomic with
|
|
28
33
|
* the role_grant write on accept/revoke) or by the handler via the bound
|
|
@@ -76,23 +81,8 @@ export interface RoleGrantOfferActionOptions {
|
|
|
76
81
|
authorize?: RoleGrantOfferCreateAuthorize;
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* The admin-grant-path filter in `create_handler` runs **before** the
|
|
83
|
-
* `authorize` callback, so this never sees roles whose `grant_paths`
|
|
84
|
-
* omits `'admin'`. Drop into
|
|
85
|
-
* `create_role_grant_offer_actions({authorize: authorize_admin_or_holder})`
|
|
86
|
-
* (or any factory that forwards `authorize`, e.g. `create_standard_rpc_actions`)
|
|
87
|
-
* for the common "admins offer anything; users offer what they hold"
|
|
88
|
-
* pattern. Scope-aware policies (e.g. classroom_teacher offering
|
|
89
|
-
* classroom_student in their own scope) wrap this and short-circuit `true`
|
|
90
|
-
* before delegating.
|
|
91
|
-
*/
|
|
92
|
-
export declare const authorize_admin_or_holder: RoleGrantOfferCreateAuthorize;
|
|
93
|
-
/**
|
|
94
|
-
* Create the seven role-grant-offer RPC actions (six offer-lifecycle methods
|
|
95
|
-
* plus `role_grant_revoke`).
|
|
84
|
+
* Create the eight role-grant-offer RPC actions (six offer-lifecycle methods
|
|
85
|
+
* plus `role_grant_revoke` and the immediate `role_grant_assign`).
|
|
96
86
|
*
|
|
97
87
|
* @param deps - `RouteFactoryDeps` (`log`, `audit`, …) plus optional `notification_sender` for WS fan-out — when absent, WS fan-out is silently skipped (DB-only side effects still happen). Consumers wiring `BackendWebsocketTransport` assign its instance directly (the transport's `send_to_account` signature accepts the broader `JsonrpcMessageFromServerToClient`, which is contravariantly compatible)
|
|
98
88
|
* @param options - role schema, default TTL, authorization override
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAIH,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,EAA4C,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAoC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AAgCD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAC/C,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAkiBjB,CAAC"}
|