feat(monedula): email approval loop + entity-ID matching + parquet-free recipients#174
feat(monedula): email approval loop + entity-ID matching + parquet-free recipients#174markmhendrickson wants to merge 1 commit into
Conversation
…ee recipients
Rebuild the recurring-payment daemon around email approval and stable-id
matching, replacing the Telegram approve loop.
Channel + cadence:
- Replace Telegram preview/approve/confirm with email (gws gmail). One
approval email per newly-ended session; operator replies YES/NO.
- 15-min poll (StartInterval=900) replaces the 08:00 daily run. Each tick
notifies newly-ended sessions and sweeps replies. Lookback default 168h
(MONEDULA_LOOKBACK_HOURS) with a MAX_NOTIFY_PER_RUN backstop.
- Restart-surviving markers (markers.py) key approval state by event+date;
reply sweep executes approved payments and rolls due_date (never marks
yoga/therapy complete).
Recipient resolution (Fix A): wise_transfer resolves IBAN/name from the
Neotoma contact first (profile.contact_id), contacts.parquet legacy-only —
removes the DATA_DIR dependency that blocked live Wise sends.
Calendar matching (Fix B): new handler_base.match_events_for_profile matches
by recurringEventId / event-id allowlist, falling back to keyword. Adds
keyword_match_mode ('substring'|'prefix') so therapy matches titles starting
with Therapy/Terapia (any casing/language) and excludes 'Walk to therapy'.
Guardrails preserved: per-payment approval only (no auto-execute), attendance
gate, idempotency/no double-pay, no memo on yoga BTC, IBAN/BTC validation.
Review fixes (pre-commit /code-review):
- OPERATOR_EMAIL env-sourced (swarm convention), not a hardcoded literal
(CLAUDE.md operator-config rule); tick fails safe if unset.
- notify requires both message id AND thread id before writing a marker, so a
thread-less send can't strand a session awaiting_approval forever.
- matcher drops empty/blank keywords so "" can't wildcard-match every event.
- test fixtures use synthetic payee PII (no real IBAN/phone in a public repo).
- gitignore the .monedula_pending.json runtime state.
Also adds a Cicada self-review-before-PR step (agent runs code-review on its
own diff before opening a PR; distinct from Vanellus's independent gate).
44 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
(Claude API error: HTTP 400 — {"type":"error","error":{"type":"invalid_request_error","message":"Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits."},"request_id":"req_011CckuJguFh3hWpPuQPKdrK"}) |
|
(Claude API error: HTTP 400 — {"type":"error","error":{"type":"invalid_request_error","message":"Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits."},"request_id":"req_011CckuJoVdbJ26aF69A8rpT"}) |
|
🤖 Lanius — Ateles swarm, PR gate inheritance BLOCKED Gate Status (Legacy Issue — Retroactively Initialized)
Pre-implementation gates are unsigned. This PR cannot merge until all pre-impl gates (pm, ux, arch) are signed off or waived. Unsigned pre-impl gates:
Resolution paths:
Parent issue (legacy): This PR predates the gate pipeline. Gates have been retroactively initialized. Parent issue entity: 📎 Neotoma: github_issue |
|
/confirm-gates-clear |
1 similar comment
|
/confirm-gates-clear |
|
🤖 Lanius — Ateles swarm, PR gate inheritance Gate Inheritance StatusNo parent issue found in PR body ( LEGACY-ISSUE PATH: Retroactively initializing gate metadata per ateles#109:
VerdictGATE_INHERITANCE: clear All pre-impl gates are inherited as clear (legacy retroactive init). PR may proceed to review. Vanellus assigned as reviewer. 📎 Neotoma: ateles/174 issue entity — retroactively initialized (pending backfill) |
Summary
Rebuilds the Monedula recurring-payment daemon (yoga BTC + therapy Wise) around email approval and stable-id calendar matching, replacing the Telegram approve loop. Surfaced and hardened by a live staged run that reproduced two real defects.
What changed
Channel + cadence
gws gmail) replaces Telegram for preview/approve/confirm. One approval email per newly-ended session; operator replies YES/NO.StartInterval=900) replaces the 08:00 daily run. Each tick notifies newly-ended sessions and sweeps replies. Lookback default 168h (MONEDULA_LOOKBACK_HOURS) with aMAX_NOTIFY_PER_RUNbackstop.markers.py) key approval state by event+date; reply sweep executes approved payments and rollsdue_date(never marks yoga/therapy complete).Fix A — parquet-free recipient resolution.
wise_transferresolves IBAN/name from the Neotoma contact first (profile.contact_id);contacts.parquetis legacy fallback only. Removes theDATA_DIRdependency that blocked live Wise sends.Fix B — entity-ID calendar matching. New
handler_base.match_events_for_profilematches byrecurringEventId/ event-id allowlist, falling back to keyword. Addskeyword_match_mode(substring|prefix) so therapy matches titles starting with Therapy/Terapia (any casing/language) and excludes incidental events like "Walk to therapy".Guardrails preserved
Per-payment approval only (no auto-execute) · attendance gate · idempotency / no double-pay · no memo on yoga BTC · IBAN/BTC validation.
Pre-merge review fixes (found by
/code-review+ hooks, before this PR)OPERATOR_EMAIL(swarm convention), tick fails safe if unset.awaiting_approvalforever → notify now requires both message id AND thread id or writes no marker.""can't match every event..monedula_pending.jsonruntime state gitignored.Tests
44 pass (
pytest execution/daemons/monedula/test_monedula.py), including regressions for each review fix.launchctl) and re-arming two currently-held payments (yoga + therapy) via fresh approval emails.btc_transfer/wise_transfer/monedula.py— worth a follow-up consolidation.🤖 Generated with Claude Code