Repository navigation
feat(spec, plugin-email): IEmailService.isConfigured() answers whether mail is delivered - #25125
Conversation
…r mail is delivered Adds an optional isConfigured(): boolean member to IEmailService, shaped like ISmsService.isConfigured(). plugin-email's EmailService answers false while its transport is the LogTransport and true otherwise, read from the live transport so it follows runtime swaps from the mail settings. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
…ail-transport-live
…ail-transport-live
…-protocol none IEmailService is a service contract, not authorable metadata, so under the level rule now on main the entry declares Metadata-protocol: none and ships patch for both packages; the Clause-2 widening line stays as claimed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
📓 Docs Drift CheckThis PR changes 2 package(s): 4 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 — 145 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 dd6f57d16e05308826b417b96148676e5587d956 && git checkout dd6f57d16e05308826b417b96148676e5587d956
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 03966ceae096f75c73c8fada48c828b86f86e02a 1fe7845d183a573acf5b188889f3f3c2791baf05 && git checkout -B drift-repro 03966ceae096f75c73c8fada48c828b86f86e02a && git merge --no-ff 1fe7845d183a573acf5b188889f3f3c2791baf05
node scripts/docs-audit/affected-docs.mjs --json 03966ceae096f75c73c8fada48c828b86f86e02a
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Seat re-read of the check-runs at landing: all 42 runs on |
Closes #25065
Clause-②: yes (widening: an optional member on
IEmailService)Metadata-protocol: none
What
packages/spec/src/contracts/email-service.ts):IEmailServicegains one OPTIONAL member,isConfigured?(): boolean, with the same name and shape asISmsService.isConfigured(). Its TSDoc says:truemeans the active transport hands mail to a real delivery provider;falsemeans no mail leaves the process (sends still resolve and audit); alogtransport is never delivering, in any environment; the answer is read at call time and follows runtime transport swaps; consumers treat an ABSENT member as unknown and must never read it as delivering.packages/plugins/plugin-email/src/email-service.ts):EmailService.isConfigured()returnsfalsewhile the active transport is theLogTransportandtruefor any other transport. It reads the live transport on every call, so it followssetTransportswaps, including the ones the plugin makes from themailsettings namespace.@objectstack/spec+@objectstack/plugin-emailatpatch. Under ruling A (changeset levels: minor only when the metadata protocol changes (added / narrowed / semantic), patch otherwise; release notes grouped by what the reader must do (maintainer ruling A, 2026-10-21) #25105, landed in feat(gates): the changeset level follows the metadata protocol —Metadata-protocol:line, level axis, console refresh at patch, release notes by reader action (ruling A) #25114) the level follows the metadata protocol only.IEmailServiceis a service contract, not an authorable key, value or metadata type, so the kind isnoneand the level ispatch. TheClause-②widening line stays as the claim states it.openandemail_domainpostures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060 and theplugin-authreaders (hasEmailTransport()/isEmailServiceAvailable()) are out of this claim. plugin-auth: under theopenandemail_domainpostures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060 remains open.Tests (final head
1fe7845d18)pnpm --filter @objectstack/plugin-email test: 36 files / 649 tests passed.typecheck(tsc + test typecheck) exit 0.src/contracts/email-service.test.tspasses 6/6. The type test pins that the member is optional (an implementation without it stays assignable), and uses@ts-expect-erroron a non-boolean answer.typecheck, includingcheck:test-typecheck, exits 0, so that directive is compiled.test:repo: 72 files / 1237 tests passed.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date against a freshly built dist. No baseline moved, so the api-surface snapshot does not record interface members.scripts/ablation-replace.mjson theisConfiguredbody. The subject is imported fromsrcthrough relative paths, so there is no dist leg. Leg 1 (return true) and leg 2 (return false) each ran with the marker on disk (1 hit, anchor 0 hits). Each turned 4 of the new tests red (2 files failed, 4 failed / 50 passed). Both restores were verified: blob equals the HEAD blob279f67336b27andgit diff HEADis empty.node scripts/pm/dispatch-gates.mjs --commands(90 commands, plus the 5 roster families under touched directories) all exited 0. The changeset gates were re-run after the regrade commit.Acceptance notes
dispatch-gatesnames (shard attestations, type-check workspace lanes, Build Core) were not measured locally. CI runs them.Generated by Claude Code