Background-monitoring email alerts: real SMTP send + pluggable external mailer#171
Merged
Conversation
…tave guard + tests
- New libs/EventDetection/EmailTransport.m: handle class with Server/Port/User/
Password/PasswordEnv/SecurityMode/From NV-pair config
- PURE static buildMailProps(mode, port) returns containers.Map of mail.smtp.*
keys for none/starttls/ssl modes without touching prefs or JVM
- send() has Octave guard (exist('sendmail','file')==0 -> log-and-return, no error);
applies JVM props then delegates to MATLAB sendmail
- EmailTransport:invalidSecurityMode error on unknown mode
- tests/test_email_transport.m: function-based tests for all three prop maps,
invalid mode, and octave-guard no-throw
- tests/suite/TestEmailTransport.m: class-based mirror using verifyEqual/verifyError
MATLAB test execution deferred to orchestrator (no MCP access in executor).
…otificationService
- NotificationService now accepts SmtpPort(587)/SmtpUser/SmtpPassword/PasswordEnv/
SecurityMode('starttls')/CooldownMinutes(5)/Transport via constructor inputParser
- sendEmail_ delegates to Transport.send(...); lazily constructs real EmailTransport
when Transport is empty (DI seam for mock injection)
- Added per-(SensorName|ThresholdLabel) cooldown in notify(): suppresses both
real-send and dry-run within window; stamps AFTER Enabled+rule guards so
test_disabled/test_default_rule stay green; SuppressedCount incremented on suppress
- Added hidden setLastSentForTesting_ seam for deterministic expiry testing
- tests/suite/MockEmailTransport.m: new test double recording send() call args
- tests/test_notification_service.m: extended with test_transport_delegation,
test_cooldown_suppresses_within_window, test_cooldown_allows_after_expiry;
add_event_path now also adds tests/suite to path for MockEmailTransport
MATLAB test execution deferred to orchestrator (no MCP access in executor).
…ive ticks
- processMonitorTag_ gains 3rd return value sensorData (struct X/Y/thresholdValue/
thresholdDirection); well-formed empty struct on every early-return path
- sensorData built from fullX/fullY accumulated grid + first new event's
ThresholdValue/Direction (matches generateEventSnapshot contract exactly)
- runCycle accumulates allSensorData cell array in parallel with allNewEvents;
notify(ev, sd) now passes real per-event sensorData instead of struct()
- NotificationService('DryRun', true) constructor default unchanged (backward-compat)
- example_live_pipeline.m: runnable path stays dry-run; added commented real-send
config block with SmtpServer/SmtpPort/PasswordEnv/SecurityMode/CooldownMinutes
- examples/05-events/smoke_email_send.m: documented manual one-shot real-send using
FASTSENSE_SMTP_* env vars; STARTTLS:587; gracefully returns when vars unset;
Octave-safe (EmailTransport guard logs-and-skips)
MATLAB test execution deferred to orchestrator (no MCP access in executor).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or() Code Analyzer flagged the single-string literal wrapped in [...] in validateSecurityMode_'s error() call. Cosmetic only; test_invalid_mode still green. New file is now fully Code-Analyzer-clean (MISS_HIT was already clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- STATE.md: add 260529-rxf row to Quick Tasks Completed (Status: Verified) and bump Last activity. STATE.md is force-tracked under the otherwise gitignored .planning/; gsd-tools commit skipped it on a naive .gitignore read, so committed directly. - Force-add the plan doc (260529-rxf-PLAN.md) to match repo convention — prior quick tasks track both PLAN.md and SUMMARY.md; the executor's docs commit (0e88253) captured only the SUMMARY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nal mailer
FunctionTransport wraps a user-supplied function handle as a NotificationService
Transport, so an existing site/company MATLAB email function can be reused for
background-monitoring alerts with NO SMTP config (no server/port/credentials,
no Gmail App Password). Drop-in: same send(recipients,subject,body,attachments)
signature as EmailTransport (duck-typed), normalizes recipients to a flat
cellstr, defaults attachments to {}, Octave-safe (only calls user code).
Purely additive — EmailTransport/NotificationService behavior unchanged.
transport = FunctionTransport(@(to,subject,body,attachments) ...
companyMail(to,subject,body,attachments));
notif = NotificationService('DryRun',false,'Transport',transport);
Tests: tests/test_function_transport.m (5/5 — forwarding, recipients
normalization, attachments default, invalid-handle error, NotificationService
integration). test_notification_service still 10/10. MISS_HIT + Code Analyzer
clean. Example gains a commented FunctionTransport option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…tificationService new logic
Codecov flagged FunctionTransport at 0% and the NotificationService
cooldown/delegation lines as uncovered. Root cause: CI's coverage runner
(scripts/run_tests_with_coverage.m) uses TestSuite.fromFolder('tests/suite'),
so it runs ONLY class-based suites — the function-based test_*.m files
(where the new logic was tested) never execute in CI. Mirror those tests
into the class suites so the new code is actually CI-gated and covered:
- NEW tests/suite/TestFunctionTransport.m (5 tests) — forwarding, recipient
normalization, attachments default, invalid-handle error, NotificationService
integration. Lands in the E-I batch (passing).
- tests/suite/TestNotificationService.m — +3 methods (transport delegation,
cooldown-suppress, cooldown-expiry via the Hidden setLastSentForTesting_
seam) and add tests/suite to the path for MockEmailTransport. J-P batch.
Local: TestFunctionTransport 5/5, TestNotificationService 10/10. MISS_HIT clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
Coverage addressed in a44106e. Root cause: CI's coverage runner ( Fix: mirrored the new-logic tests into class suites that CI runs:
The remaining |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the background monitoring pipeline (
LiveEventPipeline) actually send per-event alert emails — the rule/template/snapshot machinery existed but was locked to dry-run and never sent. Implemented as a small, mockable transport seam (Approach B), plus an adapter to reuse an existing external mailer.Real SMTP path (quick task
260529-rxf)EmailTransport— owns SMTP mechanics: auth + STARTTLS (587, default) / SSL (465) / none, a purebuildMailPropsmapping (CI-testable without sending), env-var password support, and an Octave guard (logs-and-skips whensendmailis absent, never errors).NotificationServicenow delegates sending to an injectableTransportand adds a per-(sensor, threshold) cooldown (default 5 min, dry-run honors it too) with aSuppressedCount.LiveEventPipelineforwards real per-eventsensorData(X/Y + threshold) soIncludeSnapshotrules attach PNGs in live mode.Pluggable external mailer (fast task
260529-fnt)FunctionTransport— wraps any function handle as aTransport, so an existing site/company MATLAB mailer (e.g.companyMail(to,subject,body,attachments)) can be used for alerts with no SMTP config (no Gmail/App-Password). Normalizes recipients to a flat cellstr; Octave-safe.Backward compatible:
LiveEventPipelinestill defaults toNotificationService('DryRun', true), so existing scripts behave identically. All changes are additive.Test Plan
Verified in MATLAB R2025a (live session):
tests/test_email_transport.m— 5/5 (prop-map none/starttls/ssl, invalid-mode error, Octave-guard no-throw)tests/test_function_transport.m— 5/5 (forwarding, recipient-flattening, attachments default, invalid-handle, NotificationService integration)tests/test_notification_service.m— 10/10 (7 original + 3 new: delegation, cooldown-suppress, cooldown-expiry)tests/test_live_event_pipeline_tag.m— 3/3 (no regression from 3-outputprocessMonitorTag_)TestEmailTransport5/5,TestNotificationService7/7,TestLiveEventPipelineTag3/3mh_style+mh_lint) clean; MATLAB Code Analyzer clean on all touched libsexamples/05-events/smoke_email_send.m(or wrap your own mailer inFunctionTransport)🤖 Generated with Claude Code