@zhama/tego-plugin-sdk 0.0.0-stage → 1.36.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 (98) hide show
  1. package/LICENSE +172 -0
  2. package/README.md +846 -2
  3. package/access-catalog.schema.json +304 -0
  4. package/access-fixtures/resolve.json +1359 -0
  5. package/bin/tego-plugin-pack.mjs +2 -0
  6. package/bin/tego-plugin.mjs +3 -0
  7. package/dist/access-drizzle.cjs +1 -0
  8. package/dist/access-drizzle.d.cts +726 -0
  9. package/dist/access-drizzle.d.ts +726 -0
  10. package/dist/access-drizzle.js +1 -0
  11. package/dist/access-hono.cjs +3 -0
  12. package/dist/access-hono.d.cts +108 -0
  13. package/dist/access-hono.d.ts +108 -0
  14. package/dist/access-hono.js +3 -0
  15. package/dist/access-implications.cjs +1 -0
  16. package/dist/access-implications.d.cts +25 -0
  17. package/dist/access-implications.d.ts +25 -0
  18. package/dist/access-implications.js +1 -0
  19. package/dist/access.cjs +26 -0
  20. package/dist/access.d.cts +884 -0
  21. package/dist/access.d.ts +884 -0
  22. package/dist/access.js +26 -0
  23. package/dist/catalog-CSSbrfEf.d.cts +361 -0
  24. package/dist/catalog-CSSbrfEf.d.ts +361 -0
  25. package/dist/chunk-3KUOBGJN.js +19 -0
  26. package/dist/chunk-3WA3M347.cjs +1 -0
  27. package/dist/chunk-5CU33JVY.js +1 -0
  28. package/dist/chunk-7K37PVGV.js +5 -0
  29. package/dist/chunk-7XHF3YSK.js +1 -0
  30. package/dist/chunk-AJY7ZHXK.cjs +3 -0
  31. package/dist/chunk-BN6TGHSB.cjs +1 -0
  32. package/dist/chunk-BTHXQWHT.js +1 -0
  33. package/dist/chunk-C2OCOPZQ.cjs +1 -0
  34. package/dist/chunk-IMAVYQ4U.cjs +4 -0
  35. package/dist/chunk-OP64Y4IN.cjs +1 -0
  36. package/dist/chunk-QKSPPMVN.js +1 -0
  37. package/dist/chunk-ROFUQLOZ.cjs +1 -0
  38. package/dist/chunk-SNUCO2MK.js +1 -0
  39. package/dist/chunk-THMWRUAT.cjs +19 -0
  40. package/dist/chunk-VMCGDDGP.js +3 -0
  41. package/dist/chunk-W7C357A6.cjs +5 -0
  42. package/dist/chunk-WLTASJF6.js +4 -0
  43. package/dist/egress.cjs +7 -0
  44. package/dist/egress.d.cts +9 -0
  45. package/dist/egress.d.ts +9 -0
  46. package/dist/egress.js +7 -0
  47. package/dist/frontend.cjs +2 -0
  48. package/dist/frontend.d.cts +353 -0
  49. package/dist/frontend.d.ts +353 -0
  50. package/dist/frontend.js +2 -0
  51. package/dist/gateway-signature.cjs +1 -0
  52. package/dist/gateway-signature.d.cts +112 -0
  53. package/dist/gateway-signature.d.ts +112 -0
  54. package/dist/gateway-signature.js +1 -0
  55. package/dist/index.cjs +1 -0
  56. package/dist/index.d.cts +528 -0
  57. package/dist/index.d.ts +528 -0
  58. package/dist/index.js +1 -0
  59. package/dist/manifest-types-DQARCQJt.d.cts +627 -0
  60. package/dist/manifest-types-DQARCQJt.d.ts +627 -0
  61. package/dist/manifest.cjs +1 -0
  62. package/dist/manifest.d.cts +2824 -0
  63. package/dist/manifest.d.ts +2824 -0
  64. package/dist/manifest.js +1 -0
  65. package/dist/mcp-identity.cjs +2 -0
  66. package/dist/mcp-identity.d.cts +106 -0
  67. package/dist/mcp-identity.d.ts +106 -0
  68. package/dist/mcp-identity.js +2 -0
  69. package/dist/pack.cjs +1 -0
  70. package/dist/pack.d.cts +169 -0
  71. package/dist/pack.d.ts +169 -0
  72. package/dist/pack.js +1 -0
  73. package/dist/publish.cjs +1 -0
  74. package/dist/publish.d.cts +96 -0
  75. package/dist/publish.d.ts +96 -0
  76. package/dist/publish.js +1 -0
  77. package/dist/scaffold.cjs +523 -0
  78. package/dist/scaffold.d.cts +182 -0
  79. package/dist/scaffold.d.ts +182 -0
  80. package/dist/scaffold.js +523 -0
  81. package/dist/server-runtime.cjs +1 -0
  82. package/dist/server-runtime.d.cts +63 -0
  83. package/dist/server-runtime.d.ts +63 -0
  84. package/dist/server-runtime.js +1 -0
  85. package/dist/server.cjs +1 -0
  86. package/dist/server.d.cts +1808 -0
  87. package/dist/server.d.ts +1808 -0
  88. package/dist/server.js +1 -0
  89. package/dist/testing.cjs +2 -0
  90. package/dist/testing.d.cts +92 -0
  91. package/dist/testing.d.ts +92 -0
  92. package/dist/testing.js +2 -0
  93. package/dist/vite.cjs +1 -0
  94. package/dist/vite.d.cts +133 -0
  95. package/dist/vite.d.ts +133 -0
  96. package/dist/vite.js +1 -0
  97. package/package.json +160 -4
  98. package/sql/access.sql +73 -0
