feat(campaign): govern attachment reuse

This commit is contained in:
2026-08-20 05:20:07 +02:00
parent f4fe534ee1
commit 112ef9dc31
16 changed files with 705 additions and 2 deletions
@@ -594,6 +594,7 @@ def set_version_review_state(
"inspection_complete": payload.inspection_complete,
"reviewed_message_count": len(payload.reviewed_message_keys),
"issue_decision_count": len(payload.issue_decisions),
"issue_decisions": _review_decision_audit_evidence(version),
},
commit=True,
)
@@ -879,6 +880,9 @@ def build_version(
"residual_file_disposition": _residual_file_audit_evidence(
result.get("residual_file_disposition")
),
"attachment_reuse": _attachment_reuse_audit_evidence(
result.get("attachment_reuse")
),
},
commit=True,
)
@@ -925,3 +929,54 @@ def _residual_file_audit_evidence(value: object) -> dict[str, object]:
"residual_file_count",
)
}
def _attachment_reuse_audit_evidence(value: object) -> dict[str, object]:
if not isinstance(value, dict):
return {}
policy = value.get("policy")
return {
"contract_version": value.get("contract_version"),
"policy": dict(policy) if isinstance(policy, dict) else {},
"duplicate_file_count": value.get("duplicate_file_count"),
"allowed_file_count": value.get("allowed_file_count"),
"violation_file_count": value.get("violation_file_count"),
"affected_message_count": value.get("affected_message_count"),
}
def _review_decision_audit_evidence(
version: CampaignVersion,
) -> dict[str, object]:
editor_state = version.editor_state if isinstance(version.editor_state, dict) else {}
review_state = editor_state.get("review_send")
if not isinstance(review_state, dict):
return {}
raw_decisions = review_state.get("issue_decisions")
decisions = [item for item in raw_decisions or [] if isinstance(item, dict)]
evidence = [
{
"decision": item.get("decision"),
"issue_codes": sorted(
str(code) for code in item.get("issue_codes") or [] if code
),
"issue_fingerprint": item.get("issue_fingerprint"),
"message_sha256": item.get("message_sha256"),
"reason_recorded": bool(str(item.get("reason") or "").strip()),
}
for item in decisions
]
return {
"count": len(evidence),
"with_reason_count": sum(
1 for item in evidence if item["reason_recorded"]
),
"issue_codes": sorted(
{
code
for item in evidence
for code in item["issue_codes"]
}
),
"evidence_sha256": _canonical_sha256(evidence),
}