@futdevpro/nts-dynamo 1.15.316 → 1.15.320

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 (44) hide show
  1. package/build/_collections/database-unavailable.util.d.ts +27 -0
  2. package/build/_collections/database-unavailable.util.d.ts.map +1 -0
  3. package/build/_collections/database-unavailable.util.js +55 -0
  4. package/build/_collections/database-unavailable.util.js.map +1 -0
  5. package/build/_modules/privacy-lifecycle/index.d.ts +2 -0
  6. package/build/_modules/privacy-lifecycle/index.d.ts.map +1 -1
  7. package/build/_modules/privacy-lifecycle/index.js +5 -1
  8. package/build/_modules/privacy-lifecycle/index.js.map +1 -1
  9. package/build/_modules/privacy-lifecycle/privacy-ledger.mongo-store.d.ts +92 -0
  10. package/build/_modules/privacy-lifecycle/privacy-ledger.mongo-store.d.ts.map +1 -0
  11. package/build/_modules/privacy-lifecycle/privacy-ledger.mongo-store.js +295 -0
  12. package/build/_modules/privacy-lifecycle/privacy-ledger.mongo-store.js.map +1 -0
  13. package/build/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.d.ts +136 -0
  14. package/build/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.d.ts.map +1 -0
  15. package/build/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.js +316 -0
  16. package/build/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.js.map +1 -0
  17. package/build/_modules/usage/index.d.ts +2 -0
  18. package/build/_modules/usage/index.d.ts.map +1 -1
  19. package/build/_modules/usage/index.js +2 -0
  20. package/build/_modules/usage/index.js.map +1 -1
  21. package/build/_modules/usage/usage-session-account-query.interface.d.ts +13 -0
  22. package/build/_modules/usage/usage-session-account-query.interface.d.ts.map +1 -0
  23. package/build/_modules/usage/usage-session-account-query.interface.js +3 -0
  24. package/build/_modules/usage/usage-session-account-query.interface.js.map +1 -0
  25. package/build/_modules/usage/usage-session-owner.interface.d.ts +15 -0
  26. package/build/_modules/usage/usage-session-owner.interface.d.ts.map +1 -0
  27. package/build/_modules/usage/usage-session-owner.interface.js +3 -0
  28. package/build/_modules/usage/usage-session-owner.interface.js.map +1 -0
  29. package/build/_modules/usage/usage.controller.d.ts +16 -0
  30. package/build/_modules/usage/usage.controller.d.ts.map +1 -1
  31. package/build/_modules/usage/usage.controller.js +42 -9
  32. package/build/_modules/usage/usage.controller.js.map +1 -1
  33. package/build/_modules/usage/usage.data-service.d.ts +41 -1
  34. package/build/_modules/usage/usage.data-service.d.ts.map +1 -1
  35. package/build/_modules/usage/usage.data-service.js +112 -0
  36. package/build/_modules/usage/usage.data-service.js.map +1 -1
  37. package/build/_services/base/db.service.d.ts.map +1 -1
  38. package/build/_services/base/db.service.js +7 -2
  39. package/build/_services/base/db.service.js.map +1 -1
  40. package/build/index.d.ts +1 -0
  41. package/build/index.d.ts.map +1 -1
  42. package/build/index.js +1 -0
  43. package/build/index.js.map +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,136 @@