@@ -0,0 +1,884 @@
1
+ import { D as DelegationAction, G as Grants, A as AccessCatalog, a as GrantValue, T as TypedAccessCatalog, P as PermissionOf } from './catalog-CSSbrfEf.cjs';
2
+ export { b as AccessCatalogError, c as AccessCatalogInput, d as AgentPolicy, B as BUILTIN_PRINCIPAL_KINDS, C as CatalogIssue, e as CatalogPermission, f as CatalogRole, g as DELEGATION_ACTIONS, h as GLOBAL_DOMAIN, H as HOST_ADMIN_ROLE, R as RESERVED_GROUP, i as RESERVED_PERMISSIONS, j as accessCatalogFromJson, k as accessCatalogSchema, l as defineAccess, m as grantProblem, v as validateAccessCatalog } from './catalog-CSSbrfEf.cjs';
3
+ import { PluginUserContext } from './frontend.cjs';
4
+ import { PluginHostApi } from './server.cjs';
5
+ import { z } from 'zod';
6
+ export { expandImplications, mergeGrant, scopeRank } from './access-implications.cjs';
7
+
8
+ /**
9
+ * The pure core of the decision engine: given who the caller is and what the
10
+ * store holds, which permissions do they have, and why.
11
+ *
12
+ * No I/O here. The store loads rows, the principal sources say which units or
13
+ * groups the caller belongs to, and this module only combines them. That is
14
+ * what lets the contract fixtures — shared with the Python SDK — and the
15
+ * property tests exercise the real semantics without a database.
16
+ */
17
+
18
+ interface AccessSubject {
19
+ tenantId: string;
20
+ userId: string;
21
+ /** The host's role names for the person, as the gateway signed them. */
22
+ hostRoles: readonly string[];
23
+ /** Set when a digital employee acts for `userId`. */
24
+ agent?: {
25
+ avatarId: string;
26
+ delegation: readonly DelegationAction[];
27
+ };
28
+ }
29
+ declare function subjectFromContext(ctx: PluginUserContext): AccessSubject;
30
+ interface AccessDomain {
31
+ type: string;
32
+ id: string;
33
+ }
34
+ interface AccessRole {
35
+ id: string;
36
+ /** The catalog key of a built-in role; null for a custom one. */
37
+ key: string | null;
38
+ builtin: boolean;
39
+ /** A custom role usable only inside this domain; null for a global role. */
40
+ domain: AccessDomain | null;
41
+ name: string;
42
+ description: string;
43
+ grants: Grants;
44
+ catalogVersion: number;
45
+ createdAt: string;
46
+ updatedAt: string;
47
+ updatedBy: string | null;
48
+ }
49
+ interface AssignmentPrincipal {
50
+ kind: string;
51
+ id: string;
52
+ /** For a unit or a group: also everyone in the units below it. */
53
+ includeDescendants: boolean;
54
+ /** For a unit: only members holding one of these unit roles. Null for all. */
55
+ unitRoles: string[] | null;
56
+ }
57
+ interface AccessAssignment {
58
+ id: string;
59
+ roleId: string;
60
+ principal: AssignmentPrincipal;
61
+ domain: AccessDomain | null;
62
+ createdBy: string;
63
+ createdAt: string;
64
+ }
65
+ /** A unit or group the subject belongs to. */
66
+ interface PrincipalMembership {
67
+ kind: string;
68
+ id: string;
69
+ /** Enclosing units, for assignments made with `includeDescendants`. */
70
+ ancestors: readonly string[];
71
+ /** The subject's role inside it, matched against `unitRoles`. */
72
+ role?: string;
73
+ }
74
+ type GrantSourceKind = 'assignment' | 'hostRole' | 'unassigned';
75
+ interface GrantSource {
76
+ roleId: string;
77
+ roleKey: string | null;
78
+ source: GrantSourceKind;
79
+ /** The assignment's principal, or the host role name. */
80
+ principal?: {
81
+ kind: string;
82
+ id: string;
83
+ };
84
+ hostRole?: string;
85
+ domain?: AccessDomain | null;
86
+ /** An assignment made at an ancestor of the resolved domain. */
87
+ inherited?: true;
88
+ }
89
+ type DecisionReason = 'granted' | 'not_granted' | 'scope_mismatch' | 'agent_forbidden' | 'delegation_missing';
90
+ interface Decision {
91
+ allowed: boolean;
92
+ permission: string;
93
+ /** The scope held, for a scoped permission that is granted. */
94
+ scope: string | null;
95
+ /** The scope that matched the resource, when a resource was checked. */
96
+ matchedScope?: string;
97
+ reason: DecisionReason;
98
+ via: GrantSource[];
99
+ }
100
+ interface ResolveInput {
101
+ catalog: AccessCatalog;
102
+ subject: AccessSubject;
103
+ /** Every role of the tenant, by id. */
104
+ roles: ReadonlyMap<string, AccessRole>;
105
+ /** Assignments naming the subject or any of its memberships, in any domain. */
106
+ assignments: readonly AccessAssignment[];
107
+ memberships: readonly PrincipalMembership[];
108
+ unassignedRoleId: string | null;
109
+ domain: AccessDomain | null;
110
+ /** For a domain of a hierarchical type: see `DomainInheritance`. */
111
+ inheritance?: DomainInheritance;
112
+ }
113
+ /**
114
+ * How assignments made above a domain reach into it.
115
+ *
116
+ * `ancestors` are the resolved domain's, nearest first. An assignment at any of
117
+ * them applies here with the role's grants passed through `inherit`, which can
118
+ * only narrow: a grant it adds, or widens past the role's own, is ignored.
119
+ */
120
+ interface DomainInheritance {
121
+ ancestors: readonly AccessDomain[];
122
+ inherit(grants: Grants): Grants;
123
+ }
124
+ interface HeldGrant {
125
+ value: GrantValue;
126
+ via: GrantSource[];
127
+ }
128
+ /**
129
+ * What one subject holds in one domain.
130
+ *
131
+ * Grants are allow-only and combine monotonically: a union across roles, the
132
+ * widest scope wins, implications are expanded per role. A digital employee
133
+ * then keeps only what each permission's agent policy and the live delegation
134
+ * allow — never more than the person it acts for.
135
+ */
136
+ declare class EffectiveGrants {
137
+ readonly catalog: AccessCatalog;
138
+ readonly subject: AccessSubject;
139
+ readonly domain: AccessDomain | null;
140
+ private readonly held;
141
+ private readonly withheld;
142
+ /** Every role that contributed, with how. */
143
+ readonly sources: readonly GrantSource[];
144
+ /** Ids of the domains in which the subject has explicit assignments. */
145
+ readonly assignedDomains: readonly AccessDomain[];
146
+ constructor(catalog: AccessCatalog, subject: AccessSubject, domain: AccessDomain | null, held: ReadonlyMap<string, HeldGrant>, withheld: ReadonlyMap<string, {
147
+ reason: 'agent_forbidden' | 'delegation_missing';
148
+ via: GrantSource[];
149
+ }>,
150
+ /** Every role that contributed, with how. */
151
+ sources: readonly GrantSource[],
152
+ /** Ids of the domains in which the subject has explicit assignments. */
153
+ assignedDomains: readonly AccessDomain[]);
154
+ /** The grant value, or undefined when the permission is not held. */
155
+ value(permission: string): GrantValue | undefined;
156
+ can(permission: string): boolean;
157
+ /** The scope held, or null for an unscoped permission or one not held. */
158
+ scope(permission: string): string | null;
159
+ /** Whether the permission is held at `scope` or wider. */
160
+ atLeast(permission: string, scope: string): boolean;
161
+ via(permission: string): GrantSource[];
162
+ /** Why a permission is not held: not granted, or withheld from the agent. */
163
+ denial(permission: string): DecisionReason | null;
164
+ permissions(): Record<string, GrantValue>;
165
+ /** Whether `access.manage` comes from a host role binding. */
166
+ managesThroughHostRole(): boolean;
167
+ /** The decision for a functional check, before any resource is looked at. */
168
+ decide(permission: string): Decision;
169
+ }
170
+ /**
171
+ * The decision for `permission` on `resource`.
172
+ *
173
+ * Without a resource, or for an unscoped permission, this is the functional
174
+ * check. Otherwise the resource must match the held scope or one narrower on
175
+ * the ladder, tried widest first so `all` short-circuits.
176
+ */
177
+ declare function decideResource(grants: EffectiveGrants, permission: string, resource: unknown, matches: (scope: string) => boolean | Promise<boolean>): Promise<Decision>;
178
+ /** Whether an assignment applies when resolving in `domain`. */
179
+ declare function assignmentInDomain(assignment: AccessAssignment, domain: AccessDomain | null): boolean;
180
+ /** Whether an assignment names the subject, directly or through a membership. */
181
+ declare function assignmentMatches(assignment: AccessAssignment, subject: AccessSubject, memberships: readonly PrincipalMembership[]): boolean;
182
+ /**
183
+ * Drops grants the current catalog does not recognise.
184
+ *
185
+ * A custom role saved under an older catalog may name a permission since
186
+ * removed, or a scope since dropped from its ladder. Ignoring the grant —
187
+ * rather than failing the request or widening it — is the only reading that
188
+ * cannot hand anyone more than the catalog now describes.
189
+ */
190
+ declare function validGrants(catalog: AccessCatalog, grants: Grants): Grants;
191
+ declare function resolveGrants(input: ResolveInput): EffectiveGrants;
192
+
193
+ /**
194
+ * SQL fragments a scope contributes to a list query.
195
+ *
196
+ * Built with a tagged template so values can only ever be parameters: there
197
+ * is no way to splice one into the text. Identifiers (the caller's table
198
+ * alias and column names) go through {@link ident}, which accepts only plain
199
+ * lower-case names — they are the one thing a parameter cannot carry.
200
+ *
201
+ * Placeholders are numbered when the fragment is rendered, against the
202
+ * caller's own parameter list, so a fragment composes into any query without
203
+ * either side knowing the other's numbering.
204
+ */
205
+ declare const IDENT: unique symbol;
206
+ declare const FRAGMENT: unique symbol;
207
+ interface Identifier {
208
+ readonly [IDENT]: string;
209
+ }
210
+ /**
211
+ * Only {@link sql} makes one: an interpolated object that merely has
212
+ * `strings` and `values` is a parameter, so its text never reaches the query.
213
+ */
214
+ interface SqlFragment {
215
+ readonly [FRAGMENT]: true;
216
+ readonly strings: readonly string[];
217
+ readonly values: readonly unknown[];
218
+ }
219
+ /** A lower-case SQL identifier; anything else throws. */
220
+ declare function ident(name: string): Identifier;
221
+ /**
222
+ * Tagged template producing a fragment.
223
+ *
224
+ * Interpolated values become parameters, an {@link ident} becomes the
225
+ * identifier, and a nested fragment is inlined with its own parameters.
226
+ */
227
+ declare function sql(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment;
228
+ declare const TRUE_FRAGMENT: SqlFragment;
229
+ declare const FALSE_FRAGMENT: SqlFragment;
230
+ /** `(a) OR (b) …`, collapsing to TRUE/FALSE where that is already decided. */
231
+ declare function orFragments(fragments: readonly SqlFragment[]): SqlFragment;
232
+ /**
233
+ * Renders a fragment with `$n` placeholders starting after `offset`.
234
+ *
235
+ * `offset` is the number of parameters the surrounding query already uses;
236
+ * append the returned `params` to them.
237
+ */
238
+ declare function renderFragment(fragment: SqlFragment, offset?: number): {
239
+ text: string;
240
+ params: unknown[];
241
+ };
242
+ /**
243
+ * Splices a fragment into a query that already has `params`.
244
+ *
245
+ * Returns the fragment's text, numbered after the existing parameters, and
246
+ * the combined parameter list.
247
+ */
248
+ declare function bindFragment(params: readonly unknown[], fragment: SqlFragment): {
249
+ text: string;
250
+ params: unknown[];
251
+ };
252
+
253
+ /**
254
+ * Scope evaluators: what a scope name means for one resource and for a list.
255
+ *
256
+ * Each evaluator answers both questions — `matches` for a single resource,
257
+ * `filter` for the WHERE clause of a list — so the two cannot drift apart:
258
+ * the classic leak where a list shows rows the detail page refuses (or the
259
+ * other way round) comes from writing them separately.
260
+ */
261
+
262
+ /** Where the filtered table sits in the caller's query. */
263
+ interface ScopeRef {
264
+ /** The table alias the fragment qualifies its columns with. */
265
+ alias: string;
266
+ /** Logical column name → actual column, for evaluators that need one. */
267
+ columns?: Record<string, string>;
268
+ }
269
+ interface ScopeContext {
270
+ host: PluginHostApi;
271
+ permission: string;
272
+ }
273
+ interface ScopeEvaluator<R = unknown> {
274
+ matches(subject: AccessSubject, resource: R, context: ScopeContext): boolean | Promise<boolean>;
275
+ filter(subject: AccessSubject, ref: ScopeRef): SqlFragment;
276
+ }
277
+ /** The column an evaluator needs, validated as an identifier. */
278
+ declare function refColumn(ref: ScopeRef, name: string): ReturnType<typeof ident>;
279
+ /** Every resource. */
280
+ declare const allScope: ScopeEvaluator<unknown>;
281
+ /** Resources the subject owns: `resource.ownerId`, column `ownerId`. */
282
+ declare const ownScope: ScopeEvaluator<{
283
+ ownerId: string | null;
284
+ }>;
285
+ declare const BUILTIN_SCOPES: Record<string, ScopeEvaluator<never>>;
286
+
287
+ /**
288
+ * A denied check, carrying the decision that denied it.
289
+ *
290
+ * 403 says "you cannot use this", which reveals nothing about any record.
291
+ * 404 is for a resource outside the caller's scope when the route asked to
292
+ * conceal it: confirming that a confidential record exists is itself a leak.
293
+ */
294
+ declare class AccessDeniedError extends Error {
295
+ readonly decision: Decision;
296
+ readonly status: 403 | 404;
297
+ constructor(decision: Decision, status: 403 | 404);
298
+ }
299
+ /** A refused admin operation, with the status and a stable code. */
300
+ declare class AccessAdminError extends Error {
301
+ readonly status: 400 | 403 | 404 | 409;
302
+ readonly code: string;
303
+ constructor(status: 400 | 403 | 404 | 409, code: string, message: string);
304
+ }
305
+
306
+ /**
307
+ * Reads and writes of the `tego_access_*` tables.
308
+ *
309
+ * Plain parameterized SQL over the plugin's scoped connection, so it runs the
310
+ * same in every plugin whatever query layer the plugin itself uses. Every
311
+ * statement filters on `tenant_id`.
312
+ */
313
+
314
+ interface Queryable {
315
+ query<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
316
+ }
317
+ interface AccessSettingsRow {
318
+ unassignedRoleId: string | null;
319
+ version: number;
320
+ epoch: string;
321
+ updatedAt: string;
322
+ updatedBy: string | null;
323
+ }
324
+ interface AuditEntry {
325
+ id: string;
326
+ actorId: string | null;
327
+ action: string;
328
+ targetType: string;
329
+ targetId: string;
330
+ before: unknown;
331
+ after: unknown;
332
+ at: string;
333
+ }
334
+
335
+ /**
336
+ * The decision point: `createAccess` binds a catalog to its scope evaluators
337
+ * and principal sources, and hands out a session per caller.
338
+ *
339
+ * A session is cheap and lives for one request (or one storage check). It
340
+ * reads the tenant's access version once, and everything derived from that —
341
+ * role definitions, the caller's assignments — is cached across requests
342
+ * under that version, so a change made in any process is seen by every other
343
+ * process on its next request without any invalidation message.
344
+ */
345
+
346
+ interface PrincipalEntry {
347
+ id: string;
348
+ displayName: string;
349
+ parentId?: string | null;
350
+ active?: boolean;
351
+ }
352
+ interface PrincipalQuery {
353
+ search?: string;
354
+ ids?: string[];
355
+ offset: number;
356
+ limit: number;
357
+ }
358
+ /** A kind of principal roles can be assigned to, beyond a single user. */
359
+ interface PrincipalSource {
360
+ kind: string;
361
+ /** The subject's memberships of this kind. */
362
+ memberships(subject: AccessSubject, host: PluginHostApi): Promise<PrincipalMembership[]>;
363
+ /** Lookup and search, for the admin screens and for validating a grantee. */
364
+ list(host: PluginHostApi, query: PrincipalQuery): Promise<{
365
+ items: PrincipalEntry[];
366
+ hasMore: boolean;
367
+ }>;
368
+ }
369
+ /** The plugin's own records a domain type names (EHS's organizations). */
370
+ interface DomainSource {
371
+ /** Display names by id; an id missing from the result does not exist. */
372
+ describe(ids: string[], host: PluginHostApi): Promise<Map<string, string>>;
373
+ /**
374
+ * Required for a hierarchical domain type: the ancestors of `id`, nearest
375
+ * first. Leave out one that should confer nothing below it (a deactivated
376
+ * organization, say); the walk continues past it.
377
+ */
378
+ ancestors?(id: string, host: PluginHostApi): Promise<string[]>;
379
+ /**
380
+ * Required for a hierarchical domain type: what a role assigned at an
381
+ * ancestor confers here, given the role's grants with implications
382
+ * expanded. It can only narrow; see `DomainInheritance`.
383
+ */
384
+ inherit?(grants: Grants): Grants;
385
+ }
386
+ type AccessChange = {
387
+ type: 'role.created';
388
+ role: AccessRole;
389
+ } | {
390
+ type: 'role.updated';
391
+ role: AccessRole;
392
+ before: AccessRole;
393
+ }
394
+ /**
395
+ * Also when `reconcile` retires a built-in role the catalog dropped; its
396
+ * assignments go with it and are not reported one by one.
397
+ */
398
+ | {
399
+ type: 'role.deleted';
400
+ role: AccessRole;
401
+ } | {
402
+ type: 'assignment.created';
403
+ assignment: AccessAssignment;
404
+ } | {
405
+ type: 'assignment.deleted';
406
+ assignment: AccessAssignment;
407
+ } | {
408
+ type: 'settings.updated';
409
+ before: {
410
+ unassignedRoleId: string | null;
411
+ };
412
+ after: {
413
+ unassignedRoleId: string | null;
414
+ };
415
+ } | {
416
+ type: 'catalog.reconciled';
417
+ report: ReconcileReport;
418
+ };
419
+ interface AccessHooks {
420
+ /**
421
+ * Runs inside the transaction that made the change, after it.
422
+ *
423
+ * For data derived from roles — compiled sharing rules, an index marked
424
+ * stale — that has to commit or roll back together with the change.
425
+ * Throwing rolls the change back.
426
+ */
427
+ onChange?(tx: Queryable, change: AccessChange, context: {
428
+ tenantId: string;
429
+ host: PluginHostApi;
430
+ }): Promise<void>;
431
+ }
432
+ interface CreateAccessOptions<C extends AccessCatalog> {
433
+ catalog: C;
434
+ /** Evaluators for the catalog's scopes; `all` and `own` are built in. */
435
+ evaluators?: Record<string, ScopeEvaluator<never>>;
436
+ /** Principal kinds beyond `user` and `unit`. */
437
+ principals?: PrincipalSource[];
438
+ /** One source per domain type the catalog declares. */
439
+ domains?: Record<string, DomainSource>;
440
+ hooks?: AccessHooks;
441
+ /** Cached assignment sets across requests. Defaults to 2000. */
442
+ cacheSize?: number;
443
+ }
444
+ interface ReconcileReport {
445
+ created: string[];
446
+ updated: string[];
447
+ retired: string[];
448
+ /** Custom-role grants the catalog no longer recognises, ignored until saved. */
449
+ staleGrants: {
450
+ roleId: string;
451
+ permission: string;
452
+ }[];
453
+ }
454
+ interface AccessSnapshot {
455
+ userId: string;
456
+ agent: boolean;
457
+ catalogVersion: number;
458
+ domain: AccessDomain | null;
459
+ permissions: Record<string, GrantValue>;
460
+ /** Navigation key → visible. Keys the catalog does not gate are absent. */
461
+ menus: Record<string, boolean>;
462
+ roles: {
463
+ id: string;
464
+ key: string | null;
465
+ builtin: boolean;
466
+ name: string;
467
+ nameKey: string | null;
468
+ source: GrantSourceKind;
469
+ hostRole?: string;
470
+ }[];
471
+ /** Domain ids by type in which the caller has explicit assignments. */
472
+ domains: Record<string, string[]>;
473
+ }
474
+ interface TenantState {
475
+ settings: AccessSettingsRow;
476
+ roles: Map<string, AccessRole>;
477
+ kinds: string[];
478
+ }
479
+ /** Users are matched by id; this source exists for lookup. */
480
+ declare const userPrincipalSource: PrincipalSource;
481
+ /** The host's organization units, with the member's role in each. */
482
+ declare const unitPrincipalSource: PrincipalSource;
483
+ declare function domainKey(domain: AccessDomain | null | undefined): string;
484
+ interface CheckOptions {
485
+ domain?: AccessDomain | null;
486
+ }
487
+ declare class AccessEngine<P extends string = string> {
488
+ readonly catalog: AccessCatalog;
489
+ readonly evaluators: ReadonlyMap<string, ScopeEvaluator<never>>;
490
+ readonly principalSources: ReadonlyMap<string, PrincipalSource>;
491
+ readonly domainSources: ReadonlyMap<string, DomainSource>;
492
+ readonly hooks: AccessHooks;
493
+ private readonly tenantCache;
494
+ private readonly assignmentCache;
495
+ constructor(options: CreateAccessOptions<AccessCatalog>);
496
+ forRequest(ctx: PluginUserContext, host: PluginHostApi): AccessSession<P>;
497
+ forSubject(subject: AccessSubject, host: PluginHostApi): AccessSession<P>;
498
+ /**
499
+ * A person as the directory knows them now, or null when inactive.
500
+ *
501
+ * For the entry points that carry a user id but no signed context: the
502
+ * storage authorizer, and explaining someone else's access.
503
+ */
504
+ subjectFor(userId: string, host: PluginHostApi): Promise<AccessSubject | null>;
505
+ /** @internal */
506
+ tenantState(host: PluginHostApi): Promise<TenantState>;
507
+ /** @internal */
508
+ assignments(host: PluginHostApi, settings: AccessSettingsRow, principals: {
509
+ kind: string;
510
+ id: string;
511
+ }[]): Promise<AccessAssignment[]>;
512
+ /**
513
+ * Brings the built-in roles in line with the catalog.
514
+ *
515
+ * Run from the backend's `start`: built-in roles are owned by the catalog,
516
+ * so their grants are rewritten whenever it changes, and a built-in role it
517
+ * no longer lists is removed with its assignments. Serialized across
518
+ * processes by an advisory lock, and a no-op — no version bump — when
519
+ * nothing differs.
520
+ */
521
+ reconcile(host: PluginHostApi): Promise<ReconcileReport>;
522
+ }
523
+ declare class AccessSession<P extends string = string> {
524
+ readonly engine: AccessEngine<P>;
525
+ readonly subject: AccessSubject;
526
+ readonly host: PluginHostApi;
527
+ private state?;
528
+ private memberships?;
529
+ private readonly grantsByDomain;
530
+ constructor(engine: AccessEngine<P>, subject: AccessSubject, host: PluginHostApi);
531
+ /** @internal */
532
+ tenant(): Promise<TenantState>;
533
+ private loadMemberships;
534
+ private inheritance;
535
+ /**
536
+ * Everything the subject holds in `domain` (global when omitted), including
537
+ * what assignments at its ancestors pass down when its type is hierarchical.
538
+ */
539
+ grants(domain?: AccessDomain | null): Promise<EffectiveGrants>;
540
+ can(permission: P, options?: CheckOptions): Promise<boolean>;
541
+ /**
542
+ * Whether `permission` is held globally or in any domain the subject has an
543
+ * assignment in. Inheritance only narrows what an assignment grants, so a
544
+ * domain below those cannot hold more.
545
+ */
546
+ holdsAnywhere(permission: P): Promise<boolean>;
547
+ /** The scope held, or null when not held or the permission has no scopes. */
548
+ scope(permission: P, options?: CheckOptions): Promise<string | null>;
549
+ /**
550
+ * The decision for `permission`, and for `resource` when one is given.
551
+ *
552
+ * Without a resource this is the functional check: is the permission held
553
+ * at any scope. With one, a scoped permission must also match it under the
554
+ * held scope or one narrower.
555
+ */
556
+ authorize(permission: P, resource?: unknown, options?: CheckOptions): Promise<Decision>;
557
+ /**
558
+ * {@link authorize}, throwing when denied.
559
+ *
560
+ * A permission not held at all is a 403 whatever `conceal` says: that
561
+ * reveals only that the caller cannot use the feature. A resource outside
562
+ * the held scope is a 404 under `conceal`, so its existence stays hidden.
563
+ */
564
+ assert(permission: P, resource?: unknown, options?: CheckOptions & {
565
+ conceal?: boolean;
566
+ }): Promise<Decision>;
567
+ /**
568
+ * A WHERE-clause fragment selecting the rows `permission` reaches.
569
+ *
570
+ * FALSE when the permission is not held, TRUE when it is unscoped or held at
571
+ * a scope that covers everything, otherwise the OR of every scope up to the
572
+ * held one — the same reading {@link authorize} applies to one resource.
573
+ */
574
+ filter(permission: P, ref: ScopeRef, options?: CheckOptions): Promise<SqlFragment>;
575
+ /** What `/access/me` answers: enough for a UI to gate itself. */
576
+ snapshot(options?: CheckOptions): Promise<AccessSnapshot>;
577
+ /** Whether the caller manages access (holds `access.manage`). */
578
+ manages(options?: CheckOptions): Promise<boolean>;
579
+ }
580
+ declare function createAccess<C extends TypedAccessCatalog<string>>(options: CreateAccessOptions<C>): AccessEngine<PermissionOf<C>>;
581
+
582
+ /**
583
+ * The access administration operations, independent of any HTTP framework.
584
+ *
585
+ * The Hono routes (and the Python SDK's FastAPI routes, which mirror this
586
+ * file) only parse input and map errors; every rule lives here:
587
+ *
588
+ * - every operation needs `access.manage` in the domain it touches, so an
589
+ * assignment of `access.manage` inside one domain delegates exactly that
590
+ * domain's administration;
591
+ * - no escalation: whoever writes a role, assigns it, removes an assignment
592
+ * of it or makes it the default must already hold everything it grants, at
593
+ * the same scope or wider. Someone managing access through a host role
594
+ * binding is exempt — that is the recovery path when nobody else can;
595
+ * - a role marked `assignableBy: host-admin` is assigned and unassigned only
596
+ * through such a host role;
597
+ * - the catalog's per-user role limits, and referential rules the schema
598
+ * cannot express (a domain role assigned in its own domain only).
599
+ *
600
+ * Every write runs in one transaction that bumps the tenant's access version,
601
+ * appends to the audit log and runs the plugin's change hook.
602
+ */
603
+
604
+ declare const domainInputSchema: z.ZodObject<{
605
+ type: z.ZodString;
606
+ id: z.ZodString;
607
+ }, "strict", z.ZodTypeAny, {
608
+ type: string;
609
+ id: string;
610
+ }, {
611
+ type: string;
612
+ id: string;
613
+ }>;
614
+ declare const createRoleInputSchema: z.ZodObject<{
615
+ name: z.ZodString;
616
+ description: z.ZodDefault<z.ZodString>;
617
+ grants: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodLiteral<true>, z.ZodString]>>;
618
+ domain: z.ZodDefault<z.ZodNullable<z.ZodObject<{
619
+ type: z.ZodString;
620
+ id: z.ZodString;
621
+ }, "strict", z.ZodTypeAny, {
622
+ type: string;
623
+ id: string;
624
+ }, {
625
+ type: string;
626
+ id: string;
627
+ }>>>;
628
+ }, "strict", z.ZodTypeAny, {
629
+ grants: Record<string, string | true>;
630
+ domain: {
631
+ type: string;
632
+ id: string;
633
+ } | null;
634
+ name: string;
635
+ description: string;
636
+ }, {
637
+ grants: Record<string, string | true>;
638
+ name: string;
639
+ domain?: {
640
+ type: string;
641
+ id: string;
642
+ } | null | undefined;
643
+ description?: string | undefined;
644
+ }>;
645
+ declare const updateRoleInputSchema: z.ZodObject<{
646
+ name: z.ZodOptional<z.ZodString>;
647
+ description: z.ZodOptional<z.ZodString>;
648
+ grants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodLiteral<true>, z.ZodString]>>>;
649
+ }, "strict", z.ZodTypeAny, {
650
+ grants?: Record<string, string | true> | undefined;
651
+ name?: string | undefined;
652
+ description?: string | undefined;
653
+ }, {
654
+ grants?: Record<string, string | true> | undefined;
655
+ name?: string | undefined;
656
+ description?: string | undefined;
657
+ }>;
658
+ declare const cloneRoleInputSchema: z.ZodObject<{
659
+ name: z.ZodString;
660
+ domain: z.ZodOptional<z.ZodNullable<z.ZodObject<{
661
+ type: z.ZodString;
662
+ id: z.ZodString;
663
+ }, "strict", z.ZodTypeAny, {
664
+ type: string;
665
+ id: string;
666
+ }, {
667
+ type: string;
668
+ id: string;
669
+ }>>>;
670
+ }, "strict", z.ZodTypeAny, {
671
+ name: string;
672
+ domain?: {
673
+ type: string;
674
+ id: string;
675
+ } | null | undefined;
676
+ }, {
677
+ name: string;
678
+ domain?: {
679
+ type: string;
680
+ id: string;
681
+ } | null | undefined;
682
+ }>;
683
+ declare const createAssignmentInputSchema: z.ZodObject<{
684
+ roleId: z.ZodString;
685
+ principal: z.ZodObject<{
686
+ kind: z.ZodString;
687
+ id: z.ZodString;
688
+ includeDescendants: z.ZodDefault<z.ZodBoolean>;
689
+ unitRoles: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodEnum<["owner", "admin", "member", "viewer"]>, "many">>>;
690
+ }, "strict", z.ZodTypeAny, {
691
+ kind: string;
692
+ id: string;
693
+ includeDescendants: boolean;
694
+ unitRoles: ("admin" | "owner" | "member" | "viewer")[] | null;
695
+ }, {
696
+ kind: string;
697
+ id: string;
698
+ includeDescendants?: boolean | undefined;
699
+ unitRoles?: ("admin" | "owner" | "member" | "viewer")[] | null | undefined;
700
+ }>;
701
+ domain: z.ZodDefault<z.ZodNullable<z.ZodObject<{
702
+ type: z.ZodString;
703
+ id: z.ZodString;
704
+ }, "strict", z.ZodTypeAny, {
705
+ type: string;
706
+ id: string;
707
+ }, {
708
+ type: string;
709
+ id: string;
710
+ }>>>;
711
+ }, "strict", z.ZodTypeAny, {
712
+ domain: {
713
+ type: string;
714
+ id: string;
715
+ } | null;
716
+ principal: {
717
+ kind: string;
718
+ id: string;
719
+ includeDescendants: boolean;
720
+ unitRoles: ("admin" | "owner" | "member" | "viewer")[] | null;
721
+ };
722
+ roleId: string;
723
+ }, {
724
+ principal: {
725
+ kind: string;
726
+ id: string;
727
+ includeDescendants?: boolean | undefined;
728
+ unitRoles?: ("admin" | "owner" | "member" | "viewer")[] | null | undefined;
729
+ };
730
+ roleId: string;
731
+ domain?: {
732
+ type: string;
733
+ id: string;
734
+ } | null | undefined;
735
+ }>;
736
+ declare const assignmentQuerySchema: z.ZodObject<{
737
+ roleId: z.ZodOptional<z.ZodString>;
738
+ principalKind: z.ZodOptional<z.ZodString>;
739
+ principalId: z.ZodOptional<z.ZodString>;
740
+ }, "strict", z.ZodTypeAny, {
741
+ roleId?: string | undefined;
742
+ principalKind?: string | undefined;
743
+ principalId?: string | undefined;
744
+ }, {
745
+ roleId?: string | undefined;
746
+ principalKind?: string | undefined;
747
+ principalId?: string | undefined;
748
+ }>;
749
+ declare const principalQuerySchema: z.ZodObject<{
750
+ kind: z.ZodString;
751
+ search: z.ZodOptional<z.ZodString>;
752
+ ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
753
+ offset: z.ZodDefault<z.ZodNumber>;
754
+ limit: z.ZodDefault<z.ZodNumber>;
755
+ }, "strict", z.ZodTypeAny, {
756
+ kind: string;
757
+ offset: number;
758
+ limit: number;
759
+ search?: string | undefined;
760
+ ids?: string[] | undefined;
761
+ }, {
762
+ kind: string;
763
+ search?: string | undefined;
764
+ ids?: string[] | undefined;
765
+ offset?: number | undefined;
766
+ limit?: number | undefined;
767
+ }>;
768
+ declare const updateSettingsInputSchema: z.ZodObject<{
769
+ unassignedRoleId: z.ZodNullable<z.ZodString>;
770
+ }, "strict", z.ZodTypeAny, {
771
+ unassignedRoleId: string | null;
772
+ }, {
773
+ unassignedRoleId: string | null;
774
+ }>;
775
+ declare const auditQuerySchema: z.ZodObject<{
776
+ offset: z.ZodDefault<z.ZodNumber>;
777
+ limit: z.ZodDefault<z.ZodNumber>;
778
+ }, "strict", z.ZodTypeAny, {
779
+ offset: number;
780
+ limit: number;
781
+ }, {
782
+ offset?: number | undefined;
783
+ limit?: number | undefined;
784
+ }>;
785
+ interface RoleView {
786
+ id: string;
787
+ key: string | null;
788
+ builtin: boolean;
789
+ domain: AccessDomain | null;
790
+ name: string;
791
+ nameKey: string | null;
792
+ description: string;
793
+ descriptionKey: string | null;
794
+ grants: Grants;
795
+ assignableBy: 'access-manager' | 'host-admin';
796
+ exemptFromRoleLimit: boolean;
797
+ assignmentCount: number;
798
+ isDefault: boolean;
799
+ /** Grants the current catalog no longer recognises; dropped on next save. */
800
+ staleGrants: string[];
801
+ updatedAt: string;
802
+ updatedBy: string | null;
803
+ }
804
+ interface AssignmentView extends AccessAssignment {
805
+ principalName: string | null;
806
+ domainName: string | null;
807
+ }
808
+ interface ExplainView {
809
+ userId: string;
810
+ domain: AccessDomain | null;
811
+ roles: (GrantSource & {
812
+ name: string;
813
+ nameKey: string | null;
814
+ })[];
815
+ permissions: Pick<Decision, 'permission' | 'allowed' | 'scope' | 'reason' | 'via'>[];
816
+ }
817
+ interface AuditEntryView extends AuditEntry {
818
+ actorName: string | null;
819
+ }
820
+ interface SettingsView {
821
+ unassignedRoleId: string | null;
822
+ version: number;
823
+ updatedAt: string;
824
+ updatedBy: string | null;
825
+ }
826
+ /** The grants of `grants` the actor does not hold at the same scope or wider. */
827
+ declare function uncoveredGrants(catalog: AccessCatalog, actor: EffectiveGrants, grants: Grants): string[];
828
+ declare class AccessAdmin {
829
+ private readonly session;
830
+ constructor(session: AccessSession);
831
+ private get engine();
832
+ private get catalog();
833
+ private get host();
834
+ private get tenantId();
835
+ private get actorId();
836
+ /** The caller's grants in `domain`, refusing anyone without `access.manage`. */
837
+ private manager;
838
+ private assertCovers;
839
+ private assertDomain;
840
+ private validateGrants;
841
+ private write;
842
+ private role;
843
+ private view;
844
+ catalogView(domain: AccessDomain | null): Promise<AccessCatalog>;
845
+ /** Global roles, plus the roles of `domain` when one is given. */
846
+ listRoles(domain: AccessDomain | null): Promise<RoleView[]>;
847
+ createRole(input: z.output<typeof createRoleInputSchema>): Promise<RoleView>;
848
+ private assertNameFree;
849
+ updateRole(id: string, input: z.output<typeof updateRoleInputSchema>): Promise<RoleView>;
850
+ deleteRole(id: string): Promise<void>;
851
+ cloneRole(id: string, input: z.output<typeof cloneRoleInputSchema>): Promise<RoleView>;
852
+ private principalNames;
853
+ private domainNames;
854
+ listAssignments(domain: AccessDomain | null, query: z.output<typeof assignmentQuerySchema>): Promise<AssignmentView[]>;
855
+ /** Refuses a default role that manages access: every user would. */
856
+ private assertDefaultMayHold;
857
+ /** The checks every grant or removal of `role` in `domain` must pass. */
858
+ private assertMayAssign;
859
+ createAssignment(input: z.output<typeof createAssignmentInputSchema>): Promise<AssignmentView>;
860
+ deleteAssignment(id: string): Promise<void>;
861
+ principals(domain: AccessDomain | null, query: z.output<typeof principalQuerySchema>): Promise<{
862
+ items: PrincipalEntry[];
863
+ hasMore: boolean;
864
+ }>;
865
+ /** Every permission of another user, with where each one comes from. */
866
+ explain(userId: string, domain: AccessDomain | null): Promise<ExplainView>;
867
+ settings(): Promise<SettingsView>;
868
+ updateSettings(input: z.output<typeof updateSettingsInputSchema>): Promise<SettingsView>;
869
+ audit(query: z.output<typeof auditQuerySchema>): Promise<{
870
+ items: AuditEntryView[];
871
+ hasMore: boolean;
872
+ }>;
873
+ }
874
+
875
+ /**
876
+ * The catalog's JSON Schema, for editors.
877
+ *
878
+ * Shipped as `@tego/plugin-sdk/access-catalog.schema.json`; an `access.json`
879
+ * pointing its `$schema` there gets completion and inline errors. The schema
880
+ * only covers shape — cross-references are checked by the pack step.
881
+ */
882
+ declare function accessCatalogJsonSchema(): Record<string, unknown>;
883
+
884
+ export { AccessAdmin, AccessAdminError, type AccessAssignment, AccessCatalog, type AccessChange, AccessDeniedError, type AccessDomain, AccessEngine, type AccessHooks, type AccessRole, AccessSession, type AccessSettingsRow, type AccessSnapshot, type AccessSubject, type AssignmentPrincipal, type AssignmentView, type AuditEntry, type AuditEntryView, BUILTIN_SCOPES, type CheckOptions, type CreateAccessOptions, type Decision, type DecisionReason, DelegationAction, type DomainInheritance, type DomainSource, EffectiveGrants, type ExplainView, FALSE_FRAGMENT, type GrantSource, type GrantSourceKind, GrantValue, Grants, PermissionOf, type PrincipalEntry, type PrincipalMembership, type PrincipalQuery, type PrincipalSource, type Queryable, type ReconcileReport, type ResolveInput, type RoleView, type ScopeContext, type ScopeEvaluator, type ScopeRef, type SettingsView, type SqlFragment, TRUE_FRAGMENT, TypedAccessCatalog, accessCatalogJsonSchema, allScope, assignmentInDomain, assignmentMatches, assignmentQuerySchema, auditQuerySchema, bindFragment, cloneRoleInputSchema, createAccess, createAssignmentInputSchema, createRoleInputSchema, decideResource, domainInputSchema, domainKey, ident, orFragments, ownScope, principalQuerySchema, refColumn, renderFragment, resolveGrants, sql, subjectFromContext, uncoveredGrants, unitPrincipalSource, updateRoleInputSchema, updateSettingsInputSchema, userPrincipalSource, validGrants };