@echovisionlab/geul-telemetry 0.1.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 (58) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +38 -0
  3. package/fixtures/audit-records.json +316 -0
  4. package/fixtures/collaboration-checkpoint-entity-types.json +15 -0
  5. package/fixtures/domain-audit-actions.json +132 -0
  6. package/fixtures/domain-audit-wire-parity.json +1751 -0
  7. package/fixtures/http-request-results.json +21 -0
  8. package/fixtures/job-catalog.json +23 -0
  9. package/fixtures/locale-content-audit.json +126 -0
  10. package/fixtures/log-redaction.json +72 -0
  11. package/fixtures/request-record.json +14 -0
  12. package/fixtures/security-access-records.json +61 -0
  13. package/fixtures/service-identities.json +11 -0
  14. package/fixtures/system-catalog.json +28 -0
  15. package/fixtures/translation-job-terminal-catalog.json +45 -0
  16. package/fixtures/translation-settings-fields.json +1 -0
  17. package/package.json +61 -0
  18. package/policy/log-redaction.json +113 -0
  19. package/src/actor.ts +81 -0
  20. package/src/audit/account.ts +206 -0
  21. package/src/audit/actor-policy.ts +63 -0
  22. package/src/audit/artist-label.ts +312 -0
  23. package/src/audit/attributes.ts +107 -0
  24. package/src/audit/builder.ts +25 -0
  25. package/src/audit/catalog.ts +116 -0
  26. package/src/audit/content.ts +723 -0
  27. package/src/audit/email-authoring.ts +220 -0
  28. package/src/audit/file.ts +168 -0
  29. package/src/audit/form.ts +229 -0
  30. package/src/audit/integrations.ts +207 -0
  31. package/src/audit/locale-code.ts +6 -0
  32. package/src/audit/locale-content.ts +358 -0
  33. package/src/audit/member-account.ts +313 -0
  34. package/src/audit/member.ts +171 -0
  35. package/src/audit/program.ts +339 -0
  36. package/src/audit/reference-data.ts +230 -0
  37. package/src/audit/reference-entities.ts +332 -0
  38. package/src/audit/relation-download-policy.ts +292 -0
  39. package/src/audit/release-campaign.ts +422 -0
  40. package/src/audit/series-type.ts +298 -0
  41. package/src/audit/settings.ts +306 -0
  42. package/src/audit/source-locale.ts +337 -0
  43. package/src/audit/types.ts +9 -0
  44. package/src/audit.ts +21 -0
  45. package/src/authentication.ts +183 -0
  46. package/src/error-metadata.ts +11 -0
  47. package/src/index.ts +11 -0
  48. package/src/job.ts +56 -0
  49. package/src/log-attribute-policy.ts +21 -0
  50. package/src/otel.ts +64 -0
  51. package/src/records.ts +1244 -0
  52. package/src/redaction.ts +35 -0
  53. package/src/request-context.ts +73 -0
  54. package/src/request-id.ts +13 -0
  55. package/src/request-result.ts +73 -0
  56. package/src/request.ts +60 -0
  57. package/src/system.ts +355 -0
  58. package/src/trace.ts +13 -0
