Skip to content

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

Closed

Description

@objectstack-fleet

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/main 72518d20f)

  • 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-email email-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)

Dedupe

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.

Activity

  1. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 4feccc2685 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-email EmailService 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:

    Read on origin/main 4feccc2685 at 2026-10-21T08:53Z: IEmailService declares no deliverability member, and ISmsService declares isConfigured(): boolean (sms-service.ts:118).

  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Paused — claim released (handover)

    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.
  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    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: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.

  4. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out — landed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:specenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions