aac-cli 0.2.3__tar.gz → 0.2.5__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 (54) hide show
  1. aac_cli-0.2.5/NOTICE +6 -0
  2. {aac_cli-0.2.3 → aac_cli-0.2.5}/PKG-INFO +68 -26
  3. aac_cli-0.2.3/aac_cli.egg-info/PKG-INFO → aac_cli-0.2.5/README.md +61 -41
  4. aac_cli-0.2.5/THIRD_PARTY_NOTICES.md +46 -0
  5. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/__init__.py +1 -1
  6. aac_cli-0.2.5/aac_cli/_aeg/__init__.py +4 -0
  7. aac_cli-0.2.5/aac_cli/_aeg/cli.py +293 -0
  8. aac_cli-0.2.5/aac_cli/_aeg/data/action-taken-v1.json +58 -0
  9. aac_cli-0.2.5/aac_cli/_aeg/data/predicates-v1.json +38 -0
  10. aac_cli-0.2.5/aac_cli/_aeg/data/renderer-source.json +24 -0
  11. aac_cli-0.2.5/aac_cli/_aeg/from_sidecar.py +741 -0
  12. aac_cli-0.2.5/aac_cli/_aeg/inputs.py +318 -0
  13. aac_cli-0.2.5/aac_cli/_aeg/model.py +904 -0
  14. aac_cli-0.2.5/aac_cli/_aeg/narrative.py +139 -0
  15. aac_cli-0.2.5/aac_cli/_aeg/schemas.py +74 -0
  16. aac_cli-0.2.5/aac_cli/_aeg/temporal.py +274 -0
  17. aac_cli-0.2.5/aac_cli/_aeg/timeline_source.py +131 -0
  18. aac_cli-0.2.5/aac_cli/_aeg/visualizer.py +2038 -0
  19. aac_cli-0.2.5/aac_cli/aeg_cli.py +37 -0
  20. aac_cli-0.2.5/aac_cli/chain_list.py +100 -0
  21. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/cli.py +59 -1
  22. aac_cli-0.2.5/aac_cli/trace_query.py +29 -0
  23. aac_cli-0.2.3/README.md → aac_cli-0.2.5/aac_cli.egg-info/PKG-INFO +83 -24
  24. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli.egg-info/SOURCES.txt +21 -2
  25. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli.egg-info/requires.txt +2 -0
  26. aac_cli-0.2.5/launchers/aac +32 -0
  27. aac_cli-0.2.5/launchers/aeg +29 -0
  28. {aac_cli-0.2.3 → aac_cli-0.2.5}/pyproject.toml +19 -9
  29. aac_cli-0.2.3/aac_cli.egg-info/entry_points.txt +0 -2
  30. {aac_cli-0.2.3 → aac_cli-0.2.5}/LICENSE +0 -0
  31. {aac_cli-0.2.3 → aac_cli-0.2.5}/MANIFEST.in +0 -0
  32. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/__main__.py +0 -0
  33. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/admin_key_pem.py +0 -0
  34. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/agent_cli.py +0 -0
  35. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/agent_config.py +0 -0
  36. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/agent_health.py +0 -0
  37. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/agent_layout.py +0 -0
  38. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/agent_record.py +0 -0
  39. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/api_key_rotation_state.py +0 -0
  40. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/config.py +0 -0
  41. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/config_render.py +0 -0
  42. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/dev_material.py +0 -0
  43. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/idp_recovery.py +0 -0
  44. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/init_cli.py +0 -0
  45. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/material_cases.py +0 -0
  46. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/profiles.py +0 -0
  47. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/reference.py +0 -0
  48. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/registration_state.py +0 -0
  49. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/secure_files.py +0 -0
  50. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/sso_login.py +0 -0
  51. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli/supplied_material.py +0 -0
  52. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli.egg-info/dependency_links.txt +0 -0
  53. {aac_cli-0.2.3 → aac_cli-0.2.5}/aac_cli.egg-info/top_level.txt +0 -0
  54. {aac_cli-0.2.3 → aac_cli-0.2.5}/setup.cfg +0 -0
aac_cli-0.2.5/NOTICE ADDED
@@ -0,0 +1,6 @@
1
+ aac-aeg
2
+ Copyright 2026 CascadeAuth contributors.
3
+
4
+ Interactive output includes vis-network assets provided by pyvis. Their embedded
5
+ license notices are retained in generated HTML. pyvis and its dependencies are
6
+ distributed separately under their own licenses.
@@ -1,15 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aac-cli
3
- Version: 0.2.3
3
+ Version: 0.2.5
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
7
- Requires-Python: >=3.10
7
+ Project-URL: Documentation, https://cascadeauth.github.io/aac-starter-guide/aeg.html
8
+ Requires-Python: >=3.11
8
9
  Description-Content-Type: text/markdown
9
10
  License-File: LICENSE
11
+ License-File: NOTICE
12
+ License-File: THIRD_PARTY_NOTICES.md
10
13
  Requires-Dist: httpx>=0.28
11
14
  Requires-Dist: cryptography>=42.0
12
15
  Requires-Dist: PyYAML>=6.0
16
+ Requires-Dist: pyvis<0.4,>=0.3.2
17
+ Requires-Dist: jsonschema>=4.18
13
18
  Provides-Extra: test
14
19
  Requires-Dist: pytest>=8.0; extra == "test"
15
20
  Requires-Dist: pytest-httpx>=0.30; extra == "test"
@@ -37,12 +42,15 @@ result also offers `--output table`.
37
42
  Two kinds of tenant use the same tool:
38
43
 
39
44
  * **Developer tenants** register themselves by signing in with GitHub or
40
- Google. `aac init` takes a developer from an installed CLI to a runnable
41
- local workload in one command.
45
+ Google. `aac init` can register the tenant as part of guided agent setup.
42
46
  * **Enterprise tenants** are registered through an onboarding ceremony with
43
- AAC operations and sign in through their own identity provider. Their
44
- keys, domains and workloads are managed with the `aac tenant` and
45
- `aac sso` commands described further down.
47
+ AAC operations and sign in through their own identity provider. After
48
+ onboarding, they can use the same guided agent setup.
49
+
50
+ Both kinds use `aac init` to prepare AAC identity, material and configuration
51
+ for an agent; you start the application and its sidecar separately. The
52
+ `aac tenant` and `aac sso` commands described below manage tenants, keys and
53
+ sign-in for both kinds.
46
54
 
47
55
  ## Install
48
56
 
@@ -51,30 +59,51 @@ pip install aac-cli
51
59
  aac --version
52
60
  ```
53
61
 
54
- Python 3.10 or newer is required.
62
+ Python 3.11 or newer is required.
55
63
 
56
64
  > **Mind the name.** `pip install aac` installs an unrelated project that
57
65
  > happens to share the acronym. The AAC package is **`aac-cli`**; the
58
66
  > command it installs is **`aac`**.
59
67
 
60
- ## Guided setup for developer tenants: `aac init`
61
-
62
- `aac init` prepares the AAC side of one of your agents on this machine: it
63
- registers the agent's identity with AAC and creates the keys, certificates
64
- and configuration its sidecar needs. The agent itself is yours. The first
65
- run also creates your profile and registers your tenant; run it again with
66
- a new `--agent` and its own `--agent-config` file, and the same `--trust-url`
67
- and `--idp`, for each further agent.
68
-
69
- Along the way it signs you in, registers a developer tenant (or reuses the
70
- one your profile is already bound to), takes the trust domain AAC assigns to the
71
- tenant, registers your workload, generates development keys and
72
- certificates, and writes a complete sidecar configuration together with the
73
- files the trust-anchor publisher and Docker Compose need.
74
-
75
- Point it at the endpoints your AAC environment gives you. The example below
76
- uses the AAC stage environment. First save the YAML example in “Agent configuration”
77
- below as `orders.yaml`:
68
+ ## Use `aeg` to view agent execution graphs
69
+
70
+ Installing or upgrading `aac-cli` supplies both `aac` and `aeg`.
71
+ Use the [AEG guide and command reference](https://cascadeauth.github.io/aac-starter-guide/aeg.html)
72
+ for rendering local evidence, selecting a profile, and migrating an older
73
+ standalone AEG installation.
74
+
75
+ ## Guided setup using `aac init`
76
+
77
+ `aac init` prepares your **AAC CLI home** (`~/.aac` by default) and sets up
78
+ AAC for an agent on this machine: it registers the agent's identity and
79
+ prepares the keys, certificates and configuration its sidecar needs. You
80
+ provide the agent application and start it and its sidecar separately.
81
+
82
+ The CLI home can hold multiple profiles, tenants and agents. It stores
83
+ profiles, tenant credentials and shared tenant material alongside a separate
84
+ folder for each agent. Each invocation initializes or resumes **one agent**,
85
+ selected by `--agent`. Run it again with the same tenant profile, a new agent
86
+ name and its own `--agent-config` file for each additional agent; existing
87
+ tenant setup is reused. Set `AAC_CLI_HOME` to use a different home directory.
88
+
89
+ Both developer and enterprise tenants can use this setup. For a new
90
+ developer tenant, the first run also creates the profile and registers the
91
+ tenant. For an enterprise tenant, complete the onboarding steps under
92
+ “Enterprise tenants” below, sign in through its configured identity provider,
93
+ and use a profile bound to that tenant with its active tenant-admin key
94
+ available locally.
95
+ Omit `--idp` for this enterprise flow; that flag selects only GitHub or
96
+ Google developer sign-in. See “Setting up aac-cli on a new computer” below
97
+ for the existing-tenant sign-in and material preparation steps.
98
+
99
+ Setup uses the tenant's assigned trust domain and writes the files the
100
+ trust-anchor publisher and Docker Compose need, as well as the sidecar
101
+ configuration. By default it generates development keys and certificates;
102
+ the certificate choices below explain how to supply your own instead.
103
+
104
+ Point it at the endpoints your AAC environment gives you. The developer-tenant
105
+ example below uses the AAC stage environment. First save the YAML example in
106
+ “Agent configuration” below as `orders.yaml`:
78
107
 
79
108
  ```bash
