Skip to content

feat(approvals): public-preview + auth-gated approve, attachments in response, feedback→re-render loop #2104

Description

@federicodeponte

Problem

The public approval share link (/s/<token>, capability URL) currently gates BOTH reviewing and approving behind a no-login token, and the reviewer "response" field is text-only. Three gaps:

  1. Approve = publish, with no auth. Clicking approve publishes the item to the owner's connected YouTube/LinkedIn (Buffer). A leaked/forwarded/logged link can publish AS the owner. (Reviewing is read-only and fine no-login; the mutation is the risk.)
  2. No file/image upload in the response. Reviewers can only leave text. They can't attach an image/screenshot to say e.g. "show THIS image at the 'lonely' beat" — so feedback that references assets is impossible on-page.
  3. Feedback doesn't close the loop. Reject-with-notes is stored but (outside the SF prototype) doesn't drive a re-render. Builds on feat(approvals): turn reject-with-notes into durable worker rules (feedback → worker brain) #2079/feat: persist rejection feedback as worker rules #2080 (feedback → durable worker rules).

Proposed: approval UX = public preview, authenticated rich-feedback approve

  1. Auth-gate the mutation. Keep read-only review on the no-login token (treat as short-lived bearer secret). Require an authenticated workspace user (with publish permission) for APPROVE and for any publish-triggering decision. Token stays as "review context"; the decision endpoint checks auth. (codex security review concurred: separate read vs approve scope; short expiry; bind approval to item+workspace+action+version; audit log IP/user/action; revoke-on-publish.)
  2. Attachments in the response. The approve/reject response accepts text notes AND image/file uploads. Reuse the existing _store_uploaded_blob + /approvals/{approval_id}/uploads machinery; persist attachments on the approval/decision so the worker can consume them.
  3. Feedback → re-render loop. Reject-with-notes (text + attachments) flows into the worker's durable rules / a re-render, so the fixed version returns as a new approval. Generalizes the SF-vlog prototype (sf_feedback_loop) into the platform, on top of feat: persist rejection feedback as worker rules #2080.

Relevant code

  • engine/apps/api/routers/approvals.py — public decision endpoints (/approvals/public-batch/{token}/items/{id}/decision, /approvals/public/{id}/approve|reject), uploads (/approvals/{id}/uploads, /approvals/public/{id}/uploads), _store_uploaded_blob, emit_approval_decided, the share-link scope helpers.
  • Share-link scope: services/share_links.py + the ShareLinkRepository (read vs approve token scopes).
  • Worker rules (feedback consumption): the feat: persist rejection feedback as worker rules #2080 worker_rules path.

Acceptance criteria

  • No-login token can READ a pending approval/batch but CANNOT approve/publish; approve requires an authenticated, publish-permitted user.
  • Approve/reject response accepts text + ≥1 image/file attachment; attachments persist on the approval and are retrievable by the worker.
  • Reject-with-notes (incl. attachments) is available to the worker for re-render / rule persistence.
  • Token hardening: read vs approve scope, short expiry, audit log of decisions (who/when/IP/action), revoke-on-publish.
  • HITL preserved; no auto-publish. Tests cover: no-login approve is rejected (401/403), authed approve works, attachment upload + retrieval, audit entry written.

Companion

Engine changes here; the cloud dashboard public approval page (workeros-cloud) needs the matching UI ("Sign in to approve", attachment picker). File a linked cloud issue/PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions