Skip to main content

Media review through the website and MCP

Status: current recommendation for owner review, 2026-09-11. Fresh source investigation at 74cf6b8a8e96cadf9622aba1cddaef9d0c35930b. No implementation, account grants, submission decisions, or production operations were performed. Authenticated runtime behavior was not tested. This extends trusted contributors and bulk contributions.

Product requirement and recommendation​

BASIC explicitly requires review through ordinary MCP tools as well as the website. A collection workflow is incomplete when a client can submit a collection but cannot inspect its actual candidate images and resolve the review queue. Ship both transports against one backend authorization and decision implementation. Do not make MCP review an operator script or require browser cookies in an agent.

Contributor capacity, upload authority, publisher authority, and review authority remain separate. BASIC approved a separate trusted-publisher permission on 2026-09-11 for clearly sourced own contributions filling empty image/logo slots on public unclaimed profiles. Replacements, uncertain identity/attribution, disputes, and previously rejected/suppressed material require independent review. A byte-upload authorization does not grant publication authority, and self-publication must not be recorded as independent review. The same-user prohibition remains on independent decisions, including when the actor changes clients. See the approved policy in the canonical brief; current code has not changed.

Verified current state​

References below are repository-relative files with exact source line anchors at the inspected commit. They describe source behavior, not a new live test.

SurfaceCurrent behavior and source
Browser accessconvex/profileMediaSubmissions.ts:249, :1105: active browser session required; current profile owners or super-admins may review, but unclaimed targets require super-admin. getReviewAccess returns active owned profiles and super-admin status. There is no limited reviewer grant or batch assignment here.
Browser queueconvex/profileMediaSubmissions.ts:1167: cursor pagination, oldest first, filtered by one status and optional profile. Global queue requires super-admin; a profile queue checks authority for that profile. It does not filter expired rows out of submitted/under-review listings, although decisions reject expired rows.
Queue presentationapps/web/src/app/account/media-review/media-review-panel.tsx:16, :179: 40-row pages and load-more; submitted, under-review, approved and rejected filters; target link, candidate image, source, credit, alt text, same-profile exact-hash prior-proposal count, contributor note and profile-change warning. No current-image comparison, batch grouping, selected-item decisions, explicit rebase, review expiry countdown, or legal-hold controls.
Private evidenceconvex/profileMediaSubmissions.ts:304: super-admin rows additionally include submitter name/email/token identifier, reviewer identifier and private reason. Owner rows do not receive these fields. Duplicate counts are capped at 20 plus a truncation indication; they are not perceptual duplicate detection. Do not reuse the full super-admin projection for a new limited reviewer.
Candidate image bytesconvex/profileMediaSubmissions.ts:1131 and apps/web/src/app/api/account/media-review/submissions/[submissionId]/file/route.ts:34: cookie-authenticated route checks backend review access, validates intent linkage and uploaded/consumed state, then reads the stored object. Response is private/no-store with sandbox CSP and nosniff. It does not send a public storage URL or refetch the original source. Owner previews are restricted to submitted, under-review and approved states; super-admin access is broader.
Start reviewconvex/profileMediaSubmissions.ts:1240: submitted, unexpired item becomes under-review with reviewer attribution; same-user review refused. This is a status marker, not an exclusive reservation or lease. There is no release action, and another authorized reviewer can decide the row.
Decideconvex/profileMediaSubmissions.ts:1267: backend accepts submitted or under-review; UI requires start-review first. Requires current public target, active authority, distinct submitter/reviewer, exact current profile revision and private reason. Rejection additionally requires contributor-visible reason. Approval verifies uploaded intent, original placement asset snapshot, duplicate published hash, valid placement and credit; consumes upload into an attributed public asset and writes an audit event.
Stale placementconvex/profileMediaSubmissions.ts:1350: approval compares current placement with the submission's original targetPlacementAssetId. Merely refreshing the queue cannot update that stored snapshot. No rebase mutation exists. Reject remains possible when the target is otherwise eligible.
Claim transitionconvex/profileMediaSubmissions.ts:1292: approval does not categorically reject claimed profiles; current owner/super-admin authority governs. A donor who later owns the profile still cannot decide their own row. The new limited-reviewer design must explicitly retain owner control when a target is claimed.
Withdrawconvex/profileMediaSubmissions.ts:1214; apps/web/src/app/account/media-contributions/media-contributions-panel.tsx:20: submitter can withdraw an open-status item in the browser; private blob deletion is scheduled after retention. Review has no exclusive lock preventing withdrawal.
Suppressconvex/profileMediaSubmissions.ts:1430: super-admin-only suppression of the matching approved community asset; marks asset deleted and moderator-suppressed with audit reason. Submission itself remains approved. Therefore history must show asset suppression separately from proposal approval.
Cleanup and holdsconvex/profileMediaSubmissions.ts:1473, :1596: browser super-admin cleanup path, and a super-admin legal-hold mutation. Hold refuses already-deleted or cleanup-in-progress blobs. Hold controls are absent from the review panel. Holds affect retention, not publication authorization.
Hosted MCP gapapps/web/src/lib/server/vrdex-mcp.ts:106, :127, :139, :2135, :2654: submission and own-submission status exist; no reviewer queue, private candidate image read, start-review, decision, rebase, withdrawal, suppression or legal-hold tools. Owner vrdex_profile_media_manage is asset management, not proposal review.
Delegation gappackages/api-contracts/src/auth.ts:1, packages/api-contracts/src/oauth.ts:35: no review-specific scope exists. assets:contribute is contribution/status delegation; assets:write is owner asset management. Existing grants must not silently acquire moderation access.

