A price before the button, and the picture that vanished when I archived one asset

Contribution Date
Contribution Project
Contribution Details
A price before the button, and the picture that vanished when I archived one asset The executor moved objects and the restores landed; nothing could ask for either. This is the surface: read a plan, run a sweep, quote a restore, ask for one, approve an expensive one — plus the two places the rest of the system had to learn that bytes can be cold. ## The estimate has to come before the confirmation, so quoting had to stop recording §6.5 requires the cost and the ETA *before* a user commits, and the only thing that produced a plan was the POST that also records the request. A screen could show a price or ask for a restore, and showing the price meant having already asked. `GET /assets/{id}/restore/quote` prices all three tiers in one response, because the comparison is the whole reason to show a number and three round trips would render half a decision. A tier the class cannot offer is present with `available: false` and the reason rather than absent: Deep Archive has no Expedited, and a chooser showing two options where another asset shows three invites a question the response can simply answer. ## Cold bytes are named at the mint, not discovered by following the URL The delivery route refuses them properly — a `202` with the class, the ETA and where to ask, rather than the `InvalidObjectState` XML that S3 was handing browsers in a response damrs never saw. But `POST /assets/{id}/download` was still minting a URL and calling it `ready`, so a client was handed something fetchable and the wait surfaced somewhere else entirely. It reports `archived` now, above the ledger write, because nothing was distributed and a cold asset that consumed a download every time somebody clicked would exhaust a capped licence without delivering a byte. After rights, deliberately: telling somebody how to thaw an asset they are not licensed for invites them to spend money on a download that will still be refused. ## Archiving one real asset made its grid cell disappear The archival check read the original's placement for *every* delivery, including thumbnails — so the first thing I saw after archiving one asset was a badge saying Archived next to a blank square. The paragraph above the function said this could not happen, because a derivative is its own object and `Key::is_tier_exempt` keeps the engine off it. The code did not do what the comment said. No test would have caught it. None of them archived anything, which is the same shape as the search-thumbnail bug: the assertion that would have failed was the one nobody thought to write, and driving the real thing is what found both. ## `restore` and `archive` join the bulk vocabulary `restore` was already in the schema's kind list, and `restores::in_batch` exists so a collection restore is one decision with one cost and one ETA — it had no caller. What the batch does *not* do is collapse the S3 calls: `RestoreObject` is per object, so four hundred assets are four hundred calls whatever the grouping, and claiming otherwise would be claiming a saving that does not exist. `archive`/`unarchive` are new, and they are the *curation* status rather than the storage tier. `assets.status` has accepted `'archived'` since 0001 and `status:archived` has been a live search selector since Q.15, with no action anywhere that could set it: the state was reachable by query and not by any means of getting there. ## Two survivors from the mutation sweep, both real A "machine key cannot approve a spend" case passed for a reason it was not testing — `authorize` refuses an identity-less key before any handler runs, so the handler's own check is unreachable. The case now asserts the property that is actually true, across four routes, and the guard is documented as unobservable rather than left reading as covered. It stays because it is the only correct handling and because `Caller::identity_id` is an `Option` three other call sites also re-check; the cleanup is its own item in TASKS.md. The other pointed out that dropping the `WHERE` guards from the new bulk kinds broke nothing: they had been wired through with no coverage at all, so `archive` was free to overwrite `deleted` and to archive an asset that was still mid-pipeline. Both now assert what they skip and why. Fourteen HTTP cases, four new delivery cases, one bulk case; seven mutations caught and one equivalent mutant confirmed as such by hand rather than filed as a gap.
Contribution Author
Bassam Ismail
Files count
0
Patches count
1