@plantops/contracts 0.1.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 (60) hide show
  1. package/README.md +11 -0
  2. package/dist/audit.d.ts +149 -0
  3. package/dist/audit.d.ts.map +1 -0
  4. package/dist/audit.js +87 -0
  5. package/dist/bindings.d.ts +125 -0
  6. package/dist/bindings.d.ts.map +1 -0
  7. package/dist/bindings.js +40 -0
  8. package/dist/clients.d.ts +240 -0
  9. package/dist/clients.d.ts.map +1 -0
  10. package/dist/clients.js +49 -0
  11. package/dist/constants.d.ts +91 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +92 -0
  14. package/dist/entitlements.d.ts +135 -0
  15. package/dist/entitlements.d.ts.map +1 -0
  16. package/dist/entitlements.js +43 -0
  17. package/dist/errors.d.ts +68 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +83 -0
  20. package/dist/grants.d.ts +70 -0
  21. package/dist/grants.d.ts.map +1 -0
  22. package/dist/grants.js +13 -0
  23. package/dist/index.d.ts +34 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +33 -0
  26. package/dist/jwt.d.ts +155 -0
  27. package/dist/jwt.d.ts.map +1 -0
  28. package/dist/jwt.js +49 -0
  29. package/dist/lib/contracts.d.ts +2 -0
  30. package/dist/lib/contracts.d.ts.map +1 -0
  31. package/dist/lib/contracts.js +3 -0
  32. package/dist/manifest.d.ts +136 -0
  33. package/dist/manifest.d.ts.map +1 -0
  34. package/dist/manifest.js +22 -0
  35. package/dist/nav.d.ts +53 -0
  36. package/dist/nav.d.ts.map +1 -0
  37. package/dist/nav.js +25 -0
  38. package/dist/pagination.d.ts +28 -0
  39. package/dist/pagination.d.ts.map +1 -0
  40. package/dist/pagination.js +27 -0
  41. package/dist/registry.d.ts +183 -0
  42. package/dist/registry.d.ts.map +1 -0
  43. package/dist/registry.js +24 -0
  44. package/dist/roles.d.ts +215 -0
  45. package/dist/roles.d.ts.map +1 -0
  46. package/dist/roles.js +45 -0
  47. package/dist/scopes.d.ts +220 -0
  48. package/dist/scopes.d.ts.map +1 -0
  49. package/dist/scopes.js +108 -0
  50. package/dist/service-accounts.d.ts +81 -0
  51. package/dist/service-accounts.d.ts.map +1 -0
  52. package/dist/service-accounts.js +35 -0
  53. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  54. package/dist/type-assertions.d.ts +18 -0
  55. package/dist/type-assertions.d.ts.map +1 -0
  56. package/dist/type-assertions.js +8 -0
  57. package/dist/users.d.ts +364 -0
  58. package/dist/users.d.ts.map +1 -0
  59. package/dist/users.js +134 -0
  60. package/package.json +39 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Registry contract — the platform-admin catalog surface (Doc 06 §4).
