draft-vods: wire draft lifecycle at kickoff + completion master
Phase 4: both entrypoints now create a 'processing' draft at kickoff, and the queue processors flip the draft to 'ready'/'error' on completion. Kickoff: - finalizeLivestream handler: after enqueuing the task, creates a draft that inherits the livestream's title/activity/tags and links back to the livestream via connections. Returns draftUri (additive, alongside uploadId) so the client can navigate straight to the draft. - upload TUS completion (pkg/upload/upload.go): creates a placeholder draft (filename or 'Uploaded video' as the title) when the VODProcessTask is enqueued. A failed create is non-fatal — the upload still processes. Completion (pkg/statedb/queue_processor.go): - processVODProcessTask + processFinalizeLivestreamVODTask: after the processor returns its cid, re-read the Upload row (the processor calls SetUploadProcessed internally, so the row now carries the finished TrackURIs/DurationMS/ContentCID) and flip the tied draft to 'ready' via markDraftReadyFromUpload — fills source/durationMs in the CBOR body and content_cid on the SQL row. On failure, flip the draft to 'error' so the user isn't stuck looking at 'processing'. pkg/statedb/draft_video.go: markDraftReadyFromUpload + draftSourceFromTrackURIs (the {uri,cid} JSON -> sourceTracks union conversion, replicated here because pkg/statedb can't import pkg/vod). SetDraft* remain no-ops when no draft row exists for the upload. Tests: the gating lifecycle test asserts a fake processor returning only a cid still produces a 'ready' draft carrying the upload's source/durationMs/ content_cid (proving the re-read data flow), plus the error-flip path. Full statedb/vod/spxrpc suites pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>