@astram/identity 0.10.0 → 0.12.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 (127) hide show
  1. package/dist/auth-facts-keys.d.ts +16 -0
  2. package/dist/auth-facts-keys.d.ts.map +1 -1
  3. package/dist/auth-facts-keys.js +26 -10
  4. package/dist/cross-tenant-grant.object.d.ts +2 -2
  5. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  6. package/dist/cross-tenant-grant.object.js +8 -4
  7. package/dist/features/audit/directory-label-resolver.d.ts +4 -1
  8. package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
  9. package/dist/features/audit/directory-label-resolver.js +8 -5
  10. package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
  11. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  12. package/dist/features/audit/mariadb-audit-reader.js +10 -2
  13. package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
  14. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  15. package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
  16. package/dist/features/authz/access-bundle-store.d.ts +4 -1
  17. package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
  18. package/dist/features/authz/access-bundle-store.js +19 -16
  19. package/dist/features/authz/catalog-bootstrap.provider.d.ts +4 -1
  20. package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
  21. package/dist/features/authz/catalog-bootstrap.provider.js +37 -6
  22. package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
  23. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  24. package/dist/features/authz/catalog-reconcile.job.js +3 -2
  25. package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
  26. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  27. package/dist/features/authz/db-permission-facts.adapter.js +22 -16
  28. package/dist/features/authz/grant-admin.service.d.ts +4 -1
  29. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  30. package/dist/features/authz/grant-admin.service.js +32 -37
  31. package/dist/features/authz/grant-ceiling.d.ts +6 -1
  32. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  33. package/dist/features/authz/grant-ceiling.js +19 -16
  34. package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
  35. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  36. package/dist/features/authz/mariadb-catalog-store.js +10 -7
  37. package/dist/features/authz/permission-version.bumper.d.ts +6 -1
  38. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  39. package/dist/features/authz/permission-version.bumper.js +13 -11
  40. package/dist/features/authz/record-authorization.d.ts +8 -2
  41. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  42. package/dist/features/authz/record-authorization.js +8 -4
  43. package/dist/features/authz/role-binding.d.ts +3 -2
  44. package/dist/features/authz/role-binding.d.ts.map +1 -1
  45. package/dist/features/authz/role-binding.js +7 -8
  46. package/dist/features/company/company-hierarchy.service.d.ts +6 -1
  47. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  48. package/dist/features/company/company-hierarchy.service.js +17 -16
  49. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +11 -6
  50. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  51. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +24 -19
  52. package/dist/features/files/files.controller.d.ts +27 -0
  53. package/dist/features/files/files.controller.d.ts.map +1 -0
  54. package/dist/features/files/files.controller.js +94 -0
  55. package/dist/features/files/files.service.d.ts +87 -0
  56. package/dist/features/files/files.service.d.ts.map +1 -0
  57. package/dist/features/files/files.service.js +311 -0
  58. package/dist/features/framework-schemas.token.d.ts +8 -0
  59. package/dist/features/framework-schemas.token.d.ts.map +1 -0
  60. package/dist/features/framework-schemas.token.js +10 -0
  61. package/dist/features/identity-feature.module.d.ts +29 -1
  62. package/dist/features/identity-feature.module.d.ts.map +1 -1
  63. package/dist/features/identity-feature.module.js +168 -41
  64. package/dist/features/membership/membership-checker.d.ts +7 -1
  65. package/dist/features/membership/membership-checker.d.ts.map +1 -1
  66. package/dist/features/membership/membership-checker.js +9 -4
  67. package/dist/features/migration-ledger-check.d.ts +15 -0
  68. package/dist/features/migration-ledger-check.d.ts.map +1 -0
  69. package/dist/features/migration-ledger-check.js +26 -0
  70. package/dist/features/policy/policy.service.d.ts +2 -1
  71. package/dist/features/policy/policy.service.d.ts.map +1 -1
  72. package/dist/features/policy/policy.service.js +15 -0
  73. package/dist/features/scope/scope-closure.service.d.ts +8 -1
  74. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  75. package/dist/features/scope/scope-closure.service.js +22 -19
  76. package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
  77. package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
  78. package/dist/features/scope/scope-subtree.resolver.js +9 -4
  79. package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
  80. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
  81. package/dist/features/service-account/lock-service-accounts.js +4 -4
  82. package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
  83. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
  84. package/dist/features/service-account/no-expiry-key-report.js +17 -10
  85. package/dist/features/service-account/service-account.store.d.ts +8 -1
  86. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  87. package/dist/features/service-account/service-account.store.js +19 -14
  88. package/dist/features/share/share.service.d.ts +19 -2
  89. package/dist/features/share/share.service.d.ts.map +1 -1
  90. package/dist/features/share/share.service.js +36 -2
  91. package/dist/features/user/user.service.d.ts +7 -3
  92. package/dist/features/user/user.service.d.ts.map +1 -1
  93. package/dist/features/user/user.service.js +12 -4
  94. package/dist/index.d.ts +9 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +13 -1
  97. package/dist/objects.d.ts.map +1 -1
  98. package/dist/objects.js +2 -0
  99. package/dist/record-share.object.d.ts +1 -1
  100. package/dist/record-share.object.d.ts.map +1 -1
  101. package/dist/record-share.object.js +7 -3
  102. package/dist/schema.d.ts +15 -2
  103. package/dist/schema.d.ts.map +1 -1
  104. package/dist/schema.js +17 -2
  105. package/dist/service-account.object.d.ts +3 -2
  106. package/dist/service-account.object.d.ts.map +1 -1
  107. package/dist/service-account.object.js +4 -2
  108. package/dist/stored-file.object.d.ts +35 -0
  109. package/dist/stored-file.object.d.ts.map +1 -0
  110. package/dist/stored-file.object.js +94 -0
  111. package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
  112. package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
  113. package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
  114. package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
  115. package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
  116. package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
  117. package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
  118. package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
  119. package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
  120. package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
  121. package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
  122. package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
  123. package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
  124. package/migrations/1710000000022-CreateStoredFile.cjs +53 -0
  125. package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
  126. package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
  127. package/package.json +8 -4
