Appearance
plexctl approval
Synopsis
plexctl approval is the operator surface for the decision queue. It inspects the rows awaiting a decision and drives each one through its decision transitions. The token stays singular (approval) because every leaf operates on a single row identified by <id>, mirroring plexctl credential get <id>.
The queue carries four row kinds, named by each row's kind:
approval— a dual-control proposal raised against a Domain's approval policy.credential_assignment— a Credential Assignment awaiting a decision on the Cloud Credential it spends.cloud_assignment— a Cloud Assignment awaiting a decision on the Cloud it spends.sink_enablement— a sink enablement awaiting a decision on the telemetry sink it spends.
The queue is the decide surface for every kind: approval get, approval approve, and approval reject take an assignment id as readily as an Approval id, and the server dispatches the decision to the service that owns the assignment. The per-row authority follows the kind — an Approval row is gated on its owning Domain, an assignment row on the Cloud Credential or the Cloud it spends.
The family spans two /v1 surfaces under a single cobra parent:
- Read —
/v1/approvals(list) and/v1/approvals/{id}(get) page the rows visible to the caller and fetch a single row by identifier. - Decision —
/v1/approvals/{id}/{approve,reject,break-glass}advance a single row to a terminal state.approveandrejectrecord an operator decision;break-glassis the audited emergency override that short-circuits the wait for a quorum.
A caller may never approve a proposal they themselves raised; the server enforces the dual-control invariant and surfaces a violation as a 4xx the CLI maps onto its exit-code taxonomy.
text
plexctl approval <subcommand> [flags]Subcommands
plexctl approval list
GETs /v1/approvals. Returns the queue rows visible to the caller. There is no entry gate on the call: any authenticated principal may run it, and the server keeps only the rows that principal may decide — read on the owning Domain for an approval row, assign on the spent Cloud Credential or Cloud for an assignment row. A caller holding neither gets an empty page rather than a permission error. --status applies an optional lifecycle filter and is validated client-side against the known Approval states before the call leaves the CLI — an unknown value exits 2. --kind applies an optional row-kind filter and is validated the same way, so a misspelled family never reaches the wire. --domain-id applies an optional residency filter and --cloud-id narrows to the assignment rows spending one Cloud (a cloud_assignment for that Cloud, or a credential_assignment for any Cloud Credential the Cloud owns); both are parsed as UUIDs locally. An approval row never matches --cloud-id, so combining it with --kind approval returns an empty page. Pagination is keyset-based via --cursor / --limit; the response carries a next_cursor the caller threads into the next --cursor to walk the pages.
In text mode each row projects onto the columns ID, KIND, DOMAIN_ID, STATE, ACTION_KIND, TARGET_RESOURCE, PROPOSER_SUBJECT, EXPIRES_AT, CREATED_AT. KIND names the source family and TARGET_RESOURCE the object the decision applies to: the cloudcredential:<uuid> or cloud:<uuid> an assignment spends, or the resource the proposed action targets. EXPIRES_AT is empty on an assignment row, which is decided without a sweeper deadline. In json / yaml mode the typed list shape is emitted verbatim.
plexctl approval get <id>
GETs /v1/approvals/{id}, where <id> is an Approval id or an assignment id. Local UUID validation rejects a malformed id with exit 2. text mode renders the list columns plus PROJECT_ID and MATERIALISED — the consuming Project of an assignment row and whether its binding is currently live. Both cells are empty on an approval row, which binds no Project and materialises no binding.
plexctl approval approve <id>
POSTs /v1/approvals/{id}/approve. Records the caller's accept decision against the row and renders the hydrated result (typically in its approved terminal state, or still pending-approval when a further quorum decision is outstanding). An assignment id is dispatched to the service that owns it, so approving here materialises the assignment's binding and writes its uses tuple. The server rejects an attempt to approve a row the caller raised themselves, comparing against the principal the row records as its requester.
plexctl approval reject <id>
POSTs /v1/approvals/{id}/reject with a --reason body. Records the caller's decline decision and renders the hydrated row in its rejected terminal state. An assignment id is dispatched to the owning service. The --reason flag is required.
plexctl approval break-glass <id>
POSTs /v1/approvals/{id}/break-glass with a --reason body. This is the emergency override that force-approves the proposal and short-circuits the wait for a quorum; the justification is audited. The --reason flag is required. The override applies to an approval row only — an assignment id is refused with 409, because an assignment carries no quorum to short-circuit.
The CLI does not validate the justification length locally. The server owns the minimum-length rule, so a too-short justification surfaces as a 400 the CLI maps onto its exit-code taxonomy. Keeping the bound server-authoritative means the CLI does not drift when the bound changes.
Flags
plexctl approval list
| Flag | Type | Required | Description |
|---|---|---|---|
--status | string | no | Lifecycle filter: proposed, pending-approval, approved, rejected, expired, or revoked. Validated client-side; an unknown value exits 2. |
--kind | string | no | Row-kind filter: approval, credential_assignment, cloud_assignment, or sink_enablement. Validated client-side; an unknown value exits 2. |
--domain-id | UUID | no | Owning Domain UUID to filter by. |
--cloud-id | UUID | no | Cloud UUID whose assignment rows to filter by. Matches a cloud_assignment for that Cloud and a credential_assignment for any Cloud Credential it owns; an approval row never matches. |
--limit | int | no | Maximum items per page (server default when zero). |
--cursor | string | no | Continuation token returned by a previous call's next_cursor. |
plexctl approval reject <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--reason | string | yes | Audit-recorded rejection rationale. |
plexctl approval break-glass <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--reason | string | yes | Audit-recorded emergency justification. Length is enforced server-side. |
plexctl approval get and plexctl approval approve take no subcommand-specific flags beyond the persistent root bag and the positional <id>.
Persistent flags inherited from root
--server, --profile, --token-file, --output, --yes. See ../plexctl.md.
Exit codes
See ../plexctl.md#exit-code-taxonomy.
Examples
List the rows awaiting a decision
shell
plexctl approval list \
--server "${PLEXSPHERE_URL}" \
--status pending-approval \
--domain-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0c0List the assignment rows spending one Cloud
shell
plexctl approval list \
--server "${PLEXSPHERE_URL}" \
--kind cloud_assignment \
--cloud-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0d0Approve a row
<id> is an Approval id or an assignment id; the server dispatches the decision to the service that owns the row.
shell
plexctl approval approve 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0e0 \
--server "${PLEXSPHERE_URL}"Reject a row with an audited rationale
shell
plexctl approval reject 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0e0 \
--server "${PLEXSPHERE_URL}" \
--reason "out of scope for this change window"Force-approve via the audited emergency override
shell
plexctl approval break-glass 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0e0 \
--server "${PLEXSPHERE_URL}" \
--reason "incident bridge: restore production access now"Cross-references
../../../api/openapi/plexsphere-v1.yaml../../../cmd/plexctl/commands/approvals.go../../api/approvals.mdcredential.md,cloud.md— the assignment families whose rows this queue decides; each keeps request, list, and revoke of its own.