Merge pull request 'feat(reports): add no-email identity summary and disclosure validator (closes #305)' (#329) from feat/issue-305-report-email-redaction into master

This commit was merged in pull request #329.
This commit is contained in:
2026-07-07 03:03:12 -05:00
2 changed files with 183 additions and 0 deletions
+81
View File
@@ -2610,3 +2610,84 @@ def build_review_mutation_proof(run_log: list[dict]) -> dict:
"missing_fields": [],
"reasons": [],
}
# ---------------------------------------------------------------------------
# Identity disclosure (#305)
# ---------------------------------------------------------------------------
EMAIL_ADDRESS_RE = re.compile(
r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
EMAIL_JUSTIFICATION_MARKERS = (
"email required",
"email is required",
"email necessary",
"necessary to disambiguate",
"disambiguate identity",
"tool requires the email",
"user explicitly asked",
)
def format_identity_summary(username, profile, role=None, remote=None):
"""Return the no-email identity line for workflow reports (#305).
Standard reports identify actors as ``<username> / <profile>`` (plus
optional role/remote). Personal email is never part of the summary; if
an email lands in the username slot, only its local part is kept.
"""
name = (username or "").strip()
if "@" in name:
name = name.split("@", 1)[0]
summary = f"{name} / {(profile or '').strip()}"
extras = [str(part).strip() for part in (role, remote)
if part and str(part).strip()]
if extras:
summary += f" ({', '.join(extras)})"
return summary
def assess_email_disclosure(
report_text,
*,
justification_markers=EMAIL_JUSTIFICATION_MARKERS,
):
"""Flag unnecessary personal-email disclosure in a report (#305).
Username/profile identity is sufficient for normal workflow reports.
An email address is tolerated only when the report itself explains why
it is necessary (tool proof, explicit request, or disambiguation).
"""
text = report_text or ""
emails = sorted(set(EMAIL_ADDRESS_RE.findall(text)))
if not emails:
return {
"proven": True,
"flagged": False,
"justified": False,
"emails": [],
"reasons": [],
}
lower = text.lower()
justified = any(marker in lower for marker in justification_markers)
if justified:
return {
"proven": True,
"flagged": False,
"justified": True,
"emails": emails,
"reasons": [
"email disclosure present but justified in the report",
],
}
return {
"proven": False,
"flagged": True,
"justified": False,
"emails": emails,
"reasons": [
f"unnecessary personal email disclosure: {email}"
for email in emails
],
}