Cancel stops at the vendor's door
An image job in flight can at least be asked to stop. cancel() posts Ember's cancel and answers honestly that a metered request already sent may still be billed. A clip gets no such request. Once a clip is running, the answer is a 409: A submitted clip cannot be cancelled: the vendor finishes it, charges it, and it is saved. Ember's own cancel says the same from its side and accepts only a clip that has not been submitted.
So a clip's cancel is meant to work only where it is free. A task still queued or preparing is cancelled locally. Usually that is the whole story, because nothing has been sent. There is one way it is not. When a submit's answer is lost to a transport error or a 5xx, the worker puts the clip back in queued to re-send under the same key (the previous lesson), and a restart does the same with a clip left in submitting. Ember may already have accepted that request. Cancel then changes only Avatars' row: Ember's clip runs on and is charged, Avatars stops looking for it, and a retry from cancelled runs under the next attempt's key, which can pay for a second clip. A task submitting or uncertain is refused with Reconcile this task first, because nobody knows what there is to cancel. The review dialog says it before Dad presses the button: a submitted clip cannot be cancelled. Even shutting the worker down follows the rule. stop() ends the polling and never cancels authorized upstream work.
Waiting, and a sentence for every ending
While Ember reports the clip as queued, submitting, generating or downloading, the worker polls GET /api-video/jobs/{id} every 5 seconds and copies Ember's phase onto the task. A poll that errors is only a read, so the worker waits and asks again. A 404 is different. Ember no longer knows the clip, so the task becomes uncertain, with a warning that a retry pays for a new clip. When polling ends, each ending gets its own words:
| Ember says | The task becomes | The task says |
|---|---|---|
failed | failed | The vendor's reason, then Nothing was charged. |
cancelled | cancelled | Cancelled before submission |
done | copying, then done | Saving the clip |
| anything else | uncertain | Resolve it in Ember's Pro Video tab, then reconcile here; never retry blindly. |
In Ember's contract, failed means the vendor refused or could not generate the clip, and nothing was charged. The test's example is a moderation refusal, which the task reports as The prompt was rejected by content moderation. Nothing was charged. Uncertain means money may have been spent and Ember cannot say, as with a finished clip that would not download. Avatars refuses to guess and points Dad at the one place that can decide.
Reconcile asks, never re-sends
reconcile_clip() has a sharper tool than the image search, because every attempt has a name. It asks Ember by clip id when a submission was acknowledged. Otherwise, or after a 404, it asks by the attempt's key through GET /api-video/requests/{key}, rebuilding the key of the attempt that was accepted. It never posts. Ember's answer moves the task:
- No clip for that key. Nothing reached the vendor. The reconciliation is recorded with zero matches, and an acknowledged retry may now pay for a new clip.
- Done. The task goes to
copyingand saves the clip. Nothing is generated again. - Still pending. Back to
running, and the worker waits again. - Failed.
failed, nothing charged. - Still uncertain in Ember. The clip id is recorded as a match, so
retry()keeps refusing until Ember resolves it.
The test plays the hard case. Ember holds a clip as uncertain because its download failed. A retry is refused before reconciling, and again after, because the match stands. Once Ember fetches the download again, reconciling moves the task to copying, the worker finishes it, and Ember has still received exactly one request.
Material, never an avatar
When Ember reports the clip done, finish_clip() fetches the MP4 and its poster frame and passes both to save_clip(). The store checks that bytes 4 to 7 read ftyp, the box an MP4 file opens with, then names the file by its SHA-256 and writes it once, exactly as it writes an image original. The poster goes beside the thumbnails as a derivative that can be made again, and a clip whose poster could not be fetched is still saved. The asset carries media: "video", the duration and pixel size, the settings, the estimate, the charge, Ember's clip id, and the subjects and references it was made from.
That lineage is why Avatars keeps a copy at all, when Ember is every clip's home. It lets Super Soul protection follow a clip through its subjects and references, as the Protection Follows the Pixels track explains. And a clip is walled off from everything an avatar is. A reference profile refuses it, an image plan refuses it, a clip's own references and frames refuse it, and every export preset refuses it. The architecture doc calls a clip material for Dad's channel.
The charge, read back
No vendor answer carries a price, so the charge arrives late. Ember reads it from the vendor's usage analytics after waits of 20, 60, 180 and 600 seconds, about 14 minutes in all. Each time the batch detail is read, Avatars asks Ember once more for any clip whose copy has no charge yet, for up to 30 minutes after saving. After that, in the comment's words, Ember stopped looking long ago and the estimate stands. The studio shows the charge when it is known and about N credits until then. The one clip measured end to end, Pippa by a fireplace on Veo 3.1 Fast at 1280×720 for 8 seconds, took 88 seconds and charged 4,848 credits, exactly the estimate.
Playing it where it lives
A clip lives on the service host, so that is where it plays. POST /api/assets/{id}/play refuses anything that is not a clip, looks the file up in the store and opens it with open -b com.cwk.ashenreel, Ashen Reel's bundle id. The caller names an asset, never a path. The answer names the Mac by its fleet label, so the lightbox's Open in Ashen Reel button can say where the clip appeared. Ashen Reel Quest teaches the same rule from the player's side: a handoff carries an identity, never a path. Inline playback on iPhone and iPad belongs to The Studio in Your Pocket.