Appearance
plexctl audit
plexctl audit is the CLI surface over the audit chains. The audit aggregate maintains two chains, and every subcommand selects one with exactly one of --domain <UUID> or --platform:
- The per-Domain chain (
--domain <UUID>) records authorization decisions a single Domain owns, against/v1/domains/{domainId}/audit/*. - The platform-residency chain (
--platform) records actions no single Domain owns, against/v1/platform/audit/*. Cross-Domain list reads such asdomain listandproject liststamp their granted rows here, so those rows are visible only through--platform.
The parent command groups four operations, each wire-symmetric across both chains:
plexctl audit entries list— page through the chain inseqorder (GET …/audit/entries).plexctl audit entries get— read one row plus its hash-chain proof (GET …/audit/entries/{seq}).plexctl audit verify— recompute thesha256(prev_hash ‖ sha256(canonical_bytes))chain (POST …/audit/verify).plexctl audit erase-identity— irreversibly purge the pseudonym ↔ identity mapping (POST …/audit/erase-identity).
The verify contract: verify returns exit 0 on a clean chain and exit 1 on tampering, so an operator script can distinguish a clean chain from a divergent one without parsing stdout. erase-identity requires an explicit --confirm flag — --yes is intentionally not honoured because the operation is forensically irreversible.
--domain and --platform are mutually exclusive, and exactly one is required; supplying both, or neither, is a flag-parse error (exit 2).
Synopsis
shell
plexctl audit entries list (--domain <UUID> | --platform) [--limit <N>] [--cursor <token>] [--all]
plexctl audit entries get (--domain <UUID> | --platform) --seq <N>
plexctl audit verify (--domain <UUID> | --platform) [--from-seq <N>] [--to-seq <N>]
plexctl audit erase-identity (--domain <UUID> | --platform) --identity <UUID> --confirmInvocation
In every flag table below, --domain and --platform are governed by the one-of contract: supply exactly one. The --platform row is omitted from each table for brevity and documented once here — it is a boolean that, when set, targets the platform-residency chain in place of a Domain.
plexctl audit entries list
Lists rows from the selected chain in seq order. The --all flag follows next_cursor until exhausted; the loop is capped at 100 000 items so a runaway server cannot exhaust the CLI's heap.
Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--domain | UUID | one-of | — | Owning Domain UUID. Mutually exclusive with --platform; exactly one is required. |
--platform | bool | one-of | false | Target the platform-residency chain instead of a Domain. |
--limit | int | no | server default | Maximum items per page. |
--cursor | string | no | — | Continuation token from a previous call's next_cursor. Mutually exclusive with --all. |
--all | bool | no | false | Follow next_cursor until empty (capped at 100 000 rows). |
Persistent flags inherited from root: see plexctl.md.
plexctl audit entries get
Reads a single row plus its hash-chain proof. The seq value is the chain's monotonic sequence number starting at 1; the CLI rejects --seq < 1 at flag-parse time so a malformed value never hits the server.
Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--domain | UUID | one-of | — | Owning Domain UUID. Mutually exclusive with --platform; exactly one is required. |
--platform | bool | one-of | false | Target the platform-residency chain instead of a Domain. |
--seq | int64 | yes | — | Monotonic sequence number, >= 1. |
Persistent flags inherited from root: see plexctl.md.
plexctl audit verify
Recomputes the chain from --from-seq (defaults to 1) up to --to-seq (defaults to chain head) and asserts every row's entry_hash = sha256(prev_hash ‖ sha256(canonical_bytes)). The server returns HTTP 200 regardless of outcome with a {ok, segment_from, segment_to, divergent_seq, expected_hash, observed_hash} body; on ok=false the CLI returns a sentinel error so the dispatcher maps the exit to 1. A clean run exits 0.
Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--domain | UUID | one-of | — | Owning Domain UUID. Mutually exclusive with --platform; exactly one is required. |
--platform | bool | one-of | false | Target the platform-residency chain instead of a Domain. |
--from-seq | int64 | no | 1 | Inclusive lower bound on seq. |
--to-seq | int64 | no | chain head | Inclusive upper bound on seq. |
Persistent flags inherited from root: see plexctl.md.
plexctl audit erase-identity
Drops the audit_subject_pii row that maps the chain's pseudonym back to the underlying identity UUID, satisfying the right-to-erasure requirement. The hash chain is preserved — only the plaintext binding is purged. The operation is forensically irreversible: once the row is dropped the pseudonym ↔ identity mapping is gone for good. --confirm is mandatory and is not substituted by the persistent --yes flag.
Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--domain | UUID | one-of | — | Owning Domain UUID. Mutually exclusive with --platform; exactly one is required. |
--platform | bool | one-of | false | Target the platform-residency chain instead of a Domain. |
--identity | UUID | yes | — | Identity whose PII mapping is purged. |
--confirm | bool | yes | false | Explicit confirmation; --yes does not substitute. |
Persistent flags inherited from root: see plexctl.md.
Exit codes
| Code | Reachable from | Meaning |
|---|---|---|
0 | every subcommand | Success — for verify this means the chain is clean. |
1 | every subcommand | Runtime / API error. For verify this is the tamper-detection signal: chain divergence at the seq named in stderr. |
2 | every subcommand | Flag-parse / misconfiguration (neither --domain nor --platform set, both set together, malformed UUID, --seq < 1, missing --confirm on erase-identity, --all and --cursor set together, …). |
3 | every subcommand | Missing or insecure credentials (no token resolved, InsecureModeError). |
4 | every subcommand | Permission denied (HTTP 403 from the server — caller lacks the domain#auditor relation for --domain, or the platform#auditor relation for --platform). |
64 | none | Audit family is fully wired; not a deferred command. |
Examples
List the first page of audit entries
shell
plexctl audit entries list \
--server "${PLEXSPHERE_URL}" \
--domain 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
--limit 50In --output text the table prints SEQ | OCCURRED_AT | REASON | RELATION | OBJECT_TYPE | OBJECT_ID | CORRELATION_ID. In --output json the wire shape is preserved verbatim, including the optional next_cursor token used to paginate.
List the platform-residency chain
Cross-Domain list reads such as domain list and project list stamp their granted audit rows onto the platform anchor, so those rows are reachable only with --platform:
shell
plexctl audit entries list \
--server "${PLEXSPHERE_URL}" \
--platform \
--output jsonThe rows carry relation: "domain.list" (or project.list) and an object of type platform. On the wire each platform row's domain_id field carries the reserved platform anchor UUID (00000000-0000-0000-0000-706c6174666d), not a real Domain — the text table never prints it, but --output json includes it, so do not treat it as a Domain identifier.
Verify a clean chain (exit 0)
shell
plexctl audit verify \
--server "${PLEXSPHERE_URL}" \
--domain 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1
echo "exit=$?" # exit=0Verify a tampered chain (exit 1)
shell
plexctl audit verify \
--server "${PLEXSPHERE_URL}" \
--domain 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
--from-seq 1 --to-seq 1000
# stderr: plexctl: audit chain divergence at seq 742 (segment 1..1000)
echo "exit=$?" # exit=1Erase an identity from the audit chain
shell
plexctl audit erase-identity \
--server "${PLEXSPHERE_URL}" \
--domain 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
--identity 0190a8b9-1234-7a0a-8a0a-a0a0a0a0a0a2 \
--confirmThe response carries the per-Domain pseudonym (safe to print) and the erased_at timestamp. Calling the command again is a no-op at the server (the row is already gone) — the operation is idempotent on subject_pseudonym.
Refusal without --confirm (exit 2)
shell
plexctl audit erase-identity \
--domain 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
--identity 0190a8b9-1234-7a0a-8a0a-a0a0a0a0a0a2
# stderr: Error: audit erase-identity: --confirm required for irreversible operation
echo "exit=$?" # exit=2Cross-references
../../../api/openapi/plexsphere-v1.yaml— OpenAPI 3.1 source of truth. The per-Domain operations (ListAuditEntries,GetAuditEntry,VerifyAuditChain,EraseIdentityFromAudit) and the wire-symmetric platform operations (ListPlatformAuditEntries,GetPlatformAuditEntry,VerifyPlatformAuditChain,EraseIdentityFromPlatformAudit) share theAuditEntry,AuditEntryList,AuditEntryProof,AuditChainVerifyRequest,AuditChainVerifyResult,AuditEraseIdentityRequest,AuditEraseIdentityResponseschemas.../../contexts/audit/access.md— read-access surface: thedomain#auditorandplatform#auditorReBAC gates and the eight OpenAPI operations they guard.../../contexts/audit/index.md— bounded-context reference: ubiquitous language, canonical-byte encoder pin (PXA1magic), hash-chain state machine, residency rules for both chains, retention matrix, threat model.../../../cmd/plexctl/commands/audit.go— source of truth for the CLI dispatch, theverifyexit-code contract, and the--confirmgate onerase-identity.plexctl.md— root command reference (persistent flags, profile resolution, shared exit-code taxonomy).