3
+ *
4
+ * Three tables, one vocabulary: `application` is what a module *is*,
5
+ * `permission` is what it can be granted, `nav_node` is what it shows. All three
6
+ * are **catalog**, not tenant data (Doc 07 §6): they carry no `client_id`, every
7
+ * authenticated subject can read them, and only a platform admin writes them.
8
+ *
9
+ * ## Natural keys, and why the DTOs carry both halves
10
+ *
11
+ * Every row here has two identifiers: a uuid, and a `key` that is unique within
12
+ * its parent (`application.key` globally, `permission.key` and `nav_node.key`
13
+ * per application). Both are returned, deliberately. The uuid is what a
14
+ * `role_permission` or `menu_permission` row references; the key is what a
15
+ * manifest is written against (Doc 02 §2) and what an operator recognises. A
16
+ * contract that returned only one of them would force every consumer to keep its
17
+ * own lookup table to get back the other.
18
+ *
19
+ * Field naming is snake_case, matching {@link ServiceAccountDTO} and the bodies
20
+ * Doc 06 §3 writes out — these are published shapes, and a convention that
21
+ * changes between two endpoints of the same API is worse than either
22
+ * convention.
23
+ */
24
+ import type { NavNodeKind } from './nav.js';
25
+ /**
26
+ * One registered application (Doc 01 §3.1).
27
+ *
28
+ * `is_active = false` is the global off switch of Doc 02 §7: the app is hidden
29
+ * everywhere and **all of its data is preserved**. There is no delete, because a
30
+ * deleted application would cascade away the permissions that existing role
31
+ * mappings and audit rows still refer to.
32
+ */
33
+ export interface ApplicationDTO {
34
+ id: string;
35
+ /** Machine key, e.g. `gatepass`. Globally unique and never editable. */
36
+ key: string;
37
+ name: string;
38
+ description: string | null;
39
+ is_active: boolean;
40
+ /** Free-form per-application settings (Doc 01 §3.1). */
41
+ config: Record<string, unknown>;
42
+ /** ISO-8601. */
43
+ created_at: string;
44
+ updated_at: string;
45
+ }
46
+ /**
47
+ * One atomic permission (Doc 01 §3.2).
48
+ *
49
+ * The key is the dotted `app.resource.action` form Doc 02 §2 writes; the pair
50
+ * `(application_id, key)` is unique, which is the 409 in Doc 06 §4.
51
+ */
52
+ export interface PermissionDTO {
53
+ id: string;
54
+ application_id: string;
55
+ key: string;
56
+ name: string;
57
+ description: string | null;
58
+ is_active: boolean;
59
+ /** ISO-8601. */
60
+ created_at: string;
61
+ updated_at: string;
62
+ }
63
+ /**
64
+ * One nav node as the **catalog** holds it (Doc 01 §3.3).
65
+ *
66
+ * Distinct from {@link NavNodeDTO}, which is the *resolved* tree a subject sees
67
+ * (Doc 05 §4): that one is pruned to what the subject may see and therefore
68
+ * omits `is_active`, `is_public` and `requires`, because a pruned tree has
69
+ * already answered the question those fields exist to decide. This one is the
70
+ * platform admin's view of what exists, visible or not.
71
+ */
72
+ export interface NavNodeCatalogDTO {
73
+ id: string;
74
+ application_id: string;
75
+ /** `null` = top level. A parent always belongs to the same application. */
76
+ parent_id: string | null;
77
+ kind: NavNodeKind;
78
+ /** Unique within the application; the manifest upsert's natural key. */
79
+ key: string;
80
+ label: string;
81
+ route: string | null;
82
+ /** Icon *key* — the frontend maps it to its own icon set (Doc 05 §7). */
83
+ icon: string | null;
84
+ sort_order: number;
85
+ is_active: boolean;
86
+ /** Leaf-only opt-in: an unmapped leaf is visible anyway (Doc 05 §3). */
87
+ is_public: boolean;
88
+ /**
89
+ * Keys of the permissions mapped to this node, OR semantics (Doc 01 §4.4).
90
+ *
91
+ * Included so the tree is inspectable in one call: the mapping is the whole
92
+ * point of a nav node, and a catalog view that omits it sends every caller
93
+ * back for a second request to find out whether a node is reachable at all.
94
+ */
95
+ requires: string[];
96
+ /** ISO-8601. */
97
+ created_at: string;
98
+ updated_at: string;
99
+ children: NavNodeCatalogDTO[];
100
+ }
101
+ /** `GET /iam/applications/:id/nav` — the application's full catalog tree. */
102
+ export interface NavCatalogResponse {
103
+ application_id: string;
104
+ /** Top-level nodes, each with its descendants, ordered by `sort_order`. */
105
+ tree: NavNodeCatalogDTO[];
106
+ }
107
+ /** `POST /iam/applications` body (Doc 06 §4). */
108
+ export interface CreateApplicationRequest {
109
+ key: string;
110
+ name: string;
111
+ description?: string;
112
+ config?: Record<string, unknown>;
113
+ }
114
+ /**
115
+ * `PATCH /iam/applications/:id` body (Doc 06 §4) — update, or activate /
116
+ * deactivate.
117
+ *
118
+ * `key` is absent on purpose: it is the natural key every manifest upload and
119
+ * every permission address is written against (Doc 02 §2), so renaming it would
120
+ * silently re-point an entire catalog.
121
+ */
122
+ export interface UpdateApplicationRequest {
123
+ name?: string;
124
+ description?: string | null;
125
+ is_active?: boolean;
126
+ config?: Record<string, unknown>;
127
+ }
128
+ /** One entry of the `POST /iam/applications/:id/permissions` body. */
129
+ export interface CreatePermissionInput {
130
+ key: string;
131
+ name: string;
132
+ description?: string;
133
+ }
134
+ /** `POST /iam/applications/:id/permissions` body — bulk (Doc 02 §2 step 2). */
135
+ export interface CreatePermissionsRequest {
136
+ permissions: CreatePermissionInput[];
137
+ }
138
+ /**
139
+ * One entry of the `POST /iam/applications/:id/nav` body.
140
+ *
141
+ * The parent is named by **either** `parent_id` or `parent_key`, never both.
142
+ * `parent_key` may name a node created earlier in the same request, which is
143
+ * what lets one call build a module and its menus in the order Doc 02 §2 step 3
144
+ * describes.
145
+ */
146
+ export interface CreateNavNodeInput {
147
+ kind: NavNodeKind;
148
+ key: string;
149
+ label: string;
150
+ route?: string;
151
+ icon?: string;
152
+ sort_order?: number;
153
+ is_public?: boolean;
154
+ parent_id?: string;
155
+ parent_key?: string;
156
+ }
157
+ /** `POST /iam/applications/:id/nav` body — bulk (Doc 02 §2 step 3). */
158
+ export interface CreateNavNodesRequest {
159
+ nodes: CreateNavNodeInput[];
160
+ }
161
+ /** One nav node and the permission keys being mapped to (or unmapped from) it. */
162
+ export interface NavPermissionMapping {
163
+ nav_key: string;
164
+ permission_keys: string[];
165
+ }
166
+ /**
167
+ * `POST` / `DELETE /iam/applications/:id/nav-permissions` body (Doc 02 §2 step 4).
168
+ *
169
+ * Both directions take the same shape because they are the same statement in
170
+ * opposite directions, and both are idempotent: mapping what is already mapped
171
+ * and unmapping what is not are no-ops that audit nothing.
172
+ */
173
+ export interface NavPermissionsRequest {
174
+ mappings: NavPermissionMapping[];
175
+ }
176
+ /** What a mapping call changed — the counts, plus the pairs it touched. */
177
+ export interface NavPermissionsResult {
178
+ /** Rows actually inserted (`POST`) or deleted (`DELETE`). */
179
+ changed: number;
180
+ /** Pairs already in the requested state, which changed nothing. */
181
+ unchanged: number;
182
+ }
183
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Registry contract — the platform-admin catalog surface (Doc 06 §4).
3
+ *
4
+ * Three tables, one vocabulary: `application` is what a module *is*,
5
+ * `permission` is what it can be granted, `nav_node` is what it shows. All three
6
+ * are **catalog**, not tenant data (Doc 07 §6): they carry no `client_id`, every
7
+ * authenticated subject can read them, and only a platform admin writes them.
8
+ *
9
+ * ## Natural keys, and why the DTOs carry both halves
10
+ *
11
+ * Every row here has two identifiers: a uuid, and a `key` that is unique within
12
+ * its parent (`application.key` globally, `permission.key` and `nav_node.key`
13
+ * per application). Both are returned, deliberately. The uuid is what a
14
+ * `role_permission` or `menu_permission` row references; the key is what a
15
+ * manifest is written against (Doc 02 §2) and what an operator recognises. A
16
+ * contract that returned only one of them would force every consumer to keep its
17
+ * own lookup table to get back the other.
18
+ *
19
+ * Field naming is snake_case, matching {@link ServiceAccountDTO} and the bodies
20
+ * Doc 06 §3 writes out — these are published shapes, and a convention that
21
+ * changes between two endpoints of the same API is worse than either
22
+ * convention.
23
+ */
24
+ export {};
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Role contract — the WHAT of the access equation (Doc 01 §4.2, Doc 06 §7).
3
+ *
4
+ * A role is a tenant-defined **bundle of catalog permissions**. It is the only
5
+ * one of the three dimensions a client composes for itself: the permissions come
6
+ * from the platform's catalog (Doc 02 §2), the scope nodes come from the client's
7
+ * own org tree ({@link ScopeNodeDTO}), and a `role_binding` ties a subject to one
8
+ * of each (Doc 01 §4.5).
9
+ *
10
+ * ## Why a role carries no permissions inline
11
+ *
12
+ * {@link RoleDTO} reports how many permissions it maps, not which — the mapping
13
+ * is its own endpoint, `GET /iam/roles/:id/permissions`, returning
14
+ * {@link RolePermissionsResponse}. Two reasons, and the second is the real one:
15
+ *
16
+ * - a role list is a list of names and counts (Doc 09 §3.2), and inlining a
17
+ * two-hundred-key permission set into every row of it is a payload nobody
18
+ * reads; and
19
+ * - the mapping needs *more* than the permission — the owning application, and
20
+ * whether that application is still enabled for the tenant — which the role
21
+ * list has no business carrying.
22
+ *
23
+ * ## What "enabled" has to do with any of this
24
+ *
25
+ * Doc 02 §6: a role may only map permissions belonging to applications **enabled
26
+ * for that role's client**. That is a rule about the moment a mapping is
27
+ * *created*. It is deliberately not a rule about the moment one is *read*,
28
+ * because Doc 02 §7 preserves mappings when an application is disabled — they go
29
+ * inert, not away, so that re-enabling is a toggle rather than a re-setup. So
30
+ * {@link RolePermissionDTO} publishes `application_enabled`: a mapped permission
31
+ * that grants nothing today is a thing an operator has to be able to see, and a
32
+ * response that silently omitted it would make a disabled application look like
33
+ * a role someone had quietly edited.
34
+ *
35
+ * Field naming is snake_case, matching every other published shape here.
36
+ */
37
+ /**
38
+ * How many permissions one `PUT /iam/roles/:id/permissions` may set.
39
+ *
40
+ * The same bound the catalog's own bulk seed uses, and for the same reason: the
41
+ * whole array is validated and written inside one request transaction, so an
42
+ * unbounded body is an unbounded transaction. A role that needs more than two
43
+ * hundred atomic permissions is a role that wants splitting.
44
+ */
45
+ export declare const MAX_PERMISSIONS_PER_ROLE = 200;
46
+ /**
47
+ * One tenant-defined role (Doc 01 §4.2).
48
+ *
49
+ * `is_system` marks a role the tenant did not create and may not rename or
50
+ * delete — today that is the `Client Admin` role seeded with the tenant's first
51
+ * administrator (Doc 02 §3 step 3). Its *permissions* are still set through the
52
+ * ordinary endpoint, because a system role with no way to be given permissions
53
+ * would be a role that can never do anything.
54
+ *
55
+ * The two counts are Doc 09 §3.2's list columns, which is the whole reason they
56
+ * are on the DTO rather than left to the caller to assemble.
57
+ */
58
+ export interface RoleDTO {
59
+ id: string;
60
+ client_id: string;
61
+ /** Unique within the client — `unique (client_id, name)` (migration 0003). */
62
+ name: string;
63
+ description: string | null;
64
+ /** Seeded by the platform; not renamable and not deletable by the tenant. */
65
+ is_system: boolean;
66
+ /** Rows in `role_permission`, enabled or not. Doc 09 §3.2's "# permissions". */
67
+ permission_count: number;
68
+ /**
69
+ * Distinct subjects bound to this role anywhere in the tree — Doc 09 §3.2's
70
+ * "# users bound".
71
+ *
72
+ * Distinct *subjects*, not bindings: one user bound at three plants is one
73
+ * person with access, which is what the column is read as. It counts service
74
+ * accounts too, since a machine identity holding a role is exactly as much a
75
+ * consequence of deleting it (Doc 01 §4.5).
76
+ */
77
+ bound_subject_count: number;
78
+ /** ISO-8601. */
79
+ created_at: string;
80
+ updated_at: string;
81
+ }
82
+ /**
83
+ * One permission as a role maps it (Doc 01 §4.3).
84
+ *
85
+ * A {@link PermissionDTO} widened with the two facts that decide whether the
86
+ * mapping currently grants anything: which application it belongs to, and
87
+ * whether that application is still enabled for this tenant. Both are joined in
88
+ * rather than left for the caller to fetch, because the permission picker of
89
+ * Doc 09 §3.2 groups by application and cannot render a row without them.
90
+ */
91
+ export interface RolePermissionDTO {
92
+ id: string;
93
+ key: string;
94
+ name: string;
95
+ description: string | null;
96
+ /** The catalog's own switch — `false` once a manifest retires the key (Doc 02 §7). */
97
+ is_active: boolean;
98
+ application_id: string;
99
+ application_key: string;
100
+ application_name: string;
101
+ /**
102
+ * Whether the owning application is enabled for this role's client.
103
+ *
104
+ * `false` means the mapping is **inert but preserved** (Doc 02 §7): it grants
105
+ * nothing today and grants again the moment the application is re-enabled.
106
+ * A new mapping cannot be created in this state — that is the Doc 02 §6 rule —
107
+ * but an existing one is never silently dropped.
108
+ */
109
+ application_enabled: boolean;
110
+ }
111
+ /**
112
+ * `GET` / `PUT /iam/roles/:id/permissions` → the role's whole mapping
113
+ * (Doc 06 §7).
114
+ *
115
+ * Not paginated, for the reason `/iam/permissions/resolve` is not: this is one
116
+ * editable unit — a picker either shows the whole selection or shows a lie —
117
+ * and it is bounded by {@link MAX_PERMISSIONS_PER_ROLE} rather than by a page
118
+ * size. Ordered by application key, then permission key, so the grouping the UI
119
+ * renders is the order it arrives in.
120
+ */
121
+ export interface RolePermissionsResponse {
122
+ role_id: string;
123
+ permissions: RolePermissionDTO[];
124
+ }
125
+ /**
126
+ * `GET /iam/roles/permission-catalog` → everything a role of this tenant may be
127
+ * given (Doc 06 §7, Doc 09 §3.2).
128
+ *
129
+ * ## Why this exists at all
130
+ *
131
+ * Doc 09 §3.2's permission picker offers "only apps enabled for the client", and
132
+ * until this endpoint there was no way for a tenant administrator to find out
133
+ * what those were: the catalog lives behind `iam.platform.permission.read`, which
134
+ * is platform authority no client admin holds, and
135
+ * `GET /iam/roles/:id/permissions` answers a narrower question — what one role
136
+ * already carries. A picker built from that could only ever offer what was
137
+ * already chosen.
138
+ *
139
+ * ## Every entry is one `PUT /iam/roles/:id/permissions` will accept
140
+ *
141
+ * The set is exactly the four conditions that endpoint validates (Doc 02 §6):
142
+ * the permission is active, its application is active in the registry, and that
143
+ * application is enabled for this client. Anything the picker offers is
144
+ * therefore mappable, which is what makes a 409 from a save a real conflict —
145
+ * something changed underneath — rather than the ordinary result of choosing a
146
+ * row that was on screen.
147
+ *
148
+ * ## It reuses {@link RolePermissionDTO}
149
+ *
150
+ * `is_active` and `application_enabled` are constant `true` for every entry
151
+ * here, by construction. They are on the wire anyway so that a picker can hold
152
+ * catalog entries and a role's own *inert* mappings — a preserved
153
+ * `role_permission` whose application is currently disabled (Doc 02 §7) — in one
154
+ * list without a union type. Those two are the rows that differ, and they are
155
+ * the rows the screen has to render differently.
156
+ */
157
+ export interface PermissionCatalogResponse {
158
+ /** The caller's tenant, from the token — never from a query parameter. */
159
+ client_id: string;
160
+ /** Ordered by application name, then by permission key. */
161
+ permissions: RolePermissionDTO[];
162
+ }
163
+ /**
164
+ * `POST /iam/roles` body (Doc 06 §7).
165
+ *
166
+ * The client is the caller's own, taken from the token's `cid` and from nowhere
167
+ * else — there is no `client_id` field here and there will not be one, for the
168
+ * reason `/iam/scopes` has no `?clientId=`: it would look like it selected a
169
+ * tenant while RLS quietly ignored it.
170
+ *
171
+ * `is_system` is likewise absent. A tenant that could set it would be able to
172
+ * make a role it cannot afterwards delete.
173
+ */
174
+ export interface CreateRoleRequest {
175
+ name: string;
176
+ description?: string;
177
+ }
178
+ /**
179
+ * `PATCH /iam/roles/:id` body — Doc 06 §7's "rename", plus the description.
180
+ *
181
+ * The description rides along because it is the same row's editable prose and
182
+ * Doc 10 §4 gives roles a `role.updated` action to record it under — the test
183
+ * `updateScopeNodeSchema` fails, which is why the scope tree's PATCH is narrower
184
+ * than this one rather than the other way round.
185
+ *
186
+ * Permissions are **not** here. They are a `PUT` of their own, because setting
187
+ * them is a replacement of a set with cross-tenant validation attached
188
+ * (Doc 02 §6), not a field of the role.
189
+ */
190
+ export interface UpdateRoleRequest {
191
+ name?: string;
192
+ description?: string;
193
+ }
194
+ /**
195
+ * `PUT /iam/roles/:id/permissions` body (Doc 06 §7).
196
+ *
197
+ * A `PUT` of the whole set, not a pair of add/remove calls: "which permissions
198
+ * does this role have" is one question with one answer, and an editor that saves
199
+ * a picker's state has exactly one set to send. An empty array is legal and
200
+ * means what it says — the role maps nothing.
201
+ *
202
+ * ## Ids, not keys
203
+ *
204
+ * A permission `key` is unique only within its application (`unique
205
+ * (application_id, key)`), so a bare key names a permission only if you already
206
+ * know which application is meant. Accepting keys would therefore mean accepting
207
+ * pairs, and a role editor gets its ids from
208
+ * `GET /iam/applications/:id/permissions` — the same call that renders the
209
+ * picker — so the pair would be assembled from data the caller already holds and
210
+ * then taken apart again on arrival.
211
+ */
212
+ export interface SetRolePermissionsRequest {
213
+ permission_ids: string[];
214
+ }
215
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../src/roles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sFAAsF;IACtF,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,yBAAyB;IACxC,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B"}
package/dist/roles.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Role contract — the WHAT of the access equation (Doc 01 §4.2, Doc 06 §7).
3
+ *
4
+ * A role is a tenant-defined **bundle of catalog permissions**. It is the only
5
+ * one of the three dimensions a client composes for itself: the permissions come
6
+ * from the platform's catalog (Doc 02 §2), the scope nodes come from the client's
7
+ * own org tree ({@link ScopeNodeDTO}), and a `role_binding` ties a subject to one
8
+ * of each (Doc 01 §4.5).
9
+ *
10
+ * ## Why a role carries no permissions inline
11
+ *
12
+ * {@link RoleDTO} reports how many permissions it maps, not which — the mapping
13
+ * is its own endpoint, `GET /iam/roles/:id/permissions`, returning
14
+ * {@link RolePermissionsResponse}. Two reasons, and the second is the real one:
15
+ *
16
+ * - a role list is a list of names and counts (Doc 09 §3.2), and inlining a
17
+ * two-hundred-key permission set into every row of it is a payload nobody
18
+ * reads; and
19
+ * - the mapping needs *more* than the permission — the owning application, and
20
+ * whether that application is still enabled for the tenant — which the role
21
+ * list has no business carrying.
22
+ *
23
+ * ## What "enabled" has to do with any of this
24
+ *
25
+ * Doc 02 §6: a role may only map permissions belonging to applications **enabled
26
+ * for that role's client**. That is a rule about the moment a mapping is
27
+ * *created*. It is deliberately not a rule about the moment one is *read*,
28
+ * because Doc 02 §7 preserves mappings when an application is disabled — they go
29
+ * inert, not away, so that re-enabling is a toggle rather than a re-setup. So
30
+ * {@link RolePermissionDTO} publishes `application_enabled`: a mapped permission
31
+ * that grants nothing today is a thing an operator has to be able to see, and a
32
+ * response that silently omitted it would make a disabled application look like
33
+ * a role someone had quietly edited.
34
+ *
35
+ * Field naming is snake_case, matching every other published shape here.
36
+ */
37
+ /**
38
+ * How many permissions one `PUT /iam/roles/:id/permissions` may set.
39
+ *
40
+ * The same bound the catalog's own bulk seed uses, and for the same reason: the
41
+ * whole array is validated and written inside one request transaction, so an
42
+ * unbounded body is an unbounded transaction. A role that needs more than two
43
+ * hundred atomic permissions is a role that wants splitting.
44
+ */
45
+ export const MAX_PERMISSIONS_PER_ROLE = 200;