aac-cli 0.1.6__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. aac_cli-0.2.1/PKG-INFO +941 -0
  2. aac_cli-0.2.1/README.md +924 -0
  3. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/__init__.py +1 -1
  4. aac_cli-0.2.1/aac_cli/agent_cli.py +683 -0
  5. aac_cli-0.2.1/aac_cli/agent_config.py +462 -0
  6. aac_cli-0.1.6/aac_cli/workspace_health.py → aac_cli-0.2.1/aac_cli/agent_health.py +144 -97
  7. aac_cli-0.1.6/aac_cli/workspace_layout.py → aac_cli-0.2.1/aac_cli/agent_layout.py +198 -125
  8. aac_cli-0.2.1/aac_cli/agent_record.py +177 -0
  9. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/cli.py +4 -4
  10. aac_cli-0.2.1/aac_cli/config_render.py +323 -0
  11. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/dev_material.py +290 -13
  12. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/init_cli.py +857 -390
  13. aac_cli-0.2.1/aac_cli/material_cases.py +175 -0
  14. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/reference.py +41 -12
  15. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/secure_files.py +1 -1
  16. aac_cli-0.2.1/aac_cli/supplied_material.py +293 -0
  17. aac_cli-0.2.1/aac_cli.egg-info/PKG-INFO +941 -0
  18. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/SOURCES.txt +7 -4
  19. {aac_cli-0.1.6 → aac_cli-0.2.1}/pyproject.toml +2 -2
  20. aac_cli-0.1.6/PKG-INFO +0 -726
  21. aac_cli-0.1.6/README.md +0 -709
  22. aac_cli-0.1.6/aac_cli/config_render.py +0 -266
  23. aac_cli-0.1.6/aac_cli/workspace_cli.py +0 -544
  24. aac_cli-0.1.6/aac_cli/workspace_manifest.py +0 -144
  25. aac_cli-0.1.6/aac_cli.egg-info/PKG-INFO +0 -726
  26. {aac_cli-0.1.6 → aac_cli-0.2.1}/LICENSE +0 -0
  27. {aac_cli-0.1.6 → aac_cli-0.2.1}/MANIFEST.in +0 -0
  28. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/__main__.py +0 -0
  29. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/admin_key_pem.py +0 -0
  30. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/api_key_rotation_state.py +0 -0
  31. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/config.py +0 -0
  32. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/idp_recovery.py +0 -0
  33. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/profiles.py +0 -0
  34. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/registration_state.py +0 -0
  35. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli/sso_login.py +0 -0
  36. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/dependency_links.txt +0 -0
  37. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/entry_points.txt +0 -0
  38. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/requires.txt +0 -0
  39. {aac_cli-0.1.6 → aac_cli-0.2.1}/aac_cli.egg-info/top_level.txt +0 -0
  40. {aac_cli-0.1.6 → aac_cli-0.2.1}/setup.cfg +0 -0