80
109
  aac init --profile stage --agent orders --agent-config orders.yaml \
@@ -944,6 +973,7 @@ Deactivation is terminal, and a deactivated SPIFFE id stays reserved.
944
973
  ```bash
945
974
  aac trust-anchor list --profile prod --output table # every published key, every lifecycle state
946
975
  aac trust-anchor describe --profile prod --kid starter-root-v1
976
+ aac chain list --profile prod --since 7d --output table
947
977
  aac chain show --profile prod \
948
978
  --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 --output table
949
979
  aac chain show --profile prod \
@@ -955,6 +985,18 @@ aac chain show --profile prod \
955
985
  chain to any tenant that took part in it. The timeline carries metadata
956
986
  only; the business content of each step stays in your own audit stream.
957
987
 
988
+ `chain list` discovers roots using central observations for your tenant. The
989
+ default is the last 24 hours; use `--since 7d` or timezone-qualified
990
+ `--from 2026-09-01T00:00Z --to 2026-09-08T00:00Z` for a different interval
991
+ (maximum 31 days). It reads no local files or business labels. JSON includes
992
+ the resolved interval and `next_page_token`; pass that token with
993
+ `--page-token` to continue the same query. Each invocation fetches one page
994
+ (default 50 rows, maximum 200). Tokens expire 15 minutes after the first page.
995
+ Central evidence may be incomplete and pages are live: late forwarding can
996
+ change summaries or add roots, including roots behind the current page.
997
+ Repeat the original interval to refresh. A returned `root_token_id` works
998
+ with `chain show --token-id` and `aeg render --root-token-id`.
999
+
958
1000
  ## Getting help
959
1001
 
960
1002
  `aac --help` lists the command groups and `aac <command> --help` shows every
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: aac-cli
3
- Version: 0.2.3
4
- Summary: The aac platform CLI — headless operator interface to the AAC control plane (tenant registration, chain audit).
5
- Author: Agent Authority Cloud Project
6
- License-Expression: Apache-2.0
7
- Requires-Python: >=3.10
8
- Description-Content-Type: text/markdown
9
- License-File: LICENSE
10
- Requires-Dist: httpx>=0.28
11
- Requires-Dist: cryptography>=42.0
12
- Requires-Dist: PyYAML>=6.0
13
- Provides-Extra: test
14
- Requires-Dist: pytest>=8.0; extra == "test"
15
- Requires-Dist: pytest-httpx>=0.30; extra == "test"
16
- Dynamic: license-file
17
-
18
1
  # aac-cli — the `aac` command
19
2
 
20
3
  `aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
@@ -37,12 +20,15 @@ result also offers `--output table`.
37
20
  Two kinds of tenant use the same tool:
38
21
 
39
22
  * **Developer tenants** register themselves by signing in with GitHub or
40
- Google. `aac init` takes a developer from an installed CLI to a runnable
41
- local workload in one command.
23
+ Google. `aac init` can register the tenant as part of guided agent setup.
42
24
  * **Enterprise tenants** are registered through an onboarding ceremony with
43
- AAC operations and sign in through their own identity provider. Their
44
- keys, domains and workloads are managed with the `aac tenant` and
45
- `aac sso` commands described further down.
25
+ AAC operations and sign in through their own identity provider. After
26
+ onboarding, they can use the same guided agent setup.
27
+
28
+ Both kinds use `aac init` to prepare AAC identity, material and configuration
29
+ for an agent; you start the application and its sidecar separately. The
30
+ `aac tenant` and `aac sso` commands described below manage tenants, keys and
31
+ sign-in for both kinds.
46
32
 
47
33
  ## Install
48
34
 
@@ -51,30 +37,51 @@ pip install aac-cli
51
37
  aac --version
52
38
  ```
53
39
 
54
- Python 3.10 or newer is required.
40
+ Python 3.11 or newer is required.
55
41
 
56
42
  > **Mind the name.** `pip install aac` installs an unrelated project that
57
43
  > happens to share the acronym. The AAC package is **`aac-cli`**; the
58
44
  > command it installs is **`aac`**.
59
45
 
60
- ## Guided setup for developer tenants: `aac init`
61
-
62
- `aac init` prepares the AAC side of one of your agents on this machine: it
63
- registers the agent's identity with AAC and creates the keys, certificates
64
- and configuration its sidecar needs. The agent itself is yours. The first
65
- run also creates your profile and registers your tenant; run it again with
66
- a new `--agent` and its own `--agent-config` file, and the same `--trust-url`
67
- and `--idp`, for each further agent.
68
-
69
- Along the way it signs you in, registers a developer tenant (or reuses the
70
- one your profile is already bound to), takes the trust domain AAC assigns to the
71
- tenant, registers your workload, generates development keys and
72
- certificates, and writes a complete sidecar configuration together with the
73
- files the trust-anchor publisher and Docker Compose need.
74
-
75
- Point it at the endpoints your AAC environment gives you. The example below
76
- uses the AAC stage environment. First save the YAML example in “Agent configuration”
77
- below as `orders.yaml`:
46
+ ## Use `aeg` to view agent execution graphs
47
+
48
+ Installing or upgrading `aac-cli` supplies both `aac` and `aeg`.
49
+ Use the [AEG guide and command reference](https://cascadeauth.github.io/aac-starter-guide/aeg.html)
50
+ for rendering local evidence, selecting a profile, and migrating an older
51
+ standalone AEG installation.
52
+
53
+ ## Guided setup using `aac init`
54
+
55
+ `aac init` prepares your **AAC CLI home** (`~/.aac` by default) and sets up
56
+ AAC for an agent on this machine: it registers the agent's identity and
57
+ prepares the keys, certificates and configuration its sidecar needs. You
58
+ provide the agent application and start it and its sidecar separately.
59
+
60
+ The CLI home can hold multiple profiles, tenants and agents. It stores
61
+ profiles, tenant credentials and shared tenant material alongside a separate
62
+ folder for each agent. Each invocation initializes or resumes **one agent**,
63
+ selected by `--agent`. Run it again with the same tenant profile, a new agent
64
+ name and its own `--agent-config` file for each additional agent; existing
65
+ tenant setup is reused. Set `AAC_CLI_HOME` to use a different home directory.
66
+
67
+ Both developer and enterprise tenants can use this setup. For a new
68
+ developer tenant, the first run also creates the profile and registers the
69
+ tenant. For an enterprise tenant, complete the onboarding steps under
70
+ “Enterprise tenants” below, sign in through its configured identity provider,
71
+ and use a profile bound to that tenant with its active tenant-admin key
72
+ available locally.
73
+ Omit `--idp` for this enterprise flow; that flag selects only GitHub or
74
+ Google developer sign-in. See “Setting up aac-cli on a new computer” below
75
+ for the existing-tenant sign-in and material preparation steps.
76
+
77
+ Setup uses the tenant's assigned trust domain and writes the files the
78
+ trust-anchor publisher and Docker Compose need, as well as the sidecar
79
+ configuration. By default it generates development keys and certificates;
80
+ the certificate choices below explain how to supply your own instead.
81
+
82
+ Point it at the endpoints your AAC environment gives you. The developer-tenant
83
+ example below uses the AAC stage environment. First save the YAML example in
84
+ “Agent configuration” below as `orders.yaml`:
78
85
 
79
86
  ```bash
