@astram/identity 0.10.0 → 0.11.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 (42) 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 +5 -2
  7. package/dist/features/authz/catalog-bootstrap.provider.d.ts +4 -1
  8. package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
  9. package/dist/features/authz/catalog-bootstrap.provider.js +37 -6
  10. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +6 -5
  11. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  12. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +6 -5
  13. package/dist/features/files/files.controller.d.ts +27 -0
  14. package/dist/features/files/files.controller.d.ts.map +1 -0
  15. package/dist/features/files/files.controller.js +94 -0
  16. package/dist/features/files/files.service.d.ts +87 -0
  17. package/dist/features/files/files.service.d.ts.map +1 -0
  18. package/dist/features/files/files.service.js +311 -0
  19. package/dist/features/identity-feature.module.d.ts.map +1 -1
  20. package/dist/features/identity-feature.module.js +37 -11
  21. package/dist/features/policy/policy.service.d.ts +2 -1
  22. package/dist/features/policy/policy.service.d.ts.map +1 -1
  23. package/dist/features/policy/policy.service.js +15 -0
  24. package/dist/features/share/share.service.d.ts +19 -2
  25. package/dist/features/share/share.service.d.ts.map +1 -1
  26. package/dist/features/share/share.service.js +36 -2
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +13 -1
  30. package/dist/objects.d.ts.map +1 -1
  31. package/dist/objects.js +2 -0
  32. package/dist/record-share.object.d.ts +1 -1
  33. package/dist/record-share.object.d.ts.map +1 -1
  34. package/dist/record-share.object.js +4 -1
  35. package/dist/service-account.object.d.ts +3 -2
  36. package/dist/service-account.object.d.ts.map +1 -1
  37. package/dist/service-account.object.js +4 -2
  38. package/dist/stored-file.object.d.ts +35 -0
  39. package/dist/stored-file.object.d.ts.map +1 -0
  40. package/dist/stored-file.object.js +94 -0
  41. package/migrations/1710000000022-CreateStoredFile.cjs +43 -0
  42. package/package.json +8 -4
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DbMembershipChecker = exports.DbScopeSubtreeResolver = exports.AlsSessionFactsReader = exports.IdentityFeatureModule = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.ServiceAccount = exports.CompanyBranch = exports.CompanyDepartment = exports.MembershipPosting = exports.CompanyMembership = exports.Branch = exports.Department = exports.Role = exports.User = exports.Company = void 0;
3
+ exports.DbMembershipChecker = exports.DbScopeSubtreeResolver = exports.AlsSessionFactsReader = exports.FilesService = exports.IdentityFeatureModule = exports.buildIdentityAuthKeys = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.StoredFile = exports.ServiceAccount = exports.CompanyBranch = exports.CompanyDepartment = exports.MembershipPosting = exports.CompanyMembership = exports.Branch = exports.Department = exports.Role = exports.User = exports.Company = void 0;
4
4
  /**
5
5
  * @astram/identity — the framework-owned identity + tenancy model.
6
6
  * The 9 identity object classes (relocated from the consumer app in Sub-plan B) +
@@ -27,6 +27,8 @@ var company_branch_object_js_1 = require("./company-branch.object.js");
27
27
  Object.defineProperty(exports, "CompanyBranch", { enumerable: true, get: function () { return company_branch_object_js_1.CompanyBranch; } });
28
28
  var service_account_object_js_1 = require("./service-account.object.js");
29
29
  Object.defineProperty(exports, "ServiceAccount", { enumerable: true, get: function () { return service_account_object_js_1.ServiceAccount; } });
30
+ var stored_file_object_js_1 = require("./stored-file.object.js");
31
+ Object.defineProperty(exports, "StoredFile", { enumerable: true, get: function () { return stored_file_object_js_1.StoredFile; } });
30
32
  var migrations_dir_js_1 = require("./migrations-dir.js");
31
33
  Object.defineProperty(exports, "identityMigrationsDir", { enumerable: true, get: function () { return migrations_dir_js_1.identityMigrationsDir; } });
32
34
  /**
@@ -46,6 +48,10 @@ Object.defineProperty(exports, "identityObjects", { enumerable: true, get: funct
46
48
  var auth_facts_keys_js_1 = require("./auth-facts-keys.js");
47
49
  Object.defineProperty(exports, "identityAuthActionKeys", { enumerable: true, get: function () { return auth_facts_keys_js_1.identityAuthActionKeys; } });
48
50
  Object.defineProperty(exports, "identityControlPlaneKeys", { enumerable: true, get: function () { return auth_facts_keys_js_1.identityControlPlaneKeys; } });
51
+ /** KI #70: the same key universe with the consumer's `extraObjects` (pass the SAME list to
52
+ * `IdentityFeatureModule.forRoot({ extraObjects })`). */
53
+ var auth_facts_keys_js_2 = require("./auth-facts-keys.js");
54
+ Object.defineProperty(exports, "buildIdentityAuthKeys", { enumerable: true, get: function () { return auth_facts_keys_js_2.buildIdentityAuthKeys; } });
49
55
  /**
50
56
  * The framework-owned identity FEATURE module (company/user/role CRUD). The consumer imports
51
57
  * `IdentityFeatureModule.forRoot()` in one line alongside `ShieldModule.forRoot(...)` (ADR-0026);
@@ -54,6 +60,12 @@ Object.defineProperty(exports, "identityControlPlaneKeys", { enumerable: true, g
54
60
  */
55
61
  var identity_feature_module_js_1 = require("./features/identity-feature.module.js");
56
62
  Object.defineProperty(exports, "IdentityFeatureModule", { enumerable: true, get: function () { return identity_feature_module_js_1.IdentityFeatureModule; } });
63
+ /**
64
+ * Stored files (file-storage SPEC §5 I-K): `store` / `open` / `remove` on the engine pipe, provided
65
+ * and exported by `IdentityFeatureModule`. Apps call it from their own `@Classified` routes.
66
+ */
67
+ var files_service_js_1 = require("./features/files/files.service.js");
68
+ Object.defineProperty(exports, "FilesService", { enumerable: true, get: function () { return files_service_js_1.FilesService; } });
57
69
  /*
58
70
  * The record-tier authorization: since 0.7 (security hardening Phase 9 #7)
59
71
  * `IdentityFeatureModule.forRoot({ extraObjects })` builds it and provides it to `ShieldModule`
@@ -1 +1 @@
1
- {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAetD,6FAA6F;AAC7F,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAcrD,CAAC"}
1
+ {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAgBtD,6FAA6F;AAC7F,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAerD,CAAC"}
package/dist/objects.js CHANGED
@@ -14,6 +14,7 @@ const cross_tenant_grant_object_js_1 = require("./cross-tenant-grant.object.js")
14
14
  const record_share_object_js_1 = require("./record-share.object.js");
15
15
  const shared_resource_object_js_1 = require("./shared-resource.object.js");
16
16
  const service_account_object_js_1 = require("./service-account.object.js");
17
+ const stored_file_object_js_1 = require("./stored-file.object.js");
17
18
  /** All identity objects, registered with the engine at boot (same set + order as before). */
