Skip to content

delivery-kid: port Coconut V2 finalize (httpstream) + disable broken fast path#94

Merged
jMyles merged 1 commit into
cryptograss:productionfrom
magent-cryptograss:fix-coconut-v2-finalize
Jun 1, 2026
Merged

delivery-kid: port Coconut V2 finalize (httpstream) + disable broken fast path#94
jMyles merged 1 commit into
cryptograss:productionfrom
magent-cryptograss:fix-coconut-v2-finalize

Conversation

@magent-cryptograss

Copy link
Copy Markdown
Contributor

TL;DR

Closes the chain that produced empty Release pages (the Nine Pound Hammer regression):

  1. `submit_to_coconut` — emit V2 `httpstream` shape for the finalize path. Replaces delivery-kid: port Coconut submit to V2 schema (preview path only) #90's `NotImplementedError`. Verified end-to-end against `api.coconut.co/v2/jobs` — HTTP 201.
  2. Finalize fast path disabled — was blindly reusing `state.preview_cid` as the Release CID, even though under V2 the preview now produces a single MP4 (different artifact) and was pointing at an IPFS dir containing only `metadata.json` (no playable content). Always go through the finalize transcode path until `preview_cid` can be trusted.
  3. `download_hls_outputs` — handle the V2 webhook callback shape (`outputs` is now a list of dicts with `key`, `url`, `status`) in addition to V1's dict shape. For V2's single `httpstream` output, chase master playlist → variant playlists → segments.

V2 schema discovery (continued from #90)

Curl-discovered the same iterative way as the `mp4` shape:

```bash
KEY=$(docker exec pinning-service printenv COCONUT_API_KEY)
J='{"input":{"url":"..."},'
J+='"storage":{"service":"coconut"},'
J+='"outputs":{"httpstream":{"hls":{"path":"/hls"}}},'
J+='"notification":{"type":"http","url":"..."}}'
echo "$J" | curl -s -w "\nHTTP %{http_code}\n" -u "$KEY:" -H "Content-Type: application/json" \
-X POST https://api.coconut.co/v2/jobs --data-binary @-
```

Result: `HTTP 201` with `outputs:[{key:httpstream, type:httpstream, format:httpstream, status:httpstream.waiting}]`. Confirms the minimum-viable shape works.

What this doesn't port yet

  • Quality variants (`qualities=[720, 480]` style). V2's variant config inside `httpstream` isn't documented and we get 201 on the minimal shape. Falls back to Coconut defaults. Follow-up.
  • Trim (`trim_start`, `trim_end`). Same — V2 trim syntax to be discovered. `submit_to_coconut` logs a warning when callers pass these so we have a breadcrumb.
  • Reinstating the fast path. Once preview can produce finalize-grade HLS too (a config we'd discover or once we expose variants), the reuse-preview-CID optimization can come back. Today it can't be trusted.

Test plan

  • Deploy delivery-kid (no `--rebuild` — Python only)
  • Re-upload to `ReleaseDraft:112de224-050d-4374-988f-4280424e2293` (Nine Pound Hammer)
  • Wait for Coconut V2 webhook callback (~1-2 min for a 27s video)
  • Click Finalize on the page
  • Confirm `Release:` appears with a CID that resolves to a directory containing `master.m3u8`, variant playlists, and `.ts` / `.m4s` segments (not just `metadata.json`)
  • If the V2 webhook callback shape isn't exactly what `_normalize_outputs` expects, the next iteration will be a small tweak — added defensive handling for both dict and list shapes so we degrade gracefully

Known unknowns

The V2 webhook callback payload — specifically the post-completion shape with `url` populated — isn't directly verified yet (Coconut hasn't called us back successfully under V2 for a real upload). The normalization is defensive but the FIRST real callback under this PR will tell us if anything else needs adjusting. Build-watch-iterate; should be a fast loop.

…fast path

Three related fixes for the regression that produced empty Release
pages (the Nine Pound Hammer case: Release CID points at an IPFS dir
containing only metadata.json, no playable content).

## 1. submit_to_coconut: V2 httpstream for finalize

Replace cryptograss#90's NotImplementedError. Verified against api.coconut.co/v2/jobs
that the minimal V2 shape returns HTTP 201:

    {
      "input": {"url": "..."},
      "storage": {"service": "coconut"},
      "outputs": {"httpstream": {"hls": {"path": "/hls"}}},
      "notification": {"type": "http", "url": "..."}
    }

Quality variants / trim / codec customization aren't yet exposed
because the V2 schema for those inside httpstream isn't documented
and we get 201 on the minimal shape. Falls back to Coconut V2 defaults
for now; warning logged when callers pass qualities/trim that won't
land. Follow-up to expose those.

## 2. Disable the finalize fast path

The fast path at finalize_sse_generator was reusing state.preview_cid
as the Release CID when wants_transcode + preview_cid + no trim. Safe
pre-V2 (preview produced full HLS), broken post-V2 (preview produces
a single mp4 — different artifact than finalize). Worse: when the V2
webhook download wasn't ported yet, preview_cid pointed at an IPFS
dir containing only metadata.json, and finalize blindly reused that.
That's the chain that produced the empty Release page.

Always go through the finalize transcode path until preview_cid can
be trusted to point at finalize-grade HLS.

## 3. download_hls_outputs: handle V2 callback shape

Coconut V2's webhook callback returns ``outputs`` as a list of dicts
(``[{"key": "httpstream", "url": ..., ...}]``), not the V1 dict shape
(``{"hls_av1_720p": {"url": ...}}``). Normalise both into a uniform
list, recognise the httpstream key, and chase the master playlist →
variant playlists → segments. Drops V1 keys silently.

Added _download_hls_variants_from_master that parses EXT-X-STREAM-INF
entries from the master playlist and fetches each variant + segments.

## Why this is the right scope right now

This is the minimum to get finalize producing real Release pages with
real HLS content again, in time for tour shows. The variant/codec/trim
customization is a clear follow-up but doesn't block uploads working.
@jMyles
jMyles merged commit f49e908 into cryptograss:production Jun 1, 2026
jMyles added a commit that referenced this pull request Jun 1, 2026
The Coconut webhook handler was only updating the *job* state — not the
draft state — when a finalize-side job completed. Net effect:

- HLS got transcoded and pinned to IPFS ✓
- job.hlsCid was set ✓
- But draft.json never got `status: finalized` or `final_cid` written
- Wiki diagnostics panel stayed stuck on "finalizing" forever
- Blue Railroad Imports bot never saw a finalized draft, so no
  Release page got created

Verified end-to-end against terrapin re-finalize (PR #94 deploy):
Coconut V2 ran cleanly, HLS pinned at QmVUq..., but the wiki page
showed nothing past "transcoding-submitted" and no Release appeared.

Two fixes:

## 1. content.py finalize path adds draftId to job_state

The preview-path job_state already had draftId so the webhook handler
could map back to a draft. The finalize-path job_state was missing it
— so even with the new _update_draft_finalize below, the webhook
wouldn't know which draft to update.

## 2. coconut.py adds _update_draft_finalize + wires it in

Mirror of _update_draft_preview for the finalize side. On
job.completed: state.status = "finalized", state.final_cid = hlsCid,
finalize_log appended, wiki snapshot fired. On job.failed:
state.status = "finalize_failed", error logged.

Webhook handler's tail now branches on isPreview to call the right
updater rather than only handling preview jobs.

This is a pre-existing gap exposed by PR #94 — pre-V2, the finalize
fast path was reusing preview_cid and the slow Coconut-finalize path
was rarely hit, so the missing draft-state update went unnoticed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants