aac-cli 0.1.2__tar.gz → 0.1.4__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 (46) hide show
  1. {aac_cli-0.1.2 → aac_cli-0.1.4}/PKG-INFO +208 -10
  2. {aac_cli-0.1.2 → aac_cli-0.1.4}/README.md +206 -9
  3. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/__init__.py +1 -1
  4. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/cli.py +250 -34
  5. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/config.py +45 -2
  6. aac_cli-0.1.4/aac_cli/config_render.py +266 -0
  7. aac_cli-0.1.4/aac_cli/dev_material.py +405 -0
  8. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/idp_recovery.py +4 -50
  9. aac_cli-0.1.4/aac_cli/init_cli.py +1392 -0
  10. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/profiles.py +12 -2
  11. aac_cli-0.1.4/aac_cli/secure_files.py +200 -0
  12. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/sso_login.py +85 -16
  13. aac_cli-0.1.4/aac_cli/workspace_cli.py +525 -0
  14. aac_cli-0.1.4/aac_cli/workspace_health.py +156 -0
  15. aac_cli-0.1.4/aac_cli/workspace_layout.py +467 -0
  16. aac_cli-0.1.4/aac_cli/workspace_manifest.py +144 -0
  17. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/PKG-INFO +208 -10
  18. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/SOURCES.txt +16 -1
  19. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/requires.txt +1 -0
  20. {aac_cli-0.1.2 → aac_cli-0.1.4}/pyproject.toml +7 -1
  21. aac_cli-0.1.4/tests/test_b241_edge_paths.py +925 -0
  22. aac_cli-0.1.4/tests/test_config_render.py +177 -0
  23. aac_cli-0.1.4/tests/test_dev_material.py +127 -0
  24. aac_cli-0.1.4/tests/test_hosted_domains_cli.py +569 -0
  25. aac_cli-0.1.4/tests/test_init_cli.py +1417 -0
  26. aac_cli-0.1.4/tests/test_inventory_docs.py +70 -0
  27. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_sso_login_cli.py +121 -1
  28. aac_cli-0.1.4/tests/test_workspace_cli.py +400 -0
  29. {aac_cli-0.1.2 → aac_cli-0.1.4}/LICENSE +0 -0
  30. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/__main__.py +0 -0
  31. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/admin_key_pem.py +0 -0
  32. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/api_key_rotation_state.py +0 -0
  33. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli/registration_state.py +0 -0
  34. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/dependency_links.txt +0 -0
  35. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/entry_points.txt +0 -0
  36. {aac_cli-0.1.2 → aac_cli-0.1.4}/aac_cli.egg-info/top_level.txt +0 -0
  37. {aac_cli-0.1.2 → aac_cli-0.1.4}/setup.cfg +0 -0
  38. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_aac_cli.py +0 -0
  39. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_admin_key_pem.py +0 -0
  40. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_api_key_reissue_cli.py +0 -0
  41. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_api_key_rotation_cli.py +0 -0
  42. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_api_key_rotation_state.py +0 -0
  43. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_idp_recovery_cli.py +0 -0
  44. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_profile_cli.py +0 -0
  45. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_registration_recovery_cli.py +0 -0
  46. {aac_cli-0.1.2 → aac_cli-0.1.4}/tests/test_registration_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aac-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -9,6 +9,7 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: httpx>=0.28
11
11
  Requires-Dist: cryptography>=42.0
12
+ Requires-Dist: PyYAML>=6.0
12
13
  Provides-Extra: test
13
14
  Requires-Dist: pytest>=8.0; extra == "test"
14
15
  Requires-Dist: pytest-httpx>=0.30; extra == "test"
@@ -99,14 +100,14 @@ aac sso login --profile dev # thereafter: plain login
99
100
  # Tenant ids are the server-allocated canonical tnt-<uuid> form
100
101
  # (B154 PR 5) — captured at registration / shown by `aac profile show`.
101
102
  aac tenant list # which tenant ids exist (no 409 probe)
102
- aac tenant describe --tenant-id tnt-<uuid> # workloads + key METADATA
103
+ aac tenant describe --profile dev # assigned domain, workloads + key METADATA
103
104
  aac tenant update --tenant-id tnt-<uuid> --display-name "ACME Manufacturing Inc"
104
105
 
105
106
  # B121/B228 — business-domain control proof and bounded claim lifecycle.
106
107
  # Registration can capture up to 16 pending rows with repeatable --tenant-domain.
107
108
  # The issue/verify/release/revoke commands
108
109
  # require the owning tenant's tenant-admin session and default --tenant-id from
109
- # the profile; the established tenant describe command keeps its explicit id.
110
+ # the profile; tenant describe also uses the selected profile by default.
110
111
  aac tenant register --display-name "ACME" --contact ops@acme.example \
111
112
  --tenant-domain acme.example
112
113
  aac tenant issue-domain-challenge --domain acme.example
@@ -117,7 +118,7 @@ aac tenant verify-domain --domain acme.example
117
118
  # never performs an implicit binding write.
118
119
  aac tenant bind-trust-domain --trust-domain acme.example
119
120
  aac tenant list-trust-domains --output table
120
- aac tenant describe --tenant-id tnt-<uuid> --output table # status + TXT
121
+ aac tenant describe --profile dev --output table # assigned binding status + TXT
121
122
  aac tenant release-domain --domain acme.example --reason "planned transfer"
122
123
  aac tenant revoke-domain --domain acme.example --reason "DNS control incident"
123
124
 
@@ -272,16 +273,17 @@ aac sso register-idp --shared --file shared-github.json \
272
273
  # admin verb (tenant list/describe/update, self-serve register-idp)
273
274
  # then rides the session automatically.
274
275
  aac sso login --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
275
- aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # >1 IdP
276
+ aac sso login --idp github # developer tenant: GitHub or Google sign-in
277
+ aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # enterprise IdP, by issuer URL
276
278
  aac sso login --flow pkce --no-browser # print the URL, don't launch
277
279
 
278
280
  # Sessions are REFRESH-LESS by design (Eng Spec §XVII.2): on expiry
279
281
  # any admin verb tells you to re-run `aac sso login` — there is no
280
282
  # refresh token to steal, and no silent re-authentication.
281
283
 
282
- # B155 — session niceties (cache-file operations; the CLI never
283
- # parses the token itself). whoami exits 0 = live, 3 = expired/none (§14).
284
- aac sso whoami --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
284
+ # B155/B239 — local session/profile inspection; no server call or JWT parsing.
285
+ # whoami exits 0 = unexpired cache, 3 = expired/none (§14).
286
+ aac sso whoami --profile dev --output table # cached session + saved assigned domain
285
287
  aac sso logout --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
286
288
  ```
287
289
 
@@ -495,8 +497,9 @@ coexistence, Eng Spec §XVII.9):
495
497
  expiry fails fast locally with the re-login message instead of
496
498
  dialing out; the server's own session 401s carry the same
497
499
  remediation (the server stays authoritative). `aac sso logout`
498
- removes the file; `aac sso whoami` reports its tenant + expiry
499
- (B155 — both operate on the cache file only).
500
+ removes the file; `aac sso whoami` reports its tenant + expiry and, when
501
+ available, the matching profile's saved AAC-assigned domain. Both commands
502
+ operate locally; `whoami` does not verify current server-side binding status.
500
503
 
501
504
  ## Your tenant_id
502
505
 
@@ -547,3 +550,198 @@ ceremony-only, and existing binding history remains B181.
547
550
  needed. Against a live stack: bring up the joined topology
548
551
  (`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
549
552
  flags at localhost.
553
+
554
+ ## Guided setup: `aac init` and `aac workspace`
555
+
556
+ `aac init` takes a developer from an installed CLI to a complete, runnable
557
+ local workload in one command. It signs you in, registers a developer tenant
558
+ (or reuses the one your profile is already bound to), takes the AAC-assigned
559
+ hosted trust domain, registers the sample workload, generates development
560
+ keys and certificates, registers the tenant-admin public key together with a
561
+ new tenant, and writes a complete sidecar configuration plus the publisher
562
+ environment and a Compose variable file.
563
+
564
+ ```bash
565
+ # Developer tier: sign in with GitHub or Google. The command shows what it
566
+ # will create and asks before registering the tenant.
567
+ aac init --profile stage --admin-url https://api.stage.cascadeauth.dev \
568
+ --data-plane-url https://api.stage.cascadeauth.dev \
569
+ --trust-url https://trust.stage.cascadeauth.dev \
570
+ --display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github
571
+
572
+ # Scripted: every prompt has a flag; --create-tenant acknowledges creating a
573
+ # permanent tenant when stdin is not a terminal.
574
+ aac init --profile stage --admin-url ... --trust-url ... \
575
+ --display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github --create-tenant
576
+ ```
577
+
578
+ Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in, hosted trust
579
+ domain, workload, material and rendering. A rerun prints every step, marking the ones
580
+ already done. The sign-in you chose with `--idp` is remembered in the workspace, so a
581
+ rerun (or `aac sso login --idp github` by hand when a session expires) never asks you
582
+ to pick between GitHub and Google. Workspace commands take `--workspace <name>`; the
583
+ profile is recorded in the workspace, so `--profile` is optional there and only checked.
584
+
585
+ What it creates, and where:
586
+
587
+ * `~/.aac/workspaces/<name>/` — the **workspace** for one workload (default
588
+ name `starter`): `pki/` with what the sidecar container mounts — the
589
+ workload, terminal-attestation and localhost TLS key pairs, this
590
+ workspace's root-signing key and the outbound CA bundle; `pair/` with what
591
+ both containers mount at `/run/secrets` — the pairing secret and the public
592
+ development CA certificate; `ca/` with the development CA private key (kept
593
+ out of both mounts so no container ever sees the key that mints
594
+ identities); `sidecar-config.yaml`; `compose.env` (non-secret variables for
595
+ a Compose consumer); and an internal manifest that lets a rerun resume.
596
+ * `~/.aac/tenants/<tenant-id>/` — **tenant-level** material shared by every
597
+ workspace of that tenant: the tenant-admin signing key, the publisher's
598
+ input directories (`root-keys/`, `spiffe-bundle/` — public halves only) and
599
+ the rendered `publisher.env`.
600
+ * The tenant API key stays where the CLI always kept it,
601
+ `~/.aac/credentials/<tenant-id>`; the rendered configuration references it
602
+ by path.
603
+
604
+ ### What each file is for, and what to back up
605
+
606
+ The table below is rendered from the CLI's own layout module and checked by a test, so it
607
+ cannot drift from the code. `<workspace>` is the workspace name (default `starter`) and
608
+ `<tenant-id>` the server-allocated tenant id.
609
+
610
+ <!-- inventory-table:start -->
611
+ | Material | Where | Used for | Lifetime | Back up? |
612
+ |---|---|---|---|---|
613
+ | 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` |
614
+ | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
615
+ | 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`); production custody belongs in an HSM/KMS, not a password manager |
616
+ | root_signing_key | `~/.aac/workspaces/<workspace>/pki/root.pem`, `~/.aac/workspaces/<workspace>/pki/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<workspace>-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 workspace name) | No. never restore: a replacement workspace gets a new name and therefore a new key id, published alongside the old one until the old one is retired |
617
+ | development_ca | `~/.aac/workspaces/<workspace>/ca/dev-ca.key`, `~/.aac/workspaces/<workspace>/pki/dev-ca.crt`, `~/.aac/workspaces/<workspace>/pair/dev-ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<workspace>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac workspace renew --ca` | No. development only; reissue |
618
+ | workload_svid | `~/.aac/workspaces/<workspace>/pki/workload.key`, `~/.aac/workspaces/<workspace>/pki/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac workspace renew` | No. development only; reissue |
619
+ | terminal_attestation | `~/.aac/workspaces/<workspace>/pki/terminal.key`, `~/.aac/workspaces/<workspace>/pki/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; kept separate from the workload key so neither can forge the other's role | 1 day; `aac workspace renew` | No. development only; reissue |
620
+ | localhost_tls | `~/.aac/workspaces/<workspace>/pki/server.key`, `~/.aac/workspaces/<workspace>/pki/server.crt` | the sidecar's HTTPS listener | 1 day; `aac workspace renew` | No. development only; reissue |
621
+ | outbound_ca_bundle | `~/.aac/workspaces/<workspace>/pki/outbound-ca.pem` | the sidecar verifies TLS to AAC and to itself | rebuilt by renew; `aac workspace renew` | No. derived |
622
+ | pairing_secret | `~/.aac/workspaces/<workspace>/pair/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 |
623
+ | workspace_manifest | `~/.aac/workspaces/<workspace>/manifest.json` | the workspace's identities and progress; every `workspace` verb reads it first | for the life of the workspace | **Recommended.** cannot be recreated by any command; without it start a new workspace under a new name (see the machine-loss procedure) |
624
+ | 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 workspaces | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement workspace 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) |
625
+ | rendered_files | `~/.aac/workspaces/<workspace>/sidecar-config.yaml`, `~/.aac/workspaces/<workspace>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret configuration derived from the manifest | re-rendered on demand; `aac workspace render --force` | No. derived; `aac workspace render --force` recreates them |
626
+ <!-- inventory-table:end -->
627
+
628
+ #### If you lose this machine
629
+
630
+ Back up two things while the machine is healthy: the tenant API key at
631
+ `~/.aac/credentials/<tenant-id>`, and the whole tenant directory
632
+ `~/.aac/tenants/<tenant-id>/` (the admin signing key plus the `root-keys/` and
633
+ `spiffe-bundle/` public inputs the publisher has already published). The workspace
634
+ manifest is worth keeping too, but a workspace can always be replaced. Then, on the new
635
+ machine:
636
+
637
+ 1. Install the CLI and `aac profile create <name> --admin-url … --data-plane-url …`.
638
+ Sign in with `aac sso login --idp github --tenant-id <tenant-id> --profile <name>`
639
+ (or `--issuer <url>` for an enterprise connection): the remaining
640
+ steps register a workload and read the tenant's admin-key state, which need a
641
+ tenant-admin session whatever way the tenant was registered (a ceremony bootstrap
642
+ token only covers registration and hosted-domain assignment).
643
+ 2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>` (mode `0600`), or issue a
644
+ new one with `aac tenant reissue-api-key` if it was not backed up. Restore
645
+ `~/.aac/tenants/<tenant-id>/` from the backup (directories `0700`, files `0600`).
646
+ 3. Run `aac init --profile <name> --workspace <new-name>` with a **new** workspace name.
647
+ Root key ids are fixed per workspace name and the control plane never accepts new key
648
+ material under an old id, so the replacement workspace publishes a new id
649
+ (`<new-name>-root-v1`) and a new development CA anchor.
650
+ 4. Start the publisher from the restored tenant directory. Its root-key set now holds the
651
+ old and the new public keys, which is what the control plane requires: a set that
652
+ drops every currently ACTIVE key at once is refused. Retire the old key later by
653
+ removing its public file once nothing signs with it.
654
+
655
+ Reusing the old workspace name on the new machine is refused on purpose: the restored
656
+ tenant directory still holds that name's published root key, and `init` never generates
657
+ different key material under an existing key id. The development certificates and the
658
+ pairing secret are regenerated by step 3; they are never restored.
659
+
660
+ Every private file is created once at mode `0600` under `0700` directories
661
+ and is never overwritten; before generating anything, `init` checks every
662
+ destination and refuses with the full list if one already exists. Rerunning
663
+ `aac init` resumes an interrupted setup (outcome `resumed`), reports a
664
+ complete workspace (outcome `complete`, exit 0), or names the exact conflict
665
+ (exit 3). Every post-registration step is pinned to the workspace's tenant,
666
+ so a profile that has since been pointed at another tenant is refused rather
667
+ than mixed in. A rerun on a complete workspace validates it offline (binding,
668
+ files, permissions, certificate validity, CA fitness) and exits 3 with the
669
+ next command if it is no longer usable. Missing flags for a new tenant are
670
+ usage errors (exit 2).
671
+
672
+ **`aac init` never installs or replaces a tenant-admin authority for an
673
+ existing tenant.** For a new tenant it generates the admin key first and
674
+ registers the public half together with the tenant, which the control plane
675
+ creates in one transaction, so no competing authority can ever be retired by
676
+ setup. For an existing tenant it only checks: the key on this machine must
677
+ already be the tenant's ACTIVE admin key. If the tenant's ACTIVE key is not on
678
+ this machine, `init` stops and tells you to restore that key to
679
+ `~/.aac/tenants/<tenant-id>/tenant-admin.pem` (or pass
680
+ `--tenant-admin-key-file`); if the tenant has no admin key at all, it tells
681
+ you to install one deliberately with `aac tenant rotate-admin-key`. Replacing
682
+ an authority is always that deliberate command, never a side effect of setup. Managed directories under `~/.aac/workspaces/` and
683
+ `~/.aac/tenants/` must be real directories at mode `0700`; a symlink or a
684
+ looser mode is refused before anything is written.
685
+ A second workspace for the same tenant (`--workspace other`) reuses the
686
+ tenant-admin key and gets its own root key id. Each key has an
687
+ `--<role>-key-file` flag to use existing material instead of generating it.
688
+
689
+ **Everything generated is development material.** The CA lives seven days and
690
+ the leaf certificates one day; nothing here qualifies for production
691
+ regardless of the tenant's hosted domain. Moving to production means
692
+ `aac tenant rotate-admin-key` with a key under production custody, a new root
693
+ key id published by your publisher, and certificates from your approved
694
+ issuer.
695
+
696
+ Inspect and maintain a workspace with the `workspace` noun:
697
+
698
+ ```bash
699
+ aac workspace status --workspace starter # what exists, expiry, permissions, next command
700
+ aac workspace status --workspace starter --remote # also: is the public trust material visible?
701
+ aac workspace renew --workspace starter # fresh leaf keys + certificates; old ones archived
702
+ aac workspace render --workspace starter --layout container --force # container path layout
703
+ ```
704
+
705
+ `status` is offline by default and exits 3 when material is missing, expired
706
+ or unsafely permissioned. `renew` reissues the one-day certificates with fresh
707
+ keys and reissues the development CA only when it is expired or within a day
708
+ of expiry (or with `--ca`); a new CA is published under the next anchor id and
709
+ needs the publisher restarted. `render` never touches keys and refuses to
710
+ overwrite rendered files unless `--force` is given. Command, flag and path
711
+ names are provisional until the public starter example has consumed them.
712
+
713
+ ## Assigned tenant domains (B239)
714
+
715
+ Registration automatically saves the server-assigned `hosted_trust_domain` in
716
+ the selected profile. The CLI displays the full name, explains its purpose in
717
+ SPIFFE identities/certificates/trust configuration, and prints the command for
718
+ finding it again. No manual copy is required. With JSON output, this guidance
719
+ goes to stderr and stdout remains one JSON document; table output includes it
720
+ inline. Completed-registration recovery provides the same saved-name guidance
721
+ without changing credential-reissue requirements.
722
+
723
+ ```sh
724
+ aac profile show dev --output table # saved locally, offline
725
+ aac tenant describe --profile dev --output table # assigned name + current binding status
726
+ aac tenant list-trust-domains --profile dev --output table # current ACTIVE bindings
727
+ aac tenant list-trust-domains --profile dev --all --output table # includes revoked history
728
+ aac sso whoami --profile dev --output table # cached session + matching saved name
729
+ ```
730
+
731
+ `tenant describe` accepts an optional `--tenant-id`; the usual flag → environment
732
+ → selected profile precedence applies. Its response comes from AAC. Binding
733
+ table output labels AAC-assigned/custom origin, ACTIVE/REVOKED status and
734
+ inherited scope. Registration, describe, profile show and binding-list JSON
735
+ fields retain their existing shapes. `whoami` adds optional
736
+ `hosted_trust_domain` and `hosted_trust_domain_source: "profile:<name>"` fields
737
+ when that profile's saved tenant matches the inspected session. These fields
738
+ describe locally saved metadata, not current binding eligibility. Tenant
739
+ overrides never display another tenant's saved domain; session exit codes and
740
+ offline operation are unchanged. Profile show says "not saved in this profile"
741
+ when metadata is absent, without asserting that AAC has no allocation.
742
+
743
+ Existing tenant admins use `aac tenant assign-hosted-domain --profile dev`;
744
+ explicit eligible restoration uses `aac tenant reactivate-hosted-domain --profile dev`.
745
+ Neither command registers another tenant or asks for a DNS name.
746
+ See [the hosted-domain guide](../../docs/b239-hosted-trust-domains.md) for
747
+ custom-domain progression, recovery compatibility and lifecycle limits.
@@ -83,14 +83,14 @@ aac sso login --profile dev # thereafter: plain login
83
83
  # Tenant ids are the server-allocated canonical tnt-<uuid> form
84
84
  # (B154 PR 5) — captured at registration / shown by `aac profile show`.
85
85
  aac tenant list # which tenant ids exist (no 409 probe)
86
- aac tenant describe --tenant-id tnt-<uuid> # workloads + key METADATA
86
+ aac tenant describe --profile dev # assigned domain, workloads + key METADATA
87
87
  aac tenant update --tenant-id tnt-<uuid> --display-name "ACME Manufacturing Inc"
88
88
 
89
89
  # B121/B228 — business-domain control proof and bounded claim lifecycle.
90
90
  # Registration can capture up to 16 pending rows with repeatable --tenant-domain.
91
91
  # The issue/verify/release/revoke commands
92
92
  # require the owning tenant's tenant-admin session and default --tenant-id from
93
- # the profile; the established tenant describe command keeps its explicit id.
93
+ # the profile; tenant describe also uses the selected profile by default.
94
94
  aac tenant register --display-name "ACME" --contact ops@acme.example \
95
95
  --tenant-domain acme.example
96
96
  aac tenant issue-domain-challenge --domain acme.example
@@ -101,7 +101,7 @@ aac tenant verify-domain --domain acme.example
101
101
  # never performs an implicit binding write.
102
102
  aac tenant bind-trust-domain --trust-domain acme.example
103
103
  aac tenant list-trust-domains --output table
104
- aac tenant describe --tenant-id tnt-<uuid> --output table # status + TXT
104
+ aac tenant describe --profile dev --output table # assigned binding status + TXT
105
105
  aac tenant release-domain --domain acme.example --reason "planned transfer"
106
106
  aac tenant revoke-domain --domain acme.example --reason "DNS control incident"
107
107
 
@@ -256,16 +256,17 @@ aac sso register-idp --shared --file shared-github.json \
256
256
  # admin verb (tenant list/describe/update, self-serve register-idp)
257
257
  # then rides the session automatically.
258
258
  aac sso login --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
259
- aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # >1 IdP
259
+ aac sso login --idp github # developer tenant: GitHub or Google sign-in
260
+ aac sso login --issuer https://login.microsoftonline.com/<tid>/v2.0 # enterprise IdP, by issuer URL
260
261
  aac sso login --flow pkce --no-browser # print the URL, don't launch
261
262
 
262
263
  # Sessions are REFRESH-LESS by design (Eng Spec §XVII.2): on expiry
263
264
  # any admin verb tells you to re-run `aac sso login` — there is no
264
265
  # refresh token to steal, and no silent re-authentication.
265
266
 
266
- # B155 — session niceties (cache-file operations; the CLI never
267
- # parses the token itself). whoami exits 0 = live, 3 = expired/none (§14).
268
- aac sso whoami --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
267
+ # B155/B239 — local session/profile inspection; no server call or JWT parsing.
268
+ # whoami exits 0 = unexpired cache, 3 = expired/none (§14).
269
+ aac sso whoami --profile dev --output table # cached session + saved assigned domain
269
270
  aac sso logout --tenant-id tnt-550e8400-e29b-41d4-a716-446655440000
270
271
  ```
271
272
 
@@ -479,8 +480,9 @@ coexistence, Eng Spec §XVII.9):
479
480
  expiry fails fast locally with the re-login message instead of
480
481
  dialing out; the server's own session 401s carry the same
481
482
  remediation (the server stays authoritative). `aac sso logout`
482
- removes the file; `aac sso whoami` reports its tenant + expiry
483
- (B155 — both operate on the cache file only).
483
+ removes the file; `aac sso whoami` reports its tenant + expiry and, when
484
+ available, the matching profile's saved AAC-assigned domain. Both commands
485
+ operate locally; `whoami` does not verify current server-side binding status.
484
486
 
485
487
  ## Your tenant_id
486
488
 
@@ -531,3 +533,198 @@ ceremony-only, and existing binding history remains B181.
531
533
  needed. Against a live stack: bring up the joined topology
532
534
  (`./bin/run-wedge-a-control-plane-compose.sh --keep-up`) and point the
533
535
  flags at localhost.
536
+
537
+ ## Guided setup: `aac init` and `aac workspace`
538
+
539
+ `aac init` takes a developer from an installed CLI to a complete, runnable
540
+ local workload in one command. It signs you in, registers a developer tenant
541
+ (or reuses the one your profile is already bound to), takes the AAC-assigned
542
+ hosted trust domain, registers the sample workload, generates development
543
+ keys and certificates, registers the tenant-admin public key together with a
544
+ new tenant, and writes a complete sidecar configuration plus the publisher
545
+ environment and a Compose variable file.
546
+
547
+ ```bash
548
+ # Developer tier: sign in with GitHub or Google. The command shows what it
549
+ # will create and asks before registering the tenant.
550
+ aac init --profile stage --admin-url https://api.stage.cascadeauth.dev \
551
+ --data-plane-url https://api.stage.cascadeauth.dev \
552
+ --trust-url https://trust.stage.cascadeauth.dev \
553
+ --display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github
554
+
555
+ # Scripted: every prompt has a flag; --create-tenant acknowledges creating a
556
+ # permanent tenant when stdin is not a terminal.
557
+ aac init --profile stage --admin-url ... --trust-url ... \
558
+ --display-name 'YOUR TEAM' --contact 'YOUR EMAIL' --idp github --create-tenant
559
+ ```
560
+
561
+ Progress lines `[1/5]` to `[5/5]` name the five steps: tenant, sign-in, hosted trust
562
+ domain, workload, material and rendering. A rerun prints every step, marking the ones
563
+ already done. The sign-in you chose with `--idp` is remembered in the workspace, so a
564
+ rerun (or `aac sso login --idp github` by hand when a session expires) never asks you
565
+ to pick between GitHub and Google. Workspace commands take `--workspace <name>`; the
566
+ profile is recorded in the workspace, so `--profile` is optional there and only checked.
567
+
568
+ What it creates, and where:
569
+
570
+ * `~/.aac/workspaces/<name>/` — the **workspace** for one workload (default
571
+ name `starter`): `pki/` with what the sidecar container mounts — the
572
+ workload, terminal-attestation and localhost TLS key pairs, this
573
+ workspace's root-signing key and the outbound CA bundle; `pair/` with what
574
+ both containers mount at `/run/secrets` — the pairing secret and the public
575
+ development CA certificate; `ca/` with the development CA private key (kept
576
+ out of both mounts so no container ever sees the key that mints
577
+ identities); `sidecar-config.yaml`; `compose.env` (non-secret variables for
578
+ a Compose consumer); and an internal manifest that lets a rerun resume.
579
+ * `~/.aac/tenants/<tenant-id>/` — **tenant-level** material shared by every
580
+ workspace of that tenant: the tenant-admin signing key, the publisher's
581
+ input directories (`root-keys/`, `spiffe-bundle/` — public halves only) and
582
+ the rendered `publisher.env`.
583
+ * The tenant API key stays where the CLI always kept it,
584
+ `~/.aac/credentials/<tenant-id>`; the rendered configuration references it
585
+ by path.
586
+
587
+ ### What each file is for, and what to back up
588
+
589
+ The table below is rendered from the CLI's own layout module and checked by a test, so it
590
+ cannot drift from the code. `<workspace>` is the workspace name (default `starter`) and
591
+ `<tenant-id>` the server-allocated tenant id.
592
+
593
+ <!-- inventory-table:start -->
594
+ | Material | Where | Used for | Lifetime | Back up? |
595
+ |---|---|---|---|---|
596
+ | 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` |
597
+ | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
598
+ | 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`); production custody belongs in an HSM/KMS, not a password manager |
599
+ | root_signing_key | `~/.aac/workspaces/<workspace>/pki/root.pem`, `~/.aac/workspaces/<workspace>/pki/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<workspace>-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 workspace name) | No. never restore: a replacement workspace gets a new name and therefore a new key id, published alongside the old one until the old one is retired |
600
+ | development_ca | `~/.aac/workspaces/<workspace>/ca/dev-ca.key`, `~/.aac/workspaces/<workspace>/pki/dev-ca.crt`, `~/.aac/workspaces/<workspace>/pair/dev-ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<workspace>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac workspace renew --ca` | No. development only; reissue |
601
+ | workload_svid | `~/.aac/workspaces/<workspace>/pki/workload.key`, `~/.aac/workspaces/<workspace>/pki/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac workspace renew` | No. development only; reissue |
602
+ | terminal_attestation | `~/.aac/workspaces/<workspace>/pki/terminal.key`, `~/.aac/workspaces/<workspace>/pki/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; kept separate from the workload key so neither can forge the other's role | 1 day; `aac workspace renew` | No. development only; reissue |
603
+ | localhost_tls | `~/.aac/workspaces/<workspace>/pki/server.key`, `~/.aac/workspaces/<workspace>/pki/server.crt` | the sidecar's HTTPS listener | 1 day; `aac workspace renew` | No. development only; reissue |
604
+ | outbound_ca_bundle | `~/.aac/workspaces/<workspace>/pki/outbound-ca.pem` | the sidecar verifies TLS to AAC and to itself | rebuilt by renew; `aac workspace renew` | No. derived |
605
+ | pairing_secret | `~/.aac/workspaces/<workspace>/pair/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 |
606
+ | workspace_manifest | `~/.aac/workspaces/<workspace>/manifest.json` | the workspace's identities and progress; every `workspace` verb reads it first | for the life of the workspace | **Recommended.** cannot be recreated by any command; without it start a new workspace under a new name (see the machine-loss procedure) |
607
+ | 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 workspaces | for the life of the tenant | **Recommended.** back up with the tenant directory: a replacement workspace 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) |
608
+ | rendered_files | `~/.aac/workspaces/<workspace>/sidecar-config.yaml`, `~/.aac/workspaces/<workspace>/compose.env`, `~/.aac/tenants/<tenant-id>/publisher.env` | non-secret configuration derived from the manifest | re-rendered on demand; `aac workspace render --force` | No. derived; `aac workspace render --force` recreates them |
609
+ <!-- inventory-table:end -->
610
+
611
+ #### If you lose this machine
612
+
613
+ Back up two things while the machine is healthy: the tenant API key at
614
+ `~/.aac/credentials/<tenant-id>`, and the whole tenant directory
615
+ `~/.aac/tenants/<tenant-id>/` (the admin signing key plus the `root-keys/` and
616
+ `spiffe-bundle/` public inputs the publisher has already published). The workspace
617
+ manifest is worth keeping too, but a workspace can always be replaced. Then, on the new
618
+ machine:
619
+
620
+ 1. Install the CLI and `aac profile create <name> --admin-url … --data-plane-url …`.
621
+ Sign in with `aac sso login --idp github --tenant-id <tenant-id> --profile <name>`
622
+ (or `--issuer <url>` for an enterprise connection): the remaining
623
+ steps register a workload and read the tenant's admin-key state, which need a
624
+ tenant-admin session whatever way the tenant was registered (a ceremony bootstrap
625
+ token only covers registration and hosted-domain assignment).
626
+ 2. Restore the tenant API key to `~/.aac/credentials/<tenant-id>` (mode `0600`), or issue a
627
+ new one with `aac tenant reissue-api-key` if it was not backed up. Restore
628
+ `~/.aac/tenants/<tenant-id>/` from the backup (directories `0700`, files `0600`).
629
+ 3. Run `aac init --profile <name> --workspace <new-name>` with a **new** workspace name.
630
+ Root key ids are fixed per workspace name and the control plane never accepts new key
631
+ material under an old id, so the replacement workspace publishes a new id
632
+ (`<new-name>-root-v1`) and a new development CA anchor.
633
+ 4. Start the publisher from the restored tenant directory. Its root-key set now holds the
634
+ old and the new public keys, which is what the control plane requires: a set that
635
+ drops every currently ACTIVE key at once is refused. Retire the old key later by
636
+ removing its public file once nothing signs with it.
637
+
638
+ Reusing the old workspace name on the new machine is refused on purpose: the restored
639
+ tenant directory still holds that name's published root key, and `init` never generates
640
+ different key material under an existing key id. The development certificates and the
641
+ pairing secret are regenerated by step 3; they are never restored.
642
+
643
+ Every private file is created once at mode `0600` under `0700` directories
644
+ and is never overwritten; before generating anything, `init` checks every
645
+ destination and refuses with the full list if one already exists. Rerunning
646
+ `aac init` resumes an interrupted setup (outcome `resumed`), reports a
647
+ complete workspace (outcome `complete`, exit 0), or names the exact conflict
648
+ (exit 3). Every post-registration step is pinned to the workspace's tenant,
649
+ so a profile that has since been pointed at another tenant is refused rather
650
+ than mixed in. A rerun on a complete workspace validates it offline (binding,
651
+ files, permissions, certificate validity, CA fitness) and exits 3 with the
652
+ next command if it is no longer usable. Missing flags for a new tenant are
653
+ usage errors (exit 2).
654
+
655
+ **`aac init` never installs or replaces a tenant-admin authority for an
656
+ existing tenant.** For a new tenant it generates the admin key first and
657
+ registers the public half together with the tenant, which the control plane
658
+ creates in one transaction, so no competing authority can ever be retired by
659
+ setup. For an existing tenant it only checks: the key on this machine must
660
+ already be the tenant's ACTIVE admin key. If the tenant's ACTIVE key is not on
661
+ this machine, `init` stops and tells you to restore that key to
662
+ `~/.aac/tenants/<tenant-id>/tenant-admin.pem` (or pass
663
+ `--tenant-admin-key-file`); if the tenant has no admin key at all, it tells
664
+ you to install one deliberately with `aac tenant rotate-admin-key`. Replacing
665
+ an authority is always that deliberate command, never a side effect of setup. Managed directories under `~/.aac/workspaces/` and
666
+ `~/.aac/tenants/` must be real directories at mode `0700`; a symlink or a
667
+ looser mode is refused before anything is written.
668
+ A second workspace for the same tenant (`--workspace other`) reuses the
669
+ tenant-admin key and gets its own root key id. Each key has an
670
+ `--<role>-key-file` flag to use existing material instead of generating it.
671
+
672
+ **Everything generated is development material.** The CA lives seven days and
673
+ the leaf certificates one day; nothing here qualifies for production
674
+ regardless of the tenant's hosted domain. Moving to production means
675
+ `aac tenant rotate-admin-key` with a key under production custody, a new root
676
+ key id published by your publisher, and certificates from your approved
677
+ issuer.
678
+
679
+ Inspect and maintain a workspace with the `workspace` noun:
680
+
681
+ ```bash
682
+ aac workspace status --workspace starter # what exists, expiry, permissions, next command
683
+ aac workspace status --workspace starter --remote # also: is the public trust material visible?
684
+ aac workspace renew --workspace starter # fresh leaf keys + certificates; old ones archived
685
+ aac workspace render --workspace starter --layout container --force # container path layout
686
+ ```
687
+
688
+ `status` is offline by default and exits 3 when material is missing, expired
689
+ or unsafely permissioned. `renew` reissues the one-day certificates with fresh
690
+ keys and reissues the development CA only when it is expired or within a day
691
+ of expiry (or with `--ca`); a new CA is published under the next anchor id and
692
+ needs the publisher restarted. `render` never touches keys and refuses to
693
+ overwrite rendered files unless `--force` is given. Command, flag and path
694
+ names are provisional until the public starter example has consumed them.
695
+
696
+ ## Assigned tenant domains (B239)
697
+
698
+ Registration automatically saves the server-assigned `hosted_trust_domain` in
699
+ the selected profile. The CLI displays the full name, explains its purpose in
700
+ SPIFFE identities/certificates/trust configuration, and prints the command for
701
+ finding it again. No manual copy is required. With JSON output, this guidance
702
+ goes to stderr and stdout remains one JSON document; table output includes it
703
+ inline. Completed-registration recovery provides the same saved-name guidance
704
+ without changing credential-reissue requirements.
705
+
706
+ ```sh
707
+ aac profile show dev --output table # saved locally, offline
708
+ aac tenant describe --profile dev --output table # assigned name + current binding status
709
+ aac tenant list-trust-domains --profile dev --output table # current ACTIVE bindings
710
+ aac tenant list-trust-domains --profile dev --all --output table # includes revoked history
711
+ aac sso whoami --profile dev --output table # cached session + matching saved name
712
+ ```
713
+
714
+ `tenant describe` accepts an optional `--tenant-id`; the usual flag → environment
715
+ → selected profile precedence applies. Its response comes from AAC. Binding
716
+ table output labels AAC-assigned/custom origin, ACTIVE/REVOKED status and
717
+ inherited scope. Registration, describe, profile show and binding-list JSON
718
+ fields retain their existing shapes. `whoami` adds optional
719
+ `hosted_trust_domain` and `hosted_trust_domain_source: "profile:<name>"` fields
720
+ when that profile's saved tenant matches the inspected session. These fields
721
+ describe locally saved metadata, not current binding eligibility. Tenant
722
+ overrides never display another tenant's saved domain; session exit codes and
723
+ offline operation are unchanged. Profile show says "not saved in this profile"
724
+ when metadata is absent, without asserting that AAC has no allocation.
725
+
726
+ Existing tenant admins use `aac tenant assign-hosted-domain --profile dev`;
727
+ explicit eligible restoration uses `aac tenant reactivate-hosted-domain --profile dev`.
728
+ Neither command registers another tenant or asks for a DNS name.
729
+ See [the hosted-domain guide](../../docs/b239-hosted-trust-domains.md) for
730
+ custom-domain progression, recovery compatibility and lifecycle limits.
@@ -10,4 +10,4 @@ entrypoint (`aac_cli.cli:entrypoint`).
10
10
  # test in bin/tests/test_cli_version_parity.py. A literal keeps the
11
11
  # shipped code free of import-time metadata lookups (and their
12
12
  # not-installed failure mode).
13
- __version__ = "0.1.2"
13
+ __version__ = "0.1.4"