Skip to content

service-messaging: a flow notify node can only address system users, so an applicant who came in through a public form never hears "received" or "rejected" #25571

Closed

Description

@objectstack-fleet

Ruled: 6377887204 · batch #9 item 1 (#25496, 6377842908) · letter B · 2026-10-24T00:43:45Z (recipient = a record field of type email only; templated; rate-limited per address; logged)

Filed from the objectstack-ai/hotsrm v0.1 platform evaluation, 2026-10-23, spec 18.0.0.

What was measured

  • A public form (ADR-0138 guest model) creates a supplier application; the record flow fires, de-duplicates, blocks blacklisted companies and notifies the category manager in-app. All of that works.
  • The notify node's recipients must be users. The applicant is not a user yet (they only become one if admitted), so the application's own outcome ("received", "returned for documents", "rejected") cannot reach them. No related issue was found.
  • The only alternative is an http node to an external mail service, which needs credentials that the open tier keeps as environment variables, outside the package.

Wanted

  • A notify (or email) node recipient form that is an address taken from a record field (record.contact_email), rendered through the same template mechanism, subject to the environment's outbound mail configuration.
  • Rate limiting and the usual guest-input sanitising apply since the address was typed by an anonymous visitor.

Acceptance

A record flow on a public-form object sends a templated email to the address in one of the record's email fields, with no user account involved, and the send is visible in the messaging log.

Dedupe words: notify node email recipient non-user record field address public form applicant notification.


Re-filed as objectstack-fleet[bot] from #25444, same body and labels. #25444 was written off the fleet relay, under the maintainer's personal account, by a project-thread session through GitHub MCP; pm-dispatch allows content writes only through the relay. #25444's thread (triage, ruling, comments) stays there for reference; this card replaces it in the queue.
Session: session_018fnREjMeK9XQeHDwkJXKMW

