Latest Results
ENG-11083: document the auto-provisioning flow end to end (SCIM + service account + team grants) (#6872)
* ENG-11083: document the auto-provisioning flow end to end
Every piece of automated provisioning now exists, but nothing tied them
together: SCIM creates the people and the groups, the team-grant API attaches a
group to a project, the namespace API places it, and a customer had to assemble
that from four unrelated surfaces. The failure mode is wiring half of it and
concluding the feature is broken.
One page, in the order a platform team actually does the work: credential,
identity, permissions, placement. Plus the wrong turns, since each one looks
plausible on its own.
The traps it names, all of which produce a working-looking system that is not:
* A token bound to a service account carries that account's FGA principal, not
the creator's. Scripts developed with an admin's own token work, and 403 on
every call the moment they switch, until the service account itself holds a
project role. It has to be project Admin: can_manage_members is an admin-only
meta that no custom role may delegate.
* pending_approval is a 200. A project gating member changes parks the grant
instead of applying it, and an org can turn that gate on by default for every
new project, so a project nobody configured behaves this way. A provisioner
reading the status code records access nobody has.
* A service account can never be an instance admin -- its backing user row is
inserted with superuser false and admin user management excludes service
accounts from the flag on purpose -- so the namespace endpoints take an
operator's own unrestricted token, not the provisioning credential. Worth
saying plainly, because the natural assumption is that one credential drives
all four steps.
* Emptying a directory group leaves its project grant in place and dormant;
deleting the group takes the team and the grants with it.
* Namespace retargeting moves the next sandbox, not the running one.
And what a provisioner should never touch: POST /project/users/invite is a
per-user role write that takes a Reflex user id and only works for somebody who
is already an org member, which is exactly the work a team grant removes. Org
invitations materialize when the invited person signs in, so nothing you can
poll turns one into access. Namespace is not a directory attribute.
The page is named automated_provisioning.md rather than auto_provisioning.md
because the sidebar label is the title-cased filename, not the H1, and the two
disagreeing reads as a bug in the nav.
* Correct four claims the review caught
Three of them describe a refusal that does not happen the way the page says,
which is the failure mode this page exists to prevent.
* Scoped tokens are not universally refused on the instance-admin endpoints.
requires_super rejects a token carrying an access map only when its account
level is below write, then falls through to the superuser flag, so a token
scoped account:write on a superuser reaches every namespace endpoint. The
inverse reading was the dangerous one: someone trusting the old sentence
could hand out a scoped token believing it could not reach an instance-admin
surface. Says what actually gates it, and that scoping this credential buys
little.
* The quoted refusal for adding a service account through the project members
API is unreachable. invite_users_to_project checks org membership first, and
a service account deliberately has no orgmember row, so the caller always
gets "user is not a member of this project's organization." A reader matching
on the documented string never matches it. Quotes the real message and adds a
troubleshooting row for it, since it names membership while the fix is a role
in org settings.
* "Everything on this page is Enterprise" is false for step 4, which gates on
requires_super alone with no tier or feature check anywhere. On-prem
operators are precisely the audience for that step, so the old blanket sent
them to sales over something their install does not gate. Also notes that
SCIM falls back to a deployment switch where plans are not enforced.
* The step 3 preamble said three endpoints and then documented four.
* Rewrite the page in a runbook voice
The facts were right and the prose was not. It read like a design document
arguing for the system rather than a runbook telling an operator what to do,
which is inherited from the ticket that specified it -- written for engineers
reviewing a decision, not for somebody wiring Okta.
What changed, none of it factual:
* 25 bolded bullet lead-ins removed. Five consecutive bolded paragraph openers
under "What does not work" were the worst of it.
* Design rationale cut back to the cases where an operator's behaviour depends
on it: the approval gate, applies_to, and what emptying a group does. The
justification clauses on everything else are gone.
* Rhetorical setup sentences deleted ("This is the step that gets skipped",
"The one thing you cannot infer from a 200", "These are the wrong turns worth
naming"). Nothing followed from them.
* Colon splices halved. The em dashes had been removed mechanically and the
colons had absorbed the work, which is its own tell. The remainder introduce
code blocks, where a colon belongs.
* Headings turned back into labels. Seven comma-appositive headings and two
that addressed the reader's nerves ("read this before you test anything",
"Constraints worth knowing before you hit them") are now plain.
* Filler intensifiers removed, and the system stopped being an agent: no verb
an API "prefers", no credentials that "die".
* The troubleshooting table stopped answering "Correct." and says what is
happening instead.
* The four bold labels in the worked example became subheadings.
2854 words to 2434 with every claim intact: all 10 code blocks are
byte-identical, and 55 checked assertions survive. Page still renders, links
still resolve, pre-commit clean.
* Say what re-asserting a parked grant actually does
The paragraph claimed a pass that ignores `pending` "keeps replacing the request
an approver is already reviewing and re-notifying them", and the reconcile loop
then told readers to leave pending requests alone. Both describe the behaviour
the route's guard exists to prevent, so a provisioner could not tell whether
re-asserting was safe.
It is safe. _identical_request_is_current short-circuits a re-assert of the same
team, action and role while the gate is still on, so it answers pending_approval
without reaching create_approval_request, which is what would replace the row and
re-notify. A different role falls through and does replace it, correctly, since
the desired state changed. And the guard asks the live policy as well as the
parked request, so a gate turned off since the request was parked lets the next
call apply the grant and cancel the stale request.
The reconcile bullet now gives the real reason to read `pending` -- a parked
grant is absent from `grants`, so it must not be recorded as access -- rather
than a re-assert hazard that does not exist.
Reported by cubic on the previous commit. Rewrite the page in a runbook voice
The facts were right and the prose was not. It read like a design document
arguing for the system rather than a runbook telling an operator what to do,
which is inherited from the ticket that specified it -- written for engineers
reviewing a decision, not for somebody wiring Okta.
What changed, none of it factual:
* 25 bolded bullet lead-ins removed. Five consecutive bolded paragraph openers
under "What does not work" were the worst of it.
* Design rationale cut back to the cases where an operator's behaviour depends
on it: the approval gate, applies_to, and what emptying a group does. The
justification clauses on everything else are gone.
* Rhetorical setup sentences deleted ("This is the step that gets skipped",
"The one thing you cannot infer from a 200", "These are the wrong turns worth
naming"). Nothing followed from them.
* Colon splices halved. The em dashes had been removed mechanically and the
colons had absorbed the work, which is its own tell. The remainder introduce
code blocks, where a colon belongs.
* Headings turned back into labels. Seven comma-appositive headings and two
that addressed the reader's nerves ("read this before you test anything",
"Constraints worth knowing before you hit them") are now plain.
* Filler intensifiers removed, and the system stopped being an agent: no verb
an API "prefers", no credentials that "die".
* The troubleshooting table stopped answering "Correct." and says what is
happening instead.
* The four bold labels in the worked example became subheadings.
2854 words to 2434 with every claim intact: all 10 code blocks are
byte-identical, and 55 checked assertions survive. Page still renders, links
still resolve, pre-commit clean.khaleel/eng-11083-document-the-auto-provisioning-flow-end-to-end-scim-service ENG-11083: document the auto-provisioning flow end to end
Every piece of automated provisioning now exists, but nothing tied them
together: SCIM creates the people and the groups, the team-grant API attaches a
group to a project, the namespace API places it, and a customer had to assemble
that from four unrelated surfaces. The failure mode is wiring half of it and
concluding the feature is broken.
One page, in the order a platform team actually does the work: credential,
identity, permissions, placement. Plus the wrong turns, since each one looks
plausible on its own.
The traps it names, all of which produce a working-looking system that is not:
* A token bound to a service account carries that account's FGA principal, not
the creator's. Scripts developed with an admin's own token work, and 403 on
every call the moment they switch, until the service account itself holds a
project role. It has to be project Admin: can_manage_members is an admin-only
meta that no custom role may delegate.
* pending_approval is a 200. A project gating member changes parks the grant
instead of applying it, and an org can turn that gate on by default for every
new project, so a project nobody configured behaves this way. A provisioner
reading the status code records access nobody has.
* A service account can never be an instance admin -- its backing user row is
inserted with superuser false and admin user management excludes service
accounts from the flag on purpose -- so the namespace endpoints take an
operator's own unrestricted token, not the provisioning credential. Worth
saying plainly, because the natural assumption is that one credential drives
all four steps.
* Emptying a directory group leaves its project grant in place and dormant;
deleting the group takes the team and the grants with it.
* Namespace retargeting moves the next sandbox, not the running one.
And what a provisioner should never touch: POST /project/users/invite is a
per-user role write that takes a Reflex user id and only works for somebody who
is already an org member, which is exactly the work a team grant removes. Org
invitations materialize when the invited person signs in, so nothing you can
poll turns one into access. Namespace is not a directory attribute.
The page is named automated_provisioning.md rather than auto_provisioning.md
because the sidebar label is the title-cased filename, not the H1, and the two
disagreeing reads as a bug in the nav.khaleel/eng-11083-document-the-auto-provisioning-flow-end-to-end-scim-service Latest Branches
+1%
khaleel/eng-11083-document-the-auto-provisioning-flow-end-to-end-scim-service 0%
agent/keyword-only-algolia-search 0%
claude/reflex-deploy-workflow-pkg-jsb589 © 2026 CodSpeed Technology