1
+ import { Model, Schema } from 'mongoose';
2
+ import { DyNTS_PrivacyContributorOutcome_Interface } from './_models/privacy-contributor-outcome.interface';
3
+ import { DyNTS_PrivacyContributorPlan_Interface } from './_models/privacy-contributor-plan.interface';
4
+ import { DyNTS_PrivacyLifecycleAdapter_Interface } from './_models/privacy-lifecycle-adapter.interface';
5
+ import { DyNTS_PrivacyRequestPersistenceCreateResult_Interface } from './_models/privacy-request-persistence-create-result.interface';
6
+ import { DyNTS_PrivacyRequestListQuery_Interface } from './_models/privacy-request-list.interface';
7
+ import { DyNTS_PrivacyRequestPersistenceListResult_Interface } from './_models/privacy-request-list-result.interface';
8
+ import { DyNTS_PrivacyRequestRecord_Interface } from './_models/privacy-request-record.interface';
9
+ /** Persistence row of a contributor plan: the contract shape plus its lookup key. */
10
+ export type DyNTS_PrivacyStoredPlan_Row = DyNTS_PrivacyContributorPlan_Interface & {
11
+ requestId: string;
12
+ };
13
+ /** Persistence row of a contributor outcome: the contract shape plus its lookup key. */
14
+ export type DyNTS_PrivacyStoredOutcome_Row = DyNTS_PrivacyContributorOutcome_Interface & {
15
+ requestId: string;
16
+ };
17
+ /** Optional isolated models — integration tests bind them to a dedicated connection. */
18
+ export interface DyNTS_PrivacyLifecycleMongoStore_Settings {
19
+ requestModel?: Model<DyNTS_PrivacyRequestRecord_Interface>;
20
+ planModel?: Model<DyNTS_PrivacyStoredPlan_Row>;
21
+ outcomeModel?: Model<DyNTS_PrivacyStoredOutcome_Row>;
22
+ }
23
+ /**
24
+ * Mongo persistence for the privacy lifecycle — the production `DyNTS_PrivacyLifecycleAdapter_Interface`.
25
+ *
26
+ * @description BFR-FUTDEVPRO-010 / BFR-ADVENTOR-025. Before this store the module shipped the contract only,
27
+ * so `DyNTS_PrivacyLifecycle_ControlService` (which REQUIRES an adapter) could not be started, and three
28
+ * products carried their own copies (company-toolmaker, futdevpro, adventor). This is the futdevpro copy,
29
+ * lifted verbatim in behaviour; the collection and model names are unchanged, so existing data stays readable.
30
+ *
31
+ * Atomicity does NOT need a transaction here — every guarantee is a single-document operation:
32
+ * - replay idempotency: the UNIQUE `idempotencyHash` index (the loser of a race reads the winner);
33
+ * - optimistic concurrency: `replaceRequest` puts `revision` INTO the filter, so a stale writer matches
34
+ * nothing and gets `false` (the caller turns that into a 409);
35
+ * - plan/outcome writes: an upsert on the UNIQUE `(requestId, contributorId)` pair.
36
+ *
37
+ * ⚠️ A broken revision check here is not "a bug": it is a lost or doubly executed erasure request —
38
+ * exactly the operation whose provability is the legal requirement (GDPR Art. 17).
39
+ *
40
+ * ⚠️ Consumers that carried a product-side copy must DELETE it in the same change-set that switches to this
41
+ * store: both register the same model names, and `resolveModel` silently returns whichever registered first.
42
+ */
43
+ export declare class DyNTS_PrivacyLifecycle_MongoStore implements DyNTS_PrivacyLifecycleAdapter_Interface {
44
+ private readonly injected;
45
+ private readiness?;
46
+ /** Accepts isolated models for integration tests; production resolves the singletons lazily. */
47
+ constructor(set?: DyNTS_PrivacyLifecycleMongoStore_Settings);
48
+ /**
49
+ * Creates the indexes BEFORE the first read or write.
50
+ *
51
+ * @description Not a convenience step: idempotency and optimistic concurrency both rest on the UNIQUE
52
+ * indexes. Without them a duplicate would not fail — it would silently become a second record.
53
+ */
54
+ ensureReady(): Promise<void>;
55
+ /**
56
+ * Creates one request, or returns the EXACT same request on an idempotent replay.
57
+ *
58
+ * @description The decision is not a racy "does it exist?" read (two parallel callers would both see
59
+ * "no"), but the UNIQUE index: the second writer gets a duplicate-key error and reads the winner.
60
+ */
61
+ createOrGetRequest(request: DyNTS_PrivacyRequestRecord_Interface): Promise<DyNTS_PrivacyRequestPersistenceCreateResult_Interface>;
62
+ /** Loads one request by id. Does not throw when missing — interpreting absence is the caller's job. */
63
+ findRequestById(requestId: string): Promise<DyNTS_PrivacyRequestRecord_Interface | null>;
64
+ /**
65
+ * One bounded, deterministic, newest-first page of the subject's own requests.
66
+ *
67
+ * @description Sorted by TWO keys (`createdAt`, then `requestId`): with a single key two requests created in
68
+ * the same millisecond would order non-deterministically and paging could skip or repeat a row
69
+ * (`createdAt` is ISO-8601 UTC text, so string order is time order). The cursor is opaque and carries no
70
+ * personal data — a timestamp and a machine id only.
71
+ */
72
+ listRequests(query: DyNTS_PrivacyRequestListQuery_Interface): Promise<DyNTS_PrivacyRequestPersistenceListResult_Interface>;
73
+ /**
74
+ * State transition with OPTIMISTIC CONCURRENCY.
75
+ *
76
+ * @description `revision` is part of the filter: if someone else rewrote the record meanwhile, the filter
77
+ * matches nothing and `false` is returned. That `false` is not an error — it is the correct answer to a
78
+ * race, and the caller turns it into a 409.
79
+ */
80
+ replaceRequest(request: DyNTS_PrivacyRequestRecord_Interface, expectedRevision: number): Promise<boolean>;
81
+ /**
82
+ * Stores one contributor plan.
83
+ *
84
+ * @description Overwrites, does not insert: a resumed run may deliver the same plan again. The upsert on the
85
+ * `(requestId, contributorId)` pair keeps it idempotent — never two plans for one contributor.
86
+ */
87
+ saveContributorPlan(requestId: string, plan: DyNTS_PrivacyContributorPlan_Interface): Promise<void>;
88
+ /** All contributor plans of one request, in a stable order. */
89
+ listContributorPlans(requestId: string): Promise<DyNTS_PrivacyContributorPlan_Interface[]>;
90
+ /**
91
+ * Stores one contributor outcome.
92
+ *
93
+ * @description The overwrite is not only for resumed runs: on a failed verification the lifecycle
94
+ * DELIBERATELY replaces an earlier (successful) outcome with a failed one — an insert-only store would make
95
+ * the request look falsely successful.
96
+ */
97
+ saveContributorOutcome(requestId: string, outcome: DyNTS_PrivacyContributorOutcome_Interface): Promise<void>;
98
+ /** All contributor outcomes of one request, in a stable order. */
99
+ listContributorOutcomes(requestId: string): Promise<DyNTS_PrivacyContributorOutcome_Interface[]>;
100
+ /**
101
+ * The request-record schema.
102
+ *
103
+ * @description Date fields are `String`, not `Date`: the contract uses an EMPTY string for "has not happened
104
+ * yet" (`identityVerifiedAt: ''`), which a `Date` field cannot represent. `strict: 'throw'` is deliberate —
105
+ * an unknown field must not slip in silently.
106
+ */
107
+ static createRequestSchema(): Schema<DyNTS_PrivacyRequestRecord_Interface>;
108
+ /** The contributor-plan schema. */
109
+ static createPlanSchema(): Schema<DyNTS_PrivacyStoredPlan_Row>;
110
+ /** The contributor-outcome schema. */
111
+ static createOutcomeSchema(): Schema<DyNTS_PrivacyStoredOutcome_Row>;
112
+ /**
113
+ * The contract shape from a stored row — an ALLOW-list.
114
+ *
115
+ * @description `.lean()` also returns the persistence's own fields (`_id`). Passing them on, the lifecycle's
116
+ * `{ ...request }` spread would write them back — the filtering keeps the domain object clean.
117
+ */
118
+ private toRequestRecord;
119
+ /** Opaque, but NOT encrypted, paging cursor — it holds no personal data. */
120
+ private encodeCursor;
121
+ /**
122
+ * Decodes the cursor — FAIL-SAFE: a damaged or forged cursor restarts paging on the FIRST page.
123
+ *
124
+ * @description Not a security hole: the `subjectId` + `organizationId` filter is independent of the cursor,
125
+ * so the subject still sees only their own requests.
126
+ */
127
+ private decodeCursor;
128
+ private getRequestModel;
129
+ private getPlanModel;
130
+ private getOutcomeModel;
131
+ /** Lazy one-time model resolution — a second `mongoose.model(name, schema)` for one name would throw. */
132
+ private resolveModel;
133
+ /** Mongo duplicate-key error — the signal that carries the "idempotent replay" branch. */
134
+ private isDuplicateKey;
135
+ }
136
+ //# sourceMappingURL=privacy-lifecycle.mongo-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-lifecycle.mongo-store.d.ts","sourceRoot":"","sources":["../../../src/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyNTS_PrivacyLifecycle_MongoStore = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importStar(require("mongoose"));
6
+ const DyNTS_privacyRequestModelName = 'DyNTSPrivacyRequest';
7
+ const DyNTS_privacyRequestPlanModelName = 'DyNTSPrivacyRequestPlan';
8
+ const DyNTS_privacyRequestOutcomeModelName = 'DyNTSPrivacyRequestOutcome';
9
+ /**
10
+ * Mongo persistence for the privacy lifecycle — the production `DyNTS_PrivacyLifecycleAdapter_Interface`.
11
+ *
12
+ * @description BFR-FUTDEVPRO-010 / BFR-ADVENTOR-025. Before this store the module shipped the contract only,
13
+ * so `DyNTS_PrivacyLifecycle_ControlService` (which REQUIRES an adapter) could not be started, and three
14
+ * products carried their own copies (company-toolmaker, futdevpro, adventor). This is the futdevpro copy,
15
+ * lifted verbatim in behaviour; the collection and model names are unchanged, so existing data stays readable.
16
+ *
17
+ * Atomicity does NOT need a transaction here — every guarantee is a single-document operation:
18
+ * - replay idempotency: the UNIQUE `idempotencyHash` index (the loser of a race reads the winner);
19
+ * - optimistic concurrency: `replaceRequest` puts `revision` INTO the filter, so a stale writer matches
20
+ * nothing and gets `false` (the caller turns that into a 409);
21
+ * - plan/outcome writes: an upsert on the UNIQUE `(requestId, contributorId)` pair.
22
+ *
23
+ * ⚠️ A broken revision check here is not "a bug": it is a lost or doubly executed erasure request —
24
+ * exactly the operation whose provability is the legal requirement (GDPR Art. 17).
25
+ *
26
+ * ⚠️ Consumers that carried a product-side copy must DELETE it in the same change-set that switches to this
27
+ * store: both register the same model names, and `resolveModel` silently returns whichever registered first.
28
+ */
29
+ class DyNTS_PrivacyLifecycle_MongoStore {
30
+ injected;
31
+ readiness;
32
+ /** Accepts isolated models for integration tests; production resolves the singletons lazily. */
33
+ constructor(set = {}) {
34
+ this.injected = set;
35
+ }
36
+ /**
37
+ * Creates the indexes BEFORE the first read or write.
38
+ *
39
+ * @description Not a convenience step: idempotency and optimistic concurrency both rest on the UNIQUE
40
+ * indexes. Without them a duplicate would not fail — it would silently become a second record.
41
+ */
42
+ async ensureReady() {
43
+ this.readiness ??= Promise.all([
44
+ this.getRequestModel().createIndexes(),
45
+ this.getPlanModel().createIndexes(),
46
+ this.getOutcomeModel().createIndexes(),
47
+ ])
48
+ .then(() => undefined)
49
+ .catch((error) => {
50
+ this.readiness = undefined;
51
+ throw error;
52
+ });
53
+ await this.readiness;
54
+ }
55
+ /**
56
+ * Creates one request, or returns the EXACT same request on an idempotent replay.
57
+ *
58
+ * @description The decision is not a racy "does it exist?" read (two parallel callers would both see
59
+ * "no"), but the UNIQUE index: the second writer gets a duplicate-key error and reads the winner.
60
+ */
61
+ async createOrGetRequest(request) {
62
+ try {
63
+ const created = await this.getRequestModel().create([request]);
64
+ const value = created[0]?.toObject();
65
+ if (!value) {
66
+ throw new Error('DYNTS|PRIVACY_LIFECYCLE|CREATE_EMPTY');
67
+ }
68
+ return { request: this.toRequestRecord(value), created: true };
69
+ }
70
+ catch (error) {
71
+ if (!this.isDuplicateKey(error)) {
72
+ throw error;
73
+ }
74
+ const existing = await this.getRequestModel()
75
+ .findOne({ idempotencyHash: request.idempotencyHash })
76
+ .lean().exec();
77
+ if (!existing) {
78
+ throw error;
79
+ }
80
+ return { request: this.toRequestRecord(existing), created: false };
81
+ }
82
+ }
83
+ /** Loads one request by id. Does not throw when missing — interpreting absence is the caller's job. */
84
+ async findRequestById(requestId) {
85
+ const found = await this.getRequestModel()
86
+ .findOne({ requestId: requestId })
87
+ .lean().exec();
88
+ return found ? this.toRequestRecord(found) : null;
89
+ }
90
+ /**
91
+ * One bounded, deterministic, newest-first page of the subject's own requests.
92
+ *
93
+ * @description Sorted by TWO keys (`createdAt`, then `requestId`): with a single key two requests created in
94
+ * the same millisecond would order non-deterministically and paging could skip or repeat a row
95
+ * (`createdAt` is ISO-8601 UTC text, so string order is time order). The cursor is opaque and carries no
96
+ * personal data — a timestamp and a machine id only.
97
+ */
98
+ async listRequests(query) {
99
+ const filter = {
100
+ subjectId: query.subjectId,
101
+ organizationId: query.organizationId,
102
+ };
103
+ const cursor = this.decodeCursor(query.cursor);
104
+ if (cursor) {
105
+ filter.$or = [
106
+ { createdAt: { $lt: cursor.createdAt } },
107
+ { createdAt: cursor.createdAt, requestId: { $lt: cursor.requestId } },
108
+ ];
109
+ }
110
+ // One MORE than the page size: "is there a next page?" is answered without a separate
111
+ // (and non-consistent) countDocuments call.
112
+ const found = await this.getRequestModel().find(filter)
113
+ .sort({ createdAt: -1, requestId: -1 })
114
+ .limit(query.limit + 1)
115
+ .lean().exec();
116
+ const page = found.slice(0, query.limit)
117
+ .map((row) => this.toRequestRecord(row));
118
+ const last = page[page.length - 1];
119
+ return {
120
+ requests: page,
121
+ nextCursor: found.length > query.limit && last ? this.encodeCursor(last) : '',
122
+ };
123
+ }
124
+ /**
125
+ * State transition with OPTIMISTIC CONCURRENCY.
126
+ *
127
+ * @description `revision` is part of the filter: if someone else rewrote the record meanwhile, the filter
128
+ * matches nothing and `false` is returned. That `false` is not an error — it is the correct answer to a
129
+ * race, and the caller turns it into a 409.
130
+ */
131
+ async replaceRequest(request, expectedRevision) {
132
+ const result = await this.getRequestModel().replaceOne({ requestId: request.requestId, revision: expectedRevision }, this.toRequestRecord(request)).exec();
133
+ return result.matchedCount === 1;
134
+ }
135
+ /**
136
+ * Stores one contributor plan.
137
+ *
138
+ * @description Overwrites, does not insert: a resumed run may deliver the same plan again. The upsert on the
139
+ * `(requestId, contributorId)` pair keeps it idempotent — never two plans for one contributor.
140
+ */
141
+ async saveContributorPlan(requestId, plan) {
142
+ await this.getPlanModel().updateOne({ requestId: requestId, contributorId: plan.contributorId }, { $set: { ...plan, requestId: requestId } }, { upsert: true }).exec();
143
+ }
144
+ /** All contributor plans of one request, in a stable order. */
145
+ async listContributorPlans(requestId) {
146
+ const rows = await this.getPlanModel().find({ requestId: requestId })
147
+ .sort({ contributorId: 1 }).lean().exec();
148
+ return rows.map((row) => ({
149
+ contributorId: row.contributorId,
150
+ action: row.action,
151
+ retentionBasisCode: row.retentionBasisCode,
152
+ }));
153
+ }
154
+ /**
155
+ * Stores one contributor outcome.
156
+ *
157
+ * @description The overwrite is not only for resumed runs: on a failed verification the lifecycle
158
+ * DELIBERATELY replaces an earlier (successful) outcome with a failed one — an insert-only store would make
159
+ * the request look falsely successful.
160
+ */
161
+ async saveContributorOutcome(requestId, outcome) {
162
+ await this.getOutcomeModel().updateOne({ requestId: requestId, contributorId: outcome.contributorId }, { $set: { ...outcome, requestId: requestId } }, { upsert: true }).exec();
163
+ }
164
+ /** All contributor outcomes of one request, in a stable order. */
165
+ async listContributorOutcomes(requestId) {
166
+ const rows = await this.getOutcomeModel().find({ requestId: requestId })
167
+ .sort({ contributorId: 1 }).lean().exec();
168
+ return rows.map((row) => ({
169
+ contributorId: row.contributorId,
170
+ action: row.action,
171
+ outcome: row.outcome,
172
+ retentionBasisCode: row.retentionBasisCode,
173
+ artifactChecksum: row.artifactChecksum,
174
+ processedRecordCount: row.processedRecordCount,
175
+ errorCode: row.errorCode,
176
+ completedAt: row.completedAt,
177
+ }));
178
+ }
179
+ /**
180
+ * The request-record schema.
181
+ *
182
+ * @description Date fields are `String`, not `Date`: the contract uses an EMPTY string for "has not happened
183
+ * yet" (`identityVerifiedAt: ''`), which a `Date` field cannot represent. `strict: 'throw'` is deliberate —
184
+ * an unknown field must not slip in silently.
185
+ */
186
+ static createRequestSchema() {
187
+ const schema = new mongoose_1.Schema({
188
+ requestId: { type: String, required: true, immutable: true, maxlength: 128 },
189
+ subjectId: { type: String, required: true, immutable: true, maxlength: 128 },
190
+ organizationId: { type: String, required: true, immutable: true, maxlength: 128 },
191
+ type: { type: String, required: true, immutable: true, maxlength: 64 },
192
+ state: { type: String, required: true, maxlength: 64 },
193
+ idempotencyHash: { type: String, required: true, immutable: true, match: /^[a-f0-9]{64}$/u },
194
+ revision: { type: Number, required: true, min: 0, validate: Number.isSafeInteger },
195
+ createdAt: { type: String, required: true, immutable: true, maxlength: 64 },
196
+ deadlineAt: { type: String, required: true, maxlength: 64 },
197
+ identityVerifiedAt: { type: String, default: '', maxlength: 64 },
198
+ scopedAt: { type: String, default: '', maxlength: 64 },
199
+ plannedAt: { type: String, default: '', maxlength: 64 },
200
+ executionStartedAt: { type: String, default: '', maxlength: 64 },
201
+ verifyingStartedAt: { type: String, default: '', maxlength: 64 },
202
+ completedAt: { type: String, default: '', maxlength: 64 },
203
+ cancelledAt: { type: String, default: '', maxlength: 64 },
204
+ }, {
205
+ collection: 'dynts_privacy_requests',
206
+ strict: 'throw',
207
+ versionKey: false,
208
+ });
209
+ schema.index({ idempotencyHash: 1 }, { unique: true, name: 'uq_dynts_privacy_request_idempotency' });
210
+ schema.index({ requestId: 1 }, { unique: true, name: 'uq_dynts_privacy_request_id' });
211
+ schema.index({ subjectId: 1, organizationId: 1, createdAt: -1, requestId: -1 }, { name: 'ix_dynts_privacy_request_subject_page' });
212
+ return schema;
213
+ }
214
+ /** The contributor-plan schema. */
215
+ static createPlanSchema() {
216
+ const schema = new mongoose_1.Schema({
217
+ requestId: { type: String, required: true, immutable: true, maxlength: 128 },
218
+ contributorId: { type: String, required: true, immutable: true, maxlength: 128 },
219
+ action: { type: String, required: true, maxlength: 64 },
220
+ retentionBasisCode: { type: String, default: '', maxlength: 128 },
221
+ }, {
222
+ collection: 'dynts_privacy_request_plans',
223
+ strict: 'throw',
224
+ versionKey: false,
225
+ });
226
+ schema.index({ requestId: 1, contributorId: 1 }, { unique: true, name: 'uq_dynts_privacy_plan_scope' });
227
+ return schema;
228
+ }
229
+ /** The contributor-outcome schema. */
230
+ static createOutcomeSchema() {
231
+ const schema = new mongoose_1.Schema({
232
+ requestId: { type: String, required: true, immutable: true, maxlength: 128 },
233
+ contributorId: { type: String, required: true, immutable: true, maxlength: 128 },
234
+ action: { type: String, required: true, maxlength: 64 },
235
+ outcome: { type: String, required: true, maxlength: 64 },
236
+ retentionBasisCode: { type: String, default: '', maxlength: 128 },
237
+ artifactChecksum: { type: String, default: '', maxlength: 128 },
238
+ processedRecordCount: { type: Number, required: true, min: 0, validate: Number.isSafeInteger },
239
+ errorCode: { type: String, default: '', maxlength: 128 },
240
+ completedAt: { type: String, required: true, maxlength: 64 },
241
+ }, {
242
+ collection: 'dynts_privacy_request_outcomes',
243
+ strict: 'throw',
244
+ versionKey: false,
245
+ });
246
+ schema.index({ requestId: 1, contributorId: 1 }, { unique: true, name: 'uq_dynts_privacy_outcome_scope' });
247
+ return schema;
248
+ }
249
+ /**
250
+ * The contract shape from a stored row — an ALLOW-list.
251
+ *
252
+ * @description `.lean()` also returns the persistence's own fields (`_id`). Passing them on, the lifecycle's
253
+ * `{ ...request }` spread would write them back — the filtering keeps the domain object clean.
254
+ */
255
+ toRequestRecord(row) {
256
+ return {
257
+ requestId: row.requestId,
258
+ subjectId: row.subjectId,
259
+ organizationId: row.organizationId,
260
+ type: row.type,
261
+ state: row.state,
262
+ idempotencyHash: row.idempotencyHash,
263
+ revision: row.revision,
264
+ createdAt: row.createdAt,
265
+ deadlineAt: row.deadlineAt,
266
+ identityVerifiedAt: row.identityVerifiedAt ?? '',
267
+ scopedAt: row.scopedAt ?? '',
268
+ plannedAt: row.plannedAt ?? '',
269
+ executionStartedAt: row.executionStartedAt ?? '',
270
+ verifyingStartedAt: row.verifyingStartedAt ?? '',
271
+ completedAt: row.completedAt ?? '',
272
+ cancelledAt: row.cancelledAt ?? '',
273
+ };
274
+ }
275
+ /** Opaque, but NOT encrypted, paging cursor — it holds no personal data. */
276
+ encodeCursor(record) {
277
+ return Buffer.from(`${record.createdAt}|${record.requestId}`, 'utf8').toString('base64url');
278
+ }
279
+ /**
280
+ * Decodes the cursor — FAIL-SAFE: a damaged or forged cursor restarts paging on the FIRST page.
281
+ *
282
+ * @description Not a security hole: the `subjectId` + `organizationId` filter is independent of the cursor,
283
+ * so the subject still sees only their own requests.
284
+ */
285
+ decodeCursor(cursor) {
286
+ if (!cursor) {
287
+ return undefined;
288
+ }
289
+ const parts = Buffer.from(cursor, 'base64url').toString('utf8').split('|');
290
+ return parts.length === 2 && parts[0] && parts[1]
291
+ ? { createdAt: parts[0], requestId: parts[1] }
292
+ : undefined;
293
+ }
294
+ getRequestModel() {
295
+ return this.injected.requestModel ?? this.resolveModel(DyNTS_privacyRequestModelName, DyNTS_PrivacyLifecycle_MongoStore.createRequestSchema());
296
+ }
297
+ getPlanModel() {
298
+ return this.injected.planModel ?? this.resolveModel(DyNTS_privacyRequestPlanModelName, DyNTS_PrivacyLifecycle_MongoStore.createPlanSchema());
299
+ }
300
+ getOutcomeModel() {
301
+ return this.injected.outcomeModel ?? this.resolveModel(DyNTS_privacyRequestOutcomeModelName, DyNTS_PrivacyLifecycle_MongoStore.createOutcomeSchema());
302
+ }
303
+ /** Lazy one-time model resolution — a second `mongoose.model(name, schema)` for one name would throw. */
304
+ resolveModel(name, schema) {
305
+ return mongoose_1.default.modelNames().includes(name)
306
+ ? mongoose_1.default.model(name)
307
+ : mongoose_1.default.model(name, schema);
308
+ }
309
+ /** Mongo duplicate-key error — the signal that carries the "idempotent replay" branch. */
310
+ isDuplicateKey(error) {
311
+ return typeof error === 'object' && error !== null && 'code' in error
312
+ && Object.getOwnPropertyDescriptor(error, 'code')?.value === 11000;
313
+ }
314
+ }
315
+ exports.DyNTS_PrivacyLifecycle_MongoStore = DyNTS_PrivacyLifecycle_MongoStore;
316
+ //# sourceMappingURL=privacy-lifecycle.mongo-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-lifecycle.mongo-store.js","sourceRoot":"","sources":["../../../src/_modules/privacy-lifecycle/privacy-lifecycle.mongo-store.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  export * from './usage.controller';
2
+ export * from './usage-session-owner.interface';
3
+ export * from './usage-session-account-query.interface';
2
4
  export * from './usage.data-service';
