Wodup Import Async Jobs
Wodup import uses public.wodup_import_batches as the import job queue.
Import is one-shot: a provider file is canonical exactly once, and after that the app owns the data. Re-ingest is an owner-run service procedure, not an app feature — see import-pipeline.md for the pipeline shape, the four invariants, and the removal/re-ingest runbooks.
Flow
- The browser uploads the original JSONL to Supabase Storage and inserts the
wodup_import_batchesrow. The insert is the only direct table write the browser performs; every later status change goes through a state-machine RPC. - The browser marks the upload result via
mark_wodup_import_batch_uploaded_v1(ormark_wodup_import_batch_failed_v1). Direct column updates are revoked. wodup-start-importreceivesbatch_id. It validates the caller JWT with an anon client and performs DB work with the service key; the verified user id flows asp_user_id.- The Edge Function calls
enqueue_wodup_import_batch(...). - The batch status becomes
queued. - The Edge Function returns quickly with HTTP 202 and the queued batch.
- The Edge Function kicks
wodup-process-import-jobswithEdgeRuntime.waitUntil(...). The start function does not own the heavy normalization/import work. - The worker claims the batch via
start_wodup_import_batch, downloads the original file, verifies size/hash against the sealed batch row, normalizes JSONL, stages rows viastage_wodup_import_batch, imports canonical sessions viaimport_wodup_batch_to_canonical, and updates the same batch row. import_wodup_batch_to_canonical(...)refuses a batch whose staged session identity already exists in canonical tables (22023/conflict_type: import_already_applied) — this is the one-shot gate. It also enqueues affected stats refresh work intouser_exercise_stats_refresh_jobs.- The UI polls
wodup_import_batchesuntil the status iscompleted,completed_with_placeholders, orfailed.
Imported sessions are read-only in the app: the session writers (save_session_v5 / delete_session_v5, issue #1215) refuse sessions whose source is not 'barbelic' (22023 / conflict_type: imported_session_read_only).
Status Contract
wodup_import_batches.status is the job lifecycle:
uploading: original file row is being created.uploaded: original file exists and can be queued.queued: start request was accepted; background work should run.normalizing: original file is being verified and normalized.ready: normalization finished; staged rows await canonical import.importing: staged rows are being materialized into app tables.completed: import finished without placeholders.completed_with_placeholders: import finished with owner-scoped external exercises for unmapped provider keys (the global-placeholder two-step is retired; mapping misses materialize once, in place, on the BRIDexternallineage).failed: import reached a terminal error. The batch row,error_messageand the stored original stay; the batch's staging rows are kept for seven days of diagnosis and then purged by the dailybarbelic-wodup-failed-staging-purgecron (purge_wodup_import_failed_staging_v1).
The browser does not wait for the long import work in the start request. It only starts the job and polls the batch row. The worker may be invoked by the start function, Supabase cron, or a future dedicated worker runner.
Audience
enqueue_wodup_import_batch, start_wodup_import_batch, stage_wodup_import_batch, and import_wodup_batch_to_canonical are service_role-only. Edge Functions are the sole callers; browsers never invoke them. Because the worker reads storage with service_role (BYPASSRLS), the batch row itself carries the ownership guard: wodup_import_batches_storage_ownership_check forces storage_path and normalized_storage_path to start with <user_id>/.
Retry Semantics
Calling wodup-start-import for an already queued batch is safe. The DB enqueue function keeps the row queued and refreshes last_operation_id.
If a duplicate worker starts, start_wodup_import_batch(...) can only claim uploaded or queued batches. A batch already in normalizing or importing remains owned by the first worker.
Re-running a batch that already reached canonical tables is refused by the one-shot gate. The only way back is the owner runbook: remove the provider data (remove_import_data_v1), requeue the batch from its preserved original (requeue_wodup_import_batch_v1), then start the import again — see import-pipeline.md.
Function Boundaries
wodup-start-import: validates the request, checks batch ownership, enqueues the batch, and kicks the worker.wodup-process-import-jobs: claims queued batches and performs normalization, staging, canonical import, and final batch status updates._shared/wodup-import-worker.ts: contains the reusable worker implementation so future worker entry points do not copy the import algorithm.
Stats Refresh
Canonical import does not directly compute stats from the browser request. It routes affected user/exercise ranges through user_exercise_stats_refresh_jobs, so stats work remains observable and retryable by job id. The stats-process-refresh-jobs Edge Function is the worker entry point for that queue and calls process_user_exercise_stats_refresh_jobs(...).