You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
IEmailService declares no "does this transport deliver mail" fact, so a consumer cannot tell plugin-email's default log transport from a live one (blocks #25060, ruling 1B's precondition) #25065
Filing gate ③, a task the maintainer's ruling needs: ruling 1B on #25040 (6308462370) makes "the mail transport is live" a precondition of #25060. Measured by #25060's dev (6309625867, M2). Filed by domain:services seat 2 (seat post #21118, os-support-ai, session session_01UmqzD97e14iws6hmVhgZ5y). ⛔ Not a claim. ⛔ Ungraded and unrouted: triage grades it, and routes it as a Seam: card if that is its shape (a packages/spec contract member, its plugin-email implementation, and #25060 as the consumer).
Reader (who acts): triage, then the spec lane for the contract and the services lane for plugin-email.
The finding (measured on origin/main72518d20f)
The contract.IEmailService (packages/spec/src/contracts/email-service.ts) carries no member that says whether mail sent through it is delivered.
The default.EmailServicePlugin defaults to its log transport, which delivers nothing and says so at boot (plugin-emailemail-plugin.ts ~:476). resolveServedCapabilities (packages/core/src/capability-composition.ts) adds the email capability whenever auth is declared. So every served boot that declares auth has an email service wired, live or not.
The consumers' only readers.plugin-auth's hasEmailTransport() / isEmailServiceAvailable() answer "a service object is set". Only plugin-email knows that a log transport is not a delivery (its mail-test settings action, ~:747). The transport can also be swapped at runtime from the mail settings.
The sibling precedent.ISmsService declares isConfigured(): boolean (packages/spec/src/contracts/sms-service.ts:118).
Why it matters
Ruling 1B: "when the mail transport is not live, the path keeps today's explicit refusal, so the #15587 black hole (a fake success with no mail) cannot recur." Reading "a service is wired" as live reopens that black hole on exactly the default boot. A consumer-side guess from the mail configuration would be a second copy of plugin-email's knowledge.
The shape the seat recommends (for triage and the spec lane to confirm; the seat call on #25060)
One optional member on IEmailService, shaped like the SMS sibling's isConfigured().
plugin-email's EmailService implements it:false while its transport is the log transport, following runtime transport swaps.
The seat's reading of the ruling: a log transport is never live, in any environment. A unified deliverability rule for SMS and mail would be a separate question.
Searched (semantic): email service transport live deliverable log transport contract isConfigured → 0 hits; log transport mail not delivered email service wired verification → 0 hits.
Dedupe words: IEmailService deliverable · email transport live log transport · isConfigured email service contract.
Claim: PM loop round 34
Session: session_01D6F9axfoPzR7t8G1J7Ytzz
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25065-email-transport-live
Worktree: objectstack-issue-25065
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/main4feccc2685 or later; stop on a breach and explain it in the report):
packages/spec/src/contracts/email-service.ts: one OPTIONAL member on IEmailService answering "does mail sent through this service get delivered", shaped like ISmsService.isConfigured() (sms-service.ts:118);
on the cross-domain path, packages/plugins/plugin-emailEmailService implements it: false while the transport is log, following runtime transport swaps;
tests, the api-surface or generated baselines the member moves, and .changeset/25065-*.md.
Consumers (#25060 and the plugin-auth readers) are OUT of this claim.
Container & model: M, mode:subagent, model: default (no path-derived mandate)
Clause-②: yes (widening: an optional member on IEmailService)
Responsibility: n/a — a widening (standing 「甲」 rule: ships in any 18.x minor); the precondition ruling 1B on #25040 (6308462370) names for #25060
Thread-read: none
Serial constraints cleared:
No open PR touches email-service.ts or plugin-email. The dev re-checks before opening.
Read on origin/main4feccc2685 at 2026-10-21T08:53Z: IEmailService declares no deliverability member, and ISmsService declares isConfigured(): boolean (sms-service.ts:118).
The seat stopped this dispatch at the maintainer's request (wind-down). The claim 6312021173 ends here, and the card returns to pm:queue.
Work on the branch:claude/issue-25065-email-transport-live, 1 commit, pushed at a8b708d991 ("IEmailService.isConfigured() answers whether …"), clean. No PR is open.
Not done: the dev was running the spec test and typecheck verification and drafting the PR body. The next dev picks up from the branch, re-runs every check (spec and plugin-email tests, api-surface, check:generated, changeset gates, dispatch-gates) and opens the draft PR. The PR owes a contract review, because it is a Clause-②: yes widening.
The handover note above is withdrawn. The maintainer has asked the seat to carry every half-finished card through to merge, so this seat's claim on this card stands again, and pm:dispatched is restored. The seat now runs one dev at a time. This card resumes from its pushed branch head, in the seat's serial order.
{
"issue": 25065,
"status": "done",
"branch": "claude/issue-25065-email-transport-live",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25125",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz (the PM session, which subagent mode inherits)",
"premise_still_valid": true,
"summary": "Resumed from a8b708d991, which already covered the whole claim. The commit adds an optional IEmailService.isConfigured?(): boolean in packages/spec/src/contracts/email-service.ts, with TSDoc that covers true/false, a log transport that never delivers, following runtime swaps, and absent read as unknown. plugin-email EmailService.isConfigured() returns false on LogTransport and true otherwise, read live, so it follows setTransport and mail-settings swaps. The commit also has the spec type test and the plugin-email unit and settings tests. This run merged origin/main twice, the second time to pick up ruling A (#25114). Per the PM's rule-change message it then regraded the changeset: AGENTS.md item 3 and pr-automation.yml WHICH LEVEL confirm the PM's reading, because a service contract is not authorable metadata. The changeset now has Metadata-protocol: none and patch for both packages, and keeps the claim's Clause-② widening line. It then opened draft PR #25125 (Closes #25065, Clause-② and Metadata-protocol lines at the top, assigned to baozhoutao). No consumer changes; #25060 remains open.",
"tests": "Final head 1fe7845d18. pnpm --filter @objectstack/plugin-email test: 36 files / 649 passed (re-run on the final head). plugin-email typecheck exit 0. spec src/contracts/email-service.test.ts: 6/6 (re-run on the final head). spec typecheck exit 0, including check:test-typecheck, so the @ts-expect-error directive is compiled. spec test:repo: 72 files / 1237 passed (measured on 2ed04b2f3f; the second merge's only spec src change is a describe in stack-server.zod.ts). Spec dist was rebuilt after the second merge, then check:generated: all 14 artifacts up to date; no baseline moved. dispatch-gates --commands on 1fe7845d18 (same 90 as on 2ed04b2f3f) plus the 5 roster families under touched directories: 95/95 exit 0. dispatch-gates --ran: 90 derived, 90 run, 0 UNRUN; its zero is marked CLAIMED because my ran list carries no exit codes, and the exit codes are in the runner's results file. Changeset gates re-run after the regrade: check-changeset-no-major --event with the PR body prints LEVEL AXIS ✓ (clause-② yes (widening), metadata-protocol none, no minor entry, no protocol baseline moved); also --base, --self-test, adr-0087-registration, empty-changeset, changeset-fixed, changeset-gate-self-tests, objectui-changeset, pm-changeset-deadline-census, nul-bytes, closing-keyword-parity: all exit 0. Ablation with scripts/ablation-replace.mjs on the isConfigured body. The subject is imported from src through relative paths and is not in the KNOWN_UNALIASED list, so there is no dist leg. Leg return true: marker 1 hit on disk, anchor 0 hits; 4 failed / 50 passed (log-false cases). Leg return false: 4 failed / 50 passed (live-true cases). Each restore reported blob == HEAD 279f67336b27 and git diff HEAD empty, and my own hash compare printed RESTORED-MATCH-HEAD. NOT MEASURED: the CI-only families (shard attestations, Build Core, type-check workspace lanes) and the integration/dogfood jobs; reason: CI-shaped, with no local invocation.",
"mcp_calls": "0",
"api_writes": "2 relay writes as objectstack-fleet[bot]: pr_create (POST /repos/objectstack-ai/objectstack/pulls, plus POST /issues/25125/assignees) and this os-dev-report comment (POST /issues/25065/comments). Plus git push (not REST). No label writes: the dispatch named no labels, and skip-changeset does not apply because the diff publishes.",
"open_questions": [],
"out_of_scope_findings": [
"Noted, not filed: the open-PR overlap check had to use REST (gh pr list is refused because GraphQL is blocked in sessions). It read the files of all 10 open PRs, and none touches contracts/email-service or plugin-email/src/email-service.",
"Noted, not filed: pnpm --filter @objectstack/spec test:repo held the verify lock for 27m16s on the shared box, so the lock wrapper flagged possible holder-side starvation. carrier: PM (lock economics); recorded in Acceptance notes context only."
]
}
Patch-id verified: the merged commit's patch-id equals the reviewed head 1fe7845d18 (906a3d7cd2).
Contract review: PASS, record 6320508889 on the PR.
What shipped:
IEmailService.isConfigured?(): boolean, an optional member shaped like ISmsService.isConfigured().
plugin-email's EmailService answers false on the log transport and true otherwise. It reads the transport live, so it follows setTransport and mail-settings swaps.
Activity
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 34
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25065-email-transport-liveWorktree:
objectstack-issue-25065Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main4feccc2685or later; stop on a breach and explain it in the report):packages/spec/src/contracts/email-service.ts: one OPTIONAL member onIEmailServiceanswering "does mail sent through this service get delivered", shaped likeISmsService.isConfigured()(sms-service.ts:118);packages/plugins/plugin-emailEmailServiceimplements it:falsewhile the transport islog, following runtime transport swaps;.changeset/25065-*.md.Consumers (#25060 and the
plugin-authreaders) are OUT of this claim.Container & model:
M,mode:subagent,model: default (no path-derived mandate)Clause-②: yes (widening: an optional member on
IEmailService)Responsibility:
n/a — a widening (standing 「甲」 rule: ships in any 18.x minor); the precondition ruling 1B on #25040 (6308462370) names for #25060Thread-read: none
Serial constraints cleared:
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 (domain:services,pm:blocked) consumes this member after it lands, so it is not touched here.email-service.tsorplugin-email. The dev re-checks before opening.Read on
origin/main4feccc2685at 2026-10-21T08:53Z:IEmailServicedeclares no deliverability member, andISmsServicedeclaresisConfigured(): boolean(sms-service.ts:118).objectstack-fleet commentedon Oct 21, 2026
Paused — claim released (handover)
The seat stopped this dispatch at the maintainer's request (wind-down). The claim
6312021173ends here, and the card returns topm:queue.claude/issue-25065-email-transport-live, 1 commit, pushed ata8b708d991("IEmailService.isConfigured() answers whether …"), clean. No PR is open.Clause-②: yeswidening.objectstack-fleet commentedon Oct 21, 2026
Resumed — claim restored
The handover note above is withdrawn. The maintainer has asked the seat to carry every half-finished card through to merge, so this seat's claim on this card stands again, and
pm:dispatchedis restored. The seat now runs one dev at a time. This card resumes from its pushed branch head, in the seat's serial order.objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25065, "status": "done", "branch": "claude/issue-25065-email-transport-live", "pr": "https://github.com/objectstack-ai/objectstack/pull/25125", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz (the PM session, which subagent mode inherits)", "premise_still_valid": true, "summary": "Resumed from a8b708d991, which already covered the whole claim. The commit adds an optional IEmailService.isConfigured?(): boolean in packages/spec/src/contracts/email-service.ts, with TSDoc that covers true/false, a log transport that never delivers, following runtime swaps, and absent read as unknown. plugin-email EmailService.isConfigured() returns false on LogTransport and true otherwise, read live, so it follows setTransport and mail-settings swaps. The commit also has the spec type test and the plugin-email unit and settings tests. This run merged origin/main twice, the second time to pick up ruling A (#25114). Per the PM's rule-change message it then regraded the changeset: AGENTS.md item 3 and pr-automation.yml WHICH LEVEL confirm the PM's reading, because a service contract is not authorable metadata. The changeset now has Metadata-protocol: none and patch for both packages, and keeps the claim's Clause-② widening line. It then opened draft PR #25125 (Closes #25065, Clause-② and Metadata-protocol lines at the top, assigned to baozhoutao). No consumer changes; #25060 remains open.", "tests": "Final head 1fe7845d18. pnpm --filter @objectstack/plugin-email test: 36 files / 649 passed (re-run on the final head). plugin-email typecheck exit 0. spec src/contracts/email-service.test.ts: 6/6 (re-run on the final head). spec typecheck exit 0, including check:test-typecheck, so the @ts-expect-error directive is compiled. spec test:repo: 72 files / 1237 passed (measured on 2ed04b2f3f; the second merge's only spec src change is a describe in stack-server.zod.ts). Spec dist was rebuilt after the second merge, then check:generated: all 14 artifacts up to date; no baseline moved. dispatch-gates --commands on 1fe7845d18 (same 90 as on 2ed04b2f3f) plus the 5 roster families under touched directories: 95/95 exit 0. dispatch-gates --ran: 90 derived, 90 run, 0 UNRUN; its zero is marked CLAIMED because my ran list carries no exit codes, and the exit codes are in the runner's results file. Changeset gates re-run after the regrade: check-changeset-no-major --event with the PR body prints LEVEL AXIS ✓ (clause-② yes (widening), metadata-protocol none, no minor entry, no protocol baseline moved); also --base, --self-test, adr-0087-registration, empty-changeset, changeset-fixed, changeset-gate-self-tests, objectui-changeset, pm-changeset-deadline-census, nul-bytes, closing-keyword-parity: all exit 0. Ablation with scripts/ablation-replace.mjs on the isConfigured body. The subject is imported from src through relative paths and is not in the KNOWN_UNALIASED list, so there is no dist leg. Leg return true: marker 1 hit on disk, anchor 0 hits; 4 failed / 50 passed (log-false cases). Leg return false: 4 failed / 50 passed (live-true cases). Each restore reported blob == HEAD 279f67336b27 and git diff HEAD empty, and my own hash compare printed RESTORED-MATCH-HEAD. NOT MEASURED: the CI-only families (shard attestations, Build Core, type-check workspace lanes) and the integration/dogfood jobs; reason: CI-shaped, with no local invocation.", "mcp_calls": "0", "api_writes": "2 relay writes as objectstack-fleet[bot]: pr_create (POST /repos/objectstack-ai/objectstack/pulls, plus POST /issues/25125/assignees) and this os-dev-report comment (POST /issues/25065/comments). Plus git push (not REST). No label writes: the dispatch named no labels, and skip-changeset does not apply because the diff publishes.", "open_questions": [], "out_of_scope_findings": [ "Noted, not filed: the open-PR overlap check had to use REST (gh pr list is refused because GraphQL is blocked in sessions). It read the files of all 10 open PRs, and none touches contracts/email-service or plugin-email/src/email-service.", "Noted, not filed: pnpm --filter @objectstack/spec test:repo held the verify lock for 27m16s on the shared box, so the lock wrapper flagged possible holder-side starvation. carrier: PM (lock economics); recorded in Acceptance notes context only." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Close-out — landed
fccbd9d0d5(single-parent squash onmain).1fe7845d18(906a3d7cd2).6320508889on the PR.IEmailService.isConfigured?(): boolean, an optional member shaped likeISmsService.isConfigured().EmailServiceanswersfalseon thelogtransport andtrueotherwise. It reads the transport live, so it followssetTransportand mail-settings swaps.patch,Clause-②: yes (widening),Metadata-protocol: none, 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).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, the consumer this precondition serves:trueis a configuration fact, not reachability, so a send can still resolvestatus: 'failed'.hasEmailTransport()/isEmailServiceAvailable()still read "service wired", and 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 or a sibling migrates them.content/docs/kernel/runtime-services/email-service.mdxdoes not yet document the member, as the SMS page does.pm:dispatchedremoved.