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