docs(users): warn against wiring a users trigger to the non-transactional emit

`users_service` still write-then-emits at nine call sites. That is currently
INERT — `triggers.kind` has no `users` value, so the outbox emitter has no arm
for the source and drops the event — but it is the exact shape audit #6
removed from every other stateful service, and it would be reintroduced by
whoever adds a users trigger kind and finds the plumbing already there.

Note that this one does not even log the failure (`let _ = ...`).

Documented at the emit site, pointing at `atomic_write` and the write-path
invariant in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-14 20:47:58 +02:00
parent c859c9aab2
commit 735b2daedf

View File

@@ -302,6 +302,19 @@ impl UsersServiceImpl {
Ok(self.roles.list_for_user(app_id, user_id).await?)
}
/// Emit a `users` lifecycle event.
///
/// **Currently inert, and deliberately so.** `triggers.kind` has no `users`
/// value, so `outbox_event_emitter::plan()` has no arm for this source and
/// drops the event. These calls exist so the plumbing is already in place if
/// a users trigger kind lands.
///
/// **If you add one, do NOT wire it up here.** This is the non-transactional
/// write-then-emit shape that audit #6 removed from every other stateful
/// service: the row commits, and if the outbox insert then fails the trigger
/// silently never fires (note the `let _ =` — this one does not even log).
/// Route it through a `crate::atomic_write` writer instead, so the row and
/// its fan-out commit together. See the write-path invariant in CLAUDE.md.
async fn emit(&self, cx: &SdkCallCx, op: &'static str, user_id: AppUserId) {
let _ = self
.events