aac-cli 0.2.9__tar.gz → 0.2.11__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 (61) hide show
  1. {aac_cli-0.2.9/aac_cli.egg-info → aac_cli-0.2.11}/PKG-INFO +130 -15
  2. {aac_cli-0.2.9 → aac_cli-0.2.11}/README.md +129 -14
  3. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/__init__.py +1 -1
  4. aac_cli-0.2.11/aac_cli/__main__.py +13 -0
  5. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/admin_key_pem.py +3 -0
  6. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/aeg_cli.py +5 -0
  7. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/agent_cli.py +15 -2
  8. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/agent_health.py +21 -4
  9. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/api_key_rotation_state.py +4 -1
  10. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/cli.py +549 -293
  11. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/config.py +189 -49
  12. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/idp_recovery.py +6 -3
  13. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/init_cli.py +3 -3
  14. aac_cli-0.2.11/aac_cli/interrupts.py +48 -0
  15. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/profiles.py +114 -59
  16. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/reference.py +13 -4
  17. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/registration_state.py +30 -18
  18. aac_cli-0.2.11/aac_cli/safe_output.py +311 -0
  19. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/sso_login.py +363 -58
  20. {aac_cli-0.2.9 → aac_cli-0.2.11/aac_cli.egg-info}/PKG-INFO +130 -15
  21. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli.egg-info/SOURCES.txt +2 -0
  22. aac_cli-0.2.11/launchers/aac +36 -0
  23. aac_cli-0.2.11/launchers/aeg +33 -0
  24. {aac_cli-0.2.9 → aac_cli-0.2.11}/pyproject.toml +2 -2
  25. aac_cli-0.2.9/aac_cli/__main__.py +0 -6
  26. aac_cli-0.2.9/launchers/aac +0 -32
  27. aac_cli-0.2.9/launchers/aeg +0 -29
  28. {aac_cli-0.2.9 → aac_cli-0.2.11}/LICENSE +0 -0
  29. {aac_cli-0.2.9 → aac_cli-0.2.11}/MANIFEST.in +0 -0
  30. {aac_cli-0.2.9 → aac_cli-0.2.11}/NOTICE +0 -0
  31. {aac_cli-0.2.9 → aac_cli-0.2.11}/THIRD_PARTY_NOTICES.md +0 -0
  32. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/__init__.py +0 -0
  33. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/cli.py +0 -0
  34. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/data/action-taken-v1.json +0 -0
  35. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/data/predicates-v1.json +0 -0
  36. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/data/renderer-source.json +0 -0
  37. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/from_sidecar.py +0 -0
  38. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/inputs.py +0 -0
  39. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/model.py +0 -0
  40. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/narrative.py +0 -0
  41. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/provenance.py +0 -0
  42. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/schemas.py +0 -0
  43. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/temporal.py +0 -0
  44. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/theme.py +0 -0
  45. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/timeline_source.py +0 -0
  46. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/_aeg/visualizer.py +0 -0
  47. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/aeg_list.py +0 -0
  48. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/agent_config.py +0 -0
  49. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/agent_layout.py +0 -0
  50. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/agent_record.py +0 -0
  51. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/chain_list.py +0 -0
  52. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/config_render.py +0 -0
  53. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/dev_material.py +0 -0
  54. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/material_cases.py +0 -0
  55. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/secure_files.py +0 -0
  56. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/supplied_material.py +0 -0
  57. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli/trace_query.py +0 -0
  58. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli.egg-info/dependency_links.txt +0 -0
  59. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli.egg-info/requires.txt +0 -0
  60. {aac_cli-0.2.9 → aac_cli-0.2.11}/aac_cli.egg-info/top_level.txt +0 -0
  61. {aac_cli-0.2.9 → aac_cli-0.2.11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aac-cli
3
- Version: 0.2.9
3
+ Version: 0.2.11
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
@@ -46,8 +46,8 @@ The `aac` command is how a tenant's operator does everything that is not
46
46
  done by the sidecar itself: register the tenant, sign in, manage keys,
47
47
  workloads and domains, and prepare a runnable local workload. Everything
48
48
  works from a terminal or a script; there is no web console to click
49
- through. Output is JSON by default, and every command that reports a
50
- result also offers `--output table`.
49
+ through. Output is JSON by default. Commands that support table output offer
50
+ `--output table`; `aac sso describe` returns JSON only.
51
51
 
52
52
  Two kinds of tenant use the same tool:
53
53
 
@@ -741,6 +741,7 @@ aac profile show stage --output table # stored and effective values,
741
741
  aac profile create prod --admin-url https://aac-admin.example.com \
742
742
  --data-plane-url https://aac-data.example.com
743
743
  aac profile update main # edit the built-in baseline
744
+ aac profile update stage --unbind # clear the local tenant association
744
745
  aac profile delete prod # local only; never touches the server
745
746
  ```
746
747
 
@@ -757,6 +758,41 @@ stored value.
757
758
  for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
758
759
  It is your organisation's identifier in the federation, allocated by AAC at
759
760
  registration in the form `tnt-<uuid>`, and it never changes.
761
+ A binding makes that tenant the profile's stored default; it does not grant
762
+ membership or configure an identity provider. Successful sign-in to an existing
763
+ tenant binds the profile; registration binds a newly created tenant.
764
+
765
+ A stale tenant binding affects only that profile. `aac profile list` and
766
+ `aac profile show` mark invalid bindings without printing the invalid values;
767
+ other profiles remain usable. Operational commands reject a selected invalid
768
+ profile with exit 3, even if a tenant flag or environment override is supplied.
769
+ File syntax, profile-name and size limits still apply to the entire file.
770
+
771
+ Use `aac profile update NAME --unbind` to remove the local `tenant_id` and its
772
+ saved hosted-domain metadata. Endpoints, other settings, credentials and the
773
+ server-side tenant are preserved. The flag works without prompts, may accompany
774
+ endpoint flags, and also works on `main` or an already-unbound profile. It
775
+ refuses profiles with unresolved registration or API-key rotation state;
776
+ finish that recovery first. Then bind the profile through registration or
777
+ successful sign-in. Endpoint-only updates and confirmed profile deletion also
778
+ work on stale profiles; reads never rewrite the config file.
779
+
780
+ For an existing tenant, the repair sequence is:
781
+
782
+ ```bash
783
+ aac profile update stage --unbind
784
+ # Bind profile stage to the correct existing tenant:
785
+ aac sso login --profile stage --tenant-id '<TENANT_ID>'
786
+ ```
787
+
788
+ **`aac sso login --profile stage --tenant-id '<TENANT_ID>'` binds profile
789
+ `stage` to that tenant.** Although the verb is `login`, successful authentication
790
+ also saves the tenant association; there is no separate `--bind` flag. Replace
791
+ `<TENANT_ID>` with the intended tenant's canonical ID. An endpoint correction
792
+ or expired session normally does not need
793
+ unbinding. Unbinding is local and does not log out, revoke access, or change
794
+ existing agent records. See the [profile repair guide](https://docs.cascadeauth.com/cli/guide/#repair-a-stale-profile-binding)
795
+ for the exact config fields removed and recovery constraints.
760
796
 
761
797
  `AAC_CLI_HOME` relocates the whole `~/.aac` tree. Use it for a throwaway
762
798
  test home, or to keep separate accounts hard-walled from each other:
@@ -774,13 +810,39 @@ Two credential files can coexist under `~/.aac/credentials/`:
774
810
  (`aac chain`, `aac trust-anchor`) present it. Only a hash of it exists
775
811
  on the server, so a lost key is replaced, never recovered.
776
812
  * `<tenant-id>.session` holds the short-lived session that `aac sso login`
777
- creates. The administration commands (`aac tenant`, `aac sso`) use it
778
- automatically. Sessions cannot be refreshed: when one expires, any
779
- administration command tells you to sign in again, and there is no
780
- refresh token that could be stolen.
813
+ creates. Authenticated administration commands use it automatically;
814
+ public reads such as `aac sso describe` do not use credentials.
815
+ Sessions have no refresh token. You can sign in again before expiry to
816
+ replace the cached session; after expiry, commands that need it ask you
817
+ to sign in again.
781
818
 
782
819
  ## Signing in
783
820
 
821
+ Inspect the available sign-in connections before starting a login:
822
+
823
+ ```bash
824
+ aac sso describe --profile stage # shared/platform candidates
825
+ aac sso describe --profile stage --tenant-id '<TENANT_ID>' # tenant plus shared candidates
826
+ ```
827
+
828
+ `sso describe` reads public metadata from the profile's admin endpoint and
829
+ prints the complete descriptor as JSON on stdout. No credentials or prior
830
+ sign-in are required, and it does not bind a profile or change cached credentials.
831
+ The output includes `connections`, `count`, and, for the tenant route,
832
+ `tenant_id`; connection fields describe issuers, client IDs and available flow
833
+ endpoints. An empty connection list is a successful read.
834
+
835
+ Only an explicit `--tenant-id` selects the tenant descriptor. Omitting it
836
+ always reads shared/platform candidates, even when the profile or the
837
+ `AAC_TENANT_ID` environment variable supplies a tenant. Replace `<TENANT_ID>`
838
+ with the intended tenant's canonical ID. The CLI still checks your local
839
+ configuration and selected profile. If it reports an invalid stored tenant
840
+ binding, clear it with `aac profile update stage --unbind`, then retry.
841
+ Output is JSON only (`--output json` is optional); errors go to stderr.
842
+ See the [command reference](https://docs.cascadeauth.com/cli/reference/aac-sso-describe/).
843
+
844
+ Then sign in using the connection you need:
845
+
784
846
  ```bash
785
847
  aac sso login --profile stage --idp github # developer tenant: GitHub or Google
786
848
  aac sso login --profile prod \
@@ -795,14 +857,39 @@ aac sso logout --profile stage
795
857
  A developer tenant signs in with `--idp github` or `--idp google`; if you
796
858
  need another sign-in provider, contact support@cascadeauth.com. An
797
859
  enterprise tenant signs in through its Microsoft Entra ID connection, named
798
- by the connection's exact URL with `--idp-url`; when several connections
799
- are registered, the error message lists the exact commands to choose one.
860
+ by the connection's exact URL with `--idp-url`. With neither flag, the
861
+ tenant's own connections take precedence over the shared developer sign-ins;
862
+ when several remain, a terminal shows a numbered list to choose from and then
863
+ prints the flag that skips the prompt next time, while a script (no terminal)
864
+ gets exit 3 and one copy-ready command per connection. The list marks which
865
+ connection the cached session was last obtained through; it never shows
866
+ which connections have members. A developer tenant admits only the provider
867
+ and account it was registered with: when the AAC STS does not accept a shared
868
+ sign-in, the CLI reminds you of that rule and lists any other shared sign-ins. GitHub connections accept the device flow
869
+ only: `--flow pkce` with a GitHub connection stops locally with exit 3.
870
+
871
+ Sessions last **4 hours by default**. Your administrator may configure a
872
+ different lifetime for the deployment or connection. You can run
873
+ `aac sso login` again before expiry: successful authentication replaces the
874
+ cached session with a new one whose lifetime starts at that login. For example,
875
+ with the four-hour default, signing in at 09:00 gives an expiry of 13:00;
876
+ signing in successfully again at 12:00 gives the new session an expiry of 16:00.
877
+ Use `aac sso whoami --profile NAME --output table` to check the current
878
+ expiration. If the session expires, sign in again; no refresh token is issued.
879
+ A command that needs a session stops locally with exit 3 when none is cached,
880
+ before anything is sent, and prints the `aac sso login` command for the same
881
+ profile and endpoints; commands that also accept a bootstrap token name that
882
+ alternative.
800
883
 
801
884
  Sign-in uses the device flow by default, so the browser can be on another
802
- machine and headless hosts work. Google connections use a loopback flow
803
- instead, because Google's device flow is scope-restricted; `--flow`
804
- overrides the choice. `whoami` and `logout` work offline and never contact
805
- AAC.
885
+ machine and headless hosts work. On a terminal with a browser at hand
886
+ (macOS, a desktop session, or `BROWSER` set), the device flow also offers to
887
+ open the sign-in page for you: press Enter to open it in your browser, or copy
888
+ the printed link and code into a browser yourself; `--no-browser` turns the
889
+ offer off, and a script (no terminal) or a headless host never sees it. Google
890
+ connections use a loopback flow instead, because Google's device flow is
891
+ scope-restricted; `--flow` overrides the choice. `whoami` and `logout` work
892
+ offline and never contact AAC.
806
893
 
807
894
  ## Output and exit codes
808
895
 
@@ -814,11 +901,16 @@ server-returned domain. Existing agents expose read-only identities through
814
901
  or `--field workload-spiffe-id`. A successful scalar is one unquoted line;
815
902
  an unavailable value or unhealthy agent fails with empty stdout. `--field`
816
903
  cannot be combined with explicit `--output`, or with agent `--remote`.
904
+ Scalar profile inspection rejects an invalid stored binding. Structured
905
+ inspection remains available and reports invalid effective IDs as `null` with
906
+ an `error`; invalid list bindings carry `binding_error`, and show bindings carry
907
+ `binding.error`. Their invalid stored tenant IDs are redacted as `null`.
817
908
 
818
909
  Every command that reports a result prints one JSON document to standard
819
910
  output by default, with progress notes and diagnostics on standard error,
820
- so the JSON stays parseable in scripts; `--output table` prints a readable
821
- table instead. The three profile writers are the exception: `aac profile
911
+ so the JSON stays parseable in scripts; where supported, `--output table`
912
+ prints a readable table instead. `aac sso describe` supports JSON only.
913
+ The three profile writers are the exception: `aac profile
822
914
  create`, `update` and `delete` confirm on standard error, leave standard
823
915
  output empty and take no `--output`.
824
916
 
@@ -829,10 +921,33 @@ output empty and take no `--output`.
829
921
  | `2` | Usage error: an invalid flag, value or flag combination. |
830
922
  | `3` | A local configuration or state problem: profile, credential file, cached session or agent. |
831
923
  | `4` | Transport failure: an endpoint could not be reached. |
924
+ | `130` | Interrupted by the operator (Ctrl-C). |
925
+
926
+ Ctrl-C stops `aac` and CLI-owned `aeg` without a traceback, with one concise
927
+ diagnostic on standard error. A remote change may already have committed;
928
+ follow any recovery guidance and retain pending registration or key-operation
929
+ state. The CLI does not roll back or automatically retry a remote change.
930
+
931
+ Walking away from sign-in retains the existing deadlines: device sign-in waits
932
+ for the identity provider's device-code lifetime (300 seconds if absent), then
933
+ exits `1` with instructions to run login again; a PKCE browser sign-in without
934
+ a callback gives up after 300 seconds with exit `3`. HTTP timeouts remain exit
935
+ `4`. These outcomes are distinct from Ctrl-C; there is no shorter patience cap.
832
936
 
833
937
  If AAC asks you to slow down, the command shows the interval to wait. The
834
938
  CLI never replays a change on its own; wait and rerun deliberately.
835
939
 
940
+ The commands that can present a ceremony token (`aac tenant register`,
941
+ `reissue-api-key`, `assign-hosted-domain`, `reactivate-hosted-domain`,
942
+ `bind-trust-domain`, `list-trust-domains`, `revoke-trust-domain`,
943
+ `rotate-admin-key`, `aac sso register-idp`, `enroll-idp-recovery-key` and
944
+ `approve-idp-repair`) and `aac sso login` report a failure by HTTP status and
945
+ AAC error code only, never the server's message or response body, because
946
+ either can echo the credential the request carried; their transport failures
947
+ show the error class and the operating system's reason, not the library's
948
+ text. Other commands still print the server's message with the status and
949
+ code.
950
+
836
951
  ## Enterprise tenants
837
952
 
838
953
  Enterprise tenants are registered through an onboarding ceremony: AAC
@@ -19,8 +19,8 @@ The `aac` command is how a tenant's operator does everything that is not
19
19
  done by the sidecar itself: register the tenant, sign in, manage keys,
20
20
  workloads and domains, and prepare a runnable local workload. Everything
21
21
  works from a terminal or a script; there is no web console to click
22
- through. Output is JSON by default, and every command that reports a
23
- result also offers `--output table`.
22
+ through. Output is JSON by default. Commands that support table output offer
23
+ `--output table`; `aac sso describe` returns JSON only.
24
24
 
25
25
  Two kinds of tenant use the same tool:
26
26
 
@@ -714,6 +714,7 @@ aac profile show stage --output table # stored and effective values,
714
714
  aac profile create prod --admin-url https://aac-admin.example.com \
715
715
  --data-plane-url https://aac-data.example.com
716
716
  aac profile update main # edit the built-in baseline
717
+ aac profile update stage --unbind # clear the local tenant association
717
718
  aac profile delete prod # local only; never touches the server
718
719
  ```
719
720
 
@@ -730,6 +731,41 @@ stored value.
730
731
  for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
731
732
  It is your organisation's identifier in the federation, allocated by AAC at
732
733
  registration in the form `tnt-<uuid>`, and it never changes.
734
+ A binding makes that tenant the profile's stored default; it does not grant
735
+ membership or configure an identity provider. Successful sign-in to an existing
736
+ tenant binds the profile; registration binds a newly created tenant.
737
+
738
+ A stale tenant binding affects only that profile. `aac profile list` and
739
+ `aac profile show` mark invalid bindings without printing the invalid values;
740
+ other profiles remain usable. Operational commands reject a selected invalid
741
+ profile with exit 3, even if a tenant flag or environment override is supplied.
742
+ File syntax, profile-name and size limits still apply to the entire file.
743
+
744
+ Use `aac profile update NAME --unbind` to remove the local `tenant_id` and its
745
+ saved hosted-domain metadata. Endpoints, other settings, credentials and the
746
+ server-side tenant are preserved. The flag works without prompts, may accompany
747
+ endpoint flags, and also works on `main` or an already-unbound profile. It
748
+ refuses profiles with unresolved registration or API-key rotation state;
749
+ finish that recovery first. Then bind the profile through registration or
750
+ successful sign-in. Endpoint-only updates and confirmed profile deletion also
751
+ work on stale profiles; reads never rewrite the config file.
752
+
753
+ For an existing tenant, the repair sequence is:
754
+
755
+ ```bash
756
+ aac profile update stage --unbind
757
+ # Bind profile stage to the correct existing tenant:
758
+ aac sso login --profile stage --tenant-id '<TENANT_ID>'
759
+ ```
760
+
761
+ **`aac sso login --profile stage --tenant-id '<TENANT_ID>'` binds profile
762
+ `stage` to that tenant.** Although the verb is `login`, successful authentication
763
+ also saves the tenant association; there is no separate `--bind` flag. Replace
764
+ `<TENANT_ID>` with the intended tenant's canonical ID. An endpoint correction
765
+ or expired session normally does not need
766
+ unbinding. Unbinding is local and does not log out, revoke access, or change
767
+ existing agent records. See the [profile repair guide](https://docs.cascadeauth.com/cli/guide/#repair-a-stale-profile-binding)
768
+ for the exact config fields removed and recovery constraints.
733
769
 
734
770
  `AAC_CLI_HOME` relocates the whole `~/.aac` tree. Use it for a throwaway
735
771
  test home, or to keep separate accounts hard-walled from each other:
@@ -747,13 +783,39 @@ Two credential files can coexist under `~/.aac/credentials/`:
747
783
  (`aac chain`, `aac trust-anchor`) present it. Only a hash of it exists
748
784
  on the server, so a lost key is replaced, never recovered.
749
785
  * `<tenant-id>.session` holds the short-lived session that `aac sso login`
750
- creates. The administration commands (`aac tenant`, `aac sso`) use it
751
- automatically. Sessions cannot be refreshed: when one expires, any
752
- administration command tells you to sign in again, and there is no
753
- refresh token that could be stolen.
786
+ creates. Authenticated administration commands use it automatically;
787
+ public reads such as `aac sso describe` do not use credentials.
788
+ Sessions have no refresh token. You can sign in again before expiry to
789
+ replace the cached session; after expiry, commands that need it ask you
790
+ to sign in again.
754
791
 
755
792
  ## Signing in
756
793
 
794
+ Inspect the available sign-in connections before starting a login:
795
+
796
+ ```bash
797
+ aac sso describe --profile stage # shared/platform candidates
798
+ aac sso describe --profile stage --tenant-id '<TENANT_ID>' # tenant plus shared candidates
799
+ ```
800
+
801
+ `sso describe` reads public metadata from the profile's admin endpoint and
802
+ prints the complete descriptor as JSON on stdout. No credentials or prior
803
+ sign-in are required, and it does not bind a profile or change cached credentials.
804
+ The output includes `connections`, `count`, and, for the tenant route,
805
+ `tenant_id`; connection fields describe issuers, client IDs and available flow
806
+ endpoints. An empty connection list is a successful read.
807
+
808
+ Only an explicit `--tenant-id` selects the tenant descriptor. Omitting it
809
+ always reads shared/platform candidates, even when the profile or the
810
+ `AAC_TENANT_ID` environment variable supplies a tenant. Replace `<TENANT_ID>`
811
+ with the intended tenant's canonical ID. The CLI still checks your local
812
+ configuration and selected profile. If it reports an invalid stored tenant
813
+ binding, clear it with `aac profile update stage --unbind`, then retry.
814
+ Output is JSON only (`--output json` is optional); errors go to stderr.
815
+ See the [command reference](https://docs.cascadeauth.com/cli/reference/aac-sso-describe/).
816
+
817
+ Then sign in using the connection you need:
818
+
757
819
  ```bash
758
820
  aac sso login --profile stage --idp github # developer tenant: GitHub or Google
759
821
  aac sso login --profile prod \
@@ -768,14 +830,39 @@ aac sso logout --profile stage
768
830
  A developer tenant signs in with `--idp github` or `--idp google`; if you
769
831
  need another sign-in provider, contact support@cascadeauth.com. An
770
832
  enterprise tenant signs in through its Microsoft Entra ID connection, named
771
- by the connection's exact URL with `--idp-url`; when several connections
772
- are registered, the error message lists the exact commands to choose one.
833
+ by the connection's exact URL with `--idp-url`. With neither flag, the
834
+ tenant's own connections take precedence over the shared developer sign-ins;
835
+ when several remain, a terminal shows a numbered list to choose from and then
836
+ prints the flag that skips the prompt next time, while a script (no terminal)
837
+ gets exit 3 and one copy-ready command per connection. The list marks which
838
+ connection the cached session was last obtained through; it never shows
839
+ which connections have members. A developer tenant admits only the provider
840
+ and account it was registered with: when the AAC STS does not accept a shared
841
+ sign-in, the CLI reminds you of that rule and lists any other shared sign-ins. GitHub connections accept the device flow
842
+ only: `--flow pkce` with a GitHub connection stops locally with exit 3.
843
+
844
+ Sessions last **4 hours by default**. Your administrator may configure a
845
+ different lifetime for the deployment or connection. You can run
846
+ `aac sso login` again before expiry: successful authentication replaces the
847
+ cached session with a new one whose lifetime starts at that login. For example,
848
+ with the four-hour default, signing in at 09:00 gives an expiry of 13:00;
849
+ signing in successfully again at 12:00 gives the new session an expiry of 16:00.
850
+ Use `aac sso whoami --profile NAME --output table` to check the current
851
+ expiration. If the session expires, sign in again; no refresh token is issued.
852
+ A command that needs a session stops locally with exit 3 when none is cached,
853
+ before anything is sent, and prints the `aac sso login` command for the same
854
+ profile and endpoints; commands that also accept a bootstrap token name that
855
+ alternative.
773
856
 
774
857
  Sign-in uses the device flow by default, so the browser can be on another
775
- machine and headless hosts work. Google connections use a loopback flow
776
- instead, because Google's device flow is scope-restricted; `--flow`
777
- overrides the choice. `whoami` and `logout` work offline and never contact
778
- AAC.
858
+ machine and headless hosts work. On a terminal with a browser at hand
859
+ (macOS, a desktop session, or `BROWSER` set), the device flow also offers to
860
+ open the sign-in page for you: press Enter to open it in your browser, or copy
861
+ the printed link and code into a browser yourself; `--no-browser` turns the
862
+ offer off, and a script (no terminal) or a headless host never sees it. Google
863
+ connections use a loopback flow instead, because Google's device flow is
864
+ scope-restricted; `--flow` overrides the choice. `whoami` and `logout` work
865
+ offline and never contact AAC.
779
866
 
780
867
  ## Output and exit codes
781
868
 
@@ -787,11 +874,16 @@ server-returned domain. Existing agents expose read-only identities through
787
874
  or `--field workload-spiffe-id`. A successful scalar is one unquoted line;
788
875
  an unavailable value or unhealthy agent fails with empty stdout. `--field`
789
876
  cannot be combined with explicit `--output`, or with agent `--remote`.
877
+ Scalar profile inspection rejects an invalid stored binding. Structured
878
+ inspection remains available and reports invalid effective IDs as `null` with
879
+ an `error`; invalid list bindings carry `binding_error`, and show bindings carry
880
+ `binding.error`. Their invalid stored tenant IDs are redacted as `null`.
790
881
 
791
882
  Every command that reports a result prints one JSON document to standard
792
883
  output by default, with progress notes and diagnostics on standard error,
793
- so the JSON stays parseable in scripts; `--output table` prints a readable
794
- table instead. The three profile writers are the exception: `aac profile
884
+ so the JSON stays parseable in scripts; where supported, `--output table`
885
+ prints a readable table instead. `aac sso describe` supports JSON only.
886
+ The three profile writers are the exception: `aac profile
795
887
  create`, `update` and `delete` confirm on standard error, leave standard
796
888
  output empty and take no `--output`.
797
889
 
@@ -802,10 +894,33 @@ output empty and take no `--output`.
802
894
  | `2` | Usage error: an invalid flag, value or flag combination. |
803
895
  | `3` | A local configuration or state problem: profile, credential file, cached session or agent. |
804
896
  | `4` | Transport failure: an endpoint could not be reached. |
897
+ | `130` | Interrupted by the operator (Ctrl-C). |
898
+
899
+ Ctrl-C stops `aac` and CLI-owned `aeg` without a traceback, with one concise
900
+ diagnostic on standard error. A remote change may already have committed;
901
+ follow any recovery guidance and retain pending registration or key-operation
902
+ state. The CLI does not roll back or automatically retry a remote change.
903
+
904
+ Walking away from sign-in retains the existing deadlines: device sign-in waits
905
+ for the identity provider's device-code lifetime (300 seconds if absent), then
906
+ exits `1` with instructions to run login again; a PKCE browser sign-in without
907
+ a callback gives up after 300 seconds with exit `3`. HTTP timeouts remain exit
908
+ `4`. These outcomes are distinct from Ctrl-C; there is no shorter patience cap.
805
909
 
806
910
  If AAC asks you to slow down, the command shows the interval to wait. The
807
911
  CLI never replays a change on its own; wait and rerun deliberately.
808
912
 
913
+ The commands that can present a ceremony token (`aac tenant register`,
914
+ `reissue-api-key`, `assign-hosted-domain`, `reactivate-hosted-domain`,
915
+ `bind-trust-domain`, `list-trust-domains`, `revoke-trust-domain`,
916
+ `rotate-admin-key`, `aac sso register-idp`, `enroll-idp-recovery-key` and
917
+ `approve-idp-repair`) and `aac sso login` report a failure by HTTP status and
918
+ AAC error code only, never the server's message or response body, because
919
+ either can echo the credential the request carried; their transport failures
920
+ show the error class and the operating system's reason, not the library's
921
+ text. Other commands still print the server's message with the status and
922
+ code.
923
+
809
924
  ## Enterprise tenants
810
925
 
811
926
  Enterprise tenants are registered through an onboarding ceremony: AAC
@@ -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.2.9"
13
+ __version__ = "0.2.11"
@@ -0,0 +1,13 @@
1
+ """`python -m aac_cli` — same entrypoint as the `aac` console script."""
2
+
3
+ import sys
4
+
5
+ try:
6
+ from aac_cli.cli import entrypoint
7
+
8
+ if __name__ == "__main__":
9
+ entrypoint()
10
+ except KeyboardInterrupt:
11
+ # The shared command boundary is not loaded during early imports.
12
+ sys.stderr.write("aac: interrupted\n")
13
+ raise SystemExit(130) from None
@@ -223,6 +223,9 @@ def require_ed25519_public_key_pem(text: str) -> str:
223
223
  try:
224
224
  der = base64.b64decode(encoded, validate=True)
225
225
  except (binascii.Error, ValueError) as exc:
226
+ # The decoder's text names the fault ("Incorrect padding", a bad
227
+ # character's position) and never quotes the data, which is what keeps
228
+ # AdminKeyPemError on the CLI-composed list of the diagnostic contract.
226
229
  raise AdminKeyPemError(f"PEM body is not valid base64: {exc}") from exc
227
230
 
228
231
  if len(der) != _ED25519_SPKI_LENGTH or not der.startswith(_ED25519_SPKI_PREFIX):
@@ -7,6 +7,7 @@ import sys
7
7
  from importlib.resources import files
8
8
 
9
9
  from . import __version__
10
+ from .interrupts import run_command
10
11
 
11
12
 
12
13
  def version_text() -> str:
@@ -15,6 +16,10 @@ def version_text() -> str:
15
16
 
16
17
 
17
18
  def main(argv: list[str] | None = None) -> int:
19
+ return run_command(lambda: _main(argv), prog="aeg")
20
+
21
+
22
+ def _main(argv: list[str] | None = None) -> int:
18
23
  argv = list(sys.argv[1:] if argv is None else argv)
19
24
  if argv == ["--version"]:
20
25
  print(version_text())
@@ -619,13 +619,21 @@ def cmd_agent_list(args: argparse.Namespace) -> int:
619
619
  row["certificates"] = case_for(record.material_source).name
620
620
  tenant = tenant_paths(record.tenant_id) if record.tenant_id else None
621
621
  health = assess(agent, tenant, record)
622
+ row["profile_binding_consistent"] = health.profile_binding_consistent
622
623
  row["next_command"] = next_step(agent, tenant, record, health)
623
624
  rows.append(row)
624
625
  if args.output == "table":
625
626
  if not rows:
626
627
  print(f"no agents under {directory}")
627
628
  for row in rows:
628
- print(f"{row['agent']:<16}: {row.get('certificates', row.get('state', '-'))}")
629
+ binding_note = (
630
+ " (profile binding inconsistent)"
631
+ if row.get("profile_binding_consistent") is False
632
+ else ""
633
+ )
634
+ print(
635
+ f"{row['agent']:<16}: {row.get('certificates', row.get('state', '-'))}{binding_note}"
636
+ )
629
637
  return 0
630
638
  print(json.dumps({"agents": rows}, indent=2))
631
639
  return 0
@@ -708,7 +716,12 @@ def add_agent_parser(nouns: argparse._SubParsersAction) -> None:
708
716
  listing = verbs.add_parser(
709
717
  "list",
710
718
  help="List every agent under the CLI home with its tenant, case and state.",
711
- description="List every agent; an unreadable or unsupported record is reported in that agent's state without hiding the other agents.",
719
+ description=(
720
+ "List every agent; an unreadable or unsupported record is reported in that "
721
+ "agent's state without hiding the other agents. Invalid profile bindings "
722
+ "are reported per agent: JSON includes profile_binding_consistent and the "
723
+ "repair next_command; table output marks inconsistent bindings."
724
+ ),
712
725
  )
713
726
  listing.add_argument(
714
727
  "--output", choices=("json", "table"), default="json", help="Output mode (JSON by default)."
@@ -22,7 +22,13 @@ from aac_cli.agent_layout import (
22
22
  material_inventory,
23
23
  )
24
24
  from aac_cli.agent_record import STEP_MATERIAL, AgentRecord
25
- from aac_cli.config import CliConfigError, cli_home, credentials_path, read_profile_section
25
+ from aac_cli.config import (
26
+ CliConfigError,
27
+ cli_home,
28
+ credentials_path,
29
+ profile_binding_error,
30
+ read_profile_section,
31
+ )
26
32
  from aac_cli.material_cases import MATERIAL_ISSUED_HERE, MATERIAL_SUPPLIED, ca_report_label
27
33
  from aac_cli.secure_files import managed_path_problem
28
34
 
@@ -40,6 +46,7 @@ class CertificateReport:
40
46
  class AgentHealth:
41
47
  setup_complete: bool
42
48
  profile_binding_consistent: bool
49
+ invalid_profile_binding: bool = False
43
50
  missing_files: list[str] = field(default_factory=list)
44
51
  permission_problems: list[str] = field(default_factory=list)
45
52
  certificates: dict[str, CertificateReport] = field(default_factory=dict)
@@ -74,7 +81,9 @@ class AgentHealth:
74
81
  reasons: list[str] = []
75
82
  if not self.setup_complete:
76
83
  reasons.append("setup is incomplete")
77
- if not self.profile_binding_consistent:
84
+ if self.invalid_profile_binding:
85
+ reasons.append("the profile has an invalid stored tenant binding")
86
+ elif not self.profile_binding_consistent:
78
87
  reasons.append("the profile is bound to a different tenant than this agent")
79
88
  reasons.extend(f"missing: {name}" for name in self.missing_files)
80
89
  reasons.extend(self.permission_problems)
@@ -111,10 +120,16 @@ def _certificate_reports(
111
120
 
112
121
  def assess(agent: AgentPaths, tenant: TenantPaths | None, record: AgentRecord) -> AgentHealth:
113
122
  """The offline health assessment both `status` and a completed `init` report."""
114
- profile_tenant = read_profile_section(record.profile).get("tenant_id")
123
+ # Offline inventory diagnoses this agent's profile without allowing one
124
+ # stale association to abort every other agent's inventory. Operational
125
+ # profile resolution stays strict; material paths use the validated record.
126
+ stored = read_profile_section(record.profile, validate_binding=False)
127
+ invalid_binding = profile_binding_error(stored) is not None
115
128
  health = AgentHealth(
116
129
  setup_complete=record.next_step() is None,
117
- profile_binding_consistent=profile_tenant == record.tenant_id,
130
+ profile_binding_consistent=not invalid_binding
131
+ and stored.get("tenant_id") == record.tenant_id,
132
+ invalid_profile_binding=invalid_binding,
118
133
  )
119
134
  if tenant is None:
120
135
  return health
@@ -387,6 +402,8 @@ def next_step(
387
402
  ) -> str | None:
388
403
  """The one next step `status` reports and a refused `init` names; None when there is none."""
389
404
  agent_flag = f"--agent {_quote(agent.name)}"
405
+ if health.invalid_profile_binding:
406
+ return f"aac profile update {_quote(record.profile)} --unbind"
390
407
  if not health.setup_complete:
391
408
  return f"aac init --profile {_quote(record.profile)} {agent_flag}"
392
409
  if tenant is not None:
@@ -16,6 +16,7 @@ from aac_cli.config import (
16
16
  cli_home,
17
17
  credentials_path,
18
18
  )
19
+ from aac_cli.safe_output import failure_class
19
20
 
20
21
  __all__ = [
21
22
  "ROTATION_STATE_WARNING",
@@ -97,7 +98,9 @@ def read_rotation_state(profile: str) -> RotationIssueState | None:
97
98
  except FileNotFoundError:
98
99
  return None
99
100
  except OSError as exc:
100
- raise CliConfigError(f"cannot read API-key rotation state {path}: {exc}") from exc
101
+ raise CliConfigError(
102
+ f"cannot read API-key rotation state {path}: {failure_class(exc)}"
103
+ ) from exc
101
104
  if len(raw) > _MAX_STATE_BYTES:
102
105
  raise CliConfigError(f"API-key rotation state {path} exceeds {_MAX_STATE_BYTES} bytes")
103
106
  try: