Repository navigation
Description
维护者速读
- 事由: 邮件设置页显示 SMTP、主机为空,而平台邮件实际进了开发日志、状态记为「已发送」,邀请提示「已发送」但什么都发不出去。
- 要做: 页面上说明当前生效的传输方式,例如「开发日志——邮件不会离开本机」。
- 完成标准: 管理员在邮件设置页能看出邮件会不会真的发出。
Filing gate ③ a task the maintainer set: the maintainer's ruling on objectstack-ai/hotcrm#2194, recorded there at 6354968262 (the director seat, posting from the maintainer's account): 「25251 B hotcrm B」, 2026-10-23T01:05:47Z. That is letter B for all three rounds, exactly as the seat recommended, and it adopts T2-U12. Filed by the repo:hotcrm seat 1 (os-elon-musk, session session_014xanhPEmGTMTfVH4UokwJT) under objectstack-ai/hotcrm#2194's After the ruling. ⛔ Not a claim.
Grade: triage's. The seat suggests priority:p3: an adopted suggestion with a measured cost on an existing surface, no wrong data and no contract change.
Reader (who acts): objectstack triage routes and grades it. The landing below is the seat's reading of origin/main; paths under src/ are HotCRM's (objectstack-ai/hotcrm).
Governing text
- The ruling above. Per [Decision] Which of the 33 UI and UX suggestions from the 2026-10-20 browser passes to adopt (seat: adopt 23, fold 4 into open cards, defer 6) hotcrm#2194's own governing text, this item is an adopted suggestion through filing gate ②, not a defect card, and a platform or console landing is changed there, never re-implemented in HotCRM: 「平台相关的功能,应该尽量先改平台。」 (maintainer, 2026-10-18, ledger
6245607810).
Why (measured)
- Page / persona: Setup → Email, the system administrator.
- Now: provider "SMTP" with an empty host, while platform mails are stored with status "sent" (the dev mail sink).
- Cost: "Invitation sent" toasts while nothing could be delivered.
- Evidence: objectstack-ai/hotcrm track 2 report, UI and UX suggestions, U-12;
27-config-email.png. Re-measured unchanged on@objectstack/*18.0.0 by the round 2 pass (round 2 track 2 report, UI and UX suggestions, the section's opening paragraph).
Landing
Read on objectstack origin/main 7b047b44:
packages/spec/src/contracts/email-service.ts~:346–368:IEmailService.isConfigured?()now exists, shaped likeISmsService.isConfigured()(added under 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), andplugin-emailanswersfalsefor itslogtransport.- So the fact exists; this card is its reader on the Setup → Email page (the mail settings in
plugin-email/service-settings, rendered by the console).
Done when
- Setup → Email states which transport is live; with the log transport it says nothing leaves this machine.
- The statement follows a transport swap made from the same page.
- A pin on the page's reading of
isConfigured().
Size: S.
Dedupe
Method: every open objectstack-ai/objectstack issue (108) and every issue updated since 2026-10-19 (544, any state), listed through the repo-scoped issues endpoint (paged) at 2026-10-23T01:20Z and matched case-insensitively on their titles. Words: transport, log sink, email, mail. 27 hits on the broad words; the related ones are closed: #25065 (the contract fact this card reads) and #24767 (send-test recipients). The open ones (#25293, #25190, #25189, #24465) are other email subjects. New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 23, 2026
Claim: PM loop round 1 ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · S mechanical card, in-seat subagent · 2026-10-23T12:19ZSession:
session_016g7nYP9wdNx1GDa5XDr7UyAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25335-email-transport-statementWorktree:
objectstack-issue-25335Domain:
domain:servicesSeat:
domain:services#1File surface: expected
packages/plugins/plugin-email/src/(the mail settings' served reading ofIEmailService.isConfigured()/ the live transport) and, if the Setup → Email page's settings descriptor lives there,packages/services/service-settings/src/; plus tests, bundles and a changeset. ⛔packages/specis not edited (isConfigured?()already exists). If the real producer is elsewhere, fix it there and name the landing and reason in the report and PR body.Container & model:
S 级机械卡,mode:subagent(general-purpose subagent loading.claude/agents/os-dev.mdas its role file),model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card (adopted suggestion, filing gate ③ on hotcrm#2194's ruling)
Thread-read: none
Serial constraints cleared: #25190 (plugin-email render exports) is on hold with no open PR; #25345 (in flight) touches
service-smsandruntime/src/flow-clone.tsonly.Resume-from: none
objectstack-fleet commentedon Oct 23, 2026
os-dev-report
{ "issue": 25335, "status": "done", "branch": "claude/issue-25335-email-transport-statement", "pr": "https://github.com/objectstack-ai/objectstack/pull/25448", "session": "session_016g7nYP9wdNx1GDa5XDr7Uy", "premise_still_valid": true, "summary": "Server half landed, as a draft PR marked 'Part of #25335'. service-settings gains SettingsService.registerLiveStatus(namespace, provider) and liveStatusOf(). GET /api/settings/:namespace asks the provider on every read and heads manifest.specifiers with a spec-valid info_banner (reserved id live_status); the registered manifest and in-process getNamespace() are unchanged. plugin-email registers the mail statement from EmailService.isConfigured(): false reads 'No mail leaves this machine' (warning), and true names the live transport, e.g. 'Mail is delivered through SMTP (host:port)'. The statement waits for any mail re-apply still in flight, so a read made right after a save names the new transport. The landing matches the claim. No packages/spec edit was needed, because info_banner already exists and the console already renders it. The SMS page had no live-sender reader to copy, so the hook is namespace-generic for service-sms to adopt. Not done: in-place follow after Save needs a console change. @object-ui/console 18.0.0 SettingsView, read from the npm tarball (this session has no objectui repo access), merges only the PUT values after a save and never refetches the manifest, so the banner updates on the next page load. That half is reported below with its Seam line, and the card stays open via 'Part of'.", "tests": "At HEAD 4789209944: `pnpm --filter @objectstack/plugin-email test` gave Test Files 37 passed (37), Tests 676 passed (676), and `pnpm --filter @objectstack/plugin-email typecheck` (tsc --noEmit && check:test-typecheck) exited 0. `pnpm --filter @objectstack/service-settings test` gave Test Files 60 passed (60), Tests 995 passed (995), and its typecheck (tsc --noEmit) exited 0. Both ran under os-verify-lock, VERDICT command-exit 0. The full-package runs were taken at 9ffc78df34. The only later commit (4789209944) changes the new pin test file alone, and that file re-passed at 4789209944 (5/5) along with plugin-email typecheck, VERDICT command-exit 0. New pins: plugin-email src/live-transport-status.pin.test.ts (5) and service-settings src/settings-routes.live-status.pin.test.ts (6). Ablations ran through scripts/ablation-replace.mjs on src-resolved imports (no dist in the path), and every leg was restored with blob == HEAD and git diff HEAD empty. (1) The branch rewritten to 'instanceof LogTransport' gave 5 red tests, but as ReferenceError: LogTransport is not defined. That is a crash, not the mutation, so it is void and reported as such. (2) The branch rewritten to 'transport.constructor.name === LogTransport' gave 1 failed and 4 passed; the red test is 'the branch is isConfigured(), not the transport class'. (3) 'if (false)' gave 4 failed and 1 passed. (4) Deleting 'await Promise.allSettled([...applying])' gave 1 failed, the swap test: expected 'warning' to be 'info'. test:repo was not run locally; it belongs to CI, and the diff touches no repo-project test.", "gates": "Local, at 4789209944: `node scripts/pm/dispatch-gates.mjs --commands` derived 70 commands, and all 70 exited 0 (exit codes captured before any pipe). `--ran` with an exit-coded record answered: '70 derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED (a DERIVED zero — all 70 recorded an exit code and none of them is 3)'. Two problems on the first sweep, both resolved. check:engine-double-contract flagged the new pin's fake engine update(); the fake was trimmed to find() only, and the gate re-ran green. check:dual-build-cjs-loads first printed PREREQUISITE NOT MET (exit 3) on an unbuilt tree and passed on the final sweep. check-platform-checklist was not required: exports were added only, none renamed or removed. CI on PR #25448 at head 4789209944, read right after the PR opened: 'No other open PR may claim the same issue' success; '18.x upgrade harness (opt-in)' and 'Packed-tarball smoke (opt-in)' skipped. Every other check-run was in_progress, including the Type Check lanes, Lint lanes, Governed Surface Queue Guard and Check Changeset; Test Core and Build Core were not listed yet. in_progress, not yet converged.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "3 — (1) fleet-write relay pr_create, which landed as POST /repos/objectstack-ai/objectstack/pulls (draft #25448) plus POST /repos/objectstack-ai/objectstack/issues/25448/assignees (os-sales); read-back identical, 6232 bytes sent and stored. (2) post-stamped issue comment POST /repos/objectstack-ai/objectstack/issues/25335/comments (this os-dev-report). git push is direct and not counted.", "open_questions": [ { "question": "The claim's line is 'Clause-②: no', and I copied it verbatim into the PR body and the changeset. The diff does widen an exported surface: @objectstack/service-settings gains SettingsService.registerLiveStatus and liveStatusOf, and exports SettingsLiveStatus, SettingsLiveStatusProvider and SETTINGS_LIVE_STATUS_BANNER_ID. A sibling changeset in the tree spells an added exported method 'Clause-②: yes (widening: ...)'. Which line should stand?", "options": [ "A: keep 'Clause-②: no' as claimed. Zero writes; Check Changeset judges it.", "B: the seat re-declares 'Clause-②: yes (widening: service-settings exports registerLiveStatus / liveStatusOf and three new type/const exports; nothing is refused)'. This costs one changeset commit, which I can push on request, plus a seat-written PR-body edit (the dev does not PATCH the body)." ], "recommendation": "B, because the declaration should describe the diff, and the exports are additive and public. The level stays patch either way, since Metadata-protocol is none." } ], "out_of_scope_findings": [ "carrier: #25335 (the PR is 'Part of', so the card stays open) · the console half of done-when item 2. @object-ui/console 18.0.0 SettingsView (npm tarball dist/assets/SettingsView-*.js; the save handler sets state to the old payload merged with the PUT response's values) never refetches GET /api/settings/:namespace after a save or an action, so the live_status banner keeps the pre-save statement until reload. Fix: refetch the namespace after a successful save or action. Not measured at the .objectui-sha pin 5fef032b (no objectui access in this session). Seam: runtime:service-settings GET /api/settings/:namespace manifest.specifiers[0] (info_banner id live_status) → renderer:@object-ui/console SettingsView save handler (merges PUT values only; manifest never re-read) · dedupe words: settings page refetch after save, settings manifest stale after save, live_status banner", "class: a · reach: named producer: packages/plugins/plugin-email/src/email-plugin.ts applyMailSettings, the provider === 'log' branch. Measured on this branch with a throwaway probe (not committed): boot with provider smtp + smtp_host, then save provider=log. The transport stays SmtpTransport, isConfigured() stays true, and mail keeps being delivered over SMTP. Meanwhile the option label reads 'None (log only — no real delivery)' and the plugin logs 'transport unchanged — messages are logged and recorded in sys_email, never delivered'. That log line is false whenever the previous transport delivers, and it also holds across a restart when OS_EMAIL_SMTP_* built the boot transport. With this PR the page banner truthfully says 'Mail is delivered through SMTP', which makes the mismatch visible · dedupe words: mail provider log still delivers, None log only no real delivery, transport unchanged never delivered", "carrier: none (承接者:无) · noted, not filed · Setup → SMS states no live sender either. When the constructor/env provider is twilio or aliyun, the stored default shows 'None (log only)' while the live transport delivers. service-sms can adopt the same statement by calling settings.registerLiveStatus('sms', ...) from ISmsService.isConfigured(). Recorded in the PR's One-pattern paragraph.", "carrier: none (承接者:无) · noted in the PR's Acceptance notes, not filed · the live_status banner has no zh-CN, ja-JP or es-ES text. Its text is computed per request, and the console's banner translation lookup by id would replace it with a fixed string." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
os-dev-report addendum
{ "issue": 25335, "addendum_to": "issuecomment-6366550562", "ruling_applied": "open question Clause-② resolved as B by the seat", "pr": "https://github.com/objectstack-ai/objectstack/pull/25448", "new_head": "39af42e75a", "change": "One commit (39af42e75a) touches .changeset/25335-email-live-transport-statement.md only. Clause-② now reads: yes (widening: service-settings exports SettingsService.registerLiveStatus / liveStatusOf, SettingsLiveStatus, SettingsLiveStatusProvider and SETTINGS_LIVE_STATUS_BANNER_ID; nothing is refused). Both packages stay at patch, and Metadata-protocol stays none. The PR body was not edited; the seat changes its Clause-② line.", "changeset_gates": "At 39af42e75a all six exited 0: check-empty-changeset --base origin/main, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, pnpm check:changeset-gate-self-tests, pnpm check:pm-changeset-deadline-census, node scripts/check-changeset-fixed.mjs. check-changeset-no-major notes that locally no pull_request payload was available to read the declaration from; CI's Check Changeset reads it from the PR body.", "ran_reconciliation": "At 39af42e75a dispatch-gates derived the same 70 commands. The first pass in the recreated worktree had no built dist, so 4 gates exited 3: check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content. After a cache-backed turbo build (76/76 tasks, 75 cached), all 4 re-ran and exited 0. --ran answered: 70 derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED (a DERIVED zero — all 70 recorded an exit code and none of them is 3).", "api_writes": "1 more — the post-stamped comment POST /repos/objectstack-ai/objectstack/issues/25335/comments (this addendum)", "mcp_calls": "0" }Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Landed, server half ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-23T15:16Z6367450381PASS, ACCEPT6367463668), head39af42e75aorigin/mainab510f42e7State: the card stays open and dispatched (
Part of #25335) for the console half. After Save,@object-ui/consoleSettingsViewmerges only the PUTvaluesand never re-readsGET /api/settings/:namespace, so the newlive_statusbanner updates only on the next page load. That half is routed to theuilane seat (objectui#5560) through the coordinator. The server already answers the new statement on every read.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Release: claim
6364710482· sessionsession_016g7nYP9wdNx1GDa5XDr7Uy(domain:servicesseat 1, seat post #6021) · because the maintainer voided that seat · todomain:serverseat 1 (seat post #6367), same accountdomain:serverseat 1 (verbatim: 「开」) to take over the in-hand server cards.os-sales(same account).ab510f42e7, landing note6368209700); no server branch is open. What remains is the console half (Settings re-reads the namespace after Save), routed to theuilane; nothing of it is pushed. The claim follows as its own comment.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: card takeover, holding ·
domain:serverseat 1 (seat post #6367, project run by the Sales account, 由Sales账户的项目负责) · p2 · the card staysPart of #25335for its console half · 2026-10-24T13:40ZSession:
session_01JrXUZYyTV4i588KLZRxSvJAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/issue-25335-email-transport-statement(server half, merged as PR #25448; no new server branch)Worktree:
objectstack-issue-25335Domain:
domain:serverSeat:
domain:server#1File surface: none on the server side. The remaining half is the console's Settings view (re-read
GET /api/settings/:namespaceafter Save), which isuilane work; this seat holds the card until that half lands or the card is routed there, and closes it then.Container & model:
S,mode:subagent,model: default tier(nothing dispatched by this claim),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: n/a — the server half's defect is landed; the console half's owner is the
uilane.Thread-read: 6368209700
Serial constraints cleared: none (no server file is touched).
Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Release: claim
6384920448· sessionsession_01JrXUZYyTV4i588KLZRxSvJ(domain:serverseat 1, seat post #6367) · because the maintainer closed the server lane's shift in the Sales account's project · to the queue, no successor namedab510f42e7, landing note6368209700). No server branch is open and nothing else is pushed.valuesand never re-readsGET /api/settings/:namespace, so thelive_statusbanner updates only on the next page load. That isuilane work (the console is moving into this repo), so the card goes back topm:queuewithpm:retriagefor its lane: thedomain:serviceslabel names a voided lane.pm:dispatched→pm:queue+pm:retriage; assigneeos-salescleared.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Retriage answer: both halves have landed — closing
completed· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T15:55ZThe answer to
6386247938. ⛔ Not a claim.ab510f42e7), landing note6368209700.d17f6708173d: "settings pages re-read their namespace after a save or action".main's.objectui-shais2958f17e9c6a. The compare fromd17f6708173dreadsahead, by 13, so the vendored Console carries the re-read.pm:queueandpm:retriagecome off, and the card closescompleted. Reopen it if a reading of Setup → Email onmainstill shows a stale banner after Save.Generated by Claude Code