Repository navigation
ref(workflow_engine): Ask has_user() for the detector and workflow creator - #129150
Merged
Merged
Conversation
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
2 times, most recently
from
October 29, 2026 18:30
7a2aa22 to
ed3ea12
Compare
This was referenced Oct 29, 2026
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 29, 2026 18:49
ed3ea12 to
e170fd2
Compare
Contributor
Sentry Snapshot Testing
|
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 29, 2026 22:27
e170fd2 to
0dda54f
Compare
gricha
changed the base branch from
ref/request-principal-interface
to
ref/sa-user-id-dashboards
October 29, 2026 22:27
This was referenced Oct 29, 2026
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 29, 2026 22:39
0dda54f to
9ed6241
Compare
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 30, 2026 08:56
e9915ef to
0aa1878
Compare
azulus
approved these changes
Oct 30, 2026
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 30, 2026 18:10
0aa1878 to
030ad41
Compare
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
Adds `acting_user_id(principal)` in `sentry.auth.principal`: the user behind whatever sits on `request.user`, or None. A user gives its own id, an agent the user it acts for, anyone else None. `RequestPrincipal` is the interface every caller shares; Django's `AnonymousUser` is patched at startup with the two members it lacks (`is_sentry_app`, `get_display_name`), and the mypy plugin declares them. The goal of the stack is that endpoints work for users, agents and service accounts by default, without each one checking which it got. A service account must never carry a user id, so `acting_user_id()` is the one question every caller can answer. It also starts a short "Request Principals" section in `src/AGENTS.md`; each PR above extends it with what it adds. It also adds the rest of the vocabulary, so every PR above can use it: `has_user(principal)` narrows to `User | RpcUser` when a user is behind the caller (the user, or an agent acting for one), and `UserRequired` is the 403 `user-required` for code that needs one. Nothing reads them yet. **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
2 times, most recently
from
October 30, 2026 18:39
4e39478 to
ce6a6d2
Compare
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
) Adds `set_request_principal(request, user, auth)` and routes every place in `src` that sets `request.user` or `request.auth` through it. That covers the auth middleware, DRF authentication (`Request._authenticate` is wrapped), the login/logout re-syncs, the cross-silo auth swap, the internal API client (the request it builds and the DRF request `initialize_request` stitches back together) and the test helpers. A test fails on new direct assignments. Django's own `login()`/`logout()` still set `request.user` internally; they only ever set a real or anonymous user. When DRF authentication finds a caller, the viewer context's actor is updated to match. Endpoints with their own `authentication_classes` run them after the viewer-context middleware, so the context would otherwise describe the middleware's result. A credential with no caller of its own, such as a signed seer-rpc call, keeps the viewer from the signed header. So does a user that `ViewerContextAuthentication` loaded from a signed `X-Viewer-Context`: the header already set its actor, e.g. an agent acting for that user. The actor now also carries superuser elevation from the credential, as the middleware already did. `ApiClient` runs the view in its own viewer-context scope, so the view's caller doesn't replace ours. **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
…eator A ServiceAccountPrincipal is authenticated with user_id=None. The detector and workflow validators treated is_authenticated as "is a person" for the audit actor and read request.user.id for the creator. They now ask has_user(request.user) and read acting_user_id(request.user), so a service account records no creator instead of raising UserRequired.
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
) On a DRF request, a service account's `request.user` is a `ServiceAccountPrincipal` instead of `AnonymousUser`. Service accounts are gated by `organizations:service-accounts`, which is now checked at authentication too (`authenticate_service_account`), so that flag is the one switch. Before, only service-account creation and the token endpoint checked it, and anything holding the shared secret could sign a service-account viewer context. The principal has no user, so `has_user()` is False and `acting_user_id()` is None, and its actor is `SERVICE_ACCOUNT`. Its `id` is a `ServiceAccountId` that never equals an int and raises `UserRequired` (403 `user-required`) when converted to one, so no `request.user.id` call site can reach the user with the same number. **Temporary:** `ServiceAccountId` is a runtime guard, because about 490 `request.user.id` reads still assume a user. It's deleted, along with the RPC pass-through for `UserRequired`, once mypy types `request.user` to include `ServiceAccountPrincipal` (plugin, shrinking per-module allowlist) and those reads are migrated. This PR sits right above the chokepoint on purpose: every PR after it is written and tested against the real principal. Endpoints aren't blocked up front. What breaks for a service account shows up in the auth matrix, and the PRs above fix it or mark the endpoint `requires_user`. What else this changes: - **Web views:** `set_request_principal` sets a service account only on a DRF `Request` (its setter also copies it to Django's `request.user`). Until DRF authenticates it, Django's `request.user` is anonymous, and session auth never sees it, since `ViewerContextAuthentication` runs before `SessionAuthentication`. The check reads the credential (`is_service_account_auth(auth)`), not `isinstance(user, ...)`: the middleware passes Django's lazy user, and loading it reads the session, which adds `Vary: Cookie` to every response. `test_leaves_lazy_user_unloaded` guards it. - **Access:** `SentryPermission.determine_access` asks `has_user()` and `acting_user_id()`, so a service account keeps the credential-based access (`from_rpc_auth`) it had as `AnonymousUser`, instead of its `ServiceAccountId` reaching the org lookup. - **Sudo:** neither the decorator nor the sudo middleware treats a service account as sudo; both ask `has_user()` before `has_usable_password()`. Sudo re-authenticates a person, so `require_sudo()` answers a caller with no user with a 403 `user-required` instead of a sudo prompt, from `@sudo_required` and from project delete. Org, team and project delete and project transfer stay user-only. - **Rate limits:** service accounts get their own bucket. - **RPC:** `UserRequired` passes through as an API error. The auth matrix enables the flag for its service-account rows and records what still fails at this point in the stack. Rows that don't match the oracle are strict xfails, so the PR that fixes a row has to remove it. At this point 89 service-account rows are known gaps: 28 audit actors, 21 per-user serializer lookups, 20 `request.user.id` reads, 2 `serialize_generic_user`, 1 project creation that needs `require_user`, 12 control-silo endpoints and 5 that check the caller's own membership or org role. The PRs above remove them. At the top of the stack, 17 remain: the control-silo and membership ones. This replaces #128983, split per review. It builds on #128779, now landed. **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
force-pushed
the
ref/sa-user-id-workflows
branch
from
October 30, 2026 18:40
ce6a6d2 to
58a972b
Compare
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
Checks that meant "is there a user" asked "is logged in" (`is_authenticated`) or read `request.user.id`. They now ask `has_user()` or call `acting_user_id(request.user)`. Every existing caller gets the same answer; only a `ServiceAccountPrincipal`, which is authenticated but has no user, changes. Nothing here is the runtime fix for service accounts: that's `determine_access` in #129146. These change for consistency and defense: - **Control-silo org lookups** use `acting_user_id()`. - **`users/me`** is a defensive 404 for a service account. - **`normalize_valid_user`** returns None unless `has_user()`, so `from_request` with no org gives a service account no access instead of an `OrganizationlessAccess`. - **Rate limits** ask `has_user` instead of `isinstance(AnonymousUser)`. The viewer-context middleware now builds its actor with `resolve_actor` instead of a copy of it. That's deduplication: the only difference is that a service-account credential gets `SERVICE_ACCOUNT` instead of `UNKNOWN`, and the signed header overrides that anyway. Service accounts are gated by `organizations:service-accounts`. **Matrix:** fixes no service-account rows (known gaps stay at 89). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
Endpoints accept every caller by default: users, agents and service accounts. One that only works for a user sets `requires_user = True`, and a `ServiceAccountPrincipal` (authenticated, no user) gets a 403 `user-required` before the handler runs. `UserEndpoint` and `RegionSiloUserEndpoint` set it, so most account routes are covered by inheritance. Which one to use: - **Browser-only endpoints** use `SessionNoAuthTokenAuthentication`, which refuses every token, so a service account never authenticates there. - **`requires_user`** is for endpoints people may call with any credential, API tokens included, but a caller with no user may not. - **`require_user(request)`** is for one user-only path inside an otherwise shared endpoint. It returns the user, or raises `NotAuthenticated` (anonymous) or `UserRequired` (no user). Creating a project calls it, since the new project's default team is the creator's own. Every `UserRequired` increments `api.user_required`, tagged by endpoint class (the same tag `record_deprecated_scope_translation` uses). That gives a burn-down list of endpoints service accounts still can't use. **Matrix:** fixes 1 service-account row (known gaps 89 → 88). The row is project creation, which now answers `user-required` instead of a 500 reading the principal's `email`. **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
…user (#129144) A `ServiceAccountPrincipal` on `request.user` has no user (`acting_user_id()` is None), and its `id` is a `ServiceAccountId`, not a user id. These checks keep new code from relying on either: - **S032** (flake8, enforced; no existing hits): `acting_user_id()` used as a queryset lookup (`filter`, `exclude`, `get`, `get_or_create`, `update_or_create`). It's None for a service account, so the lookup matches rows with no user. Writing None, e.g. to `created_by_id`, is allowed, because that's how a caller with no user is recorded. - **S033** (flake8, enforced; no existing hits): assigning `request.user` or `request.auth`, plainly, annotated, augmented, by tuple unpacking or with `setattr`, anywhere but `set_request_principal`. It replaces the AST test from #129133, so the check lives in one place. Tests may still assign them. - **`request.user.id` / `.pk`** (test, exact baseline 492): not a user id for a service account. New uses fail, and the baseline only goes down. - **`isinstance` / `hasattr` / `getattr` on `request.user`** (test, exact baseline 31): sniffing the class instead of branching on `has_user()`. The two baselines are exact, so a fix also has to lower them and they can't drift back up. **Matrix:** fixes no service-account rows (known gaps stay at 88). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
…ts (#129154) A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. Org details, org index, teams, project index, organization projects, `Team` lookups, the org deletion actor and `auth_organization_config` treated `is_authenticated` as "is there a user" or read `request.user.id`. They now ask `has_user(request.user)`, or call `acting_user_id(request.user)` where they need the id, so a service account records no user instead of getting a 403 `user-required`. The organization projects checks moved here from the core PR. `RpcAuditLogEntryActor.actor_id` becomes optional to match. Nothing changes for users and agents. Split out so the owning team can review its slice. `organizations/{org}/projects/` lists a service account's projects the way it lists an agent's: every project in the org that its access reaches. One without open membership sees only its linked projects, and projects with no team are included, which the org-token branch it used to share left out. `projects/` scopes a credential without global access to `accessible_project_ids`; org tokens have global access, so they're unaffected. Per warden and Bugbot; the tests land in #129254, where service accounts first get past the project serializer. Deleting an organization now calls `require_user(request)`, as its docs say it needs a user. Sudo already keeps a service account out; this keeps the rule in place if sudo ever opens to service accounts. Per warden. **Matrix:** fixes 1 service-account row (known gaps 88 → 87). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
…9155) A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. `update_groups`' acting user, group integration and external-issue activity, and per-user issue defaults treated `is_authenticated` as "is there a user" or read `request.user.id`. They now ask `has_user()`, or call `acting_user_id(request.user)` where they need the id, so a service account records no user instead of getting a 403 `user-required`. Nothing changes for users and agents. Split out so the owning team can review its slice. Bookmarking or subscribing with no user behind the caller now raises `UserRequired` before any group changes. Before, it wrote a bookmark or subscription with no user and answered 500, which on master already happens for org API keys. Per warden. **Matrix:** fixes 3 service-account rows (known gaps 87 → 84). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
… events (#129149) A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. Discover and Explore saved queries, saved formulas, and the events base treated `is_authenticated` as "is there a user" or read `request.user.id`. They now ask `has_user(request.user)`, or call `acting_user_id(request.user)` where they need the id, so a service account records no creator instead of getting a 403 `user-required`. Nothing changes for users and agents. Split out so the owning team can review its slice. **Matrix:** fixes 15 service-account rows (known gaps 84 → 69). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
) A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. `DashboardRevision` read `user.id` for its creator, and `get_creator_reference` (#129219), which fills a new dashboard's creator columns, read `request.user.id` for the user one. Both now call `acting_user_id(...)`. `get_creator_reference` checks the credential first, so a service account is recorded in `created_by_service_account_id`; a revision it makes records no creator instead of a 403 `user-required`. Nothing changes for users and agents. Split out so the owning team can review its slice. **Matrix:** fixes no service-account rows (known gaps stay at 69). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. `has_viewed` and viewed-by on replay details read `request.user.id`. They now call `acting_user_id(request.user)`, so a service account isn't recorded as a viewer instead of getting a 403 `user-required`. Nothing changes for users and agents. Split out so the owning team can review its slice. **Matrix:** fixes 1 service-account row (known gaps 69 → 68). The row is `OrganizationReplayDetails`; locally it also fails for every credential on an objectstore error, so CI is the check. **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
gricha
added a commit
that referenced
this pull request
Oct 30, 2026
A `ServiceAccountPrincipal` is authenticated but has no user, and its `id` is a `ServiceAccountId` that raises `UserRequired` when used as a user id. The snapshot uploader on preprod snapshot endpoints read `request.user.id` when authenticated. It now calls `acting_user_id(request.user)`, so a service account records no uploader instead of getting a 403 `user-required`. Nothing changes for users and agents. Split out so the owning team can review its slice. **Matrix:** fixes no service-account rows (known gaps stay at 68). **Stack** (each PR is based on the one above it): 1. #128982 Shared `request.user` interface 2. #129133 Set `request.user` / `request.auth` through one helper 3. #129146 Service accounts on `request.user` 4. #129136 `has_user()` in access, org lookups and `users/me` 5. #129143 `requires_user` / `require_user()` 6. #129144 Lints for patterns that break for a service account 7. #129154 Orgs, teams, projects 8. #129155 Issues 9. #129149 Discover, Explore 10. #129156 Dashboards 11. #129150 Workflow engine 12. #129151 Replays 13. #129152 Preprod 14. #129153 Remaining endpoints 15. #129253 Audit entries 16. #129254 Per-user serializer lookups 17. #129255 `serialize_generic_user` 18. #129256 Flagpole user context
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
ServiceAccountPrincipalis authenticated but has no user, and itsidis aServiceAccountIdthat raisesUserRequiredwhen used as a user id. The detector and workflow validators treatedis_authenticatedas "is there a user" for the audit actor and readrequest.user.idfor the creator. They now askhas_user(request.user)and callacting_user_id(request.user), so a service account records no creator instead of getting a 403user-required.Nothing changes for users and agents.
Split out so the owning team can review its slice.
Matrix: fixes no service-account rows (known gaps stay at 69).
Stack (each PR is based on the one above it):
request.userinterfacerequest.user/request.auththrough one helperrequest.userhas_user()in access, org lookups andusers/merequires_user/require_user()serialize_generic_user