From f21abd6299431967b5eda14794790482afc65d13 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Thu, 5 Mar 2026 02:21:21 +0000 Subject: [PATCH] Add minimal sensitive-info hygiene plan and projection cause sample diagnostics --- artifacts/plans/14-sensitive-info-hygiene.md | 9 +++++++++ core/events/projection.py | 11 ++++++++--- core/tests/test_event_projection_shadow.py | 6 ++++++ core/views/system.py | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 artifacts/plans/14-sensitive-info-hygiene.md diff --git a/artifacts/plans/14-sensitive-info-hygiene.md b/artifacts/plans/14-sensitive-info-hygiene.md new file mode 100644 index 0000000..8aa1ec2 --- /dev/null +++ b/artifacts/plans/14-sensitive-info-hygiene.md @@ -0,0 +1,9 @@ +# 14) Sensitive Information Hygiene + +## Goal +Detect and remove sensitive data exposure from code, config, logs, and payload surfaces. + +## Minimal Plan +1. Add a repeatable scan for sensitive patterns across repo and runtime-generated artifacts. +2. Expunge discovered sensitive values and replace with safe placeholders or references. +3. Add guardrails to prevent reintroduction and document the remediation workflow. diff --git a/core/events/projection.py b/core/events/projection.py index 159b931..9ae951b 100644 --- a/core/events/projection.py +++ b/core/events/projection.py @@ -179,15 +179,19 @@ def shadow_compare_session(session: ChatSession, detail_limit: int = 50) -> dict "ambiguous_reaction_target": 0, "payload_normalization_gap": 0, } + cause_samples = {key: [] for key in cause_counts.keys()} + cause_sample_limit = min(5, max(0, int(detail_limit))) def _record_detail(message_id: str, issue: str, cause: str, extra: dict | None = None): if cause in cause_counts: cause_counts[cause] += 1 + row = {"message_id": message_id, "issue": issue, "cause": cause} + if extra: + row.update(dict(extra)) if len(details) < max(0, int(detail_limit)): - row = {"message_id": message_id, "issue": issue, "cause": cause} - if extra: - row.update(dict(extra)) details.append(row) + if cause in cause_samples and len(cause_samples[cause]) < cause_sample_limit: + cause_samples[cause].append(row) for message_id, db_row in db_by_id.items(): projected = projected_by_id.get(message_id) @@ -289,5 +293,6 @@ def shadow_compare_session(session: ChatSession, detail_limit: int = 50) -> dict "mismatch_total": mismatch_total, "counters": counters, "cause_counts": cause_counts, + "cause_samples": cause_samples, "details": details, } diff --git a/core/tests/test_event_projection_shadow.py b/core/tests/test_event_projection_shadow.py index 3f3b874..785e412 100644 --- a/core/tests/test_event_projection_shadow.py +++ b/core/tests/test_event_projection_shadow.py @@ -96,6 +96,12 @@ class EventProjectionShadowTests(TestCase): ) compared = shadow_compare_session(self.session, detail_limit=10) self.assertGreater(compared["counters"]["missing_in_projection"], 0) + self.assertIn("cause_samples", compared) + self.assertIn("missing_event_write", compared["cause_samples"]) + self.assertGreaterEqual( + len(compared["cause_samples"]["missing_event_write"]), + 1, + ) def test_management_command_emits_summary(self): out = StringIO() diff --git a/core/views/system.py b/core/views/system.py index 5c023f5..f8918c4 100644 --- a/core/views/system.py +++ b/core/views/system.py @@ -257,5 +257,6 @@ class EventProjectionShadowAPI(SuperUserRequiredMixin, View): "ok": True, "result": compared, "cause_summary": dict(compared.get("cause_counts") or {}), + "cause_samples": dict(compared.get("cause_samples") or {}), } )