@@ -1,3 +1,19 @@
1
+ import type { ObjectTypeInput } from '@astram/engine';
2
+ /** The auth-facts key universe for identity's objects plus a consumer's `extraObjects`. */
3
+ export interface IdentityAuthKeys {
4
+ /** Every action key: pass as `ShieldModule.forRoot({ identityActionKeys })`. */
5
+ readonly actionKeys: string[];
6
+ /** The control-plane subset: pass as `ShieldModule.forRoot({ identityControlPlaneKeys })`. */
7
+ readonly controlPlaneKeys: string[];
8
+ }
9
+ /**
10
+ * Build the auth-facts key universe from identity's objects plus `extraObjects` (pass the same
11
+ * array as `IdentityFeatureModule.forRoot({ extraObjects })`). Same registry bridge as the record
12
+ * authorization, so the keys match decide() and the permission catalog exactly.
13
+ */
14
+ export declare function buildIdentityAuthKeys(extraObjects?: readonly ObjectTypeInput[]): IdentityAuthKeys;
15
+ /** Identity's own action keys (no consumer types; see {@link buildIdentityAuthKeys}). */
1
16
  export declare const identityAuthActionKeys: readonly string[];
17
+ /** Identity's own control-plane keys (no consumer types; see {@link buildIdentityAuthKeys}). */
2
18
  export declare const identityControlPlaneKeys: readonly string[];
3
19
  //# sourceMappingURL=auth-facts-keys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-facts-keys.d.ts","sourceRoot":"","sources":["../src/auth-facts-keys.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAEZ,CAAC;AAEzC,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAGd,CAAC"}
1
+ {"version":3,"file":"auth-facts-keys.d.ts","sourceRoot":"","sources":["../src/auth-facts-keys.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,2FAA2F;AAC3F,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,gBAAgB,CAQlB;AAID,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAA4B,CAAC;AAEjF,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAkC,CAAC"}
@@ -1,24 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.identityControlPlaneKeys = exports.identityAuthActionKeys = void 0;
4
+ exports.buildIdentityAuthKeys = buildIdentityAuthKeys;
4
5
  /**
5
- * The framework identity permission-key universe, projected once from `identityObjects` via
6
+ * The framework identity permission-key universe, projected from `identityObjects` via
6
7
  * `@astram/guard`'s registry bridge. Exported for the consumer to pass into
7
8
  * `ShieldModule.forRoot({ auth: true, identityActionKeys, identityControlPlaneKeys })` — so the
8
9
  * auth-facts adapter (in guard) can expand `identity:*` grants + union root's control-plane keys
9
10
  * WITHOUT guard importing this package (the sanctioned graph is identity → guard).
10
11
  *
12
+ * A consumer that registers its own object types passes the SAME list it gives
13
+ * `IdentityFeatureModule.forRoot({ extraObjects })` to {@link buildIdentityAuthKeys} (KI #70), so
14
+ * `/auth/facts` covers the same key universe decide() and the permission catalog use.
15
+ *
11
16
  * ⚠ NO MODULE-INIT CYCLE: `identityObjects` is imported from the leaf `./objects.js` (NOT from
12
17
  * `./index.js`, which re-exports THIS file). So the array is fully initialized before the
13
- * `buildIdentityModuleRegistry(...)` call below runs at module-eval time.
18
+ * `buildIdentityAuthKeys()` call below runs at module-eval time.
14
19
  */
15
20
  const guard_1 = require("@astram/guard");
16
21
  const objects_js_1 = require("./objects.js");
17
- const registry = (0, guard_1.buildIdentityModuleRegistry)(objects_js_1.identityObjects);
18
- exports.identityAuthActionKeys = registry
19
- .listActions()
20
- .map((a) => a.permissionKey);
21
- exports.identityControlPlaneKeys = registry
22
- .listActions()
23
- .filter((a) => a.plane === 'control')
24
- .map((a) => a.permissionKey);
22
+ /**
23
+ * Build the auth-facts key universe from identity's objects plus `extraObjects` (pass the same
24
+ * array as `IdentityFeatureModule.forRoot({ extraObjects })`). Same registry bridge as the record
25
+ * authorization, so the keys match decide() and the permission catalog exactly.
26
+ */
27
+ function buildIdentityAuthKeys(extraObjects = []) {
28
+ const actions = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects, ...extraObjects]).listActions();
29
+ return {
30
+ actionKeys: actions.map((a) => a.permissionKey),
31
+ controlPlaneKeys: actions
32
+ .filter((a) => a.plane === 'control')
33
+ .map((a) => a.permissionKey),
34
+ };
35
+ }
36
+ const identityOnly = buildIdentityAuthKeys();
37
+ /** Identity's own action keys (no consumer types; see {@link buildIdentityAuthKeys}). */
38
+ exports.identityAuthActionKeys = identityOnly.actionKeys;
39
+ /** Identity's own control-plane keys (no consumer types; see {@link buildIdentityAuthKeys}). */
40
+ exports.identityControlPlaneKeys = identityOnly.controlPlaneKeys;
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * Mirrors `membership-posting.object.ts`'s shape (system, no trusted columns needed
15
15
  * beyond the PK) — the engine's `validateAccess` requires `table` + at least one
16
- * `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD `IDENTITY_ACTIONS`
17
- * — see `@astram/guard`'s `identity-actions.ts`'s `actionsFor('cross_tenant_grant')` ->
16
+ * `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD verbs: it opts out
17
+ * (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
18
18
  * `CROSS_TENANT_GRANT_ACTIONS` (`share`/`accept`/`revoke`).
19
19
  *
20
20
  * Ported from astram3's frozen
@@ -1 +1 @@
1
- {"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAOa,gBAAgB;IAE3B,IAAI,EAAG,MAAM,CAAC;IAGd,gBAAgB,EAAG,MAAM,CAAC;IAG1B,iBAAiB,EAAG,MAAM,CAAC;CAC5B"}
1
+ {"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBASa,gBAAgB;IAE3B,IAAI,EAAG,MAAM,CAAC;IAGd,gBAAgB,EAAG,MAAM,CAAC;IAG1B,iBAAiB,EAAG,MAAM,CAAC;CAC5B"}
@@ -11,8 +11,10 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.CrossTenantGrant = void 0;
13
13
  const engine_1 = require("@astram/engine");
14
- /** The physical schema `cross_tenant_grant` lives in (migration 1710000000011). */
15
- const SCHEMA = 'shield_authz';
14
+ const guard_1 = require("@astram/guard");
15
+ /** The authz-database token `cross_tenant_grant` lives in (migration 1710000000011); the engine
16
+ * resolves it at registration (ADR-0037). */
17
+ const SCHEMA = engine_1.FRAMEWORK_SCHEMA.authz;
16
18
  /**
17
19
  * `cross_tenant_grant` — an AUTHORITY-ONLY @ObjectType, never CRUD'd through the
18
20
  * RecordEngine pipe. Its sole purpose is to give `cross_tenant_grant` an entry in the
@@ -28,8 +30,8 @@ const SCHEMA = 'shield_authz';
28
30
  *
29
31
  * Mirrors `membership-posting.object.ts`'s shape (system, no trusted columns needed
30
32
  * beyond the PK) — the engine's `validateAccess` requires `table` + at least one
31
- * `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD `IDENTITY_ACTIONS`
32
- * — see `@astram/guard`'s `identity-actions.ts`'s `actionsFor('cross_tenant_grant')` ->
33
+ * `@Field`; the PK satisfies that. Action vocabulary is NOT the CRUD verbs: it opts out
34
+ * (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
33
35
  * `CROSS_TENANT_GRANT_ACTIONS` (`share`/`accept`/`revoke`).
34
36
  *
35
37
  * Ported from astram3's frozen
@@ -59,6 +61,8 @@ exports.CrossTenantGrant = CrossTenantGrant = __decorate([
59
61
  table: 'cross_tenant_grant',
60
62
  schema: SCHEMA,
61
63
  access: { plane: 'control', system: true },
64
+ crudActions: false,
65
+ actions: guard_1.CROSS_TENANT_GRANT_ACTIONS,
62
66
  }),
63
67
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'ctg_uuid' })
64
68
  ], CrossTenantGrant);
@@ -1,5 +1,6 @@
1
1
  import type { LabelResolver, LabelKind } from '@astram/core';
2
2
  import type { AuditQueryable } from './mariadb-audit-reader.js';
3
+ import { type IdentitySchemaNames } from '../../schema.js';
3
4
  /**
4
5
  * The id->human-label chokepoint (ADR-0012, design §2.2). A deleted/unknown
5
6
  * entity resolves to a FALLBACK label (never the id); a directory OUTAGE throws
@@ -7,7 +8,9 @@ import type { AuditQueryable } from './mariadb-audit-reader.js';
7
8
  */
8
9
  export declare class DirectoryLabelResolver implements LabelResolver {
9
10
  private readonly db;
10
- constructor(db: AuditQueryable);
11
+ private readonly schemas;
12
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
13
+ constructor(db: AuditQueryable, schemas?: IdentitySchemaNames);
11
14
  resolve(kind: LabelKind, id: string, _tenantId?: string): Promise<string>;
12
15
  private fallback;
13
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"directory-label-resolver.d.ts","sourceRoot":"","sources":["../../../src/features/audit/directory-label-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,aAAa;IAC9C,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,cAAc;IAEzC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B/E,OAAO,CAAC,QAAQ;CAMjB"}
1
+ {"version":3,"file":"directory-label-resolver.d.ts","sourceRoot":"","sources":["../../../src/features/audit/directory-label-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,aAAa;IAGxD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,mBAA+C;IAGrE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B/E,OAAO,CAAC,QAAQ;CAMjB"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DirectoryLabelResolver = void 0;
4
- const APP = 'shield_app';
4
+ const schema_js_1 = require("../../schema.js");
5
5
  /**
6
6
  * The id->human-label chokepoint (ADR-0012, design §2.2). A deleted/unknown
7
7
  * entity resolves to a FALLBACK label (never the id); a directory OUTAGE throws
@@ -9,23 +9,26 @@ const APP = 'shield_app';
9
9
  */
10
10
  class DirectoryLabelResolver {
11
11
  db;
12
- constructor(db) {
12
+ schemas;
13
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
14
+ constructor(db, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
13
15
  this.db = db;
16
+ this.schemas = schemas;
14
17
  }
15
18
  async resolve(kind, id, _tenantId) {
16
19
  if (!id)
17
20
  return this.fallback(kind);
18
21
  switch (kind) {
19
22
  case 'principal': {
20
- const rows = (await this.db.query(`SELECT u_username AS label FROM \`${APP}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [id]));
23
+ const rows = (await this.db.query(`SELECT u_username AS label FROM \`${this.schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [id]));
21
24
  return rows[0]?.label ?? this.fallback(kind);
22
25
  }
23
26
  case 'tenant': {
24
- const rows = (await this.db.query(`SELECT c_name AS label FROM \`${APP}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [id]));
27
+ const rows = (await this.db.query(`SELECT c_name AS label FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [id]));
25
28
  return rows[0]?.label ?? this.fallback(kind);
26
29
  }
27
30
  case 'role': {
28
- const rows = (await this.db.query(`SELECT r_name AS label FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [id]));
31
+ const rows = (await this.db.query(`SELECT r_name AS label FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [id]));
29
32
  return rows[0]?.label ?? this.fallback(kind);
30
33
  }
31
34
  default:
@@ -1,4 +1,4 @@
1
- import type { AuditReader, AuditEvent } from '@astram/core';
1
+ import { type AuditReader, type AuditEvent } from '@astram/core';
2
2
  export interface AuditQueryable {
3
3
  query(sql: string, params?: readonly unknown[]): Promise<unknown>;
4
4
  }
@@ -11,6 +11,10 @@ export interface AuditQueryable {
11
11
  export declare class MariaDbAuditReader implements AuditReader {
12
12
  private readonly db;
13
13
  private readonly schema;
14
+ /**
15
+ * @param opts.schema the audit database. Absent ⇒ the audit name resolved from
16
+ * `SHIELD_DB_NAMESPACE` here, once at construction (unset ⇒ the default audit database).
17
+ */
14
18
  constructor(db: AuditQueryable, opts?: {
15
19
  schema?: string;
16
20
  });
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-audit-reader.d.ts","sourceRoot":"","sources":["../../../src/features/audit/mariadb-audit-reader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAgBD;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,EAAE,EAAE,cAAc,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAKxD,IAAI,CAAC,KAAK,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAgClC;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAY1F;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,OAAO;CA+BhB"}
1
+ {"version":3,"file":"mariadb-audit-reader.d.ts","sourceRoot":"","sources":["../../../src/features/audit/mariadb-audit-reader.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAeD;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;OAGG;gBACS,EAAE,EAAE,cAAc,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAQxD,IAAI,CAAC,KAAK,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAgClC;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAY1F;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,OAAO;CA+BhB"}
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MariaDbAuditReader = void 0;
4
- const DEFAULT_SCHEMA = 'shield_audit';
4
+ // mariadb-audit-reader.ts
5
+ const core_1 = require("@astram/core");
5
6
  const ALL_TENANTS = '*';
6
7
  /**
7
8
  * The astram3 AuditReader over `shield_audit.audit_log` (design §2.2). Newest-first,
@@ -12,9 +13,16 @@ const ALL_TENANTS = '*';
12
13
  class MariaDbAuditReader {
13
14
  db;
14
15
  schema;
16
+ /**
17
+ * @param opts.schema the audit database. Absent ⇒ the audit name resolved from
18
+ * `SHIELD_DB_NAMESPACE` here, once at construction (unset ⇒ the default audit database).
19
+ */
15
20
  constructor(db, opts = {}) {
16
21
  this.db = db;
17
- this.schema = opts.schema ?? DEFAULT_SCHEMA;
22
+ this.schema =
23
+ opts.schema !== undefined
24
+ ? (0, core_1.assertSchemaName)(opts.schema, 'MariaDbAuditReader opts.schema')
25
+ : (0, core_1.resolveFrameworkSchemas)(process.env).audit;
18
26
  }
19
27
  async read(query) {
20
28
  const { where, params } = this.buildScopeClause(query);
@@ -1,5 +1,6 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import type { AccessBundleSpec } from '@astram/engine';
3
+ import { type IdentitySchemaNames } from '../../schema.js';
3
4
  /** The outcome of one reconcile pass. */
4
5
  export interface AccessBundleReconcileReport {
5
6
  readonly added: number;
@@ -24,9 +25,11 @@ export interface AccessBundleReconcileReport {
24
25
  export declare class AccessBundleReconcileJob {
25
26
  private readonly pool;
26
27
  private readonly bundles;
28
+ private readonly schemas;
27
29
  private readonly store;
28
30
  private running;
29
- constructor(pool: Pool, bundles: readonly AccessBundleSpec[]);
31
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
32
+ constructor(pool: Pool, bundles: readonly AccessBundleSpec[], schemas?: IdentitySchemaNames);
30
33
  /** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
31
34
  runOnce(_nowIso: string): Promise<AccessBundleReconcileReport>;
32
35
  /** The set of permission keys that are ACTIVE in the live catalog. */
@@ -1 +1 @@
1
- {"version":3,"file":"access-bundle-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKvD,yCAAyC;AACzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,wBAAwB;IAKjC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,OAAO,CAAS;gBAGL,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,gBAAgB,EAAE;IAKvD,6EAA6E;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAqDpE,sEAAsE;YACxD,wBAAwB;CAMvC"}
1
+ {"version":3,"file":"access-bundle-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,yCAAyC;AACzC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,wBAAwB;IAMjC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,OAAO,CAAS;IAExB,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,OAAO,GAAE,mBAA+C;IAK3E,6EAA6E;IACvE,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAqDpE,sEAAsE;YACxD,wBAAwB;CAMvC"}
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AccessBundleReconcileJob = void 0;
4
4
  const access_bundle_store_js_1 = require("./access-bundle-store.js");
5
- const AUTHZ = 'shield_authz';
5
+ const schema_js_1 = require("../../schema.js");
6
6
  /**
7
7
  * Reconciles the developer-declared `AccessBundleSpec[]` (from
8
8
  * `ENGINE_ALL_ACCESS_BUNDLES`) into `shield_authz.access_bundle` (+ `_permission`),
@@ -20,12 +20,15 @@ const AUTHZ = 'shield_authz';
20
20
  class AccessBundleReconcileJob {
21
21
  pool;
22
22
  bundles;
23
+ schemas;
23
24
  store;
24
25
  running = false;
25
- constructor(pool, bundles) {
26
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
27
+ constructor(pool, bundles, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
26
28
  this.pool = pool;
27
29
  this.bundles = bundles;
28
- this.store = new access_bundle_store_js_1.AccessBundleStore(pool);
30
+ this.schemas = schemas;
31
+ this.store = new access_bundle_store_js_1.AccessBundleStore(pool, schemas);
29
32
  }
30
33
  /** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
31
34
  async runOnce(_nowIso) {
@@ -77,7 +80,7 @@ class AccessBundleReconcileJob {
77
80
  }
78
81
  /** The set of permission keys that are ACTIVE in the live catalog. */
79
82
  async loadActivePermissionKeys() {
80
- const rows = (await this.pool.query(`SELECT perm_key FROM \`${AUTHZ}\`.\`permission\` WHERE perm_status = 'active'`));
83
+ const rows = (await this.pool.query(`SELECT perm_key FROM \`${this.schemas.authz}\`.\`permission\` WHERE perm_status = 'active'`));
81
84
  return new Set(rows.map((r) => r.perm_key));
82
85
  }
83
86
  }
@@ -1,5 +1,6 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import type { AccessBundleSpec } from '@astram/engine';
3
+ import { type IdentitySchemaNames } from '../../schema.js';
3
4
  /** A row from `shield_authz.access_bundle`, resolved to camelCase. */
4
5
  export interface AccessBundleRow {
5
6
  readonly uuid: string;
@@ -18,7 +19,9 @@ export interface AccessBundleRow {
18
19
  */
19
20
  export declare class AccessBundleStore {
20
21
  private readonly pool;
21
- constructor(pool: Pool);
22
+ private readonly schemas;
23
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
24
+ constructor(pool: Pool, schemas?: IdentitySchemaNames);
22
25
  /** All bundles (active + orphaned), for the reconciler's declared-vs-present diff. */
23
26
  listBundles(): Promise<AccessBundleRow[]>;
24
27
  /** Resolve a bundle's UUID by its business key. `null` when absent. */
@@ -1 +1 @@
1
- {"version":3,"file":"access-bundle-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIvD,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAChB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEvC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAsB/C,uEAAuE;IACjE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS5E;;;;;;OAMG;IACG,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAuBnF;;;OAGG;IACG,yBAAyB,IAAI,OAAO,CACxC,KAAK,CAAC;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC,CACH;IA4CD;;;;;;OAMG;IACG,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,GAAG,WAAW,CAAC;IAuCzF;;;OAGG;IACG,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BvF;;;;OAIG;IACG,YAAY,CAAC,kBAAkB,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAUhG"}
1
+ {"version":3,"file":"access-bundle-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-bundle-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CACxC;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAG1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAG3E,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAsB/C,uEAAuE;IACjE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS5E;;;;;;OAMG;IACG,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAuBnF;;;OAGG;IACG,yBAAyB,IAAI,OAAO,CACxC,KAAK,CAAC;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC,CACH;IA4CD;;;;;;OAMG;IACG,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,GAAG,WAAW,CAAC;IAuCzF;;;OAGG;IACG,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BvF;;;;OAIG;IACG,YAAY,CAAC,kBAAkB,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAUhG"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AccessBundleStore = void 0;
4
- const AUTHZ = 'shield_authz';
4
+ const schema_js_1 = require("../../schema.js");
5
5
  /**
6
6
  * Raw-SQL (parameterized, NO ORM) CRUD over `shield_authz.access_bundle` +
7
7
  * `access_bundle_permission`, mirroring `mariadb-catalog-store.ts`.
@@ -11,13 +11,16 @@ const AUTHZ = 'shield_authz';
11
11
  */
12
12
  class AccessBundleStore {
13
13
  pool;
14
- constructor(pool) {
14
+ schemas;
15
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
16
+ constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
15
17
  this.pool = pool;
18
+ this.schemas = schemas;
16
19
  }
17
20
  /** All bundles (active + orphaned), for the reconciler's declared-vs-present diff. */
18
21
  async listBundles() {
19
22
  const rows = (await this.pool.query(`SELECT ab_uuid, ab_key, ab_module, ab_label, ab_description, ab_status
20
- FROM \`${AUTHZ}\`.\`access_bundle\``));
23
+ FROM \`${this.schemas.authz}\`.\`access_bundle\``));
21
24
  return rows.map((r) => ({
22
25
  uuid: r.ab_uuid,
23
26
  key: r.ab_key,
@@ -29,7 +32,7 @@ class AccessBundleStore {
29
32
  }
30
33
  /** Resolve a bundle's UUID by its business key. `null` when absent. */
31
34
  async resolveBundleUuid(module, key) {
32
- const rows = (await this.pool.query(`SELECT ab_uuid FROM \`${AUTHZ}\`.\`access_bundle\`
35
+ const rows = (await this.pool.query(`SELECT ab_uuid FROM \`${this.schemas.authz}\`.\`access_bundle\`
33
36
  WHERE ab_module = ? AND ab_key = ? LIMIT 1`, [module, key]));
34
37
  return rows[0]?.ab_uuid ?? null;
35
38
  }
@@ -42,14 +45,14 @@ class AccessBundleStore {
42
45
  */
43
46
  async resolveActiveBundleKeys(bundleKey) {
44
47
  const rows = (await this.pool.query(`SELECT abp.abp_perm_key AS perm_key
45
- FROM \`${AUTHZ}\`.\`access_bundle\` ab
46
- JOIN \`${AUTHZ}\`.\`access_bundle_permission\` abp
48
+ FROM \`${this.schemas.authz}\`.\`access_bundle\` ab
49
+ JOIN \`${this.schemas.authz}\`.\`access_bundle_permission\` abp
47
50
  ON abp.abp_bundle_uuid = ab.ab_uuid
48
51
  WHERE ab.ab_key = ? AND ab.ab_status = 'active'
49
52
  ORDER BY abp.abp_perm_key`, [bundleKey]));
50
53
  if (rows.length === 0) {
51
54
  // Distinguish "no such active bundle" from "active bundle with zero keys".
52
- const exists = (await this.pool.query(`SELECT 1 FROM \`${AUTHZ}\`.\`access_bundle\`
55
+ const exists = (await this.pool.query(`SELECT 1 FROM \`${this.schemas.authz}\`.\`access_bundle\`
53
56
  WHERE ab_key = ? AND ab_status = 'active' LIMIT 1`, [bundleKey]));
54
57
  if (exists.length === 0)
55
58
  return null;
@@ -64,8 +67,8 @@ class AccessBundleStore {
64
67
  async listActiveBundlesWithKeys() {
65
68
  const rows = (await this.pool.query(`SELECT ab.ab_key AS bundle_key, ab.ab_module AS module, ab.ab_label AS label,
66
69
  ab.ab_description AS description, abp.abp_perm_key AS perm_key
67
- FROM \`${AUTHZ}\`.\`access_bundle\` ab
68
- LEFT JOIN \`${AUTHZ}\`.\`access_bundle_permission\` abp
70
+ FROM \`${this.schemas.authz}\`.\`access_bundle\` ab
71
+ LEFT JOIN \`${this.schemas.authz}\`.\`access_bundle_permission\` abp
69
72
  ON abp.abp_bundle_uuid = ab.ab_uuid
70
73
  WHERE ab.ab_status = 'active'
71
74
  ORDER BY ab.ab_module, ab.ab_key, abp.abp_perm_key`));
@@ -97,7 +100,7 @@ class AccessBundleStore {
97
100
  async upsertBundle(spec) {
98
101
  const description = spec.description ?? null;
99
102
  const existing = (await this.pool.query(`SELECT ab_uuid, ab_label, ab_description, ab_status
100
- FROM \`${AUTHZ}\`.\`access_bundle\`
103
+ FROM \`${this.schemas.authz}\`.\`access_bundle\`
101
104
  WHERE ab_module = ? AND ab_key = ? LIMIT 1`, [spec.module, spec.key]));
102
105
  const cur = existing[0];
103
106
  if (cur) {
@@ -106,12 +109,12 @@ class AccessBundleStore {
106
109
  cur.ab_status === 'active';
107
110
  if (same)
108
111
  return 'unchanged';
109
- await this.pool.query(`UPDATE \`${AUTHZ}\`.\`access_bundle\`
112
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`access_bundle\`
110
113
  SET ab_label = ?, ab_description = ?, ab_status = 'active'
111
114
  WHERE ab_uuid = ?`, [spec.label, description, cur.ab_uuid]);
112
115
  return 'updated';
113
116
  }
114
- await this.pool.query(`INSERT INTO \`${AUTHZ}\`.\`access_bundle\`
117
+ await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`access_bundle\`
115
118
  (ab_uuid, ab_key, ab_module, ab_label, ab_description, ab_status)
116
119
  VALUES (UUID(), ?, ?, ?, ?, 'active')`, [spec.key, spec.module, spec.label, description]);
117
120
  return 'inserted';
@@ -121,7 +124,7 @@ class AccessBundleStore {
121
124
  * insert the missing ones. Idempotent — a re-sync with the same set is a no-op.
122
125
  */
123
126
  async syncBundlePermissions(bundleUuid, keys) {
124
- const existing = (await this.pool.query(`SELECT abp_perm_key FROM \`${AUTHZ}\`.\`access_bundle_permission\`
127
+ const existing = (await this.pool.query(`SELECT abp_perm_key FROM \`${this.schemas.authz}\`.\`access_bundle_permission\`
125
128
  WHERE abp_bundle_uuid = ?`, [bundleUuid]));
126
129
  const have = new Set(existing.map((r) => r.abp_perm_key));
127
130
  const want = new Set(keys);
@@ -129,11 +132,11 @@ class AccessBundleStore {
129
132
  const toInsert = [...want].filter((k) => !have.has(k));
130
133
  if (toDelete.length > 0) {
131
134
  const ph = toDelete.map(() => '?').join(', ');
132
- await this.pool.query(`DELETE FROM \`${AUTHZ}\`.\`access_bundle_permission\`
135
+ await this.pool.query(`DELETE FROM \`${this.schemas.authz}\`.\`access_bundle_permission\`
133
136
  WHERE abp_bundle_uuid = ? AND abp_perm_key IN (${ph})`, [bundleUuid, ...toDelete]);
134
137
  }
135
138
  for (const key of toInsert) {
136
- await this.pool.query(`INSERT INTO \`${AUTHZ}\`.\`access_bundle_permission\`
139
+ await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`access_bundle_permission\`
137
140
  (abp_uuid, abp_bundle_uuid, abp_perm_key)
138
141
  VALUES (UUID(), ?, ?)`, [bundleUuid, key]);
139
142
  }
@@ -145,7 +148,7 @@ class AccessBundleStore {
145
148
  */
146
149
  async markOrphaned(modKeysNotDeclared) {
147
150
  for (const [module, key] of modKeysNotDeclared) {
148
- await this.pool.query(`UPDATE \`${AUTHZ}\`.\`access_bundle\`
151
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`access_bundle\`
149
152
  SET ab_status = 'orphaned'
150
153
  WHERE ab_module = ? AND ab_key = ?`, [module, key]);
151
154
  }
@@ -1,5 +1,6 @@
1
1
  import { type OnApplicationBootstrap } from '@nestjs/common';
2
2
  import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
3
+ import { type RecordAuthorizationInputs } from '@astram/guard';
3
4
  import type { ObjectType } from '@astram/engine';
4
5
  import { CatalogReconcileJob } from './catalog-reconcile.job.js';
5
6
  import { AccessBundleReconcileJob } from './access-bundle-reconcile.job.js';
@@ -23,8 +24,10 @@ export declare class CatalogBootstrapProvider implements OnApplicationBootstrap
23
24
  private readonly accessBundleJob;
24
25
  private readonly scopeClosure;
25
26
  private readonly registrations;
27
+ private readonly recordAuthorization;
28
+ private readonly logger;
26
29
  private timer;
27
- constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector, job: CatalogReconcileJob, accessBundleJob: AccessBundleReconcileJob, scopeClosure: ScopeClosureService, registrations: readonly ObjectType[]);
30
+ constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector, job: CatalogReconcileJob, accessBundleJob: AccessBundleReconcileJob, scopeClosure: ScopeClosureService, registrations: readonly ObjectType[], recordAuthorization: RecordAuthorizationInputs);
28
31
  onApplicationBootstrap(): Promise<void>;
29
32
  /** Rebuild the scope closure; a failure is logged loudly (never silently swallowed) but does
30
33
  * not abort boot or a cron tick — the reconcile is idempotent and retried next interval. */
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAO5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxE;;;;;;;;;;GAUG;AACH,qBACa,wBAAyB,YAAW,sBAAsB;IAInE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAVhC,OAAO,CAAC,KAAK,CAA+B;gBAGzB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,mBAAmB,EACxB,eAAe,EAAE,wBAAwB,EACzC,YAAY,EAAE,mBAAmB,EAEjC,aAAa,EAAE,SAAS,UAAU,EAAE;IAGjD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC7C;iGAC6F;YAC/E,qBAAqB;CAOpC"}
1
+ {"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8B,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxE;;;;;;;;;;GAUG;AACH,qBACa,wBAAyB,YAAW,sBAAsB;IAKnE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAbtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6C;IACpE,OAAO,CAAC,KAAK,CAA+B;gBAGzB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,mBAAmB,EACxB,eAAe,EAAE,wBAAwB,EACzC,YAAY,EAAE,mBAAmB,EAEjC,aAAa,EAAE,SAAS,UAAU,EAAE,EAEpC,mBAAmB,EAAE,yBAAyB;IAG3D,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAyE7C;iGAC6F;YAC/E,qBAAqB;CAOpC"}
@@ -11,6 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
+ var CatalogBootstrapProvider_1;
14
15
  Object.defineProperty(exports, "__esModule", { value: true });
15
16
  exports.CatalogBootstrapProvider = void 0;
16
17
  // Ported from astram3 `server/src/features/authz/catalog-bootstrap.provider.ts`.
@@ -37,7 +38,7 @@ const RECONCILE_INTERVAL_MS = 60 * 60 * 1000; // hourly; honors DISABLE_ALL_CRON
37
38
  * health controller injects — so verify, the post-boot pass, and the endpoint all share
38
39
  * one job. This provider owns only the boot verify + the schedule.
39
40
  */
40
- let CatalogBootstrapProvider = class CatalogBootstrapProvider {
41
+ let CatalogBootstrapProvider = CatalogBootstrapProvider_1 = class CatalogBootstrapProvider {
41
42
  discovery;
42
43
  scanner;
43
44
  reflector;
@@ -45,8 +46,10 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
45
46
  accessBundleJob;
46
47
  scopeClosure;
47
48
  registrations;
49
+ recordAuthorization;
50
+ logger = new common_1.Logger(CatalogBootstrapProvider_1.name);
48
51
  timer = null;
49
- constructor(discovery, scanner, reflector, job, accessBundleJob, scopeClosure, registrations) {
52
+ constructor(discovery, scanner, reflector, job, accessBundleJob, scopeClosure, registrations, recordAuthorization) {
50
53
  this.discovery = discovery;
51
54
  this.scanner = scanner;
52
55
  this.reflector = reflector;
@@ -54,11 +57,38 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
54
57
  this.accessBundleJob = accessBundleJob;
55
58
  this.scopeClosure = scopeClosure;
56
59
  this.registrations = registrations;
60
+ this.recordAuthorization = recordAuthorization;
57
61
  }
58
62
  async onApplicationBootstrap() {
59
- const registry = (0, guard_1.buildIdentityModuleRegistry)(this.registrations);
63
+ // ONE key universe (KI #70): verify against the registry decide(), the grant ceiling and the
64
+ // catalog reconcile job all use (identity objects + the consumer's `extraObjects`).
65
+ const registry = this.recordAuthorization.registry;
60
66
  const inventoryScanner = new guard_1.OperationInventoryScanner(this.discovery, this.scanner, this.reflector);
61
- const inventory = inventoryScanner.scan(MODULE_KEY);
67
+ const scanned = inventoryScanner.scan(MODULE_KEY);
68
+ // A type registered with `EngineModule.forFeature` but not passed as `extraObjects` is
69
+ // unknown to decide() (`resource_not_registered`), so every gated access to it is denied
70
+ // and its keys are not catalogued. Before KI #70 verify ran over the engine registrations,
71
+ // so such routes booted; keep them booting (no new boot failure for existing consumers) but
72
+ // say loudly that they can never be allowed.
73
+ const engineOnly = this.registrations.filter((r) => registry.resolveObjectType(r.typeKey) === undefined);
74
+ let inventory = scanned;
75
+ if (engineOnly.length > 0) {
76
+ const engineOnlyKeys = new Set((0, guard_1.buildIdentityModuleRegistry)(engineOnly)
77
+ .listActions()
78
+ .map((a) => a.permissionKey));
79
+ const denied = scanned.operations.filter((op) => op.permissionKey !== undefined && engineOnlyKeys.has(op.permissionKey));
80
+ this.logger.warn(`object type(s) ${engineOnly.map((r) => r.typeKey).join(', ')} are registered with ` +
81
+ 'EngineModule.forFeature but not in IdentityFeatureModule.forRoot({ extraObjects }): ' +
82
+ 'decide() denies every gated access to them and their keys are not catalogued' +
83
+ (denied.length > 0
84
+ ? `; always-denied routes: ${denied.map((op) => op.operationId).join(', ')}`
85
+ : '') +
86
+ '. Add them to extraObjects.');
87
+ inventory = {
88
+ ...scanned,
89
+ operations: scanned.operations.filter((op) => !denied.includes(op)),
90
+ };
91
+ }
62
92
  // Fail-closed (ADR-0002): a route that can't be gated aborts boot.
63
93
  new guard_1.CatalogReconciler().verifyGatable(inventory, registry);
64
94
  // Mutating reconcile runs AFTER boot (not blocking, not fail-closed).
@@ -97,13 +127,14 @@ let CatalogBootstrapProvider = class CatalogBootstrapProvider {
97
127
  }
98
128
  };
99
129
  exports.CatalogBootstrapProvider = CatalogBootstrapProvider;
100
- exports.CatalogBootstrapProvider = CatalogBootstrapProvider = __decorate([
130
+ exports.CatalogBootstrapProvider = CatalogBootstrapProvider = CatalogBootstrapProvider_1 = __decorate([
101
131
  (0, common_1.Injectable)(),
102
132
  __param(6, (0, common_1.Inject)(engine_1.ENGINE_ALL_REGISTRATIONS)),
133
+ __param(7, (0, common_1.Inject)(guard_1.SHIELD_RECORD_AUTHORIZATION)),
103
134
  __metadata("design:paramtypes", [core_1.DiscoveryService,
104
135
  core_1.MetadataScanner,
105
136
  core_1.Reflector,
106
137
  catalog_reconcile_job_js_1.CatalogReconcileJob,
107
138
  access_bundle_reconcile_job_js_1.AccessBundleReconcileJob,
108
- scope_closure_service_js_1.ScopeClosureService, Array])
139
+ scope_closure_service_js_1.ScopeClosureService, Array, Object])
109
140
  ], CatalogBootstrapProvider);
@@ -1,6 +1,7 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import { type ReconcileReport } from '@astram/guard';
3
3
  import type { ModuleRegistry } from '@astram/core';
4
+ import type { IdentitySchemaNames } from '../../schema.js';
4
5
  /**
5
6
  * Single-runner wrapper around the framework CatalogReconciler.reconcile (the mutating
6
7
  * half). Called once shortly after boot and on a cron. Holds the latest report for
@@ -15,7 +16,8 @@ export declare class CatalogReconcileJob {
15
16
  private readonly store;
16
17
  private latest;
17
18
  private running;
18
- constructor(pool: Pool, registry: ModuleRegistry);
19
+ /** @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace). */
20
+ constructor(pool: Pool, registry: ModuleRegistry, schemas?: IdentitySchemaNames);
19
21
  /** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
20
22
  runOnce(nowIso: string): Promise<ReconcileReport | null>;
21
23
  getLatest(): ReconcileReport | null;
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAQ5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAP3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,OAAO,CAAS;gBAGtB,IAAI,EAAE,IAAI,EACO,QAAQ,EAAE,cAAc;IAK3C,6EAA6E;IACvE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiB9D,SAAS,IAAI,eAAe,GAAG,IAAI;CAGpC"}
1
+ {"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAS5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,OAAO,CAAS;IAExB,mGAAmG;gBAEjG,IAAI,EAAE,IAAI,EACO,QAAQ,EAAE,cAAc,EACzC,OAAO,CAAC,EAAE,mBAAmB;IAK/B,6EAA6E;IACvE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiB9D,SAAS,IAAI,eAAe,GAAG,IAAI;CAGpC"}
@@ -17,9 +17,10 @@ class CatalogReconcileJob {
17
17
  store;
18
18
  latest = null;
19
19
  running = false;
20
- constructor(pool, registry) {
20
+ /** @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace). */
21
+ constructor(pool, registry, schemas) {
21
22
  this.registry = registry;
22
- this.store = new mariadb_catalog_store_js_1.MariaDbCatalogStore(pool);
23
+ this.store = new mariadb_catalog_store_js_1.MariaDbCatalogStore(pool, schemas);
23
24
  }
24
25
  /** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
25
26
  async runOnce(nowIso) {