Feedback
Bottom-up internal feedback threads that route to a direct manager and enforce manager-chain privacy — with an AI summary banner for handlers.
Feedback
Feedback is Pact's bottom-up internal comms channel. An employee opens a thread, it routes to their direct manager, and a strict privacy invariant controls exactly who can ever see it. It's the mirror image of Broadcasts, which flow top-down.
Live
Backed by api/routes/feedback.py and core.comms.feedback, with tables from
alembic migration 0073_comms_broadcasts_feedback. Every route is tenant-scoped
via get_tenant_id.
Lifecycle
- 1
Create
POST /v1/feedbackopens a thread. It routes to the sender's direct manager (resolved fromtenant_users.manager_id). - 2
Discuss
POST /v1/feedback/{id}/messagesappends a message;GET /v1/feedback/{id}returns the privacy-checked thread detail. - 3
Escalate
POST /v1/feedback/{id}/escalatere-routes the thread one step up the manager chain when the current handler can't resolve it. - 4
Resolve or share
POST /v1/feedback/{id}/resolvecloses the thread;POST /v1/feedback/{id}/shareissues a peer-share badge (handler-only).
Two inbox views back the UI: GET /v1/feedback/inbox is the handler side (the
threads routed to you, with an AI summary banner), and GET /v1/feedback/mine
lists the threads you sent.
The privacy invariant
This is the load-bearing part of the feature. Visibility is computed in exactly
one place — core.comms.feedback.visible_to_user — and the routes never
recompute it independently. The rules:
- The sender and the current handler can always see the thread.
- Every node on the manager chain between the original handler and the current handler can see it (the escalation path).
- Down-tree subordinates of a handler cannot see it — a director's report cannot read a thread the director is handling.
- Up-tree managers above the current handler cannot see it unless the thread was explicitly escalated to them.
The manager chain is walked leaf-first and defensively capped at 64 hops. Because visibility lives in a single function, escalation and peer-share can change who sees a thread without any route re-deriving the rule.
AI summary banner
The handler inbox carries an AI-generated summary via
core.comms.summarizer.refresh_handler_summary, so a manager triaging a queue
of threads gets the gist before opening each one.
Attribution
Write routes resolve the caller's user id from the bearer token
(tenant_users.api_key_hash) so every message and state change is attributed
to a real user, and role checks use get_caller_role.