18
19
  exports.identityObjects = [
19
20
  company_object_js_1.Company,
@@ -29,4 +30,5 @@ exports.identityObjects = [
29
30
  record_share_object_js_1.RecordShare,
30
31
  shared_resource_object_js_1.SharedResource,
31
32
  service_account_object_js_1.ServiceAccount,
33
+ stored_file_object_js_1.StoredFile,
32
34
  ];
@@ -11,7 +11,7 @@
11
11
  * posture as `cross_tenant_grant`/`role`. `system: true` (never `owned`/`tenantScoped`) — the
12
12
  * table's own columns (`rs_tenant_uuid`, `rs_granted_by`) + the writer's owner-bound encode the
13
13
  * isolation; the pipe's owner/tenant baseline is not involved. Action vocabulary is NOT the CRUD
14
- * verbs — see `@astram/guard`'s `identity-actions.ts` `actionsFor('record_share')` →
14
+ * verbs: it opts out (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
15
15
  * `RECORD_SHARE_ACTIONS` (`share`/`read`).
16
16
  *
17
17
  * Mirrors `cross-tenant-grant.object.ts`'s shape (system, PK only satisfies `validateAccess`).
@@ -1 +1 @@
1
- {"version":3,"file":"record-share.object.d.ts","sourceRoot":"","sources":["../src/record-share.object.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAOa,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":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBASa,WAAW;IAEtB,IAAI,EAAG,MAAM,CAAC;IAGd,UAAU,EAAG,MAAM,CAAC;IAGpB,UAAU,EAAG,MAAM,CAAC;CACrB"}
@@ -11,6 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RecordShare = void 0;
13
13
  const engine_1 = require("@astram/engine");
14
+ const guard_1 = require("@astram/guard");
14
15
  /** The physical schema `record_share` lives in (migration 1710000000014). */
15
16
  const SCHEMA = 'shield_authz';
16
17
  /**
@@ -26,7 +27,7 @@ const SCHEMA = 'shield_authz';
26
27
  * posture as `cross_tenant_grant`/`role`. `system: true` (never `owned`/`tenantScoped`) — the
27
28
  * table's own columns (`rs_tenant_uuid`, `rs_granted_by`) + the writer's owner-bound encode the
28
29
  * isolation; the pipe's owner/tenant baseline is not involved. Action vocabulary is NOT the CRUD
29
- * verbs — see `@astram/guard`'s `identity-actions.ts` `actionsFor('record_share')` →
30
+ * verbs: it opts out (`crudActions: false`, ADR-0038) and declares exactly `@astram/guard`'s
30
31
  * `RECORD_SHARE_ACTIONS` (`share`/`read`).
31
32
  *
32
33
  * Mirrors `cross-tenant-grant.object.ts`'s shape (system, PK only satisfies `validateAccess`).
@@ -55,6 +56,8 @@ exports.RecordShare = RecordShare = __decorate([
55
56
  table: 'record_share',
56
57
  schema: SCHEMA,
57
58
  access: { plane: 'control', system: true },
59
+ crudActions: false,
60
+ actions: guard_1.RECORD_SHARE_ACTIONS,
58
61
  }),
59
62
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'rs_uuid' })
60
63
  ], RecordShare);
@@ -18,8 +18,9 @@
18
18
  * is IN their reachable subtree (a cross-tenant id is invisible → 404, no existence leak). Root
19
19
  * spans all. This is the read/write wall that makes "own tenant only" structural, not handler-level.
20
20
  *
21
- * Action vocabulary is administration, not CRUD data ops — see `@astram/guard`'s
22
- * `identity-actions.ts` (`read`/`manage` for `service_account`).
21
+ * Action vocabulary (ADR-0038): the CRUD verbs administer the account row, plus the declared
22
+ * `manage_keys` (issue/rotate/revoke its `ask_…` keys), giving `read, create, update, delete,
23
+ * manage_keys` — the same set as `@astram/guard`'s `SERVICE_ACCOUNT_ACTIONS`.
23
24
  */
24
25
  export declare class ServiceAccount {
25
26
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.object.d.ts","sourceRoot":"","sources":["../src/service-account.object.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAQa,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,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"}
@@ -32,8 +32,9 @@ const schema_1 = require("./schema");
32
32
  * is IN their reachable subtree (a cross-tenant id is invisible → 404, no existence leak). Root
33
33
  * spans all. This is the read/write wall that makes "own tenant only" structural, not handler-level.
34
34
  *
35
- * Action vocabulary is administration, not CRUD data ops — see `@astram/guard`'s
36
- * `identity-actions.ts` (`read`/`manage` for `service_account`).
35
+ * Action vocabulary (ADR-0038): the CRUD verbs administer the account row, plus the declared
36
+ * `manage_keys` (issue/rotate/revoke its `ask_…` keys), giving `read, create, update, delete,
37
+ * manage_keys` — the same set as `@astram/guard`'s `SERVICE_ACCOUNT_ACTIONS`.
37
38
  */
38
39
  let ServiceAccount = class ServiceAccount {
39
40
  id;
@@ -69,6 +70,7 @@ exports.ServiceAccount = ServiceAccount = __decorate([
69
70
  table: 'service_account',
70
71
  schema: schema_1.SCHEMA,
71
72
  access: { plane: 'control', system: true },
73
+ actions: ['manage_keys'],
72
74
  readScope: { kind: 'nodeColumn', column: 'sa_tenant_id' },
73
75
  }),
74
76
  (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sa_id' })
@@ -0,0 +1,35 @@
1
+ /**
2
+ * stored_file — the metadata record of one uploaded file (file-storage SPEC §4).
3
+ *
4
+ * The bytes live in the object store under `storageKey` (`t/<tenantId>/<uuid>`, built by the
5
+ * server); this row is what the engine gates, audits and lists. Table DDL:
6
+ * `migrations/1710000000022-CreateStoredFile.cjs` (columns mirror the fields below exactly).
7
+ *
8
+ * ACCESS: `{ tenantScoped: true, owned: true, plane: 'data' }` — tenant business content, owned by
9
+ * the uploader. `plane: 'data'` keeps the capped root out by construction.
10
+ *
11
+ * SERVER-STAMPED: `tenantId` (`trusted: 'tenantId'`) and `ownerId` (`trusted: 'actorId'`) are
12
+ * overwritten from the session on create and never re-stamped on update; client input is ignored.
13
+ *
14
+ * CREATE-ONLY: the five content fields are `immutable` — a patch can never re-point a record at
15
+ * other bytes or change what the bytes claim to be.
16
+ */
17
+ export declare class StoredFile {
18
+ uuid: string;
19
+ /** Server-stamped tenant boundary (engine overwrites from the session; never client). */
20
+ tenantId: string;
21
+ /** Server-stamped uploader (the owner; engine overwrites from the session; never client). */
22
+ ownerId: string;
23
+ /** `t/<tenantId>/<uuid>`, server-built; never returned to the client. */
24
+ storageKey: string;
25
+ /** Lower-case hex sha256 of the stored bytes. */
26
+ sha256: string;
27
+ /** Canonical type from the intake allowlist, never the client header. */
28
+ contentType: string;
29
+ sizeBytes: number;
30
+ /** Sanitized display name. */
31
+ originalName: string;
32
+ /** UTC epoch ms, set by the service. */
33
+ createdAt: number;
34
+ }
35
+ //# sourceMappingURL=stored-file.object.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,94 @@
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.StoredFile = void 0;
13
+ const engine_1 = require("@astram/engine");
14
+ const schema_1 = require("./schema");
15
+ /**
16
+ * stored_file — the metadata record of one uploaded file (file-storage SPEC §4).
17
+ *
18
+ * The bytes live in the object store under `storageKey` (`t/<tenantId>/<uuid>`, built by the
19
+ * server); this row is what the engine gates, audits and lists. Table DDL:
20
+ * `migrations/1710000000022-CreateStoredFile.cjs` (columns mirror the fields below exactly).
21
+ *
22
+ * ACCESS: `{ tenantScoped: true, owned: true, plane: 'data' }` — tenant business content, owned by
23
+ * the uploader. `plane: 'data'` keeps the capped root out by construction.
24
+ *
25
+ * SERVER-STAMPED: `tenantId` (`trusted: 'tenantId'`) and `ownerId` (`trusted: 'actorId'`) are
26
+ * overwritten from the session on create and never re-stamped on update; client input is ignored.
27
+ *
28
+ * CREATE-ONLY: the five content fields are `immutable` — a patch can never re-point a record at
29
+ * other bytes or change what the bytes claim to be.
30
+ */
31
+ let StoredFile = class StoredFile {
32
+ uuid;
33
+ /** Server-stamped tenant boundary (engine overwrites from the session; never client). */
34
+ tenantId;
35
+ /** Server-stamped uploader (the owner; engine overwrites from the session; never client). */
36
+ ownerId;
37
+ /** `t/<tenantId>/<uuid>`, server-built; never returned to the client. */
38
+ storageKey;
39
+ /** Lower-case hex sha256 of the stored bytes. */
40
+ sha256;
41
+ /** Canonical type from the intake allowlist, never the client header. */
42
+ contentType;
43
+ sizeBytes;
44
+ /** Sanitized display name. */
45
+ originalName;
46
+ /** UTC epoch ms, set by the service. */
47
+ createdAt;
48
+ };
49
+ exports.StoredFile = StoredFile;
50
+ __decorate([
51
+ (0, engine_1.Field)({ column: 'sf_uuid', type: 'uuid', role: 'primaryKey' }),
52
+ __metadata("design:type", String)
53
+ ], StoredFile.prototype, "uuid", void 0);
54
+ __decorate([
55
+ (0, engine_1.Field)({ column: 'sf_tenant_id', type: 'uuid', trusted: 'tenantId' }),
56
+ __metadata("design:type", String)
57
+ ], StoredFile.prototype, "tenantId", void 0);
58
+ __decorate([
59
+ (0, engine_1.Field)({ column: 'sf_owner_id', type: 'uuid', trusted: 'actorId' }),
60
+ __metadata("design:type", String)
61
+ ], StoredFile.prototype, "ownerId", void 0);
62
+ __decorate([
63
+ (0, engine_1.Field)({ column: 'sf_storage_key', type: 'string', required: true, immutable: true }),
64
+ __metadata("design:type", String)
65
+ ], StoredFile.prototype, "storageKey", void 0);
66
+ __decorate([
67
+ (0, engine_1.Field)({ column: 'sf_sha256', type: 'string', required: true, immutable: true }),
68
+ __metadata("design:type", String)
69
+ ], StoredFile.prototype, "sha256", void 0);
70
+ __decorate([
71
+ (0, engine_1.Field)({ column: 'sf_content_type', type: 'string', required: true, immutable: true }),
72
+ __metadata("design:type", String)
73
+ ], StoredFile.prototype, "contentType", void 0);
74
+ __decorate([
75
+ (0, engine_1.Field)({ column: 'sf_size_bytes', type: 'number', required: true, immutable: true }),
76
+ __metadata("design:type", Number)
77
+ ], StoredFile.prototype, "sizeBytes", void 0);
78
+ __decorate([
79
+ (0, engine_1.Field)({ column: 'sf_original_name', type: 'string', required: true, immutable: true }),
80
+ __metadata("design:type", String)
81
+ ], StoredFile.prototype, "originalName", void 0);
82
+ __decorate([
83
+ (0, engine_1.Field)({ column: 'sf_created_at', type: 'number', required: true }),
84
+ __metadata("design:type", Number)
85
+ ], StoredFile.prototype, "createdAt", void 0);
86
+ exports.StoredFile = StoredFile = __decorate([
87
+ (0, engine_1.ObjectType)({
88
+ typeKey: 'stored_file',
89
+ table: 'stored_file',
90
+ schema: schema_1.SCHEMA,
91
+ access: { tenantScoped: true, owned: true, plane: 'data' },
92
+ }),
93
+ (0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'sf_uuid' })
94
+ ], StoredFile);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Framework identity migration — the `shield_app`.`stored_file` table (file-storage SPEC §4).
3
+ *
4
+ * One row = the metadata of one uploaded file whose bytes live in the object store under
5
+ * `sf_storage_key` (`t/<tenantId>/<uuid>`, server-built). The row is the record the engine gates;
6
+ * the bytes are never in the database.
7
+ *
8
+ * Columns EXACTLY mirror the `StoredFile` @ObjectType's @Field columns (`src/stored-file.object.ts`):
9
+ * `sf_tenant_id` (trusted:'tenantId') and `sf_owner_id` (trusted:'actorId') are stamped from the
10
+ * session, never client input; the five content columns are create-only (`immutable`).
11
+ * `sf_sha256` is indexed with the tenant first so an app can detect a repeat upload (no dedup).
12
+ * No FK to an app record: the link from an app record to a file is the app's own column.
13
+ *
14
+ * The `shield_app` schema already exists (created by 1710000000001); this migration owns only the
15
+ * table. Idempotent (CREATE TABLE IF NOT EXISTS); `down` drops the table (DROP TABLE IF EXISTS).
16
+ * No seed, no hard-coded ids.
17
+ */
18
+
19
+ const SCHEMA = 'shield_app';
20
+
21
+ async function up({ query }) {
22
+ await query(`CREATE TABLE IF NOT EXISTS \`${SCHEMA}\`.\`stored_file\` (
23
+ sf_uuid VARCHAR(36) NOT NULL,
24
+ sf_tenant_id VARCHAR(36) NOT NULL,
25
+ sf_owner_id VARCHAR(36) NOT NULL,
26
+ sf_storage_key VARCHAR(255) NOT NULL,
27
+ sf_sha256 CHAR(64) NOT NULL,
28
+ sf_content_type VARCHAR(127) NOT NULL,
29
+ sf_size_bytes BIGINT UNSIGNED NOT NULL,
30
+ sf_original_name VARCHAR(255) NOT NULL,
31
+ sf_created_at BIGINT NOT NULL,
32
+ PRIMARY KEY (sf_uuid),
33
+ UNIQUE KEY uq_sf_storage_key (sf_storage_key),
34
+ INDEX idx_sf_tenant (sf_tenant_id),
35
+ INDEX idx_sf_tenant_sha (sf_tenant_id, sf_sha256)
36
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`);
37
+ }
38
+
39
+ async function down({ query }) {
40
+ await query(`DROP TABLE IF EXISTS \`${SCHEMA}\`.\`stored_file\``);
41
+ }
42
+
43
+ module.exports = { up, down };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/identity",
3
- "version": "0.10.0",
3
+ "version": "0.11.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",
@@ -36,9 +36,9 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "mariadb": "^3.5.4",
39
- "@astram/core": "1.5.0",
40
- "@astram/engine": "0.6.0",
41
- "@astram/guard": "0.13.0"
39
+ "@astram/core": "1.6.0",
40
+ "@astram/guard": "0.14.0",
41
+ "@astram/engine": "0.7.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@nestjs/common": "^11",
@@ -47,12 +47,16 @@
47
47
  "rxjs": "^7"
48
48
  },
49
49
  "devDependencies": {
50
+ "@aws-sdk/client-s3": "3.1142.0",
50
51
  "@nestjs/common": "^11.0.0",
51
52
  "@nestjs/core": "^11.0.0",
53
+ "@nestjs/platform-express": "^11.0.0",
52
54
  "@nestjs/testing": "^11.0.0",
53
55
  "@types/node": "^22.0.0",
56
+ "@types/supertest": "^6.0.2",
54
57
  "reflect-metadata": "^0.2.2",
55
58
  "rxjs": "^7.8.1",
59
+ "supertest": "^7.0.0",
56
60
  "typescript": "^5.7.0",
57
61
  "unplugin-swc": "^1.5.1",
58
62
  "vitest": "^2.1.8"