3
5
  export * from './get-usage-routing-module.util';
4
6
  export * from '@futdevpro/fsm-dynamo/usage';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/index.ts"],"names":[],"mappings":"AAUA,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/index.ts"],"names":[],"mappings":"AAUA,cAAc,oBAAoB,CAAC;AACnC,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,sBAAsB,CAAC;AACrC,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC"}
@@ -8,6 +8,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const tslib_1 = require("tslib");
9
9
  // USAGE MODULE
10
10
  tslib_1.__exportStar(require("./usage.controller"), exports);
11
+ tslib_1.__exportStar(require("./usage-session-owner.interface"), exports);
12
+ tslib_1.__exportStar(require("./usage-session-account-query.interface"), exports);
11
13
  tslib_1.__exportStar(require("./usage.data-service"), exports);
12
14
  tslib_1.__exportStar(require("./get-usage-routing-module.util"), exports);
13
15
  tslib_1.__exportStar(require("@futdevpro/fsm-dynamo/usage"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/usage/index.ts"],"names":[],"mappings":";AAEA,EAAE;AACF,eAAe;AACf,EAAE;AACF,qEAAqE;AACrE,EAAE;;;AAGF,eAAe;AACf,6DAAmC;AACnC,+DAAqC;AACrC,0EAAgD;AAChD,sEAA4C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/usage/index.ts"],"names":[],"mappings":";AAEA,EAAE;AACF,eAAe;AACf,EAAE;AACF,qEAAqE;AACrE,EAAE;;;AAGF,eAAe;AACf,6DAAmC;AACnC,0EAAgD;AAChD,kFAAwD;AACxD,+DAAqC;AACrC,0EAAgD;AAChD,sEAA4C"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Identifies the sessions of one account (BFR-FUTDEVPRO-015).
3
+ */
4
+ export interface DyNTS_UsageSession_AccountQuery {
5
+ /** Account id — sessions since nts-dynamo 1.15.320 store it in `issuer` */
6
+ accountId: string;
7
+ /**
8
+ * Usernames the account has used. Sessions written BEFORE 1.15.320 stored the username in `issuer`
9
+ * (and nothing in `loggedInAs`), so without these the legacy sessions are NOT found.
10
+ */
11
+ legacyUsernames?: string[];
12
+ }
13
+ //# sourceMappingURL=usage-session-account-query.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-session-account-query.interface.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/usage-session-account-query.interface.ts"],"names":[],"mappings":"AACA;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=usage-session-account-query.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-session-account-query.interface.js","sourceRoot":"","sources":["../../../src/_modules/usage/usage-session-account-query.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The owner of a usage session, as resolved from the incoming request (BFR-FUTDEVPRO-015).
3
+ *
4
+ * - `accountId` is what goes into the session's `issuer` (`fdp-issuer-is-account-id`); `'unknown'` when
5
+ * the request carries no resolvable account (anonymous visitor, or optional-auth mode).
6
+ * - `username` goes into `loggedInAs`, and ONLY when an account was resolved — it exists so that legacy
7
+ * sessions (written before 1.15.320 with `issuer: <username>`) can still be linked to their account.
8
+ */
9
+ export interface DyNTS_UsageSessionOwner {
10
+ /** Account id of the session owner, or `'unknown'` */
11
+ accountId: string;
12
+ /** Username of the session owner; `undefined` for anonymous sessions */
13
+ username?: string;
14
+ }
15
+ //# sourceMappingURL=usage-session-owner.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-session-owner.interface.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/usage-session-owner.interface.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=usage-session-owner.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-session-owner.interface.js","sourceRoot":"","sources":["../../../src/_modules/usage/usage-session-owner.interface.ts"],"names":[],"mappings":""}
@@ -1,7 +1,23 @@
1
+ import { Request } from 'express';
1
2
  import { DyNTS_Controller } from '../../_services/route/controller.service';
3
+ import { DyNTS_UsageSessionOwner } from './usage-session-owner.interface';
2
4
  export declare class DyNTS_Usage_Controller extends DyNTS_Controller {
3
5
  static getInstance(): DyNTS_Usage_Controller;
4
6
  private authService;
5
7
  setupEndpoints(): void;
8
+ /**
9
+ * Resolves the owner of a new usage session (BFR-FUTDEVPRO-015).
10
+ *
11
+ * The `issuer` of a session is ALWAYS the account id (`fdp-issuer-is-account-id`) — before this fix it was
12
+ * the username, so account erasure could not find the sessions (which carry the visitor's IP address).
13
+ * The username is kept in `loggedInAs` only for logged-in sessions, as a bridge to legacy records.
14
+ *
15
+ * Never throws: the endpoint is public, so an absent/invalid token or a missing auth service
16
+ * (optional-auth mode, BFR-FDPPAL-001) yields an anonymous `'unknown'` owner.
17
+ *
18
+ * @param req the incoming request
19
+ * @returns the resolved owner
20
+ */
21
+ getSessionOwnerFromRequest(req: Request): DyNTS_UsageSessionOwner;
6
22
  }
7
23
  //# sourceMappingURL=usage.controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage.controller.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/usage.controller.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAG5E,qBAAa,sBAAuB,SAAQ,gBAAgB;IAE1D,MAAM,CAAC,WAAW,IAAI,sBAAsB;IAK5C,OAAO,CAAC,WAAW,CAAoB;IAEvC,cAAc,IAAI,IAAI;CAiHvB"}
1
+ {"version":3,"file":"usage.controller.d.ts","sourceRoot":"","sources":["../../../src/_modules/usage/usage.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAY,MAAM,SAAS,CAAC;AAY5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG1E,qBAAa,sBAAuB,SAAQ,gBAAgB;IAE1D,MAAM,CAAC,WAAW,IAAI,sBAAsB;IAK5C,OAAO,CAAC,WAAW,CAAoB;IAEvC,cAAc,IAAI,IAAI;IA8GtB;;;;;;;;;;;;OAYG;IACH,0BAA0B,CAAC,GAAG,EAAE,OAAO,GAAG,uBAAuB;CA4BlE"}