docs(micropub): design proposed media-endpoint extensions - #374
Conversation
Add the reviewed design for the proposed media-endpoint extensions to spec/packages/micropub.md, per #363: media q=source (list and by-URL), the JSON upload response body, and a recoverable, principal-scoped action=delete backed by a .trash/ R2 prefix and a new micropub_media metadata table, all gated behind extensions.proposed (off by default). Design only — no implementation yet. Closes #363 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
davidwkeith
left a comment
There was a problem hiding this comment.
Reviewed against CONTRIBUTING.md: PR title (docs(micropub): design proposed media-endpoint extensions) is correct Conventional Commits style, the template's Summary/Packages affected/Checklist headings are kept verbatim, and every unchecked box carries a one-line reason — all compliant.
One design inconsistency worth resolving before/at implementation time, left as an inline comment: the new "unconditional" micropub_media metadata write introduces a 500 failure mode on upload even when extensions.proposed is off, which conflicts with this doc's own "observable behaviour is unchanged [when disabled]" claim a few paragraphs earlier. Everything else in the design (scope pairing, URL-ownership validation, the trash state machine, retention/purge story) is internally consistent and matches the patterns already established by the q=geo and Contacts sections above it.
Generated by Claude Code
Address review: the unconditional micropub_media insert must not change the media endpoint's failure surface while extensions.proposed is off. Split the insert-failure handling by enablement — fail-closed (delete blob, 500) only when the group is on and the row is load-bearing for q=source; best-effort (log, keep blob, 201) when it is off, preserving today's guarantee that a successful R2 write means a successful upload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A GET of the media endpoint root is 405 Method Not Allowed today, not an unsupported-query 400 — the byte-identical disabled-path claim must match. Specify the enabled-path GET fallback (unknown/absent q → 400) explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
davidwkeith
left a comment
There was a problem hiding this comment.
Reviewed this design-only spec change against CONTRIBUTING.md — no issues found.
CONTRIBUTING.md conformance: PR opened from the template with Summary/Packages affected/Checklist headings intact, Conventional Commits title (docs(micropub): design proposed media-endpoint extensions), and the two unticked checklist items (tests, changeset) each carry a correct one-line reason rather than being silently unchecked or deleted — appropriate for a docs-only design PR per this repo's own convention ("discuss significant changes first" / spec-before-code).
Content: the design is internally consistent and thorough — the enable/disable gate, the micropub_media D1 metadata model, the load-bearing copy-then-delete trash ordering with its 4-state retry table, the deliberate scope split (media-only for q=source vs. the post endpoint's scope-less version), and the "Test coverage" checklist for the follow-up implementation PR are all well specified. I cross-checked the companion implementation in #375 against this section and it matches faithfully (see review there).
One very minor observation, not a change request: the "orphaning is deliberate" rationale (no reference-counting against posts) is a reasonable scope boundary, and it's good that it's stated explicitly rather than left implicit.
No blockers — reads as ready to merge ahead of #375, which depends on it.
Generated by Claude Code
Summary
Adds the reviewed design for the proposed media-endpoint extensions to
spec/packages/micropub.md, closing #363 (roadmap #354). The section adoptsthree upstream IndieWeb proposals as one feature gated behind
extensions.proposed(off by default, existing media behaviour byte-identicalwhen disabled):
q=source— newest-first listing (limit/offset, matching thepost list) and by-URL lookup, returning the interop-consensus
url/published/mime_typeshape; requires themediascope (deliberate,justified difference from the post endpoint's scope-less
q=source).201+Locationgains the upstream minimum{ "url": ... }JSON body when enabled.action=delete(+ package-definedundelete) — requires bothdeleteand
mediascopes per the upstream proposal; strict URL-ownershipvalidation (exact
${mediaEndpoint}/<key>match, UUID-format key) defendsagainst forged URLs and path/key confusion.
Storage lifecycle: a new
micropub_mediametadata table in the existingMICROPUB_DBbinding (needed for chronological listing — R2 keys are randomUUIDs), and recoverable soft deletion via a
.trash/<key>R2 prefix with aload-bearing copy→delete→mark ordering, a four-state retry-convergence table,
and purge delegated to an R2 lifecycle rule (
mediaTrashRetentionDays,default 30). Orphaning against posts is explicitly deliberate (no reference
counting). Bindings and Config sections updated accordingly.
Design only — no implementation yet; the section states this explicitly,
mirroring how other proposed-extension designs landed (#359–#362 → #366 etc.).
Packages affected
@dwk/micropub (spec only)
Checklist
spec/packages/and updated them ifbehaviour changed
src/*.test.ts) — not applicable, docs-only design; required coverage is enumerated in the new section for the implementation PRpnpm lint && pnpm format:check && pnpm typecheck && pnpm build && pnpm testpnpm changeset) — not applicable, spec/ docs only, no published package content changescatalog.json/conformance/status.json— not applicable, no new worker or conformance change🤖 Generated with Claude Code