@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.
- package/dist/env.d.ts +168 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +155 -0
- package/dist/env.js.map +1 -1
- package/dist/lambda/media-completion-worker.d.ts +175 -0
- package/dist/lambda/media-completion-worker.d.ts.map +1 -0
- package/dist/lambda/media-completion-worker.js +373 -0
- package/dist/lambda/media-completion-worker.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +172 -1
- package/dist/lambda/media-processing-worker.d.ts.map +1 -1
- package/dist/lambda/media-processing-worker.js +343 -49
- package/dist/lambda/media-processing-worker.js.map +1 -1
- package/dist/lib/exif-stripper.d.ts +37 -22
- package/dist/lib/exif-stripper.d.ts.map +1 -1
- package/dist/lib/exif-stripper.js +101 -41
- package/dist/lib/exif-stripper.js.map +1 -1
- package/dist/lib/media/cas-keys.d.ts +63 -0
- package/dist/lib/media/cas-keys.d.ts.map +1 -0
- package/dist/lib/media/cas-keys.js +102 -0
- package/dist/lib/media/cas-keys.js.map +1 -0
- package/dist/lib/media/classify-worker-error.d.ts +48 -0
- package/dist/lib/media/classify-worker-error.d.ts.map +1 -0
- package/dist/lib/media/classify-worker-error.js +319 -0
- package/dist/lib/media/classify-worker-error.js.map +1 -0
- package/dist/lib/media/dedupe-key.d.ts +29 -0
- package/dist/lib/media/dedupe-key.d.ts.map +1 -0
- package/dist/lib/media/dedupe-key.js +49 -0
- package/dist/lib/media/dedupe-key.js.map +1 -0
- package/dist/lib/media/duration-cap.d.ts +30 -0
- package/dist/lib/media/duration-cap.d.ts.map +1 -0
- package/dist/lib/media/duration-cap.js +37 -0
- package/dist/lib/media/duration-cap.js.map +1 -0
- package/dist/lib/media/ffmpeg-args.d.ts +83 -0
- package/dist/lib/media/ffmpeg-args.d.ts.map +1 -0
- package/dist/lib/media/ffmpeg-args.js +119 -0
- package/dist/lib/media/ffmpeg-args.js.map +1 -0
- package/dist/lib/media/media-ports.d.ts +126 -0
- package/dist/lib/media/media-ports.d.ts.map +1 -0
- package/dist/lib/media/media-ports.js +129 -0
- package/dist/lib/media/media-ports.js.map +1 -0
- package/dist/lib/media/media-upsert.d.ts +55 -0
- package/dist/lib/media/media-upsert.d.ts.map +1 -0
- package/dist/lib/media/media-upsert.js +38 -0
- package/dist/lib/media/media-upsert.js.map +1 -0
- package/dist/lib/media/moderation-provider.d.ts +111 -0
- package/dist/lib/media/moderation-provider.d.ts.map +1 -0
- package/dist/lib/media/moderation-provider.js +130 -0
- package/dist/lib/media/moderation-provider.js.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts +48 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.js +37 -0
- package/dist/lib/media/moderation-resolved-payload.js.map +1 -0
- package/dist/lib/media/moderation-status.d.ts +98 -0
- package/dist/lib/media/moderation-status.d.ts.map +1 -0
- package/dist/lib/media/moderation-status.js +122 -0
- package/dist/lib/media/moderation-status.js.map +1 -0
- package/dist/lib/media/processing-types.d.ts +45 -0
- package/dist/lib/media/processing-types.d.ts.map +1 -0
- package/dist/lib/media/processing-types.js +9 -0
- package/dist/lib/media/processing-types.js.map +1 -0
- package/dist/lib/media/promote-decision.d.ts +64 -0
- package/dist/lib/media/promote-decision.d.ts.map +1 -0
- package/dist/lib/media/promote-decision.js +76 -0
- package/dist/lib/media/promote-decision.js.map +1 -0
- package/dist/lib/media/quota-check.d.ts +22 -0
- package/dist/lib/media/quota-check.d.ts.map +1 -0
- package/dist/lib/media/quota-check.js +42 -0
- package/dist/lib/media/quota-check.js.map +1 -0
- package/dist/lib/media/quota-types.d.ts +15 -0
- package/dist/lib/media/quota-types.d.ts.map +1 -0
- package/dist/lib/media/quota-types.js +9 -0
- package/dist/lib/media/quota-types.js.map +1 -0
- package/dist/lib/media/route-upload.d.ts +58 -0
- package/dist/lib/media/route-upload.d.ts.map +1 -0
- package/dist/lib/media/route-upload.js +80 -0
- package/dist/lib/media/route-upload.js.map +1 -0
- package/dist/lib/media/serve-gate.d.ts +51 -0
- package/dist/lib/media/serve-gate.d.ts.map +1 -0
- package/dist/lib/media/serve-gate.js +68 -0
- package/dist/lib/media/serve-gate.js.map +1 -0
- package/dist/lib/media/tenant-resolution.d.ts +42 -0
- package/dist/lib/media/tenant-resolution.d.ts.map +1 -0
- package/dist/lib/media/tenant-resolution.js +45 -0
- package/dist/lib/media/tenant-resolution.js.map +1 -0
- package/dist/lib/media/text-moderation.d.ts +28 -0
- package/dist/lib/media/text-moderation.d.ts.map +1 -0
- package/dist/lib/media/text-moderation.js +62 -0
- package/dist/lib/media/text-moderation.js.map +1 -0
- package/dist/lib/media/track-verdict.d.ts +45 -0
- package/dist/lib/media/track-verdict.d.ts.map +1 -0
- package/dist/lib/media/track-verdict.js +52 -0
- package/dist/lib/media/track-verdict.js.map +1 -0
- package/dist/lib/media/transcript-moderation.d.ts +47 -0
- package/dist/lib/media/transcript-moderation.d.ts.map +1 -0
- package/dist/lib/media/transcript-moderation.js +70 -0
- package/dist/lib/media/transcript-moderation.js.map +1 -0
- package/dist/lib/media-handler.d.ts.map +1 -1
- package/dist/lib/media-handler.js +15 -9
- package/dist/lib/media-handler.js.map +1 -1
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +4 -1
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/route-helpers.d.ts.map +1 -1
- package/dist/lib/route-helpers.js +9 -1
- package/dist/lib/route-helpers.js.map +1 -1
- package/dist/lib/routes/media.d.ts +21 -0
- package/dist/lib/routes/media.d.ts.map +1 -1
- package/dist/lib/routes/media.js +584 -483
- package/dist/lib/routes/media.js.map +1 -1
- package/dist/lib/services/image-normalizer.d.ts +64 -6
- package/dist/lib/services/image-normalizer.d.ts.map +1 -1
- package/dist/lib/services/image-normalizer.js +88 -6
- package/dist/lib/services/image-normalizer.js.map +1 -1
- package/dist/lib/services/media-upload-service.d.ts +2 -2
- package/dist/lib/services/media-upload-service.d.ts.map +1 -1
- package/dist/lib/services/media-upload-service.js +22 -21
- package/dist/lib/services/media-upload-service.js.map +1 -1
- package/dist/lib/tenant-scope.d.ts.map +1 -1
- package/dist/lib/tenant-scope.js +16 -1
- package/dist/lib/tenant-scope.js.map +1 -1
- package/package.json +2 -1
- package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql +49 -0
- package/prisma/migrations/20260625000001_p0b_moderation_jobs/migration.sql +73 -0
- package/prisma/schema.prisma +95 -17
- package/src/lambda/media-completion-worker.ts +567 -0
- package/src/lambda/media-processing-worker.ts +508 -59
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 4. Reconciliation worker creates database record asynchronously
|
|
9
9
|
*/
|
|
10
10
|
import { getLogger } from "../logger.js";
|
|
11
|
+
import { casKey, isCasKeyError } from "../media/cas-keys.js";
|
|
11
12
|
/**
|
|
12
13
|
* Generate unique batch ID
|
|
13
14
|
*/
|
|
@@ -23,22 +24,20 @@ async function generateContentHash(file) {
|
|
|
23
24
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
+
* Build the canonical CAS original key for a tenant-scoped object (T9 / D18).
|
|
28
|
+
*
|
|
29
|
+
* Delegates to the pure `casKey` builder (anchored allowlist validation). A
|
|
30
|
+
* validation failure here means the caller passed a malformed tenantId or hash
|
|
31
|
+
* — both are produced internally (tenantId resolved from auth context, hash
|
|
32
|
+
* from `crypto.subtle.digest`), so a failure is a programming error and is
|
|
33
|
+
* thrown rather than silently producing an unsafe key.
|
|
27
34
|
*/
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"image/webp": "webp",
|
|
35
|
-
"image/heic": "heic",
|
|
36
|
-
"image/heif": "heif",
|
|
37
|
-
"video/mp4": "mp4",
|
|
38
|
-
"video/webm": "webm",
|
|
39
|
-
"video/quicktime": "mov",
|
|
40
|
-
};
|
|
41
|
-
return mimeMap[mimeType] || "bin";
|
|
35
|
+
function buildCasOriginalKey(tenantId, contentHash) {
|
|
36
|
+
const key = casKey(tenantId, contentHash);
|
|
37
|
+
if (isCasKeyError(key)) {
|
|
38
|
+
throw new Error(`Invalid CAS key inputs: ${key.kind}`);
|
|
39
|
+
}
|
|
40
|
+
return key;
|
|
42
41
|
}
|
|
43
42
|
export class MediaUploadService {
|
|
44
43
|
r2Bucket;
|
|
@@ -57,7 +56,7 @@ export class MediaUploadService {
|
|
|
57
56
|
/**
|
|
58
57
|
* Upload single file
|
|
59
58
|
*/
|
|
60
|
-
async uploadSingle(file, userId, metadata, preReadBuffer) {
|
|
59
|
+
async uploadSingle(file, userId, tenantId, metadata, preReadBuffer) {
|
|
61
60
|
const logger = getLogger();
|
|
62
61
|
const batchId = generateBatchId();
|
|
63
62
|
try {
|
|
@@ -66,8 +65,10 @@ export class MediaUploadService {
|
|
|
66
65
|
// which may not work reliably on Cloudflare Workers for FormData Files)
|
|
67
66
|
const fileBuffer = preReadBuffer ?? await file.arrayBuffer();
|
|
68
67
|
const contentHash = await generateContentHash(fileBuffer);
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// T9: the ONE canonical CAS scheme (D18) — `cas/{tenantId}/{hash}`.
|
|
69
|
+
// The same key is what the DB `originalKey` stores and what the serve
|
|
70
|
+
// path reads, so upload-target and serve-source can never disagree.
|
|
71
|
+
const originalKey = buildCasOriginalKey(tenantId, contentHash);
|
|
71
72
|
logger.info("[MediaUpload] Starting upload", {
|
|
72
73
|
contentHash,
|
|
73
74
|
mimeType: file.type,
|
|
@@ -142,7 +143,7 @@ export class MediaUploadService {
|
|
|
142
143
|
/**
|
|
143
144
|
* Upload batch of files
|
|
144
145
|
*/
|
|
145
|
-
async uploadBatch(files, userId, metadataArray) {
|
|
146
|
+
async uploadBatch(files, userId, tenantId, metadataArray) {
|
|
146
147
|
const logger = getLogger();
|
|
147
148
|
const batchId = generateBatchId();
|
|
148
149
|
logger.info("[MediaUpload] Starting batch upload", {
|
|
@@ -154,8 +155,8 @@ export class MediaUploadService {
|
|
|
154
155
|
const uploadResults = await Promise.allSettled(files.map(async (file, index) => {
|
|
155
156
|
const fileBuffer = await file.arrayBuffer();
|
|
156
157
|
const contentHash = await generateContentHash(fileBuffer);
|
|
157
|
-
|
|
158
|
-
const originalKey =
|
|
158
|
+
// T9: canonical CAS scheme — see uploadSingle.
|
|
159
|
+
const originalKey = buildCasOriginalKey(tenantId, contentHash);
|
|
159
160
|
const metadata = metadataArray?.[index];
|
|
160
161
|
const r2Metadata = {
|
|
161
162
|
contentHash,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-upload-service.js","sourceRoot":"","sources":["../../../src/lib/services/media-upload-service.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAU,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"media-upload-service.js","sourceRoot":"","sources":["../../../src/lib/services/media-upload-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-scope.d.ts","sourceRoot":"","sources":["../../src/lib/tenant-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3D,6EAA6E;AAC7E,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,GAAG,SAAyC,GACtD,eAAe,CAEjB;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"tenant-scope.d.ts","sourceRoot":"","sources":["../../src/lib/tenant-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3D,6EAA6E;AAC7E,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,GAAG,SAAyC,GACtD,eAAe,CAEjB;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CA6BnD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAqEtD,CAAC;AAqBH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAG7D;AAYD,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GACjB;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GACzB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAChC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,SAAS,CAqDZ;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,eAAe;;;;EAyCzD"}
|
package/dist/lib/tenant-scope.js
CHANGED
|
@@ -67,6 +67,11 @@ export const TENANT_SCOPED_MODELS = new Set([
|
|
|
67
67
|
// this middleware, always filter tenant_id explicitly).
|
|
68
68
|
"Relationship",
|
|
69
69
|
"EntityRelationship",
|
|
70
|
+
// D18: MediaFile is now tenant-scoped (carries its own tenantId). Dedup is
|
|
71
|
+
// within-tenant via @@unique([tenantId, contentHash]). PostMedia is
|
|
72
|
+
// "by-relation" (no own tenantId) and crosses the scope boundary via
|
|
73
|
+
// the post→media join — flagged for T9/integration.
|
|
74
|
+
"MediaFile",
|
|
70
75
|
]);
|
|
71
76
|
/**
|
|
72
77
|
* Every other model, with the reason it is NOT auto-scoped. Exhaustive on
|
|
@@ -91,7 +96,12 @@ export const UNSCOPED_MODELS = new Map([
|
|
|
91
96
|
["FeatureToggle", "global"],
|
|
92
97
|
["IngestState", "global"],
|
|
93
98
|
["RoleMetadata", "global"],
|
|
94
|
-
|
|
99
|
+
// P0b moderation-message dedupe ledger: a system-global exactly-once table
|
|
100
|
+
// keyed on an opaque messageDedupeKey. No tenantId column; identical bytes
|
|
101
|
+
// share fan-in across tenants by design — never auto-scoped.
|
|
102
|
+
["ProcessedModerationMessage", "global"],
|
|
103
|
+
// Note: MediaFile was here as "global-content-addressed" prior to D18.
|
|
104
|
+
// It now carries its own tenantId and is in TENANT_SCOPED_MODELS above.
|
|
95
105
|
// User-scoped (boundary is userId, not tenant).
|
|
96
106
|
["User", "user"],
|
|
97
107
|
["CircleConfig", "user"],
|
|
@@ -114,6 +124,11 @@ export const UNSCOPED_MODELS = new Map([
|
|
|
114
124
|
["DirectMessage", "user-pair"],
|
|
115
125
|
// Scoped-by-relation: no own tenantId yet (doc/14 §04 C / WS0). Rely on the
|
|
116
126
|
// parent's scope + the RLS backstop until tenantId is denormalized onto them.
|
|
127
|
+
// P0b moderation job: tenant-scoped through its parent MediaFile via mediaId
|
|
128
|
+
// (onDelete: Cascade). No own tenantId column, so it cannot be auto-scoped by
|
|
129
|
+
// the where-merge/create-stamp extension — same posture as the other
|
|
130
|
+
// by-relation child tables (parent scope + RLS backstop).
|
|
131
|
+
["MediaModerationJob", "by-relation"],
|
|
117
132
|
["PostMedia", "by-relation"],
|
|
118
133
|
["PostSentiment", "by-relation"],
|
|
119
134
|
["PostSubject", "by-relation"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-scope.js","sourceRoot":"","sources":["../../src/lib/tenant-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,6EAA6E;AAC7E,MAAM,UAAU,sBAAsB,CACpC,MAA0B,OAAO,CAAC,GAAG,CAAC,iBAAiB;IAEvD,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC;IAC/D,MAAM;IACN,aAAa;IACb,QAAQ;IACR,cAAc;IACd,aAAa;IACb,OAAO;IACP,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,0BAA0B;IAC1B,mBAAmB;IACnB,kBAAkB;IAClB,eAAe;IACf,6EAA6E;IAC7E,8EAA8E;IAC9E,2EAA2E;IAC3E,gBAAgB;IAChB,wEAAwE;IACxE,2EAA2E;IAC3E,4EAA4E;IAC5E,wDAAwD;IACxD,cAAc;IACd,oBAAoB;
|
|
1
|
+
{"version":3,"file":"tenant-scope.js","sourceRoot":"","sources":["../../src/lib/tenant-scope.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@de-otio/trellis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"@vitest/ui": "^4.1.8",
|
|
92
92
|
"aws-sdk-client-mock": "^4.1.0",
|
|
93
93
|
"esbuild": "^0.28.1",
|
|
94
|
+
"fast-check": "^3.23.2",
|
|
94
95
|
"form-data": "^4.0.1",
|
|
95
96
|
"mailparser": "^3.9.11",
|
|
96
97
|
"prettier": "^3.8.4",
|
package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- T8: MediaFile tenant-scope reclassification + moderation status (D18, D13).
|
|
2
|
+
--
|
|
3
|
+
-- Summary of changes:
|
|
4
|
+
-- + tenantId (NOT NULL) — every object belongs to exactly one tenant
|
|
5
|
+
-- + ModerationStatus enum — PENDING | APPROVED | REVIEW | QUARANTINED | REJECTED
|
|
6
|
+
-- + moderationStatus column defaulting to PENDING (fail-closed)
|
|
7
|
+
-- - contentHash @unique (bare global unique) dropped
|
|
8
|
+
-- + @@unique([tenantId, contentHash]) — within-tenant dedup replaces global dedup
|
|
9
|
+
-- - gpsLatitude / gpsLongitude columns dropped (data-minimization, D13)
|
|
10
|
+
-- - @@index([gpsLatitude, gpsLongitude]) dropped with the columns
|
|
11
|
+
-- ~ metadataVisible default changed true -> false (D13 P0a)
|
|
12
|
+
-- + @@index([moderationStatus]) — moderation-queue queries
|
|
13
|
+
-- + @@index([tenantId, contentHash]) — tenant-scoped CAS serve path
|
|
14
|
+
--
|
|
15
|
+
-- Greenfield: Skybber is not live. No backfill. Dev media wiped + re-seeded
|
|
16
|
+
-- (human checkpoint per README §9 item 1 before apply).
|
|
17
|
+
|
|
18
|
+
-- CreateEnum
|
|
19
|
+
CREATE TYPE "ModerationStatus" AS ENUM ('PENDING', 'APPROVED', 'REVIEW', 'QUARANTINED', 'REJECTED');
|
|
20
|
+
|
|
21
|
+
-- AlterTable: add tenantId (NOT NULL), moderationStatus; drop GPS columns;
|
|
22
|
+
-- flip metadataVisible default.
|
|
23
|
+
ALTER TABLE "media_files"
|
|
24
|
+
ADD COLUMN "tenant_id" TEXT NOT NULL,
|
|
25
|
+
ADD COLUMN "moderation_status" "ModerationStatus" NOT NULL DEFAULT 'PENDING',
|
|
26
|
+
DROP COLUMN "gps_latitude",
|
|
27
|
+
DROP COLUMN "gps_longitude",
|
|
28
|
+
ALTER COLUMN "metadata_visible" SET DEFAULT false;
|
|
29
|
+
|
|
30
|
+
-- DropIndex: remove bare global unique on content_hash
|
|
31
|
+
DROP INDEX "media_files_content_hash_key";
|
|
32
|
+
|
|
33
|
+
-- DropIndex: the GPS composite index is already removed by the DROP COLUMN
|
|
34
|
+
-- above (Postgres cascades indexes that depend on a dropped column). IF EXISTS
|
|
35
|
+
-- makes this explicit drop a safe no-op whether or not the index is still
|
|
36
|
+
-- present / was ever named this in the migration history.
|
|
37
|
+
DROP INDEX IF EXISTS "media_files_gps_latitude_gps_longitude_idx";
|
|
38
|
+
|
|
39
|
+
-- CreateIndex: within-tenant dedup (replaces bare @unique)
|
|
40
|
+
CREATE UNIQUE INDEX "media_files_tenant_id_content_hash_key" ON "media_files"("tenant_id", "content_hash");
|
|
41
|
+
|
|
42
|
+
-- CreateIndex: moderation queue lookups
|
|
43
|
+
CREATE INDEX "media_files_moderation_status_idx" ON "media_files"("moderation_status");
|
|
44
|
+
|
|
45
|
+
-- CreateIndex: tenant-scoped CAS serve path
|
|
46
|
+
CREATE INDEX "media_files_tenant_id_content_hash_idx" ON "media_files"("tenant_id", "content_hash");
|
|
47
|
+
|
|
48
|
+
-- AddForeignKey: tenantId references tenants
|
|
49
|
+
ALTER TABLE "media_files" ADD CONSTRAINT "media_files_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- P0b: moderation job tracking — schema additions.
|
|
2
|
+
--
|
|
3
|
+
-- Summary of changes:
|
|
4
|
+
-- ~ media_files.original_key: DROP NOT NULL (nullable for video pending transcode)
|
|
5
|
+
-- ~ media_files.content_hash: DROP NOT NULL (null until the worker hashes the
|
|
6
|
+
-- transcoded bytes; @@unique([tenant, content_hash]) tolerates many NULLs)
|
|
7
|
+
-- + media_files.upload_id (TEXT, UNIQUE, nullable) — client idempotency key
|
|
8
|
+
-- + CREATE TYPE moderation_track AS ENUM ('VISUAL', 'AUDIO')
|
|
9
|
+
-- + CREATE TABLE media_moderation_jobs — per-track job records with FK + indexes
|
|
10
|
+
-- + CREATE TABLE processed_moderation_messages — SQS exactly-once dedup table
|
|
11
|
+
--
|
|
12
|
+
-- Not deployed standalone: consumed by Trellis as an npm dependency; migration
|
|
13
|
+
-- is applied when Trellis bumps @de-otio/trellis and runs prisma migrate deploy.
|
|
14
|
+
|
|
15
|
+
-- AlterTable: make original_key nullable (video rows have no key until post-transcode)
|
|
16
|
+
ALTER TABLE "media_files"
|
|
17
|
+
ALTER COLUMN "original_key" DROP NOT NULL;
|
|
18
|
+
|
|
19
|
+
-- AlterTable: make content_hash nullable (video has no hash until the worker
|
|
20
|
+
-- hashes the transcoded bytes; the within-tenant unique tolerates many NULLs)
|
|
21
|
+
ALTER TABLE "media_files"
|
|
22
|
+
ALTER COLUMN "content_hash" DROP NOT NULL;
|
|
23
|
+
|
|
24
|
+
-- AlterTable: add upload_id for client-side idempotency
|
|
25
|
+
ALTER TABLE "media_files"
|
|
26
|
+
ADD COLUMN "upload_id" TEXT;
|
|
27
|
+
|
|
28
|
+
-- CreateIndex: unique constraint on upload_id (one row per upload session)
|
|
29
|
+
CREATE UNIQUE INDEX "media_files_upload_id_key" ON "media_files"("upload_id");
|
|
30
|
+
|
|
31
|
+
-- CreateEnum: moderation track discriminator
|
|
32
|
+
CREATE TYPE "ModerationTrack" AS ENUM ('VISUAL', 'AUDIO');
|
|
33
|
+
|
|
34
|
+
-- CreateTable: per-track moderation job records
|
|
35
|
+
CREATE TABLE "media_moderation_jobs" (
|
|
36
|
+
"id" TEXT NOT NULL,
|
|
37
|
+
"media_id" TEXT NOT NULL,
|
|
38
|
+
"track" "ModerationTrack" NOT NULL,
|
|
39
|
+
"job_id" TEXT NOT NULL,
|
|
40
|
+
"decision" TEXT,
|
|
41
|
+
"threshold_snapshot" JSONB NOT NULL,
|
|
42
|
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
|
|
45
|
+
CONSTRAINT "media_moderation_jobs_pkey" PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
-- CreateIndex: unique constraint on job_id (one row per provider job)
|
|
49
|
+
CREATE UNIQUE INDEX "media_moderation_jobs_job_id_key" ON "media_moderation_jobs"("job_id");
|
|
50
|
+
|
|
51
|
+
-- CreateIndex: lookup jobs by media (used by the pipeline to find in-flight jobs)
|
|
52
|
+
CREATE INDEX "media_moderation_jobs_media_id_idx" ON "media_moderation_jobs"("media_id");
|
|
53
|
+
|
|
54
|
+
-- CreateIndex: lookup by job_id (used by the result-callback path)
|
|
55
|
+
CREATE INDEX "media_moderation_jobs_job_id_idx" ON "media_moderation_jobs"("job_id");
|
|
56
|
+
|
|
57
|
+
-- AddForeignKey: media_moderation_jobs -> media_files (cascade on delete)
|
|
58
|
+
ALTER TABLE "media_moderation_jobs"
|
|
59
|
+
ADD CONSTRAINT "media_moderation_jobs_media_id_fkey"
|
|
60
|
+
FOREIGN KEY ("media_id") REFERENCES "media_files"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
61
|
+
|
|
62
|
+
-- CreateTable: SQS exactly-once dedup for moderation result messages
|
|
63
|
+
CREATE TABLE "processed_moderation_messages" (
|
|
64
|
+
"id" TEXT NOT NULL,
|
|
65
|
+
"message_dedupe_key" TEXT NOT NULL,
|
|
66
|
+
"processed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
67
|
+
|
|
68
|
+
CONSTRAINT "processed_moderation_messages_pkey" PRIMARY KEY ("id")
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
-- CreateIndex: unique constraint on dedupe key (collision = duplicate message)
|
|
72
|
+
CREATE UNIQUE INDEX "processed_moderation_messages_message_dedupe_key_key"
|
|
73
|
+
ON "processed_moderation_messages"("message_dedupe_key");
|
package/prisma/schema.prisma
CHANGED
|
@@ -66,6 +66,18 @@ enum EntityStatus {
|
|
|
66
66
|
TRANSFERRED
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Media moderation lifecycle state. Mirrors the hand-written ModerationStatus
|
|
70
|
+
// union in apps/api/src/lib/media/moderation-status.ts — spelling must match.
|
|
71
|
+
// Every new MediaFile is born PENDING (fail-closed); only the moderation
|
|
72
|
+
// pipeline can advance to APPROVED.
|
|
73
|
+
enum ModerationStatus {
|
|
74
|
+
PENDING
|
|
75
|
+
APPROVED
|
|
76
|
+
REVIEW
|
|
77
|
+
QUARANTINED
|
|
78
|
+
REJECTED
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
model PostGeoIndex {
|
|
70
82
|
postUri String @id @map("post_uri")
|
|
71
83
|
// Multi-tenancy — denormalized from the owning post; location data is
|
|
@@ -180,7 +192,7 @@ model User {
|
|
|
180
192
|
// migration. (WebFinger/AP still resolve on `username` until the federation
|
|
181
193
|
// repoint — that change is code-only, no data migration, since `handle` is the
|
|
182
194
|
// stable key.)
|
|
183
|
-
handle String
|
|
195
|
+
handle String @unique
|
|
184
196
|
createdAt DateTime @default(now()) @map("created_at")
|
|
185
197
|
|
|
186
198
|
// Cognito Auth integration
|
|
@@ -362,8 +374,8 @@ model User {
|
|
|
362
374
|
|
|
363
375
|
// Realtime / server-blind settings sync
|
|
364
376
|
encryptedSettings EncryptedUserSetting[]
|
|
365
|
-
blocksInitiated BlockedUser[]
|
|
366
|
-
blockedBy BlockedUser[]
|
|
377
|
+
blocksInitiated BlockedUser[] @relation("BlockedUserBlocker")
|
|
378
|
+
blockedBy BlockedUser[] @relation("BlockedUserBlocked")
|
|
367
379
|
|
|
368
380
|
// Connection codes (out-of-band invite mechanism)
|
|
369
381
|
createdConnectionCodes ConnectionCode[] @relation("ConnectionCodeCreator")
|
|
@@ -605,16 +617,32 @@ model Post {
|
|
|
605
617
|
@@map("posts")
|
|
606
618
|
}
|
|
607
619
|
|
|
608
|
-
// Media files (content-addressed storage)
|
|
620
|
+
// Media files (tenant-scoped content-addressed storage).
|
|
621
|
+
// tenantId is NOT NULL — every object belongs to exactly one tenant.
|
|
622
|
+
// Dedup is within-tenant: @@unique([tenantId, contentHash]).
|
|
623
|
+
// Every new object is born PENDING (fail-closed); the moderation pipeline
|
|
624
|
+
// advances objects to APPROVED before they can be served.
|
|
609
625
|
model MediaFile {
|
|
610
626
|
id String @id @default(cuid())
|
|
611
|
-
|
|
627
|
+
tenantId String @map("tenant_id") // Tenant that owns this object (NOT NULL)
|
|
628
|
+
contentHash String? @map("content_hash") // SHA-256 hash (lowercase hex); null until post-transcode for video (set by the P0b worker)
|
|
612
629
|
cid String? @unique // AT Protocol CID (optional, for compatibility)
|
|
613
630
|
mimeType String @map("mime_type")
|
|
614
631
|
size Int
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
632
|
+
// uploadId: client-side idempotency key for the upload session. Nullable
|
|
633
|
+
// because rows created before P0b (or via the reconciliation path) have no
|
|
634
|
+
// upload session. UNIQUE so the same upload cannot race into two rows.
|
|
635
|
+
uploadId String? @unique @map("upload_id")
|
|
636
|
+
// originalKey: S3 CAS key for the raw ingested object. Nullable for video —
|
|
637
|
+
// the CAS key is computed post-transcode by the pipeline worker; the row
|
|
638
|
+
// exists before transcoding completes.
|
|
639
|
+
originalKey String? @map("original_key") // S3 key: cas/{tenantId}/{hash} (null until transcode for video)
|
|
640
|
+
thumbnailKey String? @map("thumbnail_key") // S3 key: cas/{tenantId}/{hash}/thumb
|
|
641
|
+
optimizedKey String? @map("optimized_key") // S3 key: cas/{tenantId}/{hash}/opt
|
|
642
|
+
|
|
643
|
+
// Moderation state — the lifecycle gate (see ModerationStatus enum above).
|
|
644
|
+
// Default PENDING means every object is fail-closed until approved.
|
|
645
|
+
moderationStatus ModerationStatus @default(PENDING) @map("moderation_status")
|
|
618
646
|
|
|
619
647
|
// Media metadata (extracted from file headers)
|
|
620
648
|
width Int? // Image/video width in pixels
|
|
@@ -627,13 +655,13 @@ model MediaFile {
|
|
|
627
655
|
videoMetadata Json? @map("video_metadata")
|
|
628
656
|
|
|
629
657
|
// Denormalized/unified metadata fields
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
keywords String[] @default([]) @map("keywords")
|
|
658
|
+
// GPS coordinates are NOT stored — dropped at ingestion (data-minimization).
|
|
659
|
+
dateTaken DateTime? @map("date_taken")
|
|
660
|
+
keywords String[] @default([]) @map("keywords")
|
|
634
661
|
|
|
635
|
-
// Privacy flags
|
|
636
|
-
|
|
662
|
+
// Privacy flags. metadataVisible defaults false: metadata is private unless
|
|
663
|
+
// the owner explicitly shares it (data-minimization default).
|
|
664
|
+
metadataVisible Boolean @default(false) @map("metadata_visible")
|
|
637
665
|
locationVisible Boolean @default(false) @map("location_visible")
|
|
638
666
|
|
|
639
667
|
// Media visibility and deletion tracking
|
|
@@ -659,16 +687,18 @@ model MediaFile {
|
|
|
659
687
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
660
688
|
|
|
661
689
|
// Relations
|
|
662
|
-
posts
|
|
690
|
+
posts PostMedia[] // Many-to-many with posts
|
|
691
|
+
moderationJobs MediaModerationJob[] // P0b: per-track moderation job records
|
|
663
692
|
|
|
664
|
-
@@
|
|
693
|
+
@@unique([tenantId, contentHash]) // Within-tenant dedup (replaces bare contentHash @unique)
|
|
665
694
|
@@index([cid])
|
|
695
|
+
@@index([moderationStatus]) // Gate queries: find PENDING for the moderation queue
|
|
696
|
+
@@index([tenantId, contentHash]) // Serve path: tenant-scoped CAS lookup
|
|
666
697
|
@@index([hidden, deletedAt]) // For filtering visible media
|
|
667
698
|
@@index([createdAt]) // For chronological sorting
|
|
668
699
|
@@index([dateTaken])
|
|
669
700
|
@@index([metadataVisible])
|
|
670
701
|
@@index([locationVisible])
|
|
671
|
-
@@index([gpsLatitude, gpsLongitude])
|
|
672
702
|
@@index([uploadStatus])
|
|
673
703
|
@@index([uploadedBy])
|
|
674
704
|
@@index([uploadBatchId])
|
|
@@ -693,6 +723,54 @@ model UploadSession {
|
|
|
693
723
|
@@map("upload_sessions")
|
|
694
724
|
}
|
|
695
725
|
|
|
726
|
+
// ============================================================================
|
|
727
|
+
// P0b — Moderation job tracking
|
|
728
|
+
// ============================================================================
|
|
729
|
+
|
|
730
|
+
// Discriminator for the two parallel moderation tracks.
|
|
731
|
+
// VISUAL — frame/thumbnail-level visual content analysis.
|
|
732
|
+
// AUDIO — speech-to-text + audio content analysis.
|
|
733
|
+
enum ModerationTrack {
|
|
734
|
+
VISUAL
|
|
735
|
+
AUDIO
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// One row per (media, track) moderation job submitted to an external provider.
|
|
739
|
+
// decision is String (not an enum) so the schema stays decoupled from
|
|
740
|
+
// ModerationDecision evolution; app code validates the value on read.
|
|
741
|
+
// thresholdSnapshot captures the operative thresholds at job-submission time
|
|
742
|
+
// so historical decisions remain auditable after a threshold change.
|
|
743
|
+
model MediaModerationJob {
|
|
744
|
+
id String @id @default(cuid())
|
|
745
|
+
mediaId String @map("media_id")
|
|
746
|
+
media MediaFile @relation(fields: [mediaId], references: [id], onDelete: Cascade)
|
|
747
|
+
track ModerationTrack
|
|
748
|
+
// jobId: the external provider's job identifier (unique per provider call).
|
|
749
|
+
jobId String @unique @map("job_id")
|
|
750
|
+
// decision: resolved ModerationDecision value, or null while the job is in flight.
|
|
751
|
+
decision String?
|
|
752
|
+
// thresholdSnapshot: a copy of env.media.thresholds at submission time (JSON).
|
|
753
|
+
thresholdSnapshot Json @map("threshold_snapshot")
|
|
754
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
755
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
756
|
+
|
|
757
|
+
@@index([mediaId])
|
|
758
|
+
@@index([jobId])
|
|
759
|
+
@@map("media_moderation_jobs")
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Deduplication table for SQS moderation-result messages (exactly-once delivery).
|
|
763
|
+
// A row is inserted (with ON CONFLICT DO NOTHING / unique-constraint) before
|
|
764
|
+
// processing; duplicate deliveries of the same message find the row present and
|
|
765
|
+
// are skipped. Rows may be pruned after a retention window by a cron worker.
|
|
766
|
+
model ProcessedModerationMessage {
|
|
767
|
+
id String @id @default(cuid())
|
|
768
|
+
messageDedupeKey String @unique @map("message_dedupe_key")
|
|
769
|
+
processedAt DateTime @default(now()) @map("processed_at")
|
|
770
|
+
|
|
771
|
+
@@map("processed_moderation_messages")
|
|
772
|
+
}
|
|
773
|
+
|
|
696
774
|
// Post media
|
|
697
775
|
model PostMedia {
|
|
698
776
|
id String @id @default(cuid())
|