@aiquants/auth-directory-drizzle 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -45,6 +45,22 @@ sign-in.
|
|
|
45
45
|
| `assertMembershipMode` | The single reader of `membership_mode` — no reader may default it |
|
|
46
46
|
| `promoteLedgerForUser` | First-sign-in promotion of ledger rows to real memberships |
|
|
47
47
|
| `withExternalGroupGuards` / `assertGroupMayBeLinked` | Keep administrative roles off externally-sourced groups, in both directions |
|
|
48
|
+
| `DirectoryOperatorError` | The error every refusal above is thrown as; its message is written for the operator |
|
|
49
|
+
| `defaultDirectoryLinkRefusals` | Japanese default for the link store's required `refusals` (break-glass link, allowlist on a local group) |
|
|
50
|
+
|
|
51
|
+
### Refusals are written for the operator
|
|
52
|
+
|
|
53
|
+
Refusing to link a group that holds an administrative role, to link a break-glass proof group, or to
|
|
54
|
+
let a locally managed group gate sign-in are business reasons the operator has to read — without
|
|
55
|
+
them the action just fails. An admin console that hides undeclared error text (driver errors carry
|
|
56
|
+
table names, constraint names and hosts) would turn each of them into a generic "see the server log".
|
|
57
|
+
So every refusal is a `DirectoryOperatorError`, which carries `operatorFacing: true` — the mark
|
|
58
|
+
`@aiquants/auth-react-router/admin` reads — without this package depending on the UI package.
|
|
59
|
+
|
|
60
|
+
The text comes from the deployment: the guard's `message` and the link store's required `refusals`
|
|
61
|
+
(`defaultDirectoryLinkRefusals` is the Japanese default). Each names the group by its key, which is
|
|
62
|
+
what the operator sees on screen. Failures that are not refusals — a group outside the tenant, a
|
|
63
|
+
failed read, a unique-key violation — carry no mark.
|
|
48
64
|
|
|
49
65
|
### Removals go through the lockout guard, and one refusal does not stop the group
|
|
50
66
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1464,8 +1464,14 @@ declare function findLedgerRowsForIdentity(db: DirectoryDb, tables: LedgerIdenti
|
|
|
1464
1464
|
type LinkableAssertion = {
|
|
1465
1465
|
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1466
1466
|
};
|
|
1467
|
+
type DirectoryLinkRefusals = {
|
|
1468
|
+
breakGlassLink: (groupKey: string) => string;
|
|
1469
|
+
allowlistNeedsLink: (groupKey: string) => string;
|
|
1470
|
+
};
|
|
1471
|
+
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1467
1472
|
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1468
1473
|
linkable: LinkableAssertion;
|
|
1474
|
+
refusals: DirectoryLinkRefusals;
|
|
1469
1475
|
ensureConnection?: EnsureConnection;
|
|
1470
1476
|
stalenessThresholdSec: number;
|
|
1471
1477
|
isEnabled: boolean;
|
|
@@ -1508,6 +1514,10 @@ declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirect
|
|
|
1508
1514
|
};
|
|
1509
1515
|
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode[];
|
|
1510
1516
|
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode;
|
|
1517
|
+
declare class DirectoryOperatorError extends Error {
|
|
1518
|
+
readonly operatorFacing = true;
|
|
1519
|
+
constructor(message: string);
|
|
1520
|
+
}
|
|
1511
1521
|
type PromotionRepository = {
|
|
1512
1522
|
findLedgerMatches(args: {
|
|
1513
1523
|
tenantId: string;
|
|
@@ -1705,4 +1715,4 @@ type SyncRepositoryOptions = TransactionalOptions & {
|
|
|
1705
1715
|
actor: string;
|
|
1706
1716
|
};
|
|
1707
1717
|
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1708
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
|
1718
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.d.ts
CHANGED
|
@@ -1464,8 +1464,14 @@ declare function findLedgerRowsForIdentity(db: DirectoryDb, tables: LedgerIdenti
|
|
|
1464
1464
|
type LinkableAssertion = {
|
|
1465
1465
|
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1466
1466
|
};
|
|
1467
|
+
type DirectoryLinkRefusals = {
|
|
1468
|
+
breakGlassLink: (groupKey: string) => string;
|
|
1469
|
+
allowlistNeedsLink: (groupKey: string) => string;
|
|
1470
|
+
};
|
|
1471
|
+
declare const defaultDirectoryLinkRefusals: DirectoryLinkRefusals;
|
|
1467
1472
|
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1468
1473
|
linkable: LinkableAssertion;
|
|
1474
|
+
refusals: DirectoryLinkRefusals;
|
|
1469
1475
|
ensureConnection?: EnsureConnection;
|
|
1470
1476
|
stalenessThresholdSec: number;
|
|
1471
1477
|
isEnabled: boolean;
|
|
@@ -1508,6 +1514,10 @@ declare function createDirectoryLinkStore(dbUnknown: unknown, tables: AuthDirect
|
|
|
1508
1514
|
};
|
|
1509
1515
|
declare const MEMBERSHIP_MODES: readonly AuthDirectoryMembershipMode[];
|
|
1510
1516
|
declare function assertMembershipMode(value: string): AuthDirectoryMembershipMode;
|
|
1517
|
+
declare class DirectoryOperatorError extends Error {
|
|
1518
|
+
readonly operatorFacing = true;
|
|
1519
|
+
constructor(message: string);
|
|
1520
|
+
}
|
|
1511
1521
|
type PromotionRepository = {
|
|
1512
1522
|
findLedgerMatches(args: {
|
|
1513
1523
|
tenantId: string;
|
|
@@ -1705,4 +1715,4 @@ type SyncRepositoryOptions = TransactionalOptions & {
|
|
|
1705
1715
|
actor: string;
|
|
1706
1716
|
};
|
|
1707
1717
|
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
1708
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
|
1718
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRefusals, type DirectoryLinkRow, type DirectoryLinkStoreOptions, DirectoryOperatorError, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultDirectoryLinkRefusals, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ae=Object.defineProperty;var Ge=Object.getOwnPropertyDescriptor;var Ee=Object.getOwnPropertyNames;var Me=Object.prototype.hasOwnProperty;var Ce=(e,n)=>{for(var m in n)ae(e,m,{get:n[m],enumerable:!0})},Pe=(e,n,m,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of Ee(n))!Me.call(e,l)&&l!==m&&ae(e,l,{get:()=>n[l],enumerable:!(d=Ge(n,l))||d.enumerable});return e};var _e=e=>Pe(ae({},"__esModule",{value:!0}),e);var rr={};Ce(rr,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>ne,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>He,MEMBERSHIP_MODES:()=>ue,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>oe,STRANDED_REASON:()=>Ae,UNHEALTHY_SYNC_OUTCOMES:()=>me,assertGroupMayBeLinked:()=>Ue,assertMembershipMode:()=>U,classifySyncFailure:()=>H,createDirectoryCatalogReader:()=>$e,createDirectoryCatalogWriter:()=>Oe,createDirectoryLinkStore:()=>ze,createDirectorySync:()=>Ye,createDirectorySyncRepository:()=>er,createPromotionRepository:()=>We,createSyncRequestQueue:()=>Ve,defaultExternalGroupGuardMessage:()=>Ne,defaultSyncMessages:()=>pe,defineAuthDirectorySchema:()=>Je,findLedgerRowsForIdentity:()=>W,promoteLedgerForUser:()=>Fe,transactionRunner:()=>K,withExternalGroupGuards:()=>qe});module.exports=_e(rr);var se=require("@aiquants/authz-core"),Q=require("@aiquants/mssql"),_=require("drizzle-orm");var ge=require("@aiquants/mssql");function K(e,n){return m=>(0,ge.runWithTransactionRetry)(d=>e.transaction(l=>d(()=>m(l))),n.transactionRetry)}var Le=10,O={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},he=2147483647;function Ke(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>he?he:n}function F(e,n,m){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} must not be blank`);if(d.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} exceeds its column length (${n}); truncating it would change which rows it owns`);return d}function Oe(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=K(d,m),f=n.TDDirectoryCatalog,v=n.TMDirectoryCatalogObservation,w=h=>{let a=new Map;for(let t of h)a.set(t.externalId,t);return[...a.values()]};return{async replace(h,a,t){await l();let o=F((0,se.assertTenantId)(h,"replace(tenantId)"),O.tenantId,"tenantId"),r=F(a,O.provider,"provider"),g=F(m.actor,O.actor,"actor"),u=m.now(),I=w(t.map(D=>({tenantId:o,provider:r,externalId:D.externalId.slice(0,O.externalId),externalKey:D.externalKey.slice(0,O.externalKey),name:D.name.slice(0,O.name),description:D.description===void 0?null:D.description.slice(0,O.description),memberCount:Ke(D.memberCount),observedAt:u,createdAt:u,createdBy:g})));return await s(async D=>{await D.delete(f).where((0,_.and)((0,_.eq)(f.tenantId,o),(0,_.eq)(f.provider,r)));for(let A of(0,Q.chunkByParameters)(I,Le))await D.insert(f).values(A);await D.delete(v).where((0,_.and)((0,_.eq)(v.tenantId,o),(0,_.eq)(v.provider,r))),await D.insert(v).values([{tenantId:o,provider:r,observedAt:u,entryCount:I.length,createdAt:u,createdBy:g,updatedAt:u,updatedBy:g}])}),I.length}}}function $e(e,n,m={}){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,f=n.TMDirectoryCatalogObservation;return{async read(v,w){await l();let h=F((0,se.assertTenantId)(v,"read(tenantId)"),O.tenantId,"tenantId"),a=F(w,O.provider,"provider"),t=await d.select({observedAt:f.observedAt,entryCount:f.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(f).leftJoin(s,(0,_.and)((0,_.eq)(s.tenantId,f.tenantId),(0,_.eq)(s.provider,f.provider))).where((0,_.and)((0,_.eq)(f.tenantId,h),(0,_.eq)(f.provider,a))).orderBy((0,_.asc)(s.name)),o=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),r=t[0];if(r===void 0)return{groups:o,observedAt:null};if(Number(r.entryCount)!==o.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${h}, ${a}): the observation records ${r.entryCount} entries but ${o.length} rows are present`);let g=(0,Q.toInstant)(r.observedAt);if(!Number.isFinite(g.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${h}, ${a}) has an unreadable observed_at`);return{groups:o,observedAt:g}}}}var be=require("@aiquants/authz-core"),Ne=(e,n)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${n}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${e}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function q(e,n,m,d){return new Set(e.filter(l=>n.has(l.roleId)&&l.appKey===m&&l.resourceKey===d).map(l=>l.action))}function Z(e,n){return new Set(e.filter(m=>m.groupId===n).map(m=>m.roleId))}var j=e=>be.AUTHZ_ACTIONS.every(n=>e.has(n)),ee=(e,n)=>e.find(m=>m.roleId===n)?.roleKey??`#${n}`;function X(e,n,m,d){let l=[...n].find(f=>j(q(e,new Set([f]),m,d)));return l!==void 0?ee(e,l):[...n].filter(f=>q(e,new Set([f]),m,d).size>0).map(f=>ee(e,f)).join(" + ")}function qe(e,n){return fe(e,n)}function fe(e,n){let m=e;return{...e,withTransaction:(d,l)=>m.withTransaction((s,f)=>d(fe(s,n),f),l),async assignGroupRole(d,l,s,f){await m.withTransaction(async(v,w)=>{let h=await v.listGrants(d),a=Z(await v.listGroupRoles(d),l);if(a.add(s),j(q(h,a,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(d,l,w);if(t!==null)throw new Error(n.message(X(h,a,n.adminAppKey,n.adminResourceKey),t))}await v.assignGroupRole(d,l,s,f)})},async updateGrant(d,l,s,f){let v=s.action;if(v===void 0)return m.updateGrant(d,l,s,f);await m.withTransaction(async(w,h)=>{let a=await w.listGrants(d),t=a.find(g=>g.id===l);if(t===void 0)return w.updateGrant(d,l,s,f);let r=(await w.listResources(d)).find(g=>g.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let g=await w.listGroupRoles(d);for(let u of new Set(g.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=Z(g,u),D=q(a.filter(x=>x.id!==l),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&D.add(v),!j(D))continue;let A=await n.linkLookup.externalGroupKey(d,u,h);if(A!==null)throw new Error(n.message(X(a,I,n.adminAppKey,n.adminResourceKey)||ee(a,t.roleId),A))}}await w.updateGrant(d,l,s,f)})},async createGrant(d,l,s){await m.withTransaction(async(f,v)=>{let h=(await f.listResources(d)).find(a=>a.id===l.resourceId);if(h?.appKey===n.adminAppKey&&h?.resourceKey===n.adminResourceKey){let a=await f.listGrants(d),t=await f.listGroupRoles(d);for(let o of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=q(a,Z(t,o),n.adminAppKey,n.adminResourceKey);if(r.add(l.action),!j(r))continue;let g=await n.linkLookup.externalGroupKey(d,o,v);if(g!==null)throw new Error(n.message(X(a,Z(t,o),n.adminAppKey,n.adminResourceKey)||ee(a,l.roleId),g))}}await f.createGrant(d,l,s)})}}}async function Ue(e,n,m,d){let l=(await e.listGroupRoles(n)).filter(w=>w.groupId===m);if(l.length===0)return;let s=await e.listGrants(n),f=new Set(l.map(w=>w.roleId));if(!j(q(s,f,d.adminAppKey,d.adminResourceKey)))return;let v=l[0];if(v)throw new Error(d.message(X(s,f,d.adminAppKey,d.adminResourceKey),v.groupKey))}var $=require("drizzle-orm");async function W(e,n,m){let{ledger:d,group:l}=n,{tenantId:s,externalUserId:f,email:v}=m;if(f===null&&v==="")return[];let w={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},h=async g=>(await e.select(w).from(d).innerJoin(l,(0,$.and)((0,$.eq)(l.id,d.groupId),(0,$.eq)(l.tenantId,s),(0,$.eq)(l.isActive,!0))).where(g)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),a=g=>{let u=new Map;for(let I of g)u.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...u.values()].sort((I,D)=>I.ledgerId-D.ledgerId)},t=f===null?[]:await h((0,$.eq)(d.externalUserId,f));if(v==="")return a(t);let o=await h((0,$.eq)(d.email,v)),r=g=>g.externalUserId!==null&&g.externalUserId!==f;return a([...t,...o.filter(g=>!r(g))])}var re=require("@aiquants/auth-core"),we=require("@aiquants/auth-directory-core"),C=require("@aiquants/authz-core"),de=require("@aiquants/mssql"),c=require("drizzle-orm");var ue=["direct","transitive"];function U(e){if(!ue.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${ue.join(" | ")}, received ${JSON.stringify(e)}`);return e}function Be(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function ze(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=K(l,d),v=d.policyCacheTtlMs??0;if(!Number.isFinite(v)||v<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let w=new Map,h=i=>w.delete(i),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:o,TDAllowlistGroup:r,TDDirectorySyncRequest:g}=n,{TMGroup:u,TDGroupMember:I}=m;async function D(i,y,b){if((await i.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,y),(0,c.eq)(o.groupId,b),(0,c.eq)(o.isBreakGlass,!0)))).length>0)throw new Error(`Group ${b} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function A(i,y,b){let[S]=await i.select({id:u.id}).from(u).where((0,c.and)((0,c.eq)(u.id,b),(0,c.eq)(u.tenantId,y)));if(!S)throw new Error(`Group not found in tenant: ${b}`)}function x(i,y){return{linkId:Number(i.linkId),groupId:Number(i.groupId),tenantId:String(i.tenantId),provider:String(i.provider),externalId:String(i.externalId),externalKey:String(i.externalKey),membershipMode:U(String(i.membershipMode)),isSyncEnabled:!!i.isSyncEnabled,...i.syncedAt?{syncedAt:(0,de.toInstant)(i.syncedAt).toISOString()}:{},...i.syncError?{syncError:String(i.syncError)}:{},unresolvedCount:y}}async function G(i,y){let b=await i.select({groupId:t.groupId,n:(0,c.count)()}).from(t).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.tenantId,y))).where((0,c.isNull)(t.resolvedUserId)).groupBy(t.groupId);return new Map(b.map(S=>[Number(S.groupId),Number(S.n)]))}return{async listLinks(i){(0,C.assertTenantId)(i,"listLinks(tenantId)"),await s();let y=await l.select({linkId:a.id,groupId:a.groupId,tenantId:a.tenantId,provider:a.provider,externalId:a.externalId,externalKey:a.externalKey,membershipMode:a.membershipMode,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,i)).orderBy((0,c.asc)(a.groupId)),b=await G(l,i);return y.map(S=>((0,C.assertTenantMatch)(i,String(S.tenantId),"listLinks(row.tenantId)"),x(S,b.get(Number(S.groupId))??0)))},async externalGroupKey(i,y,b){(0,C.assertTenantId)(i,"externalGroupKey(tenantId)"),await s();let[S]=await(b??l).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));return S?String(S.externalKey):null},async linkGroup(i,y,b,S){(0,C.assertTenantId)(i,"linkGroup(tenantId)");let k=U(b.membershipMode),R=b.externalId.trim(),P=(0,re.normalizeAuthEmail)(b.externalKey);if(R===""||P==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(i,y,async V=>{let E=V;await A(E,i,y),await D(E,i,y),await E.insert(a).output().values({groupId:y,tenantId:i,provider:b.provider,externalId:R,externalKey:P,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:S})}),h(i)},async unlinkGroup(i,y){(0,C.assertTenantId)(i,"unlinkGroup(tenantId)"),await s(),await f(async b=>{await A(b,i,y),await b.delete(t).where((0,c.eq)(t.groupId,y)),await b.delete(r).where((0,c.and)((0,c.eq)(r.groupId,y),(0,c.eq)(r.tenantId,i))),await b.delete(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))}),h(i)},async setSyncEnabled(i,y,b,S){(0,C.assertTenantId)(i,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:b,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))},async requestSync(i,y,b){return(0,C.assertTenantId)(i,"requestSync(tenantId)"),await s(),l.transaction(async S=>{if(y!==null&&await A(S,i,y),(await S.select({id:g.id}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),y===null?(0,c.isNull)(g.groupId):(0,c.eq)(g.groupId,y),(0,c.isNull)(g.pickedAt)))).length>0)return"already-pending";try{await S.insert(g).output().values({tenantId:i,groupId:y,requestedBy:b,requestedAt:new Date,createdAt:new Date,createdBy:b})}catch(R){if(!Be(R))throw R;return"already-pending"}return"queued"})},async getStatus(i){(0,C.assertTenantId)(i,"getStatus(tenantId)"),await s();let y=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,i)),[b]=await l.select({n:(0,c.count)()}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),(0,c.isNull)(g.finishedAt))),S=y.filter(E=>!!E.isSyncEnabled),k=S.map(E=>E.syncedAt?(0,de.toInstant)(E.syncedAt).getTime():null),R=k.length>0&&k.every(E=>E!==null)?Math.min(...k):null,V=y.length>0&&S.length===0||S.length>0&&(R===null||Date.now()-R>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:y.length,...R!==null?{oldestSyncedAt:new Date(R).toISOString()}:{},isStale:V,pausedGroupCount:y.length-S.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:y.filter(E=>!!E.syncError).map(E=>Number(E.groupId)).sort((E,Re)=>E-Re),pendingRequestCount:Number(b?.n??0)}},async loadTenantMembershipPolicy(i){(0,C.assertTenantId)(i,"loadTenantMembershipPolicy(tenantId)");let y=v>0?w.get(i):void 0;if(y&&Date.now()-y.loadedAt<v)return y.policy;await s();let b=await l.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass,externalLinkId:a.id}).from(o).innerJoin(u,(0,c.and)((0,c.eq)(u.id,o.groupId),(0,c.eq)(u.tenantId,i))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,o.groupId),(0,c.eq)(a.tenantId,i))).where((0,c.eq)(o.tenantId,i));if(b.length===0){let P={kind:"no-proof-required"};return v>0&&w.set(i,{policy:P,loadedAt:Date.now()}),P}let S=b.map(P=>Number(P.groupId)),k=b.filter(P=>!!P.isBreakGlass);for(let P of k)if(P.externalLinkId!==null)throw new Error(`Break-glass group ${Number(P.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let R={kind:"require-proof",proofGroupIds:S,breakGlassGroupIds:k.map(P=>Number(P.groupId))};return(0,we.assertTenantMembershipPolicy)(R,`tenant ${i}`),v>0&&w.set(i,{policy:R,loadedAt:Date.now()}),R},async setTenantMembershipGroup(i,y,b,S){(0,C.assertTenantId)(i,"setTenantMembershipGroup(tenantId)"),await s(),await f(async k=>{if(await A(k,i,y),b){let[P]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,i),(0,c.eq)(a.groupId,y)));if(P)throw new Error(`Group ${y} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let R=await k.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));if(!b&&!(await k.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(E=>Number(E.groupId)!==y).some(E=>!!E.isBreakGlass))throw new Error(`Declaring group ${y} as ordinary proof would leave tenant ${i} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(R.length>0){await k.update(o).set({isBreakGlass:b,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));return}await k.insert(o).output().values({tenantId:i,groupId:y,isBreakGlass:b,createdAt:new Date,createdBy:S})}),h(i)},async removeTenantMembershipGroup(i,y){(0,C.assertTenantId)(i,"removeTenantMembershipGroup(tenantId)"),await s(),await f(async b=>{let k=(await b.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(R=>Number(R.groupId)!==y);if(k.length>0&&!k.some(R=>!!R.isBreakGlass))throw new Error(`Removing group ${y} would leave tenant ${i} with a membership proof but no break-glass path.`);await b.delete(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)))}),h(i)},async setGroupActive(i,y,b,S){(0,C.assertTenantId)(i,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:b,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(u.id,y),(0,c.eq)(u.tenantId,i)))},async listLedgerGroupIds(i,y){if((0,C.assertTenantId)(i,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let b=await W(l,{ledger:t,group:u},{tenantId:i,externalUserId:y.externalUserId?.trim()||null,email:(0,re.normalizeAuthEmail)(y.email)});return[...new Set(b.map(S=>S.groupId))].sort((S,k)=>S-k)},async isEmailAllowedByGroup(i){if(!d.isEnabled)return!1;let y=(0,re.normalizeAuthEmail)(i);if(y==="")return!1;await s();let[b]=await l.select({id:t.id}).from(t).innerJoin(r,(0,c.eq)(r.groupId,t.groupId)).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.isActive,!0))).where((0,c.eq)(t.email,y));return!!b},async listAllowlistGroups(i){return(0,C.assertTenantId)(i,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:r.id,groupId:r.groupId,groupKey:u.groupKey,groupName:u.name,description:r.description}).from(r).innerJoin(u,(0,c.and)((0,c.eq)(u.id,r.groupId),(0,c.eq)(u.tenantId,i))).where((0,c.eq)(r.tenantId,i)).orderBy((0,c.asc)(u.name))).map(b=>({id:Number(b.id),type:"group",groupId:Number(b.groupId),groupKey:String(b.groupKey),groupName:String(b.groupName),...b.description?{description:String(b.description)}:{},owner:{kind:"tenant",tenantId:i},readOnly:!1}))},async addAllowlistGroup(i,y,b,S){(0,C.assertTenantId)(i,"addAllowlistGroup(tenantId)"),await s(),await f(async k=>{await A(k,i,y);let[R]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));if(!R)throw new Error(`Group ${y} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await k.insert(r).output().values({tenantId:i,groupId:y,description:b??null,createdAt:new Date,createdBy:S})})},async removeAllowlistGroup(i,y){(0,C.assertTenantId)(i,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where((0,c.and)((0,c.eq)(r.id,y),(0,c.eq)(r.tenantId,i)))},async countProjectedMembers(i,y){(0,C.assertTenantId)(i,"countProjectedMembers(tenantId)"),await s();let[b]=await l.select({n:(0,c.count)()}).from(I).innerJoin(u,(0,c.and)((0,c.eq)(u.id,I.groupId),(0,c.eq)(u.tenantId,i))).where((0,c.eq)(I.groupId,y));return Number(b?.n??0)}}}var Ie=require("@aiquants/auth-core"),te=require("@aiquants/authz-core");async function Fe(e,n){let m=(0,te.assertTenantId)(n.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(n.userId)||n.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(n.userId)}`);let d=(0,Ie.normalizeAuthEmail)(n.email),l=n.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:m,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let h of s)(0,te.assertTenantMatch)(m,h.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let f=[...new Set(s.map(h=>h.ledgerId))].sort((h,a)=>h-a),v=[...new Set(s.map(h=>h.groupId))].sort((h,a)=>h-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:m,userId:n.userId,ledgerIds:f,groupIds:v})].sort((h,a)=>h-a),matchedLedgerIds:f}}var le=require("@aiquants/authz-core"),B=require("@aiquants/mssql"),ve=require("@aiquants/mssql/drizzle"),L=require("drizzle-orm");var je=4;function We(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=K(l,d),v=d.now??(()=>new Date),w=n.TDGroupMemberEmail,{TMGroup:h,TDGroupMember:a}=m;return{async findLedgerMatches(t){return(0,le.assertTenantId)(t.tenantId,"findLedgerMatches(tenantId)"),await s(),W(l,{ledger:w,group:h},t)},async attachUserToGroups({tenantId:t,userId:o,ledgerIds:r,groupIds:g}){return(0,le.assertTenantId)(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:f(async u=>{let I=new Set;for(let G of(0,B.chunk)(g)){let i=await u.select({id:h.id}).from(h).where((0,L.and)((0,L.inArray)(h.id,G),(0,L.eq)(h.tenantId,t),(0,L.eq)(h.isActive,!0)));for(let y of i)I.add(Number(y.id))}let D=g.filter(G=>I.has(G));if(D.length===0)return[];let A=new Set;for(let G of(0,B.chunk)(D)){let i=await u.select({groupId:a.groupId}).from(a).where((0,L.and)((0,L.eq)(a.userId,o),(0,L.inArray)(a.groupId,G)));for(let y of i)A.add(Number(y.groupId))}let x=D.filter(G=>!A.has(G));for(let G of(0,B.chunk)(r))await u.update(w).set({resolvedUserId:o}).output().where((0,L.and)((0,L.inArray)(w.id,G),(0,ve.inJsonList)(w.groupId,D)));if(x.length>0){let G=v();for(let i of(0,B.chunkByParameters)(x,je))await u.insert(a).output().values(i.map(y=>({groupId:y,userId:o,createdAt:G,createdBy:d.actor})))}return x})}}}var N=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),ne="provider",He=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function Je(e,n){let m=(0,p.mssqlSchema)(e),d=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50}),updatedAt:(0,p.datetime2)("updated_at"),updatedBy:(0,p.nvarchar)("updated_by",{length:50})}),l=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),s=m.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),membershipMode:(0,p.nvarchar)("membership_mode",{length:16}).notNull(),isSyncEnabled:(0,p.bit)("is_sync_enabled").notNull(),syncedAt:(0,p.datetime2)("synced_at"),syncError:(0,p.nvarchar)("sync_error",{length:1e3}),vanishedObservations:(0,p.int)("vanished_observations").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),(0,p.foreignKey)({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),f=m.table("TDGroupMemberEmail",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),email:(0,p.nvarchar)("email",{length:320}).notNull(),externalUserId:(0,p.nvarchar)("external_user_id",{length:128}),resolvedUserId:(0,p.bigint)("resolved_user_id",{mode:"number"}),observedAt:(0,p.datetime2)("observed_at").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),(0,p.foreignKey)({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),v=m.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),(0,p.foreignKey)({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),w=m.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),(0,p.foreignKey)({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),h=m.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}),requestedBy:(0,p.nvarchar)("requested_by",{length:320}).notNull(),requestedAt:(0,p.datetime2)("requested_at").notNull(),pickedAt:(0,p.datetime2)("picked_at"),finishedAt:(0,p.datetime2)("finished_at"),result:(0,p.nvarchar)("result",{length:32}),error:(0,p.nvarchar)("error",{length:2e3}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=m.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),pattern:(0,p.nvarchar)("pattern",{length:256}).notNull(),type:(0,p.nvarchar)("type",{length:32}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=m.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),name:(0,p.nvarchar)("name",{length:256}).notNull(),description:(0,p.nvarchar)("description",{length:1e3}),memberCount:(0,p.int)("member_count"),observedAt:(0,p.datetime2)("observed_at").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),o=m.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(N.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),observedAt:(0,p.datetime2)("observed_at").notNull(),entryCount:(0,p.int)("entry_count").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:f,TDTenantMembershipGroup:v,TDAllowlistGroup:w,TDDirectorySyncRequest:h,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:o,TMAllowlist:a}}var ie=require("@aiquants/auth-directory-core");function H(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let n=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof n!="number"?"unknown":n===401||n===403?"denied":n===429||n===408?"rateLimited":n>=500?"upstreamError":"unknown"}var Se=e=>`${e.length} \u4EF6`,ce={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},pe={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[e]})\u3002`,resolverIncomplete:(e,n)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${n} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(e,n,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${n} \u56DE\u76EE\u3067\u3059\u3002${m} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,n)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${n} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${e})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:e=>{if(e.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C ${Se(e.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let n=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${n}\u304C ${e.population} \u4EBA\u4E2D ${e.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${e.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${e.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${Se(m)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalWritesFailed:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(e.map(n=>ce[n.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${e.map(n=>n.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,n)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${n} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var oe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],me=[...oe,"already-vanished"];function Ye(e){let{directory:n,repository:m,policy:d,now:l,logger:s}=e,f=e.messages??pe;if((0,ie.assertBlastRadiusPolicy)(d),!Number.isInteger(e.vanishedThreshold)||e.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(e.vanishedThreshold)}`);if(typeof e.provider!="string"||e.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function v(t,o){try{await m.recordFailure(t,o)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function w(t){try{let o=await n.getGroup(t.externalId);return o===null?{kind:"missing"}:{kind:"ok",group:o,members:await n.listGroupMembers(o,t.membershipMode)}}catch(o){return{kind:"failed",error:o instanceof Error?o:new Error(String(o))}}}async function h(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let o=await m.recordVanishedObservation(t,f.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return o<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${o}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:o}):(await m.markGroupInactive(t),await v(t,f.groupVanished(t.externalKey,o)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${o} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await v(t,f.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let o=await w(t);if(o.kind==="missing")return h(t);if(o.kind==="failed")return await v(t,f.upstreamUnreadable(H(o.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,o.error),{kind:"upstream-failed",link:t,error:o.error};try{await m.resolveExternalIdentity(t,o.group);let[r,g]=await Promise.all([m.loadGroupSnapshot(t),m.resolveMembers(o.members)]);if(g.length!==o.members.length){let x=f.resolverIncomplete(o.members.length,g.length);return await v(t,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:t,error:new Error(x)}}let u=(0,ie.reconcile)(r,g,d);if(u.aborted){await v(t,f.planRefused(u.aborted));let x=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${x.join(", ")}`),{kind:"aborted",link:t,plan:u}}let I=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,D=l(),A={refusals:[],failures:[]};return I&&(A=await m.applyPlan(t,u,D)),await m.recordSuccess(t,o.group,D),A.refusals.length>0&&(await v(t,f.removalsRefused(A.refusals)),s.warn(`[auth-directory] ${A.refusals.length} removal(s) refused for ${t.externalKey}: users ${A.refusals.map(x=>x.userId).join(", ")}`)),A.failures.length>0&&(await v(t,f.removalWritesFailed(A.failures)),s.warn(`[auth-directory] ${A.failures.length} removal(s) could not be written for ${t.externalKey}: users ${A.failures.map(x=>x.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),A.refusals.length>0||A.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:A.refusals,failures:A.failures}:I?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let g=r instanceof Error?r:new Error(String(r));return await v(t,f.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,g),{kind:"local-failed",link:t,error:g}}}return{syncOne:a,async run(t){let o=await m.listSyncableLinks(t),r=[];for(let I of o)try{r.push(await a(I))}catch(D){let A=D instanceof Error?D:new Error(String(D));s.error(`[auth-directory] ${I.externalKey} ended abnormally:`,A),r.push({kind:"local-failed",link:I,error:A})}let g=r.filter(I=>me.includes(I.kind)).length,u=r.filter(I=>oe.includes(I.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${g} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:g,newlyUnhealthyCount:u}}}}var Te=require("@aiquants/authz-core"),De=require("@aiquants/mssql"),M=require("drizzle-orm"),Ae="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",ye=2e3;function Ve(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,f=(0,Te.assertTenantId)(m.tenantId,"createSyncRequestQueue(tenantId)"),v=m.maxErrorLength??ye;if(!Number.isInteger(v)||v<1||v>ye)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${ye}, received ${String(m.maxErrorLength)}`);let w={async claim(){await l();let h=await d.select({id:s.id}).from(s).where((0,M.and)((0,M.eq)(s.tenantId,f),(0,M.isNull)(s.pickedAt))).orderBy((0,M.asc)(s.id));for(let a of h){let o=(await d.update(s).set({pickedAt:m.now()}).output().where((0,M.and)((0,M.eq)(s.tenantId,f),(0,M.eq)(s.id,Number(a.id)),(0,M.isNull)(s.pickedAt))))?.[0];if(!o)continue;if(o.requestedBy===void 0||o.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(o.id)}`);let r=o.groupId??null;return{id:Number(o.id),tenantId:f,groupId:r===null?null:Number(r),requestedBy:String(o.requestedBy)}}return null},async finish(h,a){await l();let t=a.error?a.error.slice(0,v):null;return!!(await d.update(s).set({finishedAt:m.now(),result:a.result,error:t}).output().where((0,M.and)((0,M.eq)(s.tenantId,f),(0,M.eq)(s.id,h),(0,M.isNull)(s.finishedAt))))?.length},async listStranded(h){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,M.and)((0,M.eq)(s.tenantId,f),(0,M.isNull)(s.finishedAt))).orderBy((0,M.asc)(s.id)),t=o=>{let r=(0,De.toInstant)(o);return Number.isNaN(r.getTime())||r<h};return{stranded:a.filter(o=>o.pickedAt!==null&&t(o.pickedAt)).map(o=>Number(o.id)),unclaimed:a.filter(o=>o.pickedAt===null&&t(o.requestedAt)).map(o=>Number(o.id))}},async abandonStranded(h){let{stranded:a}=await w.listStranded(h),t=[];for(let o of a)try{await w.finish(o,{result:"failed",error:Ae})&&t.push(o)}catch(r){m.onCloseFailed?.(o,r)}return t}};return w}var J=require("@aiquants/auth-core"),Y=require("@aiquants/authz-core"),z=require("@aiquants/mssql"),ke=require("@aiquants/mssql/drizzle"),T=require("drizzle-orm");var Qe=7,Ze=4,xe=1e3;function Xe(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function er(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=K(l,d),v=d.now??(()=>new Date),{TDGroupExternalLink:w,TDGroupMemberEmail:h}=n,{TMGroup:a,TDGroupMember:t}=m,o=d.actor;return{async listSyncableLinks(r){await s();let g=(0,Y.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),u=[(0,T.eq)(w.isSyncEnabled,!0),(0,T.eq)(w.tenantId,g)];return r.groupId!==void 0&&u.push((0,T.eq)(w.groupId,r.groupId)),(await l.select({linkId:w.id,groupId:w.groupId,linkTenantId:w.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:w.provider,externalId:w.externalId,externalKey:w.externalKey,membershipMode:w.membershipMode,vanishedObservations:w.vanishedObservations}).from(w).innerJoin(a,(0,T.and)((0,T.eq)(a.id,w.groupId),(0,T.eq)(a.tenantId,g))).where((0,T.and)(...u))).map(D=>((0,Y.assertTenantMatch)(String(D.groupTenantId),String(D.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(D.linkId),groupId:Number(D.groupId),groupKey:String(D.groupKey),tenantId:String(D.groupTenantId),provider:String(D.provider),externalId:String(D.externalId),externalKey:String(D.externalKey),membershipMode:U(String(D.membershipMode)),isGroupActive:!!D.isGroupActive,vanishedObservations:Number(D.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[g,u]=await Promise.all([l.select({email:h.email,externalUserId:h.externalUserId,resolvedUserId:h.resolvedUserId}).from(h).where((0,T.eq)(h.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where((0,T.eq)(t.groupId,r.groupId))]);return{ledger:g.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:u.map(I=>Number(I.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,g,u){(0,Y.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await s(),(g.ledgerUpserts.length>0||g.projectionAdds.length>0)&&await f(async x=>{let G=g.ledgerUpserts.map(b=>({...b,email:(0,J.normalizeAuthEmail)(b.email)})),i=new Map;for(let b of(0,z.chunk)(G.map(S=>S.email))){let S=await x.select({id:h.id,email:h.email}).from(h).where((0,T.and)((0,T.eq)(h.groupId,r.groupId),(0,T.inArray)(h.email,b)));for(let k of S)i.set(String(k.email),Number(k.id))}let y=G.filter(b=>!i.has(b.email));for(let b of(0,z.chunkByParameters)(y,Qe))await x.insert(h).output().values(b.map(S=>({groupId:r.groupId,email:S.email,externalUserId:S.externalUserId??null,resolvedUserId:S.resolvedUserId,observedAt:u,createdAt:u,createdBy:o})));for(let b of G){let S=i.get(b.email);S!==void 0&&await x.update(h).set({externalUserId:b.externalUserId??null,resolvedUserId:b.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:o}).output().where((0,T.eq)(h.id,S))}for(let b of(0,z.chunkByParameters)(g.projectionAdds,Ze)){let S=new Set((await x.select({userId:t.userId}).from(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.inArray)(t.userId,b)))).map(R=>Number(R.userId))),k=b.filter(R=>!S.has(R));k.length>0&&await x.insert(t).output().values(k.map(R=>({groupId:r.groupId,userId:R,createdAt:u,createdBy:o})))}});let I=[],D=[];for(let x of g.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:x}},"remove-group-member",G=>G.delete(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.eq)(t.userId,x))))}catch(G){Xe(G)?I.push({userId:x,applied:!1,refusal:G.message}):D.push({userId:x,cause:H(G)})}let A=I.map(x=>x.userId);return g.ledgerDeletes.length>0&&await f(async x=>{for(let G of(0,z.chunk)(g.ledgerDeletes)){let i=A.length>0?(0,T.or)((0,T.isNull)(h.resolvedUserId),(0,ke.notInJsonList)(h.resolvedUserId,A)):void 0;await x.delete(h).where((0,T.and)((0,T.eq)(h.groupId,r.groupId),(0,T.inArray)(h.email,G),...i?[i]:[]))}}),{refusals:I,failures:D}},async resolveExternalIdentity(r,g){await s(),!(r.externalId===g.externalId&&r.externalKey===(0,J.normalizeAuthEmail)(g.externalKey))&&await l.update(w).set({externalId:g.externalId,externalKey:(0,J.normalizeAuthEmail)(g.externalKey)}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordSuccess(r,g,u){await s(),await f(async I=>{await I.update(w).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:g.externalId,externalKey:(0,J.normalizeAuthEmail)(g.externalKey),updatedAt:u,updatedBy:o}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),r.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:u,updatedBy:o}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId)))})},async recordFailure(r,g){await s(),await l.update(w).set({syncError:g.slice(0,xe),updatedAt:v(),updatedBy:o}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordVanishedObservation(r,g){await s();let u=r.vanishedObservations+1;return await l.update(w).set({vanishedObservations:u,syncError:g.slice(0,xe),updatedAt:v(),updatedBy:o}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:v(),updatedBy:o}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId),(0,T.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
|
1
|
+
"use strict";var ue=Object.defineProperty;var Ee=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var Ce=Object.prototype.hasOwnProperty;var Pe=(e,n)=>{for(var m in n)ue(e,m,{get:n[m],enumerable:!0})},_e=(e,n,m,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of Me(n))!Ce.call(e,l)&&l!==m&&ue(e,l,{get:()=>n[l],enumerable:!(d=Ee(n,l))||d.enumerable});return e};var Le=e=>_e(ue({},"__esModule",{value:!0}),e);var nr={};Pe(nr,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>oe,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>Ye,DirectoryOperatorError:()=>K,MEMBERSHIP_MODES:()=>le,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>ie,STRANDED_REASON:()=>ke,UNHEALTHY_SYNC_OUTCOMES:()=>ge,assertGroupMayBeLinked:()=>Be,assertMembershipMode:()=>B,classifySyncFailure:()=>J,createDirectoryCatalogReader:()=>$e,createDirectoryCatalogWriter:()=>Ne,createDirectoryLinkStore:()=>je,createDirectorySync:()=>Qe,createDirectorySyncRepository:()=>tr,createPromotionRepository:()=>Je,createSyncRequestQueue:()=>Ze,defaultDirectoryLinkRefusals:()=>ze,defaultExternalGroupGuardMessage:()=>qe,defaultSyncMessages:()=>ye,defineAuthDirectorySchema:()=>Ve,findLedgerRowsForIdentity:()=>H,promoteLedgerForUser:()=>We,transactionRunner:()=>O,withExternalGroupGuards:()=>Ue});module.exports=Le(nr);var de=require("@aiquants/authz-core"),Z=require("@aiquants/mssql"),_=require("drizzle-orm");var be=require("@aiquants/mssql");function O(e,n){return m=>(0,be.runWithTransactionRetry)(d=>e.transaction(l=>d(()=>m(l))),n.transactionRetry)}var Ke=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},fe=2147483647;function Oe(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>fe?fe:n}function j(e,n,m){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} must not be blank`);if(d.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} exceeds its column length (${n}); truncating it would change which rows it owns`);return d}function Ne(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=O(d,m),f=n.TDDirectoryCatalog,S=n.TMDirectoryCatalogObservation,w=h=>{let a=new Map;for(let t of h)a.set(t.externalId,t);return[...a.values()]};return{async replace(h,a,t){await l();let o=j((0,de.assertTenantId)(h,"replace(tenantId)"),N.tenantId,"tenantId"),r=j(a,N.provider,"provider"),g=j(m.actor,N.actor,"actor"),u=m.now(),I=w(t.map(T=>({tenantId:o,provider:r,externalId:T.externalId.slice(0,N.externalId),externalKey:T.externalKey.slice(0,N.externalKey),name:T.name.slice(0,N.name),description:T.description===void 0?null:T.description.slice(0,N.description),memberCount:Oe(T.memberCount),observedAt:u,createdAt:u,createdBy:g})));return await s(async T=>{await T.delete(f).where((0,_.and)((0,_.eq)(f.tenantId,o),(0,_.eq)(f.provider,r)));for(let A of(0,Z.chunkByParameters)(I,Ke))await T.insert(f).values(A);await T.delete(S).where((0,_.and)((0,_.eq)(S.tenantId,o),(0,_.eq)(S.provider,r))),await T.insert(S).values([{tenantId:o,provider:r,observedAt:u,entryCount:I.length,createdAt:u,createdBy:g,updatedAt:u,updatedBy:g}])}),I.length}}}function $e(e,n,m={}){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,f=n.TMDirectoryCatalogObservation;return{async read(S,w){await l();let h=j((0,de.assertTenantId)(S,"read(tenantId)"),N.tenantId,"tenantId"),a=j(w,N.provider,"provider"),t=await d.select({observedAt:f.observedAt,entryCount:f.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(f).leftJoin(s,(0,_.and)((0,_.eq)(s.tenantId,f.tenantId),(0,_.eq)(s.provider,f.provider))).where((0,_.and)((0,_.eq)(f.tenantId,h),(0,_.eq)(f.provider,a))).orderBy((0,_.asc)(s.name)),o=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),r=t[0];if(r===void 0)return{groups:o,observedAt:null};if(Number(r.entryCount)!==o.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${h}, ${a}): the observation records ${r.entryCount} entries but ${o.length} rows are present`);let g=(0,Z.toInstant)(r.observedAt);if(!Number.isFinite(g.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${h}, ${a}) has an unreadable observed_at`);return{groups:o,observedAt:g}}}}var we=require("@aiquants/authz-core");var K=class extends Error{constructor(m){super(m);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var qe=(e,n)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${n}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${e}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function U(e,n,m,d){return new Set(e.filter(l=>n.has(l.roleId)&&l.appKey===m&&l.resourceKey===d).map(l=>l.action))}function X(e,n){return new Set(e.filter(m=>m.groupId===n).map(m=>m.roleId))}var W=e=>we.AUTHZ_ACTIONS.every(n=>e.has(n)),re=(e,n)=>e.find(m=>m.roleId===n)?.roleKey??`#${n}`;function ee(e,n,m,d){let l=[...n].find(f=>W(U(e,new Set([f]),m,d)));return l!==void 0?re(e,l):[...n].filter(f=>U(e,new Set([f]),m,d).size>0).map(f=>re(e,f)).join(" + ")}function Ue(e,n){return Ie(e,n)}function Ie(e,n){let m=e;return{...e,withTransaction:(d,l)=>m.withTransaction((s,f)=>d(Ie(s,n),f),l),async assignGroupRole(d,l,s,f){await m.withTransaction(async(S,w)=>{let h=await S.listGrants(d),a=X(await S.listGroupRoles(d),l);if(a.add(s),W(U(h,a,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(d,l,w);if(t!==null)throw new K(n.message(ee(h,a,n.adminAppKey,n.adminResourceKey),t))}await S.assignGroupRole(d,l,s,f)})},async updateGrant(d,l,s,f){let S=s.action;if(S===void 0)return m.updateGrant(d,l,s,f);await m.withTransaction(async(w,h)=>{let a=await w.listGrants(d),t=a.find(g=>g.id===l);if(t===void 0)return w.updateGrant(d,l,s,f);let r=(await w.listResources(d)).find(g=>g.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let g=await w.listGroupRoles(d);for(let u of new Set(g.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=X(g,u),T=U(a.filter(x=>x.id!==l),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&T.add(S),!W(T))continue;let A=await n.linkLookup.externalGroupKey(d,u,h);if(A!==null)throw new K(n.message(ee(a,I,n.adminAppKey,n.adminResourceKey)||re(a,t.roleId),A))}}await w.updateGrant(d,l,s,f)})},async createGrant(d,l,s){await m.withTransaction(async(f,S)=>{let h=(await f.listResources(d)).find(a=>a.id===l.resourceId);if(h?.appKey===n.adminAppKey&&h?.resourceKey===n.adminResourceKey){let a=await f.listGrants(d),t=await f.listGroupRoles(d);for(let o of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=U(a,X(t,o),n.adminAppKey,n.adminResourceKey);if(r.add(l.action),!W(r))continue;let g=await n.linkLookup.externalGroupKey(d,o,S);if(g!==null)throw new K(n.message(ee(a,X(t,o),n.adminAppKey,n.adminResourceKey)||re(a,l.roleId),g))}}await f.createGrant(d,l,s)})}}}async function Be(e,n,m,d){let l=(await e.listGroupRoles(n)).filter(w=>w.groupId===m);if(l.length===0)return;let s=await e.listGrants(n),f=new Set(l.map(w=>w.roleId));if(!W(U(s,f,d.adminAppKey,d.adminResourceKey)))return;let S=l[0];if(S)throw new K(d.message(ee(s,f,d.adminAppKey,d.adminResourceKey),S.groupKey))}var $=require("drizzle-orm");async function H(e,n,m){let{ledger:d,group:l}=n,{tenantId:s,externalUserId:f,email:S}=m;if(f===null&&S==="")return[];let w={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},h=async g=>(await e.select(w).from(d).innerJoin(l,(0,$.and)((0,$.eq)(l.id,d.groupId),(0,$.eq)(l.tenantId,s),(0,$.eq)(l.isActive,!0))).where(g)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),a=g=>{let u=new Map;for(let I of g)u.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...u.values()].sort((I,T)=>I.ledgerId-T.ledgerId)},t=f===null?[]:await h((0,$.eq)(d.externalUserId,f));if(S==="")return a(t);let o=await h((0,$.eq)(d.email,S)),r=g=>g.externalUserId!==null&&g.externalUserId!==f;return a([...t,...o.filter(g=>!r(g))])}var te=require("@aiquants/auth-core"),ve=require("@aiquants/auth-directory-core"),P=require("@aiquants/authz-core"),ce=require("@aiquants/mssql"),c=require("drizzle-orm");var le=["direct","transitive"];function B(e){if(!le.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${le.join(" | ")}, received ${JSON.stringify(e)}`);return e}var ze={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function Fe(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function je(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.policyCacheTtlMs??0;if(!Number.isFinite(S)||S<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let w=new Map,h=i=>w.delete(i),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:o,TDAllowlistGroup:r,TDDirectorySyncRequest:g}=n,{TMGroup:u,TDGroupMember:I}=m;async function T(i,y,b,v){if((await i.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,y),(0,c.eq)(o.groupId,b),(0,c.eq)(o.isBreakGlass,!0)))).length>0)throw new K(d.refusals.breakGlassLink(v))}async function A(i,y,b){let[v]=await i.select({groupKey:u.groupKey}).from(u).where((0,c.and)((0,c.eq)(u.id,b),(0,c.eq)(u.tenantId,y)));if(!v)throw new Error(`Group not found in tenant: ${b}`);return String(v.groupKey)}function x(i,y){return{linkId:Number(i.linkId),groupId:Number(i.groupId),tenantId:String(i.tenantId),provider:String(i.provider),externalId:String(i.externalId),externalKey:String(i.externalKey),membershipMode:B(String(i.membershipMode)),isSyncEnabled:!!i.isSyncEnabled,...i.syncedAt?{syncedAt:(0,ce.toInstant)(i.syncedAt).toISOString()}:{},...i.syncError?{syncError:String(i.syncError)}:{},unresolvedCount:y}}async function G(i,y){let b=await i.select({groupId:t.groupId,n:(0,c.count)()}).from(t).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.tenantId,y))).where((0,c.isNull)(t.resolvedUserId)).groupBy(t.groupId);return new Map(b.map(v=>[Number(v.groupId),Number(v.n)]))}return{async listLinks(i){(0,P.assertTenantId)(i,"listLinks(tenantId)"),await s();let y=await l.select({linkId:a.id,groupId:a.groupId,tenantId:a.tenantId,provider:a.provider,externalId:a.externalId,externalKey:a.externalKey,membershipMode:a.membershipMode,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,i)).orderBy((0,c.asc)(a.groupId)),b=await G(l,i);return y.map(v=>((0,P.assertTenantMatch)(i,String(v.tenantId),"listLinks(row.tenantId)"),x(v,b.get(Number(v.groupId))??0)))},async externalGroupKey(i,y,b){(0,P.assertTenantId)(i,"externalGroupKey(tenantId)"),await s();let[v]=await(b??l).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));return v?String(v.externalKey):null},async linkGroup(i,y,b,v){(0,P.assertTenantId)(i,"linkGroup(tenantId)");let k=B(b.membershipMode),R=b.externalId.trim(),E=(0,te.normalizeAuthEmail)(b.externalKey);if(R===""||E==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(i,y,async Q=>{let M=Q,se=await A(M,i,y);await T(M,i,y,se),await M.insert(a).output().values({groupId:y,tenantId:i,provider:b.provider,externalId:R,externalKey:E,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:v})}),h(i)},async unlinkGroup(i,y){(0,P.assertTenantId)(i,"unlinkGroup(tenantId)"),await s(),await f(async b=>{await A(b,i,y),await b.delete(t).where((0,c.eq)(t.groupId,y)),await b.delete(r).where((0,c.and)((0,c.eq)(r.groupId,y),(0,c.eq)(r.tenantId,i))),await b.delete(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))}),h(i)},async setSyncEnabled(i,y,b,v){(0,P.assertTenantId)(i,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)))},async requestSync(i,y,b){return(0,P.assertTenantId)(i,"requestSync(tenantId)"),await s(),l.transaction(async v=>{if(y!==null&&await A(v,i,y),(await v.select({id:g.id}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),y===null?(0,c.isNull)(g.groupId):(0,c.eq)(g.groupId,y),(0,c.isNull)(g.pickedAt)))).length>0)return"already-pending";try{await v.insert(g).output().values({tenantId:i,groupId:y,requestedBy:b,requestedAt:new Date,createdAt:new Date,createdBy:b})}catch(R){if(!Fe(R))throw R;return"already-pending"}return"queued"})},async getStatus(i){(0,P.assertTenantId)(i,"getStatus(tenantId)"),await s();let y=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,i)),[b]=await l.select({n:(0,c.count)()}).from(g).where((0,c.and)((0,c.eq)(g.tenantId,i),(0,c.isNull)(g.finishedAt))),v=y.filter(M=>!!M.isSyncEnabled),k=v.map(M=>M.syncedAt?(0,ce.toInstant)(M.syncedAt).getTime():null),R=k.length>0&&k.every(M=>M!==null)?Math.min(...k):null,Q=y.length>0&&v.length===0||v.length>0&&(R===null||Date.now()-R>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:y.length,...R!==null?{oldestSyncedAt:new Date(R).toISOString()}:{},isStale:Q,pausedGroupCount:y.length-v.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:y.filter(M=>!!M.syncError).map(M=>Number(M.groupId)).sort((M,se)=>M-se),pendingRequestCount:Number(b?.n??0)}},async loadTenantMembershipPolicy(i){(0,P.assertTenantId)(i,"loadTenantMembershipPolicy(tenantId)");let y=S>0?w.get(i):void 0;if(y&&Date.now()-y.loadedAt<S)return y.policy;await s();let b=await l.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass,externalLinkId:a.id}).from(o).innerJoin(u,(0,c.and)((0,c.eq)(u.id,o.groupId),(0,c.eq)(u.tenantId,i))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,o.groupId),(0,c.eq)(a.tenantId,i))).where((0,c.eq)(o.tenantId,i));if(b.length===0){let E={kind:"no-proof-required"};return S>0&&w.set(i,{policy:E,loadedAt:Date.now()}),E}let v=b.map(E=>Number(E.groupId)),k=b.filter(E=>!!E.isBreakGlass);for(let E of k)if(E.externalLinkId!==null)throw new Error(`Break-glass group ${Number(E.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let R={kind:"require-proof",proofGroupIds:v,breakGlassGroupIds:k.map(E=>Number(E.groupId))};return(0,ve.assertTenantMembershipPolicy)(R,`tenant ${i}`),S>0&&w.set(i,{policy:R,loadedAt:Date.now()}),R},async setTenantMembershipGroup(i,y,b,v){(0,P.assertTenantId)(i,"setTenantMembershipGroup(tenantId)"),await s(),await f(async k=>{if(await A(k,i,y),b){let[E]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,i),(0,c.eq)(a.groupId,y)));if(E)throw new Error(`Group ${y} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let R=await k.select({groupId:o.groupId}).from(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));if(!b&&!(await k.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(M=>Number(M.groupId)!==y).some(M=>!!M.isBreakGlass))throw new Error(`Declaring group ${y} as ordinary proof would leave tenant ${i} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(R.length>0){await k.update(o).set({isBreakGlass:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)));return}await k.insert(o).output().values({tenantId:i,groupId:y,isBreakGlass:b,createdAt:new Date,createdBy:v})}),h(i)},async removeTenantMembershipGroup(i,y){(0,P.assertTenantId)(i,"removeTenantMembershipGroup(tenantId)"),await s(),await f(async b=>{let k=(await b.select({groupId:o.groupId,isBreakGlass:o.isBreakGlass}).from(o).where((0,c.eq)(o.tenantId,i))).filter(R=>Number(R.groupId)!==y);if(k.length>0&&!k.some(R=>!!R.isBreakGlass))throw new Error(`Removing group ${y} would leave tenant ${i} with a membership proof but no break-glass path.`);await b.delete(o).where((0,c.and)((0,c.eq)(o.tenantId,i),(0,c.eq)(o.groupId,y)))}),h(i)},async setGroupActive(i,y,b,v){(0,P.assertTenantId)(i,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:b,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(u.id,y),(0,c.eq)(u.tenantId,i)))},async listLedgerGroupIds(i,y){if((0,P.assertTenantId)(i,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let b=await H(l,{ledger:t,group:u},{tenantId:i,externalUserId:y.externalUserId?.trim()||null,email:(0,te.normalizeAuthEmail)(y.email)});return[...new Set(b.map(v=>v.groupId))].sort((v,k)=>v-k)},async isEmailAllowedByGroup(i){if(!d.isEnabled)return!1;let y=(0,te.normalizeAuthEmail)(i);if(y==="")return!1;await s();let[b]=await l.select({id:t.id}).from(t).innerJoin(r,(0,c.eq)(r.groupId,t.groupId)).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.isActive,!0))).where((0,c.eq)(t.email,y));return!!b},async listAllowlistGroups(i){return(0,P.assertTenantId)(i,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:r.id,groupId:r.groupId,groupKey:u.groupKey,groupName:u.name,description:r.description}).from(r).innerJoin(u,(0,c.and)((0,c.eq)(u.id,r.groupId),(0,c.eq)(u.tenantId,i))).where((0,c.eq)(r.tenantId,i)).orderBy((0,c.asc)(u.name))).map(b=>({id:Number(b.id),type:"group",groupId:Number(b.groupId),groupKey:String(b.groupKey),groupName:String(b.groupName),...b.description?{description:String(b.description)}:{},owner:{kind:"tenant",tenantId:i},readOnly:!1}))},async addAllowlistGroup(i,y,b,v){(0,P.assertTenantId)(i,"addAllowlistGroup(tenantId)"),await s(),await f(async k=>{let R=await A(k,i,y),[E]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,i)));if(!E)throw new K(d.refusals.allowlistNeedsLink(R));await k.insert(r).output().values({tenantId:i,groupId:y,description:b??null,createdAt:new Date,createdBy:v})})},async removeAllowlistGroup(i,y){(0,P.assertTenantId)(i,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where((0,c.and)((0,c.eq)(r.id,y),(0,c.eq)(r.tenantId,i)))},async countProjectedMembers(i,y){(0,P.assertTenantId)(i,"countProjectedMembers(tenantId)"),await s();let[b]=await l.select({n:(0,c.count)()}).from(I).innerJoin(u,(0,c.and)((0,c.eq)(u.id,I.groupId),(0,c.eq)(u.tenantId,i))).where((0,c.eq)(I.groupId,y));return Number(b?.n??0)}}}var Se=require("@aiquants/auth-core"),ne=require("@aiquants/authz-core");async function We(e,n){let m=(0,ne.assertTenantId)(n.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(n.userId)||n.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(n.userId)}`);let d=(0,Se.normalizeAuthEmail)(n.email),l=n.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:m,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let h of s)(0,ne.assertTenantMatch)(m,h.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let f=[...new Set(s.map(h=>h.ledgerId))].sort((h,a)=>h-a),S=[...new Set(s.map(h=>h.groupId))].sort((h,a)=>h-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:m,userId:n.userId,ledgerIds:f,groupIds:S})].sort((h,a)=>h-a),matchedLedgerIds:f}}var pe=require("@aiquants/authz-core"),z=require("@aiquants/mssql"),De=require("@aiquants/mssql/drizzle"),L=require("drizzle-orm");var He=4;function Je(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.now??(()=>new Date),w=n.TDGroupMemberEmail,{TMGroup:h,TDGroupMember:a}=m;return{async findLedgerMatches(t){return(0,pe.assertTenantId)(t.tenantId,"findLedgerMatches(tenantId)"),await s(),H(l,{ledger:w,group:h},t)},async attachUserToGroups({tenantId:t,userId:o,ledgerIds:r,groupIds:g}){return(0,pe.assertTenantId)(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:f(async u=>{let I=new Set;for(let G of(0,z.chunk)(g)){let i=await u.select({id:h.id}).from(h).where((0,L.and)((0,L.inArray)(h.id,G),(0,L.eq)(h.tenantId,t),(0,L.eq)(h.isActive,!0)));for(let y of i)I.add(Number(y.id))}let T=g.filter(G=>I.has(G));if(T.length===0)return[];let A=new Set;for(let G of(0,z.chunk)(T)){let i=await u.select({groupId:a.groupId}).from(a).where((0,L.and)((0,L.eq)(a.userId,o),(0,L.inArray)(a.groupId,G)));for(let y of i)A.add(Number(y.groupId))}let x=T.filter(G=>!A.has(G));for(let G of(0,z.chunk)(r))await u.update(w).set({resolvedUserId:o}).output().where((0,L.and)((0,L.inArray)(w.id,G),(0,De.inJsonList)(w.groupId,T)));if(x.length>0){let G=S();for(let i of(0,z.chunkByParameters)(x,He))await u.insert(a).output().values(i.map(y=>({groupId:y,userId:o,createdAt:G,createdBy:d.actor})))}return x})}}}var q=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),oe="provider",Ye=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function Ve(e,n){let m=(0,p.mssqlSchema)(e),d=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50}),updatedAt:(0,p.datetime2)("updated_at"),updatedBy:(0,p.nvarchar)("updated_by",{length:50})}),l=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),s=m.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),membershipMode:(0,p.nvarchar)("membership_mode",{length:16}).notNull(),isSyncEnabled:(0,p.bit)("is_sync_enabled").notNull(),syncedAt:(0,p.datetime2)("synced_at"),syncError:(0,p.nvarchar)("sync_error",{length:1e3}),vanishedObservations:(0,p.int)("vanished_observations").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),(0,p.foreignKey)({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),f=m.table("TDGroupMemberEmail",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),email:(0,p.nvarchar)("email",{length:320}).notNull(),externalUserId:(0,p.nvarchar)("external_user_id",{length:128}),resolvedUserId:(0,p.bigint)("resolved_user_id",{mode:"number"}),observedAt:(0,p.datetime2)("observed_at").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),(0,p.foreignKey)({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),S=m.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),(0,p.foreignKey)({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),w=m.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),(0,p.foreignKey)({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),h=m.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}),requestedBy:(0,p.nvarchar)("requested_by",{length:320}).notNull(),requestedAt:(0,p.datetime2)("requested_at").notNull(),pickedAt:(0,p.datetime2)("picked_at"),finishedAt:(0,p.datetime2)("finished_at"),result:(0,p.nvarchar)("result",{length:32}),error:(0,p.nvarchar)("error",{length:2e3}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=m.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),pattern:(0,p.nvarchar)("pattern",{length:256}).notNull(),type:(0,p.nvarchar)("type",{length:32}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=m.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),name:(0,p.nvarchar)("name",{length:256}).notNull(),description:(0,p.nvarchar)("description",{length:1e3}),memberCount:(0,p.int)("member_count"),observedAt:(0,p.datetime2)("observed_at").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),o=m.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(q.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(oe,{length:32}).notNull(),observedAt:(0,p.datetime2)("observed_at").notNull(),entryCount:(0,p.int)("entry_count").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:f,TDTenantMembershipGroup:S,TDAllowlistGroup:w,TDDirectorySyncRequest:h,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:o,TMAllowlist:a}}var ae=require("@aiquants/auth-directory-core");function J(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let n=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof n!="number"?"unknown":n===401||n===403?"denied":n===429||n===408?"rateLimited":n>=500?"upstreamError":"unknown"}var Te=e=>`${e.length} \u4EF6`,me={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},ye={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${me[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${me[e]})\u3002`,resolverIncomplete:(e,n)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${n} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(e,n,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${n} \u56DE\u76EE\u3067\u3059\u3002${m} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,n)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${n} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${e})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:e=>{if(e.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C ${Te(e.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let n=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${n}\u304C ${e.population} \u4EBA\u4E2D ${e.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${e.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${e.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${Te(m)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalWritesFailed:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(e.map(n=>me[n.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${e.map(n=>n.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,n)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${n} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var ie=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],ge=[...ie,"already-vanished"];function Qe(e){let{directory:n,repository:m,policy:d,now:l,logger:s}=e,f=e.messages??ye;if((0,ae.assertBlastRadiusPolicy)(d),!Number.isInteger(e.vanishedThreshold)||e.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(e.vanishedThreshold)}`);if(typeof e.provider!="string"||e.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function S(t,o){try{await m.recordFailure(t,o)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function w(t){try{let o=await n.getGroup(t.externalId);return o===null?{kind:"missing"}:{kind:"ok",group:o,members:await n.listGroupMembers(o,t.membershipMode)}}catch(o){return{kind:"failed",error:o instanceof Error?o:new Error(String(o))}}}async function h(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let o=await m.recordVanishedObservation(t,f.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return o<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${o}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:o}):(await m.markGroupInactive(t),await S(t,f.groupVanished(t.externalKey,o)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${o} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await S(t,f.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let o=await w(t);if(o.kind==="missing")return h(t);if(o.kind==="failed")return await S(t,f.upstreamUnreadable(J(o.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,o.error),{kind:"upstream-failed",link:t,error:o.error};try{await m.resolveExternalIdentity(t,o.group);let[r,g]=await Promise.all([m.loadGroupSnapshot(t),m.resolveMembers(o.members)]);if(g.length!==o.members.length){let x=f.resolverIncomplete(o.members.length,g.length);return await S(t,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:t,error:new Error(x)}}let u=(0,ae.reconcile)(r,g,d);if(u.aborted){await S(t,f.planRefused(u.aborted));let x=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${x.join(", ")}`),{kind:"aborted",link:t,plan:u}}let I=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,T=l(),A={refusals:[],failures:[]};return I&&(A=await m.applyPlan(t,u,T)),await m.recordSuccess(t,o.group,T),A.refusals.length>0&&(await S(t,f.removalsRefused(A.refusals)),s.warn(`[auth-directory] ${A.refusals.length} removal(s) refused for ${t.externalKey}: users ${A.refusals.map(x=>x.userId).join(", ")}`)),A.failures.length>0&&(await S(t,f.removalWritesFailed(A.failures)),s.warn(`[auth-directory] ${A.failures.length} removal(s) could not be written for ${t.externalKey}: users ${A.failures.map(x=>x.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),A.refusals.length>0||A.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:A.refusals,failures:A.failures}:I?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let g=r instanceof Error?r:new Error(String(r));return await S(t,f.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,g),{kind:"local-failed",link:t,error:g}}}return{syncOne:a,async run(t){let o=await m.listSyncableLinks(t),r=[];for(let I of o)try{r.push(await a(I))}catch(T){let A=T instanceof Error?T:new Error(String(T));s.error(`[auth-directory] ${I.externalKey} ended abnormally:`,A),r.push({kind:"local-failed",link:I,error:A})}let g=r.filter(I=>ge.includes(I.kind)).length,u=r.filter(I=>ie.includes(I.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${g} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:g,newlyUnhealthyCount:u}}}}var Ae=require("@aiquants/authz-core"),xe=require("@aiquants/mssql"),C=require("drizzle-orm"),ke="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",he=2e3;function Ze(e,n,m){let d=e,l=m.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,f=(0,Ae.assertTenantId)(m.tenantId,"createSyncRequestQueue(tenantId)"),S=m.maxErrorLength??he;if(!Number.isInteger(S)||S<1||S>he)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${he}, received ${String(m.maxErrorLength)}`);let w={async claim(){await l();let h=await d.select({id:s.id}).from(s).where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.isNull)(s.pickedAt))).orderBy((0,C.asc)(s.id));for(let a of h){let o=(await d.update(s).set({pickedAt:m.now()}).output().where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.eq)(s.id,Number(a.id)),(0,C.isNull)(s.pickedAt))))?.[0];if(!o)continue;if(o.requestedBy===void 0||o.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(o.id)}`);let r=o.groupId??null;return{id:Number(o.id),tenantId:f,groupId:r===null?null:Number(r),requestedBy:String(o.requestedBy)}}return null},async finish(h,a){await l();let t=a.error?a.error.slice(0,S):null;return!!(await d.update(s).set({finishedAt:m.now(),result:a.result,error:t}).output().where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.eq)(s.id,h),(0,C.isNull)(s.finishedAt))))?.length},async listStranded(h){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,C.and)((0,C.eq)(s.tenantId,f),(0,C.isNull)(s.finishedAt))).orderBy((0,C.asc)(s.id)),t=o=>{let r=(0,xe.toInstant)(o);return Number.isNaN(r.getTime())||r<h};return{stranded:a.filter(o=>o.pickedAt!==null&&t(o.pickedAt)).map(o=>Number(o.id)),unclaimed:a.filter(o=>o.pickedAt===null&&t(o.requestedAt)).map(o=>Number(o.id))}},async abandonStranded(h){let{stranded:a}=await w.listStranded(h),t=[];for(let o of a)try{await w.finish(o,{result:"failed",error:ke})&&t.push(o)}catch(r){m.onCloseFailed?.(o,r)}return t}};return w}var Y=require("@aiquants/auth-core"),V=require("@aiquants/authz-core"),F=require("@aiquants/mssql"),Ge=require("@aiquants/mssql/drizzle"),D=require("drizzle-orm");var Xe=7,er=4,Re=1e3;function rr(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function tr(e,n,m,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),S=d.now??(()=>new Date),{TDGroupExternalLink:w,TDGroupMemberEmail:h}=n,{TMGroup:a,TDGroupMember:t}=m,o=d.actor;return{async listSyncableLinks(r){await s();let g=(0,V.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),u=[(0,D.eq)(w.isSyncEnabled,!0),(0,D.eq)(w.tenantId,g)];return r.groupId!==void 0&&u.push((0,D.eq)(w.groupId,r.groupId)),(await l.select({linkId:w.id,groupId:w.groupId,linkTenantId:w.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:w.provider,externalId:w.externalId,externalKey:w.externalKey,membershipMode:w.membershipMode,vanishedObservations:w.vanishedObservations}).from(w).innerJoin(a,(0,D.and)((0,D.eq)(a.id,w.groupId),(0,D.eq)(a.tenantId,g))).where((0,D.and)(...u))).map(T=>((0,V.assertTenantMatch)(String(T.groupTenantId),String(T.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(T.linkId),groupId:Number(T.groupId),groupKey:String(T.groupKey),tenantId:String(T.groupTenantId),provider:String(T.provider),externalId:String(T.externalId),externalKey:String(T.externalKey),membershipMode:B(String(T.membershipMode)),isGroupActive:!!T.isGroupActive,vanishedObservations:Number(T.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[g,u]=await Promise.all([l.select({email:h.email,externalUserId:h.externalUserId,resolvedUserId:h.resolvedUserId}).from(h).where((0,D.eq)(h.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where((0,D.eq)(t.groupId,r.groupId))]);return{ledger:g.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:u.map(I=>Number(I.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,g,u){(0,V.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await s(),(g.ledgerUpserts.length>0||g.projectionAdds.length>0)&&await f(async x=>{let G=g.ledgerUpserts.map(b=>({...b,email:(0,Y.normalizeAuthEmail)(b.email)})),i=new Map;for(let b of(0,F.chunk)(G.map(v=>v.email))){let v=await x.select({id:h.id,email:h.email}).from(h).where((0,D.and)((0,D.eq)(h.groupId,r.groupId),(0,D.inArray)(h.email,b)));for(let k of v)i.set(String(k.email),Number(k.id))}let y=G.filter(b=>!i.has(b.email));for(let b of(0,F.chunkByParameters)(y,Xe))await x.insert(h).output().values(b.map(v=>({groupId:r.groupId,email:v.email,externalUserId:v.externalUserId??null,resolvedUserId:v.resolvedUserId,observedAt:u,createdAt:u,createdBy:o})));for(let b of G){let v=i.get(b.email);v!==void 0&&await x.update(h).set({externalUserId:b.externalUserId??null,resolvedUserId:b.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:o}).output().where((0,D.eq)(h.id,v))}for(let b of(0,F.chunkByParameters)(g.projectionAdds,er)){let v=new Set((await x.select({userId:t.userId}).from(t).where((0,D.and)((0,D.eq)(t.groupId,r.groupId),(0,D.inArray)(t.userId,b)))).map(R=>Number(R.userId))),k=b.filter(R=>!v.has(R));k.length>0&&await x.insert(t).output().values(k.map(R=>({groupId:r.groupId,userId:R,createdAt:u,createdBy:o})))}});let I=[],T=[];for(let x of g.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:x}},"remove-group-member",G=>G.delete(t).where((0,D.and)((0,D.eq)(t.groupId,r.groupId),(0,D.eq)(t.userId,x))))}catch(G){rr(G)?I.push({userId:x,applied:!1,refusal:G.message}):T.push({userId:x,cause:J(G)})}let A=I.map(x=>x.userId);return g.ledgerDeletes.length>0&&await f(async x=>{for(let G of(0,F.chunk)(g.ledgerDeletes)){let i=A.length>0?(0,D.or)((0,D.isNull)(h.resolvedUserId),(0,Ge.notInJsonList)(h.resolvedUserId,A)):void 0;await x.delete(h).where((0,D.and)((0,D.eq)(h.groupId,r.groupId),(0,D.inArray)(h.email,G),...i?[i]:[]))}}),{refusals:I,failures:T}},async resolveExternalIdentity(r,g){await s(),!(r.externalId===g.externalId&&r.externalKey===(0,Y.normalizeAuthEmail)(g.externalKey))&&await l.update(w).set({externalId:g.externalId,externalKey:(0,Y.normalizeAuthEmail)(g.externalKey)}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId)))},async recordSuccess(r,g,u){await s(),await f(async I=>{await I.update(w).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:g.externalId,externalKey:(0,Y.normalizeAuthEmail)(g.externalKey),updatedAt:u,updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId))),r.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:u,updatedBy:o}).output().where((0,D.and)((0,D.eq)(a.id,r.groupId),(0,D.eq)(a.tenantId,r.tenantId)))})},async recordFailure(r,g){await s(),await l.update(w).set({syncError:g.slice(0,Re),updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId)))},async recordVanishedObservation(r,g){await s();let u=r.vanishedObservations+1;return await l.update(w).set({vanishedObservations:u,syncError:g.slice(0,Re),updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(w.id,r.linkId),(0,D.eq)(w.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:S(),updatedBy:o}).output().where((0,D.and)((0,D.eq)(a.id,r.groupId),(0,D.eq)(a.tenantId,r.tenantId),(0,D.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,DirectoryOperatorError,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultDirectoryLinkRefusals,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{assertTenantId as Ie}from"@aiquants/authz-core";import{chunkByParameters as Oe,toInstant as $e}from"@aiquants/mssql";import{and as V,asc as Ne,eq as N}from"drizzle-orm";import{runWithTransactionRetry as Ke}from"@aiquants/mssql";function $(r,a){return m=>Ke(d=>r.transaction(l=>d(()=>m(l))),a.transactionRetry)}var qe=10,O={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},we=2147483647;function Ue(r){if(r===void 0||!Number.isFinite(r))return null;let a=Math.trunc(r);return a<0?null:a>we?we:a}function j(r,a,m){let d=r.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} must not be blank`);if(d.length>a)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} exceeds its column length (${a}); truncating it would change which rows it owns`);return d}function fr(r,a,m){let d=r,l=m.ensureConnection??(async()=>{}),s=$(d,m),h=a.TDDirectoryCatalog,w=a.TMDirectoryCatalogObservation,b=y=>{let i=new Map;for(let t of y)i.set(t.externalId,t);return[...i.values()]};return{async replace(y,i,t){await l();let n=j(Ie(y,"replace(tenantId)"),O.tenantId,"tenantId"),e=j(i,O.provider,"provider"),p=j(m.actor,O.actor,"actor"),u=m.now(),f=b(t.map(S=>({tenantId:n,provider:e,externalId:S.externalId.slice(0,O.externalId),externalKey:S.externalKey.slice(0,O.externalKey),name:S.name.slice(0,O.name),description:S.description===void 0?null:S.description.slice(0,O.description),memberCount:Ue(S.memberCount),observedAt:u,createdAt:u,createdBy:p})));return await s(async S=>{await S.delete(h).where(V(N(h.tenantId,n),N(h.provider,e)));for(let T of Oe(f,qe))await S.insert(h).values(T);await S.delete(w).where(V(N(w.tenantId,n),N(w.provider,e))),await S.insert(w).values([{tenantId:n,provider:e,observedAt:u,entryCount:f.length,createdAt:u,createdBy:p,updatedAt:u,updatedBy:p}])}),f.length}}}function wr(r,a,m={}){let d=r,l=m.ensureConnection??(async()=>{}),s=a.TDDirectoryCatalog,h=a.TMDirectoryCatalogObservation;return{async read(w,b){await l();let y=j(Ie(w,"read(tenantId)"),O.tenantId,"tenantId"),i=j(b,O.provider,"provider"),t=await d.select({observedAt:h.observedAt,entryCount:h.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(h).leftJoin(s,V(N(s.tenantId,h.tenantId),N(s.provider,h.provider))).where(V(N(h.tenantId,y),N(h.provider,i))).orderBy(Ne(s.name)),n=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),e=t[0];if(e===void 0)return{groups:n,observedAt:null};if(Number(e.entryCount)!==n.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${y}, ${i}): the observation records ${e.entryCount} entries but ${n.length} rows are present`);let p=$e(e.observedAt);if(!Number.isFinite(p.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${y}, ${i}) has an unreadable observed_at`);return{groups:n,observedAt:p}}}}import{AUTHZ_ACTIONS as Be}from"@aiquants/authz-core";var Sr=(r,a)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${a}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${r}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function z(r,a,m,d){return new Set(r.filter(l=>a.has(l.roleId)&&l.appKey===m&&l.resourceKey===d).map(l=>l.action))}function Q(r,a){return new Set(r.filter(m=>m.groupId===a).map(m=>m.roleId))}var W=r=>Be.every(a=>r.has(a)),X=(r,a)=>r.find(m=>m.roleId===a)?.roleKey??`#${a}`;function Z(r,a,m,d){let l=[...a].find(h=>W(z(r,new Set([h]),m,d)));return l!==void 0?X(r,l):[...a].filter(h=>z(r,new Set([h]),m,d).size>0).map(h=>X(r,h)).join(" + ")}function Tr(r,a){return ve(r,a)}function ve(r,a){let m=r;return{...r,withTransaction:(d,l)=>m.withTransaction((s,h)=>d(ve(s,a),h),l),async assignGroupRole(d,l,s,h){await m.withTransaction(async(w,b)=>{let y=await w.listGrants(d),i=Q(await w.listGroupRoles(d),l);if(i.add(s),W(z(y,i,a.adminAppKey,a.adminResourceKey))){let t=await a.linkLookup.externalGroupKey(d,l,b);if(t!==null)throw new Error(a.message(Z(y,i,a.adminAppKey,a.adminResourceKey),t))}await w.assignGroupRole(d,l,s,h)})},async updateGrant(d,l,s,h){let w=s.action;if(w===void 0)return m.updateGrant(d,l,s,h);await m.withTransaction(async(b,y)=>{let i=await b.listGrants(d),t=i.find(p=>p.id===l);if(t===void 0)return b.updateGrant(d,l,s,h);let e=(await b.listResources(d)).find(p=>p.id===t.resourceId);if(e?.appKey===a.adminAppKey&&e?.resourceKey===a.adminResourceKey){let p=await b.listGroupRoles(d);for(let u of new Set(p.filter(f=>f.roleId===t.roleId).map(f=>f.groupId))){let f=Q(p,u),S=z(i.filter(D=>D.id!==l),f,a.adminAppKey,a.adminResourceKey);if(f.has(t.roleId)&&S.add(w),!W(S))continue;let T=await a.linkLookup.externalGroupKey(d,u,y);if(T!==null)throw new Error(a.message(Z(i,f,a.adminAppKey,a.adminResourceKey)||X(i,t.roleId),T))}}await b.updateGrant(d,l,s,h)})},async createGrant(d,l,s){await m.withTransaction(async(h,w)=>{let y=(await h.listResources(d)).find(i=>i.id===l.resourceId);if(y?.appKey===a.adminAppKey&&y?.resourceKey===a.adminResourceKey){let i=await h.listGrants(d),t=await h.listGroupRoles(d);for(let n of new Set(t.filter(e=>e.roleId===l.roleId).map(e=>e.groupId))){let e=z(i,Q(t,n),a.adminAppKey,a.adminResourceKey);if(e.add(l.action),!W(e))continue;let p=await a.linkLookup.externalGroupKey(d,n,w);if(p!==null)throw new Error(a.message(Z(i,Q(t,n),a.adminAppKey,a.adminResourceKey)||X(i,l.roleId),p))}}await h.createGrant(d,l,s)})}}}async function Dr(r,a,m,d){let l=(await r.listGroupRoles(a)).filter(b=>b.groupId===m);if(l.length===0)return;let s=await r.listGrants(a),h=new Set(l.map(b=>b.roleId));if(!W(z(s,h,d.adminAppKey,d.adminResourceKey)))return;let w=l[0];if(w)throw new Error(d.message(Z(s,h,d.adminAppKey,d.adminResourceKey),w.groupKey))}import{and as ze,eq as H}from"drizzle-orm";async function ee(r,a,m){let{ledger:d,group:l}=a,{tenantId:s,externalUserId:h,email:w}=m;if(h===null&&w==="")return[];let b={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},y=async p=>(await r.select(b).from(d).innerJoin(l,ze(H(l.id,d.groupId),H(l.tenantId,s),H(l.isActive,!0))).where(p)).map(f=>({ledgerId:Number(f.ledgerId),groupId:Number(f.groupId),tenantId:String(f.tenantId),externalUserId:f.externalUserId===null?null:String(f.externalUserId)})),i=p=>{let u=new Map;for(let f of p)u.set(f.ledgerId,{ledgerId:f.ledgerId,groupId:f.groupId,tenantId:f.tenantId});return[...u.values()].sort((f,S)=>f.ledgerId-S.ledgerId)},t=h===null?[]:await y(H(d.externalUserId,h));if(w==="")return i(t);let n=await y(H(d.email,w)),e=p=>p.externalUserId!==null&&p.externalUserId!==h;return i([...t,...n.filter(p=>!e(p))])}import{normalizeAuthEmail as se}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as Fe}from"@aiquants/auth-directory-core";import{assertTenantId as P,assertTenantMatch as je}from"@aiquants/authz-core";import{toInstant as Te}from"@aiquants/mssql";import{and as E,asc as De,count as ue,eq as v,isNull as re}from"drizzle-orm";var Se=["direct","transitive"];function J(r){if(!Se.includes(r))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${Se.join(" | ")}, received ${JSON.stringify(r)}`);return r}function We(r){let a=typeof r=="object"&&r!==null&&"number"in r?r.number:void 0;return a===2601||a===2627}function Or(r,a,m,d){let l=r,s=d.ensureConnection??(async()=>{}),h=$(l,d),w=d.policyCacheTtlMs??0;if(!Number.isFinite(w)||w<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let b=new Map,y=o=>b.delete(o),{TDGroupExternalLink:i,TDGroupMemberEmail:t,TDTenantMembershipGroup:n,TDAllowlistGroup:e,TDDirectorySyncRequest:p}=a,{TMGroup:u,TDGroupMember:f}=m;async function S(o,c,g){if((await o.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,c),v(n.groupId,g),v(n.isBreakGlass,!0)))).length>0)throw new Error(`Group ${g} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function T(o,c,g){let[I]=await o.select({id:u.id}).from(u).where(E(v(u.id,g),v(u.tenantId,c)));if(!I)throw new Error(`Group not found in tenant: ${g}`)}function D(o,c){return{linkId:Number(o.linkId),groupId:Number(o.groupId),tenantId:String(o.tenantId),provider:String(o.provider),externalId:String(o.externalId),externalKey:String(o.externalKey),membershipMode:J(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:Te(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:c}}async function G(o,c){let g=await o.select({groupId:t.groupId,n:ue()}).from(t).innerJoin(u,E(v(u.id,t.groupId),v(u.tenantId,c))).where(re(t.resolvedUserId)).groupBy(t.groupId);return new Map(g.map(I=>[Number(I.groupId),Number(I.n)]))}return{async listLinks(o){P(o,"listLinks(tenantId)"),await s();let c=await l.select({linkId:i.id,groupId:i.groupId,tenantId:i.tenantId,provider:i.provider,externalId:i.externalId,externalKey:i.externalKey,membershipMode:i.membershipMode,isSyncEnabled:i.isSyncEnabled,syncedAt:i.syncedAt,syncError:i.syncError}).from(i).where(v(i.tenantId,o)).orderBy(De(i.groupId)),g=await G(l,o);return c.map(I=>(je(o,String(I.tenantId),"listLinks(row.tenantId)"),D(I,g.get(Number(I.groupId))??0)))},async externalGroupKey(o,c,g){P(o,"externalGroupKey(tenantId)"),await s();let[I]=await(g??l).select({externalKey:i.externalKey}).from(i).where(E(v(i.groupId,c),v(i.tenantId,o)));return I?String(I.externalKey):null},async linkGroup(o,c,g,I){P(o,"linkGroup(tenantId)");let A=J(g.membershipMode),k=g.externalId.trim(),C=se(g.externalKey);if(k===""||C==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,c,async Y=>{let M=Y;await T(M,o,c),await S(M,o,c),await M.insert(i).output().values({groupId:c,tenantId:o,provider:g.provider,externalId:k,externalKey:C,membershipMode:A,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:I})}),y(o)},async unlinkGroup(o,c){P(o,"unlinkGroup(tenantId)"),await s(),await h(async g=>{await T(g,o,c),await g.delete(t).where(v(t.groupId,c)),await g.delete(e).where(E(v(e.groupId,c),v(e.tenantId,o))),await g.delete(i).where(E(v(i.groupId,c),v(i.tenantId,o)))}),y(o)},async setSyncEnabled(o,c,g,I){P(o,"setSyncEnabled(tenantId)"),await s(),await l.update(i).set({isSyncEnabled:g,updatedAt:new Date,updatedBy:I}).output().where(E(v(i.groupId,c),v(i.tenantId,o)))},async requestSync(o,c,g){return P(o,"requestSync(tenantId)"),await s(),l.transaction(async I=>{if(c!==null&&await T(I,o,c),(await I.select({id:p.id}).from(p).where(E(v(p.tenantId,o),c===null?re(p.groupId):v(p.groupId,c),re(p.pickedAt)))).length>0)return"already-pending";try{await I.insert(p).output().values({tenantId:o,groupId:c,requestedBy:g,requestedAt:new Date,createdAt:new Date,createdBy:g})}catch(k){if(!We(k))throw k;return"already-pending"}return"queued"})},async getStatus(o){P(o,"getStatus(tenantId)"),await s();let c=await l.select({groupId:i.groupId,isSyncEnabled:i.isSyncEnabled,syncedAt:i.syncedAt,syncError:i.syncError}).from(i).where(v(i.tenantId,o)),[g]=await l.select({n:ue()}).from(p).where(E(v(p.tenantId,o),re(p.finishedAt))),I=c.filter(M=>!!M.isSyncEnabled),A=I.map(M=>M.syncedAt?Te(M.syncedAt).getTime():null),k=A.length>0&&A.every(M=>M!==null)?Math.min(...A):null,Y=c.length>0&&I.length===0||I.length>0&&(k===null||Date.now()-k>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:c.length,...k!==null?{oldestSyncedAt:new Date(k).toISOString()}:{},isStale:Y,pausedGroupCount:c.length-I.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:c.filter(M=>!!M.syncError).map(M=>Number(M.groupId)).sort((M,Le)=>M-Le),pendingRequestCount:Number(g?.n??0)}},async loadTenantMembershipPolicy(o){P(o,"loadTenantMembershipPolicy(tenantId)");let c=w>0?b.get(o):void 0;if(c&&Date.now()-c.loadedAt<w)return c.policy;await s();let g=await l.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass,externalLinkId:i.id}).from(n).innerJoin(u,E(v(u.id,n.groupId),v(u.tenantId,o))).leftJoin(i,E(v(i.groupId,n.groupId),v(i.tenantId,o))).where(v(n.tenantId,o));if(g.length===0){let C={kind:"no-proof-required"};return w>0&&b.set(o,{policy:C,loadedAt:Date.now()}),C}let I=g.map(C=>Number(C.groupId)),A=g.filter(C=>!!C.isBreakGlass);for(let C of A)if(C.externalLinkId!==null)throw new Error(`Break-glass group ${Number(C.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let k={kind:"require-proof",proofGroupIds:I,breakGlassGroupIds:A.map(C=>Number(C.groupId))};return Fe(k,`tenant ${o}`),w>0&&b.set(o,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(o,c,g,I){P(o,"setTenantMembershipGroup(tenantId)"),await s(),await h(async A=>{if(await T(A,o,c),g){let[C]=await A.select({id:i.id}).from(i).where(E(v(i.tenantId,o),v(i.groupId,c)));if(C)throw new Error(`Group ${c} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let k=await A.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,o),v(n.groupId,c)));if(!g&&!(await A.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(M=>Number(M.groupId)!==c).some(M=>!!M.isBreakGlass))throw new Error(`Declaring group ${c} as ordinary proof would leave tenant ${o} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(k.length>0){await A.update(n).set({isBreakGlass:g,updatedAt:new Date,updatedBy:I}).output().where(E(v(n.tenantId,o),v(n.groupId,c)));return}await A.insert(n).output().values({tenantId:o,groupId:c,isBreakGlass:g,createdAt:new Date,createdBy:I})}),y(o)},async removeTenantMembershipGroup(o,c){P(o,"removeTenantMembershipGroup(tenantId)"),await s(),await h(async g=>{let A=(await g.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(k=>Number(k.groupId)!==c);if(A.length>0&&!A.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${c} would leave tenant ${o} with a membership proof but no break-glass path.`);await g.delete(n).where(E(v(n.tenantId,o),v(n.groupId,c)))}),y(o)},async setGroupActive(o,c,g,I){P(o,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:g,updatedAt:new Date,updatedBy:I}).output().where(E(v(u.id,c),v(u.tenantId,o)))},async listLedgerGroupIds(o,c){if(P(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let g=await ee(l,{ledger:t,group:u},{tenantId:o,externalUserId:c.externalUserId?.trim()||null,email:se(c.email)});return[...new Set(g.map(I=>I.groupId))].sort((I,A)=>I-A)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let c=se(o);if(c==="")return!1;await s();let[g]=await l.select({id:t.id}).from(t).innerJoin(e,v(e.groupId,t.groupId)).innerJoin(u,E(v(u.id,t.groupId),v(u.isActive,!0))).where(v(t.email,c));return!!g},async listAllowlistGroups(o){return P(o,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:e.id,groupId:e.groupId,groupKey:u.groupKey,groupName:u.name,description:e.description}).from(e).innerJoin(u,E(v(u.id,e.groupId),v(u.tenantId,o))).where(v(e.tenantId,o)).orderBy(De(u.name))).map(g=>({id:Number(g.id),type:"group",groupId:Number(g.groupId),groupKey:String(g.groupKey),groupName:String(g.groupName),...g.description?{description:String(g.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,c,g,I){P(o,"addAllowlistGroup(tenantId)"),await s(),await h(async A=>{await T(A,o,c);let[k]=await A.select({id:i.id}).from(i).where(E(v(i.groupId,c),v(i.tenantId,o)));if(!k)throw new Error(`Group ${c} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await A.insert(e).output().values({tenantId:o,groupId:c,description:g??null,createdAt:new Date,createdBy:I})})},async removeAllowlistGroup(o,c){P(o,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(e).where(E(v(e.id,c),v(e.tenantId,o)))},async countProjectedMembers(o,c){P(o,"countProjectedMembers(tenantId)"),await s();let[g]=await l.select({n:ue()}).from(f).innerJoin(u,E(v(u.id,f.groupId),v(u.tenantId,o))).where(v(f.groupId,c));return Number(g?.n??0)}}}import{normalizeAuthEmail as He}from"@aiquants/auth-core";import{assertTenantId as Je,assertTenantMatch as Ye}from"@aiquants/authz-core";async function Ur(r,a){let m=Je(a.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(a.userId)||a.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(a.userId)}`);let d=He(a.email),l=a.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await r.findLedgerMatches({tenantId:m,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let y of s)Ye(m,y.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let h=[...new Set(s.map(y=>y.ledgerId))].sort((y,i)=>y-i),w=[...new Set(s.map(y=>y.groupId))].sort((y,i)=>y-i);return{addedGroupIds:[...await r.attachUserToGroups({tenantId:m,userId:a.userId,ledgerIds:h,groupIds:w})].sort((y,i)=>y-i),matchedLedgerIds:h}}import{assertTenantId as Ae}from"@aiquants/authz-core";import{chunk as de,chunkByParameters as Ve}from"@aiquants/mssql";import{inJsonList as Qe}from"@aiquants/mssql/drizzle";import{and as le,eq as ce,inArray as pe}from"drizzle-orm";var Ze=4;function Yr(r,a,m,d){let l=r,s=d.ensureConnection??(async()=>{}),h=$(l,d),w=d.now??(()=>new Date),b=a.TDGroupMemberEmail,{TMGroup:y,TDGroupMember:i}=m;return{async findLedgerMatches(t){return Ae(t.tenantId,"findLedgerMatches(tenantId)"),await s(),ee(l,{ledger:b,group:y},t)},async attachUserToGroups({tenantId:t,userId:n,ledgerIds:e,groupIds:p}){return Ae(t,"attachUserToGroups(tenantId)"),await s(),e.length===0?[]:h(async u=>{let f=new Set;for(let G of de(p)){let o=await u.select({id:y.id}).from(y).where(le(pe(y.id,G),ce(y.tenantId,t),ce(y.isActive,!0)));for(let c of o)f.add(Number(c.id))}let S=p.filter(G=>f.has(G));if(S.length===0)return[];let T=new Set;for(let G of de(S)){let o=await u.select({groupId:i.groupId}).from(i).where(le(ce(i.userId,n),pe(i.groupId,G)));for(let c of o)T.add(Number(c.groupId))}let D=S.filter(G=>!T.has(G));for(let G of de(e))await u.update(b).set({resolvedUserId:n}).output().where(le(pe(b.id,G),Qe(b.groupId,S)));if(D.length>0){let G=w();for(let o of Ve(D,Ze))await u.insert(i).output().values(o.map(c=>({groupId:c,userId:n,createdAt:G,createdBy:d.actor})))}return D})}}}import{AUTHZ_TENANT_COLUMN as U}from"@aiquants/authz-drizzle";import{bigint as _,bit as xe,datetime2 as K,foreignKey as te,int as me,mssqlSchema as Xe,nvarchar as x,primaryKey as q,uniqueIndex as B}from"drizzle-orm/mssql-core";var ye="provider",Xr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function et(r,a){let m=Xe(r),d=()=>({createdAt:K("created_at"),createdBy:x("created_by",{length:50}),updatedAt:K("updated_at"),updatedBy:x("updated_by",{length:50})}),l=()=>({createdAt:K("created_at"),createdBy:x("created_by",{length:50})}),s=m.table("TDGroupExternalLink",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),membershipMode:x("membership_mode",{length:16}).notNull(),isSyncEnabled:xe("is_sync_enabled").notNull(),syncedAt:K("synced_at"),syncError:x("sync_error",{length:1e3}),vanishedObservations:me("vanished_observations").notNull(),...d()},e=>[q({name:`${r}_TDGroupExternalLink_pk`,columns:[e.id]}),B(`${r}_TDGroupExternalLink_group_uq`).on(e.groupId),B(`${r}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),te({name:`${r}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),h=m.table("TDGroupMemberEmail",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),email:x("email",{length:320}).notNull(),externalUserId:x("external_user_id",{length:128}),resolvedUserId:_("resolved_user_id",{mode:"number"}),observedAt:K("observed_at").notNull(),...d()},e=>[q({name:`${r}_TDGroupMemberEmail_pk`,columns:[e.id]}),B(`${r}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),te({name:`${r}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),w=m.table("TDTenantMembershipGroup",{tenantId:x(U,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),isBreakGlass:xe("is_break_glass").notNull(),...l()},e=>[q({name:`${r}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),te({name:`${r}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),b=m.table("TDAllowlistGroup",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),description:x("description",{length:512}),...l()},e=>[q({name:`${r}_TDAllowlistGroup_pk`,columns:[e.id]}),B(`${r}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),te({name:`${r}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[a.groupTable.id]})]),y=m.table("TDDirectorySyncRequest",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}),requestedBy:x("requested_by",{length:320}).notNull(),requestedAt:K("requested_at").notNull(),pickedAt:K("picked_at"),finishedAt:K("finished_at"),result:x("result",{length:32}),error:x("error",{length:2e3}),...l()},e=>[q({name:`${r}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),i=m.table("TMAllowlist",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),pattern:x("pattern",{length:256}).notNull(),type:x("type",{length:32}).notNull(),description:x("description",{length:512}),...l()},e=>[q({name:`${r}_TMAllowlist_pk`,columns:[e.id]}),B(`${r}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]),t=m.table("TDDirectoryCatalog",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),name:x("name",{length:256}).notNull(),description:x("description",{length:1e3}),memberCount:me("member_count"),observedAt:K("observed_at").notNull(),...l()},e=>[q({name:`${r}_TDDirectoryCatalog_pk`,columns:[e.id]}),B(`${r}_TDDirectoryCatalog_external_uq`).on(e.tenantId,e.provider,e.externalId)]),n=m.table("TMDirectoryCatalogObservation",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(U,{length:64}).notNull(),provider:x(ye,{length:32}).notNull(),observedAt:K("observed_at").notNull(),entryCount:me("entry_count").notNull(),...d()},e=>[q({name:`${r}_TMDirectoryCatalogObservation_pk`,columns:[e.id]}),B(`${r}_TMDirectoryCatalogObservation_target_uq`).on(e.tenantId,e.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:h,TDTenantMembershipGroup:w,TDAllowlistGroup:b,TDDirectorySyncRequest:y,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:n,TMAllowlist:i}}import{assertBlastRadiusPolicy as er,reconcile as rr}from"@aiquants/auth-directory-core";function ne(r){if(r instanceof Error&&(r.name==="AbortError"||r.name==="TimeoutError"))return"timeout";let a=typeof r=="object"&&r!==null&&"status"in r?r.status:void 0;return typeof a!="number"?"unknown":a===401||a===403?"denied":a===429||a===408?"rateLimited":a>=500?"upstreamError":"unknown"}var ke=r=>`${r.length} \u4EF6`,ge={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},Re={upstreamUnreadable:r=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ge[r]})\u3002`,localWriteFailed:r=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ge[r]})\u3002`,resolverIncomplete:(r,a)=>`\u4E0A\u6D41\u304C ${r} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${a} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(r,a,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${r})\u3002\u9023\u7D9A ${a} \u56DE\u76EE\u3067\u3059\u3002${m} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(r,a)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${a} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${r})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:r=>{if(r.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C ${ke(r.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let a=r.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=r.axis==="membership"?r.withheldProjectionRemoves.map(String):r.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${a}\u304C ${r.population} \u4EBA\u4E2D ${r.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${r.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${r.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${ke(m)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalWritesFailed:r=>`${r.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(r.map(a=>ge[a.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:r=>`${r.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${r.map(a=>a.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(r,a)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${r} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${a} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var he=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Ge=[...he,"already-vanished"];function st(r){let{directory:a,repository:m,policy:d,now:l,logger:s}=r,h=r.messages??Re;if(er(d),!Number.isInteger(r.vanishedThreshold)||r.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(r.vanishedThreshold)}`);if(typeof r.provider!="string"||r.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function w(t,n){try{await m.recordFailure(t,n)}catch(e){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,e)}}async function b(t){try{let n=await a.getGroup(t.externalId);return n===null?{kind:"missing"}:{kind:"ok",group:n,members:await a.listGroupMembers(n,t.membershipMode)}}catch(n){return{kind:"failed",error:n instanceof Error?n:new Error(String(n))}}}async function y(t){if(t.vanishedObservations>=r.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let n=await m.recordVanishedObservation(t,h.groupMissing(t.externalKey,t.vanishedObservations+1,r.vanishedThreshold));return n<r.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${n}/${r.vanishedThreshold})`),{kind:"missing",link:t,observations:n}):(await m.markGroupInactive(t),await w(t,h.groupVanished(t.externalKey,n)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${n} consecutive absences`),{kind:"vanished",link:t})}async function i(t){if(t.provider!==r.provider)return await w(t,h.foreignProvider(t.provider,r.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let n=await b(t);if(n.kind==="missing")return y(t);if(n.kind==="failed")return await w(t,h.upstreamUnreadable(ne(n.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,n.error),{kind:"upstream-failed",link:t,error:n.error};try{await m.resolveExternalIdentity(t,n.group);let[e,p]=await Promise.all([m.loadGroupSnapshot(t),m.resolveMembers(n.members)]);if(p.length!==n.members.length){let D=h.resolverIncomplete(n.members.length,p.length);return await w(t,D),s.error(`[auth-directory] ${D}`),{kind:"local-failed",link:t,error:new Error(D)}}let u=rr(e,p,d);if(u.aborted){await w(t,h.planRefused(u.aborted));let D=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${D.join(", ")}`),{kind:"aborted",link:t,plan:u}}let f=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,S=l(),T={refusals:[],failures:[]};return f&&(T=await m.applyPlan(t,u,S)),await m.recordSuccess(t,n.group,S),T.refusals.length>0&&(await w(t,h.removalsRefused(T.refusals)),s.warn(`[auth-directory] ${T.refusals.length} removal(s) refused for ${t.externalKey}: users ${T.refusals.map(D=>D.userId).join(", ")}`)),T.failures.length>0&&(await w(t,h.removalWritesFailed(T.failures)),s.warn(`[auth-directory] ${T.failures.length} removal(s) could not be written for ${t.externalKey}: users ${T.failures.map(D=>D.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),T.refusals.length>0||T.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:T.refusals,failures:T.failures}:f?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(e){let p=e instanceof Error?e:new Error(String(e));return await w(t,h.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,p),{kind:"local-failed",link:t,error:p}}}return{syncOne:i,async run(t){let n=await m.listSyncableLinks(t),e=[];for(let f of n)try{e.push(await i(f))}catch(S){let T=S instanceof Error?S:new Error(String(S));s.error(`[auth-directory] ${f.externalKey} ended abnormally:`,T),e.push({kind:"local-failed",link:f,error:T})}let p=e.filter(f=>Ge.includes(f.kind)).length,u=e.filter(f=>he.includes(f.kind)).length;return s.info(`[auth-directory] run finished: ${e.length} link(s), ${p} unhealthy (${u} new)`),{outcomes:e,unhealthyCount:p,newlyUnhealthyCount:u}}}}import{assertTenantId as tr}from"@aiquants/authz-core";import{toInstant as nr}from"@aiquants/mssql";import{and as oe,asc as Ee,eq as F,isNull as ie}from"drizzle-orm";var or="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",be=2e3;function pt(r,a,m){let d=r,l=m.ensureConnection??(async()=>{}),s=a.TDDirectorySyncRequest,h=tr(m.tenantId,"createSyncRequestQueue(tenantId)"),w=m.maxErrorLength??be;if(!Number.isInteger(w)||w<1||w>be)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${be}, received ${String(m.maxErrorLength)}`);let b={async claim(){await l();let y=await d.select({id:s.id}).from(s).where(oe(F(s.tenantId,h),ie(s.pickedAt))).orderBy(Ee(s.id));for(let i of y){let n=(await d.update(s).set({pickedAt:m.now()}).output().where(oe(F(s.tenantId,h),F(s.id,Number(i.id)),ie(s.pickedAt))))?.[0];if(!n)continue;if(n.requestedBy===void 0||n.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(n.id)}`);let e=n.groupId??null;return{id:Number(n.id),tenantId:h,groupId:e===null?null:Number(e),requestedBy:String(n.requestedBy)}}return null},async finish(y,i){await l();let t=i.error?i.error.slice(0,w):null;return!!(await d.update(s).set({finishedAt:m.now(),result:i.result,error:t}).output().where(oe(F(s.tenantId,h),F(s.id,y),ie(s.finishedAt))))?.length},async listStranded(y){await l();let i=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where(oe(F(s.tenantId,h),ie(s.finishedAt))).orderBy(Ee(s.id)),t=n=>{let e=nr(n);return Number.isNaN(e.getTime())||e<y};return{stranded:i.filter(n=>n.pickedAt!==null&&t(n.pickedAt)).map(n=>Number(n.id)),unclaimed:i.filter(n=>n.pickedAt===null&&t(n.requestedAt)).map(n=>Number(n.id))}},async abandonStranded(y){let{stranded:i}=await b.listStranded(y),t=[];for(let n of i)try{await b.finish(n,{result:"failed",error:or})&&t.push(n)}catch(e){m.onCloseFailed?.(n,e)}return t}};return b}import{normalizeAuthEmail as ae}from"@aiquants/auth-core";import{assertTenantId as Me,assertTenantMatch as ir}from"@aiquants/authz-core";import{chunk as Ce,chunkByParameters as Pe}from"@aiquants/mssql";import{notInJsonList as ar}from"@aiquants/mssql/drizzle";import{and as L,eq as R,inArray as fe,isNull as sr,or as ur}from"drizzle-orm";var dr=7,lr=4,_e=1e3;function cr(r){if(!(r instanceof Error))return!1;let a=r;return a.number===void 0&&a.code===void 0&&r.name==="Error"}function St(r,a,m,d){let l=r,s=d.ensureConnection??(async()=>{}),h=$(l,d),w=d.now??(()=>new Date),{TDGroupExternalLink:b,TDGroupMemberEmail:y}=a,{TMGroup:i,TDGroupMember:t}=m,n=d.actor;return{async listSyncableLinks(e){await s();let p=Me(e.tenantId,"listSyncableLinks(tenantId)"),u=[R(b.isSyncEnabled,!0),R(b.tenantId,p)];return e.groupId!==void 0&&u.push(R(b.groupId,e.groupId)),(await l.select({linkId:b.id,groupId:b.groupId,linkTenantId:b.tenantId,groupTenantId:i.tenantId,groupKey:i.groupKey,isGroupActive:i.isActive,provider:b.provider,externalId:b.externalId,externalKey:b.externalKey,membershipMode:b.membershipMode,vanishedObservations:b.vanishedObservations}).from(b).innerJoin(i,L(R(i.id,b.groupId),R(i.tenantId,p))).where(L(...u))).map(S=>(ir(String(S.groupTenantId),String(S.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(S.linkId),groupId:Number(S.groupId),groupKey:String(S.groupKey),tenantId:String(S.groupTenantId),provider:String(S.provider),externalId:String(S.externalId),externalKey:String(S.externalKey),membershipMode:J(String(S.membershipMode)),isGroupActive:!!S.isGroupActive,vanishedObservations:Number(S.vanishedObservations??0)}))},async loadGroupSnapshot(e){await s();let[p,u]=await Promise.all([l.select({email:y.email,externalUserId:y.externalUserId,resolvedUserId:y.resolvedUserId}).from(y).where(R(y.groupId,e.groupId)),l.select({userId:t.userId}).from(t).where(R(t.groupId,e.groupId))]);return{ledger:p.map(f=>({email:String(f.email),...f.externalUserId?{externalUserId:String(f.externalUserId)}:{},resolvedUserId:f.resolvedUserId===null?null:Number(f.resolvedUserId)})),projectedUserIds:u.map(f=>Number(f.userId))}},resolveMembers(e){return d.memberResolver.resolve(e)},async applyPlan(e,p,u){Me(e.tenantId,"applyPlan(link.tenantId)"),await s(),(p.ledgerUpserts.length>0||p.projectionAdds.length>0)&&await h(async D=>{let G=p.ledgerUpserts.map(g=>({...g,email:ae(g.email)})),o=new Map;for(let g of Ce(G.map(I=>I.email))){let I=await D.select({id:y.id,email:y.email}).from(y).where(L(R(y.groupId,e.groupId),fe(y.email,g)));for(let A of I)o.set(String(A.email),Number(A.id))}let c=G.filter(g=>!o.has(g.email));for(let g of Pe(c,dr))await D.insert(y).output().values(g.map(I=>({groupId:e.groupId,email:I.email,externalUserId:I.externalUserId??null,resolvedUserId:I.resolvedUserId,observedAt:u,createdAt:u,createdBy:n})));for(let g of G){let I=o.get(g.email);I!==void 0&&await D.update(y).set({externalUserId:g.externalUserId??null,resolvedUserId:g.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:n}).output().where(R(y.id,I))}for(let g of Pe(p.projectionAdds,lr)){let I=new Set((await D.select({userId:t.userId}).from(t).where(L(R(t.groupId,e.groupId),fe(t.userId,g)))).map(k=>Number(k.userId))),A=g.filter(k=>!I.has(k));A.length>0&&await D.insert(t).output().values(A.map(k=>({groupId:e.groupId,userId:k,createdAt:u,createdBy:n})))}});let f=[],S=[];for(let D of p.projectionRemoves)try{await d.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:D}},"remove-group-member",G=>G.delete(t).where(L(R(t.groupId,e.groupId),R(t.userId,D))))}catch(G){cr(G)?f.push({userId:D,applied:!1,refusal:G.message}):S.push({userId:D,cause:ne(G)})}let T=f.map(D=>D.userId);return p.ledgerDeletes.length>0&&await h(async D=>{for(let G of Ce(p.ledgerDeletes)){let o=T.length>0?ur(sr(y.resolvedUserId),ar(y.resolvedUserId,T)):void 0;await D.delete(y).where(L(R(y.groupId,e.groupId),fe(y.email,G),...o?[o]:[]))}}),{refusals:f,failures:S}},async resolveExternalIdentity(e,p){await s(),!(e.externalId===p.externalId&&e.externalKey===ae(p.externalKey))&&await l.update(b).set({externalId:p.externalId,externalKey:ae(p.externalKey)}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId)))},async recordSuccess(e,p,u){await s(),await h(async f=>{await f.update(b).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:p.externalId,externalKey:ae(p.externalKey),updatedAt:u,updatedBy:n}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId))),e.isGroupActive||await f.update(i).set({isActive:!0,updatedAt:u,updatedBy:n}).output().where(L(R(i.id,e.groupId),R(i.tenantId,e.tenantId)))})},async recordFailure(e,p){await s(),await l.update(b).set({syncError:p.slice(0,_e),updatedAt:w(),updatedBy:n}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId)))},async recordVanishedObservation(e,p){await s();let u=e.vanishedObservations+1;return await l.update(b).set({vanishedObservations:u,syncError:p.slice(0,_e),updatedAt:w(),updatedBy:n}).output().where(L(R(b.id,e.linkId),R(b.tenantId,e.tenantId))),u},async markGroupInactive(e){await s(),await l.update(i).set({isActive:!1,updatedAt:w(),updatedBy:n}).output().where(L(R(i.id,e.groupId),R(i.tenantId,e.tenantId),R(i.isActive,!0)))}}}export{ye as AUTH_DIRECTORY_PROVIDER_COLUMN,Xr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,Se as MEMBERSHIP_MODES,he as NEWLY_UNHEALTHY_SYNC_OUTCOMES,or as STRANDED_REASON,Ge as UNHEALTHY_SYNC_OUTCOMES,Dr as assertGroupMayBeLinked,J as assertMembershipMode,ne as classifySyncFailure,wr as createDirectoryCatalogReader,fr as createDirectoryCatalogWriter,Or as createDirectoryLinkStore,st as createDirectorySync,St as createDirectorySyncRepository,Yr as createPromotionRepository,pt as createSyncRequestQueue,Sr as defaultExternalGroupGuardMessage,Re as defaultSyncMessages,et as defineAuthDirectorySchema,ee as findLedgerRowsForIdentity,Ur as promoteLedgerForUser,$ as transactionRunner,Tr as withExternalGroupGuards};
|
|
1
|
+
import{assertTenantId as Se}from"@aiquants/authz-core";import{chunkByParameters as Ne,toInstant as $e}from"@aiquants/mssql";import{and as Q,asc as qe,eq as q}from"drizzle-orm";import{runWithTransactionRetry as Oe}from"@aiquants/mssql";function $(e,i){return p=>Oe(d=>e.transaction(l=>d(()=>p(l))),i.transactionRetry)}var Ue=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},ve=2147483647;function Be(e){if(e===void 0||!Number.isFinite(e))return null;let i=Math.trunc(e);return i<0?null:i>ve?ve:i}function W(e,i,p){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${p} must not be blank`);if(d.length>i)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${p} exceeds its column length (${i}); truncating it would change which rows it owns`);return d}function wr(e,i,p){let d=e,l=p.ensureConnection??(async()=>{}),s=$(d,p),h=i.TDDirectoryCatalog,I=i.TMDirectoryCatalogObservation,b=y=>{let a=new Map;for(let t of y)a.set(t.externalId,t);return[...a.values()]};return{async replace(y,a,t){await l();let n=W(Se(y,"replace(tenantId)"),N.tenantId,"tenantId"),r=W(a,N.provider,"provider"),m=W(p.actor,N.actor,"actor"),u=p.now(),f=b(t.map(S=>({tenantId:n,provider:r,externalId:S.externalId.slice(0,N.externalId),externalKey:S.externalKey.slice(0,N.externalKey),name:S.name.slice(0,N.name),description:S.description===void 0?null:S.description.slice(0,N.description),memberCount:Be(S.memberCount),observedAt:u,createdAt:u,createdBy:m})));return await s(async S=>{await S.delete(h).where(Q(q(h.tenantId,n),q(h.provider,r)));for(let D of Ne(f,Ue))await S.insert(h).values(D);await S.delete(I).where(Q(q(I.tenantId,n),q(I.provider,r))),await S.insert(I).values([{tenantId:n,provider:r,observedAt:u,entryCount:f.length,createdAt:u,createdBy:m,updatedAt:u,updatedBy:m}])}),f.length}}}function Ir(e,i,p={}){let d=e,l=p.ensureConnection??(async()=>{}),s=i.TDDirectoryCatalog,h=i.TMDirectoryCatalogObservation;return{async read(I,b){await l();let y=W(Se(I,"read(tenantId)"),N.tenantId,"tenantId"),a=W(b,N.provider,"provider"),t=await d.select({observedAt:h.observedAt,entryCount:h.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(h).leftJoin(s,Q(q(s.tenantId,h.tenantId),q(s.provider,h.provider))).where(Q(q(h.tenantId,y),q(h.provider,a))).orderBy(qe(s.name)),n=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),r=t[0];if(r===void 0)return{groups:n,observedAt:null};if(Number(r.entryCount)!==n.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${y}, ${a}): the observation records ${r.entryCount} entries but ${n.length} rows are present`);let m=$e(r.observedAt);if(!Number.isFinite(m.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${y}, ${a}) has an unreadable observed_at`);return{groups:n,observedAt:m}}}}import{AUTHZ_ACTIONS as ze}from"@aiquants/authz-core";var K=class extends Error{constructor(p){super(p);this.operatorFacing=!0;this.name="DirectoryOperatorError"}};var Ar=(e,i)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${i}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${e}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function F(e,i,p,d){return new Set(e.filter(l=>i.has(l.roleId)&&l.appKey===p&&l.resourceKey===d).map(l=>l.action))}function Z(e,i){return new Set(e.filter(p=>p.groupId===i).map(p=>p.roleId))}var H=e=>ze.every(i=>e.has(i)),ee=(e,i)=>e.find(p=>p.roleId===i)?.roleKey??`#${i}`;function X(e,i,p,d){let l=[...i].find(h=>H(F(e,new Set([h]),p,d)));return l!==void 0?ee(e,l):[...i].filter(h=>F(e,new Set([h]),p,d).size>0).map(h=>ee(e,h)).join(" + ")}function xr(e,i){return De(e,i)}function De(e,i){let p=e;return{...e,withTransaction:(d,l)=>p.withTransaction((s,h)=>d(De(s,i),h),l),async assignGroupRole(d,l,s,h){await p.withTransaction(async(I,b)=>{let y=await I.listGrants(d),a=Z(await I.listGroupRoles(d),l);if(a.add(s),H(F(y,a,i.adminAppKey,i.adminResourceKey))){let t=await i.linkLookup.externalGroupKey(d,l,b);if(t!==null)throw new K(i.message(X(y,a,i.adminAppKey,i.adminResourceKey),t))}await I.assignGroupRole(d,l,s,h)})},async updateGrant(d,l,s,h){let I=s.action;if(I===void 0)return p.updateGrant(d,l,s,h);await p.withTransaction(async(b,y)=>{let a=await b.listGrants(d),t=a.find(m=>m.id===l);if(t===void 0)return b.updateGrant(d,l,s,h);let r=(await b.listResources(d)).find(m=>m.id===t.resourceId);if(r?.appKey===i.adminAppKey&&r?.resourceKey===i.adminResourceKey){let m=await b.listGroupRoles(d);for(let u of new Set(m.filter(f=>f.roleId===t.roleId).map(f=>f.groupId))){let f=Z(m,u),S=F(a.filter(T=>T.id!==l),f,i.adminAppKey,i.adminResourceKey);if(f.has(t.roleId)&&S.add(I),!H(S))continue;let D=await i.linkLookup.externalGroupKey(d,u,y);if(D!==null)throw new K(i.message(X(a,f,i.adminAppKey,i.adminResourceKey)||ee(a,t.roleId),D))}}await b.updateGrant(d,l,s,h)})},async createGrant(d,l,s){await p.withTransaction(async(h,I)=>{let y=(await h.listResources(d)).find(a=>a.id===l.resourceId);if(y?.appKey===i.adminAppKey&&y?.resourceKey===i.adminResourceKey){let a=await h.listGrants(d),t=await h.listGroupRoles(d);for(let n of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=F(a,Z(t,n),i.adminAppKey,i.adminResourceKey);if(r.add(l.action),!H(r))continue;let m=await i.linkLookup.externalGroupKey(d,n,I);if(m!==null)throw new K(i.message(X(a,Z(t,n),i.adminAppKey,i.adminResourceKey)||ee(a,l.roleId),m))}}await h.createGrant(d,l,s)})}}}async function kr(e,i,p,d){let l=(await e.listGroupRoles(i)).filter(b=>b.groupId===p);if(l.length===0)return;let s=await e.listGrants(i),h=new Set(l.map(b=>b.roleId));if(!H(F(s,h,d.adminAppKey,d.adminResourceKey)))return;let I=l[0];if(I)throw new K(d.message(X(s,h,d.adminAppKey,d.adminResourceKey),I.groupKey))}import{and as Fe,eq as J}from"drizzle-orm";async function re(e,i,p){let{ledger:d,group:l}=i,{tenantId:s,externalUserId:h,email:I}=p;if(h===null&&I==="")return[];let b={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},y=async m=>(await e.select(b).from(d).innerJoin(l,Fe(J(l.id,d.groupId),J(l.tenantId,s),J(l.isActive,!0))).where(m)).map(f=>({ledgerId:Number(f.ledgerId),groupId:Number(f.groupId),tenantId:String(f.tenantId),externalUserId:f.externalUserId===null?null:String(f.externalUserId)})),a=m=>{let u=new Map;for(let f of m)u.set(f.ledgerId,{ledgerId:f.ledgerId,groupId:f.groupId,tenantId:f.tenantId});return[...u.values()].sort((f,S)=>f.ledgerId-S.ledgerId)},t=h===null?[]:await y(J(d.externalUserId,h));if(I==="")return a(t);let n=await y(J(d.email,I)),r=m=>m.externalUserId!==null&&m.externalUserId!==h;return a([...t,...n.filter(m=>!r(m))])}import{normalizeAuthEmail as de}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as je}from"@aiquants/auth-directory-core";import{assertTenantId as P,assertTenantMatch as We}from"@aiquants/authz-core";import{toInstant as Ae}from"@aiquants/mssql";import{and as E,asc as xe,count as le,eq as v,isNull as te}from"drizzle-orm";var Te=["direct","transitive"];function Y(e){if(!Te.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${Te.join(" | ")}, received ${JSON.stringify(e)}`);return e}var Ur={breakGlassLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u7DCA\u6025\u7528 (break-glass) \u306E\u30C6\u30CA\u30F3\u30C8\u5E30\u5C5E\u306E\u7D4C\u8DEF\u306B\u6307\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3068\u9023\u643A\u3067\u304D\u307E\u305B\u3093\u3002\u3053\u306E\u7D4C\u8DEF\u306F\u3001\u540C\u671F\u304C\u89E6\u308C\u306A\u3044\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u3063\u3066\u3044\u307E\u3059\u3002`,allowlistNeedsLink:e=>`\u30B0\u30EB\u30FC\u30D7 (${e}) \u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u305F\u3081\u3001\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306B\u4F7F\u3048\u307E\u305B\u3093\u3002\u30B0\u30EB\u30FC\u30D7\u5358\u4F4D\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u8A31\u53EF\u306F\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u30E1\u30F3\u30D0\u30FC\u53F0\u5E33\u3067\u5224\u5B9A\u3059\u308B\u306E\u3067\u3001\u4E0A\u6D41\u3068\u9023\u643A\u3057\u305F\u30B0\u30EB\u30FC\u30D7\u3060\u3051\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002`};function He(e){let i=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return i===2601||i===2627}function Br(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.policyCacheTtlMs??0;if(!Number.isFinite(I)||I<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let b=new Map,y=o=>b.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:n,TDAllowlistGroup:r,TDDirectorySyncRequest:m}=i,{TMGroup:u,TDGroupMember:f}=p;async function S(o,c,g,w){if((await o.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,c),v(n.groupId,g),v(n.isBreakGlass,!0)))).length>0)throw new K(d.refusals.breakGlassLink(w))}async function D(o,c,g){let[w]=await o.select({groupKey:u.groupKey}).from(u).where(E(v(u.id,g),v(u.tenantId,c)));if(!w)throw new Error(`Group not found in tenant: ${g}`);return String(w.groupKey)}function T(o,c){return{linkId:Number(o.linkId),groupId:Number(o.groupId),tenantId:String(o.tenantId),provider:String(o.provider),externalId:String(o.externalId),externalKey:String(o.externalKey),membershipMode:Y(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:Ae(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:c}}async function G(o,c){let g=await o.select({groupId:t.groupId,n:le()}).from(t).innerJoin(u,E(v(u.id,t.groupId),v(u.tenantId,c))).where(te(t.resolvedUserId)).groupBy(t.groupId);return new Map(g.map(w=>[Number(w.groupId),Number(w.n)]))}return{async listLinks(o){P(o,"listLinks(tenantId)"),await s();let c=await l.select({linkId:a.id,groupId:a.groupId,tenantId:a.tenantId,provider:a.provider,externalId:a.externalId,externalKey:a.externalKey,membershipMode:a.membershipMode,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where(v(a.tenantId,o)).orderBy(xe(a.groupId)),g=await G(l,o);return c.map(w=>(We(o,String(w.tenantId),"listLinks(row.tenantId)"),T(w,g.get(Number(w.groupId))??0)))},async externalGroupKey(o,c,g){P(o,"externalGroupKey(tenantId)"),await s();let[w]=await(g??l).select({externalKey:a.externalKey}).from(a).where(E(v(a.groupId,c),v(a.tenantId,o)));return w?String(w.externalKey):null},async linkGroup(o,c,g,w){P(o,"linkGroup(tenantId)");let A=Y(g.membershipMode),k=g.externalId.trim(),M=de(g.externalKey);if(k===""||M==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,c,async V=>{let C=V,ue=await D(C,o,c);await S(C,o,c,ue),await C.insert(a).output().values({groupId:c,tenantId:o,provider:g.provider,externalId:k,externalKey:M,membershipMode:A,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:w})}),y(o)},async unlinkGroup(o,c){P(o,"unlinkGroup(tenantId)"),await s(),await h(async g=>{await D(g,o,c),await g.delete(t).where(v(t.groupId,c)),await g.delete(r).where(E(v(r.groupId,c),v(r.tenantId,o))),await g.delete(a).where(E(v(a.groupId,c),v(a.tenantId,o)))}),y(o)},async setSyncEnabled(o,c,g,w){P(o,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(a.groupId,c),v(a.tenantId,o)))},async requestSync(o,c,g){return P(o,"requestSync(tenantId)"),await s(),l.transaction(async w=>{if(c!==null&&await D(w,o,c),(await w.select({id:m.id}).from(m).where(E(v(m.tenantId,o),c===null?te(m.groupId):v(m.groupId,c),te(m.pickedAt)))).length>0)return"already-pending";try{await w.insert(m).output().values({tenantId:o,groupId:c,requestedBy:g,requestedAt:new Date,createdAt:new Date,createdBy:g})}catch(k){if(!He(k))throw k;return"already-pending"}return"queued"})},async getStatus(o){P(o,"getStatus(tenantId)"),await s();let c=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where(v(a.tenantId,o)),[g]=await l.select({n:le()}).from(m).where(E(v(m.tenantId,o),te(m.finishedAt))),w=c.filter(C=>!!C.isSyncEnabled),A=w.map(C=>C.syncedAt?Ae(C.syncedAt).getTime():null),k=A.length>0&&A.every(C=>C!==null)?Math.min(...A):null,V=c.length>0&&w.length===0||w.length>0&&(k===null||Date.now()-k>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:c.length,...k!==null?{oldestSyncedAt:new Date(k).toISOString()}:{},isStale:V,pausedGroupCount:c.length-w.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:c.filter(C=>!!C.syncError).map(C=>Number(C.groupId)).sort((C,ue)=>C-ue),pendingRequestCount:Number(g?.n??0)}},async loadTenantMembershipPolicy(o){P(o,"loadTenantMembershipPolicy(tenantId)");let c=I>0?b.get(o):void 0;if(c&&Date.now()-c.loadedAt<I)return c.policy;await s();let g=await l.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass,externalLinkId:a.id}).from(n).innerJoin(u,E(v(u.id,n.groupId),v(u.tenantId,o))).leftJoin(a,E(v(a.groupId,n.groupId),v(a.tenantId,o))).where(v(n.tenantId,o));if(g.length===0){let M={kind:"no-proof-required"};return I>0&&b.set(o,{policy:M,loadedAt:Date.now()}),M}let w=g.map(M=>Number(M.groupId)),A=g.filter(M=>!!M.isBreakGlass);for(let M of A)if(M.externalLinkId!==null)throw new Error(`Break-glass group ${Number(M.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let k={kind:"require-proof",proofGroupIds:w,breakGlassGroupIds:A.map(M=>Number(M.groupId))};return je(k,`tenant ${o}`),I>0&&b.set(o,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(o,c,g,w){P(o,"setTenantMembershipGroup(tenantId)"),await s(),await h(async A=>{if(await D(A,o,c),g){let[M]=await A.select({id:a.id}).from(a).where(E(v(a.tenantId,o),v(a.groupId,c)));if(M)throw new Error(`Group ${c} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let k=await A.select({groupId:n.groupId}).from(n).where(E(v(n.tenantId,o),v(n.groupId,c)));if(!g&&!(await A.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(C=>Number(C.groupId)!==c).some(C=>!!C.isBreakGlass))throw new Error(`Declaring group ${c} as ordinary proof would leave tenant ${o} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(k.length>0){await A.update(n).set({isBreakGlass:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(n.tenantId,o),v(n.groupId,c)));return}await A.insert(n).output().values({tenantId:o,groupId:c,isBreakGlass:g,createdAt:new Date,createdBy:w})}),y(o)},async removeTenantMembershipGroup(o,c){P(o,"removeTenantMembershipGroup(tenantId)"),await s(),await h(async g=>{let A=(await g.select({groupId:n.groupId,isBreakGlass:n.isBreakGlass}).from(n).where(v(n.tenantId,o))).filter(k=>Number(k.groupId)!==c);if(A.length>0&&!A.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${c} would leave tenant ${o} with a membership proof but no break-glass path.`);await g.delete(n).where(E(v(n.tenantId,o),v(n.groupId,c)))}),y(o)},async setGroupActive(o,c,g,w){P(o,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:g,updatedAt:new Date,updatedBy:w}).output().where(E(v(u.id,c),v(u.tenantId,o)))},async listLedgerGroupIds(o,c){if(P(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let g=await re(l,{ledger:t,group:u},{tenantId:o,externalUserId:c.externalUserId?.trim()||null,email:de(c.email)});return[...new Set(g.map(w=>w.groupId))].sort((w,A)=>w-A)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let c=de(o);if(c==="")return!1;await s();let[g]=await l.select({id:t.id}).from(t).innerJoin(r,v(r.groupId,t.groupId)).innerJoin(u,E(v(u.id,t.groupId),v(u.isActive,!0))).where(v(t.email,c));return!!g},async listAllowlistGroups(o){return P(o,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:r.id,groupId:r.groupId,groupKey:u.groupKey,groupName:u.name,description:r.description}).from(r).innerJoin(u,E(v(u.id,r.groupId),v(u.tenantId,o))).where(v(r.tenantId,o)).orderBy(xe(u.name))).map(g=>({id:Number(g.id),type:"group",groupId:Number(g.groupId),groupKey:String(g.groupKey),groupName:String(g.groupName),...g.description?{description:String(g.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,c,g,w){P(o,"addAllowlistGroup(tenantId)"),await s(),await h(async A=>{let k=await D(A,o,c),[M]=await A.select({id:a.id}).from(a).where(E(v(a.groupId,c),v(a.tenantId,o)));if(!M)throw new K(d.refusals.allowlistNeedsLink(k));await A.insert(r).output().values({tenantId:o,groupId:c,description:g??null,createdAt:new Date,createdBy:w})})},async removeAllowlistGroup(o,c){P(o,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where(E(v(r.id,c),v(r.tenantId,o)))},async countProjectedMembers(o,c){P(o,"countProjectedMembers(tenantId)"),await s();let[g]=await l.select({n:le()}).from(f).innerJoin(u,E(v(u.id,f.groupId),v(u.tenantId,o))).where(v(f.groupId,c));return Number(g?.n??0)}}}import{normalizeAuthEmail as Je}from"@aiquants/auth-core";import{assertTenantId as Ye,assertTenantMatch as Ve}from"@aiquants/authz-core";async function Wr(e,i){let p=Ye(i.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(i.userId)||i.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(i.userId)}`);let d=Je(i.email),l=i.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:p,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let y of s)Ve(p,y.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let h=[...new Set(s.map(y=>y.ledgerId))].sort((y,a)=>y-a),I=[...new Set(s.map(y=>y.groupId))].sort((y,a)=>y-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:p,userId:i.userId,ledgerIds:h,groupIds:I})].sort((y,a)=>y-a),matchedLedgerIds:h}}import{assertTenantId as ke}from"@aiquants/authz-core";import{chunk as ce,chunkByParameters as Qe}from"@aiquants/mssql";import{inJsonList as Ze}from"@aiquants/mssql/drizzle";import{and as pe,eq as me,inArray as ye}from"drizzle-orm";var Xe=4;function et(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.now??(()=>new Date),b=i.TDGroupMemberEmail,{TMGroup:y,TDGroupMember:a}=p;return{async findLedgerMatches(t){return ke(t.tenantId,"findLedgerMatches(tenantId)"),await s(),re(l,{ledger:b,group:y},t)},async attachUserToGroups({tenantId:t,userId:n,ledgerIds:r,groupIds:m}){return ke(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:h(async u=>{let f=new Set;for(let G of ce(m)){let o=await u.select({id:y.id}).from(y).where(pe(ye(y.id,G),me(y.tenantId,t),me(y.isActive,!0)));for(let c of o)f.add(Number(c.id))}let S=m.filter(G=>f.has(G));if(S.length===0)return[];let D=new Set;for(let G of ce(S)){let o=await u.select({groupId:a.groupId}).from(a).where(pe(me(a.userId,n),ye(a.groupId,G)));for(let c of o)D.add(Number(c.groupId))}let T=S.filter(G=>!D.has(G));for(let G of ce(r))await u.update(b).set({resolvedUserId:n}).output().where(pe(ye(b.id,G),Ze(b.groupId,S)));if(T.length>0){let G=I();for(let o of Qe(T,Xe))await u.insert(a).output().values(o.map(c=>({groupId:c,userId:n,createdAt:G,createdBy:d.actor})))}return T})}}}import{AUTHZ_TENANT_COLUMN as B}from"@aiquants/authz-drizzle";import{bigint as _,bit as Re,datetime2 as O,foreignKey as ne,int as ge,mssqlSchema as er,nvarchar as x,primaryKey as U,uniqueIndex as z}from"drizzle-orm/mssql-core";var he="provider",ot=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function it(e,i){let p=er(e),d=()=>({createdAt:O("created_at"),createdBy:x("created_by",{length:50}),updatedAt:O("updated_at"),updatedBy:x("updated_by",{length:50})}),l=()=>({createdAt:O("created_at"),createdBy:x("created_by",{length:50})}),s=p.table("TDGroupExternalLink",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),membershipMode:x("membership_mode",{length:16}).notNull(),isSyncEnabled:Re("is_sync_enabled").notNull(),syncedAt:O("synced_at"),syncError:x("sync_error",{length:1e3}),vanishedObservations:ge("vanished_observations").notNull(),...d()},r=>[U({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),z(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),z(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),ne({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),h=p.table("TDGroupMemberEmail",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),email:x("email",{length:320}).notNull(),externalUserId:x("external_user_id",{length:128}),resolvedUserId:_("resolved_user_id",{mode:"number"}),observedAt:O("observed_at").notNull(),...d()},r=>[U({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),z(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),ne({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),I=p.table("TDTenantMembershipGroup",{tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),isBreakGlass:Re("is_break_glass").notNull(),...l()},r=>[U({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),ne({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),b=p.table("TDAllowlistGroup",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),description:x("description",{length:512}),...l()},r=>[U({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),z(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),ne({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[i.groupTable.id]})]),y=p.table("TDDirectorySyncRequest",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}),requestedBy:x("requested_by",{length:320}).notNull(),requestedAt:O("requested_at").notNull(),pickedAt:O("picked_at"),finishedAt:O("finished_at"),result:x("result",{length:32}),error:x("error",{length:2e3}),...l()},r=>[U({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=p.table("TMAllowlist",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),pattern:x("pattern",{length:256}).notNull(),type:x("type",{length:32}).notNull(),description:x("description",{length:512}),...l()},r=>[U({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),z(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=p.table("TDDirectoryCatalog",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{length:32}).notNull(),externalId:x("external_id",{length:256}).notNull(),externalKey:x("external_key",{length:320}).notNull(),name:x("name",{length:256}).notNull(),description:x("description",{length:1e3}),memberCount:ge("member_count"),observedAt:O("observed_at").notNull(),...l()},r=>[U({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),z(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),n=p.table("TMDirectoryCatalogObservation",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:x(B,{length:64}).notNull(),provider:x(he,{length:32}).notNull(),observedAt:O("observed_at").notNull(),entryCount:ge("entry_count").notNull(),...d()},r=>[U({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),z(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:h,TDTenantMembershipGroup:I,TDAllowlistGroup:b,TDDirectorySyncRequest:y,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:n,TMAllowlist:a}}import{assertBlastRadiusPolicy as rr,reconcile as tr}from"@aiquants/auth-directory-core";function oe(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let i=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof i!="number"?"unknown":i===401||i===403?"denied":i===429||i===408?"rateLimited":i>=500?"upstreamError":"unknown"}var Ge=e=>`${e.length} \u4EF6`,be={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},Ee={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${be[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${be[e]})\u3002`,resolverIncomplete:(e,i)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${i} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(e,i,p)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${i} \u56DE\u76EE\u3067\u3059\u3002${p} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,i)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${i} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${e})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:e=>{if(e.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C ${Ge(e.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let i=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",p=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${i}\u304C ${e.population} \u4EBA\u4E2D ${e.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${e.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${e.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${Ge(p)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalWritesFailed:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(e.map(i=>be[i.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${e.map(i=>i.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,i)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${i} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var fe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Me=[...fe,"already-vanished"];function pt(e){let{directory:i,repository:p,policy:d,now:l,logger:s}=e,h=e.messages??Ee;if(rr(d),!Number.isInteger(e.vanishedThreshold)||e.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(e.vanishedThreshold)}`);if(typeof e.provider!="string"||e.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function I(t,n){try{await p.recordFailure(t,n)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function b(t){try{let n=await i.getGroup(t.externalId);return n===null?{kind:"missing"}:{kind:"ok",group:n,members:await i.listGroupMembers(n,t.membershipMode)}}catch(n){return{kind:"failed",error:n instanceof Error?n:new Error(String(n))}}}async function y(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let n=await p.recordVanishedObservation(t,h.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return n<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${n}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:n}):(await p.markGroupInactive(t),await I(t,h.groupVanished(t.externalKey,n)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${n} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await I(t,h.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let n=await b(t);if(n.kind==="missing")return y(t);if(n.kind==="failed")return await I(t,h.upstreamUnreadable(oe(n.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,n.error),{kind:"upstream-failed",link:t,error:n.error};try{await p.resolveExternalIdentity(t,n.group);let[r,m]=await Promise.all([p.loadGroupSnapshot(t),p.resolveMembers(n.members)]);if(m.length!==n.members.length){let T=h.resolverIncomplete(n.members.length,m.length);return await I(t,T),s.error(`[auth-directory] ${T}`),{kind:"local-failed",link:t,error:new Error(T)}}let u=tr(r,m,d);if(u.aborted){await I(t,h.planRefused(u.aborted));let T=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${T.join(", ")}`),{kind:"aborted",link:t,plan:u}}let f=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,S=l(),D={refusals:[],failures:[]};return f&&(D=await p.applyPlan(t,u,S)),await p.recordSuccess(t,n.group,S),D.refusals.length>0&&(await I(t,h.removalsRefused(D.refusals)),s.warn(`[auth-directory] ${D.refusals.length} removal(s) refused for ${t.externalKey}: users ${D.refusals.map(T=>T.userId).join(", ")}`)),D.failures.length>0&&(await I(t,h.removalWritesFailed(D.failures)),s.warn(`[auth-directory] ${D.failures.length} removal(s) could not be written for ${t.externalKey}: users ${D.failures.map(T=>T.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),D.refusals.length>0||D.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:D.refusals,failures:D.failures}:f?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let m=r instanceof Error?r:new Error(String(r));return await I(t,h.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,m),{kind:"local-failed",link:t,error:m}}}return{syncOne:a,async run(t){let n=await p.listSyncableLinks(t),r=[];for(let f of n)try{r.push(await a(f))}catch(S){let D=S instanceof Error?S:new Error(String(S));s.error(`[auth-directory] ${f.externalKey} ended abnormally:`,D),r.push({kind:"local-failed",link:f,error:D})}let m=r.filter(f=>Me.includes(f.kind)).length,u=r.filter(f=>fe.includes(f.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${m} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:m,newlyUnhealthyCount:u}}}}import{assertTenantId as nr}from"@aiquants/authz-core";import{toInstant as or}from"@aiquants/mssql";import{and as ie,asc as Ce,eq as j,isNull as ae}from"drizzle-orm";var ir="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",we=2e3;function bt(e,i,p){let d=e,l=p.ensureConnection??(async()=>{}),s=i.TDDirectorySyncRequest,h=nr(p.tenantId,"createSyncRequestQueue(tenantId)"),I=p.maxErrorLength??we;if(!Number.isInteger(I)||I<1||I>we)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${we}, received ${String(p.maxErrorLength)}`);let b={async claim(){await l();let y=await d.select({id:s.id}).from(s).where(ie(j(s.tenantId,h),ae(s.pickedAt))).orderBy(Ce(s.id));for(let a of y){let n=(await d.update(s).set({pickedAt:p.now()}).output().where(ie(j(s.tenantId,h),j(s.id,Number(a.id)),ae(s.pickedAt))))?.[0];if(!n)continue;if(n.requestedBy===void 0||n.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(n.id)}`);let r=n.groupId??null;return{id:Number(n.id),tenantId:h,groupId:r===null?null:Number(r),requestedBy:String(n.requestedBy)}}return null},async finish(y,a){await l();let t=a.error?a.error.slice(0,I):null;return!!(await d.update(s).set({finishedAt:p.now(),result:a.result,error:t}).output().where(ie(j(s.tenantId,h),j(s.id,y),ae(s.finishedAt))))?.length},async listStranded(y){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where(ie(j(s.tenantId,h),ae(s.finishedAt))).orderBy(Ce(s.id)),t=n=>{let r=or(n);return Number.isNaN(r.getTime())||r<y};return{stranded:a.filter(n=>n.pickedAt!==null&&t(n.pickedAt)).map(n=>Number(n.id)),unclaimed:a.filter(n=>n.pickedAt===null&&t(n.requestedAt)).map(n=>Number(n.id))}},async abandonStranded(y){let{stranded:a}=await b.listStranded(y),t=[];for(let n of a)try{await b.finish(n,{result:"failed",error:ir})&&t.push(n)}catch(r){p.onCloseFailed?.(n,r)}return t}};return b}import{normalizeAuthEmail as se}from"@aiquants/auth-core";import{assertTenantId as Pe,assertTenantMatch as ar}from"@aiquants/authz-core";import{chunk as _e,chunkByParameters as Le}from"@aiquants/mssql";import{notInJsonList as sr}from"@aiquants/mssql/drizzle";import{and as L,eq as R,inArray as Ie,isNull as ur,or as dr}from"drizzle-orm";var lr=7,cr=4,Ke=1e3;function pr(e){if(!(e instanceof Error))return!1;let i=e;return i.number===void 0&&i.code===void 0&&e.name==="Error"}function kt(e,i,p,d){let l=e,s=d.ensureConnection??(async()=>{}),h=$(l,d),I=d.now??(()=>new Date),{TDGroupExternalLink:b,TDGroupMemberEmail:y}=i,{TMGroup:a,TDGroupMember:t}=p,n=d.actor;return{async listSyncableLinks(r){await s();let m=Pe(r.tenantId,"listSyncableLinks(tenantId)"),u=[R(b.isSyncEnabled,!0),R(b.tenantId,m)];return r.groupId!==void 0&&u.push(R(b.groupId,r.groupId)),(await l.select({linkId:b.id,groupId:b.groupId,linkTenantId:b.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:b.provider,externalId:b.externalId,externalKey:b.externalKey,membershipMode:b.membershipMode,vanishedObservations:b.vanishedObservations}).from(b).innerJoin(a,L(R(a.id,b.groupId),R(a.tenantId,m))).where(L(...u))).map(S=>(ar(String(S.groupTenantId),String(S.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(S.linkId),groupId:Number(S.groupId),groupKey:String(S.groupKey),tenantId:String(S.groupTenantId),provider:String(S.provider),externalId:String(S.externalId),externalKey:String(S.externalKey),membershipMode:Y(String(S.membershipMode)),isGroupActive:!!S.isGroupActive,vanishedObservations:Number(S.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[m,u]=await Promise.all([l.select({email:y.email,externalUserId:y.externalUserId,resolvedUserId:y.resolvedUserId}).from(y).where(R(y.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where(R(t.groupId,r.groupId))]);return{ledger:m.map(f=>({email:String(f.email),...f.externalUserId?{externalUserId:String(f.externalUserId)}:{},resolvedUserId:f.resolvedUserId===null?null:Number(f.resolvedUserId)})),projectedUserIds:u.map(f=>Number(f.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,m,u){Pe(r.tenantId,"applyPlan(link.tenantId)"),await s(),(m.ledgerUpserts.length>0||m.projectionAdds.length>0)&&await h(async T=>{let G=m.ledgerUpserts.map(g=>({...g,email:se(g.email)})),o=new Map;for(let g of _e(G.map(w=>w.email))){let w=await T.select({id:y.id,email:y.email}).from(y).where(L(R(y.groupId,r.groupId),Ie(y.email,g)));for(let A of w)o.set(String(A.email),Number(A.id))}let c=G.filter(g=>!o.has(g.email));for(let g of Le(c,lr))await T.insert(y).output().values(g.map(w=>({groupId:r.groupId,email:w.email,externalUserId:w.externalUserId??null,resolvedUserId:w.resolvedUserId,observedAt:u,createdAt:u,createdBy:n})));for(let g of G){let w=o.get(g.email);w!==void 0&&await T.update(y).set({externalUserId:g.externalUserId??null,resolvedUserId:g.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:n}).output().where(R(y.id,w))}for(let g of Le(m.projectionAdds,cr)){let w=new Set((await T.select({userId:t.userId}).from(t).where(L(R(t.groupId,r.groupId),Ie(t.userId,g)))).map(k=>Number(k.userId))),A=g.filter(k=>!w.has(k));A.length>0&&await T.insert(t).output().values(A.map(k=>({groupId:r.groupId,userId:k,createdAt:u,createdBy:n})))}});let f=[],S=[];for(let T of m.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:T}},"remove-group-member",G=>G.delete(t).where(L(R(t.groupId,r.groupId),R(t.userId,T))))}catch(G){pr(G)?f.push({userId:T,applied:!1,refusal:G.message}):S.push({userId:T,cause:oe(G)})}let D=f.map(T=>T.userId);return m.ledgerDeletes.length>0&&await h(async T=>{for(let G of _e(m.ledgerDeletes)){let o=D.length>0?dr(ur(y.resolvedUserId),sr(y.resolvedUserId,D)):void 0;await T.delete(y).where(L(R(y.groupId,r.groupId),Ie(y.email,G),...o?[o]:[]))}}),{refusals:f,failures:S}},async resolveExternalIdentity(r,m){await s(),!(r.externalId===m.externalId&&r.externalKey===se(m.externalKey))&&await l.update(b).set({externalId:m.externalId,externalKey:se(m.externalKey)}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId)))},async recordSuccess(r,m,u){await s(),await h(async f=>{await f.update(b).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:m.externalId,externalKey:se(m.externalKey),updatedAt:u,updatedBy:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId))),r.isGroupActive||await f.update(a).set({isActive:!0,updatedAt:u,updatedBy:n}).output().where(L(R(a.id,r.groupId),R(a.tenantId,r.tenantId)))})},async recordFailure(r,m){await s(),await l.update(b).set({syncError:m.slice(0,Ke),updatedAt:I(),updatedBy:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId)))},async recordVanishedObservation(r,m){await s();let u=r.vanishedObservations+1;return await l.update(b).set({vanishedObservations:u,syncError:m.slice(0,Ke),updatedAt:I(),updatedBy:n}).output().where(L(R(b.id,r.linkId),R(b.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:I(),updatedBy:n}).output().where(L(R(a.id,r.groupId),R(a.tenantId,r.tenantId),R(a.isActive,!0)))}}}export{he as AUTH_DIRECTORY_PROVIDER_COLUMN,ot as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,K as DirectoryOperatorError,Te as MEMBERSHIP_MODES,fe as NEWLY_UNHEALTHY_SYNC_OUTCOMES,ir as STRANDED_REASON,Me as UNHEALTHY_SYNC_OUTCOMES,kr as assertGroupMayBeLinked,Y as assertMembershipMode,oe as classifySyncFailure,Ir as createDirectoryCatalogReader,wr as createDirectoryCatalogWriter,Br as createDirectoryLinkStore,pt as createDirectorySync,kt as createDirectorySyncRepository,et as createPromotionRepository,bt as createSyncRequestQueue,Ur as defaultDirectoryLinkRefusals,Ar as defaultExternalGroupGuardMessage,Ee as defaultSyncMessages,it as defineAuthDirectorySchema,re as findLedgerRowsForIdentity,Wr as promoteLedgerForUser,$ as transactionRunner,xr as withExternalGroupGuards};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/auth-directory-drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Drizzle (mssql) adapter for @aiquants/auth-directory-core: the identity-side tables (external group links, upstream member ledger, tenant membership groups, login allowlist), the synchronization writer that runs inside the authz lockout guard, and an admin-store decorator that keeps admin roles off externally-sourced groups.",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"dev": "tsup --watch",
|
|
69
69
|
"typecheck": "tsc --noEmit",
|
|
70
70
|
"clean": "rimraf dist",
|
|
71
|
-
"publish:patch": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
72
|
-
"publish:minor": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
73
|
-
"publish:major": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
71
|
+
"publish:patch": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && node ../../.config/scripts/check-peer-floors.mjs --bump patch && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
72
|
+
"publish:minor": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && node ../../.config/scripts/check-peer-floors.mjs --bump minor && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
73
|
+
"publish:major": "pnpm run typecheck && pnpm run --if-present test && pnpm run build && node ../../.config/scripts/check-publish-leaks.mjs && node ../../.config/scripts/check-peer-floors.mjs --bump major && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
|
|
74
74
|
"lint": "biome lint src/",
|
|
75
75
|
"check": "biome check src/",
|
|
76
76
|
"check:fix": "biome check --write src/",
|