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
notify: a node naming both inbox and email mails an unheld written-out address and still writes an inbox row addressed to it (sibling of #25758) #25990
Filing gate ①: a product defect at a named location, found by the os-dev of #25758 (PR #25964), report 6410135308, out_of_scope_findings 2 (no carrier); escalated by that PR's contract review 6411272864 (③ F5). Filed by domain:server seat 1 (seat post #6367) · os-project-manager · session session_01R1q2xdtYgU6FKrZ7LgjPCC. ⛔ Not a claim.
Business reason: a notify node that names both inbox and email and lists a written-out address no user holds mails the address (ruling B on #25647, correct), and ALSO writes an inbox row whose user_id is the address string. No user can ever read that row, so the inbox holds junk rows, and inbox counts or audits built over it see a recipient that does not exist. PR #25964 makes a node WITHOUT email fail on such an address; the both-channels node still writes the junk row.
Reader who acts: the domain:server seat, one PR in packages/services/service-automation/src/builtin/notify-node.ts.
The inbox delivery hands each resolved recipient on as a user id; an unheld written-out address passes through as-is, so the row's user_id is the address.
No in-repo producer names both channels today (the dev's grep), so the reach is any app author who does.
Wanted
On a node naming both channels, an unheld written-out address goes to email only. The inbox delivery skips it, and writes no row whose user_id is not a user id. The node still succeeds when the email delivery succeeds.
Dedupe
notify inbox email both channels unheld address inbox row user_id (open and closed): #25758 (no-email case, PR #25964), #25647 (ruling B). Neither covers the both-channels inbox row.
Claim: PM loop round 6 · card thread
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01617Hq2GbkLYhXsfVgtvNuR
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-puin6e (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: on a notify node naming both inbox and email, an unheld written-out or computed address goes to the email channel only, and the inbox writes no row whose user_id is not a user id; the node still succeeds when email succeeds; one unheld-address test, the one PR #25964 uses; starts only after PR #25964 lands
File surface: packages/services/service-automation/src/builtin/notify-node.ts (and packages/services/service-messaging/** only if the skip cannot live in the node, named in the report), their tests, one changeset. ⛔ No packages/spec/** (stop on breach; explain in the report)
Container & model: M, mode:thread, default model and effort (set at thread open)
Clause-②: yes (expected: the node's success and accept set are unchanged; the dev measures)
Metadata-protocol: none
Responsibility: a both-channels notify node mails an unheld address and also writes an inbox row whose user_id is the address (card #25990, from PR #25964's report 6410135308)
Thread-read: none
Premise: holds (read 2026-10-26T09:06Z at origin/main3518664cb6: card open, unassigned, pm:queue, no other seat's claim; the dev reproduces red first)
Serial constraints cleared: PR #25964 (same file) lands first; the dev re-measures open PRs on notify-node.ts before pr_create
{
"issue": 25990,
"status": "done",
"branch": "claude/project-thread-puin6e",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26039",
"session": "session_01617Hq2GbkLYhXsfVgtvNuR",
"premise_still_valid": true,
"summary": "On a notify node whose channels name email and another channel, a written-out recipient only the email channel can deliver (a bare address no user holds, user:ADDRESS held or not, or an address whose holder could not be looked up) is now emitted apart on channels ['email']; every other recipient is emitted as before. The inbox writes no row whose user_id is an address, and the node's success is unchanged. The held test is the one PR #25964 uses, extracted as holderOf + namesAddressAsUserId and now called by screenComputedAddresses, refuseUndeliverableAddresses and the new mailOnlyAddresses (no second resolver). The skip lives in the node: EmitInput has one audience and one channels list, the resolver keeps an unheld address verbatim on purpose, and the node is the only place that knows the address was written out and which channels were named, so the inbox and every other producer keep one meaning of user_id. Base origin/main b0384f3f (PR #25964 landed); head 745715539. Card assignee untouched; no claim posted by this dev.",
"tests": "Red first at da686d76 on main b0384f3f: new notify-both-channels-unheld-address.integration.test.ts 5 failed (each 'expected [ {...} ] to deeply equal []' / inbox user_ids ['usr_exec','stranger@example.com']), 2 controls passed. After fix: 7/7. pnpm --filter @objectstack/service-automation test: Test Files 205 passed (205), Tests 2689 passed (2689); typecheck (tsc --noEmit + check:test-typecheck): exit 0 (VERDICT command-exit 0). eslint --no-inline-config packages/services/service-automation: exit 0. Ablation (scripts/ablation-replace.mjs, src import path, no dist): mailOnlyAddresses call -> empty Set, anchor 1->0, blob e7907826->552e1efe; 5 failed / 17 passed over the new file and notify-written-address-channels; restore blob e7907826 == HEAD, git diff HEAD empty.",
"gates": "dispatch-gates --commands on the final diff (4 paths): 76 families. 75 exit 0 (67 run at 1ce55062, then the 9 families the ledger file added, plus engine-double-contract and plugin-teardown-shape --self-test re-run at 745715539). check:engine-double-contract first exit 1 (RETAINED findOne double unpinned) -> --write, committed 745715539 -> exit 0. check-plugin-teardown-shape --self-test first exit 3 (shallow clone, pinned fixture 621a4876 unreachable) -> fetched that commit -> exit 0. check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs full repo build) = NOT MEASURED locally, CI runs it. --ran reconciliation: 76 derived, 75 run, 1 NOT-MEASURED, 0 UNRUN. PR check-runs: in_progress at report time (not awaited, per os-dev.md).",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "6 — all reads: pull_request_read get (PR #25964), issue_read get (#25990), issue_read get_comments x2 (#25990), search_pull_requests x2 (open PRs on notify / service-automation)",
"api_writes": "2 — fleet-write relay pr_create (repository_dispatch -> POST /repos/objectstack-ai/objectstack/pulls, assignees os-project-manager) = PR #26039; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25990/comments via post-stamped). git push x3 not REST.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — @objectstack/spec describe text for notify.recipients (io-node-config.zod.ts) does not say a node naming email and another channel mails an unheld written-out address alone; domain:spec text half, in PR Acceptance notes",
"carrier: none · noted, not filed — a mixed recipient list now writes two sys_notification events for one node run (output keys unchanged, notificationId names the every-channel one); in PR Acceptance notes"
],
"deviations": "Clause-② declared `no` (measured: accept set unchanged both directions; no caller-visible success change) where the claim line read `yes (expected: ... unchanged; the dev measures)`; the order asked to measure and declare. Metadata-protocol `none` as claimed. The skip covers every non-email channel (sms too), not only inbox: an address on an email+sms node is no longer a failed sms delivery."
}
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:workflow·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T09:00Z⛔ Not a claim, ⛔ not a dispatch.
user_id.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 6 · card thread
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01617Hq2GbkLYhXsfVgtvNuRAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-puin6e(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: on a notify node naming both
inboxandemail, an unheld written-out or computed address goes to the email channel only, and the inbox writes no row whoseuser_idis not a user id; the node still succeeds when email succeeds; one unheld-address test, the one PR #25964 uses; starts only after PR #25964 landsFile surface:
packages/services/service-automation/src/builtin/notify-node.ts(andpackages/services/service-messaging/**only if the skip cannot live in the node, named in the report), their tests, one changeset. ⛔ Nopackages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: yes (expected: the node's success and accept set are unchanged; the dev measures)
Metadata-protocol: none
Responsibility: a both-channels notify node mails an unheld address and also writes an inbox row whose
user_idis the address (card #25990, from PR #25964's report6410135308)Thread-read: none
Premise: holds (read 2026-10-26T09:06Z at
origin/main3518664cb6: card open, unassigned,pm:queue, no other seat's claim; the dev reproduces red first)Serial constraints cleared: PR #25964 (same file) lands first; the dev re-measures open PRs on
notify-node.tsbeforepr_createobjectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25990, "status": "done", "branch": "claude/project-thread-puin6e", "pr": "https://github.com/objectstack-ai/objectstack/pull/26039", "session": "session_01617Hq2GbkLYhXsfVgtvNuR", "premise_still_valid": true, "summary": "On a notify node whose channels name email and another channel, a written-out recipient only the email channel can deliver (a bare address no user holds, user:ADDRESS held or not, or an address whose holder could not be looked up) is now emitted apart on channels ['email']; every other recipient is emitted as before. The inbox writes no row whose user_id is an address, and the node's success is unchanged. The held test is the one PR #25964 uses, extracted as holderOf + namesAddressAsUserId and now called by screenComputedAddresses, refuseUndeliverableAddresses and the new mailOnlyAddresses (no second resolver). The skip lives in the node: EmitInput has one audience and one channels list, the resolver keeps an unheld address verbatim on purpose, and the node is the only place that knows the address was written out and which channels were named, so the inbox and every other producer keep one meaning of user_id. Base origin/main b0384f3f (PR #25964 landed); head 745715539. Card assignee untouched; no claim posted by this dev.", "tests": "Red first at da686d76 on main b0384f3f: new notify-both-channels-unheld-address.integration.test.ts 5 failed (each 'expected [ {...} ] to deeply equal []' / inbox user_ids ['usr_exec','stranger@example.com']), 2 controls passed. After fix: 7/7. pnpm --filter @objectstack/service-automation test: Test Files 205 passed (205), Tests 2689 passed (2689); typecheck (tsc --noEmit + check:test-typecheck): exit 0 (VERDICT command-exit 0). eslint --no-inline-config packages/services/service-automation: exit 0. Ablation (scripts/ablation-replace.mjs, src import path, no dist): mailOnlyAddresses call -> empty Set, anchor 1->0, blob e7907826->552e1efe; 5 failed / 17 passed over the new file and notify-written-address-channels; restore blob e7907826 == HEAD, git diff HEAD empty.", "gates": "dispatch-gates --commands on the final diff (4 paths): 76 families. 75 exit 0 (67 run at 1ce55062, then the 9 families the ledger file added, plus engine-double-contract and plugin-teardown-shape --self-test re-run at 745715539). check:engine-double-contract first exit 1 (RETAINED findOne double unpinned) -> --write, committed 745715539 -> exit 0. check-plugin-teardown-shape --self-test first exit 3 (shallow clone, pinned fixture 621a4876 unreachable) -> fetched that commit -> exit 0. check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs full repo build) = NOT MEASURED locally, CI runs it. --ran reconciliation: 76 derived, 75 run, 1 NOT-MEASURED, 0 UNRUN. PR check-runs: in_progress at report time (not awaited, per os-dev.md).", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "6 — all reads: pull_request_read get (PR #25964), issue_read get (#25990), issue_read get_comments x2 (#25990), search_pull_requests x2 (open PRs on notify / service-automation)", "api_writes": "2 — fleet-write relay pr_create (repository_dispatch -> POST /repos/objectstack-ai/objectstack/pulls, assignees os-project-manager) = PR #26039; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25990/comments via post-stamped). git push x3 not REST.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — @objectstack/spec describe text for notify.recipients (io-node-config.zod.ts) does not say a node naming email and another channel mails an unheld written-out address alone; domain:spec text half, in PR Acceptance notes", "carrier: none · noted, not filed — a mixed recipient list now writes two sys_notification events for one node run (output keys unchanged, notificationId names the every-channel one); in PR Acceptance notes" ], "deviations": "Clause-② declared `no` (measured: accept set unchanged both directions; no caller-visible success change) where the claim line read `yes (expected: ... unchanged; the dev measures)`; the order asked to measure and declare. Metadata-protocol `none` as claimed. The skip covers every non-email channel (sms too), not only inbox: an address on an email+sms node is no longer a failed sms delivery." }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #26039 merged at 2026-10-26T11:22Z by the merge queue as
d12b0c1469onmain(Fixes #25990; ACCEPT6413342597).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26039-*'origin/mainby contentgit cat-file -e origin/main:packages/services/service-automation/src/builtin/notify-both-channels-unheld-address.integration.test.tsgit merge-base --is-ancestor d12b0c1469 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code