## Summary Removed private-channel members can currently download attachments if they know the URL. With `BUZZ_MEDIA_READ_MODE=enforce`, fresh GET, HEAD and Range requests check current membership and readable references on the primary database. Removed members receive 404; remaining members retain access, and rejoining restores access. Thumbnail aliases use the parent attachment’s permissions. Publication checks also prevent copied private URLs from granting new access. They cover attachments, link previews, profile pictures, banners, group pictures and emoji. Persona creation and changed-avatar edits, plus team and individual snapshot imports, transfer animated posters and animations under the agent’s signer before profile publication. Failed persona transfers preserve existing local records; import failures appear in the results. Authorization uses bounded queries, a two-second statement deadline and transaction-local JIT suppression. An index on edit targets avoids broad scans. Publications have a 512-reference limit, and capacity waits up to 500 ms before refusal. Protection defaults off. Existing downloaded bytes cannot be recalled. All modes stop creating immutable HTTP cache entries. ### Related issue Related approaches: #7565, #5185, #8451 and #8444. This change evaluates live stored references directly. ### Testing Published head: `3d1aa7431cc495a001a0f13e95e2e422c5a77ab1`, rebased on main `2cd40badb85de059a6916206e133da7a0a88fb2e`. Push hooks were **SKIPPED** with explicit author approval. Current-head HTTP, full package, PostgreSQL migration/media and profile-filter mutation checks are pending; CI is pending. The earlier complete HTTP journey passed at `f20b449262` (identical source tree), with its evidence attributed separately. Earlier failing preview/report-fixture runs are preserved; they do not count as passes. The HTTP journey covers upload/publication, fresh GET/HEAD/Range revocation and rejoin, emoji replacement, edited previews, persona visibility and non-reference decoys. The migration regression forces an index timeout, verifies no 0086 history or media tables, then proves a migrator through the base's 0085 can restart. Human attachment removal/rejoin testing passed on the pinned `1f08a8e0fd2edf19dc94321f7258518149fe1bf6` trial. Native animated persona edits and team/individual import acceptance remain incomplete. That old-snapshot database stays intact and cannot run the reconciled binary; a new trial needs a separate fresh database. ### Rollout prerequisites - Build and verify the reference index before shadow mode. Small databases can auto-migrate through one atomic 0086 migration within the configured timeouts. It builds the index before creating the media tables, so failure records no new migration version. Large deployments must disable auto-migration and prebuild indexes concurrently. - **Enforce remains blocked unless the operator can rule out SDK/CLI attachment edits and the unverified agent-harness edit producer.** SDK/CLI edits still omit attachment metadata. That behavior is deferred, not fixed. Removing this blocker requires latest-effective attachment metadata across successive edits, no restoration of removed files, and an upload → successive edits → GET/HEAD regression. - Validate client signer compatibility and split emoji publications above 512 references before enforce mode. - Complete the human workflows above in an isolated community with enforce enabled. - Follow the [index deployment guide](https://github.com/block/buzz/blob/honor/private-channel-media/docs/private-media-index-deployment.md) and [operator guidance](https://github.com/block/buzz/blob/honor/private-channel-media/docs/operator-private-media.md) for rollout and rollback. The [prebuild timeout finding](https://github.com/block/buzz/pull/9077#discussion_r4486221478) and [avatar-transfer finding](https://github.com/block/buzz/pull/9077#discussion_r4486221458) are implemented and independently reviewed. ### Schema change checklist - [x] Additive expand and code deployment; no backfill or contract phase. Prebuild/verify before shadow under either schema deployment path. - [x] No SQL function, trigger, procedure, rule or foreign key added. Signed events are untouched. - [x] No migration mutates existing rows. - [x] Migration and desired-state schema match. Catalog parity, target parsing, future partition inheritance and upgrade refusal are covered. - [x] Old/new binaries coexist for serving. Suspend community deletion during expansion and while rolled back to a binary with the old deletion catalog. - [x] Keep auto-migration disabled on old binaries after schema expansion; sqlx rejects unknown versions. - [x] Migration lock timeout 1 s; statement timeout 5 s. Small populated databases build inline within those budgets; large deployments use concurrent child-index prebuild/attachment with auto-migration off. Missing, invalid or wrong indexes fail verification. Generated with Codex --------- Signed-off-by: Brad Seiler <seiler@squareup.com> Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz>
4.4 KiB
Media edit target index deployment
Before enabling shadow, build and verify the partial btree on the normalized first valid edit target. Small databases may use migration 0086 with auto-migration; large deployments with auto-migration off prebuild concurrently before applying 0086 or desired-state schema. Signed event tags are never rewritten. The immutable built-in JSONPath expression preserves tag order and hex casing. No SQL function is added.
For a concurrent prebuild, use the same approach as thread windows. Run from this checkout, outside a transaction:
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -f scripts/maintenance/prebuild-media-edit-index.sql
The script creates an ON ONLY parent, builds each immediate events partition
index CONCURRENTLY with PostgreSQL-generated names (matching pgschema), then
attaches it. pgschema cannot build a partitioned parent concurrently, so this
prebuild is required for large existing databases under either schema deployment
path. The final bounded migration guard checks
readiness, validity, access method, key order, predicate and exact expression.
A valid parent proves every existing partition has an attached matching index.
New monthly partitions inherit the index through ordinary PARTITION OF creation;
the PostgreSQL regression creates a future partition and verifies attachment.
Keep partition management paused while running the prebuild; if a new partition
appears before verification, rerun the idempotent script and verify again.
A failed concurrent child build leaves an invalid index. Run only one prebuild
at a time; ensure an earlier build has ended before retrying. A retry drops
only invalid, unattached child indexes whose definitions match the parent,
using DROP INDEX CONCURRENTLY outside a transaction. It then rebuilds them
concurrently with the same generated names expected by desired-state tooling.
Concurrent child creation and cleanup disable lock_timeout so older
transactions can finish; each statement retains its 30-minute timeout. Parent
creation and partition attachment retain the one-second lock timeout.
Valid or attached indexes are retained. Wrong definitions fail attachment or
migration verification and need operator correction before proceeding. Large
deployments must retain the concurrent build procedure.
Migration 0086 builds a missing index inline on empty or populated databases, with a one-second lock timeout and five-second statement timeout. Contention or a build exceeding those budgets aborts the migration without recording 0086; retry after the contention clears, or disable auto-migration and prebuild concurrently for a large deployment. A prebuilt index skips CREATE entirely, then passes the same exact catalog validation without an index-build lock. With auto-migration disabled, operators must apply and verify 0086 before deploying this binary, even in off mode. Bypassing that ordering and enabling enforce can cause lookup timeouts and authorization-unavailable responses; restore off and complete prebuild/verification before shadow. Do not use that bypass as a soak.
This is an additive expand/deploy change: existing relay reads and writes ignore the new index, which adds only normal index maintenance. No row backfill, trigger, function or foreign key is added. New and old binaries may serve against the expanded schema. Keep auto-migration disabled on old binaries/restarts/rollback because sqlx rejects unknown migration versions. Do not run community deletion between 0086's table expansion and the new binary's deletion-catalog deployment.
Migration 0086 builds the index and expands the media ownership tables in one transaction, with the fallible index build first. An index timeout leaves the previously applied migration version unchanged, so an older auto-migrating binary can still restart. After successful expansion, the usual rollback requirement above still applies.
The earlier PR snapshot split this unmerged expansion across migrations 0084 and 0085. They were consolidated during review, then renumbered to 0086 after the base added its unrelated 0084 default-channel recovery and 0085 community-policy migrations. Both base migrations are unchanged. A disposable database that already applied an earlier PR snapshot must be recreated before testing this revision; do not rewrite its sqlx migration rows or checksums. Keep any ongoing old-snapshot trial pinned to its matching binary. Merged migration history remains unchanged.