@astram/guard 0.2.0 → 0.4.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 (120) hide show
  1. package/dist/auth/account.controller.d.ts +245 -0
  2. package/dist/auth/account.controller.d.ts.map +1 -0
  3. package/dist/auth/account.controller.js +673 -0
  4. package/dist/auth/auth.controller.d.ts +219 -5
  5. package/dist/auth/auth.controller.d.ts.map +1 -1
  6. package/dist/auth/auth.controller.js +790 -12
  7. package/dist/auth/auth.module.d.ts +101 -3
  8. package/dist/auth/auth.module.d.ts.map +1 -1
  9. package/dist/auth/auth.module.js +300 -8
  10. package/dist/auth/dto.d.ts +16 -6
  11. package/dist/auth/dto.d.ts.map +1 -1
  12. package/dist/auth/ports/audit-sink.d.ts +47 -0
  13. package/dist/auth/ports/audit-sink.d.ts.map +1 -0
  14. package/dist/auth/ports/audit-sink.js +20 -0
  15. package/dist/auth/ports/credential-writer.d.ts +23 -0
  16. package/dist/auth/ports/credential-writer.d.ts.map +1 -0
  17. package/dist/auth/ports/credential-writer.js +2 -0
  18. package/dist/auth/ports/email-sender.d.ts +14 -0
  19. package/dist/auth/ports/email-sender.d.ts.map +1 -0
  20. package/dist/auth/ports/email-sender.js +2 -0
  21. package/dist/auth/ports/mfa-device-store.d.ts +45 -0
  22. package/dist/auth/ports/mfa-device-store.d.ts.map +1 -0
  23. package/dist/auth/ports/mfa-device-store.js +13 -0
  24. package/dist/auth/ports/profile-writer.d.ts +12 -0
  25. package/dist/auth/ports/profile-writer.d.ts.map +1 -0
  26. package/dist/auth/ports/profile-writer.js +2 -0
  27. package/dist/auth/ports/recovery-code-store.d.ts +10 -0
  28. package/dist/auth/ports/recovery-code-store.d.ts.map +1 -0
  29. package/dist/auth/ports/recovery-code-store.js +2 -0
  30. package/dist/auth/ports/reset-token-store.d.ts +12 -0
  31. package/dist/auth/ports/reset-token-store.d.ts.map +1 -0
  32. package/dist/auth/ports/reset-token-store.js +2 -0
  33. package/dist/auth/ports/session-facts-projector.d.ts +44 -0
  34. package/dist/auth/ports/session-facts-projector.d.ts.map +1 -0
  35. package/dist/auth/ports/session-facts-projector.js +30 -0
  36. package/dist/auth/ports/session-sweeper.d.ts +12 -0
  37. package/dist/auth/ports/session-sweeper.d.ts.map +1 -0
  38. package/dist/auth/ports/session-sweeper.js +2 -0
  39. package/dist/auth/profile-resolver.d.ts +73 -0
  40. package/dist/auth/profile-resolver.d.ts.map +1 -0
  41. package/dist/auth/profile-resolver.js +28 -0
  42. package/dist/auth/rate-limit/attempt-limiter.d.ts +91 -0
  43. package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -0
  44. package/dist/auth/rate-limit/attempt-limiter.js +133 -0
  45. package/dist/auth/tokens.d.ts +174 -0
  46. package/dist/auth/tokens.d.ts.map +1 -1
  47. package/dist/auth/tokens.js +190 -1
  48. package/dist/auth/totp-enrollment.d.ts +42 -0
  49. package/dist/auth/totp-enrollment.d.ts.map +1 -0
  50. package/dist/auth/totp-enrollment.js +96 -0
  51. package/dist/authentication/mariadb-credential-resolver.d.ts +38 -24
  52. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  53. package/dist/authentication/mariadb-credential-resolver.js +42 -28
  54. package/dist/authz/cross-tenant-grant-writer.d.ts +259 -0
  55. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -0
  56. package/dist/authz/cross-tenant-grant-writer.js +397 -0
  57. package/dist/authz/manifest-operation-authority-adapter.d.ts +65 -0
  58. package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -0
  59. package/dist/authz/manifest-operation-authority-adapter.js +108 -0
  60. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +90 -0
  61. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -0
  62. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +211 -0
  63. package/dist/authz/mariadb-record-rule-adapter.d.ts +60 -0
  64. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -0
  65. package/dist/authz/mariadb-record-rule-adapter.js +154 -0
  66. package/dist/authz/mariadb-record-share-adapter.d.ts +75 -0
  67. package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -0
  68. package/dist/authz/mariadb-record-share-adapter.js +106 -0
  69. package/dist/authz/record-share-cascade.d.ts +52 -0
  70. package/dist/authz/record-share-cascade.d.ts.map +1 -0
  71. package/dist/authz/record-share-cascade.js +49 -0
  72. package/dist/authz/record-share-writer.d.ts +175 -0
  73. package/dist/authz/record-share-writer.d.ts.map +1 -0
  74. package/dist/authz/record-share-writer.js +225 -0
  75. package/dist/authz/wire-record-authorization.d.ts +66 -0
  76. package/dist/authz/wire-record-authorization.d.ts.map +1 -0
  77. package/dist/authz/wire-record-authorization.js +34 -0
  78. package/dist/completeness/catalog-reconciler.d.ts +21 -0
  79. package/dist/completeness/catalog-reconciler.d.ts.map +1 -0
  80. package/dist/completeness/catalog-reconciler.js +74 -0
  81. package/dist/completeness/operation-inventory-scanner.d.ts +17 -0
  82. package/dist/completeness/operation-inventory-scanner.d.ts.map +1 -0
  83. package/dist/completeness/operation-inventory-scanner.js +79 -0
  84. package/dist/completeness/ports/catalog-store.d.ts +33 -0
  85. package/dist/completeness/ports/catalog-store.d.ts.map +1 -0
  86. package/dist/completeness/ports/catalog-store.js +2 -0
  87. package/dist/config/app-config.d.ts +18 -16
  88. package/dist/config/app-config.d.ts.map +1 -1
  89. package/dist/config/app-config.js +68 -35
  90. package/dist/enforcement/classification.decorator.d.ts +33 -14
  91. package/dist/enforcement/classification.decorator.d.ts.map +1 -1
  92. package/dist/enforcement/classification.decorator.js +34 -14
  93. package/dist/enforcement/global-enforcement.guard.d.ts +29 -19
  94. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
  95. package/dist/enforcement/global-enforcement.guard.js +108 -25
  96. package/dist/enforcement/reenroll-allowlist.d.ts +49 -0
  97. package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -0
  98. package/dist/enforcement/reenroll-allowlist.js +77 -0
  99. package/dist/index.d.ts +35 -3
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +159 -7
  102. package/dist/module/shield-tokens.d.ts +15 -0
  103. package/dist/module/shield-tokens.d.ts.map +1 -0
  104. package/dist/module/shield-tokens.js +17 -0
  105. package/dist/module/shield.module.d.ts +154 -10
  106. package/dist/module/shield.module.d.ts.map +1 -1
  107. package/dist/module/shield.module.js +181 -24
  108. package/dist/request-auth/request-principal.d.ts +42 -0
  109. package/dist/request-auth/request-principal.d.ts.map +1 -0
  110. package/dist/request-auth/request-principal.js +87 -0
  111. package/dist/request-auth/session-facts-als.d.ts +65 -0
  112. package/dist/request-auth/session-facts-als.d.ts.map +1 -0
  113. package/dist/request-auth/session-facts-als.js +57 -0
  114. package/dist/request-auth/session-token-binder.d.ts +130 -0
  115. package/dist/request-auth/session-token-binder.d.ts.map +1 -0
  116. package/dist/request-auth/session-token-binder.js +277 -0
  117. package/dist/request-auth/tokens.d.ts +14 -0
  118. package/dist/request-auth/tokens.d.ts.map +1 -0
  119. package/dist/request-auth/tokens.js +16 -0
  120. package/package.json +2 -2
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `buildRecordAuthorization` — the ONE place the four framework-owned record-tier
3
+ * guard adapters are constructed and handed to core's `createWiredDecisionAuthority`
4
+ * (design §9, Phase 5 — "the framework pre-wires").
5
+ *
6
+ * ── WHY THIS FILE EXISTS (the record-tier ownership invariant, ADR-0003 / the
7
+ * "framework-owns-the-data-layer" initiative): before Phase 5, the CONSUMER app
8
+ * hand-constructed `MariaDbRecordPermissionRuleAdapter` / `MariaDbRecordShareAdapter`
9
+ * / `MariaDbCrossTenantGrantAdapter` / `ManifestOperationAuthorityAdapter` and called
10
+ * core's `createWiredDecisionAuthority` itself. That meant the app COULD swap in a
11
+ * fail-OPEN record adapter — the exact hole the initiative closes. Phase 5 moves that
12
+ * construction HERE, inside the framework: the app supplies ONLY the DB pool + the two
13
+ * app-owned ports (`permissionFacts`, `sessionFacts`) + the module registry + the
14
+ * operation-manifest DATA. The four record-tier read adapters are framework-built and
15
+ * UN-overridable — a planted fail-open adapter is now impossible.
16
+ *
17
+ * ── WHY IT IS NOT STATICALLY IMPORTED BY shield.module.ts (the enforcement-only
18
+ * invariant): this file value-imports `@astram/core` (`createWiredDecisionAuthority`)
19
+ * and the adapter classes (which themselves value-import core). `shield.module.ts` is
20
+ * deliberately kept core-free in its OWN compiled output (see its header) so the
21
+ * enforcement-only path is provably free of a top-level `require('@astram/core')`.
22
+ * So `shield.module.ts` reaches `buildRecordAuthorization` ONLY through a LAZY dynamic
23
+ * import inside the `SHIELD_DECISION_AUTHORITY` async factory — which runs ONLY when a
24
+ * consumer opts into `recordAuthorization`. This file (and thus core) loads only then.
25
+ */
26
+ import type { DecisionAuthority, ModuleRegistry, PermissionFactsPort, SessionFactsReader } from '@astram/core';
27
+ import { type RuleQueryable } from './mariadb-record-rule-adapter.js';
28
+ import { type OperationManifestEntry } from './manifest-operation-authority-adapter.js';
29
+ /**
30
+ * The pieces a consumer supplies for the framework to build the real record-tier
31
+ * DecisionAuthority. The app owns ONLY these — the pool, the two app-owned ports, the
32
+ * module registry, and the operation-manifest DATA. The four record-tier read adapters
33
+ * are framework-constructed from `pool` and are DELIBERATELY NOT part of this shape
34
+ * (they cannot be overridden — the Phase-5 acceptance criterion).
35
+ */
36
+ export interface RecordAuthorizationInputs {
37
+ /**
38
+ * The MariaDB pool the three DB-backed record-tier adapters read through. Typed
39
+ * structurally (`RuleQueryable`: `{ query(sql, params?) }`) — a `mariadb` `Pool`
40
+ * satisfies it — so the guard module signature does not force a hard `mariadb` type
41
+ * on consumers that inject a custom queryable. The framework builds the adapters over
42
+ * THIS pool; the app never constructs them.
43
+ */
44
+ readonly pool: RuleQueryable;
45
+ /** APP-OWNED port: the principal's coarse grants for the object-type gate (gate 5). */
46
+ readonly permissionFacts: PermissionFactsPort;
47
+ /** APP-OWNED port: per-request cached session facts (null-slot until the Redis hot path). */
48
+ readonly sessionFacts: SessionFactsReader;
49
+ /** APP-OWNED: the module registry (object types + actions + tiers), bridged from the app's registrations. */
50
+ readonly registry: ModuleRegistry;
51
+ /**
52
+ * APP-SUPPLIED DATA (not an adapter): the declared trusted-operation manifest entries.
53
+ * The framework constructs the `ManifestOperationAuthorityAdapter` over them. Empty
54
+ * (default) ⇒ every delegation lookup is pass-through (undefined). The app supplies the
55
+ * ENTRIES; the framework owns the ADAPTER.
56
+ */
57
+ readonly operationManifest?: readonly OperationManifestEntry[];
58
+ }
59
+ /**
60
+ * Construct the four framework-owned record-tier adapters over the supplied pool +
61
+ * manifest, and return the fully-wired core {@link DecisionAuthority}. Behavior-identical
62
+ * to what the consumer's `buildDecisionAuthority` did before Phase 5 (same adapters, same
63
+ * pool, same registry, same ports) — this is a wiring MOVE, not a logic change.
64
+ */
65
+ export declare function buildRecordAuthorization(inputs: RecordAuthorizationInputs): DecisionAuthority;
66
+ //# sourceMappingURL=wire-record-authorization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire-record-authorization.d.ts","sourceRoot":"","sources":["../../src/authz/wire-record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,2CAA2C,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,eAAe,EAAE,mBAAmB,CAAC;IAC9C,6FAA6F;IAC7F,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,yBAAyB,GAAG,iBAAiB,CAmB7F"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRecordAuthorization = buildRecordAuthorization;
4
+ const core_1 = require("@astram/core");
5
+ const mariadb_record_rule_adapter_js_1 = require("./mariadb-record-rule-adapter.js");
6
+ const mariadb_record_share_adapter_js_1 = require("./mariadb-record-share-adapter.js");
7
+ const mariadb_cross_tenant_grant_adapter_js_1 = require("./mariadb-cross-tenant-grant-adapter.js");
8
+ const manifest_operation_authority_adapter_js_1 = require("./manifest-operation-authority-adapter.js");
9
+ /**
10
+ * Construct the four framework-owned record-tier adapters over the supplied pool +
11
+ * manifest, and return the fully-wired core {@link DecisionAuthority}. Behavior-identical
12
+ * to what the consumer's `buildDecisionAuthority` did before Phase 5 (same adapters, same
13
+ * pool, same registry, same ports) — this is a wiring MOVE, not a logic change.
14
+ */
15
+ function buildRecordAuthorization(inputs) {
16
+ return (0, core_1.createWiredDecisionAuthority)({
17
+ registry: inputs.registry,
18
+ permissionFacts: inputs.permissionFacts,
19
+ sessionFacts: inputs.sessionFacts,
20
+ // ── The FOUR framework-owned record-tier adapters — constructed HERE, and ONLY here.
21
+ // Record-permit ADMIN rules over `shield_authz.record_permission_rule` (widen the
22
+ // core-owned owner baseline, deny-wins). Empty table ⇒ owner-only (NOT a lockout).
23
+ recordRule: new mariadb_record_rule_adapter_js_1.MariaDbRecordPermissionRuleAdapter(inputs.pool),
24
+ // Per-record SHARE source over `shield_authz.record_share` (widening permit, union
25
+ // with baseline under deny-wins). No share ⇒ false (baseline still governs).
26
+ recordShare: new mariadb_record_share_adapter_js_1.MariaDbRecordShareAdapter(inputs.pool),
27
+ // Cross-tenant consented grants over `shield_authz.cross_tenant_grant`. No active
28
+ // grant ⇒ null (deny-by-default for the cross-tenant reach).
29
+ crossTenantGrant: new mariadb_cross_tenant_grant_adapter_js_1.MariaDbCrossTenantGrantAdapter(inputs.pool),
30
+ // Delegation / trusted-operation authority over the DECLARED manifest (no DB).
31
+ // Empty ⇒ pass-through. The app supplies the entries; the framework owns the adapter.
32
+ operationAuthority: new manifest_operation_authority_adapter_js_1.ManifestOperationAuthorityAdapter(inputs.operationManifest ?? []),
33
+ });
34
+ }
@@ -0,0 +1,21 @@
1
+ import type { OperationInventory, ModuleRegistry } from '@astram/core';
2
+ import type { CatalogStore, ReconcileReport } from './ports/catalog-store.js';
3
+ /** A @Classified route could not be gated (stranded or key-not-registered). Boot must
4
+ * abort — an ungatable route may never serve traffic (ADR-0002 fail-closed). */
5
+ export declare class CatalogGatabilityError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ export declare class CatalogReconciler {
9
+ /**
10
+ * BOOT — read-only, fail-closed. Throws if any non-public route is stranded (no key)
11
+ * or unclassified (declares a key the registry doesn't know). No DB, no writes.
12
+ */
13
+ verifyGatable(inventory: OperationInventory, registry: ModuleRegistry): void;
14
+ /**
15
+ * CRON — mutating. Auto-adds an ACTIVE catalog row per registered non-public action,
16
+ * marks orphaned any stored row with no registered action, reactivates revived ones.
17
+ * Idempotent. Returns a report for logging + /health/catalog.
18
+ */
19
+ reconcile(registry: ModuleRegistry, store: CatalogStore, nowIso: string): Promise<ReconcileReport>;
20
+ }
21
+ //# sourceMappingURL=catalog-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-reconciler.d.ts","sourceRoot":"","sources":["../../src/completeness/catalog-reconciler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE9E;iFACiF;AACjF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,iBAAiB;IAC5B;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAgB5E;;;;OAIG;IACG,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAoCzG"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CatalogReconciler = exports.CatalogGatabilityError = void 0;
4
+ // astram-framework/packages/guard/src/completeness/catalog-reconciler.ts
5
+ const core_1 = require("@astram/core");
6
+ /** A @Classified route could not be gated (stranded or key-not-registered). Boot must
7
+ * abort — an ungatable route may never serve traffic (ADR-0002 fail-closed). */
8
+ class CatalogGatabilityError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'CatalogGatabilityError';
12
+ }
13
+ }
14
+ exports.CatalogGatabilityError = CatalogGatabilityError;
15
+ class CatalogReconciler {
16
+ /**
17
+ * BOOT — read-only, fail-closed. Throws if any non-public route is stranded (no key)
18
+ * or unclassified (declares a key the registry doesn't know). No DB, no writes.
19
+ */
20
+ verifyGatable(inventory, registry) {
21
+ const result = (0, core_1.checkCompleteness)(inventory, registry);
22
+ if (result.strandedOperations.length > 0 || result.unclassifiedOperations.length > 0) {
23
+ const stranded = result.strandedOperations.map((o) => o.operationId);
24
+ const unclassified = result.unclassifiedOperations.map((o) => `${o.operationId}${o.declaredPermissionKey ? ` (${o.declaredPermissionKey})` : ''}`);
25
+ throw new CatalogGatabilityError(`Shield: ${stranded.length} stranded + ${unclassified.length} unclassified route(s) — cannot boot. ` +
26
+ `Stranded (no @Classified): ${stranded.join(', ') || 'none'}. ` +
27
+ `Unclassified (key not registered): ${unclassified.join(', ') || 'none'}. ` +
28
+ `Classify the route or register the action.`);
29
+ }
30
+ }
31
+ /**
32
+ * CRON — mutating. Auto-adds an ACTIVE catalog row per registered non-public action,
33
+ * marks orphaned any stored row with no registered action, reactivates revived ones.
34
+ * Idempotent. Returns a report for logging + /health/catalog.
35
+ */
36
+ async reconcile(registry, store, nowIso) {
37
+ const stored = await store.listPermissions();
38
+ const storedByKey = new Map(stored.map((r) => [r.permissionKey, r]));
39
+ const registered = registry.listActions().filter((a) => a.tier !== 'public');
40
+ const registeredKeys = new Set(registered.map((a) => a.permissionKey));
41
+ // code → catalog: add missing rows; revive orphaned-but-now-registered.
42
+ const added = [];
43
+ const reactivated = [];
44
+ for (const a of registered) {
45
+ const key = a.permissionKey;
46
+ const existing = storedByKey.get(key);
47
+ if (!existing) {
48
+ const inserted = await store.upsertPermission({
49
+ permissionKey: key,
50
+ moduleKey: a.moduleKey,
51
+ typeKey: a.action.typeKey,
52
+ action: a.action.actionKey,
53
+ label: `${a.action.actionKey} ${a.action.typeKey}`,
54
+ tier: a.tier,
55
+ });
56
+ if (inserted)
57
+ added.push(key);
58
+ }
59
+ else if (existing.status === 'orphaned') {
60
+ reactivated.push(key);
61
+ }
62
+ }
63
+ if (reactivated.length)
64
+ await store.markActive(reactivated);
65
+ // catalog → code: mark orphaned any active stored row with no registered action.
66
+ const orphaned = stored
67
+ .filter((r) => r.status === 'active' && !registeredKeys.has(r.permissionKey))
68
+ .map((r) => r.permissionKey);
69
+ if (orphaned.length)
70
+ await store.markOrphaned(orphaned);
71
+ return { added, orphaned, reactivated, at: nowIso };
72
+ }
73
+ }
74
+ exports.CatalogReconciler = CatalogReconciler;
@@ -0,0 +1,17 @@
1
+ import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
2
+ import type { OperationInventory } from '@astram/core';
3
+ /**
4
+ * Reflects every controller handler at boot and emits an OperationInventory for core's
5
+ * checkCompleteness. operationId = `${ControllerClass.name}#${method}` (core's contract).
6
+ * A handler's permissionKey is `${moduleKey}:${typeKey}:${action}` from its @Classified;
7
+ * @Public handlers carry isPublic:true; an unclassified non-public handler carries no
8
+ * permissionKey (→ core reports it stranded).
9
+ */
10
+ export declare class OperationInventoryScanner {
11
+ private readonly discovery;
12
+ private readonly scanner;
13
+ private readonly reflector;
14
+ constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector);
15
+ scan(moduleKey: string): OperationInventory;
16
+ }
17
+ //# sourceMappingURL=operation-inventory-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-inventory-scanner.d.ts","sourceRoot":"","sources":["../../src/completeness/operation-inventory-scanner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,KAAK,EAAE,kBAAkB,EAAuB,MAAM,cAAc,CAAC;AAO5E;;;;;;GAMG;AACH,qBACa,yBAAyB;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS;IAGvC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB;CAyC5C"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.OperationInventoryScanner = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const constants_1 = require("@nestjs/common/constants");
15
+ const core_1 = require("@nestjs/core");
16
+ const core_2 = require("@astram/core");
17
+ const classification_decorator_js_1 = require("../enforcement/classification.decorator.js");
18
+ /**
19
+ * Reflects every controller handler at boot and emits an OperationInventory for core's
20
+ * checkCompleteness. operationId = `${ControllerClass.name}#${method}` (core's contract).
21
+ * A handler's permissionKey is `${moduleKey}:${typeKey}:${action}` from its @Classified;
22
+ * @Public handlers carry isPublic:true; an unclassified non-public handler carries no
23
+ * permissionKey (→ core reports it stranded).
24
+ */
25
+ let OperationInventoryScanner = class OperationInventoryScanner {
26
+ discovery;
27
+ scanner;
28
+ reflector;
29
+ constructor(discovery, scanner, reflector) {
30
+ this.discovery = discovery;
31
+ this.scanner = scanner;
32
+ this.reflector = reflector;
33
+ }
34
+ scan(moduleKey) {
35
+ const operations = [];
36
+ for (const wrapper of this.discovery.getControllers()) {
37
+ const { instance, metatype } = wrapper;
38
+ if (!instance || !metatype)
39
+ continue;
40
+ const controllerName = metatype.name;
41
+ const prototype = Object.getPrototypeOf(instance);
42
+ for (const method of this.scanner.getAllMethodNames(prototype)) {
43
+ const handler = prototype[method];
44
+ // Only real HTTP route handlers carry route metadata (set by @Get/@Post/etc.).
45
+ // Private/helper methods (e.g. buildQuery()) are emitted by getAllMethodNames but
46
+ // carry no PATH_METADATA — skipping them prevents core from stranding them and
47
+ // aborting boot. This matches what the guard actually enforces at runtime, which
48
+ // only ever runs on mapped handlers via context.getHandler().
49
+ if (Reflect.getMetadata(constants_1.PATH_METADATA, handler) === undefined)
50
+ continue;
51
+ // Mirror the guard's precedence: method metadata overrides class-level.
52
+ const isPublic = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.PUBLIC, [
53
+ handler,
54
+ metatype,
55
+ ]) === true;
56
+ const raw = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.CLASSIFIED, [handler, metatype]);
57
+ const classification = (0, classification_decorator_js_1.normalizeClassification)(raw);
58
+ // Single-source the key format via core's derivePermissionKey (returns the
59
+ // branded PermissionKey) rather than a hand-built template string.
60
+ const permissionKey = !isPublic && classification
61
+ ? (0, core_2.derivePermissionKey)(moduleKey, classification.typeKey, classification.action)
62
+ : undefined;
63
+ operations.push({
64
+ operationId: `${controllerName}#${method}`,
65
+ isPublic,
66
+ ...(permissionKey ? { permissionKey } : {}),
67
+ });
68
+ }
69
+ }
70
+ return { operations };
71
+ }
72
+ };
73
+ exports.OperationInventoryScanner = OperationInventoryScanner;
74
+ exports.OperationInventoryScanner = OperationInventoryScanner = __decorate([
75
+ (0, common_1.Injectable)(),
76
+ __metadata("design:paramtypes", [core_1.DiscoveryService,
77
+ core_1.MetadataScanner,
78
+ core_1.Reflector])
79
+ ], OperationInventoryScanner);
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The DB seam for the catalog reconciler. The framework defines the interface only;
3
+ * the consumer app supplies the implementation (ADR-0003 — guard does no DB). A row is
4
+ * keyed by its permission key (`moduleKey:typeKey:actionKey`); `status` is the soft
5
+ * active/orphaned marker (never hard-delete).
6
+ */
7
+ export interface CatalogPermissionRow {
8
+ readonly permissionKey: string;
9
+ readonly moduleKey: string;
10
+ readonly typeKey: string;
11
+ readonly action: string;
12
+ readonly label: string;
13
+ readonly tier: string;
14
+ readonly status: 'active' | 'orphaned';
15
+ }
16
+ /** The result of one mutating reconcile pass — surfaced via /health/catalog + logged. */
17
+ export interface ReconcileReport {
18
+ readonly added: readonly string[];
19
+ readonly orphaned: readonly string[];
20
+ readonly reactivated: readonly string[];
21
+ readonly at: string;
22
+ }
23
+ export interface CatalogStore {
24
+ /** All catalog rows (both active + orphaned). */
25
+ listPermissions(): Promise<CatalogPermissionRow[]>;
26
+ /** Idempotent insert of an ACTIVE row (no-op if the key already exists as active). */
27
+ upsertPermission(row: Omit<CatalogPermissionRow, 'status'>): Promise<boolean>;
28
+ /** Mark keys orphaned (soft). No-op for keys already orphaned. */
29
+ markOrphaned(keys: readonly string[]): Promise<void>;
30
+ /** Flip keys orphaned → active (a route returned). */
31
+ markActive(keys: readonly string[]): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=catalog-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-store.d.ts","sourceRoot":"","sources":["../../../src/completeness/ports/catalog-store.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC;AAED,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnD,sFAAsF;IACtF,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,kEAAkE;IAClE,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,sDAAsD;IACtD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -42,7 +42,7 @@ export interface AppConfig {
42
42
  */
43
43
  readonly redis: RedisConfig;
44
44
  /**
45
- * The keyed-HMAC secret for the refresh-token hasher (`SHIELD_REFRESH_HMAC_KEY`).
45
+ * The keyed-HMAC secret for the refresh-token hasher (`REFRESH_HMAC_KEY`).
46
46
  * ⛔ REQUIRED FOR AUTH — this is a SECRET (a leaked DB would be offline-brute-
47
47
  * forceable without it). {@link loadAppConfig} reads it but does NOT throw when
48
48
  * blank, so the ENFORCEMENT-ONLY scaffold (example-backend / a freshly-scaffolded
@@ -146,12 +146,13 @@ export declare const SCHEMA_SVC_POOL_HARD_CAP = 2;
146
146
  /**
147
147
  * Build the {@link PoolBudgetConfig} from env with documented defaults.
148
148
  * Field ↔ env name:
149
- * workers ← SHIELD_POOL_WORKERS
150
- * maxConnections ← SHIELD_DB_MAX_CONNECTIONS
151
- * headroom ← SHIELD_POOL_HEADROOM
152
- * perAccountMax.shield_core ← SHIELD_POOL_MAX_CORE
153
- * perAccountMax.shield_app_crud ← SHIELD_POOL_MAX_APP_CRUD
154
- * perAccountMax.shield_schema_svc ← SHIELD_POOL_MAX_SCHEMA_SVC
149
+ * workers ← POOL_WORKERS
150
+ * maxConnections ← DB_MAX_CONNECTIONS
151
+ * headroom ← POOL_HEADROOM
152
+ * perAccountMax.shield_core ← POOL_MAX_CORE
153
+ * perAccountMax.shield_app_crud ← POOL_MAX_APP_CRUD
154
+ * perAccountMax.shield_schema_svc ← POOL_MAX_SCHEMA_SVC
155
+ * (Legacy `SHIELD_`-prefixed names are still read as a fallback — see {@link readEnv}.)
155
156
  */
156
157
  export declare function loadPoolBudget(env?: NodeJS.ProcessEnv): PoolBudgetConfig;
157
158
  /** Documented defaults for the runtime MariaDB descriptor (local docker substrate). */
@@ -161,9 +162,9 @@ export declare const DEFAULT_REDIS_URL = "redis://localhost:6379";
161
162
  /**
162
163
  * Build the runtime {@link DbConfig} from env with documented defaults.
163
164
  * Field ↔ env name:
164
- * host ← SHIELD_DB_HOST port ← SHIELD_DB_PORT
165
- * user ← SHIELD_DB_USER password ← SHIELD_DB_PASSWORD (secret)
166
- * database ← SHIELD_DB_NAME
165
+ * host ← DB_HOST port ← DB_PORT
166
+ * user ← DB_USER password ← DB_PASSWORD (secret)
167
+ * database ← DB_NAME (legacy SHIELD_DB_* still read as a fallback)
167
168
  * The password defaults to '' when absent — a live connection then fails at
168
169
  * connect time (loud), which is correct for a runtime that needs real creds; the
169
170
  * secret is deliberately NOT hard-required at config load (unlike the HMAC key,
@@ -171,9 +172,9 @@ export declare const DEFAULT_REDIS_URL = "redis://localhost:6379";
171
172
  */
172
173
  export declare function loadDbConfig(env?: NodeJS.ProcessEnv): DbConfig;
173
174
  /**
174
- * Build the {@link RedisConfig} from env. Prefers `SHIELD_REDIS_URL`; falls back
175
- * to composing `redis://host:port` from `SHIELD_REDIS_HOST`/`SHIELD_REDIS_PORT`,
176
- * else the documented default.
175
+ * Build the {@link RedisConfig} from env. Prefers `REDIS_URL`; falls back
176
+ * to composing `redis://host:port` from `REDIS_HOST`/`REDIS_PORT`, else the
177
+ * documented default. (Legacy `SHIELD_REDIS_*` names still read as a fallback.)
177
178
  */
178
179
  export declare function loadRedisConfig(env?: NodeJS.ProcessEnv): RedisConfig;
179
180
  /**
@@ -182,9 +183,10 @@ export declare function loadRedisConfig(env?: NodeJS.ProcessEnv): RedisConfig;
182
183
  * core's `resolveLifetimes` job (throws LOUD at composition) — this loader only
183
184
  * reads the numbers.
184
185
  * Field ↔ env name:
185
- * accessTtlMs ← SHIELD_ACCESS_TTL_MS
186
- * refreshIdleMs ← SHIELD_REFRESH_IDLE_MS
187
- * refreshAbsoluteCapMs ← SHIELD_REFRESH_ABSOLUTE_CAP_MS
186
+ * accessTtlMs ← ACCESS_TTL_MS
187
+ * refreshIdleMs ← REFRESH_IDLE_MS
188
+ * refreshAbsoluteCapMs ← REFRESH_ABSOLUTE_CAP_MS
189
+ * (Legacy `SHIELD_`-prefixed names still read as a fallback.)
188
190
  */
189
191
  export declare function loadSessionLifetimes(env?: NodeJS.ProcessEnv): SessionLifetimeOverrides;
190
192
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
@@ -47,22 +47,23 @@ function parsePositiveInt(raw, fallback) {
47
47
  /**
48
48
  * Build the {@link PoolBudgetConfig} from env with documented defaults.
49
49
  * Field ↔ env name:
50
- * workers ← SHIELD_POOL_WORKERS
51
- * maxConnections ← SHIELD_DB_MAX_CONNECTIONS
52
- * headroom ← SHIELD_POOL_HEADROOM
53
- * perAccountMax.shield_core ← SHIELD_POOL_MAX_CORE
54
- * perAccountMax.shield_app_crud ← SHIELD_POOL_MAX_APP_CRUD
55
- * perAccountMax.shield_schema_svc ← SHIELD_POOL_MAX_SCHEMA_SVC
50
+ * workers ← POOL_WORKERS
51
+ * maxConnections ← DB_MAX_CONNECTIONS
52
+ * headroom ← POOL_HEADROOM
53
+ * perAccountMax.shield_core ← POOL_MAX_CORE
54
+ * perAccountMax.shield_app_crud ← POOL_MAX_APP_CRUD
55
+ * perAccountMax.shield_schema_svc ← POOL_MAX_SCHEMA_SVC
56
+ * (Legacy `SHIELD_`-prefixed names are still read as a fallback — see {@link readEnv}.)
56
57
  */
57
58
  function loadPoolBudget(env = process.env) {
58
59
  return {
59
- workers: parsePositiveInt(env.SHIELD_POOL_WORKERS, exports.DEFAULT_POOL_BUDGET.workers),
60
- maxConnections: parsePositiveInt(env.SHIELD_DB_MAX_CONNECTIONS, exports.DEFAULT_POOL_BUDGET.maxConnections),
61
- headroom: parsePositiveInt(env.SHIELD_POOL_HEADROOM, exports.DEFAULT_POOL_BUDGET.headroom),
60
+ workers: parsePositiveInt(readEnv(env, 'POOL_WORKERS'), exports.DEFAULT_POOL_BUDGET.workers),
61
+ maxConnections: parsePositiveInt(readEnv(env, 'DB_MAX_CONNECTIONS'), exports.DEFAULT_POOL_BUDGET.maxConnections),
62
+ headroom: parsePositiveInt(readEnv(env, 'POOL_HEADROOM'), exports.DEFAULT_POOL_BUDGET.headroom),
62
63
  perAccountMax: {
63
- shield_core: parsePositiveInt(env.SHIELD_POOL_MAX_CORE, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_core),
64
- shield_app_crud: parsePositiveInt(env.SHIELD_POOL_MAX_APP_CRUD, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_app_crud),
65
- shield_schema_svc: parsePositiveInt(env.SHIELD_POOL_MAX_SCHEMA_SVC, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_schema_svc),
64
+ shield_core: parsePositiveInt(readEnv(env, 'POOL_MAX_CORE'), exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_core),
65
+ shield_app_crud: parsePositiveInt(readEnv(env, 'POOL_MAX_APP_CRUD'), exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_app_crud),
66
+ shield_schema_svc: parsePositiveInt(readEnv(env, 'POOL_MAX_SCHEMA_SVC'), exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_schema_svc),
66
67
  },
67
68
  };
68
69
  }
@@ -80,6 +81,36 @@ exports.DEFAULT_DB_CONFIG = {
80
81
  };
81
82
  /** Documented default Redis url (local docker substrate). */
82
83
  exports.DEFAULT_REDIS_URL = 'redis://localhost:6379';
84
+ /**
85
+ * Read a consumer-facing env var by its CURRENT (unprefixed) name, falling back to
86
+ * the LEGACY `SHIELD_`-prefixed name for one deprecation window. The framework's
87
+ * public config vars were debranded from `SHIELD_*` to bare names (e.g.
88
+ * `SHIELD_DB_HOST` → `DB_HOST`); reading both keeps existing `.env` files (which may
89
+ * still set `SHIELD_*`) working unchanged. The new name wins when both are set.
90
+ * A non-blank legacy value emits a one-time deprecation warning (never logs secrets —
91
+ * only the var NAME). Internal DI-token symbols (`Symbol('SHIELD_...')`) are unrelated
92
+ * and unchanged.
93
+ */
94
+ const legacyEnvWarned = new Set();
95
+ function readEnv(env, name) {
96
+ const current = env[name];
97
+ if (current !== undefined && current.trim() !== '')
98
+ return current;
99
+ const legacyName = `SHIELD_${name}`;
100
+ const legacy = env[legacyName];
101
+ if (legacy !== undefined && legacy.trim() !== '') {
102
+ if (!legacyEnvWarned.has(legacyName)) {
103
+ legacyEnvWarned.add(legacyName);
104
+ // eslint-disable-next-line no-console
105
+ console.warn(`[astram] env var '${legacyName}' is deprecated — rename it to '${name}'. ` +
106
+ `The SHIELD_ prefix will be removed in a future release.`);
107
+ }
108
+ return legacy;
109
+ }
110
+ // Return whichever was present-but-blank (so parseString applies its own fallback),
111
+ // preferring the current name.
112
+ return current ?? legacy;
113
+ }
83
114
  /** Read a string from env with a fallback (trims; blank ⇒ fallback). */
84
115
  function parseString(raw, fallback) {
85
116
  if (raw === undefined || raw.trim() === '')
@@ -101,9 +132,9 @@ function parseOptionalPositiveInt(raw) {
101
132
  /**
102
133
  * Build the runtime {@link DbConfig} from env with documented defaults.
103
134
  * Field ↔ env name:
104
- * host ← SHIELD_DB_HOST port ← SHIELD_DB_PORT
105
- * user ← SHIELD_DB_USER password ← SHIELD_DB_PASSWORD (secret)
106
- * database ← SHIELD_DB_NAME
135
+ * host ← DB_HOST port ← DB_PORT
136
+ * user ← DB_USER password ← DB_PASSWORD (secret)
137
+ * database ← DB_NAME (legacy SHIELD_DB_* still read as a fallback)
107
138
  * The password defaults to '' when absent — a live connection then fails at
108
139
  * connect time (loud), which is correct for a runtime that needs real creds; the
109
140
  * secret is deliberately NOT hard-required at config load (unlike the HMAC key,
@@ -111,25 +142,25 @@ function parseOptionalPositiveInt(raw) {
111
142
  */
112
143
  function loadDbConfig(env = process.env) {
113
144
  return {
114
- host: parseString(env.SHIELD_DB_HOST, exports.DEFAULT_DB_CONFIG.host),
115
- port: parsePort(env.SHIELD_DB_PORT, exports.DEFAULT_DB_CONFIG.port),
116
- user: parseString(env.SHIELD_DB_USER, exports.DEFAULT_DB_CONFIG.user),
117
- password: parseString(env.SHIELD_DB_PASSWORD, ''),
118
- database: parseString(env.SHIELD_DB_NAME, exports.DEFAULT_DB_CONFIG.database),
145
+ host: parseString(readEnv(env, 'DB_HOST'), exports.DEFAULT_DB_CONFIG.host),
146
+ port: parsePort(readEnv(env, 'DB_PORT'), exports.DEFAULT_DB_CONFIG.port),
147
+ user: parseString(readEnv(env, 'DB_USER'), exports.DEFAULT_DB_CONFIG.user),
148
+ password: parseString(readEnv(env, 'DB_PASSWORD'), ''),
149
+ database: parseString(readEnv(env, 'DB_NAME'), exports.DEFAULT_DB_CONFIG.database),
119
150
  };
120
151
  }
121
152
  /**
122
- * Build the {@link RedisConfig} from env. Prefers `SHIELD_REDIS_URL`; falls back
123
- * to composing `redis://host:port` from `SHIELD_REDIS_HOST`/`SHIELD_REDIS_PORT`,
124
- * else the documented default.
153
+ * Build the {@link RedisConfig} from env. Prefers `REDIS_URL`; falls back
154
+ * to composing `redis://host:port` from `REDIS_HOST`/`REDIS_PORT`, else the
155
+ * documented default. (Legacy `SHIELD_REDIS_*` names still read as a fallback.)
125
156
  */
126
157
  function loadRedisConfig(env = process.env) {
127
- const explicit = env.SHIELD_REDIS_URL;
158
+ const explicit = readEnv(env, 'REDIS_URL');
128
159
  if (explicit !== undefined && explicit.trim() !== '') {
129
160
  return { url: explicit.trim() };
130
161
  }
131
- const host = env.SHIELD_REDIS_HOST;
132
- const port = env.SHIELD_REDIS_PORT;
162
+ const host = readEnv(env, 'REDIS_HOST');
163
+ const port = readEnv(env, 'REDIS_PORT');
133
164
  if ((host !== undefined && host.trim() !== '') || (port !== undefined && port.trim() !== '')) {
134
165
  const h = parseString(host, 'localhost');
135
166
  const p = parsePort(port, 6379);
@@ -143,15 +174,16 @@ function loadRedisConfig(env = process.env) {
143
174
  * core's `resolveLifetimes` job (throws LOUD at composition) — this loader only
144
175
  * reads the numbers.
145
176
  * Field ↔ env name:
146
- * accessTtlMs ← SHIELD_ACCESS_TTL_MS
147
- * refreshIdleMs ← SHIELD_REFRESH_IDLE_MS
148
- * refreshAbsoluteCapMs ← SHIELD_REFRESH_ABSOLUTE_CAP_MS
177
+ * accessTtlMs ← ACCESS_TTL_MS
178
+ * refreshIdleMs ← REFRESH_IDLE_MS
179
+ * refreshAbsoluteCapMs ← REFRESH_ABSOLUTE_CAP_MS
180
+ * (Legacy `SHIELD_`-prefixed names still read as a fallback.)
149
181
  */
150
182
  function loadSessionLifetimes(env = process.env) {
151
183
  const overrides = {};
152
- const access = parseOptionalPositiveInt(env.SHIELD_ACCESS_TTL_MS);
153
- const idle = parseOptionalPositiveInt(env.SHIELD_REFRESH_IDLE_MS);
154
- const cap = parseOptionalPositiveInt(env.SHIELD_REFRESH_ABSOLUTE_CAP_MS);
184
+ const access = parseOptionalPositiveInt(readEnv(env, 'ACCESS_TTL_MS'));
185
+ const idle = parseOptionalPositiveInt(readEnv(env, 'REFRESH_IDLE_MS'));
186
+ const cap = parseOptionalPositiveInt(readEnv(env, 'REFRESH_ABSOLUTE_CAP_MS'));
155
187
  if (access !== undefined)
156
188
  overrides.accessTtlMs = access;
157
189
  if (idle !== undefined)
@@ -167,7 +199,7 @@ function loadSessionLifetimes(env = process.env) {
167
199
  * the key's value.
168
200
  */
169
201
  function loadRefreshHmacKey(env = process.env) {
170
- return parseString(env.SHIELD_REFRESH_HMAC_KEY, '');
202
+ return parseString(readEnv(env, 'REFRESH_HMAC_KEY'), '');
171
203
  }
172
204
  /**
173
205
  * ⛔ FAIL-CLOSED GATE for the refresh-token HMAC key. Called by the auth
@@ -186,9 +218,10 @@ function loadRefreshHmacKey(env = process.env) {
186
218
  */
187
219
  function assertRefreshHmacKey(key) {
188
220
  if (typeof key !== 'string' || key.trim() === '') {
189
- throw new Error('Shield auth composition: SHIELD_REFRESH_HMAC_KEY is required and must be ' +
221
+ throw new Error('Astram auth composition: REFRESH_HMAC_KEY is required and must be ' +
190
222
  'non-empty (fail-closed — refusing to compose auth with a blank refresh-token ' +
191
- 'HMAC key). Set it from your secrets manager; never commit the value.');
223
+ 'HMAC key). Set it from your secrets manager; never commit the value. ' +
224
+ '(The legacy env var name SHIELD_REFRESH_HMAC_KEY is still accepted.)');
192
225
  }
193
226
  return key;
194
227
  }