Repository navigation
Commit b2b4972
fix(plugin-audit): match an @mention email case-insensitively (#27751)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DNCXw1AehBds7qLHmy85L4E)_
Fixes #27503
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:评论里按邮箱 @提及同事时,邮箱改为不区分大小写匹配(先去首尾空格再转小写),与登录查找用户的方式一致。
- 为什么改:以前按原样精确匹配,大小写不同的地址(例如 `Carol@Example.com`)评论照样 201
成功,但被提及的人永远收不到通知,作者也看不到任何报错。
- 风险与回滚:风险低,只改了一处查询条件;谁能收到通知仍由记录与评论的读权限决定,没有放宽。回滚即 revert 本 PR。
- 先看:`packages/plugins/plugin-audit/src/mention-recipient-gate.ts` 里
`resolveMentionedUser` 的 `where` 那一行;然后看
`mention-recipient-gate.integration.test.ts` 新增的两个用例。
- 两分钟验证:`pnpm --filter @objectstack/plugin-audit exec vitest run
src/mention-recipient-gate.integration.test.ts`,预期 Tests 17 passed (17)。
- 需要你做的:无,席位落地。
## What changes
`resolveMentionedUser` (plugin-audit's @mention recipient gate) looked
an email-shaped mention up with `where: { email: value }`, an exact
match on `sys_user.email`. Identity stores that column lowercased, so
`G4B.MEMBER@EXAMPLE.COM` resolved nobody. The comment was still accepted
with 201, and the gate logged `mention notification withheld` with
reason `unknown_email`.
The lookup now passes the address through `normalizePlatformAdminEmail`
from `@objectstack/core`. That is the one exported email normalisation
in core (trim, then lowercase), and it is already the comparison the
platform-admin derivation and the Layer 0 wall use. For an address that
has already been trimmed, it gives exactly the spelling sign-in queries:
- `findSignInAccount` (plugin-auth) queries `where: { email:
email.toLowerCase() }`;
- better-auth's `/sign-in/email` calls
`internalAdapter.findUserByEmail(email.toLowerCase())`, and `createUser`
/ `updateUser` write `email.toLowerCase()`, so stored addresses are
already normalised.
No second matcher is added. Who may be notified does not change: the
record read and the comment read in the mentioned user's own envelope
still decide.
## Tests
`packages/plugins/plugin-audit/src/mention-recipient-gate.integration.test.ts`
(real kernel, ObjectQL, SQLite driver, AuditPlugin) gets two new cases:
- **isolated:** a comment mentioning `Carol@A.Test`, and one mentioning
` CAROL@A.TEST ` with spaces around it, each notify the user stored as
`carol@a.test`. Both go through the real `afterInsert` mention writer.
- **withheld-recipient log:** `admitMentionRecipients` with
`CAROL@A.TEST` admits CAROL and logs nothing, so there is no
`unknown_email`.
## Acceptance notes
- **Normaliser name.** `normalizePlatformAdminEmail` carries a
platform-admin name, but its docblock calls it the one email
normalisation, and it is the only exported one in `@objectstack/core`.
Renaming it is a naming question only, so no card was filed.
- **Rows outside identity.** A `sys_user` row written with a mixed-case
email outside the identity write paths (for example a raw system-context
insert) cannot be found by sign-in. After this change it cannot be found
by a mention either, so a mention now reaches exactly the users sign-in
reaches.
- **Unknown user ids.** The card's diagnostic-only note says an unknown
user id (`mentions: ["nosuchuser123"]`) is logged as
`cannot_read_thread`. It is unchanged here: it affects only the log
text, and nobody is notified who should not be. This is an observation,
not filed.
- **Fresh `origin/main`.** `origin/main` moved after the single merge
(to `34dc09b68`). The only family file that changed in that range is
`scripts/check-route-envelope.mjs`, and it is not one this diff derives.
CI runs on the merged head.
## Verification
All readings are on head `e7119109` (fix commit `5febd38e` plus one
merge of `origin/main` `ef359b95`).
<details><summary>Red before the fix (built closure, test
only)</summary>
```
× resolves an email mention whatever its casing, the way sign-in finds the user
× logs no unknown_email for a differently-cased address of a stored user
AssertionError: expected [] to deeply equal [ [ 'u_carol' ] ]
Tests 2 failed | 15 passed (17)
```
</details>
<details><summary>Green after the fix</summary>
```
Test Files 1 passed (1)
Tests 17 passed (17)
os-verify-lock: VERDICT command-exit 0
```
</details>
<details><summary>Ablation (fix committed first;
scripts/ablation-replace.mjs; suite resolves core and this package from
src, so no dist leg)</summary>
```
ablation-replace: ok mutation landed: anchor 1 -> 0, blob 8f07337 -> e29091174dd9
× resolves an email mention whatever its casing, the way sign-in finds the user
× logs no unknown_email for a differently-cased address of a stored user
Tests 2 failed | 15 passed (17)
ablation-replace: ok restored: blob == HEAD (8f07337) and `git diff HEAD` is empty
```
</details>
<details><summary>Package test, typecheck, lint (head
e711910)</summary>
```
pnpm --filter @objectstack/plugin-audit test -> Test Files 65 passed (65) · Tests 1051 passed (1051) · VERDICT command-exit 0
pnpm --filter @objectstack/plugin-audit typecheck -> VERDICT command-exit 0 (check:test-typecheck OK, 0 debt)
eslint --no-inline-config (the two changed .ts files) -> exit 0, no output
```
</details>
<details><summary>Derived gate families (dispatch-gates --commands, 61
commands, head e711910)</summary>
```
61 run, 61 exit 0
dispatch-gates --ran: 61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED
```
On the first pass three commands exited 3 (PREREQUISITE NOT MET, nothing
was measured). Each was re-run after its prerequisite was met:
- `check-plugin-teardown-shape --self-test`: fetched the pinned fixture
commit, then exit 0 (48 cases).
- `check:i18n`: built its stated closure, then exit 0 (10 packages in
sync).
- `check:dual-build-cjs-loads`: full `pnpm build` (114 tasks), then exit
0 (112 entries across 69 packages).
The PR's check-runs cover all other CI families.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4czuTWNgKR22fKv7jia
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6d9419b commit b2b4972
3 files changed
Lines changed: 40 additions & 1 deletion
File tree
- .changeset
- packages/plugins/plugin-audit/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
279 | 279 | | |
280 | 280 | | |
281 | 281 | | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
282 | 289 | | |
283 | 290 | | |
284 | 291 | | |
| |||
367 | 374 | | |
368 | 375 | | |
369 | 376 | | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
370 | 391 | | |
371 | 392 | | |
372 | 393 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| |||
254 | 255 | | |
255 | 256 | | |
256 | 257 | | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
257 | 265 | | |
258 | 266 | | |
259 | 267 | | |
| |||
263 | 271 | | |
264 | 272 | | |
265 | 273 | | |
266 | | - | |
| 274 | + | |
267 | 275 | | |
268 | 276 | | |
269 | 277 | | |
| |||
0 commit comments