@astram/identity 0.18.1 → 0.19.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 (52) hide show
  1. package/dist/branch.object.d.ts.map +1 -1
  2. package/dist/branch.object.js +20 -0
  3. package/dist/company-branch.object.d.ts.map +1 -1
  4. package/dist/company-branch.object.js +20 -0
  5. package/dist/company-department.object.d.ts.map +1 -1
  6. package/dist/company-department.object.js +20 -0
  7. package/dist/company-membership.object.d.ts.map +1 -1
  8. package/dist/company-membership.object.js +20 -0
  9. package/dist/company.object.d.ts.map +1 -1
  10. package/dist/company.object.js +21 -0
  11. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  12. package/dist/cross-tenant-grant.object.js +17 -0
  13. package/dist/department.object.d.ts.map +1 -1
  14. package/dist/department.object.js +20 -0
  15. package/dist/features/authz/app-release.d.ts +12 -0
  16. package/dist/features/authz/app-release.d.ts.map +1 -0
  17. package/dist/features/authz/app-release.js +26 -0
  18. package/dist/features/authz/catalog-bootstrap.provider.d.ts +16 -0
  19. package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
  20. package/dist/features/authz/catalog-bootstrap.provider.js +67 -3
  21. package/dist/features/authz/catalog-reconcile.job.d.ts +11 -3
  22. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  23. package/dist/features/authz/catalog-reconcile.job.js +17 -4
  24. package/dist/features/authz/grant-admin.controller.d.ts +4 -0
  25. package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
  26. package/dist/features/authz/grant-admin.service.d.ts +9 -0
  27. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  28. package/dist/features/authz/grant-admin.service.js +8 -1
  29. package/dist/features/authz/mariadb-catalog-store.d.ts +12 -2
  30. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  31. package/dist/features/authz/mariadb-catalog-store.js +64 -5
  32. package/dist/features/identity-feature.module.d.ts +8 -0
  33. package/dist/features/identity-feature.module.d.ts.map +1 -1
  34. package/dist/features/identity-feature.module.js +26 -13
  35. package/dist/membership-posting.object.d.ts.map +1 -1
  36. package/dist/membership-posting.object.js +22 -0
  37. package/dist/record-share.object.d.ts.map +1 -1
  38. package/dist/record-share.object.js +12 -0
  39. package/dist/role.object.d.ts.map +1 -1
  40. package/dist/role.object.js +21 -0
  41. package/dist/security-policy.object.d.ts.map +1 -1
  42. package/dist/security-policy.object.js +21 -0
  43. package/dist/service-account.object.d.ts.map +1 -1
  44. package/dist/service-account.object.js +25 -0
  45. package/dist/shared-resource.object.d.ts.map +1 -1
  46. package/dist/shared-resource.object.js +20 -0
  47. package/dist/stored-file.object.d.ts.map +1 -1
  48. package/dist/stored-file.object.js +20 -0
  49. package/dist/user.object.d.ts +1 -0
  50. package/dist/user.object.d.ts.map +1 -1
  51. package/dist/user.object.js +39 -1
  52. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IAGpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAGrE,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwBlD,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB7E,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAQzD"}
1
+ {"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AASpF,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKjE;AAUD;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IAGpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAGrE,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAqCxD,mGAAmG;IAC7F,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyB7E,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1D,gGAAgG;IAC1F,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzE,yFAAyF;IACnF,UAAU,CAAC,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAUpE"}
@@ -1,11 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MariaDbCatalogStore = void 0;
4
+ exports.isoFromEpochSeconds = isoFromEpochSeconds;
4
5
  const schema_js_1 = require("../../schema.js");
6
+ /*
7
+ * TIMESTAMP columns cross the driver as epoch seconds (`FROM_UNIXTIME(?)` in, `UNIX_TIMESTAMP(col)`
8
+ * out), never as a JS `Date`: the authz pool sets no driver `timezone`, so a `Date` would be sent as
9
+ * Node's local wall-clock and stored shifted whenever the process TZ differs from the DB session
10
+ * `time_zone` (a same-driver read would hide it). Epoch seconds are timezone-independent.
11
+ */
12
+ /** `UNIX_TIMESTAMP(col)` as the driver returns it (number, bigint or string) → ISO; NULL ⇒ null. */
13
+ function isoFromEpochSeconds(value) {
14
+ if (value === null || value === undefined)
15
+ return null;
16
+ const seconds = Number(value);
17
+ if (!Number.isFinite(seconds))
18
+ return null;
19
+ return new Date(seconds * 1000).toISOString();
20
+ }
21
+ /** An ISO timestamp from the reconciler as whole epoch seconds for `FROM_UNIXTIME(?)`; absent ⇒ NULL. */
22
+ function epochSecondsParam(iso) {
23
+ if (iso === null || iso === undefined)
24
+ return null;
25
+ const ms = Date.parse(iso);
26
+ if (Number.isNaN(ms))
27
+ throw new Error('catalog store: invalid timestamp');
28
+ return Math.floor(ms / 1000);
29
+ }
5
30
  /**
6
31
  * CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
7
32
  * permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
8
33
  * itself (ADR-0003).
34
+ *
35
+ * G-027: `perm_first_seen_at` / `perm_first_seen_version` are written ONLY by the insert in
36
+ * {@link upsertPermission} (insert-only, `INSERT … SELECT … WHERE NOT EXISTS`); no other
37
+ * statement here names them, so an existing row keeps its first values for good.
9
38
  */
