HITL file upload — design

Backlog: upload + review documents on HITL tasks (signed URLs, internal_files).

Source: hitl-upload-design.md
Updated: 2026-06-22

HITL file upload + review — design note (backlog)

Status: implemented (v1.5.0) — work item hitl-file-upload, Phase A / gate A4. Endpoints live: /api/tracker-upload (POST file), /api/tracker-file (GET signed download), /api/tracker-task (POST status). HITL panel is on the Live Board. v1 proxies the file through the Worker (fine for documents); the signed-upload-URL flow below is the future optimization for large files. Needs the internal-files bucket to stay private (default) — service role handles read/write. Lets a HITL task carry an uploaded document with an in-app review/approve flow — e.g. Mark uploads an S-corp proposal, Paul reviews the language and approves — instead of doing it over email.

It's a feature, not a sprint

The model already exists: internal_tracker_tasks has owner / reviewer / status (waiting → review → done) / waiting_on / milestone and an evidence_file_idinternal_files FK; internal_files + the internal-files Storage bucket are set up; file_events audits upload/download. So this is UI + a couple of Worker endpoints + a Storage policy — no new schema.

Lifecycle

HITL-BIZ-001 "Draft S-corp filing plan"  (owner Mark, reviewer Paul, A4)
  waiting   ── Mark uploads proposal.pdf ─► internal_files row + evidence_file_id
                                            + file_events:upload + tracker event
            ── status → review
  review    ── Paul opens (signed download) ─► approve → done
                                              or return → waiting (+ reason)

Upload mechanism (signed URL — don't proxy big files through the Worker)

  1. Browser asks the Access-gated Function for an upload slot: POST /api/tracker-upload { taskCode, filename, contentType }.
  2. Function (service role) calls Supabase Storage createSignedUploadUrl(bucket='internal-files', path) and returns the URL + the planned storage_path.
  3. Browser PUTs the file directly to that signed URL (Storage), so large files never pass through the Worker.
  4. Browser confirms: POST /api/tracker-upload/confirm { taskCode, storage_path, original_name, size, contentType }.
  5. Function inserts internal_files (owner_id = the Access person's profile, project_id, bucket, storage_path, original_name, content_type, size_bytes, visibility='internal'), sets task.evidence_file_id, logs file_events:upload, and moves the task waiting → review.

Review / download

UI

A HITL panel (on the Live Board or its own tab) listing internal_tracker_tasks: code, title, owner, reviewer, status, waiting-on, and the attached file (with an Upload button for the owner, a Review/Download action for the reviewer). Filter "waiting on me."

Notifications (how the owner knows)

The tracker is the record; Slack/email is the nudge — post to the channel (or email) when a task is created/assigned or flips to "waiting on ". A simple webhook from the Function on those transitions.

Security (sensitive legal/equity docs)

Build checklist (when scheduled)