@astram/guard 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/auth/account.controller.d.ts +3 -1
  2. package/dist/auth/account.controller.d.ts.map +1 -1
  3. package/dist/auth/account.controller.js +12 -5
  4. package/dist/auth/auth.controller.d.ts +130 -21
  5. package/dist/auth/auth.controller.d.ts.map +1 -1
  6. package/dist/auth/auth.controller.js +537 -143
  7. package/dist/auth/auth.module.d.ts +15 -1
  8. package/dist/auth/auth.module.d.ts.map +1 -1
  9. package/dist/auth/auth.module.js +241 -30
  10. package/dist/auth/connections.d.ts +1 -1
  11. package/dist/auth/connections.d.ts.map +1 -1
  12. package/dist/auth/connections.js +14 -3
  13. package/dist/auth/default-session-facts-projector.d.ts +26 -0
  14. package/dist/auth/default-session-facts-projector.d.ts.map +1 -0
  15. package/dist/auth/default-session-facts-projector.js +21 -0
  16. package/dist/auth/mfa-step-up.d.ts +32 -0
  17. package/dist/auth/mfa-step-up.d.ts.map +1 -0
  18. package/dist/auth/mfa-step-up.js +109 -0
  19. package/dist/auth/pending-mfa-store.d.ts +50 -52
  20. package/dist/auth/pending-mfa-store.d.ts.map +1 -1
  21. package/dist/auth/pending-mfa-store.js +103 -61
  22. package/dist/auth/ports/session-facts-projector.d.ts +5 -0
  23. package/dist/auth/ports/session-facts-projector.d.ts.map +1 -1
  24. package/dist/auth/rate-limit/canonical-hint.d.ts +24 -0
  25. package/dist/auth/rate-limit/canonical-hint.d.ts.map +1 -0
  26. package/dist/auth/rate-limit/canonical-hint.js +63 -0
  27. package/dist/auth/rate-limit/trust-proxy-check.d.ts +46 -0
  28. package/dist/auth/rate-limit/trust-proxy-check.d.ts.map +1 -0
  29. package/dist/auth/rate-limit/trust-proxy-check.js +94 -0
  30. package/dist/auth/tokens.d.ts +70 -11
  31. package/dist/auth/tokens.d.ts.map +1 -1
  32. package/dist/auth/tokens.js +71 -12
  33. package/dist/authentication/log-email-sender.d.ts +9 -4
  34. package/dist/authentication/log-email-sender.d.ts.map +1 -1
  35. package/dist/authentication/mariadb-api-key-store.d.ts +46 -0
  36. package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -0
  37. package/dist/authentication/mariadb-api-key-store.js +111 -0
  38. package/dist/authentication/mariadb-credential-resolver.d.ts +79 -65
  39. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  40. package/dist/authentication/mariadb-credential-resolver.js +116 -73
  41. package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
  42. package/dist/authentication/mariadb-or-totp-verifier.d.ts +21 -3
  43. package/dist/authentication/mariadb-or-totp-verifier.d.ts.map +1 -1
  44. package/dist/authentication/mariadb-or-totp-verifier.js +37 -6
  45. package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
  46. package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
  47. package/dist/authentication/mariadb-session-sweeper.d.ts +14 -1
  48. package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
  49. package/dist/authentication/mariadb-session-sweeper.js +29 -11
  50. package/dist/authentication/noop-email-sender.d.ts +12 -0
  51. package/dist/authentication/noop-email-sender.d.ts.map +1 -0
  52. package/dist/authentication/noop-email-sender.js +9 -0
  53. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
  54. package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -1
  55. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
  56. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +3 -1
  57. package/dist/authz/mariadb-policy-store.js +1 -1
  58. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
  59. package/dist/authz/mariadb-record-rule-adapter.js +1 -4
  60. package/dist/authz/permission-version-cache.d.ts +38 -0
  61. package/dist/authz/permission-version-cache.d.ts.map +1 -0
  62. package/dist/authz/permission-version-cache.js +81 -0
  63. package/dist/authz/wire-record-authorization.d.ts +25 -1
  64. package/dist/authz/wire-record-authorization.d.ts.map +1 -1
  65. package/dist/authz/wire-record-authorization.js +55 -2
  66. package/dist/completeness/catalog-reconciler.d.ts.map +1 -1
  67. package/dist/config/app-config.d.ts +52 -0
  68. package/dist/config/app-config.d.ts.map +1 -1
  69. package/dist/config/app-config.js +66 -4
  70. package/dist/encryption/local-kek-crypto.d.ts +15 -0
  71. package/dist/encryption/local-kek-crypto.d.ts.map +1 -0
  72. package/dist/encryption/local-kek-crypto.js +79 -0
  73. package/dist/enforcement/enforcement.module.d.ts.map +1 -1
  74. package/dist/enforcement/enforcement.module.js +9 -0
  75. package/dist/enforcement/engine-error.filter.d.ts +57 -0
  76. package/dist/enforcement/engine-error.filter.d.ts.map +1 -0
  77. package/dist/enforcement/engine-error.filter.js +118 -0
  78. package/dist/enforcement/global-enforcement.guard.d.ts +31 -2
  79. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
  80. package/dist/enforcement/global-enforcement.guard.js +148 -6
  81. package/dist/identity/auth-facts.controller.d.ts.map +1 -1
  82. package/dist/identity/identity-actions.d.ts +9 -0
  83. package/dist/identity/identity-actions.d.ts.map +1 -1
  84. package/dist/identity/identity-actions.js +16 -1
  85. package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
  86. package/dist/identity/permission-facts.adapter.js +2 -3
  87. package/dist/index.d.ts +17 -7
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +82 -25
  90. package/dist/module/shield.module.d.ts +9 -2
  91. package/dist/module/shield.module.d.ts.map +1 -1
  92. package/dist/module/shield.module.js +47 -38
  93. package/dist/request-auth/request-principal.d.ts +11 -3
  94. package/dist/request-auth/request-principal.d.ts.map +1 -1
  95. package/dist/request-auth/request-principal.js +58 -2
  96. package/dist/request-auth/session-facts-als.d.ts +16 -1
  97. package/dist/request-auth/session-facts-als.d.ts.map +1 -1
  98. package/dist/request-auth/session-facts-als.js +23 -0
  99. package/dist/request-auth/session-token-binder.d.ts +166 -10
  100. package/dist/request-auth/session-token-binder.d.ts.map +1 -1
  101. package/dist/request-auth/session-token-binder.js +408 -28
  102. package/dist/request-auth/tokens.d.ts +10 -0
  103. package/dist/request-auth/tokens.d.ts.map +1 -1
  104. package/dist/request-auth/tokens.js +11 -1
  105. package/dist/secrets/encrypted-file-secrets.d.ts +16 -0
  106. package/dist/secrets/encrypted-file-secrets.d.ts.map +1 -0
  107. package/dist/secrets/encrypted-file-secrets.js +98 -0
  108. package/dist/session/hot-revoking-session-record-store.d.ts +46 -0
  109. package/dist/session/hot-revoking-session-record-store.d.ts.map +1 -0
  110. package/dist/session/hot-revoking-session-record-store.js +58 -0
  111. package/dist/session/index.d.ts +3 -1
  112. package/dist/session/index.d.ts.map +1 -1
  113. package/dist/session/index.js +8 -1
  114. package/dist/session/mariadb-session-record-store.d.ts +6 -0
  115. package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
  116. package/dist/session/mariadb-session-record-store.js +16 -1
  117. package/dist/session/session-lifetimes.d.ts +30 -0
  118. package/dist/session/session-lifetimes.d.ts.map +1 -0
  119. package/dist/session/session-lifetimes.js +21 -0
  120. package/dist/session/session-restriction-store.d.ts +43 -0
  121. package/dist/session/session-restriction-store.d.ts.map +1 -0
  122. package/dist/session/session-restriction-store.js +35 -0
  123. package/migrations/1710000000017-CreateServiceAccountKey.cjs +41 -0
  124. package/package.json +3 -3
@@ -105,7 +105,13 @@ export interface ShieldModuleOptions {
105
105
  */
106
106
  readonly credentialWriter?: CredentialWriter;
107
107
  readonly resetTokenStore?: ResetTokenStore;
108
- readonly emailSender?: EmailSender;
108
+ /**
109
+ * Password-reset email delivery — see {@link AuthModuleOptions.emailSender}. A real
110
+ * sender, or `'disabled'` (no-op, forgot-password still 204). REQUIRED under
111
+ * `auth: true` unless the raw `NODE_ENV` is `development`/`test` (where the dev
112
+ * `LogEmailSender` is the default); otherwise boot fails (security hardening #6).
113
+ */
114
+ readonly emailSender?: EmailSender | 'disabled';
109
115
  readonly recoveryCodeStore?: RecoveryCodeStore;
110
116
  readonly profileWriter?: ProfileWriter;
111
117
  readonly sessionSweeper?: SessionSweeper;
@@ -221,7 +227,8 @@ export interface ShieldModuleAsyncOptions {
221
227
  */
222
228
  readonly credentialWriter?: CredentialWriter;
223
229
  readonly resetTokenStore?: ResetTokenStore;
224
- readonly emailSender?: EmailSender;
230
+ /** See {@link ShieldModuleOptions.emailSender} (a sender, or `'disabled'`). */
231
+ readonly emailSender?: EmailSender | 'disabled';
225
232
  readonly recoveryCodeStore?: RecoveryCodeStore;
226
233
  readonly profileWriter?: ProfileWriter;
227
234
  readonly sessionSweeper?: SessionSweeper;
@@ -1 +1 @@
1
- {"version":3,"file":"shield.module.d.ts","sourceRoot":"","sources":["../../src/module/shield.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAOnF,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQvE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AACpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAYxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAIrC,YAAY,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAyFvF;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IACnD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,yBAAyB,CAAC;IACzD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,0FAA0F;IAC1F,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IACvD;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CAChD;AAED,yEAAyE;AACzE,MAAM,WAAW,wBAAwB;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,CACnB,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,KACxB,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IACxD,gDAAgD;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,2FAA2F;IAC3F,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IACvD;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAEa,YAAY;IACvB,6DAA6D;IAC7D,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,mBAAwB,GAAG,aAAa;IA6FhE;;;;;;;;;OASG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,wBAAwB,GAAG,aAAa;CAiGtE"}
1
+ {"version":3,"file":"shield.module.d.ts","sourceRoot":"","sources":["../../src/module/shield.module.ts"],"names":[],"mappings":"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"}
@@ -55,6 +55,9 @@ const app_config_js_1 = require("../config/app-config.js");
55
55
  // the enforcement-only path never INSTANTIATES it (no live pool/Redis, no HMAC
56
56
  // gate, no core load).
57
57
  const auth_module_js_1 = require("../auth/auth.module.js");
58
+ // Core-free (type-only core imports): the deferred default projector + the version-cache token.
59
+ const default_session_facts_projector_js_1 = require("../auth/default-session-facts-projector.js");
60
+ const tokens_js_1 = require("../auth/tokens.js");
58
61
  // The `SHIELD_DECISION_AUTHORITY` token now lives in `./shield-tokens.js` so the
59
62
  // enforcement guard can import it without a cycle (guard → token → ShieldModule →
60
63
  // EnforcementModule → guard). Re-exported here to keep the public import path stable.
@@ -87,7 +90,8 @@ let cachedRecordAuthApi;
87
90
  async function loadBuildRecordAuthorization() {
88
91
  if (cachedRecordAuthApi === undefined) {
89
92
  try {
90
- cachedRecordAuthApi = (await Promise.resolve().then(() => __importStar(require('../authz/wire-record-authorization.js'))));
93
+ cachedRecordAuthApi =
94
+ (await Promise.resolve().then(() => __importStar(require('../authz/wire-record-authorization.js'))));
91
95
  }
92
96
  catch {
93
97
  // Built-dist fallback (the CJS dist path — see evalImport). `__dirname` is defined
@@ -97,7 +101,7 @@ async function loadBuildRecordAuthorization() {
97
101
  cachedRecordAuthApi = (await evalImport(base));
98
102
  }
99
103
  }
100
- return cachedRecordAuthApi.buildRecordAuthorization;
104
+ return cachedRecordAuthApi;
101
105
  }
102
106
  /**
103
107
  * Resolve the `SHIELD_DECISION_AUTHORITY` VALUE from the caller's options (the ONE seam,
@@ -113,18 +117,33 @@ async function loadBuildRecordAuthorization() {
113
117
  *
114
118
  * Async because the `recordAuthorization` path lazy-loads the (core-touching) builder.
115
119
  */
116
- async function resolveDecisionAuthority(options) {
120
+ async function resolveDecisionAuthority(options, extras = {}) {
117
121
  if (options.recordAuthorization && options.decisionAuthority) {
118
122
  throw new Error('ShieldModule: `recordAuthorization` and `decisionAuthority` are mutually exclusive — ' +
119
123
  'supply the record-authorization inputs (pool + app ports + registry) so the framework ' +
120
124
  'builds the decision authority, OR a pre-built `decisionAuthority`, but not both.');
121
125
  }
122
126
  if (options.recordAuthorization) {
123
- const build = await loadBuildRecordAuthorization();
124
- return build(options.recordAuthorization);
127
+ const api = await loadBuildRecordAuthorization();
128
+ const versionCache = extras.versionCache ?? undefined;
129
+ // ADR-0035 default projector: cache the SAME facts the authority decides on (version-cache
130
+ // wrapped, so a login projection also warms the Redis version).
131
+ extras.defaultProjector?.attach(versionCache !== undefined
132
+ ? api.withRedisPermissionVersion(options.recordAuthorization.permissionFacts, versionCache)
133
+ : options.recordAuthorization.permissionFacts);
134
+ return api.buildRecordAuthorization(options.recordAuthorization, versionCache !== undefined ? { versionCache } : {});
125
135
  }
126
136
  return options.decisionAuthority ?? core_seam_js_1.stubDecisionAuthority;
127
137
  }
138
+ /**
139
+ * The deferred default projector for a registration (ADR-0035): only when auth is on and the
140
+ * consumer did not supply its own projector. `undefined` otherwise.
141
+ */
142
+ function defaultProjectorFor(auth, explicit) {
143
+ return auth && explicit === undefined ? new default_session_facts_projector_js_1.DeferredSessionFactsProjector() : undefined;
144
+ }
145
+ /** Inject the Redis version cache when AuthModule provides it (`auth: true`); absent otherwise. */
146
+ const OPTIONAL_VERSION_CACHE = { token: tokens_js_1.SHIELD_PERMISSION_VERSION_CACHE, optional: true };
128
147
  /**
129
148
  * `ShieldModule` — the SINGLE registration seam (ADR-0023 §4).
130
149
  *
@@ -153,6 +172,10 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
153
172
  /** Register Shield with static options (the common path). */
154
173
  static forRoot(options = {}) {
155
174
  const config = options.config ?? (0, app_config_js_1.loadAppConfig)();
175
+ // ADR-0035: with auth on and no explicit projector, sessions cache the facts the framework-built
176
+ // authority decides on (attached when the authority is built; inactive until then).
177
+ const defaultProjector = defaultProjectorFor(options.auth, options.sessionFactsProjector);
178
+ const sessionFactsProjector = options.sessionFactsProjector ?? defaultProjector;
156
179
  // Build the opt-in AuthModule ONCE so the SAME DynamicModule instance is both
157
180
  // imported (composes) and re-exported (propagates its exported engine tokens
158
181
  // app-wide through the @Global ShieldModule). Re-exporting the two Symbols
@@ -172,9 +195,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
172
195
  ...(options.credentialResolver
173
196
  ? { credentialResolver: options.credentialResolver }
174
197
  : {}),
175
- ...(options.profileResolver
176
- ? { profileResolver: options.profileResolver }
177
- : {}),
198
+ ...(options.profileResolver ? { profileResolver: options.profileResolver } : {}),
178
199
  // The account/credential-surface seams — same spread-when-present rule
179
200
  // (an explicit `undefined` is not assignable under
180
201
  // exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
@@ -194,15 +215,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
194
215
  ...(options.identityControlPlaneKeys
195
216
  ? { identityControlPlaneKeys: options.identityControlPlaneKeys }
196
217
  : {}),
197
- ...(options.sessionFactsProjector
198
- ? { sessionFactsProjector: options.sessionFactsProjector }
199
- : {}),
218
+ ...(sessionFactsProjector ? { sessionFactsProjector } : {}),
200
219
  ...(options.scopeSubtreeResolver
201
220
  ? { scopeSubtreeResolver: options.scopeSubtreeResolver }
202
221
  : {}),
203
- ...(options.membershipChecker
204
- ? { membershipChecker: options.membershipChecker }
205
- : {}),
222
+ ...(options.membershipChecker ? { membershipChecker: options.membershipChecker } : {}),
206
223
  })
207
224
  : undefined;
208
225
  return {
@@ -221,24 +238,22 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
221
238
  // stub paths resolve synchronously but flow through the same one seam. Both
222
239
  // supplied → throws (mutual exclusion).
223
240
  provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
224
- useFactory: () => resolveDecisionAuthority({
241
+ useFactory: (versionCache) => resolveDecisionAuthority({
225
242
  ...(options.recordAuthorization
226
243
  ? { recordAuthorization: options.recordAuthorization }
227
244
  : {}),
228
245
  ...(options.decisionAuthority
229
246
  ? { decisionAuthority: options.decisionAuthority }
230
247
  : {}),
231
- }),
248
+ }, { versionCache, defaultProjector }),
249
+ // ADR-0035: the Redis version cache AuthModule provides under `auth: true`.
250
+ inject: [OPTIONAL_VERSION_CACHE],
232
251
  },
233
252
  ],
234
253
  // Re-export the composed AuthModule app-wide ONLY when auth is opted in — its
235
254
  // own `exports` (SHIELD_AUTHENTICATOR / SHIELD_SESSION_AUTHORITY) then become
236
255
  // visible to a consumer's controllers/services via the @Global seam.
237
- exports: [
238
- config_module_js_1.APP_CONFIG,
239
- shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
240
- ...(authModule ? [authModule] : []),
241
- ],
256
+ exports: [config_module_js_1.APP_CONFIG, shield_tokens_js_1.SHIELD_DECISION_AUTHORITY, ...(authModule ? [authModule] : [])],
242
257
  };
243
258
  }
244
259
  /**
@@ -263,6 +278,10 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
263
278
  useFactory: (opts) => opts.config ?? (0, app_config_js_1.loadAppConfig)(),
264
279
  inject: [optionsToken],
265
280
  };
281
+ // ADR-0035: the deferred default projector (auth on, no explicit projector), attached when the
282
+ // async-resolved options yield `recordAuthorization`.
283
+ const defaultProjector = defaultProjectorFor(options.auth, options.sessionFactsProjector);
284
+ const sessionFactsProjector = options.sessionFactsProjector ?? defaultProjector;
266
285
  const authorityProvider = {
267
286
  provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
268
287
  // Same one-seam resolution as forRoot, over the async-resolved options: the
@@ -270,11 +289,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
270
289
  // in its useFactory from the injected registrations) has the framework build the
271
290
  // real wired authority; `decisionAuthority` is the legacy passthrough; both →
272
291
  // throws; neither → stub. Async because the record path lazy-loads the builder.
273
- useFactory: (opts) => resolveDecisionAuthority({
292
+ useFactory: (opts, versionCache) => resolveDecisionAuthority({
274
293
  ...(opts.recordAuthorization ? { recordAuthorization: opts.recordAuthorization } : {}),
275
294
  ...(opts.decisionAuthority ? { decisionAuthority: opts.decisionAuthority } : {}),
276
- }),
277
- inject: [optionsToken],
295
+ }, { versionCache, defaultProjector }),
296
+ inject: [optionsToken, OPTIONAL_VERSION_CACHE],
278
297
  };
279
298
  // Same one-instance opt-in AuthModule as forRoot (imported + re-exported).
280
299
  // `credentialResolver` is STATIC on the async options (like `auth`) — a
@@ -289,9 +308,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
289
308
  ...(options.credentialResolver
290
309
  ? { credentialResolver: options.credentialResolver }
291
310
  : {}),
292
- ...(options.profileResolver
293
- ? { profileResolver: options.profileResolver }
294
- : {}),
311
+ ...(options.profileResolver ? { profileResolver: options.profileResolver } : {}),
295
312
  // The account/credential-surface seams — same spread-when-present rule
296
313
  // (an explicit `undefined` is not assignable under
297
314
  // exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
@@ -311,15 +328,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
311
328
  ...(options.identityControlPlaneKeys
312
329
  ? { identityControlPlaneKeys: options.identityControlPlaneKeys }
313
330
  : {}),
314
- ...(options.sessionFactsProjector
315
- ? { sessionFactsProjector: options.sessionFactsProjector }
316
- : {}),
331
+ ...(sessionFactsProjector ? { sessionFactsProjector } : {}),
317
332
  ...(options.scopeSubtreeResolver
318
333
  ? { scopeSubtreeResolver: options.scopeSubtreeResolver }
319
334
  : {}),
320
- ...(options.membershipChecker
321
- ? { membershipChecker: options.membershipChecker }
322
- : {}),
335
+ ...(options.membershipChecker ? { membershipChecker: options.membershipChecker } : {}),
323
336
  })
324
337
  : undefined;
325
338
  return {
@@ -335,11 +348,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
335
348
  ...(options.imports ?? []),
336
349
  ],
337
350
  providers: [asyncOptionsProvider, configProvider, authorityProvider],
338
- exports: [
339
- config_module_js_1.APP_CONFIG,
340
- shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
341
- ...(authModule ? [authModule] : []),
342
- ],
351
+ exports: [config_module_js_1.APP_CONFIG, shield_tokens_js_1.SHIELD_DECISION_AUTHORITY, ...(authModule ? [authModule] : [])],
343
352
  };
344
353
  }
345
354
  };
@@ -12,7 +12,7 @@
12
12
  * captured from the login-time Principal, so a genuinely-root login stays
13
13
  * root; a normal login can never become one on a later request).
14
14
  */
15
- import type { Principal } from '@astram/core';
15
+ import type { Principal, ServiceAuthenticator } from '@astram/core';
16
16
  import type { SessionTokenBinder, BoundPrincipalClaims } from './session-token-binder.js';
17
17
  /** Extract a bearer token from an `Authorization: Bearer <token>` header. */
18
18
  export declare function extractBearerToken(authorizationHeader: unknown): string | null;
@@ -22,11 +22,19 @@ export declare function extractBearerToken(authorizationHeader: unknown): string
22
22
  * a root on the request path.
23
23
  */
24
24
  export declare function principalFromClaims(claims: BoundPrincipalClaims): Principal | null;
25
+ /**
26
+ * Is this bearer token a service-account API key (`ask_…`, ADR-0033) rather than a session
27
+ * access token? A cheap prefix check; the authenticator does the real (constant-time) verify.
28
+ */
29
+ export declare function isApiKey(token: string): boolean;
25
30
  /**
26
31
  * Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
27
32
  * when there is no token, no binding, or the binding is malformed.
33
+ *
34
+ * When `serviceAuth` is provided and the bearer token is an API key (`ask_…`, ADR-0033), the token
35
+ * is authenticated as a service account instead of resolved against the session binder.
28
36
  */
29
- export declare function resolveRequestPrincipal(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<Principal | null>;
37
+ export declare function resolveRequestPrincipal(authorizationHeader: unknown, binder: SessionTokenBinder, serviceAuth?: ServiceAuthenticator): Promise<Principal | null>;
30
38
  /**
31
39
  * Resolve an HTTP request's bearer token to BOTH a `Principal` AND the raw
32
40
  * {@link BoundPrincipalClaims}, or `null` (fail-closed) — the same read path as
@@ -35,7 +43,7 @@ export declare function resolveRequestPrincipal(authorizationHeader: unknown, bi
35
43
  * uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
36
44
  * while still attaching the resolved `Principal` for `decide()`.
37
45
  */
38
- export declare function resolveRequestAuth(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<{
46
+ export declare function resolveRequestAuth(authorizationHeader: unknown, binder: SessionTokenBinder, serviceAuth?: ServiceAuthenticator): Promise<{
39
47
  principal: Principal;
40
48
  claims: BoundPrincipalClaims;
41
49
  } | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"request-principal.d.ts","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAK1F,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAiClF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAM3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAAG,IAAI,CAAC,CAQxE"}
1
+ {"version":3,"file":"request-principal.d.ts","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAa1F,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAiClF;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/C;AA4BD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAU3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAAG,IAAI,CAAC,CAgBxE"}
@@ -16,10 +16,18 @@
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.extractBearerToken = extractBearerToken;
18
18
  exports.principalFromClaims = principalFromClaims;
19
+ exports.isApiKey = isApiKey;
19
20
  exports.resolveRequestPrincipal = resolveRequestPrincipal;
20
21
  exports.resolveRequestAuth = resolveRequestAuth;
21
22
  /** Valid `Principal.kind` values (mirror of the Core union; validated at rebuild). */
22
23
  const PRINCIPAL_KINDS = new Set(['user', 'role_group', 'service_account']);
24
+ /**
25
+ * The service-account API-key prefix (mirror of Core's `API_KEY_PREFIX = 'ask_'`, ADR-0033) —
26
+ * inlined as a literal so this file keeps its ZERO-runtime-`require('@astram/core')` discipline
27
+ * (importing the value would add a runtime dep; the type-only imports above are erased). Kept in
28
+ * sync with Core by the ADR + a guard test that asserts equality with the Core export.
29
+ */
30
+ const API_KEY_PREFIX = 'ask_';
23
31
  /** Extract a bearer token from an `Authorization: Bearer <token>` header. */
24
32
  function extractBearerToken(authorizationHeader) {
25
33
  if (typeof authorizationHeader !== 'string')
@@ -64,14 +72,53 @@ function principalFromClaims(claims) {
64
72
  isRoot: claims.isRoot === true,
65
73
  });
66
74
  }
75
+ /**
76
+ * Is this bearer token a service-account API key (`ask_…`, ADR-0033) rather than a session
77
+ * access token? A cheap prefix check; the authenticator does the real (constant-time) verify.
78
+ */
79
+ function isApiKey(token) {
80
+ return token.startsWith(API_KEY_PREFIX);
81
+ }
82
+ /**
83
+ * Synthesize {@link BoundPrincipalClaims} for a machine (service-account) request from its resolved
84
+ * {@link Principal} (ADR-0033). A service account has NO interactive-session state — so the
85
+ * session-only gates are set to their benign defaults:
86
+ * - `mustReenroll: false` — re-enroll is a human recovery-code concern; a machine never re-enrolls.
87
+ * - `sessionId: ''` — there is no durable session row (the key IS the credential); the field is
88
+ * present only to satisfy the claims shape (no logout/liveness correlation applies to a key —
89
+ * revocation is via the key's `revokedAtMs`, enforced at authenticate time).
90
+ * - no `cachedFacts` (absent) — gate 5 DB-reloads the account's grants (fail-closed default; a
91
+ * machine has no login-time projection).
92
+ * The Principal's `scopeSubtreeNodeIds` is carried through so the tenant/scope gate reads the
93
+ * account's own subtree (least privilege).
94
+ */
95
+ function claimsFromServicePrincipal(principal) {
96
+ return {
97
+ id: principal.id,
98
+ tenantId: principal.tenantId,
99
+ roleIds: [...principal.roleIds],
100
+ kind: principal.kind,
101
+ isRoot: false, // a service account is NEVER root (ADR-0033) — defense-in-depth on the claims too.
102
+ sessionId: '',
103
+ mustReenroll: false,
104
+ scopeSubtreeNodeIds: [...principal.scopeSubtreeNodeIds],
105
+ };
106
+ }
67
107
  /**
68
108
  * Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
69
109
  * when there is no token, no binding, or the binding is malformed.
110
+ *
111
+ * When `serviceAuth` is provided and the bearer token is an API key (`ask_…`, ADR-0033), the token
112
+ * is authenticated as a service account instead of resolved against the session binder.
70
113
  */
71
- async function resolveRequestPrincipal(authorizationHeader, binder) {
114
+ async function resolveRequestPrincipal(authorizationHeader, binder, serviceAuth) {
72
115
  const token = extractBearerToken(authorizationHeader);
73
116
  if (token === null)
74
117
  return null;
118
+ if (serviceAuth !== undefined && isApiKey(token)) {
119
+ const res = await serviceAuth.authenticateApiKey(token);
120
+ return res.ok ? res.principal : null; // fail-closed uniform on any bad key.
121
+ }
75
122
  const claims = await binder.resolve(token);
76
123
  if (claims === null)
77
124
  return null;
@@ -85,10 +132,19 @@ async function resolveRequestPrincipal(authorizationHeader, binder) {
85
132
  * uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
86
133
  * while still attaching the resolved `Principal` for `decide()`.
87
134
  */
88
- async function resolveRequestAuth(authorizationHeader, binder) {
135
+ async function resolveRequestAuth(authorizationHeader, binder, serviceAuth) {
89
136
  const token = extractBearerToken(authorizationHeader);
90
137
  if (token === null)
91
138
  return null;
139
+ // Machine path (ADR-0033): an `ask_…` bearer is a service-account API key. Authenticate it and
140
+ // synthesize the claims (benign session defaults) so ALL downstream enforcement — the re-enroll
141
+ // gate, the facts hot-path, decide() — is IDENTICAL to a human's (invariant #3, one authority).
142
+ if (serviceAuth !== undefined && isApiKey(token)) {
143
+ const res = await serviceAuth.authenticateApiKey(token);
144
+ if (!res.ok)
145
+ return null; // uniform fail-closed (malformed/unknown/expired/revoked/outage).
146
+ return { principal: res.principal, claims: claimsFromServicePrincipal(res.principal) };
147
+ }
92
148
  const claims = await binder.resolve(token);
93
149
  if (claims === null)
94
150
  return null;
@@ -35,7 +35,7 @@
35
35
  * (`PermissionFactsPort.loadCoarseGrants`). A cache miss NEVER fabricates a permit.
36
36
  */
37
37
  import { AsyncLocalStorage } from 'node:async_hooks';
38
- import type { CoarseGrants } from '@astram/core';
38
+ import type { CoarseGrants, PermissionFactsPort } from '@astram/core';
39
39
  /**
40
40
  * The per-request session-facts store carried through the ALS. `cachedFacts` is the
41
41
  * principal's coarse grants for THIS request (or `null` when none were projected →
@@ -47,7 +47,22 @@ export interface SessionFactsStore {
47
47
  readonly cachedFacts: CoarseGrants | null;
48
48
  /** The version `cachedFacts` was projected at (0 when absent). */
49
49
  readonly permissionsVersion: number;
50
+ /**
51
+ * OPTIONAL reload signal (security hardening Phase 1C). The framework-built decision authority
52
+ * calls it when gate 5 RELOADS the coarse grants for a principal inside this request's scope
53
+ * (cache miss or stale version), passing the authoritative reloaded grants. The enforcement guard
54
+ * uses it to notice a permission-version move and re-derive the session's scope subtree. A pure
55
+ * notification: it never changes the decision, and readers ignore it.
56
+ */
57
+ readonly onReload?: (principalId: string, tenantId: string, grants: CoarseGrants) => void;
50
58
  }
59
+ /**
60
+ * Wrap an app-owned `PermissionFactsPort`-shaped object so a gate-5 reload inside an enforcement
61
+ * request notifies that request's {@link SessionFactsStore.onReload}. Behaviour-identical
62
+ * otherwise: same results, same errors (a throwing reload propagates and notifies nothing).
63
+ * Outside a guard request (no ALS store, e.g. the engine's in-handler decide calls) it is a no-op.
64
+ */
65
+ export declare function signalGateFiveReloads(port: PermissionFactsPort): PermissionFactsPort;
51
66
  /**
52
67
  * The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
53
68
  * enforcement guard imports this to `run()` the `decide()` call inside the request's
@@ -1 +1 @@
1
- {"version":3,"file":"session-facts-als.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,sCAA6C,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAG7B,CAAC"}
1
+ {"version":3,"file":"session-facts-als.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CAepF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,sCAA6C,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAG7B,CAAC"}
@@ -37,7 +37,30 @@
37
37
  */
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.NO_SESSION_FACTS = exports.SHIELD_SESSION_FACTS_ALS = void 0;
40
+ exports.signalGateFiveReloads = signalGateFiveReloads;
40
41
  const node_async_hooks_1 = require("node:async_hooks");
42
+ /**
43
+ * Wrap an app-owned `PermissionFactsPort`-shaped object so a gate-5 reload inside an enforcement
44
+ * request notifies that request's {@link SessionFactsStore.onReload}. Behaviour-identical
45
+ * otherwise: same results, same errors (a throwing reload propagates and notifies nothing).
46
+ * Outside a guard request (no ALS store, e.g. the engine's in-handler decide calls) it is a no-op.
47
+ */
48
+ function signalGateFiveReloads(port) {
49
+ return {
50
+ currentPermissionsVersion: (principalId, tenantId) => port.currentPermissionsVersion(principalId, tenantId),
51
+ loadCoarseGrants: async (principalId, tenantId) => {
52
+ const grants = await port.loadCoarseGrants(principalId, tenantId);
53
+ const store = exports.SHIELD_SESSION_FACTS_ALS.getStore();
54
+ try {
55
+ store?.onReload?.(principalId, tenantId, grants);
56
+ }
57
+ catch {
58
+ // A notification must never turn a decision into an error.
59
+ }
60
+ return grants;
61
+ },
62
+ };
63
+ }
41
64
  /**
42
65
  * The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
43
66
  * enforcement guard imports this to `run()` the `decide()` call inside the request's