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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions