Skip to content

Schema: media

The media schema provides a normalized file/media registry, replacing ai.resources. It tracks uploaded objects through their full lifecycle, binds them to arbitrary entities via attachments, and integrates with Supabase Storage for the actual file bytes.

Tables

objects

Central registry of all uploaded media objects.

ColumnTypeNotes
iduuid (PK)
workspace_iduuid NOT NULLFK -> tenancy.workspaces(id)
owner_lenser_iduuid NOT NULLFK -> lensers.profiles(id). Uploader.
buckettextSupabase Storage bucket name. NULL for inline/external.
object_keytextStorage object path within the bucket
content_texttextInline text content (avoids storage round-trips)
external_urltextExternal URL reference
mime_typetextIANA MIME type (e.g., image/png, application/pdf)
media_typetext NOT NULLtext, image, audio, video, document, json, binary
nametext NOT NULLDisplay name / filename
byte_sizebigintFile size in bytes. Set on finalize.
checksum_sha256textSHA-256 hash for integrity verification
visibilitytext NOT NULL DEFAULT 'private'public, private, unlisted
lifecycle_statetext NOT NULL DEFAULT 'pending'pending, active, archived, deleted
metadatajsonb DEFAULT '{}'Extensible metadata (dimensions, duration, etc.)
created_byuuidFK -> lensers.profiles(id)
created_attimestamptz NOT NULL DEFAULT now()
updated_attimestamptz NOT NULL DEFAULT now()Auto-updated via trigger

Unique constraint: (bucket, object_key) — partial, only when bucket IS NOT NULL

One-payload constraint: Exactly one of these must be set:

  • bucket + object_key (storage-backed file)
  • content_text (inline text content)
  • external_url (external URL reference)
  • All NULL (pending object, not yet uploaded)

attachments

Binds media objects to business entities via named slots.

ColumnTypeNotes
iduuid (PK)
object_iduuid NOT NULLFK -> media.objects(id) CASCADE
entity_typetext NOT NULLe.g., lens_version, thread, profile
entity_iduuid NOT NULLID of the entity
binding_keytext DEFAULT '_default'Named slot (e.g., context_doc, reference_image)
attached_byuuidFK -> lensers.profiles(id)
attached_attimestamptz DEFAULT now()

Unique constraint: (entity_type, entity_id, binding_key) — one object per slot per entity

Upload lifecycle

1. CREATE media.objects row (lifecycle_state = 'pending')
2. GET signed upload URL via storage adapter
3. Browser PUTs file directly to Supabase Storage
4. FINALIZE via fn_media_finalize_upload (sets bucket, object_key, lifecycle_state = 'active')
5. BIND via fn_media_bind_attachment (links object to entity with binding_key)

RPC functions

FunctionPurpose
fn_media_finalize_upload(object_id, bucket, object_key, byte_size?, checksum?)Finalizes upload, sets lifecycle_state to active
fn_media_bind_attachment(object_id, entity_type, entity_id, binding_key?)Upserts attachment binding
fn_media_unbind_attachment(entity_type, entity_id, binding_key?)Removes attachment binding
fn_media_soft_delete(object_id)Sets lifecycle_state to deleted

All RPCs are SECURITY DEFINER and validate ownership.

RLS summary

objects: Owner, workspace members, or public visibility determines read access. Only owner can write/update.

attachments: Access controlled through the referenced object's visibility and ownership.

Storage buckets

BucketPublicMax SizePurpose
lens-resourcesNo50 MBLens version attachments
user-mediaNo20 MBUser-uploaded media
artifactsNo100 MBExecution output artifacts
public-assetsYes10 MBPublic thumbnails, previews