Skip to content

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

  1. The browser uploads the original JSONL to Supabase Storage and inserts the wodup_import_batches row. The insert is the only direct table write the browser performs; every later status change goes through a state-machine RPC.
  2. The browser marks the upload result via mark_wodup_import_batch_uploaded_v1 (or mark_wodup_import_batch_failed_v1). Direct column updates are revoked.
  3. wodup-start-import receives batch_id. It validates the caller JWT with an anon client and performs DB work with the service key; the verified user id flows as p_user_id.
  4. The Edge Function calls enqueue_wodup_import_batch(...).
  5. The batch status becomes queued.
  6. The Edge Function returns quickly with HTTP 202 and the queued batch.
  7. The Edge Function kicks wodup-process-import-jobs with EdgeRuntime.waitUntil(...). The start function does not own the heavy normalization/import work.
  8. 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 via stage_wodup_import_batch, imports canonical sessions via import_wodup_batch_to_canonical, and updates the same batch row.
  9. 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 into user_exercise_stats_refresh_jobs.
  10. The UI polls wodup_import_batches until the status is completed, completed_with_placeholders, or failed.

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 BRID external lineage).
  • failed: import reached a terminal error. The batch row, error_message and the stored original stay; the batch's staging rows are kept for seven days of diagnosis and then purged by the daily barbelic-wodup-failed-staging-purge cron (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(...).