@de-otio/trellis 0.11.0 → 0.12.1

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 (126) hide show
  1. package/dist/env.d.ts +168 -0
  2. package/dist/env.d.ts.map +1 -1
  3. package/dist/env.js +155 -0
  4. package/dist/env.js.map +1 -1
  5. package/dist/lambda/media-completion-worker.d.ts +175 -0
  6. package/dist/lambda/media-completion-worker.d.ts.map +1 -0
  7. package/dist/lambda/media-completion-worker.js +373 -0
  8. package/dist/lambda/media-completion-worker.js.map +1 -0
  9. package/dist/lambda/media-processing-worker.d.ts +172 -1
  10. package/dist/lambda/media-processing-worker.d.ts.map +1 -1
  11. package/dist/lambda/media-processing-worker.js +343 -49
  12. package/dist/lambda/media-processing-worker.js.map +1 -1
  13. package/dist/lib/exif-stripper.d.ts +37 -22
  14. package/dist/lib/exif-stripper.d.ts.map +1 -1
  15. package/dist/lib/exif-stripper.js +101 -41
  16. package/dist/lib/exif-stripper.js.map +1 -1
  17. package/dist/lib/media/cas-keys.d.ts +63 -0
  18. package/dist/lib/media/cas-keys.d.ts.map +1 -0
  19. package/dist/lib/media/cas-keys.js +102 -0
  20. package/dist/lib/media/cas-keys.js.map +1 -0
  21. package/dist/lib/media/classify-worker-error.d.ts +48 -0
  22. package/dist/lib/media/classify-worker-error.d.ts.map +1 -0
  23. package/dist/lib/media/classify-worker-error.js +319 -0
  24. package/dist/lib/media/classify-worker-error.js.map +1 -0
  25. package/dist/lib/media/dedupe-key.d.ts +29 -0
  26. package/dist/lib/media/dedupe-key.d.ts.map +1 -0
  27. package/dist/lib/media/dedupe-key.js +49 -0
  28. package/dist/lib/media/dedupe-key.js.map +1 -0
  29. package/dist/lib/media/duration-cap.d.ts +30 -0
  30. package/dist/lib/media/duration-cap.d.ts.map +1 -0
  31. package/dist/lib/media/duration-cap.js +37 -0
  32. package/dist/lib/media/duration-cap.js.map +1 -0
  33. package/dist/lib/media/ffmpeg-args.d.ts +83 -0
  34. package/dist/lib/media/ffmpeg-args.d.ts.map +1 -0
  35. package/dist/lib/media/ffmpeg-args.js +119 -0
  36. package/dist/lib/media/ffmpeg-args.js.map +1 -0
  37. package/dist/lib/media/media-ports.d.ts +126 -0
  38. package/dist/lib/media/media-ports.d.ts.map +1 -0
  39. package/dist/lib/media/media-ports.js +129 -0
  40. package/dist/lib/media/media-ports.js.map +1 -0
  41. package/dist/lib/media/media-upsert.d.ts +55 -0
  42. package/dist/lib/media/media-upsert.d.ts.map +1 -0
  43. package/dist/lib/media/media-upsert.js +38 -0
  44. package/dist/lib/media/media-upsert.js.map +1 -0
  45. package/dist/lib/media/moderation-provider.d.ts +111 -0
  46. package/dist/lib/media/moderation-provider.d.ts.map +1 -0
  47. package/dist/lib/media/moderation-provider.js +130 -0
  48. package/dist/lib/media/moderation-provider.js.map +1 -0
  49. package/dist/lib/media/moderation-resolved-payload.d.ts +48 -0
  50. package/dist/lib/media/moderation-resolved-payload.d.ts.map +1 -0
  51. package/dist/lib/media/moderation-resolved-payload.js +37 -0
  52. package/dist/lib/media/moderation-resolved-payload.js.map +1 -0
  53. package/dist/lib/media/moderation-status.d.ts +98 -0
  54. package/dist/lib/media/moderation-status.d.ts.map +1 -0
  55. package/dist/lib/media/moderation-status.js +122 -0
  56. package/dist/lib/media/moderation-status.js.map +1 -0
  57. package/dist/lib/media/processing-types.d.ts +45 -0
  58. package/dist/lib/media/processing-types.d.ts.map +1 -0
  59. package/dist/lib/media/processing-types.js +9 -0
  60. package/dist/lib/media/processing-types.js.map +1 -0
  61. package/dist/lib/media/promote-decision.d.ts +64 -0
  62. package/dist/lib/media/promote-decision.d.ts.map +1 -0
  63. package/dist/lib/media/promote-decision.js +76 -0
  64. package/dist/lib/media/promote-decision.js.map +1 -0
  65. package/dist/lib/media/quota-check.d.ts +22 -0
  66. package/dist/lib/media/quota-check.d.ts.map +1 -0
  67. package/dist/lib/media/quota-check.js +42 -0
  68. package/dist/lib/media/quota-check.js.map +1 -0
  69. package/dist/lib/media/quota-types.d.ts +15 -0
  70. package/dist/lib/media/quota-types.d.ts.map +1 -0
  71. package/dist/lib/media/quota-types.js +9 -0
  72. package/dist/lib/media/quota-types.js.map +1 -0
  73. package/dist/lib/media/route-upload.d.ts +58 -0
  74. package/dist/lib/media/route-upload.d.ts.map +1 -0
  75. package/dist/lib/media/route-upload.js +80 -0
  76. package/dist/lib/media/route-upload.js.map +1 -0
  77. package/dist/lib/media/serve-gate.d.ts +51 -0
  78. package/dist/lib/media/serve-gate.d.ts.map +1 -0
  79. package/dist/lib/media/serve-gate.js +68 -0
  80. package/dist/lib/media/serve-gate.js.map +1 -0
  81. package/dist/lib/media/tenant-resolution.d.ts +42 -0
  82. package/dist/lib/media/tenant-resolution.d.ts.map +1 -0
  83. package/dist/lib/media/tenant-resolution.js +45 -0
  84. package/dist/lib/media/tenant-resolution.js.map +1 -0
  85. package/dist/lib/media/text-moderation.d.ts +28 -0
  86. package/dist/lib/media/text-moderation.d.ts.map +1 -0
  87. package/dist/lib/media/text-moderation.js +62 -0
  88. package/dist/lib/media/text-moderation.js.map +1 -0
  89. package/dist/lib/media/track-verdict.d.ts +45 -0
  90. package/dist/lib/media/track-verdict.d.ts.map +1 -0
  91. package/dist/lib/media/track-verdict.js +52 -0
  92. package/dist/lib/media/track-verdict.js.map +1 -0
  93. package/dist/lib/media/transcript-moderation.d.ts +47 -0
  94. package/dist/lib/media/transcript-moderation.d.ts.map +1 -0
  95. package/dist/lib/media/transcript-moderation.js +70 -0
  96. package/dist/lib/media/transcript-moderation.js.map +1 -0
  97. package/dist/lib/media-handler.d.ts.map +1 -1
  98. package/dist/lib/media-handler.js +15 -9
  99. package/dist/lib/media-handler.js.map +1 -1
  100. package/dist/lib/post-handler.d.ts.map +1 -1
  101. package/dist/lib/post-handler.js +4 -1
  102. package/dist/lib/post-handler.js.map +1 -1
  103. package/dist/lib/route-helpers.d.ts.map +1 -1
  104. package/dist/lib/route-helpers.js +9 -1
  105. package/dist/lib/route-helpers.js.map +1 -1
  106. package/dist/lib/routes/media.d.ts +21 -0
  107. package/dist/lib/routes/media.d.ts.map +1 -1
  108. package/dist/lib/routes/media.js +584 -483
  109. package/dist/lib/routes/media.js.map +1 -1
  110. package/dist/lib/services/image-normalizer.d.ts +64 -6
  111. package/dist/lib/services/image-normalizer.d.ts.map +1 -1
  112. package/dist/lib/services/image-normalizer.js +88 -6
  113. package/dist/lib/services/image-normalizer.js.map +1 -1
  114. package/dist/lib/services/media-upload-service.d.ts +2 -2
  115. package/dist/lib/services/media-upload-service.d.ts.map +1 -1
  116. package/dist/lib/services/media-upload-service.js +22 -21
  117. package/dist/lib/services/media-upload-service.js.map +1 -1
  118. package/dist/lib/tenant-scope.d.ts.map +1 -1
  119. package/dist/lib/tenant-scope.js +16 -1
  120. package/dist/lib/tenant-scope.js.map +1 -1
  121. package/package.json +2 -1
  122. package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql +49 -0
  123. package/prisma/migrations/20260625000001_p0b_moderation_jobs/migration.sql +73 -0
  124. package/prisma/schema.prisma +95 -17
  125. package/src/lambda/media-completion-worker.ts +567 -0
  126. package/src/lambda/media-processing-worker.ts +508 -59
@@ -0,0 +1,319 @@
1
+ /**
2
+ * classify-worker-error.ts — pure functional core, no I/O, no AWS SDK.
3
+ *
4
+ * Classifies any error thrown by a media-processing worker into one of two
5
+ * treatment buckets:
6
+ *
7
+ * "poison" — the media itself (or the job payload) is bad. A retry cannot
8
+ * help: the same bytes will fail the same way. The caller must
9
+ * ACK the message and move the object to moderationStatus=REVIEW
10
+ * for human triage, avoiding an infinite DLQ loop.
11
+ *
12
+ * "retryable" — a transient infrastructure fault: throttling, timeout, 5xx
13
+ * upstream, network blip, eventual-consistency AccessDenied.
14
+ * The caller lets SQS retry naturally; the 3-strike DLQ + alert
15
+ * acts as the upper bound.
16
+ *
17
+ * DEFAULT RULE (documented here — the only operative "fallback threshold"):
18
+ * When the error is unknown or cannot be classified with confidence, the
19
+ * function returns "retryable". Rationale: an unrecognised error is more
20
+ * likely to be a transient infra fault than a permanent media defect, and the
21
+ * DLQ + 3-strike alert provides a bounded safety net. Silently sending
22
+ * ambiguous errors to REVIEW would suppress alerts and hide systemic infra
23
+ * problems. This default is deliberately fail-OPEN for retry but
24
+ * fail-CLOSED for approval — REVIEW is never returned from this module; the
25
+ * caller owns the status mapping.
26
+ *
27
+ * PURITY GUARANTEE: this module has no I/O, no AWS SDK dependency, no
28
+ * Date.now/Math.random. node:crypto is NOT used. All inputs produce
29
+ * deterministic outputs.
30
+ */
31
+ // ---------------------------------------------------------------------------
32
+ // Known poison error names / codes
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Error `.name` values that indicate a permanent media/payload defect.
36
+ * All matching is case-insensitive to absorb capitalisation variance.
37
+ */
38
+ const POISON_ERROR_NAMES = new Set([
39
+ // Standard decode/parse failures
40
+ "decodeerror",
41
+ "parseerror",
42
+ "formaterror",
43
+ "syntaxerror",
44
+ // Validation failures (payload, schema, duration)
45
+ "validationerror",
46
+ "schemaerror",
47
+ "constrainterror",
48
+ "durationerror",
49
+ "durationcapexceeded",
50
+ // Unsupported / corrupt media
51
+ "unsupportedformaterror",
52
+ "unsupportedmediaerror",
53
+ "unsupportedtypeerror",
54
+ "corrupterror",
55
+ "corruptmediaerror",
56
+ "corruptfileerror",
57
+ "invalidimageerror",
58
+ "invalidmediaerror",
59
+ "invalidpayloaderror",
60
+ "invalidinputerror",
61
+ // AWS Rekognition / media-specific permanent errors
62
+ "invalidimageexception",
63
+ "imagetoolargeexception",
64
+ "invalidparameter",
65
+ "invalidparameterexception",
66
+ "invalids3objectexception",
67
+ ]);
68
+ /**
69
+ * Error `.name` substrings (lowercased) that indicate a poison condition.
70
+ * Used when the name doesn't match exactly but carries an indicative fragment.
71
+ */
72
+ const POISON_NAME_FRAGMENTS = [
73
+ "decode",
74
+ "parse",
75
+ "corrupt",
76
+ "invalid",
77
+ "unsupported",
78
+ "format",
79
+ "duration",
80
+ "validation",
81
+ "schema",
82
+ "constraint",
83
+ "payload",
84
+ ];
85
+ /**
86
+ * Error message substrings (lowercased) that indicate a permanent media fault
87
+ * even when the error name itself is generic (e.g. plain `Error`).
88
+ */
89
+ const POISON_MESSAGE_FRAGMENTS = [
90
+ "decode",
91
+ "corrupt",
92
+ "unsupported format",
93
+ "unsupported media",
94
+ "unsupported type",
95
+ "invalid format",
96
+ "invalid image",
97
+ "invalid media",
98
+ "invalid payload",
99
+ "invalid input",
100
+ "duration cap",
101
+ "duration exceeded",
102
+ "duration limit",
103
+ "too large",
104
+ "cannot parse",
105
+ "failed to parse",
106
+ "failed to decode",
107
+ "not a valid",
108
+ "malformed",
109
+ ];
110
+ // ---------------------------------------------------------------------------
111
+ // Known retryable error names / codes
112
+ // ---------------------------------------------------------------------------
113
+ /**
114
+ * HTTP status codes (from AWS SDK `$metadata.httpStatusCode` or elsewhere) that
115
+ * indicate a transient upstream fault. 4xx codes are explicitly enumerated —
116
+ * only the "transient 4xx" set (429 + 503/504 aliases sometimes arrive as 4xx
117
+ * depending on SDK version) are retryable; all other 4xx are poison (client
118
+ * error, not fixable by retry).
119
+ */
120
+ const RETRYABLE_HTTP_STATUS = new Set([
121
+ 429, // Too Many Requests / throttle
122
+ 500, // Internal Server Error
123
+ 502, // Bad Gateway
124
+ 503, // Service Unavailable
125
+ 504, // Gateway Timeout
126
+ ]);
127
+ /**
128
+ * AWS SDK error `.name` values (and common equivalents) that map to transient
129
+ * infrastructure faults. Matched case-insensitively.
130
+ */
131
+ const RETRYABLE_ERROR_NAMES = new Set([
132
+ // AWS throttle / quota
133
+ "throttlingexception",
134
+ "requestlimitexceeded",
135
+ "provisionedthroughputexceededexception",
136
+ "limitsexceededexception",
137
+ "servicequotaexceededexception",
138
+ "toomanyrequestsexception",
139
+ // AWS transient service errors
140
+ "serviceexception",
141
+ "internalfailure",
142
+ "internalservererror",
143
+ "serviceunavailableexception",
144
+ "serviceunavailable",
145
+ "requesttimeoutexception",
146
+ "requestexpired",
147
+ "requesttimeout",
148
+ // AWS eventual-consistency access issues (retryable — policy propagation lag)
149
+ "accessdeniedexception",
150
+ // Network / socket
151
+ "networkerror",
152
+ "networktimeout",
153
+ "connectionerror",
154
+ "connectionreset",
155
+ "connectiontimeout",
156
+ "sockettimeout",
157
+ "econnreset",
158
+ "econnrefused",
159
+ "enotfound",
160
+ "etimedout",
161
+ "ehostunreach",
162
+ // Generic timeout
163
+ "timeouterror",
164
+ "aborterror",
165
+ "fetcherror",
166
+ ]);
167
+ /**
168
+ * Error message substrings (lowercased) that indicate a transient fault even
169
+ * when the error name is generic.
170
+ */
171
+ const RETRYABLE_MESSAGE_FRAGMENTS = [
172
+ "throttl",
173
+ "rate limit",
174
+ "rate exceeded",
175
+ "quota exceeded",
176
+ "too many requests",
177
+ "timeout",
178
+ "timed out",
179
+ "connection reset",
180
+ "network error",
181
+ "socket hang up",
182
+ "econnreset",
183
+ "econnrefused",
184
+ "service unavailable",
185
+ "temporarily unavailable",
186
+ "try again",
187
+ "please retry",
188
+ "internal server error",
189
+ "502",
190
+ "503",
191
+ "504",
192
+ ];
193
+ // ---------------------------------------------------------------------------
194
+ // Main classifier
195
+ // ---------------------------------------------------------------------------
196
+ /**
197
+ * Classifies `err` as `"poison"` or `"retryable"`.
198
+ *
199
+ * - TOTAL: never throws for any input (including null, undefined, non-Error objects).
200
+ * - FAIL-CLOSED on unknown: returns `"retryable"` when the error cannot be
201
+ * classified with confidence (DLQ + alert is the bounded backstop).
202
+ * - Poison wins over retryable when signals conflict (conservative for serving
203
+ * safety: a bad payload that also triggers throttle is still bad).
204
+ */
205
+ export function classifyWorkerError(err) {
206
+ // --- 1. Pull every signal we can out of the value, safely. ---
207
+ const name = safeString(extractName(err));
208
+ const message = safeString(extractMessage(err));
209
+ const httpStatus = extractHttpStatus(err);
210
+ const code = safeString(extractCode(err));
211
+ // --- 2. Poison checks (take priority). ---
212
+ if (isPoisonByName(name))
213
+ return "poison";
214
+ if (isPoisonByMessage(message))
215
+ return "poison";
216
+ // String errors that look like decode/parse failures
217
+ if (typeof err === "string" && isPoisonByMessage(err.toLowerCase())) {
218
+ return "poison";
219
+ }
220
+ // --- 3. Retryable checks. ---
221
+ if (httpStatus !== undefined && RETRYABLE_HTTP_STATUS.has(httpStatus)) {
222
+ return "retryable";
223
+ }
224
+ if (isRetryableByName(name))
225
+ return "retryable";
226
+ if (isRetryableByName(code))
227
+ return "retryable";
228
+ if (isRetryableByMessage(message))
229
+ return "retryable";
230
+ if (typeof err === "string" && isRetryableByMessage(err.toLowerCase())) {
231
+ return "retryable";
232
+ }
233
+ // --- 4. Unknown — default to retryable (see module-level docstring). ---
234
+ return "retryable";
235
+ }
236
+ // ---------------------------------------------------------------------------
237
+ // Predicate helpers
238
+ // ---------------------------------------------------------------------------
239
+ function isPoisonByName(name) {
240
+ if (!name)
241
+ return false;
242
+ if (POISON_ERROR_NAMES.has(name))
243
+ return true;
244
+ // Fragment scan: e.g. "UnsupportedFormatException" contains "format"
245
+ return POISON_NAME_FRAGMENTS.some((f) => name.includes(f));
246
+ }
247
+ function isPoisonByMessage(msg) {
248
+ if (!msg)
249
+ return false;
250
+ return POISON_MESSAGE_FRAGMENTS.some((f) => msg.includes(f));
251
+ }
252
+ function isRetryableByName(name) {
253
+ if (!name)
254
+ return false;
255
+ return RETRYABLE_ERROR_NAMES.has(name);
256
+ }
257
+ function isRetryableByMessage(msg) {
258
+ if (!msg)
259
+ return false;
260
+ return RETRYABLE_MESSAGE_FRAGMENTS.some((f) => msg.includes(f));
261
+ }
262
+ // ---------------------------------------------------------------------------
263
+ // Safe extraction helpers — never throw
264
+ // ---------------------------------------------------------------------------
265
+ function extractName(err) {
266
+ if (err === null || err === undefined)
267
+ return undefined;
268
+ if (typeof err === "object") {
269
+ return err.name;
270
+ }
271
+ return undefined;
272
+ }
273
+ function extractMessage(err) {
274
+ if (err === null || err === undefined)
275
+ return undefined;
276
+ if (typeof err === "object") {
277
+ return err.message;
278
+ }
279
+ return undefined;
280
+ }
281
+ function extractCode(err) {
282
+ if (err === null || err === undefined)
283
+ return undefined;
284
+ if (typeof err === "object") {
285
+ const o = err;
286
+ // AWS SDK v3 uses .code or .__type; normalise both
287
+ return o.code ?? o.__type;
288
+ }
289
+ return undefined;
290
+ }
291
+ function extractHttpStatus(err) {
292
+ if (err === null || err === undefined)
293
+ return undefined;
294
+ if (typeof err !== "object")
295
+ return undefined;
296
+ const o = err;
297
+ // AWS SDK v3: err.$metadata.httpStatusCode
298
+ const meta = o.$metadata;
299
+ if (meta !== null && meta !== undefined && typeof meta === "object") {
300
+ const status = meta.httpStatusCode;
301
+ if (typeof status === "number")
302
+ return status;
303
+ }
304
+ // Generic: err.statusCode or err.status
305
+ const statusCode = o.statusCode ?? o.status;
306
+ if (typeof statusCode === "number")
307
+ return statusCode;
308
+ return undefined;
309
+ }
310
+ /**
311
+ * Coerce an extracted value to a lowercase string for matching.
312
+ * Returns `""` for anything that cannot be cleanly turned into a string.
313
+ */
314
+ function safeString(value) {
315
+ if (typeof value === "string")
316
+ return value.toLowerCase();
317
+ return "";
318
+ }
319
+ //# sourceMappingURL=classify-worker-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify-worker-error.js","sourceRoot":"","sources":["../../../src/lib/media/classify-worker-error.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { DedupeKeyInput } from "./processing-types.js";
2
+ export type { DedupeKeyInput };
3
+ /**
4
+ * Derive a stable, collision-resistant dedupe key for one (contentHash, jobId,
5
+ * track) tuple.
6
+ *
7
+ * Implementation: encode the three fields as a length-prefixed canonical form,
8
+ * then SHA-256 hash it. Length-prefix encoding ensures that distinct tuples
9
+ * cannot be made to collide by crafting delimiter-containing field values
10
+ * (e.g. a jobId that itself contains a colon, slash, or any other separator).
11
+ *
12
+ * Format fed to SHA-256:
13
+ * `<len(contentHash)>:<contentHash>|<len(jobId)>:<jobId>|<len(track)>:<track>`
14
+ *
15
+ * The hash output is lowercased hex — 64 characters, safe as a map key, a
16
+ * DynamoDB attribute value, or a Redis key.
17
+ *
18
+ * Properties (all property-tested):
19
+ * - Deterministic (idempotent): same tuple → same key on every call.
20
+ * - Injective: distinct tuples → distinct keys, even when jobId contains
21
+ * delimiters (`|`, `:`), Unicode, NUL bytes, or embedded length-prefix
22
+ * strings that would fool a naive join.
23
+ * - Total: never throws, returns a non-empty string for any input.
24
+ *
25
+ * @param input - The (contentHash, jobId, track) triple.
26
+ * @returns A 64-character lowercase hex SHA-256 digest.
27
+ */
28
+ export declare function deriveDedupeKey(input: DedupeKeyInput): string;
29
+ //# sourceMappingURL=dedupe-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe-key.d.ts","sourceRoot":"","sources":["../../../src/lib/media/dedupe-key.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAW7D"}
@@ -0,0 +1,49 @@
1
+ // Pure functional-core unit. No I/O, no clock, no random.
2
+ //
3
+ // Derives a collision-resistant, injection-safe dedupe key for a (contentHash,
4
+ // jobId, track) tuple. Used by the processing worker to idempotently fan-out /
5
+ // fan-in async per-track moderation jobs without double-processing.
6
+ //
7
+ // COLLISION RESISTANCE: a naive delimiter join ("a:b:c") is vulnerable when
8
+ // jobId is provider-controlled and may contain the delimiter. This module hashes
9
+ // the canonical tuple with SHA-256 so that distinct tuples always yield distinct
10
+ // keys regardless of delimiter characters in any field.
11
+ //
12
+ // Ships in the PUBLIC npm tarball: NO thresholds, secrets, or real-category
13
+ // vocabulary here. node:crypto createHash is allowed (deterministic, CPU-only).
14
+ import { createHash } from "node:crypto";
15
+ /**
16
+ * Derive a stable, collision-resistant dedupe key for one (contentHash, jobId,
17
+ * track) tuple.
18
+ *
19
+ * Implementation: encode the three fields as a length-prefixed canonical form,
20
+ * then SHA-256 hash it. Length-prefix encoding ensures that distinct tuples
21
+ * cannot be made to collide by crafting delimiter-containing field values
22
+ * (e.g. a jobId that itself contains a colon, slash, or any other separator).
23
+ *
24
+ * Format fed to SHA-256:
25
+ * `<len(contentHash)>:<contentHash>|<len(jobId)>:<jobId>|<len(track)>:<track>`
26
+ *
27
+ * The hash output is lowercased hex — 64 characters, safe as a map key, a
28
+ * DynamoDB attribute value, or a Redis key.
29
+ *
30
+ * Properties (all property-tested):
31
+ * - Deterministic (idempotent): same tuple → same key on every call.
32
+ * - Injective: distinct tuples → distinct keys, even when jobId contains
33
+ * delimiters (`|`, `:`), Unicode, NUL bytes, or embedded length-prefix
34
+ * strings that would fool a naive join.
35
+ * - Total: never throws, returns a non-empty string for any input.
36
+ *
37
+ * @param input - The (contentHash, jobId, track) triple.
38
+ * @returns A 64-character lowercase hex SHA-256 digest.
39
+ */
40
+ export function deriveDedupeKey(input) {
41
+ const { contentHash, jobId, track } = input;
42
+ // Length-prefixed encoding prevents injection: a field value that contains
43
+ // the separator cannot silently absorb a neighbouring field's value.
44
+ const canonical = `${contentHash.length}:${contentHash}` +
45
+ `|${jobId.length}:${jobId}` +
46
+ `|${track.length}:${track}`;
47
+ return createHash("sha256").update(canonical, "utf8").digest("hex");
48
+ }
49
+ //# sourceMappingURL=dedupe-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe-key.js","sourceRoot":"","sources":["../../../src/lib/media/dedupe-key.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,oEAAoE;AACpE,EAAE;AACF,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,wDAAwD;AACxD,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAEhF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAE5C,2EAA2E;IAC3E,qEAAqE;IACrE,MAAM,SAAS,GACb,GAAG,WAAW,CAAC,MAAM,IAAI,WAAW,EAAE;QACtC,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE;QAC3B,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;IAE9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Duration cap gate — pure functional core unit.
3
+ *
4
+ * Determines whether a probed media duration exceeds the configured cap.
5
+ * Fail-closed: any input that cannot be verified as a finite non-negative
6
+ * number is treated as "too long" (rejected), so uncertainty never yields a
7
+ * decision to process.
8
+ *
9
+ * Ships in the PUBLIC npm tarball: NO hard-coded operational thresholds here.
10
+ * The cap arrives as a function argument sourced from Env.media.maxDurationSeconds.
11
+ *
12
+ * Pure functional core: no I/O, no clock, no network, no fs. Total over all inputs.
13
+ */
14
+ /**
15
+ * Returns `true` when `probedSeconds` exceeds the configured `capSeconds`,
16
+ * meaning the media object is too long to process.
17
+ *
18
+ * Fail-closed boundary conditions:
19
+ * - `probedSeconds` is NaN => true (cannot verify length; reject)
20
+ * - `probedSeconds` is ±Infinity => true (cannot verify length; reject)
21
+ * - `probedSeconds` is negative => true (invalid probe; reject)
22
+ * - `probedSeconds === capSeconds` => false (exactly at cap is allowed)
23
+ * - `probedSeconds > capSeconds` => true
24
+ *
25
+ * @param probedSeconds The duration reported by the media probe (e.g. ffprobe).
26
+ * @param capSeconds The maximum allowed duration, sourced from Env.media.maxDurationSeconds.
27
+ * Never a literal in this file.
28
+ */
29
+ export declare function exceedsDurationCap(probedSeconds: number, capSeconds: number): boolean;
30
+ //# sourceMappingURL=duration-cap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-cap.d.ts","sourceRoot":"","sources":["../../../src/lib/media/duration-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAQT"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Duration cap gate — pure functional core unit.
3
+ *
4
+ * Determines whether a probed media duration exceeds the configured cap.
5
+ * Fail-closed: any input that cannot be verified as a finite non-negative
6
+ * number is treated as "too long" (rejected), so uncertainty never yields a
7
+ * decision to process.
8
+ *
9
+ * Ships in the PUBLIC npm tarball: NO hard-coded operational thresholds here.
10
+ * The cap arrives as a function argument sourced from Env.media.maxDurationSeconds.
11
+ *
12
+ * Pure functional core: no I/O, no clock, no network, no fs. Total over all inputs.
13
+ */
14
+ /**
15
+ * Returns `true` when `probedSeconds` exceeds the configured `capSeconds`,
16
+ * meaning the media object is too long to process.
17
+ *
18
+ * Fail-closed boundary conditions:
19
+ * - `probedSeconds` is NaN => true (cannot verify length; reject)
20
+ * - `probedSeconds` is ±Infinity => true (cannot verify length; reject)
21
+ * - `probedSeconds` is negative => true (invalid probe; reject)
22
+ * - `probedSeconds === capSeconds` => false (exactly at cap is allowed)
23
+ * - `probedSeconds > capSeconds` => true
24
+ *
25
+ * @param probedSeconds The duration reported by the media probe (e.g. ffprobe).
26
+ * @param capSeconds The maximum allowed duration, sourced from Env.media.maxDurationSeconds.
27
+ * Never a literal in this file.
28
+ */
29
+ export function exceedsDurationCap(probedSeconds, capSeconds) {
30
+ // Fail closed on any un-verifiable probe value.
31
+ if (!Number.isFinite(probedSeconds) || probedSeconds < 0) {
32
+ return true;
33
+ }
34
+ // Exactly at cap is permitted; strictly over cap is rejected.
35
+ return probedSeconds > capSeconds;
36
+ }
37
+ //# sourceMappingURL=duration-cap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-cap.js","sourceRoot":"","sources":["../../../src/lib/media/duration-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,EACrB,UAAkB;IAElB,gDAAgD;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8DAA8D;IAC9D,OAAO,aAAa,GAAG,UAAU,CAAC;AACpC,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Pure functional-core builder for ffmpeg argument arrays.
3
+ *
4
+ * Produces an argv ARRAY passed directly to the ffmpeg process — NEVER a shell
5
+ * string. No interpolation into a shell context means no command injection risk
6
+ * regardless of what inputPath / outputPath / posterPath contain.
7
+ *
8
+ * Hardening applied unconditionally on every invocation:
9
+ * - "-protocol_whitelist","file,pipe" blocks SSRF (no http/https/rtmp/etc.)
10
+ * - "-t", String(maxDurationSeconds) bounds processing time (from spec; never a literal)
11
+ * - "-dn" drop data tracks
12
+ * - "-sn" drop subtitle tracks
13
+ *
14
+ * Video additionally gets:
15
+ * - "-c:v","libx264","-c:a","aac" re-encode to safe codecs
16
+ * - "-movflags","+faststart" progressive streaming
17
+ *
18
+ * Audio-only gets:
19
+ * - "-c:a","aac" re-encode to safe codec
20
+ *
21
+ * Poster frame extraction is a SEPARATE argv (buildPosterArgs). Splitting the
22
+ * two operations keeps the main transcode deterministic and lets the poster be
23
+ * produced in a separate process without retranscoding. The poster job uses
24
+ * "-frames:v","1" to extract exactly one frame and inherits all hardening args.
25
+ *
26
+ * PURITY: no I/O, no AWS SDK, no fs, no Date.now, no Math.random. All
27
+ * operational parameters (maxDurationSeconds) arrive as function arguments
28
+ * sourced from Env.media — never as literals in this file. Ships in the PUBLIC
29
+ * npm tarball: no hard-coded operational numbers here.
30
+ */
31
+ /**
32
+ * Specification for a single ffmpeg transcode job.
33
+ *
34
+ * - `kind` — "video" or "audio"; controls codec flags
35
+ * - `inputPath` — absolute path to the source file (read-only)
36
+ * - `outputPath` — absolute path for the transcoded output
37
+ * - `posterPath` — if set, {@link buildPosterArgs} targets this path
38
+ * - `maxDurationSeconds` — duration cap sourced from Env.media; passed to -t
39
+ */
40
+ export interface FfmpegJobSpec {
41
+ readonly kind: "video" | "audio";
42
+ readonly inputPath: string;
43
+ readonly outputPath: string;
44
+ readonly posterPath?: string;
45
+ readonly maxDurationSeconds: number;
46
+ }
47
+ /**
48
+ * Build the argv for transcoding a media object.
49
+ *
50
+ * The returned array is passed to the child-process launcher directly — never
51
+ * joined into a shell string. The caller is responsible for prepending the
52
+ * ffmpeg binary path or using `execFile`/`spawn` with `shell: false`.
53
+ *
54
+ * The protocol whitelist, duration cap, and track-drop flags are always present
55
+ * in the output regardless of kind. The caller should verify these are present
56
+ * before launching (the test suite does so exhaustively).
57
+ */
58
+ export declare function buildFfmpegArgs(spec: FfmpegJobSpec): string[];
59
+ /**
60
+ * Build the argv for extracting a poster frame from a video.
61
+ *
62
+ * This is a SEPARATE invocation from {@link buildFfmpegArgs}: splitting the two
63
+ * means the transcode and the poster extraction run independently, the poster
64
+ * can be regenerated without retranscoding, and the transcode argv stays clean.
65
+ *
66
+ * The poster job inherits all hardening (protocol whitelist, duration cap,
67
+ * track drops) and adds:
68
+ * - "-frames:v","1" — extract exactly one video frame
69
+ * - "-an" — no audio output (image output only)
70
+ *
71
+ * Callers should only invoke this when `spec.posterPath` is defined. The
72
+ * function documents this contract via its signature: if posterPath is absent,
73
+ * the output path would be undefined and the caller must guard before spawning.
74
+ * The implementation always uses spec.posterPath so TypeScript callers can call
75
+ * with a spec that has posterPath set and get a well-formed array.
76
+ *
77
+ * @param spec - must have `posterPath` set; calling without it is a caller
78
+ * contract violation (outputPath would be undefined).
79
+ */
80
+ export declare function buildPosterArgs(spec: FfmpegJobSpec & {
81
+ readonly posterPath: string;
82
+ }): string[];
83
+ //# sourceMappingURL=ffmpeg-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffmpeg-args.d.ts","sourceRoot":"","sources":["../../../src/lib/media/ffmpeg-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAyC7D;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpD,MAAM,EAAE,CAwBV"}