Skip to content

feat(share-backend)!: remove share expiry#385

Merged
graydawnc merged 3 commits into
mainfrom
feat/remove-share-expiry-backend
Jun 10, 2026
Merged

feat(share-backend)!: remove share expiry#385
graydawnc merged 3 commits into
mainfrom
feat/remove-share-expiry-backend

Conversation

@graydawnc

Copy link
Copy Markdown
Collaborator

What

Removes the share-expiry feature from the backend:

  • publish: the expires_at schema field, the 5-min/1-year bound checks, and the D1 / KV-meta / R2 publish-block writes
  • snapshot / meta / OG: the three expiry 410 gates — revoke is now the only tombstone the backend produces
  • profiles: the expires_at exclusion in the public listing query
  • deletion worker: the expired-asset half of the orphan sweep (the revoked-asset half stays)
  • /api/me/shares: the wire field

Why

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.object strips unknown keys, so an older client still sending expires_at publishes 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 stale expires_at serves 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

graydawnc and others added 3 commits June 10, 2026 16:33
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>
@graydawnc
graydawnc added this pull request to the merge queue Jun 10, 2026
Merged via the queue into main with commit f2f793d Jun 10, 2026
6 checks passed
@graydawnc
graydawnc deleted the feat/remove-share-expiry-backend branch June 10, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant