screenInbound runs the agent's content scan (when inbound_scan='on'), combines it with the ingestion-gate decision into one applied action, and decides whether the message is HELD (review/block) or delivered (flag/allow). - review → held as pending_review (awaiting a human / TTL), delivery suppress
(ctx context.Context, agent *identity.AgentIdentity, messageID, senderEmail string, body []byte, auth *emailauth.Result, gate inboundpolicy.Decision)
| 46 | // - flag → delivered + annotated (the gate's email.flagged path is unchanged). |
| 47 | // - allow → delivered normally. |
| 48 | func (s *Server) screenInbound(ctx context.Context, agent *identity.AgentIdentity, messageID, senderEmail string, body []byte, auth *emailauth.Result, gate inboundpolicy.Decision) inboundScreenResult { |
| 49 | var res inboundScreenResult |
| 50 | |
| 51 | // Gate action: a flagged sender escalates to the agent's inbound_policy_action |
| 52 | // (default 'flag' → no behavior change; operators opt into review/block). |
| 53 | gateAction := piguard.ActionAllow |
| 54 | if gate.Flagged { |
| 55 | gateAction = piguard.Action(agent.InboundPolicyAction) |
| 56 | res.Events = append(res.Events, identity.ProtectionEvent{ |
| 57 | ID: identity.DeterministicProtectionEventID(messageID, identity.ScreeningSourceGate, identity.ReviewReasonSenderGate, ""), |
| 58 | MessageID: messageID, |
| 59 | AgentID: agent.ID, |
| 60 | Direction: "inbound", |
| 61 | Source: identity.ScreeningSourceGate, |
| 62 | Reason: identity.ReviewReasonSenderGate, |
| 63 | Action: agent.InboundPolicyAction, |
| 64 | SubjectAddr: senderEmail, |
| 65 | }) |
| 66 | } |
| 67 | |
| 68 | // Scan action. |
| 69 | scanAction := piguard.ActionAllow |
| 70 | var scanScore *float64 |
| 71 | if agent.InboundScan == identity.ScanOn { |
| 72 | segs, sig, _ := piguard.Extract(body, 0) |
| 73 | agg := s.screen.Evaluate(ctx, piguard.Request{ |
| 74 | Direction: piguard.DirectionInput, |
| 75 | Segments: segs, |
| 76 | Signals: sig, |
| 77 | Sender: senderEmail, |
| 78 | Auth: auth, |
| 79 | SizeBytes: len(body), |
| 80 | }) |
| 81 | act := agg.Action(agent.InboundScanReviewThreshold, agent.InboundScanBlockThreshold) |
| 82 | // Record only violations (action ≠ allow). A below-threshold score is allowed |
| 83 | // and delivered silently; flag (from the force-floor, e.g. Unicode tags) is |
| 84 | // recorded + delivered; review/block are held. |
| 85 | if act != piguard.ActionAllow { |
| 86 | scanAction = act |
| 87 | score := agg.Score |
| 88 | scanScore = &score |
| 89 | res.Detected = true |
| 90 | res.Score = score |
| 91 | res.Reason = scanReason(agg) |
| 92 | for _, c := range agg.Categories { |
| 93 | res.Categories = append(res.Categories, c.Name) |
| 94 | } |
| 95 | catsJSON, _ := json.Marshal(agg.Categories) |
| 96 | res.Events = append(res.Events, identity.ProtectionEvent{ |
| 97 | ID: identity.DeterministicProtectionEventID(messageID, identity.ScreeningSourceScan, identity.ReviewReasonInboundScan, "heuristics"), |
| 98 | MessageID: messageID, |
| 99 | AgentID: agent.ID, |
| 100 | Direction: "inbound", |
| 101 | Source: identity.ScreeningSourceScan, |
| 102 | Reason: identity.ReviewReasonInboundScan, |
| 103 | Action: string(act), |
| 104 | Detector: "heuristics", |
| 105 | Score: &score, |