aac_cli-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,941 @@
1
+ Metadata-Version: 2.4
2
+ Name: aac-cli
3
+ Version: 0.2.1
4
+ Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
5
+ Author: Agent Authority Cloud Project
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: httpx>=0.28
11
+ Requires-Dist: cryptography>=42.0
12
+ Requires-Dist: PyYAML>=6.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest>=8.0; extra == "test"
15
+ Requires-Dist: pytest-httpx>=0.30; extra == "test"
16
+ Dynamic: license-file
17
+
18
+ # aac-cli — the `aac` command
19
+
20
+ `aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
21
+ AAC lets an organisation's AI agents and services prove, on every request,
22
+ which tenant they belong to and what they were authorised to do. A
23
+ **tenant** is one organisation registered with AAC. Each of its workloads
24
+ runs beside an AAC sidecar that signs outgoing requests and verifies
25
+ incoming ones against public trust material the tenant publishes.
26
+
27
+ New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-sidecar-go/#what-aac-is-for)
28
+ explains what delegated authority between agents buys you, and what it does not.
29
+
30
+ The `aac` command is how a tenant's operator does everything that is not
31
+ done by the sidecar itself: register the tenant, sign in, manage keys,
32
+ workloads and domains, and prepare a runnable local workload. Everything
33
+ works from a terminal or a script; there is no web console to click
34
+ through. Output is JSON by default, and every command that reports a
35
+ result also offers `--output table`.
36
+
37
+ Two kinds of tenant use the same tool:
38
+
39
+ * **Developer tenants** register themselves by signing in with GitHub or
40
+ Google. `aac init` takes a developer from an installed CLI to a runnable
41
+ local workload in one command.
42
+ * **Enterprise tenants** are registered through an onboarding ceremony with
43
+ AAC operations and sign in through their own identity provider. Their
44
+ keys, domains and workloads are managed with the `aac tenant` and
45
+ `aac sso` commands described further down.
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ pip install aac-cli
51
+ aac --version
52
+ ```
53
+
54
+ Python 3.10 or newer is required.
55
+
56
+ > **Mind the name.** `pip install aac` installs an unrelated project that
57
+ > happens to share the acronym. The AAC package is **`aac-cli`**; the
58
+ > command it installs is **`aac`**.
59
+
60
+ ## Guided setup for developer tenants: `aac init`
61
+
62
+ `aac init` prepares the AAC side of one of your agents on this machine: it
63
+ registers the agent's identity with AAC and creates the keys, certificates
64
+ and configuration its sidecar needs. The agent itself is yours. The first
65
+ run also creates your profile and registers your tenant; run it again with
66
+ a new `--agent` and its own `--agent-config` file, and the same `--trust-url`
67
+ and `--idp`, for each further agent.
68
+
69
+ Along the way it signs you in, registers a developer tenant (or reuses the
70
+ one your profile is already bound to), takes the trust domain AAC assigns to the
71
+ tenant, registers your workload, generates development keys and
72
+ certificates, and writes a complete sidecar configuration together with the
73
+ files the trust-anchor publisher and Docker Compose need.
74
+
75
+ Point it at the endpoints your AAC environment gives you. The example below
76
+ uses the AAC stage environment. First save the YAML example in “Agent configuration”
77
+ below as `orders.yaml`:
78
+
79
+ ```bash
80
+ aac init --profile stage --agent orders --agent-config orders.yaml \
81
+ --admin-url https://api.stage.cascadeauth.dev \
82
+ --data-plane-url https://api.stage.cascadeauth.dev \
83
+ --trust-url https://trust.stage.cascadeauth.dev \
84
+ --display-name 'Example Team' --contact 'dev@example.com' --idp github
85
+ ```
86
+
87
+ The command shows what it will create and asks before registering the
88
+ tenant, because a tenant is permanent. In a script, where there is no
89
+ terminal to answer the question, pass `--create-tenant`:
90
+
91
+ ```bash
92
+ aac init --profile stage --agent orders --agent-config orders.yaml \
93
+ --admin-url https://api.stage.cascadeauth.dev \
94
+ --data-plane-url https://api.stage.cascadeauth.dev \
95
+ --trust-url https://trust.stage.cascadeauth.dev \
96
+ --display-name 'Example Team' --contact 'dev@example.com' --idp github \
97
+ --create-tenant
98
+ ```
99
+
100
+ `--idp` names how you sign in: `github` or `google`, the two sign-ins AAC
101
+ offers developer tenants. If you need another sign-in provider, contact
102
+ support@cascadeauth.com.
103
+
104
+ `--trust-url` is required when an agent is created: it is the public URL
105
+ where your AAC environment serves trust material, and for AAC stage it is
106
+ `https://trust.stage.cascadeauth.dev`. If you leave it out, the command
107
+ stops before creating anything and names the value your tenant already
108
+ uses, if it has one.
109
+
110
+ Setting up a new developer tenant takes two browser sign-ins: the first
111
+ registers the tenant, the second starts the tenant-admin session that
112
+ registers the workload. The command says so before the first one.
113
+
114
+ Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in,
115
+ hosted trust domain, workload, and material; the settings files are
116
+ written last. Rerunning the
117
+ command is safe: it resumes an interrupted setup, reports a complete
118
+ agent, or names the exact conflict. It never overwrites private
119
+ material. The sign-in you chose with `--idp` is remembered, so a rerun
120
+ never asks you to pick between GitHub and Google again.
121
+
122
+ ### What it creates, and where
123
+
124
+ * `~/.aac/agents/<agent>/` holds the files **one sidecar** needs, in folders named for where each file goes. `sidecar/` is
125
+ what you mount or copy into the sidecar: the workload, receipt and HTTPS
126
+ key pairs, this agent's root-signing key, the CA certificate and the
127
+ outbound CA bundle. `agent/` is what the paired application reads: the
128
+ pairing secret and the CA certificate. `keep/` stays with you — it holds
129
+ the development CA private key, out of both mounts, so no container ever
130
+ sees the key that mints identities; an agent whose certificates your own
131
+ issuer signed has no `keep/` at all. Beside them sit
132
+ `sidecar-config.yaml`, `compose.env`, `state/` and `record.json`, which
133
+ lets a rerun resume.
134
+ * `~/.aac/tenants/<tenant-id>/` is **tenant-level** material shared by every
135
+ agent of that tenant: the tenant-admin signing key, the publisher's input
136
+ directories (`root-keys/` and `spiffe-bundle/`, public halves only) and
137
+ the generated `publisher.env`.
138
+ * `~/.aac/credentials/<tenant-id>` is the tenant API key. The generated
139
+ configuration references it by path.
140
+
141
+ **By default everything `aac init` generates is development material.** The
142
+ development CA lives seven days and the leaf certificates one day. Nothing
143
+ there qualifies for production, whatever the tenant's domain. If your own
144
+ issuer signs your agent's certificates instead, see "Two ways to get your
145
+ agent's certificates" below; moving to production also means a tenant-admin
146
+ key under production custody (`aac tenant rotate-admin-key`) and a root key
147
+ id published by your publisher.
148
+
149
+ ### What an agent folder is for, and how to use it
150
+
151
+ An agent folder holds everything one workload needs to run beside an AAC
152
+ sidecar, kept apart from every other workload: its identity in your
153
+ tenant's trust domain, its own root signing key and key id, its
154
+ certificates, the pairing secret its sidecar and application share, and the
155
+ settings files written for them. It also records how far setup got, so
156
+ rerunning `aac init` resumes where it stopped.
157
+
158
+ Use one agent per workload. `aac init --agent <name>` creates an agent
159
+ (with `--agent-config` and `--trust-url`) or resumes it. There is no default agent;
160
+ three commands look after it:
161
+
162
+ ```bash
163
+ aac agent status --agent orders # what exists, what is due, and the next step
164
+ aac agent renew --agent orders # replace the short-lived certificates
165
+ aac agent list # every agent on this machine
166
+ ```
167
+
168
+ `agent list` reports an unreadable or unsupported record in that agent's
169
+ `state` and continues listing the others. `agent status --agent <name>` still
170
+ returns a local-state error for that individual agent.
171
+
172
+ There is no separate command for the settings files. Running setup again —
173
+ with the profile the agent belongs to — writes `sidecar-config.yaml`,
174
+ `compose.env` and the tenant's `publisher.env` from what the agent already
175
+ records, without touching a single key:
176
+
177
+ ```bash
178
+ aac init --profile stage --agent orders # write them again
179
+ aac init --profile stage --agent orders --layout container # and change the layout
180
+ ```
181
+
182
+ That is how you rebuild a settings file you lost and how you move between
183
+ running the sidecar directly on this machine and running it in a container.
184
+ `--profile` is required because setup picks the profile before it reads the
185
+ agent; `aac agent status` prints the same command with the right profile
186
+ whenever it is the next step.
187
+
188
+ A second workload is a second agent with its own workload path:
189
+
190
+ ```bash
191
+ aac init --profile stage --agent worker --agent-config worker.yaml \
192
+ --idp github --trust-url https://trust.stage.cascadeauth.dev
193
+ ```
194
+
195
+ Pass the same `--trust-url` and `--idp` as for your first agent. Each agent
196
+ records its own, and the trust URL also goes into the tenant's
197
+ `publisher.env`, which every agent of the tenant shares. Writing the
198
+ settings files never changes that file's endpoints, so an agent created with
199
+ a different trust URL stops at its last step and has to be set up again
200
+ under a new name.
201
+
202
+ `--agent` names the local files and must match the file's `agent_name`.
203
+ The file's required `workload_path` supplies the path at the end of the SPIFFE ID.
204
+ An optional `--workload-path` must match the file or saved identity; omission
205
+ on refresh reuses that identity. Missing or conflicting identities are refused. Two agents with the same workload path are the same
206
+ workload holding different keys: that is what a replacement computer wants,
207
+ and what a second workload does not. The agent name is permanent, because
208
+ the root key id is made from it. `aac init` needs the profile the agent was
209
+ created with; the `aac agent` commands read it from the agent itself.
210
+
211
+ ### Agent configuration
212
+
213
+ The operator writes one YAML file. `aac init --agent-config PATH` validates and
214
+ explicitly applies it. The profile remains INI; this file describes one agent,
215
+ not its profile or the hosted service. For example, save this as `orders.yaml`:
216
+
217
+ ```yaml
218
+ agent_name: orders
219
+ workload_path: fulfilment/orders
220
+ tls_server_names: [orders.internal.example, localhost, 127.0.0.1]
221
+ sidecar:
222
+ agent_invoke_url: http://127.0.0.1:8000/invoke
223
+ external_bind_address: 127.0.0.1
224
+ telemetry:
225
+ sink: orders.jsonl
226
+ central_forward: false
227
+ classes_of_action:
228
+ fulfil_order:
229
+ predicates:
230
+ action: reserve_inventory
231
+ amount_max: 10000
232
+ originator_reference: "PO #4143"
233
+ valid_for: +8m
234
+ destinations: {}
235
+ ```
236
+
237
+ This declares the tenant's permission; AAC does not invent one. The application
238
+ chooses `fulfil_order` when starting a chain and checks that its business action
239
+ matches the authorized context. Units and business meaning belong to the
240
+ application. Empty classes and destinations are allowed for registration before
241
+ peer identities are known. No self destination is added.
242
+
243
+ **Chain-start boundary:** restrict `/v1/agent/delegations` and
244
+ `/v1/agent/mint-root` to the authorized originating application at the
245
+ network/ingress boundary. The current sidecar does not authenticate these callers;
246
+ sharing a private network with peers is insufficient. Expose only intended
247
+ receive/A2A routes to peers. Development certificates do not require `dev_mode`.
248
+
249
+ The following is the complete input schema. Unknown fields, duplicate mapping
250
+ keys, YAML aliases/merge keys, object tags and files over 64 KiB are refused.
251
+ Null is not an omitted value. Strings must be nonempty and have no surrounding
252
+ whitespace or control characters. Lists allow at most 128 unique values; class
253
+ and destination maps allow at most 128 entries each.
254
+
255
+ | Field | Type and meaning | Default when omitted |
256
+ |---|---|---|
257
+ | `agent_name` | Required local name, 3–15 characters under the same name rules as `--agent`; must match it. | None |
258
+ | `workload_path` | Required concrete SPIFFE path, 3–150 ASCII characters; slash-separated letters/digits/`._~-`, no empty or dot segments, wildcard, query or escaped character. Fixed once the agent exists. | None |
259
+ | `tls_server_names` | Required nonempty list of DNS names/IP addresses callers use for HTTPS, including the originating application. No URLs, wildcards or unspecified bind addresses. | None |
260
+ | `sidecar.dev_mode` | Boolean enabling the runtime's development exceptions; independent of certificate source and replay profile. | `false` |
261
+ | `sidecar.loopback_bind_address` | Must be exactly `127.0.0.1` when `dev_mode: false`; another loopback IP address requires `dev_mode: true`. Non-loopback addresses are not accepted by this input. | `127.0.0.1` |
262
+ | `sidecar.external_bind_address` | IP bind address for the peer listener; choose the reachability your ingress allows. | `127.0.0.1` |
263
+ | `sidecar.loopback_port`, `sidecar.external_port` | Distinct integer ports, 1–65535. | `8080`, `9443` |
264
+ | `sidecar.agent_invoke_url` | Absolute HTTP(S) URL of your application's invoke endpoint. | `http://127.0.0.1:8000/invoke` |
265
+ | `sidecar.log_level` | `debug`, `info`, `warn` or `error`. | `info` |
266
+ | `sidecar.telemetry.sink` | Filename within managed `state/` (letters/digits/`_.-`, starting with a letter/digit), or `stdout` / `none`. `a2a-egress.db` is reserved for managed database state, regardless of case or whether A2A is enabled. Host/container paths are generated. | `telemetry.jsonl` |
267
+ | `sidecar.telemetry.central_forward` | Boolean opt-in for asynchronous metadata-only forwarding. The endpoint and tenant API-key file come from the selected profile/agent binding, never another tenant's file. | `false` |
268
+ | `replay_protection.backend` | `memory` or `valkey`. | `memory` |
269
+ | `replay_protection.deployment_profile` | `basic` with memory; `ha-retained-write-safe` with Valkey. No fallback. | The matching profile |
270
+ | `replay_protection.url` | Required absolute `valkeys://` URL for Valkey, with an optional numeric database path. No query, fragment or embedded credentials. Not valid with memory. | None |
271
+ | `replay_protection.memory_max_entries` | Integer 1–1000000. | `100000` |
272
+ | `replay_protection.connect_timeout_ms` | Integer 1–100. | Runtime default `100` |
273
+ | `replay_protection.operation_timeout_ms` | Integer 1–250. | Runtime default `50` |
274
+ | `replay_protection.max_connections` | Integer 1–4096. | Runtime default `32` |
275
+ | `replay_protection.cold_start_quarantine_seconds` | Integer 120–3600. | Runtime default `120` |
276
+ | `trust_anchors.tenant_ids` | Additional canonical `tnt-<UUID>` tenant IDs whose root signatures you trust. | Empty; own tenant is always included |
277
+ | `spiffe_bundles.trust_domains` | Additional lowercase trust domains whose workload certificates you trust. | Empty; own assigned domain is always included |
278
+ | `https_trust.ca_files` | Paths to public PEM CA certificate files, relative to the input file or absolute; up to 256 KiB each and 1 MiB total. Certificate-only PEM is saved; refresh/renewal do not reread the source files. No private keys. | Empty; own CA is always included |
279
+ | `https_trust.system_roots` | Boolean adding the current machine's system public HTTPS roots, with certifi fallback. With `false`, every outbound TLS service—including the control plane, trust URL, peer and Valkey—must chain to the own CA or a CA listed in `ca_files`. | `true` |
280
+ | `classes_of_action.<name>.predicates` | Mapping of registered predicate names to supported values, described below. The class entry's `<name>` uses letters/digits/`_.-`, starting with a letter/digit, up to 128 characters; predicate names come only from the closed registry. | Empty mapping |
281
+ | `classes_of_action.<name>.valid_for` | Required positive duration, 1 second–24 hours: digits with optional `+`, then `s`, `m`, `h` or `d`. The CLI supplies the registered `audience_self`. | None |
282
+ | `destinations.<name>.url` | Required absolute HTTPS recipient URL, for native receive or A2A. | None |
283
+ | `destinations.<name>.audience_pattern` | Required exact registered SPIFFE identity; no wildcard. | None |
284
+ | `destinations.<name>.predicates`, `.valid_for` | Same value/duration contract as classes. | Empty predicates; duration required |
285
+ | `destinations.<name>.timeout_ms` | Integer 1–2147483647 without A2A; 1–60000 for every destination when A2A is enabled, matching its generated deadline. | `10000` |
286
+ | `a2a.public_base_url`, `a2a.local_handler_url` | Optional A2A block; both required if present. Public URL is HTTPS with no path except optional `/`; the HTTP(S) local handler path must be exactly `/a2a/v1`, with no escaped spelling. Neither URL permits query or fragment markers, even empty ones. Managed A2A state paths and a 60-second deadline are generated. No A2A self-route is added. | A2A disabled |
287
+
288
+ URL hosts are ASCII DNS names or IP addresses; URL fields reject whitespace,
289
+ backslashes, embedded credentials and fragments. Basic replay history
290
+ is process-local: restarting loses it, and replicas do not share it. Selecting
291
+ Shared durable declares that the operator has qualified the Valkey service; the
292
+ CLI does not establish durability. This configuration interface does not accept
293
+ credential-bearing replay URLs.
294
+
295
+ The three kinds of trust above are separate checks. Obtain peer tenant IDs,
296
+ assigned trust domains and exact workload identities from their public status
297
+ or registration output. Exchange public CA certificates only. After registering
298
+ both agents, explicitly apply their peer configurations with the same command:
299
+
300
+ ```bash
301
+ aac init --profile stage --agent orders --agent-config orders.yaml
302
+ ```
303
+
304
+ The closed predicate registry accepts `action`, `amount_max`, `amount_min`,
305
+ `amount`, `valid_from`, `beneficiary_account`, `beneficiary_class`, `currency`,
306
+ `data_scope`, `purpose`, `human_authorization_class`, `task_ref`,
307
+ `min_account_age_days`, `max_authority_amount`, `assessed_damage_amount`,
308
+ `assessment_outcome`, `surveyor_findings`, `payout_amount`, `program_reference`,
309
+ `reporting_quarter`, `claim_ref`, `hours`, `quantity_units`, `beneficiary`,
310
+ `unit_price_usd`, `account`, `esg_scope3_co2e_kg`, `conflict_minerals_clear`,
311
+ `originator_reference`, `scope`, `composite_payout_amount`,
312
+ `composite_payout_total`, `composite_units_total`,
313
+ `composite_esg_scope3_co2e_kg_total` and `composite_conflict_minerals_clear`.
314
+ `amount_max`, `amount_min` and `valid_from` accept nonnegative integers or
315
+ canonical decimal strings (`0` or a nonzero digit followed by digits), at most
316
+ 9223372036854775807. Enforced numbers are checked before YAML integer conversion:
317
+ spellings such as `010`, `0x10`, `1_000` and `+10` are refused, quoted or unquoted,
318
+ so the loader cannot silently reinterpret a limit. Other values must be strings. Commas are refused because
319
+ they separate wire predicates. The combined mapping has a 7900-byte UTF-8
320
+ budget, leaving room for the generated expiry.
321
+
322
+ `valid_until` is generated authority data, so it is refused in static input:
323
+ use `valid_for`. Registry membership alone does not give business labels new
324
+ monotonic enforcement. The current HTTP mint route uses the selected class's
325
+ static first limits; it accepts no per-request obligations. Your application
326
+ remains responsible for its policy decision.
327
+
328
+ Applying a file rewrites the generated configuration; restart the sidecar to
329
+ load it. Ordinary refresh and certificate renewal use the saved accepted values,
330
+ even if the original file is edited, moved or removed. Public CA file changes
331
+ also require explicit reapplication. The source file is never rewritten. Keep
332
+ the agent directory, credentials and keys; the input file is not their backup.
333
+ Generated files are disposable outputs and should not be hand-edited.
334
+
335
+ HTTPS certificates must authenticate every configured connection name. The CLI
336
+ issues them for those names or checks supplied certificates against them; renewal
337
+ keeps those names. Applying a different name set requires the installed certificate
338
+ to cover it already: supplied issuers can issue a certificate covering both sets
339
+ before the change. A CLI-issued agent needing new names must be created under a
340
+ new name with the intended configuration. No old agent-record format is migrated.
341
+
342
+ Central forwarding retains the local sink and sends only permitted metadata.
343
+ It is asynchronous and best-effort; a local event does not prove central delivery.
344
+ It excludes business predicates, task references, raw tokens/proofs and receipts.
345
+ `AAC_TELEMETRY_SINK` and `AAC_AGENT_STATE_DIR` in `compose.env` identify the local
346
+ file and mount; `AAC_API_KEY_FILE` identifies this tenant's credential. Mount it
347
+ at `/run/secrets/tenant-api-key` in the container layout. Do not read private
348
+ `record.json` from consumers.
349
+
350
+ For a CA change: stop the affected sidecar/application; renew or install the
351
+ issuer's replacement certificates; restart the publisher and verify publication
352
+ with `aac agent status --agent <name> --remote`; exchange the new public CA with
353
+ each intended peer and explicitly reapply that peer's configuration; then restart
354
+ the sidecars/applications and verify traffic in both directions. A peer's files
355
+ are never changed implicitly. Old published CA anchors remain until deliberately
356
+ retired. Leaf-only renewal needs a sidecar restart, without changing peer trust.
357
+
358
+ ### Two ways to get your agent's certificates
359
+
360
+ Find your situation here and use the flags it lists. Nothing is inferred:
361
+ which case you are in is decided by whether you pass certificates your own
362
+ issuer signed, and the agent records the answer, so `aac agent status` can
363
+ tell you later and `aac agent renew` behaves accordingly.
364
+
365
+ <!-- material-cases:start -->
366
+
367
+ <!-- Generated from aac_cli/material_cases.py. Do not edit by hand. -->
368
+
369
+ #### The CLI creates a development CA
370
+
371
+ The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
372
+
373
+ **Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
374
+
375
+ **Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
376
+
377
+ **The CLI** creates a 7-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
378
+
379
+ **The CLI does not** ask you for anything from your own certificate authority.
380
+
381
+ **Renewal.** `aac agent renew --agent <name>` issues fresh certificates from the same CA.
382
+
383
+ #### I bring my own CA
384
+
385
+ Your own issuer has signed the agent's certificates, and your CA private key never reaches this machine. Certificate source alone does not qualify a production deployment.
386
+
387
+ **Flags.** All seven together: `--workload-cert-file`, `--terminal-cert-file` and `--tls-cert-file`, each with its key file (`--workload-key-file`, `--terminal-key-file`, `--tls-key-file`), plus `--ca-cert-file`. Never `--ca-key-file`: the CLI does not want your CA key.
388
+
389
+ **Keys and signatures.** Your CA's key must be Ed25519 or EC P-256, and it must have signed each certificate with Ed25519 or ECDSA-with-SHA-256. The two identity keys may be Ed25519 or EC P-256; the HTTPS key must be EC P-256. RSA is not supported: the sidecar cannot verify against it.
390
+
391
+ **The CLI** checks each certificate against its key, its issuer and its validity window, and HTTPS certificates against every configured connection name; registers the workload and writes the settings files and the pairing secret; publishes your CA certificate as a trust anchor named `<agent>-ca`.
392
+
393
+ **The CLI does not** create a certificate authority; issue any certificate; ask for, read or store your CA private key.
394
+
395
+ **Renewal.** `aac agent renew --agent <name>` cannot reissue what it did not sign: it takes the replacements your issuer produced.
396
+
397
+ <!-- material-cases:end -->
398
+
399
+ The settings files, `sidecar-config.yaml` and `compose.env` in the agent
400
+ folder and `publisher.env` in the tenant directory, follow from what the
401
+ agent records, in both cases. They hold endpoints, identifiers, settings and
402
+ the paths of the key files, never key material. `aac init` writes them as
403
+ its last step and writes them again on any later run. The layout decides the
404
+ paths written into the sidecar configuration: `host` (the default) uses
405
+ paths on this machine, for a sidecar that runs directly on it, and
406
+ `container` uses the paths where the files are mounted inside the
407
+ containers.
408
+
409
+ `publisher.env` belongs to the tenant, and every agent of the tenant writes
410
+ it. A write rewrites its paths and poll interval for this machine (hand
411
+ edits to those are replaced), but refuses to change its tenant ID, trust
412
+ domain or URLs, because that would repoint the publisher for every agent.
413
+ The refusal names the values that differ. If the file is the stale one, for
414
+ example after you deliberately moved the tenant to new endpoints and set up
415
+ its agents again, move it aside (`mv publisher.env publisher.env.old` in the
416
+ tenant directory) and run setup again from an agent with the intended
417
+ endpoints. The admin key and the published public keys stay where they are.
418
+
419
+ ### How profiles, agents and tenant data fit together
420
+
421
+ ```text
422
+ profile (~/.aac/config) ── bound to ──> tenant
423
+ ├── credentials: ~/.aac/credentials/<tenant-id>
424
+ ├── tenant directory: ~/.aac/tenants/<tenant-id>/
425
+ └── agents: ~/.aac/agents/<name>/, one per workload
426
+ ```
427
+
428
+ * A **profile**, in `~/.aac/config`, names the AAC admin and data-plane
429
+ endpoints you use and, once registration or sign-in has bound it, the one
430
+ tenant it acts for. The trust URL and the sign-in choice are recorded in
431
+ each agent instead. "Profiles and configuration", further down,
432
+ covers profiles in full.
433
+ * The **tenant data** is shared by all of a tenant's agents on this
434
+ machine: the tenant API key and the tenant-admin session in
435
+ `~/.aac/credentials/`, and the tenant directory
436
+ `~/.aac/tenants/<tenant-id>/` with the tenant-admin signing key, the public
437
+ keys and certificates the publisher uploads, and `publisher.env`.
438
+ * An **agent folder**, in `~/.aac/agents/<name>/`, holds one workload's
439
+ material. It records the profile and the tenant it was created for and
440
+ keeps them: one tenant can have many agents, and an agent never
441
+ moves to another tenant.
442
+
443
+ ### What each file is for, and what to back up
444
+
445
+ The table lists every file the CLI creates, what it is for, how long it
446
+ lives and whether to back it up. `<agent>` is your explicitly selected agent
447
+ name and `<tenant-id>` the tenant id AAC allocated.
448
+
449
+ For what each of these proves, who else ever gets a copy, what never leaves you
450
+ and how the pieces fit together — including when your own certificate authority
451
+ signs instead of this tool — see
452
+ [Keys and certificates](https://cascadeauth.github.io/aac-sidecar-go/keys-and-certificates.html).
453
+
454
+ <!-- inventory-table:start -->
455
+ | No. | Material | Where | Used for | Lifetime | Back up? |
456
+ |---|---|---|---|---|---|
457
+ | 1 | tenant_api_key | `~/.aac/credentials/<tenant-id>` | CLI data-plane calls; the sidecar's workload projection | until retired or rotated; `aac tenant reissue-api-key` | **Yes.** save a protected copy in your secret manager; loss needs `aac tenant reissue-api-key` |
458
+ | 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
459
+ | 3 | tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`; a lost public half is derived from the private key); production custody belongs in an HSM/KMS, not a password manager |
460
+ | 4 | root_signing_key | `~/.aac/agents/<agent>/sidecar/root.pem`, `~/.aac/agents/<agent>/sidecar/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<agent>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per agent name) | No. never restore the private key: a replacement agent gets a new name and therefore a new key id, published alongside the old one until the old one is retired; its public copies are recreated from it |
461
+ | 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
462
+ | 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac agent renew` | No. reissue |
463
+ | 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 1 day; `aac agent renew` | No. reissue |
464
+ | 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 1 day; `aac agent renew` | No. reissue |
465
+ | 9 | outbound_ca_bundle | `~/.aac/agents/<agent>/sidecar/outbound-ca.pem` | the sidecar verifies TLS to AAC and configured peers | rebuilt by renew; `aac agent renew` | No. derived |
466
+ | 10 | pairing_secret | `~/.aac/agents/<agent>/agent/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
467
+ | 11 | agent_record | `~/.aac/agents/<agent>/record.json` | the agent's identities and progress; every `aac agent` verb reads it first | for the life of the agent | **Recommended.** cannot be recreated by any command; without it start a new agent under a new name (see the new-computer setup procedure) |
468
+ | 12 | published_public_inputs | `~/.aac/tenants/<tenant-id>/root-keys`, `~/.aac/tenants/<tenant-id>/spiffe-bundle` | the publisher's input directories: every root public key and CA certificate this tenant has published, across all agents | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement agent must publish its new root key alongside a still-ACTIVE previous one (the control plane refuses a root-key set that drops every ACTIVE key at once) |
469
+ | 13 | settings_files | `~/.aac/agents/<agent>/sidecar-config.yaml`, `~/.aac/agents/<agent>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret settings derived from the agent's record | written again on demand; `aac init --profile <profile> --agent <agent>` | No. derived; `aac init --profile <profile> --agent <agent>` writes them again |
470
+ <!-- inventory-table:end -->
471
+
472
+ In short: back up the tenant API key and the whole tenant directory while
473
+ the machine is healthy. Everything else is regenerated.
474
+
475
+ ### Inspect and maintain an agent
476
+
477
+ ```bash
478
+ aac agent status --agent orders --output table # what exists, expiry, permissions, next command
479
+ aac agent status --agent orders --remote # also: is the public trust material visible?
480
+ aac agent renew --agent orders # replace the short-lived certificates; old ones archived
481
+ aac agent list # every agent on this machine, with its case
482
+ aac init --profile stage --agent orders --layout container # write the settings again, container paths
483
+ ```
484
+
485
+ `status` works offline by default and exits with code 3 when material is
486
+ missing, expired or unsafely permissioned. `renew` reissues the one-day
487
+ certificates with fresh keys, and reissues the development CA only when it
488
+ is expired or within a day of expiry (or when `--ca` is given); a new CA is
489
+ published under the next anchor id and needs the publisher restarted. For an
490
+ agent whose own issuer signed its certificates, `renew` cannot reissue
491
+ anything: it takes the replacements your issuer produced, through the same
492
+ flags `aac init` accepts.
493
+ Re-running `aac init` writes the settings files again and never touches a
494
+ key.
495
+
496
+ `status` also names one next step, `next_command`. When material is
497
+ missing, it is the fix for the first missing item: restore it from your
498
+ backup, recreate a lost public copy from the file it copies, renew, or run
499
+ setup again. Otherwise it is renewal when a certificate is due. After the step,
500
+ run `status` again. Commands it names that change your tenant carry the
501
+ agent's own `--tenant-id` and `--admin-url`, so a value left in
502
+ `AAC_TENANT_ID` or `AAC_ADMIN_URL` cannot aim them at another tenant. The
503
+ file recipes use `openssl`, which must be OpenSSL 1.1.1 or newer: the
504
+ `openssl` that ships with macOS cannot read Ed25519 keys, so install a
505
+ current OpenSSL there, for example with Homebrew.
506
+
507
+ Every private file is created once, readable only by you, and never
508
+ overwritten. Before generating anything, `init` checks every destination
509
+ and refuses with the full list if one already exists. `aac init` never
510
+ installs or replaces a tenant-admin key for an existing tenant: the key on
511
+ this machine must already be the tenant's active admin key, and replacing
512
+ it is always the deliberate `aac tenant rotate-admin-key` command, never a
513
+ side effect of setup. A second agent for the same tenant
514
+ (`--agent other`) reuses the tenant-admin key and gets its own root key
515
+ id.
516
+
517
+ ### What to do when keys and certificates expire
518
+
519
+ It is the certificates that expire, and renewing replaces their keys with
520
+ them. `aac agent status` shows when each certificate runs out and flags
521
+ it before it does: six hours ahead for the one-day certificates, a day
522
+ ahead for the development CA.
523
+
524
+ | Material | Lasts | When it runs out | What to do |
525
+ |---|---|---|---|
526
+ | Workload, terminal-attestation and HTTPS certificates | 1 day | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
527
+ | Development CA | 7 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within a day of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
528
+ | Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
529
+ | Tenant API key, tenant-admin signing key, root signing key | do not expire | — | replaced only on purpose: see "Keys" and "Setting up aac-cli on a new computer" |
530
+
531
+ A simple routine: when you start work, run
532
+ `aac agent status --agent <name>`, and when it names renewal, run
533
+ `aac agent renew --agent <name>` and restart the sidecar. About once
534
+ a week that renewal also replaces the development CA (`renew` then reports
535
+ `republication_required: true`): restart the publisher, the sidecar and the
536
+ agent, and anything else that trusted the old CA. Renewing is safe to
537
+ repeat, but it is not a no-op: every run issues fresh keys and moves the old
538
+ files to the agent's `archive/` directory, so whatever reads them must
539
+ reload. Recreate or restart the affected processes with your deployment's
540
+ normal commands after publishing and explicitly refreshing each peer's public
541
+ trust. Restarting a process does not renew its certificates.
542
+
543
+ All of this is development material. In production, certificates come from
544
+ your own issuer and follow its renewal process.
545
+
546
+ ## Setting up aac-cli on a new computer
547
+
548
+ This procedure covers a lost machine and a second machine alike. It needs
549
+ the two backups named above: the tenant API key from
550
+ `~/.aac/credentials/<tenant-id>` and the tenant directory
551
+ `~/.aac/tenants/<tenant-id>/`. The tenant id is the `tnt-…` value in your
552
+ profile; `aac profile show` prints it, and the example below uses
553
+ `tnt-550e8400-e29b-41d4-9716-446655440000`.
554
+
555
+ 1. Install the CLI, create the profile, and sign in. Signing in needs the
556
+ tenant id, because a fresh profile is not yet bound to a tenant:
557
+
558
+ ```bash
559
+ pip install aac-cli
560
+ aac profile create stage \
561
+ --admin-url https://api.stage.cascadeauth.dev \
562
+ --data-plane-url https://api.stage.cascadeauth.dev
563
+ aac sso login --profile stage --idp github \
564
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
565
+ ```
566
+
567
+ An enterprise tenant signs in through its own identity provider instead,
568
+ naming the connection by its URL:
569
+
570
+ ```bash
571
+ aac sso login --profile stage \
572
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
573
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000
574
+ ```
575
+
576
+ 2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>`, readable
577
+ only by you, or issue a new one if it was not backed up:
578
+
579
+ ```bash
580
+ aac tenant reissue-api-key --profile stage
581
+ ```
582
+
583
+ Restore `~/.aac/tenants/<tenant-id>/` from the backup, with directories
584
+ readable only by you and files likewise. Its `publisher.env` still names
585
+ the old machine's paths; step 3 rewrites them for this one.
586
+
587
+ If the tenant directory was not backed up, first check whether another
588
+ machine still holds `~/.aac/tenants/<tenant-id>/`: every machine that has
589
+ run `aac init` for this tenant has one. If so, copy it from there. That
590
+ is the backup, and the rest of this step does not apply.
591
+
592
+ If no copy exists anywhere, the tenant-admin key and every root key that
593
+ lived on the lost machine are gone for good. Install a new admin key,
594
+ then account for every root key the tenant has published (the `openssl`
595
+ commands need OpenSSL 1.1.1 or newer, which macOS does not ship):
596
+
597
+ ```bash
598
+ openssl genpkey -algorithm ed25519 -out tenant-admin.pem
599
+ openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
600
+ aac tenant rotate-admin-key --profile stage \
601
+ --tenant-admin-pubkey-file tenant-admin.public.pem
602
+ aac trust-anchor list --profile stage --role root-signing --output table
603
+ aac trust-anchor revoke --profile stage --kid starter-root-v1 \
604
+ --reason 'machine lost'
605
+ ```
606
+
607
+ The list shows each root key with its state. A publisher may drop a key
608
+ from its published set only while another active key stays in the set,
609
+ so a set holding nothing but the new key is accepted only once no other
610
+ key is active. Revoke every active key whose private half was lost, one
611
+ `revoke` per key id, and no other key: a root key that is still in use
612
+ elsewhere must stay active, and its public file must be in the tenant
613
+ directory the publisher runs from. Then run step 3 with
614
+ `--tenant-admin-key-file tenant-admin.pem` added.
615
+
616
+ 3. Prepare `laptop-2.yaml` with the new `agent_name` and the original
617
+ workload path, deployment and trust choices. Run setup with that **new agent name**. A developer tenant
618
+ passes its sign-in choice, so an expired session is renewed without a
619
+ prompt:
620
+
621
+ ```bash
622
+ aac init --profile stage --agent laptop-2 --agent-config laptop-2.yaml --idp github \
623
+ --trust-url https://trust.stage.cascadeauth.dev
624
+ ```
625
+
626
+ An enterprise tenant runs the same command without `--idp`; `--idp`
627
+ selects only the GitHub and Google developer sign-ins. The guided setup
628
+ then uses the session from step 1. If that session has expired, repeat
629
+ the `aac sso login --idp-url …` command from step 1 first:
630
+
631
+ ```bash
632
+ aac init --profile stage --agent laptop-2 --agent-config laptop-2.yaml \
633
+ --trust-url https://trust.stage.cascadeauth.dev
634
+ ```
635
+
636
+ 4. Start the publisher from the tenant directory, restored or written by
637
+ step 3. A restored directory's root-key set holds the old and the new
638
+ public keys, which is exactly what AAC requires: a set that drops every
639
+ currently active key at once is refused. Retire the old key later, by
640
+ removing its public file, once nothing signs with it. A directory written
641
+ fresh after the revocations in step 2 holds only the new key, which is
642
+ accepted because no other key is active any more.
643
+
644
+ ### Why the replacement agent needs a new name
645
+
646
+ The agent name is baked into two identifiers that are published for
647
+ the whole tenant: the root key id `<agent>-root-v1` and the CA
648
+ anchor id (`<agent>-dev-ca` for a development CA the CLI created,
649
+ `<agent>-ca` for your own issuer's). Other parties look up your keys and
650
+ certificates by those ids. AAC therefore never accepts new key material
651
+ under an id that has already been published: a key id names one key, for
652
+ good. A new machine has to generate new keys, so it has to publish them
653
+ under new ids, which means a new agent name. The new ids are published
654
+ alongside the old ones until the old ones are retired.
655
+
656
+ Reusing the old agent name on the new machine is refused on purpose:
657
+ the restored tenant directory still holds that name's published root key,
658
+ and `init` never generates different key material under an existing key
659
+ id. The development certificates and the pairing secret are regenerated by
660
+ step 3; they are never restored.
661
+
662
+ ## Profiles and configuration
663
+
664
+ A **profile** is a named set of endpoints plus the tenant the profile is
665
+ bound to. Profiles live in `~/.aac/config`, a plain INI file with one
666
+ section per profile:
667
+
668
+ ```ini
669
+ [main]
670
+ admin_url = http://127.0.0.1:8000
671
+ data_plane_url = http://127.0.0.1:9000
672
+ tenant_id = tnt-550e8400-e29b-41d4-9716-446655440000
673
+
674
+ [stage]
675
+ admin_url = https://api.stage.cascadeauth.dev
676
+ data_plane_url = https://api.stage.cascadeauth.dev
677
+ ```
678
+
679
+ ```bash
680
+ aac profile list # every profile, including the built-in main
681
+ aac profile show stage --output table # stored and effective values, with sources
682
+ aac profile create prod --admin-url https://aac-admin.example.com \
683
+ --data-plane-url https://aac-data.example.com
684
+ aac profile update main # edit the built-in baseline
685
+ aac profile delete prod # local only; never touches the server
686
+ ```
687
+
688
+ `main` is the reserved baseline profile. It always exists, with localhost
689
+ defaults until `aac profile update main` stores real values, and cannot be
690
+ created or deleted. Every operational command selects its profile in this
691
+ order: `--profile <name>`, then the `AAC_PROFILE` environment variable, then
692
+ `main`. A selected profile that does not exist is an error, never a silent
693
+ fallback. Within the selected profile, a flag beats an environment variable
694
+ (`AAC_ADMIN_URL`, `AAC_DATA_PLANE_URL`, `AAC_TENANT_ID`), which beats the
695
+ stored value.
696
+
697
+ `tenant_id` is managed for you. Profile commands display it but never ask
698
+ for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
699
+ It is your organisation's identifier in the federation, allocated by AAC at
700
+ registration in the form `tnt-<uuid>`, and it never changes.
701
+
702
+ `AAC_CLI_HOME` relocates the whole `~/.aac` tree. Use it for a throwaway
703
+ test home, or to keep separate accounts hard-walled from each other:
704
+
705
+ ```bash
706
+ AAC_CLI_HOME=$HOME/.aac-prod aac profile list
707
+ ```
708
+
709
+ ### Credentials
710
+
711
+ Two credential files can coexist under `~/.aac/credentials/`:
712
+
713
+ * `<tenant-id>` holds the tenant API key, written once by registration and
714
+ printed exactly once. The sidecar and the data-plane commands
715
+ (`aac chain`, `aac trust-anchor`) present it. Only a hash of it exists
716
+ on the server, so a lost key is replaced, never recovered.
717
+ * `<tenant-id>.session` holds the short-lived session that `aac sso login`
718
+ creates. The administration commands (`aac tenant`, `aac sso`) use it
719
+ automatically. Sessions cannot be refreshed: when one expires, any
720
+ administration command tells you to sign in again, and there is no
721
+ refresh token that could be stolen.
722
+
723
+ ## Signing in
724
+
725
+ ```bash
726
+ aac sso login --profile stage --idp github # developer tenant: GitHub or Google
727
+ aac sso login --profile prod \
728
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0
729
+ aac sso login --profile prod \
730
+ --idp-url https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 \
731
+ --flow pkce --no-browser # print the URL instead of opening a browser
732
+ aac sso whoami --profile stage --output table # cached session and the tenant's assigned domain
733
+ aac sso logout --profile stage
734
+ ```
735
+
736
+ A developer tenant signs in with `--idp github` or `--idp google`; if you
737
+ need another sign-in provider, contact support@cascadeauth.com. An
738
+ enterprise tenant signs in through its Microsoft Entra ID connection, named
739
+ by the connection's exact URL with `--idp-url`; when several connections
740
+ are registered, the error message lists the exact commands to choose one.
741
+
742
+ Sign-in uses the device flow by default, so the browser can be on another
743
+ machine and headless hosts work. Google connections use a loopback flow
744
+ instead, because Google's device flow is scope-restricted; `--flow`
745
+ overrides the choice. `whoami` and `logout` work offline and never contact
746
+ AAC.
747
+
748
+ ## Output and exit codes
749
+
750
+ Every command that reports a result prints one JSON document to standard
751
+ output by default, with progress notes and diagnostics on standard error,
752
+ so the JSON stays parseable in scripts; `--output table` prints a readable
753
+ table instead. The three profile writers are the exception: `aac profile
754
+ create`, `update` and `delete` confirm on standard error, leave standard
755
+ output empty and take no `--output`.
756
+
757
+ | Exit code | Meaning |
758
+ |---|---|
759
+ | `0` | Success. |
760
+ | `1` | AAC or the identity provider rejected the request. |
761
+ | `2` | Usage error: an invalid flag, value or flag combination. |
762
+ | `3` | A local configuration or state problem: profile, credential file, cached session or agent. |
763
+ | `4` | Transport failure: an endpoint could not be reached. |
764
+
765
+ If AAC asks you to slow down, the command shows the interval to wait. The
766
+ CLI never replays a change on its own; wait and rerun deliberately.
767
+
768
+ ## Enterprise tenants
769
+
770
+ Enterprise tenants are registered through an onboarding ceremony: AAC
771
+ operations opens a registration window and hands the tenant operator a
772
+ bootstrap token, which the commands below read from `--bootstrap-token` or
773
+ the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorises only
774
+ the onboarding steps shown with it; every other administration command runs
775
+ under a signed-in session.
776
+
777
+ For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
778
+ Azure Active Directory or Azure AD). If your organisation needs another
779
+ identity provider, contact support@cascadeauth.com.
780
+
781
+ ### Register the tenant
782
+
783
+ Generate the tenant-admin key pair first. The CLI transmits the public half
784
+ only and refuses a private key:
785
+
786
+ ```bash
787
+ openssl genpkey -algorithm ed25519 -out tenant-admin.pem
788
+ openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
789
+ aac profile create prod --admin-url https://aac-admin.example.com \
790
+ --data-plane-url https://aac-data.example.com
791
+ aac tenant register --profile prod --display-name 'Example Corporation' \
792
+ --contact ops@example.com \
793
+ --workload-spiffe-id spiffe://example.com/treasury-agent/v1 \
794
+ --tenant-admin-pubkey-file ./tenant-admin.public.pem \
795
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
796
+ ```
797
+
798
+ AAC allocates the tenant id; you never choose one. The response prints the
799
+ id and the tenant API key exactly once, and the CLI saves both. A profile
800
+ that is already bound to a tenant refuses to register another; use an
801
+ unbound profile. If the response is lost, rerun the same command with no
802
+ changes to the identifying flags: the CLI resumes the frozen request rather
803
+ than registering twice.
804
+
805
+ ```bash
806
+ aac tenant describe --profile prod --output table
807
+ aac tenant update --profile prod --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
808
+ --display-name 'Example Corporation Ltd'
809
+ ```
810
+
811
+ ### Connect your identity provider
812
+
813
+ An identity-provider connection is described by a JSON file
814
+ (`aac sso register-idp --help` shows the fields, with an example for a
815
+ Microsoft Entra ID tenant). Before the first
816
+ connection, generate an offline recovery key so the connection can be
817
+ repaired if the provider is ever unavailable; only the enrollment file
818
+ leaves the machine:
819
+
820
+ ```bash
821
+ aac sso generate-idp-recovery-key \
822
+ --private-key-file offline-idp-recovery-private.pem \
823
+ --enrollment-file idp-recovery-enrollment.json
824
+ aac sso enroll-idp-recovery-key --profile prod \
825
+ --file idp-recovery-enrollment.json --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
826
+ aac sso register-idp --profile prod --file connection.json \
827
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
828
+ aac sso list-idp --profile prod
829
+ ```
830
+
831
+ Later connections, corrections (`aac sso replace-idp`) and the two-party
832
+ repair ceremony (`aac sso request-idp-repair`, `aac sso approve-idp-repair`)
833
+ are described by their `--help`.
834
+
835
+ ### Keys
836
+
837
+ Replacing the tenant-admin key is immediate and forward-only: the previous
838
+ key stops verifying at once and can never be reinstated. The trust-anchor
839
+ publisher signs its uploads with this key and loads it once, at start, so
840
+ sequence the change: stop the publisher, register the new public half, put
841
+ the new private key where the publisher's configuration points, start the
842
+ publisher, and confirm that its first upload is accepted under the new key.
843
+ A publisher installed as a system service reads the key path from its
844
+ environment file, which you can point at the new file. A Docker publisher's
845
+ environment and key mount are fixed when the container is created, so
846
+ overwrite the key at the mounted path and recreate the container; a plain
847
+ restart would come back on the old key.
848
+
849
+ ```bash
850
+ aac tenant rotate-admin-key --profile prod \
851
+ --tenant-admin-pubkey-file tenant-admin.public.pem
852
+ ```
853
+
854
+ A developer tenant set up with `aac init` keeps its tenant-admin key in its
855
+ tenant directory, where the publisher's configuration points. To replace a
856
+ lost key there, write the new pair in place, readable only by you, sign in,
857
+ register the new public half, and then recreate every publisher container.
858
+ Pass the tenant id and admin URL explicitly, as `aac agent status`
859
+ does, so the change cannot land on another tenant. The `openssl` commands
860
+ need OpenSSL 1.1.1 or newer, which macOS does not ship:
861
+
862
+ ```bash
863
+ cd ~/.aac/tenants/tnt-550e8400-e29b-41d4-9716-446655440000
864
+ (umask 077 && openssl genpkey -algorithm ed25519 -out tenant-admin.pem.tmp && mv tenant-admin.pem.tmp tenant-admin.pem)
865
+ (umask 077 && openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.pub.pem.tmp && mv tenant-admin.pub.pem.tmp tenant-admin.pub.pem)
866
+ aac sso login --profile stage --idp github \
867
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
868
+ --admin-url https://api.stage.cascadeauth.dev
869
+ aac tenant rotate-admin-key --profile stage \
870
+ --tenant-id tnt-550e8400-e29b-41d4-9716-446655440000 \
871
+ --admin-url https://api.stage.cascadeauth.dev \
872
+ --tenant-admin-pubkey-file tenant-admin.pub.pem
873
+ ```
874
+
875
+ The tenant API key can be rotated without downtime. `issue` stages a second
876
+ active key in a new file and never replaces the current one; move every
877
+ client to it, then retire the old key by its exact id:
878
+
879
+ ```bash
880
+ aac tenant api-key list --profile prod
881
+ aac tenant api-key issue --profile prod
882
+ aac tenant api-key retire --profile prod --key-id ak_0123456789abcdef --yes
883
+ ```
884
+
885
+ A lost or suspected-compromised key is replaced instead: every active key is
886
+ revoked and one fresh key is printed once.
887
+
888
+ ```bash
889
+ aac tenant reissue-api-key --profile prod
890
+ ```
891
+
892
+ ### Domains and workloads
893
+
894
+ Every tenant gets a hosted trust domain from AAC; registration saves it in
895
+ the profile, and `aac tenant assign-hosted-domain` assigns one to a tenant
896
+ that has none yet. To use your own DNS domain as a trust domain instead,
897
+ prove control of it with a DNS TXT record, then bind it:
898
+
899
+ ```bash
900
+ aac tenant issue-domain-challenge --profile prod --domain example.com
901
+ # Publish the TXT record the command prints, then:
902
+ aac tenant verify-domain --profile prod --domain example.com
903
+ aac tenant bind-trust-domain --profile prod --trust-domain example.com
904
+ aac tenant list-trust-domains --profile prod --output table
905
+ ```
906
+
907
+ Workloads are the identities your sidecars run under:
908
+
909
+ ```bash
910
+ aac tenant add-workload --profile prod --spiffe-id spiffe://example.com/payroll/v1 \
911
+ --display-name Payroll
912
+ aac tenant list-workloads --profile prod
913
+ aac tenant deactivate-workload --profile prod --workload-id wl-2f9c1e --reason 'service retired'
914
+ ```
915
+
916
+ Deactivation is terminal, and a deactivated SPIFFE id stays reserved.
917
+
918
+ ### Inspect trust material and audit a chain
919
+
920
+ ```bash
921
+ aac trust-anchor list --profile prod --output table # every published key, every lifecycle state
922
+ aac trust-anchor describe --profile prod --kid starter-root-v1
923
+ aac chain show --profile prod \
924
+ --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 --output table
925
+ aac chain show --profile prod \
926
+ --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 \
927
+ --render --render-out ./chain.html
928
+ ```
929
+
930
+ `chain show` accepts any hop's token id or the chain root id and shows the
931
+ chain to any tenant that took part in it. The timeline carries metadata
932
+ only; the business content of each step stays in your own audit stream.
933
+
934
+ ## Getting help
935
+
936
+ `aac --help` lists the command groups and `aac <command> --help` shows every
937
+ flag with its default. Errors name the exact command to run next.
938
+
939
+ ## License
940
+
941
+ Apache-2.0.