Repository navigation
fix(service-messaging): the email channel answers unavailable while only the log transport is live - #30547
Conversation
…transport only logs (red) Claude-Session: https://claude.ai/code/session_01Utp6CaGmRyZ3XL5MuUgEZg Co-authored-by: Claude <noreply@anthropic.com>
…nly the log transport is live The channel read a registered email service as available, so on a deployment with no mail settings every e-mail notification dispatched to success while plugin-email's LogTransport sent nothing. It now asks the service's own isConfigured() on every call: false answers transport_not_configured at fan-out and refuses send() with the same reason. A service without the optional member keeps today's behaviour. Claude-Session: https://claude.ai/code/session_01Utp6CaGmRyZ3XL5MuUgEZg Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a7471f4ef8528572be19b1a35e55b66a10b05caa && git checkout a7471f4ef8528572be19b1a35e55b66a10b05caa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c045c341329ddcd83abe4adf12428e100b7a700a fbfbaf47ae889c4a9ed2e1365a8e7d4298b1c581 && git checkout -B drift-repro c045c341329ddcd83abe4adf12428e100b7a700a && git merge --no-ff fbfbaf47ae889c4a9ed2e1365a8e7d4298b1c581
node scripts/docs-audit/affected-docs.mjs --json c045c341329ddcd83abe4adf12428e100b7a700a
|
|
domain:server#6 · os-bill · session_01Utp6CXCS98uhsFQYk64epE 席位记录 · 2026-11-05T15:28Z · Head-sha
|
Requested by Bill · project thread
domain:server#6 · os-bill · session_01Utp6CXCS98uhsFQYk64epE
维护者速读
packages/services/service-messaging/src/email-channel.ts的emailTransportRefusal与isAvailable/send两处调用;其次是测试文件末尾新增的 describe。pnpm --filter @objectstack/service-messaging exec vitest run --maxWorkers=2 src/email-channel.test.ts,预期 47 个全绿,其中a LogTransport is no transport (#30510)下 5 个。Fixes #30510
Clause-②: no
Metadata-protocol: none
Skills-docs: none —
git grep -n -i -c "logtransport|log transport|transport_not_configured|email channel|e-mail channel" -- 'skills/**'has no hit (exit 1)What changes
plugin-email registers the
emailservice on every boot. With nomailsettings saved, its live transport is theLogTransport: a send succeeds, writessys_email, and no mail leaves the process. Theemailchannel answeredisAvailable()from "an email service is registered", so fan-out wrote a delivery row and the dispatcher marked itsuccesswhile nobody was mailed. Every hosted tenant is in this state until its admin configures mail (objectstack-ai/cloud#2549, ruling B, item 3).email-channel.ts: newemailTransportRefusal(email)asks the service's own deliverability answer,IEmailService.isConfigured()(already on the spec contract, already implemented by plugin-email from the live transport and already following themailsettings hot swap). An explicitfalsemakesisAvailable()answer{ available: false, reason: 'transport_not_configured' }and makessend()refuse with that same reason, graded permanent, without calling the service. It is read on every call, never cached, so a transport the settings swap in after boot flips the channel back with no restart.EmailSenderSurface(the channel's structural view of the service) gains the optionalisConfigured?(): boolean, mirroring the contract member.Tests
New top-level describe
email channel — a LogTransport is no transport (#30510)inemail-channel.test.ts(the package's shared harness for this channel). Its double mirrors plugin-email'sEmailServiceon the one axis that matters:isConfigured()answers from a swappable live transport.transport_not_configuredwhile the transport only logs;send()refuses with the same reason, graded permanent, and the service'ssendis never called (no fakesys_emailrow);isConfiguredstays available and still delivers;success.The producer half is already pinned in plugin-email (
email-plugin.mail-settings.test.ts, "registered email service — isConfigured follows the mail settings":falseon the out-of-the-box boot,trueafter a saved SMTP config, no restart).Acceptance notes
isConfiguredis read as unknown and keeps today's behaviour (available). The contract makes the member optional and says a consumer must not read absence astrue; this channel keeps sending through such a service, as it did before, because refusing would mute every host-supplied email service that predates the member. This follows the same choice the fan-out ruling made for a channel with noisAvailable. Only an explicitfalserefuses. If the seat reads the contract the other way, flipping it is a one-line change inemailTransportRefusal.EmailServiceor theLogTransport, so it does not overlap seat 5's Setup small fixes: impersonation landing, account email lifetime, sender name, agent sets in pickers #30212 row 3.Verification
All on head
fbfbaf47(origin/mainfa9c0aa2merged; no new commits on main since the branch point).Red on main, green on the fix, ablation
fa9c0aa2):Tests 4 failed | 43 passed (47); the four new pins red, the control green.Test Files 1 passed (1)·Tests 47 passed (47).scripts/ablation-replace.mjs(anchorif (typeof email.isConfigured !== 'function') return undefined;replaced so the helper always returnsundefined, the pre-fix behaviour): anchor 1 to 0, blobdeebc1c7be23toa81f47328d2c;Tests 4 failed | 43 passed (47); restored, blob equals HEAD,git diff HEADempty. Tests import the channel fromsrc/by relative path, so no build sits between the mutation and the run.Package suite, typecheck, lint, gates
pnpm --filter @objectstack/service-messaging testunder the verify lock:Test Files 66 passed (66)·Tests 817 passed (817)·VERDICT command-exit 0.pnpm --filter @objectstack/service-messaging typecheck: exit 0 (tsc --listFilesincludesemail-channel.test.ts).eslint --no-inline-configon the two changed source files: exit 0. The repo-widepnpm lintis left to CI's Lint job.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 commands; each run with its exit code captured before any pipe.--ranreconciliation:62 derived, 59 run, 3 NOT-MEASURED, 0 UNRUN. The three exit-3 prerequisite refusals:pnpm check:dual-build-cjs-loads(needs the whole workspace built),pnpm check:i18n(needs its build closure; this diff touches no translation bundle),node scripts/check-plugin-teardown-shape.mjs --self-test(needs an unshallow clone; checker health only). CI runs all three.Generated by Claude Code