Context
During PR #280 review, Copilot flagged that WithholdPolicy sets redacted_content=None to trigger ToolInvoker's fail-closed branch, but the invoker currently logs this as "no redacted content available" — which is misleading when redaction was available but was intentionally withheld by policy.
Problem
The ToolInvoker cannot distinguish between:
- No redacted content available (scanner couldn't produce a redacted version)
- Content intentionally withheld by policy (
WithholdPolicy cleared it)
Both cases result in has_sensitive_data=True + redacted_content=None, but the semantics differ.
Proposed Enhancement
Add an explicit signal to OutputScanResult so the invoker can log/report the correct reason:
Option A: Add a withheld: bool = False field to OutputScanResult — WithholdPolicy sets it to True.
Option B: Add an outcome enum field (e.g. redacted, withheld, passed_through, log_only) that policies set.
Option C: Use a sentinel string (e.g. "[WITHHELD BY POLICY]") — but this defeats fail-closed since the invoker would use the string.
Option B is likely cleanest as it scales to all policies, but requires updating all consumers of OutputScanResult.
Impact
OutputScanResult model (new field)
- All 4 policy implementations (set the new field)
ToolInvoker (use the field for logging/metrics)
- Tests across security and tools modules
Origin
Review comment from Copilot on PR #280: #280 (comment)
Context
During PR #280 review, Copilot flagged that
WithholdPolicysetsredacted_content=Noneto triggerToolInvoker's fail-closed branch, but the invoker currently logs this as "no redacted content available" — which is misleading when redaction was available but was intentionally withheld by policy.Problem
The
ToolInvokercannot distinguish between:WithholdPolicycleared it)Both cases result in
has_sensitive_data=True+redacted_content=None, but the semantics differ.Proposed Enhancement
Add an explicit signal to
OutputScanResultso the invoker can log/report the correct reason:Option A: Add a
withheld: bool = Falsefield toOutputScanResult—WithholdPolicysets it toTrue.Option B: Add an
outcomeenum field (e.g.redacted,withheld,passed_through,log_only) that policies set.Option C: Use a sentinel string (e.g.
"[WITHHELD BY POLICY]") — but this defeats fail-closed since the invoker would use the string.Option B is likely cleanest as it scales to all policies, but requires updating all consumers of
OutputScanResult.Impact
OutputScanResultmodel (new field)ToolInvoker(use the field for logging/metrics)Origin
Review comment from Copilot on PR #280: #280 (comment)