Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(kora): KR-EMAIL-PANEL-FLIP — endpoint reads from JSONLs#144

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-EMAIL-PANEL-FLIP
May 23, 2026
Merged

feat(kora): KR-EMAIL-PANEL-FLIP — endpoint reads from JSONLs#144
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-EMAIL-PANEL-FLIP

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Flips `GET /api/email/recent` from the v1 stub to a live read of both email JSONLs. Symmetric with CC#2's KR-SLACK-DM-PANEL-FLIP (#137). Backend-only — EmailMessage TS interface already matches the projection shape per PR #121.

Bucket spec: `17_cc_bucket_prompts/KR-EMAIL-PANEL-FLIP_stub_to_real.md`

Source PRs cited:

Surface

Layer Net
`kora_cli/web_server.py` -106 stub + +330 projection helpers + live endpoint body
`tests/kora_cli/test_web_server_email.py` -119 stub-shape tests; updated empty-JSONL path + fixed fixture-isolation discipline; preserved all 4-layer security walk-payload guards + FE source pins from PR #121
`tests/kora_cli/test_web_server_email_panel_flip.py` (NEW) 596 LOC, 32 tests covering JSONL-driven projection + merge/sort + limit + tolerance + security

Projection (per spec §2)

Inbound (`email_inbound_log.jsonl` → EmailMessage):

  • `id`: `f"inbound-{lineno}"`
  • `direction`: `"inbound"`
  • `from_label`: `"joshua"` if `entry.from` matches `KORA_EMAIL_JOSHUA_ADDRESS` (case-insensitive); else `"unknown_sender"`
  • `to_label`: `"kora"` if any address in `entry.to` matches `KORA_EMAIL_KORA_ADDRESS`; else `"other"`
  • `body_text_truncated_400`: `entry.body_text_truncated_2k[:400]`
  • `spoofing_warning`: `NOT entry.spoofing_check_skipped` (semantic flip — handler currently always sets skipped=True, so warning=False; future real-detection bucket flips this)
  • `handled_status` mapping (handler taxonomy → FE union):
    • `received` → `received`
    • `filtered_paused` / `filtered_stopped` → `dropped_paused`
    • `filtered_non_allowlist` → `filtered_non_allowlist`
    • `filtered_wrong_recipient` → `filtered_wrong_recipient`
    • `filtered_non_joshua` → `filtered_non_allowlist` (collapse — operator view doesn't need finer grain)
    • `handler_error` → `handler_error`

Outbound (`email_outbound_log.jsonl` → EmailMessage):

  • `id`: `f"outbound-{lineno}"`
  • `from_label`: always `"kora"`
  • `to_label`: `"joshua"` if first recipient matches `KORA_EMAIL_JOSHUA_ADDRESS`; else `"other"`
  • `body_text_truncated_400`: placeholder string (PR feat(kora): KR-FEAT-EMAIL ST1 — Purelymail outbound via SMTP #124 contract: outbound body NEVER logged)
  • `handled_status`: `f"sent_{send_status}"` (`sent_ok` / `sent_failed`)
  • `has_html`: `False`, `attachments_count`: `0` (out of scope for this flip — outbound MIME extraction is a future bucket)

SECURITY — 4-layer + message_id carve-out

  1. from_label / to_label are LABELS — never raw email addresses. Walk-payload regex sweep verifies (with carve-out below).
  2. message_id carve-out: RFC 5322 message-ids legitimately contain the operator domain (`id@stormhavenenterprises.com`). The walk-payload sweep EXCLUDES `message_id` + `in_reply_to` fields from the email-regex check (documented carve-out in module docstring + test_no_email_addresses_outside_message_id_carve_out). Everywhere else still flagged.
  3. body_text_truncated_400 is plain text — FE renders as JSX child (PR feat(kora): KR-EMAIL-PANEL — email inbox/outbox view stub #121 source-pin preserved); dangerouslySetInnerHTML banned in EmailPanel.tsx.
  4. Walk-payload guards — Purelymail token env-var-name hints + 32+ hex HMAC shapes + Bearer/Authorization headers all swept.

CC#2 #137 fixture-isolation lesson applied

The endpoint resolves `get_kora_home` from its OWN module namespace (`kora_cli.web_server`), not via a fresh import. Tests must monkeypatch all 3:

  • `kora_constants.get_kora_home` (the underlying definition)
  • `kora_cli.config.get_kora_home` (one level up)
  • `kora_cli.web_server.get_kora_home` (the live call site)

Caught a real bleed: the original PR #121 stub-test fixture only patched the upstream namespace; one test in the post-flip world was reading JSONLs from another parallel pytest-xdist worker's KORA_HOME because the web_server namespace wasn't patched. Fixed in this PR.

Test plan

  • 47 new tests pass (32 in panel-flip file + 15 preserved security/shape pins in original file)
  • 185 cross-bucket regression (web_server email + slack-dm + email handler + Purelymail SMTP/IMAP clients)
  • Ruff clean
  • Stub badge auto-disappears (`stub: false` always; FE consumes the same EmailMessage shape per PR feat(kora): KR-EMAIL-PANEL — email inbox/outbox view stub #121)

Cascade

Standalone PR. Ship checklist §4 satisfied.

🤖 Generated with Claude Code

Flips ``GET /api/email/recent`` from the v1 stub (PR #121) to a
live read of both email JSONLs:

  * ``${KORA_HOME}/email_inbound_log.jsonl`` (PR #138 writer)
  * ``${KORA_HOME}/email_outbound_log.jsonl`` (PR #124 writer)

Symmetric with CC#2's KR-SLACK-DM-PANEL-FLIP (#137). Backend
endpoint swap only — no FE changes (EmailMessage TS interface
in web/src/lib/api.ts already matches the projection per PR #121).

Per-direction projection helpers:

  * ``_project_email_inbound`` — derives from_label
    (joshua / unknown_sender) by comparing ``entry.from`` against
    ``KORA_EMAIL_JOSHUA_ADDRESS``; derives to_label (kora / other)
    by checking ``KORA_EMAIL_KORA_ADDRESS`` against ``entry.to``;
    truncates body_text_truncated_2k → 400 chars; flips
    spoofing_check_skipped → spoofing_warning semantically; maps
    handler's HANDLED_* taxonomy to FE's EmailHandledStatus union
    (filtered_paused/stopped → dropped_paused;
    filtered_non_joshua → filtered_non_allowlist)
  * ``_project_email_outbound`` — from_label always "kora"; to_label
    derived from first recipient; body is placeholder (outbound
    JSONL never contains body per PR #124 security contract);
    derives sent_ok / sent_failed from send_status

4-layer SECURITY contract preserved + extended:

  1. from_label / to_label are LABELS — never raw email addresses
     (walk-payload regex sweep verifies)
  2. message_id passes through as RFC 5322 ``<id@<operator-domain>>``
     — operator domain is NOT personal identification + FE needs
     it for threading. Walk-payload sweep EXCLUDES message_id +
     in_reply_to fields from the email-regex check (documented
     carve-out in module docstring)
  3. body_text_truncated_400 is plain text (truncation at API edge;
     FE renders as JSX child per the existing PR #121 source-pin)
  4. Walk-payload guards for Purelymail-token env-var-name hints +
     32+-hex HMAC shapes + Bearer/Authorization headers catch future
     log-entry drift

CC#2 #137 fixture-isolation lesson applied: tests monkeypatch
``get_kora_home`` in all 3 module namespaces
(kora_constants + kora_cli.config + kora_cli.web_server) because
the endpoint resolves it from its own module namespace via a
``from kora_cli.config import get_kora_home`` re-import. The
existing PR #121 stub-test fixture only patched the upstream
namespace; updated to apply the discipline (caught a real bleed
where pytest-xdist parallel tests saw stale state from each
other's KORA_HOME without the 3-namespace patch).

47 new tests pass (32 panel-flip + 15 preserved security/shape
pins) + 185 cross-bucket regression
(panel + slack-dm + email handler + Purelymail SMTP/IMAP clients).
Ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 054f408 into feature/phase2-upgrades May 23, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-EMAIL-PANEL-FLIP branch May 23, 2026 20:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant