Skip to content

[Decision] A notify node already mails a written-out or computed email address: does ruling 6377842908's 「⛔ 不接受自由文本或表达式拼出的地址」 reach it — refuse both (A), refuse computed addresses only (B), or leave both (C)? #25647

Closed

Description

@objectstack-fleet

Ruled: 6386629237 · batch #12 item 2 · letter B · 2026-10-24T16:24:48Z (a computed address no user holds fails the node pointing to { emailField }; a written-out address stays an author constant; rides #25571 + a spec describe-text card)

Filing gate ②: only the maintainer can decide this. The dev raised it as an open question on #25610 (report 6384295899, open_questions[2]), and the domain:spec seat 2 review of PR #25645 confirms it. Filed by domain:spec seat 2 (seat post #18549) · sunhaolin · session session_01PC4NPXPgPmpDRG9DdDnyXT. ⛔ Not a claim. Domain and grade are triage's; the seat suggests domain:services (where B and most of A live) and p2 (it limits a path that works today; no new protocol).

Reader who acts: the director seat presents it. After the ruling, the lane that owns the letter acts (see After the ruling).

维护者速读

Background

Ruling 6377731715 on #25496 (batch #388 item 4, the maintainer's 「同意」), item 3, verbatim: "#25444 → B. A notify recipient form that is an address taken from an email-typed field of the triggering record — never a free string — rendered through the same templates, subject to the environment's outbound mail configuration, rate-limited per address, written to the messaging log." The governing ruling on the same row, 6377842908 (reconciliation 6377965040), gives the execution condition verbatim: 「收件人只能来自记录的 email 类型字段,⛔ 不接受自由文本或表达式拼出的地址;按地址限速,记消息日志」.

Two paths that exist today put an address no user holds into outbound mail:

  • Written out. A string recipient shaped like an email is delivered as a literal address by the email channel: packages/services/service-messaging/src/email-channel.ts about :192, if (EMAIL_SHAPE(recipient)) { … return { address: recipient, … } }. The resolver keeps it verbatim when no user holds it (recipient-resolver.ts about :77, "'<email>' → sys_user lookup by email → id (verbatim on miss)"). The spec docblock names it as an audience selector (packages/spec/src/automation/io-node-config.zod.ts about :277, "team:ID, an email address").
  • Computed. A CEL envelope in recipients may evaluate to any string, so { dialect: 'cel', source: 'record.contact_text' } over a free-text field reaches the same branch.

PR #25645 does not touch either path; its Acceptance notes record them.

Governing text: the ruling's 「收件人只能来自记录的 email 类型字段」 and 「⛔ 不接受自由文本或表达式拼出的地址」; facet ③ of the director's increment 6377530193, 「#25444 收件人只许记录的 email 字段,不许自由串」. Read literally, the words cover every recipient. Read in context, they are the execution condition on the new form for #25444, where the risk named is a guest-supplied value turning outbound mail into a relay. The text does not settle which reading holds, so it is raised.

Protocol: A narrows the protocol (Clause-②: yes (narrowing), BREAKING). B narrows a runtime behaviour and the spec describe text, not the accepted shape. C changes nothing but the describe text.

Premises, each with its re-check

  • The written-out path delivers: git grep -c "if (EMAIL_SHAPE(recipient))" origin/main -- packages/services/service-messaging/src/email-channel.ts → 1. Control: git grep -c "verbatim on miss" origin/main -- packages/services/service-messaging/src/recipient-resolver.ts → 1.
  • In-repo producers of the written-out path: git grep -cE "recipients\s*:\s*\[?\s*['\"][^'\"]*@" origin/main -- examples → 5, all in examples/app-showcase/src/automation/flows/index.ts (admin@objectos.ai ×4, the seeded admin, a user; exec@example.com ×1, no user). Control: git grep -c "recipients:" origin/main -- examples/app-showcase/src/automation/flows/index.ts → 21.
  • No in-repo envelope computes an address: every recipients envelope in examples/** reads a user reference (record.assignee, record.owner, task.owner, ownerId, record.account.owner, …). Re-check: git grep -nE "recipients\s*:\s*\[?\s*\{\s*dialect" origin/main -- examples and read each source.

The question

Does the ruling's 「⛔ 不接受自由文本或表达式拼出的地址」 apply to the recipients a notify node already accepts, or only to the new email-field form?

Options

Letter What is done What the customer sees Workload
A notify refuses an email-shaped string recipient at publish (spec and lint), and the run fails an envelope whose value is an address no user holds. An external address comes only from { emailField }. A flow that mails a fixed mailbox (ops@company.com) is refused at deploy; the author must give that mailbox a user or a record field. The 5 showcase nodes change (4 to the admin's user id). domain:spec + domain:services · 2 PRs · BREAKING changeset and migration notes. Nearest precedent: #24983 (refusing template rows for unsupported channels, the 19.0 half of ruling D).
B A written-out address in the metadata keeps working, as an author-chosen constant. A computed address, an envelope that evaluates to an address no user holds, fails the node with a located message pointing to { emailField }. An envelope that evaluates to a user's address still resolves to that user. Fixed mailboxes keep working. A flow that builds an address from record text (the relay risk) is told at run time to use an email field. No in-repo producer changes. domain:services (service-automation's resolveRecipients or the messaging resolver) plus the spec describe text · 1 PR, could ride #25571 · no precedent.
C Both paths stay. The describe text states the two ways to an external address (written out, or { emailField }); #25571's per-address rate limit and log apply to both. Nothing changes. A guest-filled text field fed through an expression can still pick the address a flow mails. domain:spec describe text · S.

Business meaning: A is a mailroom that only delivers to staff or to an address printed on the customer's own file. B also takes a fixed address the manager wrote on the routing slip, but refuses one the visitor wrote in the margin. C delivers to any address on the slip.

Four axes

Recommendation: B. Two years out, mainstream workflow platforms reach an outside address through an authored constant or an email field (Salesforce email alerts take exactly those two). Confidence gap: whether "free text" in the ruling also meant the authored constant. If it did, the answer is A, and the seat would not argue it. Not A: it removes a common, low-risk need (a fixed mailbox) that the ruling's risk does not cover. Not C: it keeps the exact path the ruling's second clause names.

Self-check: looking at ① alone, B. Do ②③④ flip it? No.

After the ruling

os-decision-facets

Prior rulings read: "notify recipient email address free text expression" → 2 hits (6377731715, 6377842908 on #25496; reconciliation 6377965040; director increment 6377530193); ADR-0012 (recipient resolver), ADR-0030 P1 (the resolver's single home); thread: none (new card)

Dedupe

Activity

  1. added
    area:workflowApprovals and automation — the work that runs without a person driving it
    domain:serverDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    on Oct 24, 2026
  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first touch — enhancement · priority:p2 · domain:server · area:workflow · needs-user-decision · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-24T13:53Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. os-elon-musk commented on Oct 24, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #12 item 2 · letter B · maintainer 「25635 25647 25675 同意」 2026-10-24T16:24:48Z

    B = 只关「表达式算出来的地址」:notify 收件人若由表达式(CEL 信封)求值得到一个不属于任何用户的邮箱地址,节点失败并给出定位提示指向 { emailField };求值得到某个用户的地址仍解析为该用户。元数据里写死的地址是作者常量,照常发出。裁决 6377842908 的「⛔ 不接受自由文本或表达式拼出的地址」按此读:它管的是运行时拼出的地址,不管作者写死的常量。

    工作量:domain:server 1 个 PR(service-automation 的 resolveRecipients 或消息解析器),可并入 #25571 的文件;domain:spec 改描述文字,把「写死地址」写成作者常量,并从受众选择器列表里去掉笼统的「an email address」。仓库内无任何表达式算地址的生产者,B 不拒绝任何现有配置。

    维护者动作已完成:needs-user-decision → pm:queue;裁不派。批 #12 简报:https://claude.ai/artifact/YLrSzMXcnWVmbkZCxgxcQt


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:spec seat 2 · 2026-10-24T16:47Z · the spec half of ruling B 6386629237 (the recipients describe names a written-out address as an author constant and drops the bare "an email address" selector) is filed as #25696, to land with or after this card's runtime half.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · rides #25571 under ruling 6386629237 (「可并入 #25571 的文件」); the full claim is on #25571
    Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01CmnfU7vRbNtSFtBQwyyKpB
    Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-e1l64i (shared with #25571; one PR, one Fixes line per card)
    Domain: domain:server
    Seat: domain:server#1
    File surface: as on #25571 (packages/services/service-automation/src/builtin/notify-node.ts resolveRecipients for this card's half). ⛔ Not packages/spec/**: ruling B's describe half is #25696.
    Container & model: M, mode:thread, model: opus (the default of dispatch-gates --tier), effort: xhigh
    Clause-②: no
    Metadata-protocol: narrowed
    Responsibility: n/a — not a defect card
    Thread-read: 6386884557
    Premise: holds (2026-10-24T23:07Z: no in-repo recipients envelope computes an address, per the card's re-check; the dev re-runs it)
    Serial constraints cleared: see #25571's claim.

  6. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed with #25571: PR #25721 (d1e329e96d, 2026-10-25T04:41:00Z) carries ruling B's runtime half. Landing record: #25571 (the seat's comment of 2026-10-25T04:43Z). Card closed by Fixes; pm:dispatched removed.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling record · confirmation of ruling 6386629237 B's channel scope · domain:server seat 1 (seat post #6367) · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-25T05:05Z

    Provenance: who: Leehom (maintainer) · said: chose 「A:只查含 email 通道的节点」 on the question 「#25647 裁决 B 的「表达式算出的地址必须属于用户」只管发邮件的节点吗?」 · when: 2026-10-25T05:04:48Z · where: the server-lane project thread, on a decision card the seat posted.

    A = ruling B's refusal of a computed address no user holds applies only to a notify node whose channels include email. A node on inbox alone (the messaging default) mails nobody, so its computed recipients are not screened. This is what PR #25721 (d1e329e96d) shipped; nothing further is owed. The contract reviews' flag F2 (6392441398, 6392736321) is closed by this answer. The describe text on #25696 should say the screen is scoped to the email channel.


    Generated by Claude Code

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

Metadata

Metadata

Labels

area:workflowApprovals and automation — the work that runs without a person driving itdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew 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