10
39
  class MariaDbCatalogStore {
11
40
  pool;
@@ -16,7 +45,10 @@ class MariaDbCatalogStore {
16
45
  this.schemas = schemas;
17
46
  }
18
47
  async listPermissions() {
19
- const rows = (await this.pool.query(`SELECT perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status
48
+ const rows = (await this.pool.query(`SELECT perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status,
49
+ perm_description, perm_caution,
50
+ UNIX_TIMESTAMP(perm_first_seen_at) AS perm_first_seen_at, perm_first_seen_version,
51
+ UNIX_TIMESTAMP(perm_last_activated_at) AS perm_last_activated_at
20
52
  FROM \`${this.schemas.authz}\`.\`permission\``));
21
53
  return rows.map((r) => ({
22
54
  permissionKey: r.perm_key,
@@ -26,12 +58,19 @@ class MariaDbCatalogStore {
26
58
  label: r.perm_label,
27
59
  tier: r.perm_tier,
28
60
  status: r.perm_status === 'orphaned' ? 'orphaned' : 'active',
61
+ description: r.perm_description ?? null,
62
+ caution: r.perm_caution ?? null,
63
+ firstSeenAt: isoFromEpochSeconds(r.perm_first_seen_at),
64
+ firstSeenVersion: r.perm_first_seen_version ?? null,
65
+ lastActivatedAt: isoFromEpochSeconds(r.perm_last_activated_at),
29
66
  }));
30
67
  }
68
+ /** Insert-only: a key that already exists matches no row (`WHERE NOT EXISTS`) and is untouched. */
31
69
  async upsertPermission(row) {
32
70
  const res = (await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`permission\`
33
- (perm_uuid, perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status)
34
- SELECT UUID(), ?, ?, ?, ?, ?, ?, 'active'
71
+ (perm_uuid, perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status,
72
+ perm_description, perm_caution, perm_first_seen_at, perm_first_seen_version, perm_last_activated_at)
73
+ SELECT UUID(), ?, ?, ?, ?, ?, ?, 'active', ?, ?, FROM_UNIXTIME(?), ?, FROM_UNIXTIME(?)
35
74
  WHERE NOT EXISTS (SELECT 1 FROM \`${this.schemas.authz}\`.\`permission\` WHERE perm_key = ?)`, [
36
75
  row.permissionKey,
37
76
  row.moduleKey,
@@ -39,6 +78,11 @@ class MariaDbCatalogStore {
39
78
  row.action,
40
79
  row.label,
41
80
  row.tier,
81
+ row.description ?? null,
82
+ row.caution ?? null,
83
+ epochSecondsParam(row.firstSeenAt),
84
+ row.firstSeenVersion ?? null,
85
+ epochSecondsParam(row.lastActivatedAt),
42
86
  row.permissionKey,
43
87
  ]));
44
88
  return Number(res?.affectedRows ?? 0) > 0;
@@ -49,11 +93,26 @@ class MariaDbCatalogStore {
49
93
  const ph = keys.map(() => '?').join(', ');
50
94
  await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'orphaned' WHERE perm_key IN (${ph})`, [...keys]);
51
95
  }
52
- async markActive(keys) {
96
+ /** Orphaned → active; with `nowIso` also stamps `perm_last_activated_at` (never first seen). */
97
+ async markActive(keys, nowIso) {
53
98
  if (keys.length === 0)
54
99
  return;
55
100
  const ph = keys.map(() => '?').join(', ');
56
- await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'active' WHERE perm_key IN (${ph})`, [...keys]);
101
+ if (nowIso === undefined) {
102
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'active' WHERE perm_key IN (${ph})`, [...keys]);
103
+ return;
104
+ }
105
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\`
106
+ SET perm_status = 'active', perm_last_activated_at = FROM_UNIXTIME(?)
107
+ WHERE perm_key IN (${ph})`, [epochSecondsParam(nowIso), ...keys]);
108
+ }
109
+ /** G-027: the code's text replaces the stored text, one parameterized UPDATE per key. */
110
+ async updateText(rows) {
111
+ for (const row of rows) {
112
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\`
113
+ SET perm_label = ?, perm_description = ?, perm_caution = ?
114
+ WHERE perm_key = ?`, [row.label, row.description ?? null, row.caution ?? null, row.permissionKey]);
115
+ }
57
116
  }
58
117
  }
59
118
  exports.MariaDbCatalogStore = MariaDbCatalogStore;
@@ -2,6 +2,7 @@ import { type DynamicModule } from '@nestjs/common';
2
2
  import { type FrameworkSchemas } from '@astram/core';
3
3
  import { type EngineDbConfig, type ObjectTypeInput } from '@astram/engine';
4
4
  import { type InviteOptions } from './user/invite.config.js';
5
+ import { type TypeSchemaSource } from './policy/policy.service.js';
5
6
  /**
6
7
  * The boot check behind {@link IDENTITY_DECISION_AUTHORITY_CHECK}: with this module wired, the
7
8
  * request path must decide with the authority guard built from this module's inputs. A missing
@@ -31,6 +32,13 @@ export declare function assertFrameworkDatabaseAgrees(identityDatabase: string,
31
32
  readonly database?: string;
32
33
  };
33
34
  } | null | undefined): true;
35
+ /**
36
+ * The guided rule-builder schema source (`GET /identity/record-policies/schema`): per registered
37
+ * type its label (G-027 spec §5K2: the declared `ObjectType.label`, else the `typeKey`), its
38
+ * action vocabulary and its fields (engine `reflectObjectType` keeps field `type`, which the core
39
+ * registry descriptor drops). Field labels stay `f.key` (not in G-027 step 1).
40
+ */
41
+ export declare function policySchemaSourceOf(inputs: readonly ObjectTypeInput[], actionsByType: (typeKey: string) => readonly string[] | undefined): TypeSchemaSource[];
34
42
  /**
35
43
  * Map guard's DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). The
36
44
  * database and default user follow `schemas` (the names `forRoot` resolved or was given); an
@@ -1 +1 @@
1
- {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAaL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAyDxB,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA2G/E;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAwBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAyBhB;AA2BD,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,EACrB,MAAM,GACP,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,aAAa,CAAC;KACnB,GAAG,aAAa;CAmqBvB"}
1
+ {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAaL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA0DxB,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA0C/E,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAkElF;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAwBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,GAChE,gBAAgB,EAAE,CAapB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAyBhB;AA2BD,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,EACrB,MAAM,GACP,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,aAAa,CAAC;KACnB,GAAG,aAAa;CAgqBvB"}
@@ -11,6 +11,7 @@ exports.IdentityFeatureModule = void 0;
11
11
  exports.assertRequestPathUsesIdentityAuthorization = assertRequestPathUsesIdentityAuthorization;
12
12
  exports.assertFrameworkSchemasAgree = assertFrameworkSchemasAgree;
13
13
  exports.assertFrameworkDatabaseAgrees = assertFrameworkDatabaseAgrees;
14
+ exports.policySchemaSourceOf = policySchemaSourceOf;
14
15
  exports.engineDbFromEnv = engineDbFromEnv;
15
16
  const common_1 = require("@nestjs/common");
16
17
  const core_1 = require("@nestjs/core");
@@ -64,6 +65,7 @@ const service_account_store_js_1 = require("./service-account/service-account.st
64
65
  const service_account_service_js_1 = require("./service-account/service-account.service.js");
65
66
  const service_account_controller_js_1 = require("./service-account/service-account.controller.js");
66
67
  const catalog_reconcile_job_js_1 = require("./authz/catalog-reconcile.job.js");
68
+ const app_release_js_1 = require("./authz/app-release.js");
67
69
  const access_bundle_reconcile_job_js_1 = require("./authz/access-bundle-reconcile.job.js");
68
70
  const catalog_bootstrap_provider_js_1 = require("./authz/catalog-bootstrap.provider.js");
69
71
  const authz_pool_lifecycle_js_1 = require("./authz/authz-pool-lifecycle.js");
@@ -180,6 +182,26 @@ function assertFrameworkDatabaseAgrees(identityDatabase, guardConfig) {
180
182
  `${guardDatabase}. Pass the same names (or none) to ShieldModule and ` +
181
183
  'IdentityFeatureModule, and one APP_DB_NAME.');
182
184
  }
185
+ /**
186
+ * The guided rule-builder schema source (`GET /identity/record-policies/schema`): per registered
187
+ * type its label (G-027 spec §5K2: the declared `ObjectType.label`, else the `typeKey`), its
188
+ * action vocabulary and its fields (engine `reflectObjectType` keeps field `type`, which the core
189
+ * registry descriptor drops). Field labels stay `f.key` (not in G-027 step 1).
190
+ */
191
+ function policySchemaSourceOf(inputs, actionsByType) {
192
+ return inputs.map((input) => {
193
+ const reg = typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input;
194
+ return {
195
+ typeKey: reg.typeKey,
196
+ label: reg.label ?? reg.typeKey,
197
+ actions: actionsByType(reg.typeKey) ?? [],
198
+ fields: reg.fields.map((f) => {
199
+ const ref = (0, engine_1.referencedTypeOf)(f);
200
+ return { key: f.key, type: f.type, ...(ref ? { references: ref } : {}) };
201
+ }),
202
+ };
203
+ });
204
+ }
183
205
  /**
184
206
  * Map guard's DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). The
185
207
  * database and default user follow `schemas` (the names `forRoot` resolved or was given); an
@@ -440,18 +462,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
440
462
  // let the delegated-admin ceiling + validateGuidedPolicy gate what a caller may actually
441
463
  // author. A consumer's own types (extraObjects) would extend this the same way (future).
442
464
  // Include the consumer's `extraObjects` so their OWNED types are policy-authorable too.
443
- const schemaSource = [...objects_js_1.identityObjects, ...extraObjects].map((input) => {
444
- const reg = typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input;
445
- return {
446
- typeKey: reg.typeKey,
447
- label: reg.typeKey,
448
- actions: actionsByType(reg.typeKey) ?? [],
449
- fields: reg.fields.map((f) => {
450
- const ref = (0, engine_1.referencedTypeOf)(f);
451
- return { key: f.key, type: f.type, ...(ref ? { references: ref } : {}) };
452
- }),
453
- };
454
- });
465
+ const schemaSource = policySchemaSourceOf([...objects_js_1.identityObjects, ...extraObjects], actionsByType);
455
466
  const policyService = new policy_service_js_1.PolicyService({
456
467
  // The shared registry (KI #57): the schema below already offers the consumer's types, so
457
468
  // the author must know their actions too.
@@ -766,7 +777,9 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
766
777
  // ceiling know, so a consumer type's keys are grantable and its rows are not orphaned.
767
778
  {
768
779
  provide: catalog_reconcile_job_js_1.CatalogReconcileJob,
769
- useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, recordAuthorization.registry, schemas),
780
+ // G-027: the code's permission text over the SAME types as the registry (keys match
781
+ // it exactly), and `APP_RELEASE` (read once; an invalid value refuses boot).
782
+ useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, recordAuthorization.registry, schemas, (0, guard_1.permissionTextByKey)([...objects_js_1.identityObjects, ...extraObjects]), (0, app_release_js_1.loadAppRelease)(process.env)),
770
783
  },
771
784
  // The bundle half: reconciles the merged AccessBundleSpec[] (ENGINE_ALL_ACCESS_BUNDLES,
772
785
  // reflected by the engine from identityAccessBundles) into shield_authz.access_bundle.
@@ -1 +1 @@
1
- {"version":3,"file":"membership-posting.object.d.ts","sourceRoot":"","sources":["../src/membership-posting.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAiBa,iBAAiB;IAE5B,IAAI,EAAG,MAAM,CAAC;IAKd,cAAc,EAAG,MAAM,CAAC;IAGxB,QAAQ,EAAG,MAAM,CAAC;IAGlB,cAAc,EAAG,MAAM,CAAC;IAGxB,UAAU,EAAG,MAAM,CAAC;IAQpB,aAAa,EAAG,MAAM,CAAC;IAGvB,cAAc,EAAG,MAAM,CAAC;IAGxB,SAAS,EAAG,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"membership-posting.object.d.ts","sourceRoot":"","sources":["../src/membership-posting.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAwCa,iBAAiB;IAE5B,IAAI,EAAG,MAAM,CAAC;IAKd,cAAc,EAAG,MAAM,CAAC;IAGxB,QAAQ,EAAG,MAAM,CAAC;IAGlB,cAAc,EAAG,MAAM,CAAC;IAGxB,UAAU,EAAG,MAAM,CAAC;IAQpB,aAAa,EAAG,MAAM,CAAC;IAGvB,cAAc,EAAG,MAAM,CAAC;IAGxB,SAAS,EAAG,MAAM,CAAC;CACpB"}
@@ -83,6 +83,28 @@ exports.MembershipPosting = MembershipPosting = __decorate([
83
83
  pkColumn: 'mp_membership_uuid',
84
84
  subquerySql: `SELECT cm_uuid FROM \`${schema_1.SCHEMA}\`.\`company_membership\` WHERE cm_company_uuid IN (${engine_1.READ_SCOPE_NODES_TOKEN})`,
85
85
  },
86
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
87
+ label: 'Membership posting',
88
+ permissions: {
89
+ read: {
90
+ label: 'Postings: view',
91
+ description: "See each membership's postings: its role, department, branch and the dates they apply.",
92
+ },
93
+ create: {
94
+ label: 'Postings: create',
95
+ description: 'Add postings to memberships, giving users a role in a company.',
96
+ caution: 'Giving someone a role gives them everything that role allows.',
97
+ },
98
+ update: {
99
+ label: 'Postings: edit',
100
+ description: "Replace a membership's postings: its roles, departments, branches and dates.",
101
+ caution: 'Giving someone a role gives them everything that role allows.',
102
+ },
103
+ delete: {
104
+ label: 'Postings: delete',
105
+ description: 'Remove postings from memberships, taking those roles away.',
106
+ },
107
+ },
86
108
  }),
87
109
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'mp_uuid' })
88
110
  ], MembershipPosting);
@@ -1 +1 @@
1
- {"version":3,"file":"record-share.object.d.ts","sourceRoot":"","sources":["../src/record-share.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBASa,WAAW;IAEtB,IAAI,EAAG,MAAM,CAAC;IAGd,UAAU,EAAG,MAAM,CAAC;IAGpB,UAAU,EAAG,MAAM,CAAC;CACrB"}
1
+ {"version":3,"file":"record-share.object.d.ts","sourceRoot":"","sources":["../src/record-share.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAqBa,WAAW;IAEtB,IAAI,EAAG,MAAM,CAAC;IAGd,UAAU,EAAG,MAAM,CAAC;IAGpB,UAAU,EAAG,MAAM,CAAC;CACrB"}
@@ -59,6 +59,18 @@ exports.RecordShare = RecordShare = __decorate([
59
59
  access: { plane: 'control', system: true },
60
60
  crudActions: false,
61
61
  actions: guard_1.RECORD_SHARE_ACTIONS,
62
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
63
+ label: 'Record share',
64
+ permissions: {
65
+ share: {
66
+ label: 'Record shares: share',
67
+ description: 'Share records they own with other users or roles, and stop sharing them.',
68
+ },
69
+ read: {
70
+ label: 'Record shares: view',
71
+ description: 'See who the records they own are shared with.',
72
+ },
73
+ },
62
74
  }),
63
75
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'rs_uuid' })
64
76
  ], RecordShare);
@@ -1 +1 @@
1
- {"version":3,"file":"role.object.d.ts","sourceRoot":"","sources":["../src/role.object.ts"],"names":[],"mappings":"AAGA,uFAAuF;AACvF,qBAWa,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAMd,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGjC,SAAS,EAAG,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"role.object.d.ts","sourceRoot":"","sources":["../src/role.object.ts"],"names":[],"mappings":"AAGA,uFAAuF;AACvF,qBAiCa,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAMd,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGjC,SAAS,EAAG,MAAM,CAAC;CACpB"}
@@ -49,6 +49,27 @@ exports.Role = Role = __decorate([
49
49
  // the caller's reachable subtree. Walled create (ADR-0034, no `create` key): the owner is a
50
50
  // reached company, so the post-insert wall holds. NULL owner ⇒ root only. Root spans all.
51
51
  readScope: { kind: 'nodeColumn', column: 'r_owner_company_uuid' },
52
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
53
+ label: 'Role',
54
+ permissions: {
55
+ read: {
56
+ label: 'Roles: view',
57
+ description: 'See roles and what they allow, the permission list, record policies, cross-tenant grants and the audit log.',
58
+ },
59
+ create: {
60
+ label: 'Roles: create',
61
+ description: 'Create new roles for the companies they can reach.',
62
+ },
63
+ update: {
64
+ label: 'Roles: edit',
65
+ description: 'Rename roles and change their permissions, access bundles and record policies.',
66
+ caution: 'Holders can give a role any permission they hold themselves.',
67
+ },
68
+ delete: {
69
+ label: 'Roles: delete',
70
+ description: 'Delete roles.',
71
+ },
72
+ },
52
73
  }),
53
74
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'r_uuid' })
54
75
  ], Role);
@@ -1 +1 @@
1
- {"version":3,"file":"security-policy.object.d.ts","sourceRoot":"","sources":["../src/security-policy.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAQa,cAAc;IAEzB,IAAI,EAAG,MAAM,CAAC;IAEd,2FAA2F;IAE3F,aAAa,EAAG,MAAM,GAAG,IAAI,CAAC;IAE9B,wCAAwC;IAExC,QAAQ,EAAG,OAAO,CAAC;IAEnB,wCAAwC;IAExC,UAAU,EAAG,MAAM,CAAC;IAEpB,iEAAiE;IAEjE,WAAW,EAAG,MAAM,GAAG,IAAI,CAAC;IAE5B,wBAAwB;IAExB,SAAS,EAAG,MAAM,CAAC;IAEnB;;;OAGG;IAEH,aAAa,EAAG,MAAM,GAAG,IAAI,CAAC;IAE9B,gFAAgF;IAEhF,SAAS,EAAG,MAAM,CAAC;IAEnB,wCAAwC;IAExC,WAAW,EAAG,MAAM,CAAC;IAErB,uDAAuD;IAEvD,WAAW,EAAG,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"security-policy.object.d.ts","sourceRoot":"","sources":["../src/security-policy.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBA+Ba,cAAc;IAEzB,IAAI,EAAG,MAAM,CAAC;IAEd,2FAA2F;IAE3F,aAAa,EAAG,MAAM,GAAG,IAAI,CAAC;IAE9B,wCAAwC;IAExC,QAAQ,EAAG,OAAO,CAAC;IAEnB,wCAAwC;IAExC,UAAU,EAAG,MAAM,CAAC;IAEpB,iEAAiE;IAEjE,WAAW,EAAG,MAAM,GAAG,IAAI,CAAC;IAE5B,wBAAwB;IAExB,SAAS,EAAG,MAAM,CAAC;IAEnB;;;OAGG;IAEH,aAAa,EAAG,MAAM,GAAG,IAAI,CAAC;IAE9B,gFAAgF;IAEhF,SAAS,EAAG,MAAM,CAAC;IAEnB,wCAAwC;IAExC,WAAW,EAAG,MAAM,CAAC;IAErB,uDAAuD;IAEvD,WAAW,EAAG,MAAM,CAAC;CACtB"}
@@ -102,6 +102,27 @@ exports.SecurityPolicy = SecurityPolicy = __decorate([
102
102
  schema: schema_1.SCHEMA,
103
103
  access: { plane: 'control', system: true },
104
104
  readScope: { kind: 'nodeColumn', column: 'sp_scope_node_uuid' },
105
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
106
+ label: 'Security policy',
107
+ permissions: {
108
+ read: {
109
+ label: 'Security policies: view',
110
+ description: 'See which companies, roles and permissions require two-factor sign-in, in the companies they can reach.',
111
+ },
112
+ create: {
113
+ label: 'Security policies: create',
114
+ description: 'Require two-factor sign-in for a company, for a role, or for holders of chosen permissions.',
115
+ },
116
+ update: {
117
+ label: 'Security policies: edit',
118
+ description: "Change a two-factor policy's target or its grace period.",
119
+ },
120
+ delete: {
121
+ label: 'Security policies: delete',
122
+ description: 'Remove two-factor policies.',
123
+ caution: 'People a removed policy covered no longer have to use two-factor sign-in.',
124
+ },
125
+ },
105
126
  }),
106
127
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sp_uuid' })
107
128
  ], SecurityPolicy);
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.object.d.ts","sourceRoot":"","sources":["../src/service-account.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBASa,cAAc;IAEzB,EAAE,EAAG,MAAM,CAAC;IAGZ,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;IAGf,MAAM,EAAG,MAAM,CAAC;IAGhB,WAAW,EAAG,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"service-account.object.d.ts","sourceRoot":"","sources":["../src/service-account.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAoCa,cAAc;IAEzB,EAAE,EAAG,MAAM,CAAC;IAGZ,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;IAGf,MAAM,EAAG,MAAM,CAAC;IAGhB,WAAW,EAAG,MAAM,CAAC;CACtB"}
@@ -72,6 +72,31 @@ exports.ServiceAccount = ServiceAccount = __decorate([
72
72
  access: { plane: 'control', system: true },
73
73
  actions: ['manage_keys'],
74
74
  readScope: { kind: 'nodeColumn', column: 'sa_tenant_id' },
75
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
76
+ label: 'Service account',
77
+ permissions: {
78
+ read: {
79
+ label: 'Service accounts: view',
80
+ description: 'See the service accounts (sign-ins for scripts and AI agents) of their tenant.',
81
+ },
82
+ create: {
83
+ label: 'Service accounts: create',
84
+ description: 'Create service accounts and choose the roles and companies they act in.',
85
+ },
86
+ update: {
87
+ label: 'Service accounts: edit',
88
+ description: "Change a service account's name, roles, companies or status, including disabling it.",
89
+ },
90
+ delete: {
91
+ label: 'Service accounts: delete',
92
+ description: 'Delete service accounts. The framework has no delete route; disable an account by editing its status.',
93
+ },
94
+ manage_keys: {
95
+ label: 'Service accounts: manage keys',
96
+ description: 'Issue, rotate and revoke the API keys service accounts sign in with.',
97
+ caution: 'Anyone holding a key can act as the service account until the key is revoked.',
98
+ },
99
+ },
75
100
  }),
76
101
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sa_id' })
77
102
  ], ServiceAccount);
@@ -1 +1 @@
1
- {"version":3,"file":"shared-resource.object.d.ts","sourceRoot":"","sources":["../src/shared-resource.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAOa,cAAc;IAEzB,IAAI,EAAG,MAAM,CAAC;IAEd,yFAAyF;IAEzF,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"shared-resource.object.d.ts","sourceRoot":"","sources":["../src/shared-resource.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBA+Ba,cAAc;IAEzB,IAAI,EAAG,MAAM,CAAC;IAEd,yFAAyF;IAEzF,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;CAChB"}
@@ -58,6 +58,26 @@ exports.SharedResource = SharedResource = __decorate([
58
58
  table: 'shared_resource',
59
59
  schema: schema_1.SCHEMA,
60
60
  access: { tenantScoped: true, plane: 'data' },
61
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
62
+ label: 'Shared resource',
63
+ permissions: {
64
+ read: {
65
+ label: 'Shared resources: view',
66
+ description: "See their tenant's shared resources, the sample type cross-tenant grants offer.",
67
+ },
68
+ create: {
69
+ label: 'Shared resources: create',
70
+ description: 'Add shared resources to their tenant (the sample type cross-tenant grants offer; the framework has no route for it).',
71
+ },
72
+ update: {
73
+ label: 'Shared resources: edit',
74
+ description: "Rename their tenant's shared resources (sample type; the framework has no route for it).",
75
+ },
76
+ delete: {
77
+ label: 'Shared resources: delete',
78
+ description: "Delete their tenant's shared resources (sample type; the framework has no route for it).",
79
+ },
80
+ },
61
81
  }),
62
82
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sr_uuid' })
63
83
  ], SharedResource);
@@ -1 +1 @@
1
- {"version":3,"file":"stored-file.object.d.ts","sourceRoot":"","sources":["../src/stored-file.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH,qBAOa,UAAU;IAErB,IAAI,EAAG,MAAM,CAAC;IAEd,yFAAyF;IAEzF,QAAQ,EAAG,MAAM,CAAC;IAElB,6FAA6F;IAE7F,OAAO,EAAG,MAAM,CAAC;IAEjB,yEAAyE;IAEzE,UAAU,EAAG,MAAM,CAAC;IAEpB,iDAAiD;IAEjD,MAAM,EAAG,MAAM,CAAC;IAEhB,yEAAyE;IAEzE,WAAW,EAAG,MAAM,CAAC;IAGrB,SAAS,EAAG,MAAM,CAAC;IAEnB,8BAA8B;IAE9B,YAAY,EAAG,MAAM,CAAC;IAEtB,wCAAwC;IAExC,SAAS,EAAG,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"stored-file.object.d.ts","sourceRoot":"","sources":["../src/stored-file.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH,qBA2Ba,UAAU;IAErB,IAAI,EAAG,MAAM,CAAC;IAEd,yFAAyF;IAEzF,QAAQ,EAAG,MAAM,CAAC;IAElB,6FAA6F;IAE7F,OAAO,EAAG,MAAM,CAAC;IAEjB,yEAAyE;IAEzE,UAAU,EAAG,MAAM,CAAC;IAEpB,iDAAiD;IAEjD,MAAM,EAAG,MAAM,CAAC;IAEhB,yEAAyE;IAEzE,WAAW,EAAG,MAAM,CAAC;IAGrB,SAAS,EAAG,MAAM,CAAC;IAEnB,8BAA8B;IAE9B,YAAY,EAAG,MAAM,CAAC;IAEtB,wCAAwC;IAExC,SAAS,EAAG,MAAM,CAAC;CACpB"}
@@ -89,6 +89,26 @@ exports.StoredFile = StoredFile = __decorate([
89
89
  table: 'stored_file',
90
90
  schema: schema_1.SCHEMA,
91
91
  access: { tenantScoped: true, owned: true, plane: 'data' },
92
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
93
+ label: 'File',
94
+ permissions: {
95
+ read: {
96
+ label: 'Files: view',
97
+ description: 'Download the uploaded files they have access to.',
98
+ },
99
+ create: {
100
+ label: 'Files: upload',
101
+ description: 'Upload files.',
102
+ },
103
+ update: {
104
+ label: 'Files: edit',
105
+ description: "Edit a file's record. The file's content, name and type cannot be changed.",
106
+ },
107
+ delete: {
108
+ label: 'Files: delete',
109
+ description: 'Delete the uploaded files they have access to.',
110
+ },
111
+ },
92
112
  }),
93
113
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sf_uuid' })
94
114
  ], StoredFile);
@@ -11,6 +11,7 @@ export declare class User {
11
11
  * The sign-in handle (auth capabilities R1.2, migration 103): lower-case `[a-z0-9._-]{3,64}`,
12
12
  * globally unique, NULL = none (sign in by email only). Set only by an admin (dominance,
13
13
  * `UserService`) or `bootstrap:org`; never by `/account/*`. Not the display name (`username`).
14
+ * `nullable` so an admin's `null` clears it (G-031: the engine refuses NULL otherwise).
14
15
  */
15
16
  loginName?: string | null;
16
17
  status: string;
@@ -1 +1 @@
1
- {"version":3,"file":"user.object.d.ts","sourceRoot":"","sources":["../src/user.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBA4Ba,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;IAEf;;;;OAIG;IAEH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"user.object.d.ts","sourceRoot":"","sources":["../src/user.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAoEa,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;IAEf;;;;;OAKG;IAEH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;CACpB"}
@@ -25,6 +25,7 @@ let User = class User {
25
25
  * The sign-in handle (auth capabilities R1.2, migration 103): lower-case `[a-z0-9._-]{3,64}`,
26
26
  * globally unique, NULL = none (sign in by email only). Set only by an admin (dominance,
27
27
  * `UserService`) or `bootstrap:org`; never by `/account/*`. Not the display name (`username`).
28
+ * `nullable` so an admin's `null` clears it (G-031: the engine refuses NULL otherwise).
28
29
  */
29
30
  loginName;
30
31
  status;
@@ -44,7 +45,7 @@ __decorate([
44
45
  __metadata("design:type", String)
45
46
  ], User.prototype, "email", void 0);
46
47
  __decorate([
47
- (0, engine_1.Field)({ column: 'u_login_name', type: 'string' }),
48
+ (0, engine_1.Field)({ column: 'u_login_name', type: 'string', nullable: true }),
48
49
  __metadata("design:type", Object)
49
50
  ], User.prototype, "loginName", void 0);
50
51
  __decorate([
@@ -82,6 +83,43 @@ exports.User = User = __decorate([
82
83
  pkColumn: 'u_uuid',
83
84
  subquerySql: `SELECT cm_user_uuid FROM \`${schema_1.SCHEMA}\`.\`company_membership\` WHERE cm_company_uuid IN (${engine_1.READ_SCOPE_NODES_TOKEN})`,
84
85
  },
86
+ // G-027: permission catalog text, read by every tenant's administrators (no tenant names).
87
+ label: 'User',
88
+ permissions: {
89
+ read: {
90
+ label: 'Users: view',
91
+ description: 'See the users who belong to the companies they can reach.',
92
+ },
93
+ create: {
94
+ label: 'Users: create',
95
+ description: 'Add new user accounts, each with a first company membership.',
96
+ },
97
+ update: {
98
+ label: 'Users: edit',
99
+ description: "Change a user's name, email, sign-in name or status, including disabling them.",
100
+ },
101
+ delete: {
102
+ label: 'Users: delete',
103
+ description: 'Delete user accounts.',
104
+ },
105
+ invite: {
106
+ label: 'Users: invite',
107
+ description: 'Email a user who has not set a password yet a link to set one.',
108
+ },
109
+ reset_mfa: {
110
+ label: 'Users: reset two-factor sign-in',
111
+ description: "Remove a user's two-factor devices and recovery codes and sign them out, so they set up two-factor sign-in again.",
112
+ caution: "Confirm the person's identity first: until they set up two-factor sign-in again, their password alone signs them in.",
113
+ },
114
+ unlock: {
115
+ label: 'Users: unlock sign-in',
116
+ description: 'Clear the lock that failed sign-in attempts put on a user, so they can try again.',
117
+ },
118
+ end_sessions: {
119
+ label: 'Users: sign out everywhere',
120
+ description: 'Sign a user out on every device.',
121
+ },
122
+ },
85
123
  }),
86
124
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'u_uuid' })
87
125
  ], User);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/identity",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "license": "MIT",
5
5
  "description": "Astram framework — the framework-owned identity + tenancy model. Hosts the identity @ObjectType classes (user, company, membership, posting), framework-authored identity migrations, and the identity resolver. Top of the dependency graph: depends on @astram/engine (for @ObjectType/@Field decorators) + @astram/guard / @astram/core (for the auth ports).",
6
6
  "type": "commonjs",
@@ -37,8 +37,8 @@
37
37
  "dependencies": {
38
38
  "mariadb": "^3.5.4",
39
39
  "@astram/core": "1.12.0",
40
- "@astram/guard": "0.22.0",
41
- "@astram/engine": "0.15.0"
40
+ "@astram/guard": "0.23.0",
41
+ "@astram/engine": "0.16.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@nestjs/common": "^11",