Gap
Serializers construct attachment URLs (e.g. apps/api/src/routes/projects-members.ts:33 builds /api/attachments/${person.avatarKey} for avatarUrl) but the route itself doesn't exist — any reference to a stored attachment 404s.
Pre-requisite for #32 (avatar upload): even once upload lands, the file can't be read back without this.
Spec
specs/behaviors/storage.md — gitsheets attachment plumbing.
specs/api/people.md and specs/data-model.md#projectbuzz reference imageKey / avatarKey fields whose URLs flow through this endpoint.
Scope
GET /api/attachments/:key — streams the bytes for the given gitsheets attachment key
- Resolves via
Sheet.getAttachment(key) (or the equivalent gitsheets API at v1.3.1)
- Sets
Content-Type from stored metadata; falls back to application/octet-stream
- 404 when the key isn't resolvable; 410 when its parent record is tombstoned
- Cache-Control: long-lived since attachment keys are content-addressed
Out of scope
Gap
Serializers construct attachment URLs (e.g.
apps/api/src/routes/projects-members.ts:33builds/api/attachments/${person.avatarKey}foravatarUrl) but the route itself doesn't exist — any reference to a stored attachment 404s.Pre-requisite for #32 (avatar upload): even once upload lands, the file can't be read back without this.
Spec
specs/behaviors/storage.md— gitsheets attachment plumbing.specs/api/people.mdandspecs/data-model.md#projectbuzzreferenceimageKey/avatarKeyfields whose URLs flow through this endpoint.Scope
GET /api/attachments/:key— streams the bytes for the given gitsheets attachment keySheet.getAttachment(key)(or the equivalent gitsheets API at v1.3.1)Content-Typefrom stored metadata; falls back toapplication/octet-streamOut of scope