aac-cli 0.2.2__tar.gz → 0.2.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.
- aac_cli-0.2.4/NOTICE +6 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/PKG-INFO +55 -26
- aac_cli-0.2.2/aac_cli.egg-info/PKG-INFO → aac_cli-0.2.4/README.md +49 -42
- aac_cli-0.2.4/THIRD_PARTY_NOTICES.md +46 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/__init__.py +1 -1
- aac_cli-0.2.4/aac_cli/_aeg/__init__.py +4 -0
- aac_cli-0.2.4/aac_cli/_aeg/cli.py +293 -0
- aac_cli-0.2.4/aac_cli/_aeg/data/action-taken-v1.json +58 -0
- aac_cli-0.2.4/aac_cli/_aeg/data/predicates-v1.json +38 -0
- aac_cli-0.2.4/aac_cli/_aeg/data/renderer-source.json +24 -0
- aac_cli-0.2.4/aac_cli/_aeg/from_sidecar.py +741 -0
- aac_cli-0.2.4/aac_cli/_aeg/inputs.py +318 -0
- aac_cli-0.2.4/aac_cli/_aeg/model.py +904 -0
- aac_cli-0.2.4/aac_cli/_aeg/narrative.py +139 -0
- aac_cli-0.2.4/aac_cli/_aeg/schemas.py +74 -0
- aac_cli-0.2.4/aac_cli/_aeg/temporal.py +274 -0
- aac_cli-0.2.4/aac_cli/_aeg/timeline_source.py +131 -0
- aac_cli-0.2.4/aac_cli/_aeg/visualizer.py +2038 -0
- aac_cli-0.2.4/aac_cli/aeg_cli.py +37 -0
- aac_cli-0.2.4/aac_cli/trace_query.py +29 -0
- aac_cli-0.2.2/README.md → aac_cli-0.2.4/aac_cli.egg-info/PKG-INFO +71 -25
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli.egg-info/SOURCES.txt +20 -2
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli.egg-info/requires.txt +2 -0
- aac_cli-0.2.4/launchers/aac +32 -0
- aac_cli-0.2.4/launchers/aeg +29 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/pyproject.toml +16 -6
- aac_cli-0.2.2/aac_cli.egg-info/entry_points.txt +0 -2
- {aac_cli-0.2.2 → aac_cli-0.2.4}/LICENSE +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/MANIFEST.in +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/__main__.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/admin_key_pem.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/agent_cli.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/agent_config.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/agent_health.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/agent_layout.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/agent_record.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/api_key_rotation_state.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/cli.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/config.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/config_render.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/dev_material.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/idp_recovery.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/init_cli.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/material_cases.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/profiles.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/reference.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/registration_state.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/secure_files.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/sso_login.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli/supplied_material.py +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli.egg-info/dependency_links.txt +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/aac_cli.egg-info/top_level.txt +0 -0
- {aac_cli-0.2.2 → aac_cli-0.2.4}/setup.cfg +0 -0
aac_cli-0.2.4/NOTICE
ADDED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aac-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.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
|
|
7
|
+
Project-URL: Documentation, https://cascadeauth.github.io/aac-starter-guide/aeg.html
|
|
7
8
|
Requires-Python: >=3.10
|
|
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"
|
|
@@ -24,7 +29,7 @@ which tenant they belong to and what they were authorised to do. A
|
|
|
24
29
|
runs beside an AAC sidecar that signs outgoing requests and verifies
|
|
25
30
|
incoming ones against public trust material the tenant publishes.
|
|
26
31
|
|
|
27
|
-
New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-
|
|
32
|
+
New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-starter-guide/#what-aac-is-for)
|
|
28
33
|
explains what delegated authority between agents buys you, and what it does not.
|
|
29
34
|
|
|
30
35
|
The `aac` command is how a tenant's operator does everything that is not
|
|
@@ -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`
|
|
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.
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
|
@@ -57,24 +65,45 @@ Python 3.10 or newer is required.
|
|
|
57
65
|
> happens to share the acronym. The AAC package is **`aac-cli`**; the
|
|
58
66
|
> command it installs is **`aac`**.
|
|
59
67
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
`aac
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 \
|
|
@@ -464,7 +493,7 @@ name and `<tenant-id>` the tenant id AAC allocated.
|
|
|
464
493
|
For what each of these proves, who else ever gets a copy, what never leaves you
|
|
465
494
|
and how the pieces fit together — including when your own certificate authority
|
|
466
495
|
signs instead of this tool — see
|
|
467
|
-
[Keys and certificates](https://cascadeauth.github.io/aac-
|
|
496
|
+
[Keys and certificates](https://cascadeauth.github.io/aac-starter-guide/keys-and-certificates.html).
|
|
468
497
|
|
|
469
498
|
<!-- inventory-table:start -->
|
|
470
499
|
| No. | Material | Where | Used for | Lifetime | Back up? |
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: aac-cli
|
|
3
|
-
Version: 0.2.2
|
|
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).
|
|
@@ -24,7 +7,7 @@ which tenant they belong to and what they were authorised to do. A
|
|
|
24
7
|
runs beside an AAC sidecar that signs outgoing requests and verifies
|
|
25
8
|
incoming ones against public trust material the tenant publishes.
|
|
26
9
|
|
|
27
|
-
New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-
|
|
10
|
+
New to AAC? [What AAC is for](https://cascadeauth.github.io/aac-starter-guide/#what-aac-is-for)
|
|
28
11
|
explains what delegated authority between agents buys you, and what it does not.
|
|
29
12
|
|
|
30
13
|
The `aac` command is how a tenant's operator does everything that is not
|
|
@@ -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`
|
|
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.
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
|
@@ -57,24 +43,45 @@ Python 3.10 or newer is required.
|
|
|
57
43
|
> happens to share the acronym. The AAC package is **`aac-cli`**; the
|
|
58
44
|
> command it installs is **`aac`**.
|
|
59
45
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
`aac
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 \
|
|
@@ -464,7 +471,7 @@ name and `<tenant-id>` the tenant id AAC allocated.
|
|
|
464
471
|
For what each of these proves, who else ever gets a copy, what never leaves you
|
|
465
472
|
and how the pieces fit together — including when your own certificate authority
|
|
466
473
|
signs instead of this tool — see
|
|
467
|
-
[Keys and certificates](https://cascadeauth.github.io/aac-
|
|
474
|
+
[Keys and certificates](https://cascadeauth.github.io/aac-starter-guide/keys-and-certificates.html).
|
|
468
475
|
|
|
469
476
|
<!-- inventory-table:start -->
|
|
470
477
|
| No. | Material | Where | Used for | Lifetime | Back up? |
|
|
@@ -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/
|
|
@@ -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())
|