Restrict comms_by_user/email_counts_by_user to matched-investor email

Both NL-query intents counted/listed a user's ENTIRE captured sent corpus
(internal, vendor, personal mail) rather than only email to a matched investor
— they were missing the `EXISTS email_investor_links` gate that recent_emails
and the Communications panel's query_email_activity use. Their own docstrings
said "investor emails", so the behavior was wrong, not just loose.

Add the matched-only gate to both, mirroring query_email_activity. The runner
test now seeds an unmatched sent email and asserts it is excluded (without the
fix comms_by_user returns 3 not 2, this_week 2 not 1) — the prior fixture
linked every email, so the leak went uncaught.

Also documents the matched-only rule in the nl-query guide, and refreshes the
AGENTS.md Current state (v93 deployed; this fix pending a v94 s9pk since the
intents run on the box, not the bot).
This commit is contained in:
Keysat
2026-06-18 20:24:52 -05:00
parent f7b03ee109
commit 2d43bad6fc
4 changed files with 38 additions and 10 deletions
+11 -4
View File
@@ -306,8 +306,11 @@ def run_investor_last_contact(conn, slots):
def run_comms_by_user(conn, slots):
"""The most recent `limit` outbound investor emails sent by a given user (matched by
username or full name). Soft-delete-correct (live sighting, is_sent)."""
"""The most recent `limit` outbound **investor** emails sent by a given user (matched by
username or full name). MATCHED-ONLY: restricted to investor-linked email (an
email_investor_links row exists), mirroring query_email_activity / recent_emails — NOT the
user's entire sent corpus (internal/vendor/personal mail is captured but never surfaced
here). Soft-delete-correct (live sighting, is_sent)."""
n, pat = slots["limit"], like_contains(slots["user"])
rows = _rows(conn.execute(
"SELECT e.subject, e.sent_at, u.full_name AS sender, "
@@ -318,6 +321,7 @@ def run_comms_by_user(conn, slots):
"AND eam.deleted_at IS NULL AND eam.is_sent = 1 "
"JOIN email_accounts ea ON ea.id = eam.account_id JOIN users u ON u.id = ea.user_id "
"WHERE (u.username LIKE ? ESCAPE '\\' OR u.full_name LIKE ? ESCAPE '\\') "
"AND EXISTS (SELECT 1 FROM email_investor_links l2 WHERE l2.email_id = e.id) "
"ORDER BY e.sent_at DESC LIMIT ?", (pat, pat, n)))
return {"columns": ["sent_at", "subject", "sender", "investor"], "rows": rows,
"truncated": False,
@@ -325,13 +329,16 @@ def run_comms_by_user(conn, slots):
def run_email_counts_by_user(conn, slots):
"""Per-user counts of outbound investor emails over this week / month / year-to-date.
"""Per-user counts of outbound **investor** emails over this week / month / year-to-date.
MATCHED-ONLY: counts only investor-linked email (an email_investor_links row exists),
mirroring query_email_activity / recent_emails — not the user's entire sent corpus.
Windows are calendar-based: week = since Monday, month = since the 1st, ytd = since Jan 1."""
today = _today()
wk = (today - timedelta(days=today.weekday())).isoformat()
mo = today.replace(day=1).isoformat()
yr = today.replace(month=1, day=1).isoformat()
where = "WHERE eam.deleted_at IS NULL AND eam.is_sent = 1"
where = ("WHERE eam.deleted_at IS NULL AND eam.is_sent = 1 "
"AND EXISTS (SELECT 1 FROM email_investor_links l WHERE l.email_id = e.id)")
params = [wk, mo, yr]
if slots.get("user"):
pat = like_contains(slots["user"])