@@ -0,0 +1,332 @@
1
+ import type {
2
+ AuditAssetSlot,
3
+ AuditCollectionOperation,
4
+ AuditRecord,
5
+ AuditState,
6
+ } from "../records.ts";
7
+ import {
8
+ assertOnlyAuditAttributes,
9
+ canonicalAuditValues,
10
+ } from "./attributes.ts";
11
+ import { buildAuditRecord } from "./builder.ts";
12
+ import {
13
+ AUDIT_ACTIONS,
14
+ AUDIT_CATALOG,
15
+ requireCatalogTarget,
16
+ } from "./catalog.ts";
17
+ import type { AuditMetadata } from "./types.ts";
18
+
19
+ type ReferenceEntityTarget =
20
+ "client" | "map_place" | "audience_segment" | "menu";
21
+ type ReferenceEntityAction = Extract<
22
+ (typeof AUDIT_ACTIONS)[number],
23
+ | "client.created"
24
+ | "client.updated"
25
+ | "client.deleted"
26
+ | "map_place.created"
27
+ | "map_place.updated"
28
+ | "map_place.deleted"
29
+ | "audience_segment.created"
30
+ | "audience_segment.updated"
31
+ | "menu.created"
32
+ | "menu.updated"
33
+ | "menu.deleted"
34
+ >;
35
+ export type ClientMetadataField = "name" | "website";
36
+ export type MapPlaceMetadataField =
37
+ "address" | "address_components" | "google_place_id" | "lat" | "lng" | "name";
38
+ export type AudienceSegmentConfigField =
39
+ | "account_roles"
40
+ | "created_after"
41
+ | "created_before"
42
+ | "description"
43
+ | "exclude_member_ids"
44
+ | "member_tag_ids"
45
+ | "name"
46
+ | "segment_type";
47
+ export type MenuSourceField = "items" | "name";
48
+ export type AudienceSegmentLifecycleState = Extract<
49
+ AuditState,
50
+ "active" | "archived"
51
+ >;
52
+
53
+ const targetTypes = new Set<ReferenceEntityTarget>([
54
+ "client",
55
+ "map_place",
56
+ "audience_segment",
57
+ "menu",
58
+ ]);
59
+ // Module ownership follows the immutable catalog mapping, not a duplicate action-target table.
60
+ const referenceEntityActions = new Set<ReferenceEntityAction>(
61
+ AUDIT_ACTIONS.filter((action) =>
62
+ targetTypes.has(AUDIT_CATALOG[action] as ReferenceEntityTarget),
63
+ ) as ReferenceEntityAction[],
64
+ );
65
+ const clientMetadataFields = new Set<ClientMetadataField>(["name", "website"]);
66
+ const mapPlaceMetadataFields = new Set<MapPlaceMetadataField>([
67
+ "address",
68
+ "address_components",
69
+ "google_place_id",
70
+ "lat",
71
+ "lng",
72
+ "name",
73
+ ]);
74
+ const audienceSegmentConfigFields = new Set<AudienceSegmentConfigField>([
75
+ "account_roles",
76
+ "created_after",
77
+ "created_before",
78
+ "description",
79
+ "exclude_member_ids",
80
+ "member_tag_ids",
81
+ "name",
82
+ "segment_type",
83
+ ]);
84
+ const menuSourceFields = new Set<MenuSourceField>(["items", "name"]);
85
+ function buildReferenceEntityAuditRecord(
86
+ metadata: AuditMetadata,
87
+ action: ReferenceEntityAction,
88
+ targetId: string,
89
+ attributes: Parameters<typeof buildAuditRecord>[2] = {},
90
+ ): AuditRecord {
91
+ return buildAuditRecord(
92
+ metadata,
93
+ { action, target_type: AUDIT_CATALOG[action], target_id: targetId },
94
+ attributes,
95
+ );
96
+ }
97
+ function requireFields(
98
+ record: AuditRecord,
99
+ allowed: ReadonlySet<string>,
100
+ ): void {
101
+ const fields = record.changed_fields;
102
+ if (
103
+ !fields?.length ||
104
+ fields.some(
105
+ (field, index) =>
106
+ !allowed.has(field) || (index > 0 && fields[index - 1] >= field),
107
+ )
108
+ )
109
+ throw new TypeError("invalid or non-canonical changed_fields");
110
+ }
111
+ function requireIdentifier(name: string, value: string | undefined): void {
112
+ if (!value || value.length > 255 || value.trim() !== value)
113
+ throw new TypeError(`${name} requires an identifier`);
114
+ }
115
+ function requireNoExtra(record: AuditRecord, allowed: readonly string[]): void {
116
+ assertOnlyAuditAttributes(record, allowed);
117
+ }
118
+ function requireFileBinding(record: AuditRecord, requiresSlot: boolean): void {
119
+ if (
120
+ record.collection_operation !== "added" &&
121
+ record.collection_operation !== "removed"
122
+ )
123
+ throw new TypeError("file binding requires collection_operation");
124
+ requireIdentifier("file_id", record.file_id);
125
+ if (
126
+ requiresSlot &&
127
+ record.asset_slot !== "light" &&
128
+ record.asset_slot !== "dark"
129
+ )
130
+ throw new TypeError("client logo requires asset_slot");
131
+ requireNoExtra(
132
+ record,
133
+ requiresSlot
134
+ ? ["changed_fields", "collection_operation", "asset_slot", "file_id"]
135
+ : ["changed_fields", "collection_operation", "file_id"],
136
+ );
137
+ }
138
+
139
+ /** Returns true only for Client, Map Place, Audience Segment, and Menu actions. */
140
+ export function validateReferenceEntityAuditRecord(
141
+ record: AuditRecord,
142
+ ): boolean {
143
+ if (!referenceEntityActions.has(record.action as ReferenceEntityAction))
144
+ return false;
145
+ requireCatalogTarget(record.action, record.target_type, record.target_id);
146
+ if (!record.action.endsWith(".updated")) {
147
+ requireNoExtra(record, []);
148
+ return true;
149
+ }
150
+ switch (record.action) {
151
+ case "client.updated":
152
+ if (
153
+ record.changed_fields?.length === 1 &&
154
+ record.changed_fields[0] === "logo"
155
+ ) {
156
+ requireFileBinding(record, true);
157
+ break;
158
+ }
159
+ requireFields(record, clientMetadataFields);
160
+ requireNoExtra(record, ["changed_fields"]);
161
+ break;
162
+ case "map_place.updated":
163
+ if (
164
+ record.changed_fields?.length === 1 &&
165
+ record.changed_fields[0] === "image"
166
+ ) {
167
+ requireFileBinding(record, false);
168
+ break;
169
+ }
170
+ requireFields(record, mapPlaceMetadataFields);
171
+ requireNoExtra(record, ["changed_fields"]);
172
+ break;
173
+ case "audience_segment.updated":
174
+ if (
175
+ record.changed_fields?.length === 1 &&
176
+ record.changed_fields[0] === "status"
177
+ ) {
178
+ if (!(
179
+ (record.previous_state === "active" &&
180
+ record.new_state === "archived") ||
181
+ (record.previous_state === "archived" &&
182
+ record.new_state === "active")
183
+ ))
184
+ throw new TypeError(
185
+ "audience status requires active/archived transition",
186
+ );
187
+ requireNoExtra(record, [
188
+ "changed_fields",
189
+ "previous_state",
190
+ "new_state",
191
+ ]);
192
+ break;
193
+ }
194
+ requireFields(record, audienceSegmentConfigFields);
195
+ requireNoExtra(record, ["changed_fields"]);
196
+ break;
197
+ case "menu.updated":
198
+ requireFields(record, menuSourceFields);
199
+ requireNoExtra(record, ["changed_fields"]);
200
+ break;
201
+ }
202
+ return true;
203
+ }
204
+
205
+ export function buildClientCreatedAuditRecord(
206
+ metadata: AuditMetadata,
207
+ id: string,
208
+ ): AuditRecord {
209
+ return buildReferenceEntityAuditRecord(metadata, "client.created", id);
210
+ }
211
+ export function buildClientMetadataUpdatedAuditRecord(
212
+ metadata: AuditMetadata,
213
+ id: string,
214
+ fields: readonly ClientMetadataField[],
215
+ ): AuditRecord {
216
+ return buildReferenceEntityAuditRecord(metadata, "client.updated", id, {
217
+ changed_fields: canonicalAuditValues(fields),
218
+ });
219
+ }
220
+ export function buildClientLogoUpdatedAuditRecord(
221
+ metadata: AuditMetadata,
222
+ id: string,
223
+ slot: AuditAssetSlot,
224
+ operation: AuditCollectionOperation,
225
+ fileId: string,
226
+ ): AuditRecord {
227
+ return buildReferenceEntityAuditRecord(metadata, "client.updated", id, {
228
+ changed_fields: ["logo"],
229
+ asset_slot: slot,
230
+ collection_operation: operation,
231
+ file_id: fileId,
232
+ });
233
+ }
234
+ export function buildClientDeletedAuditRecord(
235
+ metadata: AuditMetadata,
236
+ id: string,
237
+ ): AuditRecord {
238
+ return buildReferenceEntityAuditRecord(metadata, "client.deleted", id);
239
+ }
240
+ export function buildMapPlaceCreatedAuditRecord(
241
+ metadata: AuditMetadata,
242
+ id: string,
243
+ ): AuditRecord {
244
+ return buildReferenceEntityAuditRecord(metadata, "map_place.created", id);
245
+ }
246
+ export function buildMapPlaceMetadataUpdatedAuditRecord(
247
+ metadata: AuditMetadata,
248
+ id: string,
249
+ fields: readonly MapPlaceMetadataField[],
250
+ ): AuditRecord {
251
+ return buildReferenceEntityAuditRecord(metadata, "map_place.updated", id, {
252
+ changed_fields: canonicalAuditValues(fields),
253
+ });
254
+ }
255
+ export function buildMapPlaceImageUpdatedAuditRecord(
256
+ metadata: AuditMetadata,
257
+ id: string,
258
+ operation: AuditCollectionOperation,
259
+ fileId: string,
260
+ ): AuditRecord {
261
+ return buildReferenceEntityAuditRecord(metadata, "map_place.updated", id, {
262
+ changed_fields: ["image"],
263
+ collection_operation: operation,
264
+ file_id: fileId,
265
+ });
266
+ }
267
+ export function buildMapPlaceDeletedAuditRecord(
268
+ metadata: AuditMetadata,
269
+ id: string,
270
+ ): AuditRecord {
271
+ return buildReferenceEntityAuditRecord(metadata, "map_place.deleted", id);
272
+ }
273
+ export function buildAudienceSegmentCreatedAuditRecord(
274
+ metadata: AuditMetadata,
275
+ id: string,
276
+ ): AuditRecord {
277
+ return buildReferenceEntityAuditRecord(
278
+ metadata,
279
+ "audience_segment.created",
280
+ id,
281
+ );
282
+ }
283
+ export function buildAudienceSegmentConfigUpdatedAuditRecord(
284
+ metadata: AuditMetadata,
285
+ id: string,
286
+ fields: readonly AudienceSegmentConfigField[],
287
+ ): AuditRecord {
288
+ return buildReferenceEntityAuditRecord(
289
+ metadata,
290
+ "audience_segment.updated",
291
+ id,
292
+ { changed_fields: canonicalAuditValues(fields) },
293
+ );
294
+ }
295
+ export function buildAudienceSegmentLifecycleUpdatedAuditRecord(
296
+ metadata: AuditMetadata,
297
+ id: string,
298
+ previousState: AudienceSegmentLifecycleState,
299
+ newState: AudienceSegmentLifecycleState,
300
+ ): AuditRecord {
301
+ return buildReferenceEntityAuditRecord(
302
+ metadata,
303
+ "audience_segment.updated",
304
+ id,
305
+ {
306
+ changed_fields: ["status"],
307
+ previous_state: previousState,
308
+ new_state: newState,
309
+ },
310
+ );
311
+ }
312
+ export function buildMenuCreatedAuditRecord(
313
+ metadata: AuditMetadata,
314
+ id: string,
315
+ ): AuditRecord {
316
+ return buildReferenceEntityAuditRecord(metadata, "menu.created", id);
317
+ }
318
+ export function buildMenuSourceUpdatedAuditRecord(
319
+ metadata: AuditMetadata,
320
+ id: string,
321
+ fields: readonly MenuSourceField[],
322
+ ): AuditRecord {
323
+ return buildReferenceEntityAuditRecord(metadata, "menu.updated", id, {
324
+ changed_fields: canonicalAuditValues(fields),
325
+ });
326
+ }
327
+ export function buildMenuDeletedAuditRecord(
328
+ metadata: AuditMetadata,
329
+ id: string,
330
+ ): AuditRecord {
331
+ return buildReferenceEntityAuditRecord(metadata, "menu.deleted", id);
332
+ }
@@ -0,0 +1,292 @@
1
+ import type { AuditRecord, AuditState } from "../records.ts";
2
+ import {
3
+ assertOnlyAuditAttributes,
4
+ canonicalAuditValues,
5
+ } from "./attributes.ts";
6
+ import { buildAuditRecord } from "./builder.ts";
7
+ import { AUDIT_CATALOG } from "./catalog.ts";
8
+ import type { AuditMetadata } from "./types.ts";
9
+
10
+ export type RelationDownloadPolicyChangedField =
11
+ "file_download_audience" | "file_download_audience_segment_ids";
12
+ export type FileDownloadAudience =
13
+ "disabled" | "public" | "authenticated" | "restricted";
14
+
15
+ const fields = new Set<RelationDownloadPolicyChangedField>([
16
+ "file_download_audience",
17
+ "file_download_audience_segment_ids",
18
+ ]);
19
+ const audiences = new Set<FileDownloadAudience>([
20
+ "disabled",
21
+ "public",
22
+ "authenticated",
23
+ "restricted",
24
+ ]);
25
+
26
+ function requireIdentifier(name: string, value: string | undefined): void {
27
+ if (!value || value.length > 255 || value.trim() !== value)
28
+ throw new TypeError(`${name} requires an identifier`);
29
+ }
30
+
31
+ function requireIdentifiers(
32
+ name: string,
33
+ values: readonly string[] | undefined,
34
+ ): void {
35
+ if (
36
+ values === undefined ||
37
+ values.some(
38
+ (value, index) =>
39
+ value === "" ||
40
+ value.length > 255 ||
41
+ value.trim() !== value ||
42
+ (index > 0 && values[index - 1] >= value),
43
+ )
44
+ )
45
+ throw new TypeError(`${name} requires sorted, unique identifiers`);
46
+ }
47
+
48
+ export function isRelationDownloadPolicyAuditRecord(
49
+ record: AuditRecord,
50
+ ): boolean {
51
+ return Boolean(
52
+ record.changed_fields?.some((field) =>
53
+ fields.has(field as RelationDownloadPolicyChangedField),
54
+ ),
55
+ );
56
+ }
57
+
58
+ export function validateRelationDownloadPolicyAuditRecord(
59
+ record: AuditRecord,
60
+ ): void {
61
+ const changedFields = record.changed_fields;
62
+ if (
63
+ !changedFields?.length ||
64
+ changedFields.some(
65
+ (field, index) =>
66
+ !fields.has(field as RelationDownloadPolicyChangedField) ||
67
+ (index > 0 && changedFields[index - 1] >= field),
68
+ )
69
+ )
70
+ throw new TypeError("invalid download policy changed_fields");
71
+ if (record.actor_kind !== "member")
72
+ throw new TypeError("download policy requires member actor");
73
+ requireIdentifier("item_id", record.item_id);
74
+ requireIdentifier("file_id", record.file_id);
75
+ const hasAudience = changedFields.includes("file_download_audience");
76
+ const hasSegments = changedFields.includes(
77
+ "file_download_audience_segment_ids",
78
+ );
79
+ if (hasAudience) {
80
+ if (
81
+ !audiences.has(record.previous_state as FileDownloadAudience) ||
82
+ !audiences.has(record.new_state as FileDownloadAudience) ||
83
+ record.previous_state === record.new_state
84
+ )
85
+ throw new TypeError("download policy audience requires a transition");
86
+ } else if (
87
+ record.previous_state !== undefined ||
88
+ record.new_state !== undefined
89
+ ) {
90
+ throw new TypeError("download policy states require audience field");
91
+ }
92
+ if (hasSegments) {
93
+ requireIdentifiers("previous_item_ids", record.previous_item_ids);
94
+ requireIdentifiers("item_ids", record.item_ids);
95
+ if (
96
+ JSON.stringify(record.previous_item_ids) ===
97
+ JSON.stringify(record.item_ids)
98
+ )
99
+ throw new TypeError("download policy segments require a transition");
100
+ } else if (
101
+ record.previous_item_ids !== undefined ||
102
+ record.item_ids !== undefined
103
+ ) {
104
+ throw new TypeError("download policy segment IDs require segment field");
105
+ }
106
+ assertOnlyAuditAttributes(record, [
107
+ "changed_fields",
108
+ "item_id",
109
+ "file_id",
110
+ "previous_state",
111
+ "new_state",
112
+ "previous_item_ids",
113
+ "item_ids",
114
+ ]);
115
+ }
116
+
117
+ type RelationDownloadPolicyAction =
118
+ | "post.updated"
119
+ | "page.updated"
120
+ | "work.updated"
121
+ | "program_event.updated"
122
+ | "release.updated";
123
+
124
+ function buildRelationDownloadPolicyAuditRecord(
125
+ metadata: AuditMetadata,
126
+ action: RelationDownloadPolicyAction,
127
+ targetId: string,
128
+ itemId: string,
129
+ fileId: string,
130
+ previousAudience: FileDownloadAudience,
131
+ newAudience: FileDownloadAudience,
132
+ previousSegmentIds: readonly string[],
133
+ segmentIds: readonly string[],
134
+ ): AuditRecord {
135
+ const previousSegments = canonicalAuditValues(previousSegmentIds);
136
+ const newSegments = canonicalAuditValues(segmentIds);
137
+ if (!audiences.has(previousAudience) || !audiences.has(newAudience))
138
+ throw new TypeError(
139
+ "download policy requires valid before and after audiences",
140
+ );
141
+ requireIdentifiers("previous_segment_ids", previousSegments);
142
+ requireIdentifiers("segment_ids", newSegments);
143
+ const changedFields: RelationDownloadPolicyChangedField[] = [];
144
+ if (previousAudience !== newAudience)
145
+ changedFields.push("file_download_audience");
146
+ if (JSON.stringify(previousSegments) !== JSON.stringify(newSegments))
147
+ changedFields.push("file_download_audience_segment_ids");
148
+ return buildAuditRecord(
149
+ metadata,
150
+ {
151
+ action,
152
+ target_type: AUDIT_CATALOG[action],
153
+ target_id: targetId,
154
+ },
155
+ {
156
+ changed_fields: canonicalAuditValues(changedFields),
157
+ item_id: itemId,
158
+ file_id: fileId,
159
+ previous_state:
160
+ previousAudience === newAudience
161
+ ? undefined
162
+ : (previousAudience as AuditState),
163
+ new_state:
164
+ previousAudience === newAudience
165
+ ? undefined
166
+ : (newAudience as AuditState),
167
+ previous_item_ids:
168
+ JSON.stringify(previousSegments) === JSON.stringify(newSegments)
169
+ ? undefined
170
+ : previousSegments,
171
+ item_ids:
172
+ JSON.stringify(previousSegments) === JSON.stringify(newSegments)
173
+ ? undefined
174
+ : newSegments,
175
+ },
176
+ );
177
+ }
178
+
179
+ export function buildPostFileBlockDownloadPolicyAuditRecord(
180
+ metadata: AuditMetadata,
181
+ postId: string,
182
+ blockId: string,
183
+ fileId: string,
184
+ previousAudience: FileDownloadAudience,
185
+ newAudience: FileDownloadAudience,
186
+ previousSegmentIds: readonly string[],
187
+ segmentIds: readonly string[],
188
+ ): AuditRecord {
189
+ return buildRelationDownloadPolicyAuditRecord(
190
+ metadata,
191
+ "post.updated",
192
+ postId,
193
+ blockId,
194
+ fileId,
195
+ previousAudience,
196
+ newAudience,
197
+ previousSegmentIds,
198
+ segmentIds,
199
+ );
200
+ }
201
+
202
+ export function buildPageFileBlockDownloadPolicyAuditRecord(
203
+ metadata: AuditMetadata,
204
+ pageId: string,
205
+ blockId: string,
206
+ fileId: string,
207
+ previousAudience: FileDownloadAudience,
208
+ newAudience: FileDownloadAudience,
209
+ previousSegmentIds: readonly string[],
210
+ segmentIds: readonly string[],
211
+ ): AuditRecord {
212
+ return buildRelationDownloadPolicyAuditRecord(
213
+ metadata,
214
+ "page.updated",
215
+ pageId,
216
+ blockId,
217
+ fileId,
218
+ previousAudience,
219
+ newAudience,
220
+ previousSegmentIds,
221
+ segmentIds,
222
+ );
223
+ }
224
+
225
+ export function buildWorkFileBlockDownloadPolicyAuditRecord(
226
+ metadata: AuditMetadata,
227
+ workId: string,
228
+ blockId: string,
229
+ fileId: string,
230
+ previousAudience: FileDownloadAudience,
231
+ newAudience: FileDownloadAudience,
232
+ previousSegmentIds: readonly string[],
233
+ segmentIds: readonly string[],
234
+ ): AuditRecord {
235
+ return buildRelationDownloadPolicyAuditRecord(
236
+ metadata,
237
+ "work.updated",
238
+ workId,
239
+ blockId,
240
+ fileId,
241
+ previousAudience,
242
+ newAudience,
243
+ previousSegmentIds,
244
+ segmentIds,
245
+ );
246
+ }
247
+
248
+ export function buildProgramEventFileBlockDownloadPolicyAuditRecord(
249
+ metadata: AuditMetadata,
250
+ eventId: string,
251
+ blockId: string,
252
+ fileId: string,
253
+ previousAudience: FileDownloadAudience,
254
+ newAudience: FileDownloadAudience,
255
+ previousSegmentIds: readonly string[],
256
+ segmentIds: readonly string[],
257
+ ): AuditRecord {
258
+ return buildRelationDownloadPolicyAuditRecord(
259
+ metadata,
260
+ "program_event.updated",
261
+ eventId,
262
+ blockId,
263
+ fileId,
264
+ previousAudience,
265
+ newAudience,
266
+ previousSegmentIds,
267
+ segmentIds,
268
+ );
269
+ }
270
+
271
+ export function buildReleaseTrackDownloadPolicyAuditRecord(
272
+ metadata: AuditMetadata,
273
+ releaseId: string,
274
+ trackId: string,
275
+ fileId: string,
276
+ previousAudience: FileDownloadAudience,
277
+ newAudience: FileDownloadAudience,
278
+ previousSegmentIds: readonly string[],
279
+ segmentIds: readonly string[],
280
+ ): AuditRecord {
281
+ return buildRelationDownloadPolicyAuditRecord(
282
+ metadata,
283
+ "release.updated",
284
+ releaseId,
285
+ trackId,
286
+ fileId,
287
+ previousAudience,
288
+ newAudience,
289
+ previousSegmentIds,
290
+ segmentIds,
291
+ );
292
+ }