registrystack / registrystack/registry-stack

BReg: an attachment download hides quarantine and erasure behind resource.not_found

Open
#971 0 comments 0 reactions 0 assignees View on GitHub
area:breg enhancement rust
Dominant language
Rust
Stars
2
Forks
0
Avg merge
2h 55m
Merged PRs (30d)
130

Description

## What happens today

A raw attachment download, `GET /v1/records/{route}/{id}/attachments/{slot}`, answers `404 resource.not_found` in every refusal case. One code covers four authorization outcomes and two post-authorization ones.

Authorization outcomes, concealed on purpose:

- no token, or a token that does not satisfy the profile
- a route the profile cannot reach
- a record outside the caller's row boundary
- an unknown or unreachable slot

Post-authorization outcomes, concealed as a side effect:

- the stored content is held by attachment verification and is not releasable yet
- the stored content has been erased

## Why the second pair is different

The authorization gate runs first and returns before any content lookup. `attachment_version_is_authorized` in `crates/registry-breg/src/postgres/read.rs` short-circuits with `Ok(None)`. The erasure predicate (`a.erased_at IS NULL`) and the verification predicate (`verification_status.permits_content()`) in `crates/registry-breg/src/attachment_store.rs` only run once that gate has passed. A caller who reaches them has already proved the record, the route, the profile, and the row boundary.

The published concealment statement is scoped to authorization only. `docs/site/src/data/breg-api.yaml` describes `resource.not_found` as covering "No token, a token that does not satisfy the profile, a route the profile cannot reach, or a record outside the caller's row boundary. The server does not distinguish these cases." Quarantine and erasure are not on that list.

The same facts are already disclosed to the same caller elsewhere. The attachment metadata served on a record read carries `erased` and `verificationStatus` for each slot. A caller who can reach the download can read that metadata, so distinguishing these two download refusals discloses nothing they cannot already see.

## Cost of leaving it

An operator cannot tell "this attachment was never uploaded, or you may not see it" from "this attachment exists, you may see it, and the server will not release the bytes yet" from "this attachment existed and is gone for good". The first is a caller bug, the second is a wait and retry, the third is permanent. Today all three are one 404 and the only way to separate them is a second call to the metadata route.

## Proposal

Register two codes answered only by the attachment download, and only after authorization has passed:

- quarantined content, under `409 Conflict`, retryable once verification completes
- erased content, under `410 Gone`, permanent

Open questions for whoever picks this up:

1. Naming. Every registered code today uses a stack-wide noun (`resource`, `mutation`, `request`, `action`, `lookup`, and so on), and error codes are stack-wide surface under the compatibility promise. An `attachment.` prefix introduces a new noun owned by one product. `resource.quarantined` and `resource.erased` reuse an existing noun but widen what it means.
2. `ProblemCode::title()` has no 410 arm today and would fall through to its default reason phrase. Adding 410 to that table is part of the work.
3. Compatibility. New error codes are additive under `reference/api-stability`, but the clients fail closed on a code they do not recognise: an older client meeting a quarantined attachment would report a protocol failure where it used to report a clean not-found. That belongs in the release notes, and it is why this is a ticket rather than a drive-by change.

## Scope

- `crates/registry-breg/src/problem.rs`: the two variants, their status mapping, their detail text, `DOCUMENTED`, and the 410 reason phrase
- `crates/registry-breg/src/api/attachments.rs` and `crates/registry-breg/src/attachment_store.rs`: separate the two post-authorization refusals from `concealed()`
- `crates/registry-breg/src/artifacts.rs`: the published problem schema and the OpenAPI document
- `crates/registry-breg-client/src/error.rs` and the Node and Python bindings that render a code to a caller-facing string
- `docs/site/src/data/breg-api.yaml`: the problem table, plus the `resource.not_found` description, which stops being the whole story
- the `request-attachments` acceptance baselines that assert today's 404

Contributor guide

Open the contributing guide

Research direction

Start with crates/registry-breg/src/problem.rs, then trace attachment download handling in api/attachments.rs and attachment_store.rs; run the request-attachments acceptance baselines that assert today’s 404. Review artifacts.rs, the client and binding error renderers, and docs/site/src/data/breg-api.yaml. Done means the authorized quarantine and erasure cases have documented distinct responses across the API, clients, schema, and tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, postgresql, python, rust
Domain
api, backend-api-design, databases, documentation
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.