Tests already exist in tests/backend/hosted-mcp-profile-media-contributions.test.ts and tests/backend/auth-session-authorization-boundary.test.ts. The former includes the independent-review refusal and the latter covers browser-session boundaries. This investigation did not run tests or claim those tests prove the proposed functionality.

Small shared review contract​

Extract policy and transitions into backend helpers accepting a validated actor context. Keep browser wrappers requiring browser sessions. Add internal MCP wrappers that resolve the delegated user and validate token/client/scope through the existing hosted-MCP authority path. Never accept a client-supplied reviewer ID or let an API token impersonate a browser session.

Recommended scope shape: assets:review:read for private review queue/detail/preview and assets:review:write for review actions, paired with mcp:read or mcp:write respectively. Exact names are a proposal. Both remain intersected with owner authority, super-admin authority, or active reviewer grant plus assigned batch. Read-only delegation should be possible without publication authority. Request fresh OAuth consent; update registration allowlists, consent text, discovery, token validation, documentation and tests together. A grant cannot widen an existing token's scopes.

Prefer a small explicit tool set:

Proposed toolContract
vrdex_list_media_reviewsOptional profile/batch/status filters, cursor and bounded page size. Returns only caller-authorized rows, expiry, action availability and conflict codes, stable IDs and current revision. Default actionable queue excludes logically expired rows. History remains separately filterable. No preview bytes or raw private source URLs in a bulk listing.
vrdex_get_media_reviewExact submission ID. Returns target identity, current image context, candidate metadata, public credit, necessary private provenance, duplicate hints, current disposition, allowed actions and a review version bound to candidate content, metadata, target revision and placement. Private identity fields and internal moderation notes remain separately restricted.
vrdex_get_media_review_previewExact submission ID and review version, optionally candidate/current image variant. Returns bounded native MCP image content from the stored candidate, with MIME/dimensions and the same version. If a supported client needs a separate authenticated resource read, use the same authorization policy. Never use arbitrary source fetches as the review image.
vrdex_decide_media_reviewExact submission ID, expected review version, decision, final public metadata, required private reason and rejection disposition, idempotency key. Atomic authority/staleness checks and durable per-item receipt. Returns applied/replayed/conflict/refused with asset ID when approved and safe next action. Supports lookup/replay after a lost response without publishing again.
vrdex_rebase_media_reviewExact submission ID, expected old review version and explicitly selected current target/placement version. Creates an auditable proposal revision and invalidates older decisions. Does not approve. Subsequent detail/preview and independent decision use the new revision. No automatic rebase inside approval.
vrdex_withdraw_media_submissionOwn submission ID, expected version and idempotency key, under contribution scope. No review grant required. Preserves audit and retention behavior.

Capability discovery should return the caller's permitted review scopes/resources, supported preview formats, decision operations and page/batch bounds. A contributor with no review grant sees a clear unavailable capability, not someone else's private queue. Missing scope and missing account/resource authority are different errors.

Native MCP image content is the preferred v1 because it works without exporting a private bearer URL or requiring browser navigation. Verify rendering in the actual supported clients. Use a bounded normalized rendition for large originals, preserving aspect ratio and enough detail to judge identity/crop; expose whether it is a rendition. Keep the content digest internal and bind it into an opaque review version. A preview response proves which bytes were returned, not that a human or model understood them.

If minted preview endpoints are added, they are a distinct read capability from upload endpoints. Prefer an authenticated server route with authority rechecked on every redemption. Any bearer-only link needs explicit TTL, audience and replay semantics, log redaction, and revocation handling; do not promise immediate grant revocation if a previously minted URL bypasses that check. Never expose private source-fetch credentials or storage keys as the viewing contract.

