Chuyển tới nội dung chính

Permission model API

Data permission model (grants, rules, explain).

This page lists 30 endpoints (OpenAPI tag perm). It is generated from openapi.json; see the REST API overview for authentication, CSRF and error conventions.

MethodPathOperation idNotes
POST/api/control-plane/perm/compile/{service_id}compileThe manual/repair trigger.
GET/api/control-plane/perm/engine-capabilitieslist_engine_capabilities
GET/api/control-plane/perm/engine-grantslist_all_engine_grants
PATCH/api/control-plane/perm/engine-grants/{id}patch_engine_grant
DELETE/api/control-plane/perm/engine-grants/{id}delete_engine_grant
GET/api/control-plane/perm/engines/{engine_id}/grantslist_engine_grants
POST/api/control-plane/perm/engines/{engine_id}/grantscreate_engine_grant
POST/api/control-plane/perm/explainexplainPOST because the request carries a subject, an action and a whole target scope — a body, not a query string.
POST/api/control-plane/perm/explain/capabilityexplain_capabilityEvaluate one user and one engine capability on the artifact the engine's profile serves.
POST/api/control-plane/perm/explain/principalexplain_principal
GET/api/control-plane/perm/grantslist_grants
POST/api/control-plane/perm/grantscreate_grant
PATCH/api/control-plane/perm/grants/{id}patch_grantEdit in place, keeping the id — SP4b D1. The body is the whole grant, exactly the create route's shape: this replaces the grant's body and keeps its iden...
DELETE/api/control-plane/perm/grants/{id}delete_grant
GET/api/control-plane/perm/overlapslist_overlapsEvery mask / row-filter pair already stored in a profile's download.
GET/api/control-plane/perm/peoplelist_people
GET/api/control-plane/perm/people/groups/{name}read_group
GET/api/control-plane/perm/people/roles/{name}read_role
GET/api/control-plane/perm/people/users/{user_id}read_user
POST/api/control-plane/perm/people/users/{user_id}/usernameallocate_username
GET/api/control-plane/perm/policies/services/{service_id}/classifyclassify_service_policiesSP4d-3 Task 5: every policy of one profile, classified — its owner, whether it can be migrated into the permission model, and if not, every reason as a sente...
POST/api/control-plane/perm/policies/services/{service_id}/migratemigrate_service_policiesSP4d-3 Task 7: migrate hand-written policies of one profile into the permission model, PROVABLY — see crate::migrate for the transaction, its order, and th...
POST/api/control-plane/perm/policies/services/{service_id}/migrate/undoundo_service_policy_migrationSP4d-3 Task 7: undo migrations — delete the grants and rules provenance names, recompile, switch each original back on, delete the provenance.
GET/api/control-plane/perm/policies/services/{service_id}/warningsservice_policy_warningsSP4d-4 Task 11: the policies of one profile whose stored resource levels Ranger reads differently from how the editor shows them — a policy that stops above ...
POST/api/control-plane/perm/previewpreview**The dry run — SP4b D2.
GET/api/control-plane/perm/ruleslist_rules
POST/api/control-plane/perm/rulescreate_ruleCreate a named mask or row-filter rule and recompile the profiles it affects, in one transaction.
PATCH/api/control-plane/perm/rules/{id}patch_ruleEdit a rule in place, keeping the id — the rule half of SP4b D1.
DELETE/api/control-plane/perm/rules/{id}delete_rule
GET/api/control-plane/perm/unresolved-principalslist_unresolved_principalsEvery stored grant principal and rule exemption whose ref matches nobody (J-F3 Task 2, design R5/R6).

POST /api/control-plane/perm/compile/{service_id}​

Operation id: compile. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

The manual/repair trigger. Unlike the mutating routes it has no write of its own to be atomic with, so it opens its own transaction around recompile_service_in_tx and the profile:compile governance event (DG #3); a refused compile also records its failure in perm.compile_runs.

It exists because the affected-service sets above are derived from the mutated ROW, and there are model-adjacent changes no row mutation covers — an engine newly bound to a profile, a profile that had no binding when a grant was written, a tag renamed out from under a rule. This is how an operator brings one profile current without editing the model.

ParameterInTypeRequiredDescription
service_idpathinteger (int64)yespolicy.authz_services id

Response 200 (application/json): CompileResponse

FieldTypeRequiredDescription
servicesinteger (int64)[]yesThe services this pass recompiled.
createdintegeryes
updatedintegeryes
deletedintegeryes
unchangedintegeryes
skippedSkipReport[]yes
StatusMeaning
200What the recompile wrote
400The model cannot be compiled for this profile
401Not authenticated
403Missing policy:update permission
404No such policy profile
409A rule overlap: two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds one, which refuses every recompile of that profile until one rule is...
500Internal error

GET /api/control-plane/perm/engine-capabilities​

Operation id: list_engine_capabilities. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Response 200 (application/json): EngineCapabilitiesResponse

FieldTypeRequiredDescription
enginesEngineCapabilities[]yes
StatusMeaning
200The curated engine capability catalogue, per engine type, with what is deliberately not grantable and why
401Not authenticated
403Missing policy:read permission

GET /api/control-plane/perm/engine-grants​

Operation id: list_all_engine_grants. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Response 200 (application/json): array of EngineGrantView

FieldTypeRequiredDescription
idstring (uuid)yes
engineIdstring (uuid)yesThe engine the grant was written on. On a legacy shared profile a listing of another engine of the profile shows it too (it reaches that engine), marked by this id and [Self::engine_name].
engineNamestringyes
capabilitystringyes
valuesstring[]yesThe values in the catalogue's key order, an impersonation target as the user's CURRENT username (it is stored by id). A target with no username is left out; targetUserId still names them.
targetUserIdstring (uuid), nullablenoThe impersonation target's user id, for an impersonation capability.
principalsPermPrincipal[]yesAllow-only, include-only (spec E5).
policyNamestringyesperm-engine-\{id\}.
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
StatusMeaning
200Every engine grant, each with the engines it reaches (People pages match principals against standsFor)
401Not authenticated
403Missing policy:read permission

PATCH /api/control-plane/perm/engine-grants/{id}​

Operation id: patch_engine_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesEngine grant id

Request body (application/json): NewEngineGrant

FieldTypeRequiredDescription
capabilitystringyes
valuesstring[]noIn the catalogue's key order; an impersonation target as a username.
principalsPermPrincipal[]nouser, group (never public) or role; at least one.

Response 200 (application/json): EngineGrantWriteResponse

FieldTypeRequiredDescription
idstring (uuid)yes
compileCompileResponseyesWhat a mutation's (or compile's) recompile pass did, aggregated over every service it touched.
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
StatusMeaning
200Engine grant replaced (the engine is fixed) AND its profile recompiled
400As for create
401Not authenticated
403Missing policy:update permission
404No such engine grant
409As for create

DELETE /api/control-plane/perm/engine-grants/{id}​

Operation id: delete_engine_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesEngine grant id

Response 200 (application/json): EngineGrantWriteResponse

FieldTypeRequiredDescription
idstring (uuid)yes
compileCompileResponseyesWhat a mutation's (or compile's) recompile pass did, aggregated over every service it touched.
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
StatusMeaning
200Engine grant deleted AND its compiled policy reaped
401Not authenticated
403Missing policy:update permission
404No such engine grant

GET /api/control-plane/perm/engines/{engine_id}/grants​

Operation id: list_engine_grants. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

ParameterInTypeRequiredDescription
engine_idpathstring (uuid)yesEngine id

Response 200 (application/json): EngineGrantsResponse

FieldTypeRequiredDescription
engineEngineRefyesThe engine a listing is about.
profileProfileRefno
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
grantsEngineGrantView[]yes
managedManagedEntry[]yes
StatusMeaning
200The engine, its profile, every engine it shares that profile with, its engine grants and the profile's seeded (managed) policies
401Not authenticated
403Missing policy:read permission
404No such engine

POST /api/control-plane/perm/engines/{engine_id}/grants​

Operation id: create_engine_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
engine_idpathstring (uuid)yesEngine id

Request body (application/json): NewEngineGrant

FieldTypeRequiredDescription
capabilitystringyes
valuesstring[]noIn the catalogue's key order; an impersonation target as a username.
principalsPermPrincipal[]nouser, group (never public) or role; at least one.

Response 201 (application/json): EngineGrantWriteResponse

FieldTypeRequiredDescription
idstring (uuid)yes
compileCompileResponseyesWhat a mutation's (or compile's) recompile pass did, aggregated over every service it touched.
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
StatusMeaning
201Engine grant created AND the engine's profile recompiled, in one transaction
400Unknown capability, a capability of another engine type, a value that breaks the catalogue's rules, a catalog the engine does not reach, or empty/invalid/unresolvable principals
401Not authenticated
403Missing policy:update permission
404No such engine
409The engine has no profile, is being deleted or is RisingWave; the impersonation target is reserved or an engine admin; the engine already has that grant; or the recompile refused (overlap, ceiling)

POST /api/control-plane/perm/explain​

Operation id: explain. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:read permission, or a workspace-scoped impersonation overlay asking about a catalog that workspace is not assigned.

POST because the request carries a subject, an action and a whole target scope — a body, not a query string. It is nonetheless read-only: it opens no transaction and issues no write. It gates on policy:read, not policy:update, precisely so the method does not decide the permission.

What this answers with, and why it changed (SP4d-2a)

Request body (application/json): ExplainRequest

FieldTypeRequiredDescription
subjectExplainSubjectyesThe caller explain decides about.
actionActionyesThe two actions SP4a knows. read compiles to select on every engine; write compiles to insert,delete on Trino (no update access type exists there) and insert,update,delete on StarRocks. DDL is out of scope for SP4a — see the...
targetScopeyesA path into a catalog. schema/table/column narrow progressively — perm.grants and perm.rules both CHECK that a level cannot be skipped (a column implies a table implies a schema).

Response 200 (application/json): ExplainResponse

FieldTypeRequiredDescription
outcomeExplainOutcomeyesWhich of explain's two shapes a response carries.
profilesExplainProfile[]yesEmpty exactly when outcome is no_profile_serves_the_catalog.
tag_resolution_declinedExplainTagDeclineno
StatusMeaning
200One verdict per profile over the artifact each engine downloads — including the no_profile_serves_the_catalog outcome for a catalog that exists and that no profile serves, which is an answer and not an error
400The target skips a resource level (a column implies a table implies a schema), names a level as "" or "*" (Ranger's "every value", not a resource name), or names a catalog id no registered catalog has
401Not authenticated
403Missing policy:read permission, or a workspace-scoped impersonation overlay asking about a catalog that workspace is not assigned
500Internal error

POST /api/control-plane/perm/explain/capability​

Operation id: explain_capability. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:read permission.

Evaluate one user and one engine capability on the artifact the engine's profile serves. Undecidable is returned as such: on a delegating Trino profile every impersonation question is Undecidable, because the self-impersonation baseline names \{USER\}, which egret's evaluator does not expand.

Request body (application/json): ExplainCapabilityRequest

FieldTypeRequiredDescription
subjectExplainSubjectyesA username; no groups or roles, as /perm/explain.
engineIdstring (uuid)yes
capabilitystringyes
valuesstring[]noIn the catalogue's key order, an impersonation target as a username.

Response 200 (application/json): ExplainCapabilityResponse

FieldTypeRequiredDescription
engineEngineRefyesThe engine a listing is about.
profileProfileRefno
reachReachyesEvery engine a grant reaches: every engine bound to the profile it compiles into, ordered by name (spec E9). One engine in every installation created since I2; more only on a legacy shared profile.
outcomeCapabilityOutcomeyesWhether the engine's profile could be asked at all.
decisionWireDecisionno
attributionExplainAttributionno
policyNamestring, nullableno
tagResolutionCapabilityTagResolutionyesWhat egret did about tags for this question.
StatusMeaning
200The decision, or outcome: no_profile with decision: null for an engine that downloads no profile
400Unknown capability, a capability of another engine type, or a value that breaks the catalogue's rules
401Not authenticated
403Missing policy:read permission
404No such engine
409The profile's engines run a plugin version egret does not model

POST /api/control-plane/perm/explain/principal​

Operation id: explain_principal. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:read permission, or a workspace-scoped impersonation overlay asking about a catalog that workspace is not assigned.

Request body (application/json): ExplainPrincipalRequest

FieldTypeRequiredDescription
principalExplainPrincipalRefyesA group or a role, as a grant principal stores it.
actionActionyesThe two actions SP4a knows. read compiles to select on every engine; write compiles to insert,delete on Trino (no update access type exists there) and insert,update,delete on StarRocks. DDL is out of scope for SP4a — see the...
targetScopeyesA path into a catalog. schema/table/column narrow progressively — perm.grants and perm.rules both CHECK that a level cannot be skipped (a column implies a table implies a schema).

Response 200 (application/json): ExplainPrincipalResponse

FieldTypeRequiredDescription
membersTotalintegeryesEvery member, with or without a username.
limitintegeryes[EXPLAIN_MEMBER_LIMIT].
truncatedbooleanyesMore members with a username than limit: the rest were not evaluated (membersTotal - evaluated - notEvaluated of them).
evaluatedMemberVerdict[]yesSorted by username; the first limit members with one.
notEvaluatedMemberNotEvaluated[]yesEvery member with no username, sorted by label.
StatusMeaning
200Each member of the group or role (as the compiler reads them) evaluated as a user over the artifacts each engine downloads, exactly as /perm/explain answers for that user. At most 200 are evaluated, sorted by username; members with no...
400The principal is public, a workspace, a user (ask /perm/explain), or a group or role no one has; or the target is refused as /perm/explain refuses it
401Not authenticated
403Missing policy:read permission, or a workspace-scoped impersonation overlay asking about a catalog that workspace is not assigned
500Internal error

GET /api/control-plane/perm/grants​

Operation id: list_grants. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Response 200 (application/json): array of GrantView

FieldTypeRequiredDescription
idstring (uuid)yes
effectEffectyesperm.effect.
actionsAction[]yes
scopeScopeyesA path into a catalog. schema/table/column narrow progressively — perm.grants and perm.rules both CHECK that a level cannot be skipped (a column implies a table implies a schema).
principalsPrincipalSetyesA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...
originGrantOriginyesWhy a grant exists. WorkspaceAssignment rows are written by the assignment path, not by a human, and are recreated from the assignment on demand (D3) — perm.grants enforces at most one such row per (catalog_id, workspace_id) via...
catalog_namestring, nullablenoregistry.catalogs.name for scope.catalog_id, resolved server-side.
StatusMeaning
200Every grant in the model, with its principal subtract and its catalog's resolved name
401Not authenticated
403Missing policy:read permission
500Internal error

POST /api/control-plane/perm/grants​

Operation id: create_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

Request body (application/json): NewGrant

FieldTypeRequiredDescription
effectEffectyesperm.effect.
actionsAction[]yes
scopeScopeyesA path into a catalog. schema/table/column narrow progressively — perm.grants and perm.rules both CHECK that a level cannot be skipped (a column implies a table implies a schema).
principalsPrincipalSetnoA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...
StatusMeaning
201Grant created AND the profiles it affects recompiled, in one transaction
400The grant names a scope level as "" (Ranger's "every value at that level", not a resource name — it would be enforced across the whole catalog), names a level beneath a "" one (a path cannot skip a level, and a "" names none), or cannot...
401Not authenticated
403Missing policy:update permission
409A rule overlap: two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds one, which refuses every recompile of that profile until one rule is...
500Internal error

PATCH /api/control-plane/perm/grants/{id}​

Operation id: patch_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

Edit in place, keeping the id — SP4b D1. The body is the whole grant, exactly the create route's shape: this replaces the grant's body and keeps its identity, which is what a UI "Edit" must mean. Before this route the only way to change a grant was delete-plus-create, i.e. a new id, two recompiles and a window with no row — and an explain reason carries the grant_id the UI deep-links from, so every such link broke on every edit.

Replace a grant's body, keeping its id, and recompile the UNION of the profiles it affected before and after the change — a patch that moves the grant to another catalog must reap the policy from the profile it used to reach, or that engine enforces it forever.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesGrant id

Request body (application/json): NewGrant

FieldTypeRequiredDescription
effectEffectyesperm.effect.
actionsAction[]yes
scopeScopeyesA path into a catalog. schema/table/column narrow progressively — perm.grants and perm.rules both CHECK that a level cannot be skipped (a column implies a table implies a schema).
principalsPrincipalSetnoA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...

Response 200 (application/json): CompileResponse

FieldTypeRequiredDescription
servicesinteger (int64)[]yesThe services this pass recompiled.
createdintegeryes
updatedintegeryes
deletedintegeryes
unchangedintegeryes
skippedSkipReport[]yes
StatusMeaning
200Grant updated AND every affected profile recompiled, in one transaction
400The patched grant names a scope level as "" (Ranger's "every value at that level", not a resource name), names a level beneath a "" one (a path cannot skip a level, and a "" names none), cannot be compiled, names no principal or no...
401Not authenticated
403Missing policy:update permission
404No such grant
409The grant changed concurrently and now affects a profile this request did not lock; retry; or two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already...
500Internal error

DELETE /api/control-plane/perm/grants/{id}​

Operation id: delete_grant. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesGrant id
StatusMeaning
200Grant deleted AND its compiled policies reaped, in one transaction
401Not authenticated
403Missing policy:update permission
404No such grant
409A rule overlap: two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds one, which refuses every recompile of that profile until one rule is...
500Internal error

GET /api/control-plane/perm/overlaps​

Operation id: list_overlaps. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Every mask / row-filter pair already stored in a profile's download. Writes refuse only the pairs they create; this is where an operator sees the rest.

Response 200 (application/json): array of StoredOverlap

FieldTypeRequiredDescription
serviceIdinteger (int64)yesThe profile whose download serves the pair (a tag service on its own when no profile names it).
serviceNamestringyes
kindstringyes"mask" or "row_filter".
coordinatestringyes
rulesStoredMember[]yesIn the pair's order (policy names ascending).
servedTodayStoredMemberno
sentencestringyes
howToFixstringyes
StatusMeaning
200Every overlapping mask or row-filter pair stored in a profile's download, compiled or hand-written, with the profile that serves it
401Not authenticated
403Missing policy:read permission
500Internal error

GET /api/control-plane/perm/people​

Operation id: list_people. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

ParameterInTypeRequiredDescription
kindquerystringnouser | group | role. Required.
qquerystringnoCase-insensitive substring of a user's name, email or username, or of a group's or role's name. % and _ are literal.
usernamequerystringnoExact, case-insensitive username lookup. kind=user only. Finds a service account whatever includeServiceAccounts says.
includeServiceAccountsquerybooleannokind=user only: list service accounts too. Default false.
limitqueryinteger (int64)noPage size; default 50, at most 200 (a larger value is capped).
offsetqueryinteger (int64)noRows to skip; default 0.

Response 200 (application/json): PeoplePage

FieldTypeRequiredDescription
itemsPersonRow[]yes
totalinteger (int64)yes
StatusMeaning
200Users, groups or roles, filtered and paged. Users exclude service accounts unless includeServiceAccounts=true or username is given (a service account row is marked serviceAccount), and are sorted by label; groups and roles by name. A...
400kind missing or not user, group or role; username or includeServiceAccounts with a kind other than user; or a negative limit or offset
401Not authenticated
403Missing policy:read permission
500Internal error

GET /api/control-plane/perm/people/groups/{name}​

Operation id: read_group. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

ParameterInTypeRequiredDescription
namepathstringyesThe group's name, as a grant principal stores it; public for everyone

Response 200 (application/json): GroupPrincipal

FieldTypeRequiredDescription
namestringyes
sourcestring, nullablenolocal or oidc; null for public.
isPublicbooleanyes
membersMemberRef[]yesDirect members, as the compiler reads them; empty for public.
serviceAccountsinteger (int64)yesHow many of members are service accounts.
rolesMembership[]yes
workspacesMembership[]yes
standsForStandsFor[]yes
clearancesClearanceFact[]yes
StatusMeaning
200The group: source, direct members (as the compiler reads them; service accounts included), the roles and workspaces it holds (egret only), standsFor and clearances. public answers isPublic: true with no members
401Not authenticated
403Missing policy:read permission
404No group has this name
500Internal error

GET /api/control-plane/perm/people/roles/{name}​

Operation id: read_role. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

ParameterInTypeRequiredDescription
namepathstringyesThe role's name, across every scope defining it

Response 200 (application/json): RolePrincipal

FieldTypeRequiredDescription
namestringyes
scopesstring[]yesEvery scope defining the name, sorted.
holdersMemberRef[]yesLive allow holders that are users, as the compiler reads them.
serviceAccountsinteger (int64)yesHow many of holders are service accounts.
holderGroupsGroupRef[]yesGroups holding the role, unexpired (egret only).
standsForStandsFor[]yes
clearancesClearanceFact[]yes
StatusMeaning
200The role: scopes, live holders that are users (as the compiler reads them; service accounts included), groups holding it (egret only), standsFor and clearances
401Not authenticated
403Missing policy:read permission
404No role has this name
500Internal error

GET /api/control-plane/perm/people/users/{user_id}​

Operation id: read_user. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

ParameterInTypeRequiredDescription
user_idpathstring (uuid)yesThe user's id

Response 200 (application/json): UserPrincipal

FieldTypeRequiredDescription
idstring (uuid)yes
emailstringyes
namestringyes
usernamestring, nullableno
statusstringyesactive or disabled.
serviceAccountbooleanyes
groupsMembership[]yes
rolesMembership[]yesRole grants, direct and through a group.
workspacesMembership[]yesWorkspace-scoped grants, direct and through a group.
standsForStandsFor[]yes
clearancesClearanceFact[]yes
StatusMeaning
200The user: identity, memberships (groups, roles and workspaces, direct and through a group, expired ones included and marked), standsFor (every grant principal that reaches the user, and whether engines apply it) and a clearance answer per...
401Not authenticated
403Missing policy:read permission
404No user has this id
500Internal error

POST /api/control-plane/perm/people/users/{user_id}/username​

Operation id: allocate_username. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
user_idpathstring (uuid)yesThe user's id

Response 200 (application/json): AllocatedUsername

FieldTypeRequiredDescription
usernamestringyes
allocatedbooleanyestrue only for the one request that allocated it; every other call (before or concurrently) answers false.
StatusMeaning
200The user's engine username. A user who has none is allocated the name egret would allocate at their first query (allocated: true); one who has one is answered it unchanged (allocated: false). A service account is allocated like any user
401Not authenticated
403Missing policy:update permission
404No user has this id
409A stored grant principal or rule exemption already names the username that would be allocated as a user ref (compared case-insensitively, as egret resolves user refs). It matches nobody today and would start applying to this person, so...
500Internal error

GET /api/control-plane/perm/policies/services/{service_id}/classify​

Operation id: classify_service_policies. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

SP4d-3 Task 5: every policy of one profile, classified — its owner, whether it can be migrated into the permission model, and if not, every reason as a sentence naming the offending value. Read-only; infra and compiler rows are reported (as not migratable, with the reason), never omitted. The classify module doc says how the service kind, the catalog lookup and the tag lookup are derived from the profile.

ParameterInTypeRequiredDescription
service_idpathinteger (int64)yespolicy.authz_services id

Response 200 (application/json): array of ClassifiedPolicy

FieldTypeRequiredDescription
idinteger (int64)yes
namestringyes
managed_bystringyescompiler | infra | operator — policy.authz_policies.managed_by.
enabledbooleanyesThe enabled COLUMN — what decides whether the engine downloads it.
migratablebooleanyes
blockersstring[]yesEvery reason the policy cannot be migrated, as operator-readable sentences naming the offending value. Empty iff migratable.
proposedProposedObjectsno
migratedMigrationRecordno
warningsPolicyWarning[]yesSP4d-4 Task 11: what this policy's resource levels mean to Ranger, independent of migration — a policy that stops above column and allows select grants no column read, and one with a gap decides nothing. Empty for every row that is...
StatusMeaning
200Every policy of the profile, ordered by id, with its owner, verdict and blocker sentences
401Not authenticated
403Missing policy:read permission
404No such policy profile
500Internal error

POST /api/control-plane/perm/policies/services/{service_id}/migrate​

Operation id: migrate_service_policies. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

SP4d-3 Task 7: migrate hand-written policies of one profile into the permission model, PROVABLY — see crate::migrate for the transaction, its order, and the refusal order. The equivalence gate runs inside the transaction over every profile the new objects compile into, against the downloads as they stand after the write.

dry_run runs the identical code and differs in exactly one place: the line below that decides to commit. A proven migration answers 200; one the gate did not prove answers 422 with the same body shape (its disagreements, or why the gate refused), and nothing is written.

ParameterInTypeRequiredDescription
service_idpathinteger (int64)yespolicy.authz_services id

Request body (application/json): MigrateRequest

FieldTypeRequiredDescription
policy_namesstring[]yesNames of policies of THIS profile, exactly as stored.
dry_runbooleannoRun everything, report it, and roll back.

Response 200 (application/json): MigrateResponse

FieldTypeRequiredDescription
dry_runbooleanyes
committedbooleanyesWhether the transaction committed. false for every dry run and for every migration the gate did not prove.
equivalentbooleanyesThe gate ran over every affected profile and found no disagreement.
refusalstring, nullablenoWhy the gate refused to answer at all (no disagreement list is a proof then). Absent when it answered.
policiesMigratedPolicy[]yesEach migrated policy and the model objects it became.
profilesGatedProfile[]yesEVERY profile the gate evaluated — the source and every other profile the new objects compile into — with what changed in its download.
probe_evaluationsintegeryesProbe evaluations run (probes × plugin versions, over every profile).
disagreementsWireDisagreement[]yesEvery probe on which a profile answers differently after the write.
compileCompileResponseyesWhat the recompile did.
noticesstring[]yesConsequences to read before committing (see the module doc).
StatusMeaning
200Proved equivalent on every affected profile; committed unless dry_run
400A named policy is not migratable (the classifier's sentences), or the request names no policy / a policy twice
401Not authenticated
403Missing policy:update permission
404No such profile, or no policy of that name in it
409Already migrated, not hand-written (compiler or infra owned), or a rule name it would create is taken; or two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it...
422Not proved: the disagreements on any affected profile, or why the gate could not answer. Nothing was written
500Internal error

POST /api/control-plane/perm/policies/services/{service_id}/migrate/undo​

Operation id: undo_service_policy_migration. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

SP4d-3 Task 7: undo migrations — delete the grants and rules provenance names, recompile, switch each original back on, delete the provenance. One transaction.

ParameterInTypeRequiredDescription
service_idpathinteger (int64)yespolicy.authz_services id

Request body (application/json): UndoRequest

FieldTypeRequiredDescription
policy_namesstring[]yes

Response 200 (application/json): UndoResponse

FieldTypeRequiredDescription
policiesUndonePolicy[]yes
compileCompileResponseyesWhat a mutation's (or compile's) recompile pass did, aggregated over every service it touched.
noticesstring[]yesConsequences that outlive the undo (review I-2).
StatusMeaning
200Every named migration undone
400The request names no policy, or a policy twice
401Not authenticated
403Missing policy:update permission
404No such profile, or no migration on record for a named policy
409Nothing was changed: an object the migration created was edited or deleted since, or the original was edited or switched on (each change is named); or a profile became reachable from the migrated objects while undo waited for its locks...
500Internal error

GET /api/control-plane/perm/policies/services/{service_id}/warnings​

Operation id: service_policy_warnings. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

SP4d-4 Task 11: the policies of one profile whose stored resource levels Ranger reads differently from how the editor shows them — a policy that stops above column and allows select (it grants no column read), or one with a gap (it decides nothing) — with warningCount for the profile. Read-only: a stored row is flagged here, never rewritten. The same warnings ride on each policy of GET .../classify.

ParameterInTypeRequiredDescription
service_idpathinteger (int64)yespolicy.authz_services id

Response 200 (application/json): ProfileWarnings

FieldTypeRequiredDescription
warningCountintegeryes
policiesWarnedPolicy[]yes
StatusMeaning
200warningCount and, ordered by id, the policies of the profile that carry a warning
401Not authenticated
403Missing policy:read permission
404No such policy profile
500Internal error

POST /api/control-plane/perm/preview​

Operation id: preview. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

The dry run — SP4b D2. It runs the REAL write and rolls it back.

Dry-run a grant or rule write: report the policy profiles it would recompile, and the compile error it would fail with, without storing anything. Runs the real write in a transaction that is rolled back, so the answer cannot differ from what the write would do.

Request body (application/json): PreviewRequest

FieldTypeRequiredDescription
idstring (uuid), nullablenoPresent ⇒ preview a PATCH of this existing row. Absent ⇒ a create.
grantNewGrantno
ruleNewRuleno

Response 200 (application/json): PreviewResponse

FieldTypeRequiredDescription
affectedAffectedProfile[]yes
compileCompileResponseyesWhat a mutation's (or compile's) recompile pass did, aggregated over every service it touched.
StatusMeaning
200The profiles the write would touch, and what its recompile would do
400The same refusal the write itself would have returned
401Not authenticated
403Missing policy:update permission
404Previewing a patch of an id that does not exist
409The previewed row changed concurrently — the same refusal the write would have given; or two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds...
500Internal error

GET /api/control-plane/perm/rules​

Operation id: list_rules. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Response 200 (application/json): array of RuleView

FieldTypeRequiredDescription
idstring (uuid)yes
namestringyes
kindRuleKindyesperm.rule_kind plus its type-dependent payload, CHECK-constrained in the database ((kind = 'mask') = (mask_type IS NOT NULL), etc.) and made unrepresentable here instead.
selectorSelectoryesWhat a rule's scope is selected by. Tag references tags.key — never tags.name, which is mutable and non-unique (D6) — and egret never emits a tag access policy for it (D2); only masking/row-filter rules select by tag. Path is a...
exemptPrincipalSetyesA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...
catalog_namestring, nullablenoregistry.catalogs.name for a path selector's catalog; None for a tag selector.
tag_namestring, nullablenotags.name for a tag selector's key; None for a path selector.
StatusMeaning
200Every named rule in the model, with its exemption set and its selector's resolved display name
401Not authenticated
403Missing policy:read permission
500Internal error

POST /api/control-plane/perm/rules​

Operation id: create_rule. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

Create a named mask or row-filter rule and recompile the profiles it affects, in one transaction. A tag-selected rule is installation-wide: it recompiles every Trino and StarRocks profile an engine binds, and a compile failure on any one of them fails this request and rolls the rule back.

Request body (application/json): NewRule

FieldTypeRequiredDescription
namestringyes
kindRuleKindyesperm.rule_kind plus its type-dependent payload, CHECK-constrained in the database ((kind = 'mask') = (mask_type IS NOT NULL), etc.) and made unrepresentable here instead.
selectorSelectoryesWhat a rule's scope is selected by. Tag references tags.key — never tags.name, which is mutable and non-unique (D6) — and egret never emits a tag access policy for it (D2); only masking/row-filter rules select by tag. Path is a...
exemptPrincipalSetnoA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...
StatusMeaning
201Rule created AND the profiles it affects recompiled, in one transaction
400The rule names a path level as "" (Ranger's "every value at that level", not a resource name), names a path level beneath a "" one (a path cannot skip a level, and a "" names none), is a tag-addressed mask whose tag is attached to a whole...
401Not authenticated
403Missing policy:update permission
409A rule overlap: two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds one, which refuses every recompile of that profile until one rule is...
500Internal error

PATCH /api/control-plane/perm/rules/{id}​

Operation id: patch_rule. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

Edit a rule in place, keeping the id — the rule half of SP4b D1. Same old-plus-new union as [patch_grant], and the same reason: an explain reason carries the rule_id a UI links from.

Replace a rule's body, keeping its id, and recompile the UNION of the profiles it affected before and after the change. A patch that turns a path selector into a tag selector (or back) changes that set by the whole installation, not by one profile.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesRule id

Request body (application/json): NewRule

FieldTypeRequiredDescription
namestringyes
kindRuleKindyesperm.rule_kind plus its type-dependent payload, CHECK-constrained in the database ((kind = 'mask') = (mask_type IS NOT NULL), etc.) and made unrepresentable here instead.
selectorSelectoryesWhat a rule's scope is selected by. Tag references tags.key — never tags.name, which is mutable and non-unique (D6) — and egret never emits a tag access policy for it (D2); only masking/row-filter rules select by tag. Path is a...
exemptPrincipalSetnoA principal subtract: include minus exclude. This is what lets egret drop Ranger's allow-exception/deny-exception blocks from the authoring surface (see crates/egret-perm/tests/store.rs) — both Grant.principals and Rule.exempt use...

Response 200 (application/json): CompileResponse

FieldTypeRequiredDescription
servicesinteger (int64)[]yesThe services this pass recompiled.
createdintegeryes
updatedintegeryes
deletedintegeryes
unchangedintegeryes
skippedSkipReport[]yes
StatusMeaning
200Rule updated AND every affected profile recompiled, in one transaction
400The patched rule names a path level as "" (Ranger's "every value at that level", not a resource name), names a path level beneath a "" one (a path cannot skip a level, and a "" names none), is a tag-addressed mask whose tag is attached to...
401Not authenticated
403Missing policy:update permission
404No such rule
409The rule changed concurrently and now affects a profile this request did not lock; retry; or two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already...
500Internal error

DELETE /api/control-plane/perm/rules/{id}​

Operation id: delete_rule. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing policy:update permission.

ParameterInTypeRequiredDescription
idpathstring (uuid)yesRule id
StatusMeaning
200Rule deleted AND its compiled policies reaped, in one transaction
401Not authenticated
403Missing policy:update permission
404No such rule
409A rule overlap: two mask (or two row-filter) rules would decide one request differently: this write would compile such a pair, or a profile it recompiles already holds one, which refuses every recompile of that profile until one rule is...
500Internal error

GET /api/control-plane/perm/unresolved-principals​

Operation id: list_unresolved_principals. Auth: Session cookie. Forbidden (403): Missing policy:read permission.

Every stored grant principal and rule exemption whose ref matches nobody (J-F3 Task 2, design R5/R6). Writes refuse such a ref since J-F3 Task 1; a row stored before that, or naming a principal deleted since, is listed here, never rewritten.

Response 200 (application/json): array of UnresolvedPrincipal

FieldTypeRequiredDescription
sourcestringyes"grant", "rule" or "engine_grant" (F11: always an allow include, so it never fails open).
idstring (uuid)yesThe grant's or the rule's id.
namestring, nullablenoThe rule's name; null for a grant (a grant has none).
kindstringyesuser, group, role or workspace_members.
refstringyesThe ref as stored, byte-for-byte — the string the compiled policy names.
positionstringyes"include" or "exclude" (of the grant's principals, or of the rule's exempt).
effectstring, nullablenoThe grant's allow / deny; null for a rule exemption.
statusstringyes"unresolved": the ref names nobody egret knows. "empty": it names a group that exists and has no members, so it matches nobody TODAY.
reasonstringyesWhy it matches nobody, as a sentence an author can act on.
failsOpenbooleanyesMatching nobody here makes the compiled policy allow MORE than the model states: an unresolved ref in a deny grant's include, an allow grant's exclude, or a rule exemption's exclude. An allow include, a deny grant's exclude (a...
StatusMeaning
200Every stored grant principal and rule exemption whose ref matches nobody, ordered by source, id, position, kind and ref. failsOpen marks the ones where matching nobody makes the compiled policy allow more than the model states (a deny...
401Not authenticated
403Missing policy:read permission
500Internal error