Machine Access
A workload trust binding is how a machine gets an identity. It says: a token from this issuer, for this repository, this ref, this workflow and this subject may act as this service principal, inside this organization, project and environment, up to this capability, for this long. Nothing about a workload is trusted because of where it runs — it is trusted because a named human conferred a binding that matches the claims its token carries.
vagaris machine-access is the terminal door to those bindings. It reaches the same authorities as the Machine access page in the console, and both are subordinate to the same gates: core decides, not the client.
The authentication is separate, and that is the point
These commands do not use your stored vagaris auth login credential. Core accepts exactly one credential here: an access token minted for the workload-binding admin resource (<issuer>/resources/admin/workload-bindings) and nothing else in its audience, carrying the workload_bindings.admin scope, for a human principal, from an authorization-code exchange whose authentication is fresh.
So every subcommand runs its own browser sign-in — authorization code with PKCE, prompt=login, max_age=0 — and asks core for that audience by name on both the authorization and the token request. You will be asked to authenticate again even if you signed in a minute ago. That is the design: the credential proves the act, not the session.
The token lives in memory for one call. It is never written to the credential store and never printed.
One authentication, one act. Core treats the access token itself as the step-up and records its identifier once, so a second mutation on the same token is refused as a replay. Two conferrals mean two sign-ins.
Confer
vagaris machine-access confer \
--issuer https://token.actions.githubusercontent.com \
--audience https://auth.vagarylabs.com \
--repository-owner VagaryLabs \
--repository vagris \
--refs refs/heads/main \
--environments production \
--job-workflow-ref 'VagaryLabs/vagris/.github/workflows/deploy.yml@refs/heads/main' \
--subject 'repo:VagaryLabs/vagris:ref:refs/heads/main' \
--service-identity <service-principal-id> \
--organization-id <org-id> --project-id <project-id> --environment-id <env-id> \
--capability governance --scope governance.read \
--ttl-seconds 900 --risk-class high \
--reason 'the deploy workflow needs a machine identity'
The request is shaped locally before the browser opens, so a missing or malformed flag costs you nothing. Then core checks, in its own order: the token's audience, client, scope, lifetime and freshness; that the principal is an active human; that the binding is being conferred in the organization the credential acts in; that you are not binding a workload to your own identity; and the shape of the binding itself. A binding whose risk class demands dual control is created pending approval, not active.
--organization-id must be your own organization. Your access token carries the organization on your platform principal, and nothing in this flow can switch it — core's organization-switching grant is pinned to a different audience and can never mint this door's token. Naming another organization is refused as a mismatch.
--audience, --refs, --environments and --scope repeat. --no-environments is not the same as omitting --environments: it sends null, meaning this binding pins no environment at all, where the default sends an empty list. Core reads the two differently.
Read
vagaris machine-access list
vagaris machine-access list --status pending_approval
vagaris machine-access show <binding-id>
list returns the bindings in the organization your credential acts in, together with any bootstrap exception in force for it. show adds the governance events recorded against one binding — who conferred it, who approved it, what was said.
Reading authenticates the same way as writing. Core does not offer a cheaper door for reads.
Approve and reject
vagaris machine-access approve <binding-id>
vagaris machine-access reject <binding-id> --reason 'wrong environment'
Approval is the second human. Core enforces who may give it — for a binding whose risk class requires dual control, the conferrer is not eligible — and refuses a binding that is no longer pending.
Revoke
vagaris machine-access revoke <binding-id> --reason 'repository archived'
Containment for one binding. Core returns only once federation no longer selects the row, so a successful revoke means the workload has already stopped being honoured — not that a revocation has been queued.
Revoking every binding for a repository or service identity is deliberately not available here. Core requires a separately presented re-authentication assertion for that blast radius, and no CLI surface issues one; use the console.
When core refuses
Refusals come back by their own name, unchanged. The common ones:
| Refusal | What it means |
|---|---|
workload_binding_admin_client_not_allowed | This deployment's operator CLI client is not registered for the admin resource. An identity-side registration, not a flag. |
workload_binding_admin_fresh_login_not_requested / ..._authentication_not_fresh | The sign-in was reused or took too long. Run the command again and complete it promptly. |
workload_binding_admin_scope_missing / ..._wrong_audience | A token reached the route that was not minted for this door. |
workload_binding_admin_organization_mismatch | The binding names an organization other than the one your credential acts in. |
workload_binding_step_up_replayed | This credential already authorized a mutation. Authenticate again. |
workload_binding_admin_principal_not_eligible | The authenticated identity is not an active human platform principal. |
A 503 naming the principal as unreadable, or the replay ledger as unavailable, is core saying it could not decide — not that you may not act. Retry rather than route around it.
Grants — the authority the verbs above presuppose
Core decides confer, approve and revoke from a named grant a human already holds
(provider:workload_bindings:request, :approve, :bootstrap_exception), pinned to an organization.
A fleet with no such grant can confer nothing, and until now no client could make the first one. These
verbs go through the same door — same fresh step-up, same one-use ledger — and core refuses by name.
vagaris machine-access grants
vagaris machine-access grant --grantee-user-id <id> --organization-id <org> \
--grant provider:workload_bindings:approve --reason "<ruling / ticket / successor>"
vagaris machine-access revoke-grant --grantee-user-id <id> --organization-id <org> \
--grant provider:workload_bindings:request --reason "<why>"
Two shapes, and core — not the CLI — decides which applies:
- By an approver. You hold
provider:workload_bindings:approvehere and grant to another eligible human who already has owner/admin membership. Granting to yourself is refused (binding_grant_self_grant): an approver may not widen their own authority. - Bootstrap. Your organization has no approver at all, you are an owner, and the grant is
provider:workload_bindings:bootstrap_exception— nothing else (binding_grant_bootstrap_only_exception_grant). This is the one permitted self-row; it closes the moment one approver exists.
A grant confers nothing by itself. Membership is not conferred here (binding_grant_grantee_no_standing),
and core re-asks eligibility, standing and scope every time the grant is used. Every grant and revocation
is an append-only audit row under the actor who made it, carrying the step-up it consumed.
Declare a bootstrap exception
vagaris machine-access declare-exception --organization-id <org> \
--reason "<why dual control is not yet possible>" \
--retirement-predicate "two distinct approvers exist" \
--retire-when-approvers-at-least 2 --expires-at <epoch-ms>
Requires provider:workload_bindings:bootstrap_exception. It governs the ceremony of a keystone
binding: while it is in force, one human may activate what would otherwise stay pending_approval
until a distinct second person approves. It retires on its expiry or once the stated number of distinct
approvers exists — whichever comes first. An ordinary-risk binding never needs one.
Prerequisites
The deployment must publish an operator CLI client (IDENTITY_OPERATOR_CLI_CLIENT_ID) that core allows for the admin resource. Without it, vagaris machine-access fails at the first step with the server's own 503 rather than opening a browser against a client core would refuse.