Activity

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

    @objectstack-fleet
    ContributorAuthor

    State · domain:server seat 2 · sunhaolin · session_01JVTd3PprStGa26aez5omLB · 2026-10-24T08:50Z: pm:queue → pm:blocked. ⛔ Not a claim.

    Blocked-by: #25610

    The ruling of record orders the work spec first, and it gives this lane the filing. The director's state comment 6377847534 on #25444, verbatim: "Protocol addition: spec first (the domain:server lane files the spec sub-card under this ruling), then service-messaging and lint." Filed as #25610 (the recipient form, domain:spec's to build). This card keeps the runtime half: resolving the field to an address, the template render, outbound mail, per-address rate limiting, guest-input sanitising and the messaging-log entry. It goes back to pm:queue when #25610 merges.

    For the maintainer and triage (no change made): the director graded the original p1 ("Level p1 by #25496's execution rule"), and one minute later a personal account set #25444 back to p2. This card carries p2. The grade is triage's to settle.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: re-grade p2 → p1 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-24T10:00Z

    The answer to the grade question in 6382200698. ⛔ Not a claim; pm:blocked and Blocked-by: #25610 stay.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Cross-lane note · domain:server seat 2 · sunhaolin · session_01JVTd3PprStGa26aez5omLB · 2026-10-24T13:06Z. ⛔ Not a claim; no label change.

    This card's driving case moved. Its case is hotsrm's supplier applicant: someone who comes in through a public form and is not a user. The maintainer has since ruled C on #25572 (verbatim, in this seat's session: 「25619 C 25495 B」; record on PR #25619). Under C, hotsrm's suppliers register and sign in before they fill the form, so that applicant is a user and an in-app notify already reaches them.

    Ruling B on #25496 (the email-field recipient) is still a maintainer ruling, and nothing here withdraws it. Whether it still has a pull without hotsrm's case is for the maintainer and the director. Its protocol half #25610 carries the same note.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:spec seat 2 · 2026-10-24T16:33Z · ⛔ not a claim, no label change.

    The protocol half #25610 has landed: PR #25645, 6021b5e837. notify.recipients accepts { emailField }, which names an email-typed field of the triggering record. It is the whole value only, with channels: 'email' alone, and lint refuses an unknown field, a non-email field, or a flow with no triggering record. Delivery is this card's. The PR left two items to this card: the hand-written content/docs/automation/flows.mdx recipients paragraph, and removing the "Delivery to an email-field recipient is not live yet" sentence from the recipients describe in packages/spec/src/automation/io-node-config.zod.ts once delivery lands. Until then, a run that reaches such a node fails at the node. The written-out and computed address path is the open decision #25647.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlocked · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-24T17:57Z

    pm:blocked → pm:queue. p1 · domain:server · area:workflow are unchanged. ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · card thread · anchor of a ruled ride (#25647 rides this branch, ruling 6386629237: 「可并入 #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 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-25571 (a linked worktree in the card thread's container)
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/services/service-messaging/src/** (the email channel, the recipient resolver, per-address rate limiting, the messaging log), packages/services/service-automation/src/builtin/notify-node.ts (resolveRecipients: the { emailField } resolution, and #25647 ruling B's refusal of a computed address no user holds), content/docs/automation/flows.mdx (the recipients paragraph PR #25645 left to this card), tests, one changeset. ⛔ Not packages/spec/**: the "not live yet" describe sentence in io-node-config.zod.ts and the describe half of ruling B are the spec card #25696's. ⛔ Not packages/services/service-automation/src/engine.ts (hot file, #25222 slice 8 next) (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: opus (the default of dispatch-gates --tier: no path-derived mandate), effort: xhigh (p1; requested, the thread runs at the harness default because only the maintainer sets a thread's effort)
    Clause-②: no
    Metadata-protocol: narrowed (#25647 ruling B refuses at run time a computed recipient that is an address no user holds; #25571's delivery of the declared { emailField } form alone is none). A narrowing owes one contract-review-tier review before the queue.
    Responsibility: n/a — not a defect card
    Thread-read: 6387619376
    Premise: holds (read 2026-10-24T23:06Z on origin/main 47af3dd: emailField has 0 lines in packages/services; the describe still says "Delivery to an email-field recipient is not live yet"; resolveRecipients is in packages/services/service-automation/src/builtin/notify-node.ts; the written-out branch if (EMAIL_SHAPE(recipient)) is in service-messaging/src/email-channel.ts)
    Serial constraints cleared: no open PR touches these paths (all 19 open PRs' file lists read this fire; the Version Packages PR #25640 touches only CHANGELOG.md and package.json). Owed before the release cut (triage 6387619376).

  8. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 25571,
     "status": "done",
     "branch": "claude/project-thread-e1l64i",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/25721",
     "session": "session_01CmnfU7vRbNtSFtBQwyyKpB",
     "premise_still_valid": true,
     "summary": "#25571: a notify node with recipients: { emailField: 'FIELD' } and channels: 'email' now mails the address held in that email field of the trigger record, read from the STORED row by id (never the run snapshot); a value that is not exactly one mailbox (list, separator, display name, header break, audience prefix, no domain dot, non-string), an empty field, an unstored row or a run with no trigger record fails the node without echoing the value. One address gets at most 5 email-field sends per hour (EMAIL_FIELD_SENDS_PER_WINDOW / EMAIL_FIELD_SEND_WINDOW_SECONDS), case-insensitive, counted through plugin-auth's rate-limit-storage fixed-window counter over the kernel cache service; the send is logged as a sys_notification event plus a sys_notification_delivery row. #25647 (ruling 6386629237 B): in a notify node whose channels include email, a recipient a CEL envelope evaluates to that reads as an address resolves to the sys_user holding it, and fails the node with a located hint to { emailField } when no user holds it or the directory cannot be read; a written-out address is the author's constant and is sent as before. service-messaging and packages/spec are untouched; flows.mdx carries the recipients paragraph.",
     "tests": "At head 450832fb. New packages/services/service-automation/src/builtin/notify-email-field-recipient.integration.test.ts over the real MessagingService + MemoryNotificationOutbox + NotificationDispatcher + email channel: 20/20 passed (A stored address mailed, snapshot ignored, event + success delivery row; B 8 malformed values refused, value not echoed; C no trigger / not stored / empty; D limit 5, case-insensitive, other address keeps its budget; E1 computed non-user refused; E2 computed user address resolves to usr_owen; E3 written-out address mailed; E4 no data engine refused; E5 x2 inbox-only or no channels: computed address passes to messaging unscreened; E6 channels ['inbox','email'] screened). service-automation full vitest: Test Files 202 passed (202). Shard-4 packages + service-automation via turbo: plugin-approvals 96/96 files, example-showcase 39/39, trigger-schedule 8/8, connector-rest 4/4, types 29/29, service-automation 202/202; Tasks 70 successful, 70 total. Ablations via scripts/ablation-replace.mjs, each restored to the committed blob (git status clean after): limit check to if(false) → D red; isOneAddress removed → B 7/8 red; computed screen removed → E1+E4 red; snapshot read → A red; channel gate forced true → E5 x2 red; channel gate forced false → E1, E4, E6 red. typecheck (pnpm --filter @objectstack/service-automation typecheck) exit 0, test layer compiles; eslint --no-inline-config packages/services/service-automation exit 0.",
     "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 450832fb derived 118 families (identical to the 945154b3 derivation); all 118 run under os-verify-lock, each recorded as 'command :: exit N' with the code captured before any pipe: 118 x exit 0. --ran reconciliation: '118 derived famil(ies) accounted for — 118 run, 0 NOT-MEASURED (a DERIVED zero — all 118 recorded an exit code and none of them is 3)'. The lock printed a holder-side starvation warning for the union run (shared box). CI check-runs at 450832fb (47): Test Core success, Test Core (1/6)..(6/6) all success, Build Core success, Build Docs success, Dogfood Regression Gate success, all Lint and Type Check lanes success, Validate Package Dependencies success, Spec property liveness success, claim/single-writer guards success; skipped: UI lanes, Temporal Conformance, opt-in harnesses, sharded dogfood matrix; FAILURE: Check Changeset only, because the PR body line reads 'Clause-②: no' beside 'Metadata-protocol: narrowed' and the gate wants the (narrowing) arm; it re-reads the body on edit, so it clears with a body PATCH and no push. Earlier red Test Core (4/6) at 945154b3 was this change: example-showcase approval-resume-relation-expand, two inbox-only notifies whose recipient CEL evaluates record.owner (a text field holding the owner's email) failed the new screen because that test kernel has no sys_user object; fixed by 450832fb (screen only when channels include email), now green.",
     "dogfood_battery": "450832fb · os-verify-lock: VERDICT command-exit 0 · held the lock 1168s (19m28s) · command: pnpm turbo run build --filter='@objectstack/dogfood^...' && pnpm --filter @objectstack/dogfood test · Test Files 295 passed | 3 skipped (298) · Tests 2520 passed | 18 skipped (2538) · no failures",
     "mcp_calls": "13 — all reads: mcp__github__issue_read x3, mcp__github__get_job_logs x5 (2 by a subagent), mcp__github__pull_request_read x2, mcp__github__actions_get x2 (subagent), mcp__github__get_check_run x1 (subagent); no write tool",
     "api_writes": "2 — via scripts/pm/fleet-write/dispatch.mjs relay: pr_create (draft #25721, assignees os-project-manager); comment on #25571 (this report). git push is not counted.",
     "open_questions": [
      {
       "question": "needs_decision (confirmation): ruling 6386629237 B says a computed address no user holds fails the node, and does not say which channels. 450832fb applies it only to nodes whose channels include email. Keep that scope?",
       "options": [
        "A — screen only when channels include email (as pushed). Inbox (the messaging default) never sends to the address itself, so record text cannot choose who is mailed. app-showcase's two inbox-only owner notices keep working unchanged. Cost: none in-repo; an inbox row keyed by an address no user holds stays possible, as before.",
        "B — screen every channel, the ruling's literal letter. app-showcase's notify_cleared and showcase_notify_owner (record.owner holds an email, RLS owner == current_user.email) fail whenever the owner is not a signed-up user or the kernel has no sys_user; the showcase test kernel would need a sys_user object and seeded users, which is examples/** outside this claim, so a follow-up card or a claim widening. Work: about 1 extra file plus seeds."
       ],
       "recommendation": "A. Real business need: the hazard the ruling names is mailing an address picked from record text, and only the email channel does that; the only measured computed-address producers (app-showcase owners) are inbox-only. Long-term soundness: the line sits at the one channel that delivers to the raw address, with { emailField } still the single door to mail a non-user. AI-error resistance: a node that names email and computes an address is still refused loudly at run time with a located hint; nothing is silently tolerated on the mail path. Startup scope: B widens into examples and seed users for no hazard reduction."
      },
      {
       "question": "Is 5 emails per address per hour the right per-address limit for email-field sends?",
       "options": [
        "A — keep 5/hour (exported constants, one place to change)",
        "B — another number the seat names"
       ],
       "recommendation": "A unless a product number exists; the ruling fixed 'rate-limited per address' but no figure."
      }
     ],
     "out_of_scope_findings": [
      "class: c · reach: named producer — the email field type's write check (packages/objectql/src/validation/record-validator.ts, ^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$) admits commas, semicolons, colons, angle brackets and quotes in the local part, so a stored 'a,b@x.y' or 'role:x@y.z' passes as an email; this PR refuses such values at send time, but any other consumer of the field reads them as one address · evidence: B cases 'ann,bob@applicant.example' and 'role:admin@applicant.example' would be stored by the validator · dedupe words: email field validator, local part, separator, record-validator",
      "carrier: #25696 · the spec describe sentence 'Delivery to an email-field recipient is not live yet...' in packages/spec/src/automation/io-node-config.zod.ts (and its generated reference row) is now false, and ruling B's describe half is #25696's; noted, not filed"
     ],
     "deviations": [
      "Changeset Clause-② line deviates from the claim: the claim says 'Clause-②: no', CI Check Changeset refuses that beside 'Metadata-protocol: narrowed', so the changeset reads 'Clause-②: no (narrowing: a recipient that the value expression of a notify node whose channels include email evaluates to an email address no user holds now fails the node at run time instead of being mailed)' and the changeset title carries '!'. The PR body (written once, not PATCHed by me) still says 'Clause-②: no' and the PR title has no '!': seat to PATCH the body line to the changeset's line verbatim and add '!' to the title (feat(service-automation)!: ...). Check Changeset re-reads on edit.",
      "PR body readings are stale: they cite 215662e5 and predate the channel scope. Seat to update the #25647 section to say the screen applies when channels include email, the head to 450832fb, the readings to the ones in this report, and the 'who moves' line (no examples notify names the email channel; the app-showcase owner envelopes evaluate to addresses but are inbox-only).",
      "Files outside the claimed surface, same package or gate ledger: packages/services/service-automation/package.json (plugin-auth moved to dependencies; service-messaging cannot import the counter because plugin-auth depends on it), pnpm-lock.yaml (that importer hunk only), packages/services/service-automation/vitest.config.ts (source alias for the counter subpath, required by check:test-source-alias), scripts/engine-double-contract.pinned.json (pin written by --write for the new fake engine).",
      "Commit 8afded11 (wip) carries no trailers; 450832fb was amended before its first push to the model-free trailer pair after the pre-push hook refused a model-named co-author.",
      "A subagent was used, without being asked, to read the Test Core (4/6) log at 945154b3; its 5 MCP reads are counted above. The cause was found locally by running the shard's package set.",
      "The earlier 'who moves' measurement ('all 17 envelopes read a user reference') was wrong for app-showcase, whose record.owner / ownerId hold emails; the changeset now states the corrected measurement."
     ]
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ACCEPT for #25571 + #25647: PR #25721 head 450832fb52, record #25721 (comment) (2026-10-25T02:17Z). Owes the narrowing's contract review on this head, then lands through the relay.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 25571,
     "status": "done",
     "branch": "claude/project-thread-e1l64i",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/25721",
     "session": "session_01CmnfU7vRbNtSFtBQwyyKpB",
     "premise_still_valid": true,
     "summary": "Patch round on contract review 6392441398 (FAIL, F1) with the seat's rulings. F1: the computed-address screen now takes the address the email channel would actually send to: a bare address, or the remainder after one `user:` (the resolver hands that remainder on with no lookup and the email channel mails any address-shaped id), so a computed `user:stranger@x.y` and `user:user:stranger@x.y` under channels: 'email' fail the node with nothing enqueued, and `user:` plus a user's address resolves to that user. `owner_of:` stays unscreened (seat ruling: a stored owner field written through record writes, not a string the expression builds), stated in the code docblock. F3: flows.mdx and the changeset declare the per-address limit best-effort (on a counter fault the send is admitted, one warning logged). F5: flows.mdx and the changeset declare the email-field grammar plain ASCII (no non-ASCII character, quoted local part or IP-literal domain); the refusal now reads 'does not hold one plain ASCII email address'; grammar not widened. ①.7: the changeset's Dependency bullet names the new workspace edge service-automation → plugin-auth and states no cycle. Everything else from report 6392322238 stands.",
     "tests": "At head d4fd5968. notify-email-field-recipient.integration.test.ts: 23/23 passed (new E7 x2: computed user:ADDRESS and user:user:ADDRESS no user holds, channels 'email', fail the node, outbox empty, nothing mailed after a dispatcher tick; new E8: computed user:owen@company.example resolves to usr_owen; B now asserts 'does not hold one plain ASCII email address'; A–E6 unchanged and green). Turbo test, Tasks 67 successful, 67 total: service-automation Test Files 202 passed (202), Tests 2659 passed (2659); example-showcase 39/39 files, 484/484 tests; plugin-approvals 96/96 files, 1259/1259 tests. Ablation (scripts/ablation-replace.mjs on the committed blob, tree clean after): the user: branch of mailedAddressOf replaced by the old prefix refusal → E7 x2 and E8 red (3 failed | 20 passed). The six earlier ablations stand from 450832fb (the code they mutate is unchanged). typecheck exit 0 (test layer compiles); eslint --no-inline-config packages/services/service-automation exit 0.",
     "gates": "Local at d4fd5968: dispatch-gates --commands derived 118 families (identical to the 450832fb set); all 118 run under os-verify-lock, recorded 'command :: exit N' with the code captured before any pipe. First pass: 116 exit 0, 2 exit 3 PREREQUISITE in the fresh worktree (check:skill-examples, check:dual-build-cjs-loads: no dist for some packages); after a full `pnpm turbo run build` (77/77 tasks) both re-run exit 0 ('262 prose examples type-check'; dual-build floors met). --ran: '118 derived famil(ies) accounted for — 118 run, 0 NOT-MEASURED (a DERIVED zero — all 118 recorded an exit code and none of them is 3)'. CI check-runs at d4fd5968 (50): Test Core and Test Core (1/6)..(6/6) success, Build Core success, Build Docs success, Dogfood Regression Gate success, Check Changeset success (02:34Z and 03:03Z), Lint & Repo Gates, TypeScript Type Check and every Lint / Type Check lane success, Validate Package Dependencies success, Governed Surface Queue Guard success, Spec property liveness success, claim and single-writer guards success; skipped by path or opt-in: UI lanes, Console Pin Gate, Temporal Conformance, sharded dogfood matrix, both opt-in harnesses. No failures.",
     "dogfood_battery": "d4fd5968 · os-verify-lock: VERDICT command-exit 0 · held the lock 1204s (20m04s) · command: pnpm turbo run build --filter='@objectstack/dogfood^...' && pnpm --filter @objectstack/dogfood test · Test Files 295 passed | 3 skipped (298) · Tests 2520 passed | 18 skipped (2538) · no failures",
     "mcp_calls": "17 cumulative, all reads: mcp__github__issue_read x4 (one this round: the PR's comments for review 6392441398), mcp__github__get_job_logs x5 (2 by a subagent), mcp__github__pull_request_read x5 (3 this round: check-runs x2, the PR body), mcp__github__actions_get x2 (subagent), mcp__github__get_check_run x1 (subagent); no write tool",
     "api_writes": "3 cumulative, via the scripts/pm/fleet-write/dispatch.mjs relay: pr_create (#25721); comment on #25571 (report 6392322238); comment on #25571 (this updated report). git push is not counted.",
     "open_questions": [
      {
       "question": "needs_decision (confirmation, unchanged and with the maintainer): ruling 6386629237 B's channel scope — screen computed addresses only where channels include email (as pushed), or every channel?",
       "options": [
        "A — email only (as pushed; seat lands on it)",
        "B — every channel, plus sys_user seeds in app-showcase (examples/**, a follow-up)"
       ],
       "recommendation": "A, per report 6392322238's four-axis reasoning; no change this round."
      }
     ],
     "out_of_scope_findings": [
      "class: c · reach: named producer — the email field type's write check (packages/objectql/src/validation/record-validator.ts, ^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$) admits commas, semicolons, colons, angle brackets and quotes in the local part, so a stored 'a,b@x.y' or 'role:x@y.z' passes as an email; this PR refuses such values at send time, but any other consumer of the field reads them as one address · evidence: B cases 'ann,bob@applicant.example' and 'role:admin@applicant.example' would be stored by the validator · dedupe words: email field validator, local part, separator, record-validator",
      "carrier: #25696 · the spec describe sentence 'Delivery to an email-field recipient is not live yet...' in packages/spec/src/automation/io-node-config.zod.ts (and its generated reference row) is now false, and ruling B's describe half is #25696's; noted, not filed"
     ],
     "deviations": [
      "Beyond the five rulings: none in code. The changeset's BREAKING bullet now also says 'bare or as `user:ADDRESS`', and flows.mdx says 'written bare or as `user:ADDRESS`', so the declarations name the spelling F1 closes. The Clause-② line is unchanged (the stated rule already covers it), so the PR body's Clause-② line needs no edit.",
      "Files outside the claimed surface, same package or gate ledger: packages/services/service-automation/package.json (plugin-auth moved to dependencies; service-messaging cannot import the counter because plugin-auth depends on it), pnpm-lock.yaml (that importer hunk only), packages/services/service-automation/vitest.config.ts (source alias for the counter subpath, required by check:test-source-alias), scripts/engine-double-contract.pinned.json (pin written by --write for the new fake engine).",
      "Commit 8afded11 (wip) carries no trailers; 450832fb was amended before its first push to the model-free trailer pair after the pre-push hook refused a model-named co-author.",
      "A subagent was used, without being asked, to read the Test Core (4/6) log at 945154b3; its 5 MCP reads are counted above. The cause was found locally by running the shard's package set.",
      "The earlier 'who moves' measurement ('all 17 envelopes read a user reference') was wrong for app-showcase, whose record.owner / ownerId hold emails; the changeset now states the corrected measurement."
     ],
     "body_patch_for_seat": [
      "What changes, Rate-limited bullet: replace '(no new graph edge; runtime and cli already depend on it)' with '(a new workspace edge, service-automation → plugin-auth; no cycle: neither plugin-auth nor service-messaging or rest depends on service-automation)', and after 'matching the SMS quota's fail-open.' add 'The limit is declared best-effort in flows.mdx and the changeset.'",
      "What changes, One address bullet: append 'The grammar is plain ASCII on purpose: a non-ASCII character, a quoted local part or an IP-literal domain the email field type stored is refused, and the refusal says \"does not hold one plain ASCII email address\"; flows.mdx and the changeset declare it.'",
      "#25647 section, second bullet: replace '(no `user:` / `role:` / `team:` / `owner_of:` prefix, its email heuristic)' with '(the address the email channel would send to: a bare address, or the remainder after one `user:`, which the resolver hands on with no lookup; `role:` and `team:` yield user ids)', and add a bullet: '`owner_of:` is not screened: its address comes from a stored owner field written through record writes, not from a string the expression builds (seat ruling on F1).'",
      "Tests: add 'E7 (×2): a computed `user:ADDRESS` and `user:user:ADDRESS` no user holds under `channels: email` fail the node, nothing enqueued or mailed. E8: a computed `user:` plus a user's address resolves to that user.'; readings line: head d4fd5968, the file 23/23, service-automation 202/202 files (2659 tests), example-showcase 39/39, plugin-approvals 96/96, dogfood 295 files / 2,520 tests passed, 0 failed; ablation table: add row 'the `user:` branch (old prefix refusal) | E7 ×2, E8'.",
      "Acceptance notes: the line saying check:dual-build-cjs-loads and check:skill-examples are NOT MEASURED locally is stale; both exit 0 after a full build (450832fb and d4fd5968)."
     ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    PR #25721 merged at 2026-10-25T04:41:00Z by the merge queue as squash d1e329e96d on main (seat ACCEPT 6392365089; contract review FAIL 6392441398 on 450832fb, then PASS 6392736321 on head d4fd5968). It closes #25571 and #25647.

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/*' for pr-25721 0 refs (left the queue)
    origin/main by content git grep "plain ASCII email address" origin/main -- packages/services/service-automation/src/builtin/notify-node.ts line 466 (the patch round's refusal text)
    origin/main by content notify-email-field-recipient.integration.test.ts present

    State: both cards closed by Fixes (completed); pm:dispatched removed from both (2026-10-25T04:42Z). Open after landing:


    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:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions