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
comments: an @mention by email is matched case-sensitively, so a differently-cased address is accepted 201 and silently never notified #27503
Filing gate: ① a product defect with a reproduction, found by an off-clause probe of QA run #27438 (wave 3 of the 18.3.0 sweep, anchor #27377). reach: measured at the public comment door. The mention is accepted with 201, but no notification is sent.
Business reason: a user who @-mentions a colleague by email address and types it in a different case, or pastes it from a directory that capitalises it, gets no error, and the colleague is never notified. Email addresses compare case-insensitively everywhere else in identity, sign-in included. The comment reads as if the mention worked.
Reader who acts: the triage seat, then the implementer for mention resolution (packages/plugins/plugin-audit, the sys_comment mention notifier).
Dedupe (open AND closed): the query "mention by email case sensitive unknown_email notification withheld" returned 0 hits.
Have a member whose email is g4b.member@example.com and who can read the record's thread.
POST /api/v1/data/sys_comment {"thread_id":"showcase_account:ID","body":"x","mentions":["G4B.MEMBER@EXAMPLE.COM"]} → 201.
The member receives no notification. The server log reads [audit] mention notification withheld {reason: unknown_email}.
The same request with g4b.member@example.com delivers the notification.
Expected: an email mention matches case-insensitively, the same way sign-in does.
Also noticed, diagnostic only:mentions: ["nosuchuser123"] (an unknown user id) is logged as cannot_read_thread instead of as an unknown user, which misleads anyone reading the log.
QA-source: #27438 · records-forms.record-discussion-mentions · observed beside the clauses
Read fresh. An @mention by email is matched case-sensitively, so a differently-cased address is accepted 201 and the colleague is silently never notified. A silent drop of an authored value: p2.
Compare mention emails with the same case-insensitive normalisation identity and sign-in already use. No second email matcher.
Claim: PM loop round 1 · 2026-10-29T21:22Z
Session: session_016WV4czuTWNgKR22fKv7jia (seat; mode:subagent, the dev shares the seat's session — identity by branch)
Account: os-elon-musk
Branch: claude/project-thread-rcp68o
Worktree: objectstack-issue-27503
Domain: domain:server
Seat: domain:server#3
File surface: packages/plugins/plugin-audit/src/mention-recipient-gate.ts (resolveMentionedUser) and its tests, .changeset/*; ⛔ no packages/spec, no governed path (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: plugin-audit's mention gate matches the email exactly; the platform path that covers it is the case-insensitive email normalisation identity and sign-in already use (triage 6495269327: no second email matcher); reached by any comment author who types a differently-cased address.
Thread-read: 6495269327
Premise: holds (where: { email: value } exact match on origin/maind93d9be5)
Serial constraints cleared: none — no open PR or in-flight claim on plugin-audit/src/mention-recipient-gate.ts found at claim time.
{
"issue": 27503,
"status": "done",
"branch": "claude/project-thread-rcp68o",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27751",
"head_sha": "e711910983d66be94059b99aa485bff340c4a016",
"session": "session_016WV4czuTWNgKR22fKv7jia",
"premise_still_valid": true,
"summary": "The premise was measured and holds: on d93d9be5, resolveMentionedUser in plugin-audit's mention-recipient-gate.ts looked an email mention up with where: { email: value }, an exact match, and a differently-cased address answered unknown_email. Stored emails are normalised: better-auth internal-adapter createUser/updateUser write email.toLowerCase(), and sign-in queries the lowercased address (better-auth /sign-in/email findUserByEmail(email.toLowerCase()); plugin-auth findSignInAccount where: { email: email.toLowerCase() }). The fix reuses the existing shared normaliser normalizePlatformAdminEmail from @objectstack/core (trim, then lowercase; its docblock calls it the ONE normalization) on the query value, so a mention names the same user sign-in would; no second matcher was added. The integration test gained two cases, one through the real afterInsert writer and one at the log seam, plus a patch changeset for @objectstack/plugin-audit.",
"tests": "Red before the fix (closure built): pnpm --filter @objectstack/plugin-audit exec vitest run --maxWorkers=2 src/mention-recipient-gate.integration.test.ts -> 2 failed | 15 passed (17); AssertionError: expected [] to deeply equal [ [ 'u_carol' ] ]. Green after: 17 passed (17), VERDICT command-exit 0. Ablation (fix committed at 5febd38e; scripts/ablation-replace.mjs; the suite aliases @objectstack/core and imports this package's src, so there is no dist leg): mutation landed (anchor 1 -> 0, blob 8f07337134ce -> e29091174dd9) -> 2 failed | 15 passed (17); restored (blob == HEAD 8f07337134ce, git diff HEAD empty). On head e7119109: pnpm --filter @objectstack/plugin-audit test -> Test Files 65 passed (65), Tests 1051 passed (1051); pnpm --filter @objectstack/plugin-audit typecheck -> exit 0 (check:test-typecheck OK, 0 debt); eslint --no-inline-config on the two changed .ts files -> exit 0, no output. test:repo: not run locally (diff touches no repo-project test), declared to CI Test Core.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on head e7119109: 61 commands, all run with exit codes captured before any pipe. 58 exited 0 on the first pass. 3 exited 3 (PREREQUISITE NOT MET) and each was re-run green once its prerequisite was met: check-plugin-teardown-shape --self-test (fetched the pinned fixture commit 621a4876; 48 cases), check:i18n (built its stated closure; 10 packages in sync), check:dual-build-cjs-loads (full pnpm build, 114 tasks; 112 entries across 69 packages). dispatch-gates --ran: 61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED. --ran flagged the tree as 4 commits behind a re-fetched origin/main; the only stale family file is scripts/check-route-envelope.mjs, which is not in this diff's derivation. CI families: PR check-runs in_progress at report time (not awaited, per contract).",
"line_budget": "41 changed lines (+40 / -1) across 3 files, under the 3000 human-merge threshold; no skills/** touched, so no line ratchet applies",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"files_changed": [
"packages/plugins/plugin-audit/src/mention-recipient-gate.ts",
"packages/plugins/plugin-audit/src/mention-recipient-gate.integration.test.ts",
".changeset/27503-mention-email-case-insensitive.md"
],
"deviations": [
"The dispatch asked for pr_create with assignees: []. The relay validator refused an empty list ('actions[0].assignees must name at least one item'), and os-dev.md forbids assignees on a PR, so the key was dropped. The read-back reports no assignees.",
"The PR body ends with the dispatch's footer ('Generated with Claude Code' plus the session URL) and not the AGENTS.md session-URL footer block. The dispatch's wording was followed, and the conflict is reported here.",
"The branch is a platform-named card-thread branch, so no empty-branch probe push was made (os-dev rule 1). It was pushed once, after the fix and the single merge of origin/main."
],
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments); 0 write tools",
"api_writes": "2 relay writes as objectstack-fleet[bot]: pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, run 40964865860, read-back 6418/6418 bytes identical) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27503/comments); plus 1 git push (not REST)",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — the card's diagnostic-only observation that an unknown user id (mentions: [nosuchuser123]) is logged as cannot_read_thread rather than as an unknown user. It is log-text only, nobody is wrongly notified, and it is outside the three filing classes. Recorded in the PR Acceptance notes.",
"carrier: none · noted, not filed — the shared email normaliser is named normalizePlatformAdminEmail although its docblock calls it the one email normalization. This is naming only. Dedupe words: email normaliser name, normalizeEmail core, platform admin email helper."
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 业务对象与记录 | records-forms.record-discussion-mentions | P②
Triage: p2 ·
domain:server·area:records· 2026-10-29T11:20Z⛔ Not a claim.
bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
Claim: PM loop round 1 · 2026-10-29T21:22Z
Session:
session_016WV4czuTWNgKR22fKv7jia(seat;mode:subagent, the dev shares the seat's session — identity by branch)Account:
os-elon-muskBranch:
claude/project-thread-rcp68oWorktree:
objectstack-issue-27503Domain:
domain:serverSeat:
domain:server#3File surface:
packages/plugins/plugin-audit/src/mention-recipient-gate.ts(resolveMentionedUser) and its tests,.changeset/*; ⛔ nopackages/spec, no governed path (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
plugin-audit's mention gate matches the email exactly; the platform path that covers it is the case-insensitive email normalisation identity and sign-in already use (triage 6495269327: no second email matcher); reached by any comment author who types a differently-cased address.Thread-read: 6495269327
Premise: holds (
where: { email: value }exact match onorigin/maind93d9be5)Serial constraints cleared: none — no open PR or in-flight claim on
plugin-audit/src/mention-recipient-gate.tsfound at claim time.objectstack-fleet commentedon Oct 29, 2026
os-dev-report
{ "issue": 27503, "status": "done", "branch": "claude/project-thread-rcp68o", "pr": "https://github.com/objectstack-ai/objectstack/pull/27751", "head_sha": "e711910983d66be94059b99aa485bff340c4a016", "session": "session_016WV4czuTWNgKR22fKv7jia", "premise_still_valid": true, "summary": "The premise was measured and holds: on d93d9be5, resolveMentionedUser in plugin-audit's mention-recipient-gate.ts looked an email mention up with where: { email: value }, an exact match, and a differently-cased address answered unknown_email. Stored emails are normalised: better-auth internal-adapter createUser/updateUser write email.toLowerCase(), and sign-in queries the lowercased address (better-auth /sign-in/email findUserByEmail(email.toLowerCase()); plugin-auth findSignInAccount where: { email: email.toLowerCase() }). The fix reuses the existing shared normaliser normalizePlatformAdminEmail from @objectstack/core (trim, then lowercase; its docblock calls it the ONE normalization) on the query value, so a mention names the same user sign-in would; no second matcher was added. The integration test gained two cases, one through the real afterInsert writer and one at the log seam, plus a patch changeset for @objectstack/plugin-audit.", "tests": "Red before the fix (closure built): pnpm --filter @objectstack/plugin-audit exec vitest run --maxWorkers=2 src/mention-recipient-gate.integration.test.ts -> 2 failed | 15 passed (17); AssertionError: expected [] to deeply equal [ [ 'u_carol' ] ]. Green after: 17 passed (17), VERDICT command-exit 0. Ablation (fix committed at 5febd38e; scripts/ablation-replace.mjs; the suite aliases @objectstack/core and imports this package's src, so there is no dist leg): mutation landed (anchor 1 -> 0, blob 8f07337134ce -> e29091174dd9) -> 2 failed | 15 passed (17); restored (blob == HEAD 8f07337134ce, git diff HEAD empty). On head e7119109: pnpm --filter @objectstack/plugin-audit test -> Test Files 65 passed (65), Tests 1051 passed (1051); pnpm --filter @objectstack/plugin-audit typecheck -> exit 0 (check:test-typecheck OK, 0 debt); eslint --no-inline-config on the two changed .ts files -> exit 0, no output. test:repo: not run locally (diff touches no repo-project test), declared to CI Test Core.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on head e7119109: 61 commands, all run with exit codes captured before any pipe. 58 exited 0 on the first pass. 3 exited 3 (PREREQUISITE NOT MET) and each was re-run green once its prerequisite was met: check-plugin-teardown-shape --self-test (fetched the pinned fixture commit 621a4876; 48 cases), check:i18n (built its stated closure; 10 packages in sync), check:dual-build-cjs-loads (full pnpm build, 114 tasks; 112 entries across 69 packages). dispatch-gates --ran: 61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED. --ran flagged the tree as 4 commits behind a re-fetched origin/main; the only stale family file is scripts/check-route-envelope.mjs, which is not in this diff's derivation. CI families: PR check-runs in_progress at report time (not awaited, per contract).", "line_budget": "41 changed lines (+40 / -1) across 3 files, under the 3000 human-merge threshold; no skills/** touched, so no line ratchet applies", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "files_changed": [ "packages/plugins/plugin-audit/src/mention-recipient-gate.ts", "packages/plugins/plugin-audit/src/mention-recipient-gate.integration.test.ts", ".changeset/27503-mention-email-case-insensitive.md" ], "deviations": [ "The dispatch asked for pr_create with assignees: []. The relay validator refused an empty list ('actions[0].assignees must name at least one item'), and os-dev.md forbids assignees on a PR, so the key was dropped. The read-back reports no assignees.", "The PR body ends with the dispatch's footer ('Generated with Claude Code' plus the session URL) and not the AGENTS.md session-URL footer block. The dispatch's wording was followed, and the conflict is reported here.", "The branch is a platform-named card-thread branch, so no empty-branch probe push was made (os-dev rule 1). It was pushed once, after the fix and the single merge of origin/main." ], "mcp_calls": "2 — mcp__github__issue_read (get, get_comments); 0 write tools", "api_writes": "2 relay writes as objectstack-fleet[bot]: pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, run 40964865860, read-back 6418/6418 bytes identical) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27503/comments); plus 1 git push (not REST)", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — the card's diagnostic-only observation that an unknown user id (mentions: [nosuchuser123]) is logged as cannot_read_thread rather than as an unknown user. It is log-text only, nobody is wrongly notified, and it is outside the three filing classes. Recorded in the PR Acceptance notes.", "carrier: none · noted, not filed — the shared email normaliser is named normalizePlatformAdminEmail although its docblock calls it the one email normalization. This is naming only. Dedupe words: email normaliser name, normalizeEmail core, platform admin email helper." ] }objectstack-fleet commentedon Oct 29, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
ACCEPT → PR #27751 comment 6510146711 · 2026-10-29T22:40Z · ready + auto-merge armed through the relay.