aac-cli 0.1.1__tar.gz → 0.1.3__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 (34) hide show
  1. {aac_cli-0.1.1 → aac_cli-0.1.3}/PKG-INFO +271 -12
  2. {aac_cli-0.1.1 → aac_cli-0.1.3}/README.md +269 -11
  3. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/__init__.py +1 -1
  4. aac_cli-0.1.3/aac_cli/admin_key_pem.py +236 -0
  5. aac_cli-0.1.3/aac_cli/api_key_rotation_state.py +192 -0
  6. aac_cli-0.1.3/aac_cli/cli.py +5587 -0
  7. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/config.py +72 -39
  8. aac_cli-0.1.3/aac_cli/idp_recovery.py +489 -0
  9. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/profiles.py +28 -16
  10. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/registration_state.py +135 -75
  11. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/sso_login.py +55 -34
  12. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/PKG-INFO +271 -12
  13. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/SOURCES.txt +9 -0
  14. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/requires.txt +1 -0
  15. {aac_cli-0.1.1 → aac_cli-0.1.3}/pyproject.toml +5 -2
  16. aac_cli-0.1.3/tests/test_aac_cli.py +3254 -0
  17. aac_cli-0.1.3/tests/test_admin_key_pem.py +331 -0
  18. aac_cli-0.1.3/tests/test_api_key_reissue_cli.py +313 -0
  19. aac_cli-0.1.3/tests/test_api_key_rotation_cli.py +895 -0
  20. aac_cli-0.1.3/tests/test_api_key_rotation_state.py +177 -0
  21. aac_cli-0.1.3/tests/test_hosted_domains_cli.py +533 -0
  22. aac_cli-0.1.3/tests/test_idp_recovery_cli.py +1140 -0
  23. {aac_cli-0.1.1 → aac_cli-0.1.3}/tests/test_profile_cli.py +32 -73
  24. {aac_cli-0.1.1 → aac_cli-0.1.3}/tests/test_registration_recovery_cli.py +67 -16
  25. {aac_cli-0.1.1 → aac_cli-0.1.3}/tests/test_registration_state.py +71 -35
  26. {aac_cli-0.1.1 → aac_cli-0.1.3}/tests/test_sso_login_cli.py +204 -132
  27. aac_cli-0.1.1/aac_cli/cli.py +0 -2780
  28. aac_cli-0.1.1/tests/test_aac_cli.py +0 -1734
  29. {aac_cli-0.1.1 → aac_cli-0.1.3}/LICENSE +0 -0
  30. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli/__main__.py +0 -0
  31. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/dependency_links.txt +0 -0
  32. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/entry_points.txt +0 -0
  33. {aac_cli-0.1.1 → aac_cli-0.1.3}/aac_cli.egg-info/top_level.txt +0 -0
  34. {aac_cli-0.1.1 → aac_cli-0.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aac-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
5
5
  Author: Agent Authority Cloud Project
6
6
  License-Expression: Apache-2.0
@@ -8,6 +8,7 @@ Requires-Python: >=3.10
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: httpx>=0.28
11
+ Requires-Dist: cryptography>=42.0
11
12
  Provides-Extra: test
12
13
  Requires-Dist: pytest>=8.0; extra == "test"
13
14
  Requires-Dist: pytest-httpx>=0.30; extra == "test"
@@ -98,9 +99,51 @@ aac sso login --profile dev # thereafter: plain login
98
99
  # Tenant ids are the server-allocated canonical tnt-<uuid> form
99
100
  # (B154 PR 5) — captured at registration / shown by `aac profile show`.
100
101
  aac tenant list # which tenant ids exist (no 409 probe)
101
- aac tenant describe --tenant-id tnt-<uuid> # workloads + key METADATA
102
+ aac tenant describe --profile dev # assigned domain, workloads + key METADATA
102
103
  aac tenant update --tenant-id tnt-<uuid> --display-name "ACME Manufacturing Inc"
103
104
 
105
+ # B121/B228 — business-domain control proof and bounded claim lifecycle.
106
+ # Registration can capture up to 16 pending rows with repeatable --tenant-domain.
107
+ # The issue/verify/release/revoke commands
108
+ # require the owning tenant's tenant-admin session and default --tenant-id from
109
+ # the profile; tenant describe also uses the selected profile by default.
110
+ aac tenant register --display-name "ACME" --contact ops@acme.example \
111
+ --tenant-domain acme.example
112
+ aac tenant issue-domain-challenge --domain acme.example
113
+ # Publish the exact record printed above, then:
114
+ aac tenant verify-domain --domain acme.example
115
+ # B182 keeps verification and binding explicit. The binding command consumes
116
+ # current exact-name evidence (and lazily revalidates when due); verify-domain
117
+ # never performs an implicit binding write.
118
+ aac tenant bind-trust-domain --trust-domain acme.example
119
+ aac tenant list-trust-domains --output table
120
+ aac tenant describe --profile dev --output table # assigned binding status + TXT
121
+ aac tenant release-domain --domain acme.example --reason "planned transfer"
122
+ aac tenant revoke-domain --domain acme.example --reason "DNS control incident"
123
+
124
+ # If bind-trust-domain reports missing proof, its printed issue/verify/rerun
125
+ # commands pin the selected profile, effective admin URL, tenant id, and exact
126
+ # domain. They never print a session, bootstrap token, or challenge value.
127
+
128
+ # B153 — replace the tenant-admin key. Accepts and transmits the PUBLIC half
129
+ # ONLY. It never generates or writes private-key material, and it refuses a
130
+ # private key without transmitting or logging it — it does read the file you
131
+ # name, because refusing requires inspecting it.
132
+ # Replacement is IMMEDIATE and forward-only: the previous key stops
133
+ # verifying ingest JWSs at once (admin keys get NO grace window) and can
134
+ # never be reinstated.
135
+ openssl genpkey -algorithm ed25519 -out tenant-admin.pem
136
+ openssl pkey -in tenant-admin.pem -pubout -out tenant-admin.public.pem
137
+ aac tenant rotate-admin-key --tenant-admin-pubkey-file tenant-admin.public.pem
138
+ # BEFORE running it in production: stage the new PRIVATE key on the
139
+ # trust-anchor publisher host and be ready to restart it. The restart
140
+ # procedure DIFFERS by deployment path — a Docker publisher's env and key
141
+ # bind-mount are fixed at container creation, so a plain restart comes back
142
+ # on the OLD key. Follow trust_anchor_publisher/INSTALL.md, "Rotating the
143
+ # tenant-admin key". Publication stays paused until it restarts.
144
+ # --bootstrap-token (or $AAC_BOOTSTRAP_TOKEN) lets a ceremony operator
145
+ # rotate for a tenant that has no session.
146
+
104
147
  # B172 — post-registration workload lifecycle. --tenant-id is optional
105
148
  # here: the selected profile supplies it by default. JSON is default;
106
149
  # every command also supports --output table.
@@ -130,17 +173,90 @@ aac trust-anchor list --tenant-id tnt-<uuid> # both roles, all states
130
173
  aac trust-anchor list --role tenant-admin # ingest-signing keys only
131
174
  aac trust-anchor describe --kid key-1 # lifecycle + public PEM
132
175
  aac trust-anchor ingest-history --artifact-class root-keys --limit 20
176
+ # Session-only emergency/environment-retirement operation. Terminal, may
177
+ # revoke the last root, and cannot be authorized by bootstrap token/API key:
178
+ aac trust-anchor revoke --tenant-id tnt-<uuid> --kid key-1 \
179
+ --reason "root signing key suspected compromised"
180
+
181
+ # B144 — before the first tenant IdP, Tenant Ops generates an Ed25519
182
+ # offline recovery keypair. The private PEM stays offline and mode 0600;
183
+ # only the non-secret enrollment JSON crosses to AAC Ops/control plane.
184
+ aac sso generate-idp-recovery-key \
185
+ --private-key-file offline-idp-recovery-private.pem \
186
+ --enrollment-file idp-recovery-enrollment.json
187
+ aac sso enroll-idp-recovery-key \
188
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
189
+ --file idp-recovery-enrollment.json \
190
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
133
191
 
134
192
  # B140 — register a tenant↔IdP connection (Eng Spec §XVII.4; the sso
135
193
  # noun). The connection config rides as a JSON file (nested claims
136
- # mapping — see --help for an Entra example). First-IdP onboarding is
137
- # the ops ceremony; later IdPs ride your cached session instead:
194
+ # mapping — see --help for an Entra example). Initial recovery-key
195
+ # enrollment above happens before first-IdP registration. First-IdP
196
+ # registration is the ops ceremony; later IdPs ride a cached session:
138
197
  # (--tenant-id takes the canonical tnt-<uuid> id captured at your
139
198
  # tenant's registration — B154 PR 5: a dotted value fails locally.)
140
199
  aac sso register-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
141
200
  --file connection.json \
142
201
  --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
143
202
 
203
+ # B151 — discover the opaque connection id and current revision without a
204
+ # session. The public response contains only safe issuer metadata, a stable
205
+ # idp_connections.id, and the numeric ETag needed for correction:
206
+ aac sso list-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
207
+
208
+ # B151 — correct the COMPLETE connection document. Pin the revision you
209
+ # reviewed; the CLI sends it with the cached tenant-admin session and never
210
+ # substitutes a newer revision. Repeating after a lost response is safe:
211
+ aac sso replace-idp --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
212
+ --connection-id <id-from-list-idp> \
213
+ --revision <revision-from-list-idp> \
214
+ --file corrected-connection.json
215
+
216
+ # B144 — if the IdP is unavailable, the same B151 replacement is authorized
217
+ # by a three-command two-party ceremony. Tenant Ops signs the exact corrected
218
+ # file and receives a request id plus REPAIR-INTENT fingerprint. AAC Ops
219
+ # compares both through the incident channel, approves that exact intent, and
220
+ # manually notifies Tenant Ops. Tenant Ops then runs replace-idp with the same
221
+ # request/file/revision/key. The CLI internally redeems a ten-minute,
222
+ # single-use capability, holds it only in memory, and never prints or stores it.
223
+ # It grants no login, data-plane access, or general administration.
224
+ aac sso request-idp-repair \
225
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
226
+ --connection-id <id-from-list-idp> \
227
+ --revision <revision-from-list-idp> \
228
+ --file corrected-connection.json \
229
+ --recovery-key-file offline-idp-recovery-private.pem
230
+ aac sso approve-idp-repair \
231
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
232
+ --connection-id <id-from-list-idp> \
233
+ --repair-request-id <tenant-provided-request-id> \
234
+ --repair-intent-fingerprint sha256:<tenant-provided-intent-hash> \
235
+ --bootstrap-token "$AAC_BOOTSTRAP_TOKEN"
236
+ aac sso replace-idp \
237
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
238
+ --connection-id <id-from-list-idp> \
239
+ --revision <same-request-revision> \
240
+ --file corrected-connection.json \
241
+ --repair-request-id <approved-request-id> \
242
+ --recovery-key-file offline-idp-recovery-private.pem
243
+
244
+ # These fingerprints are different: recovery_key_fingerprint identifies the
245
+ # enrolled Ed25519 PUBLIC key; repair_intent_fingerprint identifies one exact
246
+ # tenant/connection/revision/document/key intent. Neither replaces the other.
247
+
248
+ # Recovery-key lifecycle is normal-session administration. Generate a fresh
249
+ # pair before rotation; old material cannot return. Revocation is terminal.
250
+ aac sso list-idp-recovery-keys --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
251
+ aac sso rotate-idp-recovery-key \
252
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
253
+ --file new-idp-recovery-enrollment.json \
254
+ --replaces-recovery-key-id <current-active-key-id>
255
+ aac sso revoke-idp-recovery-key \
256
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
257
+ --recovery-key-id <current-active-key-id> \
258
+ --reason "offline media lost"
259
+
144
260
  # B142 — register a SHARED developer-tier connection (platform
145
261
  # operator ceremony ONLY; owned by no tenant). GitHub registers with
146
262
  # jwks_static {"keys": []} + explicit endpoints (GitHub is not an OIDC
@@ -163,9 +279,9 @@ aac sso login --flow pkce --no-browser # print the URL, don't launch
163
279
  # any admin verb tells you to re-run `aac sso login` — there is no
164
280
  # refresh token to steal, and no silent re-authentication.
165
281
 
166
- # B155 — session niceties (cache-file operations; the CLI never
167
- # parses the token itself). whoami exits 0 = live, 3 = expired/none (§14).
168
- aac sso whoami --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
282
+ # B155/B239 — local session/profile inspection; no server call or JWT parsing.
283
+ # whoami exits 0 = unexpired cache, 3 = expired/none (§14).
284
+ aac sso whoami --profile dev --output table # cached session + saved assigned domain
169
285
  aac sso logout --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
170
286
  ```
171
287
 
@@ -248,6 +364,104 @@ one-time API key. The CLI binds the profile and transitions the state to
248
364
  `completed_credential_reissue_required`, directing the operator to the
249
365
  separate B173 reissue capability; B171 does not implement reissue.
250
366
 
367
+ ### Replace or recover a tenant API key (B173)
368
+
369
+ After establishing an own-tenant `tenant-admin` session, run:
370
+
371
+ ```console
372
+ aac tenant reissue-api-key \
373
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
374
+ --profile production
375
+ ```
376
+
377
+ This is immediate replacement, not B98/B189 graceful rotation: the server
378
+ revokes every ACTIVE tenant API key and returns one fresh `aac_ak_*` value once.
379
+ The CLI makes one HTTP request per invocation and atomically replaces the
380
+ mode-`0600` credential file only after validating the complete success
381
+ response. A transport interruption leaves the local file untouched and tells
382
+ the administrator to run the command again; the next explicit invocation is a
383
+ fresh attempt that revokes any unknown prior result. There is no automatic
384
+ retry or secret-response journal. A successful B173 replacement also removes
385
+ any superseded non-secret B189 local rotation record. It does not silently
386
+ delete the separate staged key file; that value is now revoked and must be
387
+ removed through the approved secure-file procedure.
388
+
389
+ After success the CLI prints copy/paste-ready `aac trust-anchor list` and curl
390
+ commands for API-key-only verification against `GET /v1/trust-anchors`. The
391
+ curl recipe streams the stored credential through stdin configuration rather
392
+ than writing plaintext into shell history or an OS process argument.
393
+
394
+ Operator-assisted onboarding recovery is exceptional and requires both the
395
+ explicitly opened bootstrap ceremony and the matching completed registration
396
+ locator. Load `AAC_BOOTSTRAP_TOKEN` through the approved secret-handling
397
+ process, then run:
398
+
399
+ ```console
400
+ aac tenant reissue-api-key \
401
+ --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000 \
402
+ --registration-request-id treq-550e8400-e29b-41d4-a716-446655440000 \
403
+ --profile onboarding
404
+ ```
405
+
406
+ The server admits this only before a tenant login path exists. The `treq-*`
407
+ value is a locator, never a credential. Tenant API keys are separate from
408
+ tenant-admin Ed25519 keys and B144 offline IdP-recovery material. See
409
+ **SOP-010 — Recover or replace a tenant API key** in the AAC design vault's
410
+ `sops/` directory for the tenant-administrator and AAC Ops procedures.
411
+
412
+ ### Rotate a tenant API key without downtime (B189)
413
+
414
+ Planned rotation uses a nested command family and an own-tenant
415
+ `tenant-admin` AAC session:
416
+
417
+ ```console
418
+ aac tenant api-key list --profile production
419
+ aac tenant api-key issue --profile production
420
+ ```
421
+
422
+ `issue` saves a non-secret `akr-*` issuance identity before sending one HTTP
423
+ request. It writes the returned plaintext only to a new mode-`0600` staged
424
+ file (default `~/.aac/credentials/<tenant-id>.next`), refuses existing files
425
+ and symlinks, requires any custom parent directory to exist, preserves that
426
+ directory's permissions, and never replaces the current profile credential. Its success
427
+ output contains metadata and a curl recipe that reads the staged file without
428
+ putting plaintext in shell history or process arguments.
429
+
430
+ Move every client to the staged key through the approved secret store, verify
431
+ client traffic, then list again and select the old key by its exact non-secret
432
+ `key_id`:
433
+
434
+ ```console
435
+ aac tenant api-key retire \
436
+ --key-id ak_0123456789abcdef \
437
+ --yes \
438
+ --profile production
439
+ ```
440
+
441
+ Retirement requires two ACTIVE keys and is terminal. The CLI fetches the
442
+ authoritative set immediately before the request, names the exact key that will
443
+ remain, and never guesses by age or position. It does not silently promote the
444
+ staged file into the current credential location. Do that only through the
445
+ approved local secret-handling procedure after the selected old key retires.
446
+
447
+ If an issue response is ambiguous, rerun the same command. The saved identity
448
+ is reused: a non-committed attempt may return the new plaintext, while a
449
+ committed attempt reports that plaintext is unavailable and identifies the
450
+ unknown new row through `api-key list`. Retire that unknown row, leaving the
451
+ known old credential ACTIVE, then issue again. No automatic retry or plaintext
452
+ journal exists. A profile with unresolved rotation state cannot be deleted.
453
+ See **SOP-011 — Rotate a tenant API key without downtime** in the design vault.
454
+
455
+ `~/.aac/idp-repair-requests/<tenant_id>/<connection_id>.json` is B144's
456
+ separate AAC-managed IdP repair record (directory 0700, file 0600). It is
457
+ written before `request-idp-repair` transmits anything and contains the exact
458
+ connection document, document digest, request id, recovery-key fingerprint,
459
+ and public signature. It contains neither the offline private key nor the
460
+ short-lived AAC repair capability. An identical retry reuses the request id;
461
+ a different live intent is refused locally. Preserve this file with the
462
+ incident until replacement is complete, and do not edit, rename, copy between
463
+ tenants, or delete it to “cancel” a request that may already exist server-side.
464
+
251
465
  ### `AAC_CLI_HOME` — isolated homes
252
466
 
253
467
  `AAC_CLI_HOME` relocates the whole `~/.aac` tree (config +
@@ -281,8 +495,9 @@ coexistence, Eng Spec §XVII.9):
281
495
  expiry fails fast locally with the re-login message instead of
282
496
  dialing out; the server's own session 401s carry the same
283
497
  remediation (the server stays authoritative). `aac sso logout`
284
- removes the file; `aac sso whoami` reports its tenant + expiry
285
- (B155 — both operate on the cache file only).
498
+ removes the file; `aac sso whoami` reports its tenant + expiry and, when
499
+ available, the matching profile's saved AAC-assigned domain. Both commands
500
+ operate locally; `whoami` does not verify current server-side binding status.
286
501
 
287
502
  ## Your tenant_id
288
503
 
@@ -298,9 +513,14 @@ captures it into the selected profile, and it is immutable, public,
298
513
  and never reused — like an AWS account ID. `--parent-tenant-id` takes
299
514
  the parent org's canonical `tnt-` id — the server resolves handles to
300
515
  internal row ids; no database identifier ever crosses the API (B118
301
- D3/D4). Domain ownership verification (DNS TXT) is a separate,
302
- additive control tracked in the `tenant_domains` table — the challenge
303
- flow ships in a follow-up increment.
516
+ D3/D4). B121 domain ownership verification is a separate, additive
517
+ control tracked in `tenant_domains`: `_aac-challenge.<domain>.` TXT
518
+ `aac-verify=<token>`, issued/verified through the tenant-admin commands
519
+ above. A verified business domain is evidence only. B182's separate
520
+ `bind-trust-domain` request admits a domain's first SPIFFE binding only from
521
+ B228-current evidence for the exact same name; parent-domain proof does not
522
+ cover subdomains. SPIFFE-valid values outside the DNS proof grammar remain
523
+ ceremony-only, and existing binding history remains B181.
304
524
 
305
525
  ## Semantics worth knowing
306
526
 
@@ -318,6 +538,9 @@ flow ships in a follow-up increment.
318
538
  configuration or state (missing profile/credential, malformed INI,
319
539
  bad registration state) / 4 transport failure (unreachable or timed
320
540
  out — the retryable class).
541
+ * B159 rate limiting: a numeric server `Retry-After` is rendered beside
542
+ the stable 429/503 error. The CLI never automatically replays a
543
+ mutation; wait for the displayed interval and rerun deliberately.
321
544
 
322
545
  ## Tests
323
546
 
@@ -325,3 +548,39 @@ flow ships in a follow-up increment.
325
548
  needed. Against a live stack: bring up the joined topology
326
549
  (`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
327
550
  flags at localhost.
551
+
552
+ ## Assigned tenant domains (B239)
553
+
554
+ Registration automatically saves the server-assigned `hosted_trust_domain` in
555
+ the selected profile. The CLI displays the full name, explains its purpose in
556
+ SPIFFE identities/certificates/trust configuration, and prints the command for
557
+ finding it again. No manual copy is required. With JSON output, this guidance
558
+ goes to stderr and stdout remains one JSON document; table output includes it
559
+ inline. Completed-registration recovery provides the same saved-name guidance
560
+ without changing credential-reissue requirements.
561
+
562
+ ```sh
563
+ aac profile show dev --output table # saved locally, offline
564
+ aac tenant describe --profile dev --output table # assigned name + current binding status
565
+ aac tenant list-trust-domains --profile dev --output table # current ACTIVE bindings
566
+ aac tenant list-trust-domains --profile dev --all --output table # includes revoked history
567
+ aac sso whoami --profile dev --output table # cached session + matching saved name
568
+ ```
569
+
570
+ `tenant describe` accepts an optional `--tenant-id`; the usual flag → environment
571
+ → selected profile precedence applies. Its response comes from AAC. Binding
572
+ table output labels AAC-assigned/custom origin, ACTIVE/REVOKED status and
573
+ inherited scope. Registration, describe, profile show and binding-list JSON
574
+ fields retain their existing shapes. `whoami` adds optional
575
+ `hosted_trust_domain` and `hosted_trust_domain_source: "profile:<name>"` fields
576
+ when that profile's saved tenant matches the inspected session. These fields
577
+ describe locally saved metadata, not current binding eligibility. Tenant
578
+ overrides never display another tenant's saved domain; session exit codes and
579
+ offline operation are unchanged. Profile show says "not saved in this profile"
580
+ when metadata is absent, without asserting that AAC has no allocation.
581
+
582
+ Existing tenant admins use `aac tenant assign-hosted-domain --profile dev`;
583
+ explicit eligible restoration uses `aac tenant reactivate-hosted-domain --profile dev`.
584
+ Neither command registers another tenant or asks for a DNS name.
585
+ See [the hosted-domain guide](../../docs/b239-hosted-trust-domains.md) for
586
+ custom-domain progression, recovery compatibility and lifecycle limits.