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 Issue Link
Files count
0
Patches count
1