feat(share-backend)!: remove share expiry#385
Merged
Conversation
Changing a live share's visibility previously required a full republish (POST /api/publish with override_slug), which needs the complete snapshot body — the desktop Shares page renders rows from cached metadata and has no snapshot to send. Listing state is metadata, so it gets a metadata-sized endpoint. The handler mirrors the publish/revoke discipline: bearer auth, per-user hourly rate bucket, ownership collapsed into 404, revoked tombstones immutable (410), the same profile-listed-requires-handle gate as publish, idempotent no-op on a repeat value, D1 first then the KV meta sidecar. The R2 snapshot keeps its publish-time visibility — no reader renders it, and rewriting a multi-MB object to flip one cosmetic field isn't worth the partial-failure surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Published row's ⋯ menu gains a visibility toggle — "List on profile" / "Remove from profile" — wired through a new share-publish:set-visibility IPC to the backend's PATCH endpoint. Listing is offered only when the account has a handle (the server enforces the same gate); unlisting is always offered. The toggle reuses the row's busy/lock discipline and reconciles through the normal myShares refresh, guarded by the hook's mutation generation. e2e: mock backend gains the PATCH route; new spec drives the toggle both ways through the menu and asserts the meta-line glyph plus mock state; the live-row spec now also asserts the item is absent for handle-less accounts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The expires_at publish option saw no real use in dogfooding and its cost surface was wide: validation bounds, three 410 read gates (snapshot / meta / OG), a profile-listing filter, and a scheduled R2 sweep for expired assets. All of it is gone: - publish: schema field, bound checks, and the D1/KV/R2 writes - snapshot, meta, OG: the expiry 410 gates — revoke is now the only tombstone the backend produces - profiles: the expires_at exclusion in the listing query - deletion worker: the expired-asset half of the orphan sweep - /api/me/shares: the wire field Legacy data needs no migration: the D1 column and old KV meta fields become dead data nobody reads, which means pre-removal shares that carried an expiry silently become permanent. z.object strips unknown keys, so an older client still sending expires_at gets a permanent share instead of a 422 — no ordering constraint against the app-side removal. Pre-GA, the only affected rows are our own dogfood data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Removes the share-expiry feature from the backend:
expires_atschema field, the 5-min/1-year bound checks, and the D1 / KV-meta / R2publish-block writesexpires_atexclusion in the public listing queryWhy
Nobody used it in dogfooding, and the cost surface was wide for a feature with no users: every read path carried an expiry gate, the scheduled worker swept for expired assets, and the publish validator carried bound checks. Revoke already covers the "take it down" need.
Legacy data
No migration needed, by design. The D1 column and old KV-meta fields become dead data nobody reads, so pre-removal shares that carried an expiry silently become permanent.
z.objectstrips unknown keys, so an older client still sendingexpires_atpublishes a permanent share instead of tripping a 422 — no deploy-ordering constraint against the client-side removal (next PR in the stack). New regression tests pin the legacy semantics: a share whose KV meta still carries a staleexpires_atserves 200 on snapshot/meta/OG, stays profile-listed, and is not swept.Test plan
Full share-backend suite green (238): expiry-bound cases replaced by an ignores-legacy-field case; expired-410 cases flipped to serves-legacy-200; profiles/sweep cases updated to the new semantics.
🤖 Generated with Claude Code