Website and batch behavior​

Both transports also need an explicit publish-own-contribution action for the separately granted trusted publisher. Proposed delegation is mcp:write assets:publish, with own-item-only detail/preview access and a current publisher grant. This is separate from assets:review:write; exact scope/tool names remain proposed. Intake and upload completion remain private. Publication checks the inspected revision, source/credit and identity evidence, current public/unclaimed target, empty slot, and applicable rejection/dispute/suppression history in the committing transaction. A concurrent slot fill routes the item to independent review. Record publisher identity and publication method without inventing a second reviewer. Give publishers the option to request independent review even when eligible.

Keep the existing review page and add batch/status filters, current-versus-candidate comparison, expiry and conflict indicators, and exact selected-item decisions. New views should expose the same review version and action availability as MCP. Show concise fields and images, not trust essays. New substantive public copy remains subject to BASIC's exact-copy review.

Starting review may remain an optional advisory action using the same helper, but it should not be mandatory before every decision. Avoid adding exclusive leases merely to prevent duplicated attention. Optimistic decision checks solve the correctness problem: the first valid decision wins and later attempts report the resulting state. If staffing later warrants assignments or claim/release indicators, do not conflate them with the independent-review rule.

For v1 bulk decisions, use a bounded array (proposed maximum 20) of explicit submission IDs, viewed review versions, individual decisions/reasons and per-item idempotency keys. Implement as independent calls to the same single-item decision helper with partial results. No approve all matching filter or future-inclusive batch approval. The browser can submit this selection; MCP clients can use the same optional bounded wrapper or iterate the single-item tool. Do not introduce a second batch-publication engine.

Changing the profile while processing one item can invalidate another selected item's version. Report that conflict and require fresh inspection, especially for multiple images competing for one placement. Do not refresh revisions behind the reviewer's back. Missing bytes, revoked authority, expiry, new claim, source restriction, changed candidate metadata and suppression must also stop the affected approval. A cursor is a navigation aid, not a snapshot approval token.

Rebase should initially address placement/profile staleness on the same target. Retargeting an image to another identity is a new explicit proposal revision with full authority/provenance checks, not a convenient automatic correction. Preserve old context for audit. Source evidence is untrusted content and must never become instructions to the reviewing agent.

Rebase is a reviewer action under review-write delegation and current owner/super-admin or assigned-batch authority. Ordinary contributors can request review or withdraw/resubmit their own item; contribution scope does not grant rebase or private reviewer-detail access. Rebase never satisfies the independent publication decision.

Legal holds, cleanup and suppression remain separate administrative powers. Do not include them in assets:review:write. They need not block the first MCP approve/reject slice. If moderation MCP parity is included later, use a separately delegated administrative scope and the same super-admin backend checks. Give limited reviewers an escalation action without revealing hold reasons or permitting blob deletion. Asset suppression does not itself restore the previous image; correction/reversion remains an explicit checked operation.

Delivery and verification​

Move usable MCP review before raising bulk intake capacity. First ship shared authority/detail/preview/decision and own-withdrawal with browser parity; then add rebase and selected-item review alongside batch staging. Larger limits should wait until the review loop can actually process and resume a collection.

Required proof is a synthetic submission from account A reviewed by separately authorized account B in both transports. Verify queue pagination, actual candidate image rendering, current-image comparison, approval publication, rejection visibility, interrupted-call replay, two simultaneous reviewers, submitter withdrawal during review, same user across different MCP clients, grant/assignment/token revocation, changed target/placement, expiry, byte deletion, hidden profiles, claimed-profile ownership, source restriction and selected rows changed after viewing. Confirm browser and MCP return the same authority and decision result. New UI needs screenshots and visual review. No production proposals should be used as test approvals.

Owner decisions​

  1. Bounded self-publication is approved policy. Initial publisher recipients, qualification details and implementation contracts remain to settle. Existing same-user approval remains forbidden until an explicit trusted-publication path is implemented; no current queue is authorized for publication by this planning decision.
  2. Confirm read-only and decision delegation as separate scopes, and initial independent reviewers. Recommend explicit assigned batches for limited reviewers, retaining current owner/super-admin paths.
  3. Confirm actual MCP clients used for review, so native image rendering can be demonstrated before claiming a complete workflow. A text-only client can inspect metadata but cannot provide visual-review confidence.
  4. Decide whether suppression needs MCP parity in the first release. Recommend keeping it separate from ordinary review scope; legal holds and cleanup can remain operator features initially.

These are implementation and policy proposals, not locked decisions.