-- 038: media assets for publishing (media attachments backend). -- -- Owner need: posts need images/video, not text alone. This is the asset -- spine the draft's mediaAssetIds point at — one row per uploaded file, the -- bytes themselves in object storage (envelope-encrypted, content-addressed) -- keyed by storage_key. Everything is per-workspace; the sha256 is the -- plaintext content hash the storage service computes, so a re-upload -- deduplicates the OBJECT but still gets its own row (different alt text, -- different post usage). created_by is SET NULL on user deletion: the asset -- outlives the account that uploaded it. CREATE TABLE media_assets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, storage_key text NOT NULL, mime text NOT NULL, size_bytes bigint NOT NULL CHECK (size_bytes > 0), sha256 text NOT NULL, alt_text text, created_by uuid REFERENCES users (id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (id, workspace_id) ); CREATE POLICY tenant_isolation ON media_assets USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid); CREATE INDEX media_assets_workspace_created ON media_assets (workspace_id, created_at);