Backlog: upload + review documents on HITL tasks (signed URLs, internal_files).
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.
The model already exists: internal_tracker_tasks has owner / reviewer / status
(waiting → review → done) / waiting_on / milestone and an
evidence_file_id → internal_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.
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)
POST /api/tracker-upload { taskCode, filename, contentType }.createSignedUploadUrl(bucket='internal-files', path) and returns the URL +
the planned storage_path.POST /api/tracker-upload/confirm { taskCode, storage_path, original_name, size, contentType }.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.GET /api/tracker-file?taskCode=… → Function returns a short-lived signed
download URL (createSignedUrl, ~60s) for the task's evidence file; logs
file_events:download.status → done) or returns (status → waiting + a reason).
Both write a tracker event (who/when/why), same as status changes.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."
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
internal_files.visibility scopes exposure (private / internal / project).file_events + tracker events give a full who/uploaded/downloaded/approved
trail./api/tracker-upload (signed upload URL) + /confirm (record + transition)/api/tracker-file (signed download URL)