@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
package/dist/access.js ADDED
@@ -0,0 +1,26 @@
1
+ var At=Object.defineProperty;var r=(t,e)=>At(t,"name",{value:e,configurable:!0});import{RESERVED_PERMISSIONS as v,grantProblem as Z,GLOBAL_DOMAIN as Ot,accessCatalogSchema as Dt}from"./chunk-VMCGDDGP.js";export{AccessCatalogError,BUILTIN_PRINCIPAL_KINDS,DELEGATION_ACTIONS,GLOBAL_DOMAIN,HOST_ADMIN_ROLE,RESERVED_GROUP,RESERVED_PERMISSIONS,accessCatalogFromJson,accessCatalogSchema,defineAccess,grantProblem,validateAccessCatalog}from"./chunk-VMCGDDGP.js";import{expandImplications as E,mergeGrant as Mt,scopeRank as M}from"./chunk-SNUCO2MK.js";export{expandImplications,mergeGrant,scopeRank}from"./chunk-SNUCO2MK.js";import{z as u}from"zod";import{zodToJsonSchema as Ct}from"zod-to-json-schema";function tt(t){return{tenantId:t.tenantId,userId:t.userId,hostRoles:t.roles,...t.avatarId?{agent:{avatarId:t.avatarId,delegation:t.delegation?.actions??[]}}:{}}}r(tt,"subjectFromContext");var et=class{static{r(this,"EffectiveGrants")}constructor(t,e,n,s,i,a,o){this.catalog=t,this.subject=e,this.domain=n,this.held=s,this.withheld=i,this.sources=a,this.assignedDomains=o}catalog;subject;domain;held;withheld;sources;assignedDomains;value(t){return this.held.get(t)?.value}can(t){return this.held.has(t)}scope(t){const e=this.held.get(t)?.value;return typeof e=="string"?e:null}atLeast(t,e){const n=this.catalog.permissions[t],s=this.held.get(t)?.value;if(!n?.scopes||typeof s!="string")return!1;const i=n.scopes.indexOf(e);return i>=0&&n.scopes.indexOf(s)>=i}via(t){return this.held.get(t)?.via??this.withheld.get(t)?.via??[]}denial(t){return this.held.has(t)?null:this.withheld.get(t)?.reason??"not_granted"}permissions(){return Object.fromEntries([...this.held].map(([t,e])=>[t,e.value]))}managesThroughHostRole(){return this.held.get(v.manage)?.via.some(t=>t.source==="hostRole")??!1}decide(t){const e=this.held.get(t);return e?{allowed:!0,permission:t,scope:typeof e.value=="string"?e.value:null,reason:"granted",via:e.via}:{allowed:!1,permission:t,scope:null,reason:this.denial(t),via:this.via(t)}}};async function nt(t,e,n,s){const i=t.decide(e);if(!i.allowed||n===void 0||i.scope===null)return i;const a=t.catalog.permissions[e].scopes;for(let o=a.indexOf(i.scope);o>=0;o--){const d=a[o];if(await s(d))return{...i,matchedScope:d}}return{...i,allowed:!1,reason:"scope_mismatch"}}r(nt,"decideResource");function K(t,e){return t===null||e===null?t===e:t.type===e.type&&t.id===e.id}r(K,"sameDomain");function st(t,e){return t.domain===null||K(t.domain,e)}r(st,"assignmentInDomain");function it(t,e,n){const{principal:s}=t;return s.kind==="user"?s.id===e.userId:n.some(i=>i.kind===s.kind&&(i.id===s.id||s.includeDescendants&&i.ancestors.includes(s.id))&&(s.unitRoles===null||i.role!==void 0&&s.unitRoles.includes(i.role)))}r(it,"assignmentMatches");function I(t,e){const n={};for(const[s,i]of Object.entries(e)){const a=t.permissions[s];a&&Z(a,i)===null&&(n[s]=i)}return n}r(I,"validGrants");function jt(t,e,n){const s=E(t.permissions,I(t,n)),i={};for(const[a,o]of Object.entries(s)){const d=e[a];if(d===void 0)continue;const l=t.permissions[a];i[a]=l.scopes!==null&&M(l,o)>M(l,d)?d:o}return i}r(jt,"narrowInherited");function Lt(t,e){const{[v.manage]:n,...s}=I(t,e);return E(t.permissions,s)}r(Lt,"unassignedGrants");function at(t){const{catalog:e,subject:n,roles:s,domain:i}=t,a=[],o=t.assignments.filter(c=>it(c,n,t.memberships)&&s.has(c.roleId)),d=[];for(const c of o)c.domain&&!d.some(h=>K(h,c.domain))&&d.push(c.domain);const l=o.filter(c=>st(c,i));for(const c of l){const h=s.get(c.roleId);a.push({roleId:h.id,roleKey:h.key,source:"assignment",principal:{kind:c.principal.kind,id:c.principal.id},domain:c.domain})}const m=t.inheritance?.ancestors??[],p=new Map;for(const c of o){if(c.domain===null||!m.some(O=>K(O,c.domain)))continue;const h=s.get(c.roleId),f=E(e.permissions,I(e,h.grants)),_=jt(e,f,t.inheritance.inherit(f));if(Object.keys(_).length===0)continue;const R={roleId:h.id,roleKey:h.key,source:"assignment",principal:{kind:c.principal.kind,id:c.principal.id},domain:c.domain,inherited:!0};a.push(R),p.set(R,_)}const b=new Map;for(const c of s.values())c.builtin&&c.key&&b.set(c.key,c);for(const c of n.hostRoles)for(const h of e.hostRoleBindings[c]??[]){const f=b.get(h);f&&a.push({roleId:f.id,roleKey:f.key,source:"hostRole",hostRole:c})}if(l.length===0&&t.unassignedRoleId){const c=s.get(t.unassignedRoleId);c&&a.push({roleId:c.id,roleKey:c.key,source:"unassigned"})}const w=new Map;for(const c of a){const h=s.get(c.roleId),f=p.get(c)??(c.source==="unassigned"?Lt(e,h.grants):E(e.permissions,I(e,h.grants)));for(const[_,R]of Object.entries(f)){const O=e.permissions[_],D=w.get(_),X=Mt(O,D?.value,R);D?(D.value=X,D.via.push(c)):w.set(_,{value:X,via:[c]})}}const g=new Map;if(n.agent){const c=n.agent.delegation;for(const[h,f]of w){const _=e.permissions[h].agent;_.allowed?_.requires.every(R=>c.includes(R))||g.set(h,{reason:"delegation_missing",via:f.via}):g.set(h,{reason:"agent_forbidden",via:f.via})}for(const h of g.keys())w.delete(h)}return new et(e,n,i,w,g,a,d)}r(at,"resolveGrants");var Ft=/^[a-z_][a-z0-9_]*$/,W=Symbol("tego.access.ident"),ot=Symbol("tego.access.fragment");function C(t){if(!Ft.test(t))throw new Error(`"${t}" is not a plain lower-case SQL identifier (^[a-z_][a-z0-9_]*$)`);return{[W]:t}}r(C,"ident");function Ut(t){return typeof t=="object"&&t!==null&&W in t}r(Ut,"isIdentifier");function Bt(t){return typeof t=="object"&&t!==null&&ot in t}r(Bt,"isFragment");function V(t,e){return{[ot]:!0,strings:t,values:e}}r(V,"fragment");function $(t,...e){const n=[t[0]],s=[],i=r(a=>{n[n.length-1]+=a},"append");return e.forEach((a,o)=>{Ut(a)?i(a[W]):Bt(a)?(i(a.strings[0]),a.values.forEach((d,l)=>{s.push(d),n.push(a.strings[l+1])})):(s.push(a),n.push("")),i(t[o+1])}),V(n,s)}r($,"sql");var k=V(["TRUE"],[]),j=V(["FALSE"],[]);function rt(t){const e=t.filter(s=>s!==j);if(e.includes(k))return k;if(e.length===0)return j;if(e.length===1)return e[0];let n=$`(${e[0]})`;for(const s of e.slice(1))n=$`${n} OR (${s})`;return $`(${n})`}r(rt,"orFragments");function ct(t,e=0){let n=t.strings[0];return t.values.forEach((s,i)=>{n+=`$${e+i+1}${t.strings[i+1]}`}),{text:n,params:[...t.values]}}r(ct,"renderFragment");function Gt(t,e){const n=ct(e,t.length);return{text:n.text,params:[...t,...n.params]}}r(Gt,"bindFragment");function dt(t,e){const n=t.columns?.[e];if(!n)throw new Error(`This scope filters on "${e}": pass it in ref.columns, e.g. { ${e}: 'created_by' }`);return C(n)}r(dt,"refColumn");var lt={matches:r(()=>!0,"matches"),filter:r(()=>k,"filter")},ut={matches:r((t,e)=>e.ownerId===t.userId,"matches"),filter:r((t,e)=>$`${C(e.alias)}.${dt(e,"ownerId")} = ${t.userId}`,"filter")},gt={all:lt,own:ut},ht=class extends Error{static{r(this,"AccessDeniedError")}constructor(t,e){super(e===404?"Not found":`Permission "${t.permission}" is required (${t.reason})`),this.decision=t,this.status=e,this.name="AccessDeniedError"}decision;status},S=class extends Error{static{r(this,"AccessAdminError")}constructor(t,e,n){super(n),this.status=t,this.code=e,this.name="AccessAdminError"}status;code},N=r(t=>t instanceof Date?t.toISOString():new Date(t).toISOString(),"iso");function mt(t,e){return t!==null&&e!==null?{type:t,id:e}:null}r(mt,"domainOf");function L(t){return{id:t.id,key:t.key,builtin:t.builtin,domain:mt(t.domain_type,t.domain_id),name:t.name,description:t.description,grants:t.grants,catalogVersion:t.catalog_version,createdAt:N(t.created_at),updatedAt:N(t.updated_at),updatedBy:t.updated_by}}r(L,"toRole");function F(t){return{id:t.id,roleId:t.role_id,principal:{kind:t.principal_kind,id:t.principal_id,includeDescendants:t.include_descendants,unitRoles:t.unit_roles},domain:mt(t.domain_type,t.domain_id),createdBy:t.created_by,createdAt:N(t.created_at)}}r(F,"toAssignment");var U="id, key, builtin, domain_type, domain_id, name, description, grants, catalog_version, created_at, updated_at, updated_by",B="id, role_id, principal_kind, principal_id, include_descendants, unit_roles, domain_type, domain_id, created_by, created_at";async function pt(t,e){const[n]=await t.query("SELECT unassigned_role_id, version, epoch, updated_at, updated_by FROM tego_access_settings WHERE tenant_id = $1",[e]);return n?{unassignedRoleId:n.unassigned_role_id,version:n.version,epoch:n.epoch,updatedAt:N(n.updated_at),updatedBy:n.updated_by}:null}r(pt,"readSettings");async function ft(t,e){return(await t.query(`SELECT ${U} FROM tego_access_roles WHERE tenant_id = $1 ORDER BY builtin DESC, created_at, id`,[e])).map(L)}r(ft,"listRoles");async function z(t,e,n){const[s]=await t.query(`SELECT ${U} FROM tego_access_roles WHERE tenant_id = $1 AND id = $2`,[e,n]);return s?L(s):null}r(z,"findRole");async function Pt(t,e){return(await t.query("SELECT DISTINCT principal_kind FROM tego_access_assignments WHERE tenant_id = $1",[e])).map(s=>s.principal_kind)}r(Pt,"principalKindsInUse");async function Ht(t,e,n){return n.length===0?[]:(await t.query(`SELECT ${B} FROM tego_access_assignments
2
+ WHERE tenant_id = $1
3
+ AND (principal_kind, principal_id) IN (
4
+ SELECT * FROM unnest($2::text[], $3::text[])
5
+ )`,[e,n.map(i=>i.kind),n.map(i=>i.id)])).map(F)}r(Ht,"assignmentsFor");async function yt(t,e,n){const s=["tenant_id = $1"],i=[e],a=r(d=>(i.push(d),`$${i.length}`),"param");return n.roleId&&s.push(`role_id = ${a(n.roleId)}`),n.principal&&s.push(`principal_kind = ${a(n.principal.kind)} AND principal_id = ${a(n.principal.id)}`),n.domain===null?s.push("domain_type IS NULL AND domain_id IS NULL"):n.domain&&s.push(`domain_type = ${a(n.domain.type)} AND domain_id = ${a(n.domain.id)}`),(await t.query(`SELECT ${B} FROM tego_access_assignments WHERE ${s.join(" AND ")} ORDER BY created_at, id`,i)).map(F)}r(yt,"listAssignments");async function qt(t,e,n){const[s]=await t.query(`SELECT ${B} FROM tego_access_assignments WHERE tenant_id = $1 AND id = $2`,[e,n]);return s?F(s):null}r(qt,"findAssignment");async function xt(t,e,n){const[s]=await t.query("SELECT count(*)::text AS count FROM tego_access_assignments WHERE tenant_id = $1 AND role_id = $2",[e,n]);return Number(s?.count??0)}r(xt,"countAssignments");async function wt(t,e){const n=await t.query("SELECT role_id, count(*)::text AS count FROM tego_access_assignments WHERE tenant_id = $1 GROUP BY role_id",[e]);return new Map(n.map(s=>[s.role_id,Number(s.count)]))}r(wt,"assignmentCounts");async function _t(t,e,n){const[s]=await t.query(`UPDATE tego_access_settings
6
+ SET version = version + 1, updated_at = now(), updated_by = $2
7
+ WHERE tenant_id = $1
8
+ RETURNING version`,[e,n]);if(!s)throw new Error("Access settings are missing: call access.reconcile(host) from the backend start hook");return s.version}r(_t,"lockAndBump");async function It(t,e,n){const[s]=await t.query(`INSERT INTO tego_access_roles
9
+ (id, tenant_id, key, builtin, domain_type, domain_id, name, description, grants, catalog_version, updated_by)
10
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11)
11
+ RETURNING ${U}`,[crypto.randomUUID(),e,n.key,n.builtin,n.domain?.type??null,n.domain?.id??null,n.name,n.description,JSON.stringify(n.grants),n.catalogVersion,n.updatedBy]);return L(s)}r(It,"insertRole");async function bt(t,e,n,s){const[i]=await t.query(`UPDATE tego_access_roles
12
+ SET name = $3, description = $4, grants = $5::jsonb, catalog_version = $6,
13
+ updated_by = $7, updated_at = now()
14
+ WHERE tenant_id = $1 AND id = $2
15
+ RETURNING ${U}`,[e,n,s.name,s.description,JSON.stringify(s.grants),s.catalogVersion,s.updatedBy]);return L(i)}r(bt,"updateRole");async function Rt(t,e,n){await t.query("DELETE FROM tego_access_roles WHERE tenant_id = $1 AND id = $2",[e,n])}r(Rt,"deleteRole");async function Kt(t,e,n){const[s]=await t.query(`INSERT INTO tego_access_assignments
16
+ (id, tenant_id, role_id, principal_kind, principal_id, include_descendants, unit_roles, domain_type, domain_id, created_by)
17
+ VALUES ($1, $2, $3, $4, $5, $6, $7::text[], $8, $9, $10)
18
+ ON CONFLICT DO NOTHING
19
+ RETURNING ${B}`,[crypto.randomUUID(),e,n.roleId,n.principal.kind,n.principal.id,n.principal.includeDescendants,n.principal.unitRoles,n.domain?.type??null,n.domain?.id??null,n.createdBy]);return s?F(s):null}r(Kt,"insertAssignment");async function Wt(t,e,n){await t.query("DELETE FROM tego_access_assignments WHERE tenant_id = $1 AND id = $2",[e,n])}r(Wt,"deleteAssignment");async function J(t,e,n){await t.query("UPDATE tego_access_settings SET unassigned_role_id = $2 WHERE tenant_id = $1",[e,n])}r(J,"writeSettings");async function G(t,e,n){await t.query(`INSERT INTO tego_access_audit (id, tenant_id, actor_id, action, target_type, target_id, before, after)
20
+ VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb)`,[crypto.randomUUID(),e,n.actorId,n.action,n.targetType,n.targetId,n.before==null?null:JSON.stringify(n.before),n.after==null?null:JSON.stringify(n.after)])}r(G,"appendAudit");async function Vt(t,e,n){const s=await t.query(`SELECT id, actor_id, action, target_type, target_id, before, after, at
21
+ FROM tego_access_audit WHERE tenant_id = $1
22
+ ORDER BY at DESC, id DESC OFFSET $2 LIMIT $3`,[e,n.offset,n.limit+1]);return{items:s.slice(0,n.limit).map(i=>({id:i.id,actorId:i.actor_id,action:i.action,targetType:i.target_type,targetId:i.target_id,before:i.before,after:i.after,at:N(i.at)})),hasMore:s.length>n.limit}}r(Vt,"listAudit");var vt=class{static{r(this,"Lru")}constructor(t){this.max=t}max;map=new Map;get(t){const e=this.map.get(t);return e!==void 0&&(this.map.delete(t),this.map.set(t,e)),e}set(t,e){this.map.delete(t),this.map.set(t,e),this.map.size>this.max&&this.map.delete(this.map.keys().next().value)}},P=r(()=>new Error('Access reads people and units from the host directory: declare "directory.read" in the manifest'),"directoryRequired"),Et={kind:"user",memberships:r(async()=>[],"memberships"),list:r(async(t,e)=>{if(!t.directory)throw P();const n=await t.directory.list({kind:"users",...e});return{items:n.items.map(s=>({id:s.id,displayName:s.displayName,active:s.active})),hasMore:n.hasMore}},"list")},$t={kind:"unit",memberships:r(async(t,e)=>{if(!e.directory)throw P();return(await e.directory.identity(t.userId)).memberships.map(s=>({kind:"unit",id:s.unitId,ancestors:s.ancestors,role:s.role}))},"memberships"),list:r(async(t,e)=>{if(!t.directory)throw P();const n=await t.directory.list({kind:"units",...e});return{items:n.items.map(s=>({id:s.id,displayName:s.displayName,parentId:s.parentId??null,active:s.active})),hasMore:n.hasMore}},"list")};function kt(t){return t?`${t.type}:${t.id}`:""}r(kt,"domainKey");var St=class{static{r(this,"AccessEngine")}catalog;evaluators;principalSources;domainSources;hooks;tenantCache=new vt(32);assignmentCache;constructor(t){this.catalog=t.catalog;const e=new Map(Object.entries({...gt,...t.evaluators}));for(const i of Object.keys(this.catalog.scopes))if(!e.has(i))throw new Error(`Scope "${i}" has no evaluator: pass one in "evaluators"`);this.evaluators=e;const n=new Map([["user",Et],["unit",$t]]);for(const i of t.principals??[])n.set(i.kind,i);for(const i of this.catalog.assignment.principals)if(!n.has(i))throw new Error(`Principal kind "${i}" has no source: pass one in "principals"`);this.principalSources=n;const s=new Map(Object.entries(t.domains??{}));for(const[i,a]of Object.entries(this.catalog.domains)){const o=s.get(i);if(!o)throw new Error(`Domain type "${i}" has no source: pass one in "domains"`);if(a.hierarchical&&(!o.ancestors||!o.inherit))throw new Error(`Domain type "${i}" is hierarchical: its source needs "ancestors" and "inherit"`)}this.domainSources=s,this.hooks=t.hooks??{},this.assignmentCache=new vt(t.cacheSize??2e3)}forRequest(t,e){if(t.tenantId!==e.tenantId)throw new Error("The request names a tenant this install does not serve");return new Y(this,tt(t),e)}forSubject(t,e){if(t.tenantId!==e.tenantId)throw new Error("The subject names a tenant this install does not serve");return new Y(this,t,e)}async subjectFor(t,e){if(!e.directory)throw P();const n=await e.directory.identity(t);return n.active?{tenantId:e.tenantId,userId:t,hostRoles:n.roleNames}:null}async tenantState(t){const e=t.tenantId,n=await pt(t.db,e);if(!n)throw new Error("Access is not set up for this tenant: call access.reconcile(host) from the backend start hook");const s=`${e}:${n.epoch}:${n.version}`,i=this.tenantCache.get(s);if(i)return{...i,settings:n};const[a,o]=await Promise.all([ft(t.db,e),Pt(t.db,e)]),d={settings:n,roles:new Map(a.map(l=>[l.id,l])),kinds:o};return this.tenantCache.set(s,d),d}async assignments(t,e,n){const s=`${t.tenantId}:${e.epoch}:${e.version}:${n.map(o=>`${o.kind}/${o.id}`).sort().join(",")}`,i=this.assignmentCache.get(s);if(i)return i;const a=await Ht(t.db,t.tenantId,n);return this.assignmentCache.set(s,a),a}async reconcile(t){const e=t.tenantId;return t.db.transaction(async n=>{await n.query("SELECT pg_advisory_xact_lock(hashtext($1))",[`tego_access:${e}`]);const i=(await n.query(`INSERT INTO tego_access_settings (tenant_id, unassigned_role_id, version, epoch)
23
+ VALUES ($1, NULL, 0, $2) ON CONFLICT (tenant_id) DO NOTHING RETURNING tenant_id`,[e,crypto.randomUUID()])).length>0;await n.query("SELECT 1 FROM tego_access_settings WHERE tenant_id = $1 FOR UPDATE",[e]);const a=await ft(n,e),o=await pt(n,e),d=new Map(a.filter(g=>g.builtin).map(g=>[g.key,g])),l={created:[],updated:[],retired:[],staleGrants:[]},m=(()=>{let g=!1;return async()=>{g||(g=!0,await _t(n,e,null))}})(),p=new Map;for(const g of Object.values(this.catalog.roles)){const c=d.get(g.key);if(!c){await m();const h=await It(n,e,{key:g.key,builtin:!0,domain:null,name:g.key,description:"",grants:g.grants,catalogVersion:this.catalog.version,updatedBy:null});p.set(g.key,h.id),l.created.push(g.key),await G(n,e,{actorId:null,action:"role.create",targetType:"role",targetId:h.id,before:null,after:{key:h.key,grants:h.grants}});continue}p.set(g.key,c.id),(c.catalogVersion!==this.catalog.version||!zt(c.grants,g.grants))&&(await m(),await bt(n,e,c.id,{name:g.key,description:"",grants:g.grants,catalogVersion:this.catalog.version,updatedBy:null}),l.updated.push(g.key),await G(n,e,{actorId:null,action:"role.update",targetType:"role",targetId:c.id,before:{grants:c.grants},after:{grants:g.grants}}))}const b=this.catalog.defaults.unassigned?p.get(this.catalog.defaults.unassigned):null;for(const[g,c]of d)this.catalog.roles[g]||(await m(),o.unassignedRoleId===c.id&&await J(n,e,b),await Rt(n,e,c.id),l.retired.push(g),await G(n,e,{actorId:null,action:"role.delete",targetType:"role",targetId:c.id,before:{key:g,grants:c.grants},after:null}),await this.hooks.onChange?.(n,{type:"role.deleted",role:c},{tenantId:e,host:t}));i&&(await m(),await J(n,e,b));for(const g of a)if(!g.builtin)for(const[c,h]of Object.entries(g.grants)){const f=this.catalog.permissions[c];(!f||(f.scopes===null?h!==!0:typeof h!="string"||!f.scopes.includes(h)))&&l.staleGrants.push({roleId:g.id,permission:c})}return l.created.length+l.updated.length+l.retired.length>0&&await this.hooks.onChange?.(n,{type:"catalog.reconciled",report:l},{tenantId:e,host:t}),l.staleGrants.length>0&&t.log.warn("Custom roles hold grants the access catalog no longer has",{staleGrants:l.staleGrants}),l})}};function zt(t,e){const n=Object.keys(t);return n.length===Object.keys(e).length&&n.every(s=>t[s]===e[s])}r(zt,"sameGrants");var Y=class{static{r(this,"AccessSession")}constructor(t,e,n){this.engine=t,this.subject=e,this.host=n}engine;subject;host;state;memberships;grantsByDomain=new Map;tenant(){return this.state??=this.engine.tenantState(this.host),this.state}async loadMemberships(t){return this.memberships??=(async()=>(await Promise.all(t.filter(n=>n!=="user").map(n=>{const s=this.engine.principalSources.get(n);return s?s.memberships(this.subject,this.host):(this.host.log.warn("An assignment names a principal kind with no source",{kind:n}),[])}))).flat())(),this.memberships}async inheritance(t){if(!t||!this.engine.catalog.domains[t.type]?.hierarchical)return null;const e=this.engine.domainSources.get(t.type);return{ancestors:(await e.ancestors(t.id,this.host)).map(s=>({type:t.type,id:s})),inherit:r(s=>e.inherit(s),"inherit")}}grants(t=null){const e=kt(t);let n=this.grantsByDomain.get(e);return n||(n=(async()=>{const s=await this.tenant(),i=await this.loadMemberships(s.kinds),a=[{kind:"user",id:this.subject.userId}];for(const l of i){a.push({kind:l.kind,id:l.id});for(const m of l.ancestors)a.push({kind:l.kind,id:m})}const[o,d]=await Promise.all([this.engine.assignments(this.host,s.settings,a),this.inheritance(t)]);return at({catalog:this.engine.catalog,subject:this.subject,roles:s.roles,assignments:o,memberships:i,unassignedRoleId:s.settings.unassignedRoleId,domain:t,...d?{inheritance:d}:{}})})(),this.grantsByDomain.set(e,n)),n}async can(t,e={}){return(await this.grants(e.domain)).can(t)}async holdsAnywhere(t){const e=await this.grants();if(e.can(t))return!0;for(const n of e.assignedDomains)if((await this.grants(n)).can(t))return!0;return!1}async scope(t,e={}){return(await this.grants(e.domain)).scope(t)}async authorize(t,e,n={}){const s=await this.grants(n.domain);return nt(s,t,e,i=>this.engine.evaluators.get(i).matches(this.subject,e,{host:this.host,permission:t}))}async assert(t,e,n={}){const s=await this.authorize(t,e,n);if(!s.allowed)throw new ht(s,s.reason==="scope_mismatch"&&n.conceal?404:403);return s}async filter(t,e,n={}){C(e.alias);const s=await this.grants(n.domain);if(!s.can(t))return j;const i=s.scope(t);if(i===null)return k;const a=this.engine.catalog.permissions[t].scopes;return rt(a.slice(0,a.indexOf(i)+1).map(o=>this.engine.evaluators.get(o).filter(this.subject,e)))}async snapshot(t={}){const e=await this.grants(t.domain),n=await this.tenant(),s=this.engine.catalog,i=[];for(const o of e.sources){if(i.some(l=>l.id===o.roleId&&l.source===o.source))continue;const d=n.roles.get(o.roleId);i.push({id:d.id,key:d.key,builtin:d.builtin,name:d.name,nameKey:d.key?s.roles[d.key]?.nameKey??null:null,source:o.source,...o.hostRole?{hostRole:o.hostRole}:{}})}const a={};for(const o of e.assignedDomains)(a[o.type]??=[]).push(o.id);return{userId:this.subject.userId,agent:this.subject.agent!==void 0,catalogVersion:s.version,domain:t.domain??null,permissions:e.permissions(),menus:Object.fromEntries(Object.entries(s.menus).map(([o,d])=>[o,d.some(l=>e.can(l))])),roles:i,domains:a}}async manages(t={}){return(await this.grants(t.domain)).can(v.manage)}};function Jt(t){return new St(t)}r(Jt,"createAccess");var T=/^[a-z][a-z0-9_]*$/,Yt=["owner","admin","member","viewer"],H=u.object({type:u.string().regex(T),id:u.string().min(1).max(200)}).strict(),Nt=u.record(u.string(),u.union([u.literal(!0),u.string().regex(T)])),Qt=u.object({name:u.string().trim().min(1).max(80),description:u.string().trim().max(500).default(""),grants:Nt,domain:H.nullable().default(null)}).strict(),Xt=u.object({name:u.string().trim().min(1).max(80).optional(),description:u.string().trim().max(500).optional(),grants:Nt.optional()}).strict(),Zt=u.object({name:u.string().trim().min(1).max(80),domain:H.nullable().optional()}).strict(),te=u.object({roleId:u.string().min(1),principal:u.object({kind:u.string().regex(T),id:u.string().min(1).max(200),includeDescendants:u.boolean().default(!1),unitRoles:u.array(u.enum(Yt)).min(1).nullable().default(null)}).strict(),domain:H.nullable().default(null)}).strict(),ee=u.object({roleId:u.string().min(1).optional(),principalKind:u.string().regex(T).optional(),principalId:u.string().min(1).optional()}).strict(),ne=u.object({kind:u.string().regex(T),search:u.string().max(200).optional(),ids:u.array(u.string().min(1)).min(1).max(100).optional(),offset:u.number().int().min(0).default(0),limit:u.number().int().min(1).max(100).default(50)}).strict(),se=u.object({unassignedRoleId:u.string().min(1).nullable()}).strict(),ie=u.object({offset:u.number().int().min(0).default(0),limit:u.number().int().min(1).max(200).default(50)}).strict(),q=r((t,e)=>new S(403,t,e),"forbidden"),y=r((t,e)=>new S(400,t,e),"badRequest"),A=r((t,e)=>new S(409,t,e),"conflict"),x=r(t=>new S(404,"not_found",`${t} not found`),"notFound");function Q(t,e){return t===null||e===null?t===e:t.type===e.type&&t.id===e.id}r(Q,"sameDomain2");function Tt(t,e,n){const s=E(t.permissions,I(t,n)),i=[];for(const[a,o]of Object.entries(s)){const d=t.permissions[a],l=e.value(a);(l===void 0||d.scopes!==null&&M(d,l)<M(d,o))&&i.push(a)}return i}r(Tt,"uncoveredGrants");var ae=class{static{r(this,"AccessAdmin")}constructor(t){this.session=t}session;get engine(){return this.session.engine}get catalog(){return this.session.engine.catalog}get host(){return this.session.host}get tenantId(){return this.session.subject.tenantId}get actorId(){return this.session.subject.userId}async manager(t){const e=await this.session.grants(t);if(!e.can(v.manage))throw q("permission_required",`Permission "${v.manage}" is required`);return e}assertCovers(t,e){if(t.managesThroughHostRole())return;const n=Tt(this.catalog,t,e);if(n.length>0)throw q("escalation_denied",`You can only grant what you hold yourself. Missing: ${n.join(", ")}`)}async assertDomain(t){if(!t)return;if(!this.catalog.domains[t.type])throw y("unknown_domain",`Unknown domain type "${t.type}"`);if(!(await this.engine.domainSources.get(t.type).describe([t.id],this.host)).has(t.id))throw y("domain_not_found",`${t.type} "${t.id}" does not exist`)}validateGrants(t,e={}){const n=[];for(const[s,i]of Object.entries(t)){const a=this.catalog.permissions[s];if(!a){n.push(`${s}: unknown permission`);continue}a.deprecated&&e[s]===void 0&&n.push(`${s}: deprecated, cannot be newly granted`);const o=Z(a,i);o&&n.push(`${s}: ${o}`)}if(n.length>0)throw y("invalid_grants",n.join("; "));return t}async write(t){return this.host.db.transaction(async e=>(await _t(e,this.tenantId,this.actorId),t(e,n=>this.engine.hooks.onChange?.(e,n,{tenantId:this.tenantId,host:this.host})??Promise.resolve(),n=>G(e,this.tenantId,{actorId:this.actorId,...n}))))}async role(t){const e=await z(this.host.db,this.tenantId,t);if(!e)throw x("Role");return e}view(t,e,n){const s=t.key?this.catalog.roles[t.key]:void 0,i=I(this.catalog,t.grants);return{id:t.id,key:t.key,builtin:t.builtin,domain:t.domain,name:t.name,nameKey:s?.nameKey??null,description:t.description,descriptionKey:s?.descriptionKey??null,grants:i,assignableBy:s?.assignableBy??"access-manager",exemptFromRoleLimit:s?.exemptFromRoleLimit??!1,assignmentCount:e.get(t.id)??0,isDefault:t.id===n,staleGrants:Object.keys(t.grants).filter(a=>!(a in i)),updatedAt:t.updatedAt,updatedBy:t.updatedBy}}async catalogView(t){return await this.manager(t),this.catalog}async listRoles(t){await this.manager(t);const e=await this.session.tenant(),n=await wt(this.host.db,this.tenantId);return[...e.roles.values()].filter(s=>s.domain===null||Q(s.domain,t)).map(s=>this.view(s,n,e.settings.unassignedRoleId))}async createRole(t){const e=await this.manager(t.domain);await this.assertDomain(t.domain);const n=this.validateGrants(t.grants);return this.assertCovers(e,n),this.write(async(s,i,a)=>{await this.assertNameFree(s,t.name,t.domain,null);const o=await It(s,this.tenantId,{key:null,builtin:!1,domain:t.domain,name:t.name,description:t.description,grants:n,catalogVersion:this.catalog.version,updatedBy:this.actorId});return await a({action:"role.create",targetType:"role",targetId:o.id,before:null,after:{name:o.name,domain:o.domain,grants:o.grants}}),await i({type:"role.created",role:o}),this.view(o,new Map,null)})}async assertNameFree(t,e,n,s){if((await t.query(`SELECT id FROM tego_access_roles
24
+ WHERE tenant_id = $1 AND NOT builtin AND lower(name) = lower($2)
25
+ AND domain_type IS NOT DISTINCT FROM $3 AND domain_id IS NOT DISTINCT FROM $4
26
+ AND id IS DISTINCT FROM $5`,[this.tenantId,e,n?.type??null,n?.id??null,s])).length>0)throw A("role_name_taken",`A role named "${e}" already exists`)}async updateRole(t,e){const n=await this.role(t);if(n.builtin)throw y("builtin_locked","Built-in roles follow the plugin catalog; clone one to change it");const s=await this.manager(n.domain),i=e.grants?this.validateGrants(e.grants,n.grants):I(this.catalog,n.grants);return this.assertCovers(s,I(this.catalog,n.grants)),this.assertCovers(s,i),this.write(async(a,o,d)=>{const[l]=await a.query("SELECT unassigned_role_id FROM tego_access_settings WHERE tenant_id = $1",[this.tenantId]);l?.unassigned_role_id===t&&this.assertDefaultMayHold(i);const m=e.name??n.name;e.name&&await this.assertNameFree(a,m,n.domain,t);const p=await bt(a,this.tenantId,t,{name:m,description:e.description??n.description,grants:i,catalogVersion:this.catalog.version,updatedBy:this.actorId});await d({action:"role.update",targetType:"role",targetId:t,before:{name:n.name,description:n.description,grants:n.grants},after:{name:p.name,description:p.description,grants:p.grants}}),await o({type:"role.updated",role:p,before:n});const b=await wt(a,this.tenantId);return this.view(p,b,l?.unassigned_role_id??null)})}async deleteRole(t){const e=await this.role(t);if(e.builtin)throw y("builtin_locked","Built-in roles cannot be deleted");const n=await this.manager(e.domain);this.assertCovers(n,e.grants),await this.write(async(s,i,a)=>{if(await xt(s,this.tenantId,t)>0)throw A("role_in_use","Remove the role from everyone holding it first");const[o]=await s.query("SELECT unassigned_role_id FROM tego_access_settings WHERE tenant_id = $1",[this.tenantId]);if(o?.unassigned_role_id===t)throw A("role_is_default","This role is what unassigned users get; choose another default first");await Rt(s,this.tenantId,t),await a({action:"role.delete",targetType:"role",targetId:t,before:{name:e.name,domain:e.domain,grants:e.grants},after:null}),await i({type:"role.deleted",role:e})})}async cloneRole(t,e){const n=await this.role(t),s=e.domain===void 0?n.domain:e.domain;if(n.domain&&!Q(n.domain,s))throw y("domain_mismatch","A role of one domain can only be cloned within that domain");return this.createRole({name:e.name,description:n.description,grants:I(this.catalog,n.grants),domain:s})}async principalNames(t){const e=new Map;for(const s of t)e.has(s.kind)||e.set(s.kind,new Set),e.get(s.kind).add(s.id);const n=new Map;return await Promise.all([...e].map(async([s,i])=>{const a=this.engine.principalSources.get(s);if(!a)return;const o=[...i];for(let d=0;d<o.length;d+=100){const l=o.slice(d,d+100),m=await a.list(this.host,{ids:l,offset:0,limit:l.length});for(const p of m.items)n.set(`${s}/${p.id}`,p.displayName)}})),n}async domainNames(t){const e=new Map,n=new Map;for(const s of t)n.has(s.type)||n.set(s.type,new Set),n.get(s.type).add(s.id);return await Promise.all([...n].map(async([s,i])=>{const a=this.engine.domainSources.get(s);if(a)for(const[o,d]of await a.describe([...i],this.host))e.set(`${s}:${o}`,d)})),e}async listAssignments(t,e){await this.manager(t);const n=await yt(this.host.db,this.tenantId,{...e.roleId?{roleId:e.roleId}:{},...e.principalKind&&e.principalId?{principal:{kind:e.principalKind,id:e.principalId}}:{},domain:t}),[s,i]=await Promise.all([this.principalNames(n.map(a=>a.principal)),this.domainNames(n.flatMap(a=>a.domain?[a.domain]:[]))]);return n.map(a=>({...a,principalName:s.get(`${a.principal.kind}/${a.principal.id}`)??null,domainName:a.domain?i.get(`${a.domain.type}:${a.domain.id}`)??null:null}))}assertDefaultMayHold(t){if(v.manage in E(this.catalog.permissions,I(this.catalog,t)))throw y("default_role_manages","The default role cannot manage access: every user would")}assertMayAssign(t,e){if((e.key?this.catalog.roles[e.key]:void 0)?.assignableBy==="host-admin"&&!t.managesThroughHostRole())throw q("host_admin_required","Only a host administrator can assign or remove this role");this.assertCovers(t,e.grants)}async createAssignment(t){const e=await this.role(t.roleId),{principal:n,domain:s}=t;if(e.domain&&!Q(e.domain,s))throw y("domain_mismatch","This role belongs to one domain and can only be assigned there");const i=await this.manager(s);if(await this.assertDomain(s),!this.catalog.assignment.principals.includes(n.kind))throw y("principal_kind_not_allowed",`Roles cannot be assigned to "${n.kind}" in this plugin`);if(n.kind==="user"&&(n.includeDescendants||n.unitRoles))throw y("invalid_principal","includeDescendants and unitRoles apply to units and groups only");if(n.kind!=="unit"&&n.unitRoles)throw y("invalid_principal","unitRoles apply to host units only");this.assertMayAssign(i,e);const o=(await this.principalNames([n])).get(`${n.kind}/${n.id}`);if(o===void 0)throw y("principal_not_found",`${n.kind} "${n.id}" does not exist`);const d=e.key?this.catalog.roles[e.key]:void 0,l=this.catalog.assignment.maxRolesPerPrincipal[s?s.type:Ot];return this.write(async(m,p,b)=>{if(n.kind==="user"&&l!==void 0&&!d?.exemptFromRoleLimit){const c=await yt(m,this.tenantId,{principal:{kind:"user",id:n.id},domain:s}),h=await this.session.tenant();if(c.filter(_=>{const R=h.roles.get(_.roleId);return!(R?.key?this.catalog.roles[R.key]:void 0)?.exemptFromRoleLimit}).length>=l)throw A("role_limit_reached",`A user can hold at most ${l} role(s) here; remove the current one first`)}const w=await Kt(m,this.tenantId,{roleId:e.id,principal:n,domain:s,createdBy:this.actorId});if(!w)throw A("already_assigned","This role is already assigned to them here");await b({action:"assignment.create",targetType:"assignment",targetId:w.id,before:null,after:{roleId:e.id,principal:n,domain:s}}),await p({type:"assignment.created",assignment:w});const g=await this.domainNames(s?[s]:[]);return{...w,principalName:o,domainName:s?g.get(`${s.type}:${s.id}`)??null:null}})}async deleteAssignment(t){const e=await qt(this.host.db,this.tenantId,t);if(!e)throw x("Assignment");const n=await this.role(e.roleId),s=await this.manager(e.domain);this.assertMayAssign(s,n),await this.write(async(i,a,o)=>{await Wt(i,this.tenantId,t),await o({action:"assignment.delete",targetType:"assignment",targetId:t,before:{roleId:e.roleId,principal:e.principal,domain:e.domain},after:null}),await a({type:"assignment.deleted",assignment:e})})}async principals(t,e){if(await this.manager(t),!this.catalog.assignment.principals.includes(e.kind))throw y("principal_kind_not_allowed",`Roles cannot be assigned to "${e.kind}" in this plugin`);return this.engine.principalSources.get(e.kind).list(this.host,{...e.search?{search:e.search}:{},...e.ids?{ids:e.ids}:{},offset:e.offset,limit:e.limit})}async explain(t,e){await this.manager(e);const n=await this.engine.subjectFor(t,this.host);if(!n)throw x("Active user");const s=await this.engine.forSubject(n,this.host).grants(e),i=await this.session.tenant();return{userId:t,domain:e,roles:s.sources.map(a=>{const o=i.roles.get(a.roleId);return{...a,name:o.name,nameKey:o.key?this.catalog.roles[o.key]?.nameKey??null:null}}),permissions:Object.keys(this.catalog.permissions).map(a=>{const{allowed:o,scope:d,reason:l,via:m}=s.decide(a);return{permission:a,allowed:o,scope:d,reason:l,via:m}})}}async settings(){await this.manager(null);const{unassignedRoleId:t,version:e,updatedAt:n,updatedBy:s}=(await this.session.tenant()).settings;return{unassignedRoleId:t,version:e,updatedAt:n,updatedBy:s}}async updateSettings(t){const e=await this.manager(null),n=(await this.session.tenant()).settings;if(n.unassignedRoleId!==null){const s=await z(this.host.db,this.tenantId,n.unassignedRoleId);s&&this.assertMayAssign(e,s)}if(t.unassignedRoleId!==null){const s=await this.role(t.unassignedRoleId);if(s.domain)throw y("domain_role","The default role must be a global role");this.assertMayAssign(e,s)}return this.write(async(s,i,a)=>{if(t.unassignedRoleId!==null){const d=await z(s,this.tenantId,t.unassignedRoleId);if(!d)throw x("Role");this.assertDefaultMayHold(d.grants)}await J(s,this.tenantId,t.unassignedRoleId),await a({action:"settings.update",targetType:"settings",targetId:this.tenantId,before:{unassignedRoleId:n.unassignedRoleId},after:{unassignedRoleId:t.unassignedRoleId}}),await i({type:"settings.updated",before:{unassignedRoleId:n.unassignedRoleId},after:{unassignedRoleId:t.unassignedRoleId}});const[o]=await s.query("SELECT version, updated_at, updated_by FROM tego_access_settings WHERE tenant_id = $1",[this.tenantId]);return{unassignedRoleId:t.unassignedRoleId,version:o.version,updatedAt:new Date(o.updated_at).toISOString(),updatedBy:o.updated_by}})}async audit(t){if(!(await this.session.grants(null)).can(v.audit))throw q("permission_required",`Permission "${v.audit}" is required`);const n=await Vt(this.host.db,this.tenantId,t),s=await this.principalNames(n.items.flatMap(i=>i.actorId?[{kind:"user",id:i.actorId}]:[]));return{...n,items:n.items.map(i=>({...i,actorName:i.actorId?s.get(`user/${i.actorId}`)??null:null}))}}};function oe(){return Ct(Dt,{name:"TegoAccessCatalog",$refStrategy:"none"})}r(oe,"accessCatalogJsonSchema");export{ae as AccessAdmin,S as AccessAdminError,ht as AccessDeniedError,St as AccessEngine,Y as AccessSession,gt as BUILTIN_SCOPES,et as EffectiveGrants,j as FALSE_FRAGMENT,k as TRUE_FRAGMENT,oe as accessCatalogJsonSchema,lt as allScope,st as assignmentInDomain,it as assignmentMatches,ee as assignmentQuerySchema,ie as auditQuerySchema,Gt as bindFragment,Zt as cloneRoleInputSchema,Jt as createAccess,te as createAssignmentInputSchema,Qt as createRoleInputSchema,nt as decideResource,H as domainInputSchema,kt as domainKey,C as ident,rt as orFragments,ut as ownScope,ne as principalQuerySchema,dt as refColumn,ct as renderFragment,at as resolveGrants,$ as sql,tt as subjectFromContext,Tt as uncoveredGrants,$t as unitPrincipalSource,Xt as updateRoleInputSchema,se as updateSettingsInputSchema,Et as userPrincipalSource,I as validGrants};
@@ -0,0 +1,361 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * The access catalog: everything a plugin declares about its own permissions.
5
+ *
6
+ * One JSON document per plugin (`access.json`, referenced from the manifest's
7
+ * `access.catalog`), read the same way by the TypeScript and the Python
8
+ * runtime. It is data rather than code so the pack step can validate it for
9
+ * either runtime, and so a TypeScript plugin importing it still gets its
10
+ * permission keys as literal types — JSON imports keep property names.
11
+ *
12
+ * What lives here is the *vocabulary*: permissions, their scope ladders, the
13
+ * roles the plugin ships, which host roles map onto them, and who a user is
14
+ * when nobody assigned them anything. Who holds which custom role is tenant
15
+ * data and lives in the `tego_access_*` tables.
16
+ */
17
+
18
+ /**
19
+ * The actions a person can delegate to a digital employee.
20
+ *
21
+ * Mirrors `PluginUserContext.delegation.actions`; widening that union widens
22
+ * this one, and the catalog then accepts the new action.
23
+ */
24
+ declare const DELEGATION_ACTIONS: readonly ["records.read", "work.prepare"];
25
+ type DelegationAction = (typeof DELEGATION_ACTIONS)[number];
26
+ /** Permissions every catalog has, owned by the SDK's admin API. */
27
+ declare const RESERVED_PERMISSIONS: {
28
+ readonly manage: "access.manage";
29
+ readonly audit: "access.audit";
30
+ };
31
+ declare const RESERVED_GROUP = "access";
32
+ /**
33
+ * The host role that must always be able to manage access.
34
+ *
35
+ * A tenant's administrators install plugins; a catalog that left them unable
36
+ * to reach the access settings would leave a tenant with no way back in once
37
+ * the last plugin-side manager was removed.
38
+ */
39
+ declare const HOST_ADMIN_ROLE = "admin";
40
+ /** Built-in principal kinds; a catalog may add its own (CRM's departments). */
41
+ declare const BUILTIN_PRINCIPAL_KINDS: readonly ["user", "unit"];
42
+ /** The pseudo domain type naming global assignments in role limits. */
43
+ declare const GLOBAL_DOMAIN = "global";
44
+ declare const accessCatalogSchema: z.ZodObject<{
45
+ $schema: z.ZodOptional<z.ZodString>;
46
+ /** Bumped by the plugin when the meaning of the catalog changes. */
47
+ version: z.ZodNumber;
48
+ groups: z.ZodArray<z.ZodObject<{
49
+ key: z.ZodString;
50
+ labelKey: z.ZodString;
51
+ }, "strict", z.ZodTypeAny, {
52
+ key: string;
53
+ labelKey: string;
54
+ }, {
55
+ key: string;
56
+ labelKey: string;
57
+ }>, "many">;
58
+ scopes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
59
+ labelKey: z.ZodString;
60
+ descriptionKey: z.ZodOptional<z.ZodString>;
61
+ }, "strict", z.ZodTypeAny, {
62
+ labelKey: string;
63
+ descriptionKey?: string | undefined;
64
+ }, {
65
+ labelKey: string;
66
+ descriptionKey?: string | undefined;
67
+ }>>>;
68
+ permissions: z.ZodRecord<z.ZodString, z.ZodObject<{
69
+ group: z.ZodString;
70
+ labelKey: z.ZodString;
71
+ descriptionKey: z.ZodOptional<z.ZodString>;
72
+ /** Ordered from narrowest to widest; holding one includes those before it. */
73
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
74
+ implies: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
75
+ /** Absent means a digital employee never holds this permission. */
76
+ agent: z.ZodOptional<z.ZodUnion<[z.ZodObject<{
77
+ allowed: z.ZodLiteral<false>;
78
+ }, "strict", z.ZodTypeAny, {
79
+ allowed: false;
80
+ }, {
81
+ allowed: false;
82
+ }>, z.ZodObject<{
83
+ requires: z.ZodArray<z.ZodEnum<["records.read", "work.prepare"]>, "many">;
84
+ }, "strict", z.ZodTypeAny, {
85
+ requires: ("records.read" | "work.prepare")[];
86
+ }, {
87
+ requires: ("records.read" | "work.prepare")[];
88
+ }>]>>;
89
+ /** Still honoured on existing roles; no longer offered for new grants. */
90
+ deprecated: z.ZodOptional<z.ZodBoolean>;
91
+ }, "strict", z.ZodTypeAny, {
92
+ group: string;
93
+ labelKey: string;
94
+ descriptionKey?: string | undefined;
95
+ scopes?: string[] | undefined;
96
+ implies?: string[] | undefined;
97
+ agent?: {
98
+ allowed: false;
99
+ } | {
100
+ requires: ("records.read" | "work.prepare")[];
101
+ } | undefined;
102
+ deprecated?: boolean | undefined;
103
+ }, {
104
+ group: string;
105
+ labelKey: string;
106
+ descriptionKey?: string | undefined;
107
+ scopes?: string[] | undefined;
108
+ implies?: string[] | undefined;
109
+ agent?: {
110
+ allowed: false;
111
+ } | {
112
+ requires: ("records.read" | "work.prepare")[];
113
+ } | undefined;
114
+ deprecated?: boolean | undefined;
115
+ }>>;
116
+ /**
117
+ * Navigation key → the permission that reveals it, or a list of which any
118
+ * one does. Unlisted keys are always shown.
119
+ */
120
+ menus: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>>;
121
+ roles: z.ZodRecord<z.ZodString, z.ZodObject<{
122
+ nameKey: z.ZodString;
123
+ descriptionKey: z.ZodOptional<z.ZodString>;
124
+ grants: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodLiteral<true>, z.ZodString]>>;
125
+ /** `host-admin`: only someone managing access through a host role may assign it. */
126
+ assignableBy: z.ZodOptional<z.ZodEnum<["access-manager", "host-admin"]>>;
127
+ /** Not counted against `assignment.maxRolesPerPrincipal`. */
128
+ exemptFromRoleLimit: z.ZodOptional<z.ZodBoolean>;
129
+ }, "strict", z.ZodTypeAny, {
130
+ nameKey: string;
131
+ grants: Record<string, string | true>;
132
+ descriptionKey?: string | undefined;
133
+ assignableBy?: "access-manager" | "host-admin" | undefined;
134
+ exemptFromRoleLimit?: boolean | undefined;
135
+ }, {
136
+ nameKey: string;
137
+ grants: Record<string, string | true>;
138
+ descriptionKey?: string | undefined;
139
+ assignableBy?: "access-manager" | "host-admin" | undefined;
140
+ exemptFromRoleLimit?: boolean | undefined;
141
+ }>>;
142
+ /** Host role name → the built-in roles every holder of it has. */
143
+ hostRoleBindings: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>;
144
+ defaults: z.ZodObject<{
145
+ /** The built-in role of a user nobody assigned anything, or null for none. */
146
+ unassigned: z.ZodNullable<z.ZodString>;
147
+ }, "strict", z.ZodTypeAny, {
148
+ unassigned: string | null;
149
+ }, {
150
+ unassigned: string | null;
151
+ }>;
152
+ domains: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
153
+ labelKey: z.ZodString;
154
+ hierarchical: z.ZodOptional<z.ZodBoolean>;
155
+ }, "strict", z.ZodTypeAny, {
156
+ labelKey: string;
157
+ hierarchical?: boolean | undefined;
158
+ }, {
159
+ labelKey: string;
160
+ hierarchical?: boolean | undefined;
161
+ }>>>;
162
+ assignment: z.ZodObject<{
163
+ principals: z.ZodArray<z.ZodString, "many">;
164
+ /** Per domain type (or `global`): direct roles one user may hold there. */
165
+ maxRolesPerPrincipal: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
166
+ }, "strict", z.ZodTypeAny, {
167
+ principals: string[];
168
+ maxRolesPerPrincipal?: Record<string, number> | undefined;
169
+ }, {
170
+ principals: string[];
171
+ maxRolesPerPrincipal?: Record<string, number> | undefined;
172
+ }>;
173
+ }, "strict", z.ZodTypeAny, {
174
+ version: number;
175
+ permissions: Record<string, {
176
+ group: string;
177
+ labelKey: string;
178
+ descriptionKey?: string | undefined;
179
+ scopes?: string[] | undefined;
180
+ implies?: string[] | undefined;
181
+ agent?: {
182
+ allowed: false;
183
+ } | {
184
+ requires: ("records.read" | "work.prepare")[];
185
+ } | undefined;
186
+ deprecated?: boolean | undefined;
187
+ }>;
188
+ groups: {
189
+ key: string;
190
+ labelKey: string;
191
+ }[];
192
+ roles: Record<string, {
193
+ nameKey: string;
194
+ grants: Record<string, string | true>;
195
+ descriptionKey?: string | undefined;
196
+ assignableBy?: "access-manager" | "host-admin" | undefined;
197
+ exemptFromRoleLimit?: boolean | undefined;
198
+ }>;
199
+ hostRoleBindings: Record<string, string[]>;
200
+ defaults: {
201
+ unassigned: string | null;
202
+ };
203
+ assignment: {
204
+ principals: string[];
205
+ maxRolesPerPrincipal?: Record<string, number> | undefined;
206
+ };
207
+ scopes?: Record<string, {
208
+ labelKey: string;
209
+ descriptionKey?: string | undefined;
210
+ }> | undefined;
211
+ $schema?: string | undefined;
212
+ menus?: Record<string, string | string[]> | undefined;
213
+ domains?: Record<string, {
214
+ labelKey: string;
215
+ hierarchical?: boolean | undefined;
216
+ }> | undefined;
217
+ }, {
218
+ version: number;
219
+ permissions: Record<string, {
220
+ group: string;
221
+ labelKey: string;
222
+ descriptionKey?: string | undefined;
223
+ scopes?: string[] | undefined;
224
+ implies?: string[] | undefined;
225
+ agent?: {
226
+ allowed: false;
227
+ } | {
228
+ requires: ("records.read" | "work.prepare")[];
229
+ } | undefined;
230
+ deprecated?: boolean | undefined;
231
+ }>;
232
+ groups: {
233
+ key: string;
234
+ labelKey: string;
235
+ }[];
236
+ roles: Record<string, {
237
+ nameKey: string;
238
+ grants: Record<string, string | true>;
239
+ descriptionKey?: string | undefined;
240
+ assignableBy?: "access-manager" | "host-admin" | undefined;
241
+ exemptFromRoleLimit?: boolean | undefined;
242
+ }>;
243
+ hostRoleBindings: Record<string, string[]>;
244
+ defaults: {
245
+ unassigned: string | null;
246
+ };
247
+ assignment: {
248
+ principals: string[];
249
+ maxRolesPerPrincipal?: Record<string, number> | undefined;
250
+ };
251
+ scopes?: Record<string, {
252
+ labelKey: string;
253
+ descriptionKey?: string | undefined;
254
+ }> | undefined;
255
+ $schema?: string | undefined;
256
+ menus?: Record<string, string | string[]> | undefined;
257
+ domains?: Record<string, {
258
+ labelKey: string;
259
+ hierarchical?: boolean | undefined;
260
+ }> | undefined;
261
+ }>;
262
+ type AccessCatalogInput = z.input<typeof accessCatalogSchema>;
263
+ type GrantValue = true | string;
264
+ type Grants = Record<string, GrantValue>;
265
+ type AgentPolicy = {
266
+ allowed: false;
267
+ } | {
268
+ allowed: true;
269
+ requires: DelegationAction[];
270
+ };
271
+ interface CatalogPermission {
272
+ key: string;
273
+ group: string;
274
+ labelKey: string;
275
+ descriptionKey?: string;
276
+ /** Null for a permission without scopes. */
277
+ scopes: string[] | null;
278
+ implies: string[];
279
+ agent: AgentPolicy;
280
+ deprecated: boolean;
281
+ /** Owned by the SDK (`access.*`), present in every catalog. */
282
+ reserved: boolean;
283
+ }
284
+ interface CatalogRole {
285
+ key: string;
286
+ nameKey: string;
287
+ descriptionKey?: string;
288
+ grants: Grants;
289
+ assignableBy: 'access-manager' | 'host-admin';
290
+ exemptFromRoleLimit: boolean;
291
+ }
292
+ /** The validated catalog the engine and the admin API run on. */
293
+ interface AccessCatalog {
294
+ version: number;
295
+ groups: {
296
+ key: string;
297
+ labelKey: string;
298
+ }[];
299
+ scopes: Record<string, {
300
+ labelKey: string;
301
+ descriptionKey?: string | undefined;
302
+ }>;
303
+ permissions: Record<string, CatalogPermission>;
304
+ /** Navigation key → the permissions of which any one reveals it. */
305
+ menus: Record<string, string[]>;
306
+ roles: Record<string, CatalogRole>;
307
+ hostRoleBindings: Record<string, string[]>;
308
+ defaults: {
309
+ unassigned: string | null;
310
+ };
311
+ domains: Record<string, {
312
+ labelKey: string;
313
+ hierarchical: boolean;
314
+ }>;
315
+ assignment: {
316
+ principals: string[];
317
+ maxRolesPerPrincipal: Record<string, number>;
318
+ };
319
+ }
320
+ /** A catalog whose permission keys are known to the compiler. */
321
+ type TypedAccessCatalog<P extends string> = AccessCatalog & {
322
+ readonly __permission?: P;
323
+ };
324
+ type PermissionOf<C> = C extends TypedAccessCatalog<infer P> ? P : string;
325
+ interface CatalogIssue {
326
+ path: string;
327
+ message: string;
328
+ }
329
+ declare class AccessCatalogError extends Error {
330
+ readonly issues: CatalogIssue[];
331
+ constructor(issues: CatalogIssue[]);
332
+ }
333
+ declare function validateAccessCatalog(input: unknown): {
334
+ ok: true;
335
+ catalog: AccessCatalog;
336
+ } | {
337
+ ok: false;
338
+ issues: CatalogIssue[];
339
+ };
340
+ /**
341
+ * Validates a catalog and keeps its permission keys as a type.
342
+ *
343
+ * Throws {@link AccessCatalogError} listing every problem, so a broken
344
+ * catalog stops the backend at import rather than at the first request.
345
+ */
346
+ declare function defineAccess<const I extends AccessCatalogInput>(input: I): TypedAccessCatalog<(keyof I['permissions'] & string) | (typeof RESERVED_PERMISSIONS)[keyof typeof RESERVED_PERMISSIONS]>;
347
+ /**
348
+ * {@link defineAccess} for the catalog file the manifest names, imported as
349
+ * JSON.
350
+ *
351
+ * A JSON import widens `"allowed": false` to `boolean`, so it cannot satisfy
352
+ * the input type at compile time; the catalog is validated here at runtime
353
+ * instead, exactly as pack validated it, and typed by its permission keys.
354
+ */
355
+ declare function accessCatalogFromJson<const J extends {
356
+ permissions: Record<string, unknown>;
357
+ }>(json: J): TypedAccessCatalog<(keyof J['permissions'] & string) | (typeof RESERVED_PERMISSIONS)[keyof typeof RESERVED_PERMISSIONS]>;
358
+ /** Why `value` is not a valid grant of `permission`, or null when it is. */
359
+ declare function grantProblem(permission: CatalogPermission, value: GrantValue): string | null;
360
+
361
+ export { type AccessCatalog as A, BUILTIN_PRINCIPAL_KINDS as B, type CatalogIssue as C, type DelegationAction as D, type Grants as G, HOST_ADMIN_ROLE as H, type PermissionOf as P, RESERVED_GROUP as R, type TypedAccessCatalog as T, type GrantValue as a, AccessCatalogError as b, type AccessCatalogInput as c, type AgentPolicy as d, type CatalogPermission as e, type CatalogRole as f, DELEGATION_ACTIONS as g, GLOBAL_DOMAIN as h, RESERVED_PERMISSIONS as i, accessCatalogFromJson as j, accessCatalogSchema as k, defineAccess as l, grantProblem as m, validateAccessCatalog as v };