# Hash-bound private YouTube upload + Dashboard recovery

Use this when the user returns an approval command such as `APPROVE_UPLOAD <package_id> <sha_prefix>` for an AutoShorts Dashboard package and the upload must be private YouTube only.

## Safe upload sequence

1. Verify the package and hash before any API call:
   - `VideoAsset.candidate_id/package_id` matches the approved package.
   - Stored file exists and SHA256 matches DB/checksum.
   - Full SHA256 starts with the approval hash prefix.
   - YouTube `PostDraft.privacy_status == private`.
   - `quality.overall == passed` or package status is already `approved` by the Dashboard.
2. Validate OAuth before upload:
   - Token/client files exist.
   - Scope set is exactly `https://www.googleapis.com/auth/youtube.upload`.
   - Do not add read/comment/analytics scopes.
3. Create or reuse a Dashboard `UploadRequest` so the approval is auditable.
4. Call only `youtube.videos.insert` with:
   - `privacyStatus: private`
   - `notifySubscribers: false`
   - synthetic media disclosure when applicable
   - no thumbnail upload unless separately approved/known supported.
5. Write an audit file immediately after the API response with the video ID, Shorts URL, scope used, privacy, approval command, and video SHA.
6. Update Dashboard rows (`VideoAsset`, `PostDraft`, `UploadRequest`, `YouTubeUploadAudit`) from that audit; do not rely on chat memory.

## Post-API failure recovery

If YouTube returns a video ID but a local DB/audit update fails, **do not rerun the uploader**. Treat this as `upload succeeded, local bookkeeping failed`:

1. Read the already-written audit file or captured API response.
2. Recover the `youtube_video_id` and `youtube_url`.
3. Insert/fix the Dashboard audit/status rows manually or with a repair script.
4. Mark only the active upload request `completed`.
5. Mark duplicate older approved/sent requests for the same package as superseded/failed so they cannot execute later.

This prevents accidental duplicate private uploads after a late SQLite/schema error.

## Dashboard schema pitfalls

- Some tables require UUID IDs; do not assume SQLite autoincrement. Inspect `pragma table_info(...)` or existing rows before inserting into audit/event tables.
- `YouTubeUploadAudit` may require `platform_account_id`; use the existing Jarvis uploader `PlatformAccount` when the real token lives outside the Dashboard DB.
- `ActivityEvent` may require `created_by`; missing activity events are less important than correctly updating the core upload/audit rows.
- If approving via Dashboard changes `VideoAsset.status` from `ready_for_review` to `approved`, keep that status eligible for upload; do not fail just because it is no longer `ready_for_review`.

## Privacy-safe website follow-up

After a private upload, immediately run the website companion sync, but keep the private URL hidden:

- Store `youtube_video_id`, private Shorts URL, `youtube_privacy: private`, and `website_link_allowed: false` in candidate/source metadata.
- Set visitor-facing `videoUrl` to `null` until the user makes the Short public or explicitly allows the link.
- Build and grep generated HTML/JS for the YouTube ID. It must not appear in visitor-facing dist files.
- Commit/push the companion metadata/page separately from upload bookkeeping.

## Final report shape

Report:

- package ID and full video SHA
- YouTube video ID + Shorts/Studio URL
- privacy status
- upload endpoint and exact OAuth scope
- Dashboard row statuses
- audit path
- website sync status and whether the private ID is absent from built HTML
- explicit note that TikTok/API and public website video button were not used unless they were explicitly requested.