fix(routing): per-recipient checks via the alerts service principal #46

Merged
KofTwentyTwo merged 2 commits from feature/service-principal into develop 2026-10-16 18:33:28 +00:00
KofTwentyTwo commented 2026-10-16 13:30:08 +00:00 (Migrated from github.com)

Part of the service-identity work, QRun-IO/qqq#1248. Draft: it needs the QQQ RC that contains qqq#1255 and #1256. Bump qqq.snapshot.version once the release captain cuts it.

Problem: under OAuth2, AlertRouter.knownVisibility / canReadRecord and AlertsAudienceResolver used createAutomatedSessionForUser. Those sessions are token-less and rejected, so the checks always fell to the conservative branch:

  • in-app sends only;
  • external sends only for alerts with no permission or subject;
  • followers dropped.

Fix: AlertsSecurity.recipientSession.

  • When the host declares the alerts principal (AlertsQBitConfig.servicePrincipal), the checks run on the recipient's behalf through it: the recipient's current access intersected with the principal's. Record locks still apply.
  • With no principal declared, the old path is kept.

Deliberate deviation from the design table (section 6.1): alert recording and routing bookkeeping (AlertsSecurity.systemSession for the sink, monitors and router) stay on the system session. If they ran as a principal, a failed or slow identity provider would also stop alerting, including the service_identity.unavailable alert itself. The bookkeeping touches only alerts tables. What reaches recipients is decided by the delegated sessions.

Tests:

  • AlertsServicePrincipalTest: recipient sessions before and after declaring the principal.
  • Full suite: 229, 0 failures, against a local install of qqq#1256.

🤖 Generated with Claude Code

Part of the service-identity work, QRun-IO/qqq#1248. **Draft:** it needs the QQQ RC that contains qqq#1255 and #1256. Bump `qqq.snapshot.version` once the release captain cuts it. **Problem:** under OAuth2, `AlertRouter.knownVisibility` / `canReadRecord` and `AlertsAudienceResolver` used `createAutomatedSessionForUser`. Those sessions are token-less and rejected, so the checks always fell to the conservative branch: - in-app sends only; - external sends only for alerts with no permission or subject; - followers dropped. **Fix:** `AlertsSecurity.recipientSession`. - When the host declares the `alerts` principal (`AlertsQBitConfig.servicePrincipal`), the checks run on the recipient's behalf through it: the recipient's current access intersected with the principal's. Record locks still apply. - With no principal declared, the old path is kept. **Deliberate deviation from the design table (section 6.1):** alert recording and routing bookkeeping (`AlertsSecurity.systemSession` for the sink, monitors and router) stay on the system session. If they ran as a principal, a failed or slow identity provider would also stop alerting, including the `service_identity.unavailable` alert itself. The bookkeeping touches only alerts tables. What reaches recipients is decided by the delegated sessions. **Tests:** - `AlertsServicePrincipalTest`: recipient sessions before and after declaring the principal. - Full suite: 229, 0 failures, against a local install of qqq#1256. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.