80
87
  aac init --profile stage --agent orders --agent-config orders.yaml \
@@ -944,6 +951,7 @@ Deactivation is terminal, and a deactivated SPIFFE id stays reserved.
944
951
  ```bash
945
952
  aac trust-anchor list --profile prod --output table # every published key, every lifecycle state
946
953
  aac trust-anchor describe --profile prod --kid starter-root-v1
954
+ aac chain list --profile prod --since 7d --output table
947
955
  aac chain show --profile prod \
948
956
  --token-id c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9c3d5e7f9 --output table
949
957
  aac chain show --profile prod \
@@ -955,6 +963,18 @@ aac chain show --profile prod \
955
963
  chain to any tenant that took part in it. The timeline carries metadata
956
964
  only; the business content of each step stays in your own audit stream.
957
965
 
966
+ `chain list` discovers roots using central observations for your tenant. The
967
+ default is the last 24 hours; use `--since 7d` or timezone-qualified
968
+ `--from 2026-09-01T00:00Z --to 2026-09-08T00:00Z` for a different interval
969
+ (maximum 31 days). It reads no local files or business labels. JSON includes
970
+ the resolved interval and `next_page_token`; pass that token with
971
+ `--page-token` to continue the same query. Each invocation fetches one page
972
+ (default 50 rows, maximum 200). Tokens expire 15 minutes after the first page.
973
+ Central evidence may be incomplete and pages are live: late forwarding can
974
+ change summaries or add roots, including roots behind the current page.
975
+ Repeat the original interval to refresh. A returned `root_token_id` works
976
+ with `chain show --token-id` and `aeg render --root-token-id`.
977
+
958
978
  ## Getting help
959
979
 
960
980
  `aac --help` lists the command groups and `aac <command> --help` shows every
@@ -0,0 +1,46 @@
1
+ # Renderer third-party notices
2
+
3
+ The renderer uses pyvis 0.3.x, distributed separately as a declared dependency.
4
+ Interactive HTML includes vis-network 9.1.2 assets supplied by pyvis. Embedded
5
+ notices remain in the generated HTML. Other dependencies retain their own
6
+ license material in their distributions.
7
+
8
+ ## pyvis (BSD-3-Clause)
9
+
10
+ Copyright (c) 2018, West Health Institute
11
+ All rights reserved.
12
+
13
+ Redistribution and use in source and binary forms, with or without modification,
14
+ are permitted provided that the following conditions are met:
15
+
16
+ - Redistributions of source code must retain the above copyright notice,
17
+ this list of conditions and the following disclaimer.
18
+
19
+ - Redistributions in binary form must reproduce the above copyright notice,
20
+ this list of conditions and the following disclaimer in the documentation
21
+ and/or other materials provided with the distribution.
22
+
23
+ - Neither the name of West Health Institute nor the names of its contributors may
24
+ be used to endorse or promote products derived from this software without
25
+ specific prior written permission.
26
+
27
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
28
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
31
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
34
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
+
38
+ ## vis-network 9.1.2
39
+
40
+ Copyright (c) 2011-2017 Almende B.V.
41
+ Copyright (c) 2017-2019 visjs contributors.
42
+
43
+ vis-network is dual licensed under Apache-2.0 and MIT. The AAC renderer uses
44
+ these assets under Apache-2.0; the full license accompanies this distribution
45
+ in LICENSE. The original dual-license notice remains in the generated HTML.
46
+ Source: https://visjs.github.io/vis-network/
@@ -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.3"
13
+ __version__ = "0.2.5"
@@ -0,0 +1,4 @@
1
+ """Agent Execution Graph readers, token model and self-contained renderers.
2
+
3
+ Import as aac_aeg. Hosted database adapters are supplied by the host application.
4
+ The model and renderers are shared by local commands and hosted consumers."""
@@ -0,0 +1,293 @@
1
+ """Public local renderer and run discovery; online access uses only aac CLI JSON."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import re
8
+ import sys
9
+ import tempfile
10
+ from collections.abc import Callable
11
+ from datetime import datetime, timedelta, timezone
12
+ from pathlib import Path
13
+
14
+ from .inputs import (
15
+ InputError,
16
+ read_actions,
17
+ read_events,
18
+ read_json,
19
+ read_trace,
20
+ roots_and_mapping,
21
+ select_events,
22
+ token_id,
23
+ unjoinable_diagnostics,
24
+ )
25
+ from .model import extract_token_relation_graph
26
+ from .visualizer import build_visual_aeg, write_html_aeg
27
+
28
+ Query = Callable[[str, str], tuple[str, list]]
29
+
30
+
31
+ def parser(
32
+ *, prog: str = "aeg", version_text: str = "shared AAC renderer"
33
+ ) -> argparse.ArgumentParser:
34
+ result = argparse.ArgumentParser(
35
+ prog=prog,
36
+ description="Explore observed authority and business evidence. Local inputs are never uploaded.",
37
+ )
38
+ result.add_argument("--version", action="version", version=version_text)
39
+ commands = result.add_subparsers(dest="command", required=True)
40
+ render = commands.add_parser("render", help="Write a self-contained interactive HTML graph.")
41
+ listing = commands.add_parser(
42
+ "list", help="List candidate roots from explicitly supplied local files."
43
+ )
44
+ for command in (render, listing):
45
+ command.add_argument(
46
+ "--events",
47
+ type=Path,
48
+ action="append",
49
+ default=[],
50
+ metavar="FILE",
51
+ help="Sidecar JSONL file; repeat for each agent.",
52
+ )
53
+ command.add_argument(
54
+ "--actions",
55
+ type=Path,
56
+ action="append",
57
+ default=[],
58
+ metavar="FILE",
59
+ help="Application action_taken JSONL file; repeat as needed.",
60
+ )
61
+ source = render.add_mutually_exclusive_group()
62
+ source.add_argument(
63
+ "--profile", help="Query this existing AAC CLI profile for authorized central metadata."
64
+ )
65
+ source.add_argument(
66
+ "--trace-json",
67
+ type=Path,
68
+ metavar="FILE",
69
+ help="Read a saved chain-show JSON export offline.",
70
+ )
71
+ selector = render.add_mutually_exclusive_group()
72
+ selector.add_argument("--root-token-id", metavar="ID")
73
+ selector.add_argument("--mint-response", type=Path, metavar="FILE")
74
+ render.add_argument("--output", type=Path, required=True, metavar="FILE")
75
+ listing.add_argument("--task-ref", help="Exact local task reference.")
76
+ time_start = listing.add_mutually_exclusive_group()
77
+ time_start.add_argument("--since", help="Look back from now, e.g. 24h or 7d.")
78
+ time_start.add_argument(
79
+ "--from", dest="from_time", help="Inclusive ISO 8601 observation time with timezone."
80
+ )
81
+ listing.add_argument(
82
+ "--to", dest="to_time", help="Exclusive ISO 8601 observation time with timezone."
83
+ )
84
+ listing.add_argument("--output", choices=("table", "json"), default="table")
85
+ return result
86
+
87
+
88
+ def _query(profile: str, root: str):
89
+ raise InputError("online rendering requires the aeg command supplied by aac-cli")
90
+
91
+
92
+ def _render(args, events, query: Query) -> int:
93
+ if args.profile is not None and not args.profile.strip():
94
+ raise InputError("--profile must name an existing AAC CLI profile")
95
+ if not (args.events or args.actions or args.profile or args.trace_json):
96
+ raise InputError(
97
+ "supply --events, --actions, --trace-json or --profile; a root ID is a selector, not evidence"
98
+ )
99
+ root = args.root_token_id
100
+ if args.mint_response:
101
+ response = read_json(args.mint_response)
102
+ if not isinstance(response, dict):
103
+ raise InputError(f"{args.mint_response}: expected successful mint-response JSON object")
104
+ root = token_id(response.get("root_token_id"), str(args.mint_response))
105
+ if root is not None:
106
+ root = token_id(root, "--root-token-id")
107
+ trace_root = None
108
+ if args.trace_json:
109
+ trace_root, trace_events = read_trace(read_json(args.trace_json), str(args.trace_json))
110
+ events.extend(trace_events)
111
+ if root and root != trace_root:
112
+ raise InputError("selected root does not match --trace-json root_token_id")
113
+ if root is None:
114
+ roots, _ = roots_and_mapping(events)
115
+ if trace_root:
116
+ root = trace_root
117
+ elif len(roots) == 1:
118
+ root = next(iter(roots))
119
+ else:
120
+ raise InputError(
121
+ "cannot select one root; use local list then --root-token-id or --mint-response (never selects latest)"
122
+ )
123
+ print(f"Selected root: {root}", file=sys.stderr)
124
+ central_status = "not requested (offline)"
125
+ query_failed = False
126
+ if args.profile:
127
+ try:
128
+ queried_root, central = query(args.profile, root)
129
+ if queried_root != root:
130
+ raise InputError("central response root does not match selected root")
131
+ events.extend(central)
132
+ central_status = "available; best-effort metadata, not a complete execution record"
133
+ except InputError as exc:
134
+ query_failed = True
135
+ central_status = str(exc)
136
+ print(central_status, file=sys.stderr)
137
+ elif args.trace_json:
138
+ central_status = f"saved trace: {args.trace_json}; offline metadata export"
139
+ selected, diagnostics = select_events(events, root)
140
+ graph = extract_token_relation_graph(selected)
141
+ if not graph.tokens:
142
+ raise InputError(
143
+ "no reconstructable token observations for the selected root; supply sidecar evidence or an available central trace"
144
+ )
145
+ graph.diagnostics.extend(diagnostics)
146
+ graph.diagnostics.extend(
147
+ [
148
+ f"Selected root: {root}",
149
+ f"Central evidence: {central_status}",
150
+ "Partial evidence: only supplied observations are shown. Missing participants, ancestors, fields and outcomes are not success or failure.",
151
+ "Central metadata intentionally omits private business details, authority caps, exact expiry and full receipts. Forwarding may also omit or delay events.",
152
+ "Application records are reports, not verified business truth. A mint/receive success does not prove business completion. Receipt verdicts are observations, not re-verification by this tool.",
153
+ "Distinct action records are retained; these formats do not establish exactly-once business-action counts.",
154
+ ]
155
+ )
156
+ protected = [
157
+ *args.events,
158
+ *args.actions,
159
+ *([args.trace_json] if args.trace_json else []),
160
+ *([args.mint_response] if args.mint_response else []),
161
+ ]
162
+ if args.output.resolve() in {path.resolve() for path in protected}:
163
+ raise InputError("--output must not overwrite an evidence input")
164
+ visual = build_visual_aeg(graph)
165
+ with tempfile.TemporaryDirectory(prefix="aac-aeg-") as directory:
166
+ rendered = write_html_aeg(visual, root, out_dir=Path(directory))
167
+ args.output.parent.mkdir(parents=True, exist_ok=True)
168
+ args.output.write_text(rendered.read_text(encoding="utf-8"), encoding="utf-8")
169
+ for diagnostic in diagnostics:
170
+ print(diagnostic, file=sys.stderr)
171
+ print(
172
+ f"AEG written to {args.output} ({len(graph.tokens)} observed/referenced tokens; partial evidence)",
173
+ file=sys.stderr,
174
+ )
175
+ return 4 if query_failed else 0
176
+
177
+
178
+ def _date(value: str) -> datetime:
179
+ try:
180
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
181
+ if parsed.tzinfo is None:
182
+ raise ValueError("timezone required")
183
+ return parsed.astimezone(timezone.utc)
184
+ except ValueError as exc:
185
+ raise InputError(f"invalid time {value!r}; use ISO 8601 with timezone") from exc
186
+
187
+
188
+ def _list(args, events) -> int:
189
+ if not (args.events or args.actions):
190
+ raise InputError(
191
+ "local list requires --events and/or --actions; profile and hybrid listing belong to a later release"
192
+ )
193
+ start = _date(args.from_time) if args.from_time else None
194
+ stop = _date(args.to_time) if args.to_time else None
195
+ if args.since:
196
+ match = re.fullmatch(r"([1-9][0-9]*)([mhd])", args.since)
197
+ if not match:
198
+ raise InputError("--since requires a positive duration such as 30m, 24h or 7d")
199
+ try:
200
+ start = datetime.now(timezone.utc) - timedelta(
201
+ seconds=int(match[1]) * {"m": 60, "h": 3600, "d": 86400}[match[2]]
202
+ )
203
+ except OverflowError as exc:
204
+ raise InputError("--since duration is outside supported range") from exc
205
+ if start and stop and start > stop:
206
+ raise InputError("--from/--since must precede --to")
207
+ roots, mapping = roots_and_mapping(events)
208
+ diagnostics = [
209
+ f"{e.raw_event.get('_source', e.source)}: unmatched action {e.token_id}; supply sidecar root mapping"
210
+ for e in events
211
+ if e.event_type == "action_taken" and e.token_id not in mapping
212
+ ]
213
+ rows = []
214
+ diagnostics.extend(unjoinable_diagnostics(events))
215
+ for root in sorted(roots):
216
+ observed = [
217
+ e
218
+ for e in events
219
+ if e.root_token_id == root
220
+ or (e.event_type == "action_taken" and mapping.get(e.token_id) == root)
221
+ ]
222
+ task_refs = sorted(
223
+ {
224
+ str(
225
+ e.raw_event.get("task_ref")
226
+ or (e.raw_event.get("action_payload") or {}).get("task_ref")
227
+ )
228
+ for e in observed
229
+ if e.raw_event.get("task_ref")
230
+ or (e.raw_event.get("action_payload") or {}).get("task_ref")
231
+ }
232
+ )
233
+ if args.task_ref and args.task_ref not in task_refs:
234
+ continue
235
+ observed = [
236
+ e
237
+ for e in observed
238
+ if (start is None or _date(e.timestamp_iso) >= start)
239
+ and (stop is None or _date(e.timestamp_iso) < stop)
240
+ ]
241
+ if not observed:
242
+ continue
243
+ times = sorted(e.timestamp_iso for e in observed)
244
+ rows.append(
245
+ {
246
+ "root_token_id": root,
247
+ "task_refs": task_refs,
248
+ "first_observed": times[0],
249
+ "last_observed": times[-1],
250
+ "sources": "local",
251
+ "evidence_sources": sorted(
252
+ {str(e.raw_event.get("_source", e.source)) for e in observed}
253
+ ),
254
+ }
255
+ )
256
+ if args.output == "json":
257
+ print(json.dumps({"chains": rows, "diagnostics": diagnostics}, indent=2))
258
+ else:
259
+ print("ROOT TOKEN ID | TASK REFERENCES | FIRST OBSERVED (UTC) | SOURCES")
260
+ for row in rows:
261
+ print(
262
+ f"{row['root_token_id']} | {', '.join(row['task_refs']) or '(unknown)'} | {row['first_observed']} | local"
263
+ )
264
+ for diagnostic in diagnostics:
265
+ print(diagnostic, file=sys.stderr)
266
+ return 0
267
+
268
+
269
+ def main(
270
+ argv: list[str] | None = None,
271
+ *,
272
+ query: Query | None = None,
273
+ prog: str = "aeg",
274
+ version_text: str = "shared AAC renderer",
275
+ ) -> int:
276
+ args = parser(prog=prog, version_text=version_text).parse_args(argv)
277
+ events = []
278
+ try:
279
+ events = read_events(args.events) + read_actions(args.actions)
280
+ return (
281
+ _render(args, events, query or _query)
282
+ if args.command == "render"
283
+ else _list(args, events)
284
+ )
285
+ except (InputError, ValueError, OSError) as exc:
286
+ for diagnostic in unjoinable_diagnostics(events):
287
+ print(diagnostic, file=sys.stderr)
288
+ print(f"{prog}: {exc}", file=sys.stderr)
289
+ return 2
290
+
291
+
292
+ if __name__ == "__main__":
293
+ raise SystemExit(main())