aac-cli 0.2.11__tar.gz → 0.2.13__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 (58) hide show
  1. {aac_cli-0.2.11/aac_cli.egg-info → aac_cli-0.2.13}/PKG-INFO +29 -25
  2. {aac_cli-0.2.11 → aac_cli-0.2.13}/README.md +28 -24
  3. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/__init__.py +1 -1
  4. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/cli.py +5 -2
  5. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/renderer-source.json +7 -7
  6. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/from_sidecar.py +4 -0
  7. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/inputs.py +2 -0
  8. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/model.py +97 -6
  9. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/provenance.py +22 -6
  10. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/visualizer.py +51 -8
  11. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_cli.py +4 -3
  12. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_health.py +5 -1
  13. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_layout.py +6 -4
  14. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/dev_material.py +47 -8
  15. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/init_cli.py +5 -1
  16. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/material_cases.py +3 -2
  17. {aac_cli-0.2.11 → aac_cli-0.2.13/aac_cli.egg-info}/PKG-INFO +29 -25
  18. {aac_cli-0.2.11 → aac_cli-0.2.13}/pyproject.toml +1 -1
  19. {aac_cli-0.2.11 → aac_cli-0.2.13}/LICENSE +0 -0
  20. {aac_cli-0.2.11 → aac_cli-0.2.13}/MANIFEST.in +0 -0
  21. {aac_cli-0.2.11 → aac_cli-0.2.13}/NOTICE +0 -0
  22. {aac_cli-0.2.11 → aac_cli-0.2.13}/THIRD_PARTY_NOTICES.md +0 -0
  23. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/__main__.py +0 -0
  24. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/__init__.py +0 -0
  25. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/action-taken-v1.json +0 -0
  26. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/data/predicates-v1.json +0 -0
  27. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/narrative.py +0 -0
  28. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/schemas.py +0 -0
  29. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/temporal.py +0 -0
  30. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/theme.py +0 -0
  31. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/_aeg/timeline_source.py +0 -0
  32. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/admin_key_pem.py +0 -0
  33. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/aeg_cli.py +0 -0
  34. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/aeg_list.py +0 -0
  35. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_config.py +0 -0
  36. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/agent_record.py +0 -0
  37. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/api_key_rotation_state.py +0 -0
  38. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/chain_list.py +0 -0
  39. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/cli.py +0 -0
  40. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/config.py +0 -0
  41. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/config_render.py +0 -0
  42. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/idp_recovery.py +0 -0
  43. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/interrupts.py +0 -0
  44. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/profiles.py +0 -0
  45. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/reference.py +0 -0
  46. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/registration_state.py +0 -0
  47. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/safe_output.py +0 -0
  48. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/secure_files.py +0 -0
  49. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/sso_login.py +0 -0
  50. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/supplied_material.py +0 -0
  51. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli/trace_query.py +0 -0
  52. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/SOURCES.txt +0 -0
  53. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/dependency_links.txt +0 -0
  54. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/requires.txt +0 -0
  55. {aac_cli-0.2.11 → aac_cli-0.2.13}/aac_cli.egg-info/top_level.txt +0 -0
  56. {aac_cli-0.2.11 → aac_cli-0.2.13}/launchers/aac +0 -0
  57. {aac_cli-0.2.11 → aac_cli-0.2.13}/launchers/aeg +0 -0
  58. {aac_cli-0.2.11 → aac_cli-0.2.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aac-cli
3
- Version: 0.2.11
3
+ Version: 0.2.13
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
@@ -28,9 +28,9 @@ Dynamic: license-file
28
28
  # aac-cli — the `aac` command
29
29
 
30
30
  `aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
31
- AAC lets an organisation's AI agents and services prove, on every request,
32
- which tenant they belong to and what they were authorised to do. A
33
- **tenant** is one organisation registered with AAC. Each of its workloads
31
+ AAC lets an organization's AI agents and services prove, on every request,
32
+ which tenant they belong to and what they were authorized to do. A
33
+ **tenant** is one organization registered with AAC. Each of its workloads
34
34
  runs beside an AAC sidecar that signs outgoing requests and verifies
35
35
  incoming ones against public trust material the tenant publishes.
36
36
 
@@ -69,7 +69,8 @@ pip install aac-cli
69
69
  aac --version
70
70
  ```
71
71
 
72
- Python 3.11 to 3.14 is supported. Release checks test all four versions.
72
+ Supported platforms are macOS, Linux and Linux Docker containers. Python 3.11
73
+ to 3.14 is supported. Release checks test all four versions.
73
74
 
74
75
  > **Mind the name.** `pip install aac` installs an unrelated project that
75
76
  > happens to share the acronym. The AAC package is **`aac-cli`**; the
@@ -183,7 +184,7 @@ never asks you to pick between GitHub and Google again.
183
184
  configuration references it by path.
184
185
 
185
186
  **By default everything `aac init` generates is development material.** The
186
- development CA lives seven days and the leaf certificates one day. Nothing
187
+ development CA lives 365 days and the leaf certificates 90 days. Nothing
187
188
  there qualifies for production, whatever the tenant's domain. If your own
188
189
  issuer signs your agent's certificates instead, see "Two ways to get your
189
190
  agent's certificates" below; moving to production also means a tenant-admin
@@ -205,7 +206,7 @@ three commands look after it:
205
206
 
206
207
  ```bash
207
208
  aac agent status --agent orders # what exists, what is due, and the next step
208
- aac agent renew --agent orders # replace the short-lived certificates
209
+ aac agent renew --agent orders # replace the certificates
209
210
  aac agent list # every agent on this machine
210
211
  ```
211
212
 
@@ -429,11 +430,11 @@ tell you later and `aac agent renew` behaves accordingly.
429
430
 
430
431
  The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
431
432
 
432
- **Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
433
+ **Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone. A CA that expires before the certificates it would sign is refused.
433
434
 
434
435
  **Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
435
436
 
436
- **The CLI** creates a 7-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
437
+ **The CLI** creates a 365-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
437
438
 
438
439
  **The CLI does not** ask you for anything from your own certificate authority.
439
440
 
@@ -517,10 +518,10 @@ signs instead of this tool — see
517
518
  | 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
518
519
  | 3 | tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`; a lost public half is derived from the private key); production custody belongs in an HSM/KMS, not a password manager |
519
520
  | 4 | root_signing_key | `~/.aac/agents/<agent>/sidecar/root.pem`, `~/.aac/agents/<agent>/sidecar/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<agent>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per agent name) | No. never restore the private key: a replacement agent gets a new name and therefore a new key id, published alongside the old one until the old one is retired; its public copies are recreated from it |
520
- | 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
521
- | 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac agent renew` | No. reissue |
522
- | 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 1 day; `aac agent renew` | No. reissue |
523
- | 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 1 day; `aac agent renew` | No. reissue |
521
+ | 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 365 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
522
+ | 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 90 days; `aac agent renew` | No. reissue |
523
+ | 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 90 days; `aac agent renew` | No. reissue |
524
+ | 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 90 days; `aac agent renew` | No. reissue |
524
525
  | 9 | outbound_ca_bundle | `~/.aac/agents/<agent>/sidecar/outbound-ca.pem` | the sidecar verifies TLS to AAC and configured peers | rebuilt by renew; `aac agent renew` | No. derived |
525
526
  | 10 | pairing_secret | `~/.aac/agents/<agent>/agent/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
526
527
  | 11 | agent_record | `~/.aac/agents/<agent>/record.json` | the agent's identities and progress; every `aac agent` verb reads it first | for the life of the agent | **Recommended.** cannot be recreated by any command; without it start a new agent under a new name (see the new-computer setup procedure) |
@@ -536,15 +537,15 @@ the machine is healthy. Everything else is regenerated.
536
537
  ```bash
537
538
  aac agent status --agent orders --output table # what exists, expiry, permissions, next command
538
539
  aac agent status --agent orders --remote # also: is the public trust material visible?
539
- aac agent renew --agent orders # replace the short-lived certificates; old ones archived
540
+ aac agent renew --agent orders # replace the certificates; old ones archived
540
541
  aac agent list # every agent on this machine, with its case
541
542
  aac init --profile stage --agent orders --layout container # write the settings again, container paths
542
543
  ```
543
544
 
544
545
  `status` works offline by default and exits with code 3 when material is
545
- missing, expired or unsafely permissioned. `renew` reissues the one-day
546
+ missing, expired or unsafely permissioned. `renew` reissues the 90-day
546
547
  certificates with fresh keys, and reissues the development CA only when it
547
- is expired or within a day of expiry (or when `--ca` is given); a new CA is
548
+ is expired or within 90 days of expiry (or when `--ca` is given); a new CA is
548
549
  published under the next anchor id and needs the publisher restarted. For an
549
550
  agent whose own issuer signed its certificates, `renew` cannot reissue
550
551
  anything: it takes the replacements your issuer produced, through the same
@@ -577,20 +578,23 @@ id.
577
578
 
578
579
  It is the certificates that expire, and renewing replaces their keys with
579
580
  them. `aac agent status` shows when each certificate runs out and flags
580
- it before it does: six hours ahead for the one-day certificates, a day
581
- ahead for the development CA.
581
+ it before it does: seven days ahead for the 90-day certificates, 90 days
582
+ ahead for the development CA. That CA window covers a whole leaf lifetime, so
583
+ a renewal never issues a certificate that would outlive its CA. Certificates
584
+ your own issuer signed get the seven-day heads-up for the CA as well.
582
585
 
583
586
  | Material | Lasts | When it runs out | What to do |
584
587
  |---|---|---|---|
585
- | Workload, terminal-attestation and HTTPS certificates | 1 day | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
586
- | Development CA | 7 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within a day of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
588
+ | Workload, terminal-attestation and HTTPS certificates | 90 days | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
589
+ | Development CA | 365 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within 90 days of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
587
590
  | Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
588
591
  | Tenant API key, tenant-admin signing key, root signing key | do not expire | — | replaced only on purpose: see "Keys" and "Setting up aac-cli on a new computer" |
589
592
 
590
593
  A simple routine: when you start work, run
591
594
  `aac agent status --agent <name>`, and when it names renewal, run
592
- `aac agent renew --agent <name>` and restart the sidecar. About once
593
- a week that renewal also replaces the development CA (`renew` then reports
595
+ `aac agent renew --agent <name>` and restart the sidecar. About every nine
596
+ months, once the CA is within 90 days of expiry, that renewal also replaces
597
+ the development CA (`renew` then reports
594
598
  `republication_required: true`): restart the publisher, the sidecar and the
595
599
  agent, and anything else that trusted the old CA. Renewing is safe to
596
600
  repeat, but it is not a no-op: every run issues fresh keys and moves the old
@@ -756,7 +760,7 @@ stored value.
756
760
 
757
761
  `tenant_id` is managed for you. Profile commands display it but never ask
758
762
  for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
759
- It is your organisation's identifier in the federation, allocated by AAC at
763
+ It is your organization's identifier in the federation, allocated by AAC at
760
764
  registration in the form `tnt-<uuid>`, and it never changes.
761
765
  A binding makes that tenant the profile's stored default; it does not grant
762
766
  membership or configure an identity provider. Successful sign-in to an existing
@@ -953,12 +957,12 @@ code.
953
957
  Enterprise tenants are registered through an onboarding ceremony: AAC
954
958
  operations opens a registration window and hands the tenant operator a
955
959
  bootstrap token, which the commands below read from `--bootstrap-token` or
956
- the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorises only
960
+ the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorizes only
957
961
  the onboarding steps shown with it; every other administration command runs
958
962
  under a signed-in session.
959
963
 
960
964
  For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
961
- Azure Active Directory or Azure AD). If your organisation needs another
965
+ Azure Active Directory or Azure AD). If your organization needs another
962
966
  identity provider, contact support@cascadeauth.com.
963
967
 
964
968
  ### Register the tenant
@@ -1,9 +1,9 @@
1
1
  # aac-cli — the `aac` command
2
2
 
3
3
  `aac` is the command-line tool for tenants of Agent Authority Cloud (AAC).
4
- AAC lets an organisation's AI agents and services prove, on every request,
5
- which tenant they belong to and what they were authorised to do. A
6
- **tenant** is one organisation registered with AAC. Each of its workloads
4
+ AAC lets an organization's AI agents and services prove, on every request,
5
+ which tenant they belong to and what they were authorized to do. A
6
+ **tenant** is one organization registered with AAC. Each of its workloads
7
7
  runs beside an AAC sidecar that signs outgoing requests and verifies
8
8
  incoming ones against public trust material the tenant publishes.
9
9
 
@@ -42,7 +42,8 @@ pip install aac-cli
42
42
  aac --version
43
43
  ```
44
44
 
45
- Python 3.11 to 3.14 is supported. Release checks test all four versions.
45
+ Supported platforms are macOS, Linux and Linux Docker containers. Python 3.11
46
+ to 3.14 is supported. Release checks test all four versions.
46
47
 
47
48
  > **Mind the name.** `pip install aac` installs an unrelated project that
48
49
  > happens to share the acronym. The AAC package is **`aac-cli`**; the
@@ -156,7 +157,7 @@ never asks you to pick between GitHub and Google again.
156
157
  configuration references it by path.
157
158
 
158
159
  **By default everything `aac init` generates is development material.** The
159
- development CA lives seven days and the leaf certificates one day. Nothing
160
+ development CA lives 365 days and the leaf certificates 90 days. Nothing
160
161
  there qualifies for production, whatever the tenant's domain. If your own
161
162
  issuer signs your agent's certificates instead, see "Two ways to get your
162
163
  agent's certificates" below; moving to production also means a tenant-admin
@@ -178,7 +179,7 @@ three commands look after it:
178
179
 
179
180
  ```bash
180
181
  aac agent status --agent orders # what exists, what is due, and the next step
181
- aac agent renew --agent orders # replace the short-lived certificates
182
+ aac agent renew --agent orders # replace the certificates
182
183
  aac agent list # every agent on this machine
183
184
  ```
184
185
 
@@ -402,11 +403,11 @@ tell you later and `aac agent renew` behaves accordingly.
402
403
 
403
404
  The laptop case. The CLI creates a certificate authority on this machine and signs the agent's identity, receipt and HTTPS certificates with it.
404
405
 
405
- **Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone.
406
+ **Flags.** No flags are needed. To reuse a development CA across agents, pass `--ca-key-file` and `--ca-cert-file` together; neither one alone. A CA that expires before the certificates it would sign is refused.
406
407
 
407
408
  **Keys and signatures.** The CA and the two identity keys are Ed25519; the HTTPS key is EC P-256.
408
409
 
409
- **The CLI** creates a 7-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
410
+ **The CLI** creates a 365-day certificate authority and keeps its private key in the agent's keep/ folder; issues the workload, receipt and HTTPS certificates; publishes the CA certificate as a trust anchor named `<agent>-dev-ca`.
410
411
 
411
412
  **The CLI does not** ask you for anything from your own certificate authority.
412
413
 
@@ -490,10 +491,10 @@ signs instead of this tool — see
490
491
  | 2 | tenant_admin_session | `~/.aac/credentials/<tenant-id>.session` | CLI admin calls | hours; `aac sso login` | No. sign in again |
491
492
  | 3 | tenant_admin_signing_key | `~/.aac/tenants/<tenant-id>/tenant-admin.pem`, `~/.aac/tenants/<tenant-id>/tenant-admin.pub.pem` | the publisher signs trust-material uploads; the public half is registered with AAC | until rotated; `aac tenant rotate-admin-key` | **Recommended.** recommended for developer tenants (loss needs `aac tenant rotate-admin-key`; a lost public half is derived from the private key); production custody belongs in an HSM/KMS, not a password manager |
492
493
  | 4 | root_signing_key | `~/.aac/agents/<agent>/sidecar/root.pem`, `~/.aac/agents/<agent>/sidecar/root.pub.pem`, `~/.aac/tenants/<tenant-id>/root-keys/<agent>-root-v1.pub.pem` | the sidecar mints authority chains; the public half is published under the key id | until you retire its key id (root key ids are fixed per agent name) | No. never restore the private key: a replacement agent gets a new name and therefore a new key id, published alongside the old one until the old one is retired; its public copies are recreated from it |
493
- | 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 7 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
494
- | 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 1 day; `aac agent renew` | No. reissue |
495
- | 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 1 day; `aac agent renew` | No. reissue |
496
- | 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 1 day; `aac agent renew` | No. reissue |
494
+ | 5 | development_ca | `~/.aac/agents/<agent>/keep/ca.key`, `~/.aac/agents/<agent>/sidecar/ca.crt`, `~/.aac/agents/<agent>/agent/ca.crt`, `~/.aac/tenants/<tenant-id>/spiffe-bundle/<agent>-dev-ca.ca.pem` | signs the workload, terminal and TLS certificates; peers trust it via the published bundle | 365 days; `aac agent renew --ca` | No. development only; reissue; a lost copy of the certificate is copied back from `sidecar/ca.crt` |
495
+ | 6 | workload_svid | `~/.aac/agents/<agent>/sidecar/workload.key`, `~/.aac/agents/<agent>/sidecar/workload.crt` | the sidecar's SPIFFE identity: proves on every live request that this workload holds the key behind its certificate | 90 days; `aac agent renew` | No. reissue |
496
+ | 7 | terminal_attestation | `~/.aac/agents/<agent>/sidecar/terminal.key`, `~/.aac/agents/<agent>/sidecar/terminal.crt` | signs the receipt (terminal attestation) the sidecar issues when this workload completes a delegated request; auditors verify it offline against your published CA; setup gives it its own key so the two signing jobs stay separate, but a receipt is verified against your published CA and the expected agent identity, which the workload certificate also satisfies — treat both keys as one blast radius | 90 days; `aac agent renew` | No. reissue |
497
+ | 8 | https_tls | `~/.aac/agents/<agent>/sidecar/server.key`, `~/.aac/agents/<agent>/sidecar/server.crt` | the sidecar's HTTPS listener | 90 days; `aac agent renew` | No. reissue |
497
498
  | 9 | outbound_ca_bundle | `~/.aac/agents/<agent>/sidecar/outbound-ca.pem` | the sidecar verifies TLS to AAC and configured peers | rebuilt by renew; `aac agent renew` | No. derived |
498
499
  | 10 | pairing_secret | `~/.aac/agents/<agent>/agent/pairing.secret` | the sidecar and its agent authenticate each other (same bytes in both) | until regenerated | No. regenerable; both processes must read the same file |
499
500
  | 11 | agent_record | `~/.aac/agents/<agent>/record.json` | the agent's identities and progress; every `aac agent` verb reads it first | for the life of the agent | **Recommended.** cannot be recreated by any command; without it start a new agent under a new name (see the new-computer setup procedure) |
@@ -509,15 +510,15 @@ the machine is healthy. Everything else is regenerated.
509
510
  ```bash
510
511
  aac agent status --agent orders --output table # what exists, expiry, permissions, next command
511
512
  aac agent status --agent orders --remote # also: is the public trust material visible?
512
- aac agent renew --agent orders # replace the short-lived certificates; old ones archived
513
+ aac agent renew --agent orders # replace the certificates; old ones archived
513
514
  aac agent list # every agent on this machine, with its case
514
515
  aac init --profile stage --agent orders --layout container # write the settings again, container paths
515
516
  ```
516
517
 
517
518
  `status` works offline by default and exits with code 3 when material is
518
- missing, expired or unsafely permissioned. `renew` reissues the one-day
519
+ missing, expired or unsafely permissioned. `renew` reissues the 90-day
519
520
  certificates with fresh keys, and reissues the development CA only when it
520
- is expired or within a day of expiry (or when `--ca` is given); a new CA is
521
+ is expired or within 90 days of expiry (or when `--ca` is given); a new CA is
521
522
  published under the next anchor id and needs the publisher restarted. For an
522
523
  agent whose own issuer signed its certificates, `renew` cannot reissue
523
524
  anything: it takes the replacements your issuer produced, through the same
@@ -550,20 +551,23 @@ id.
550
551
 
551
552
  It is the certificates that expire, and renewing replaces their keys with
552
553
  them. `aac agent status` shows when each certificate runs out and flags
553
- it before it does: six hours ahead for the one-day certificates, a day
554
- ahead for the development CA.
554
+ it before it does: seven days ahead for the 90-day certificates, 90 days
555
+ ahead for the development CA. That CA window covers a whole leaf lifetime, so
556
+ a renewal never issues a certificate that would outlive its CA. Certificates
557
+ your own issuer signed get the seven-day heads-up for the CA as well.
555
558
 
556
559
  | Material | Lasts | When it runs out | What to do |
557
560
  |---|---|---|---|
558
- | Workload, terminal-attestation and HTTPS certificates | 1 day | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
559
- | Development CA | 7 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within a day of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
561
+ | Workload, terminal-attestation and HTTPS certificates | 90 days | the sidecar can no longer prove its identity, sign receipts or serve HTTPS | `aac agent renew --agent <name>`, then restart the sidecar so it loads them |
562
+ | Development CA | 365 days | certificates it signed stop being trusted, and no new ones can be issued | `renew` also replaces the CA when it is expired or within 90 days of expiry, or when `--ca` is given, and then reissues every certificate under it: restart the publisher so the new CA is published, restart the sidecar and anything else that trusted the old CA, and confirm with `aac agent status --agent <name> --remote` |
560
563
  | Tenant-admin session | hours | administration commands ask you to sign in again | `aac sso login` |
561
564
  | Tenant API key, tenant-admin signing key, root signing key | do not expire | — | replaced only on purpose: see "Keys" and "Setting up aac-cli on a new computer" |
562
565
 
563
566
  A simple routine: when you start work, run
564
567
  `aac agent status --agent <name>`, and when it names renewal, run
565
- `aac agent renew --agent <name>` and restart the sidecar. About once
566
- a week that renewal also replaces the development CA (`renew` then reports
568
+ `aac agent renew --agent <name>` and restart the sidecar. About every nine
569
+ months, once the CA is within 90 days of expiry, that renewal also replaces
570
+ the development CA (`renew` then reports
567
571
  `republication_required: true`): restart the publisher, the sidecar and the
568
572
  agent, and anything else that trusted the old CA. Renewing is safe to
569
573
  repeat, but it is not a no-op: every run issues fresh keys and moves the old
@@ -729,7 +733,7 @@ stored value.
729
733
 
730
734
  `tenant_id` is managed for you. Profile commands display it but never ask
731
735
  for it; only `aac tenant register`, `aac init` and `aac sso login` write it.
732
- It is your organisation's identifier in the federation, allocated by AAC at
736
+ It is your organization's identifier in the federation, allocated by AAC at
733
737
  registration in the form `tnt-<uuid>`, and it never changes.
734
738
  A binding makes that tenant the profile's stored default; it does not grant
735
739
  membership or configure an identity provider. Successful sign-in to an existing
@@ -926,12 +930,12 @@ code.
926
930
  Enterprise tenants are registered through an onboarding ceremony: AAC
927
931
  operations opens a registration window and hands the tenant operator a
928
932
  bootstrap token, which the commands below read from `--bootstrap-token` or
929
- the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorises only
933
+ the `AAC_BOOTSTRAP_TOKEN` environment variable. The token authorizes only
930
934
  the onboarding steps shown with it; every other administration command runs
931
935
  under a signed-in session.
932
936
 
933
937
  For enterprise sign-in, AAC supports Microsoft Entra ID (also known as
934
- Azure Active Directory or Azure AD). If your organisation needs another
938
+ Azure Active Directory or Azure AD). If your organization needs another
935
939
  identity provider, contact support@cascadeauth.com.
936
940
 
937
941
  ### Register the tenant
@@ -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.11"
13
+ __version__ = "0.2.13"
@@ -204,15 +204,18 @@ def _render(args, events, query: Query, sources: list[SourceInput]) -> int:
204
204
  if args.output.resolve() in {path.resolve() for path in protected}:
205
205
  raise InputError("--output must not overwrite an evidence input")
206
206
  visual = build_visual_aeg(graph)
207
- visual.provenance = summarize_sources(sources, selected, central_status=central_status)
207
+ visual.provenance = summarize_sources(
208
+ sources, selected, central_status=central_status, evidence_complete=graph.evidence_complete
209
+ )
208
210
  with tempfile.TemporaryDirectory(prefix="aac-aeg-") as directory:
209
211
  rendered = write_html_aeg(visual, root, out_dir=Path(directory))
210
212
  args.output.parent.mkdir(parents=True, exist_ok=True)
211
213
  args.output.write_text(rendered.read_text(encoding="utf-8"), encoding="utf-8")
212
214
  for diagnostic in diagnostics:
213
215
  print(diagnostic, file=sys.stderr)
216
+ coverage = "" if graph.evidence_complete else "; partial evidence"
214
217
  print(
215
- f"AEG written to {args.output} ({len(graph.tokens)} observed/referenced tokens; partial evidence)",
218
+ f"AEG written to {args.output} ({len(graph.tokens)} observed/referenced tokens{coverage})",
216
219
  file=sys.stderr,
217
220
  )
218
221
  return 4 if query_failed else 0
@@ -1,20 +1,20 @@
1
1
  {
2
- "content_sha256": "6e8bcd00bd94c415e04af65deb527be4d1581a6a5396d74c95708d6dda97fca4",
2
+ "content_sha256": "f45099fcfa1d1ceca5f31244dfa526ea096094ced6cfc7faf27cc1babe482163",
3
3
  "files": {
4
4
  "__init__.py": "80d80a1ba35353d271cddf02532801c60b57f5ec06fb268ae823747742ce8979",
5
- "cli.py": "98aa2b0c528fefb5f1f90a16584a6fbda1f075ebfcf61605f52948cdb940a1af",
5
+ "cli.py": "a180a171b4d1f819fd96d0821394eee19b45fafddddbd50c74778af6ac3d5fb0",
6
6
  "data/action-taken-v1.json": "d863343afe7f314646a73cf063480a7e650f93857d2ed5ec3a7b1f9838671fdf",
7
7
  "data/predicates-v1.json": "35e83a29feada10e8cfbef7009245146a9717d4f4fd58d74124e0ec91ae922c8",
8
- "from_sidecar.py": "6ac023f6925a13e94e0fee762656a02ca98981f2add194b052e9014ca48e2219",
9
- "inputs.py": "ebf1ca77957d29d59683348f6916bac3c21e2037f087f26ff7c7242d75edc77c",
10
- "model.py": "b631dcd7af587dcc89c18e1058bc81fb1043f04250a0d6ea585f9c595fd75cfc",
8
+ "from_sidecar.py": "8671c3e0efe9bfe9ecf9be057b70e245ca8d0d645b6bad8819f6fb1b882e255e",
9
+ "inputs.py": "047a6b7dd788e04c49e10f090de9336ed3870e754b547c469371369d27b55b8b",
10
+ "model.py": "715d0e92c3aafd9e0a194a74b4df7354bd24d1bf815767d590cf245da3d60018",
11
11
  "narrative.py": "8ac4b12add84cc8be907cff5f25e58731ab1144888aec8d376e286de461a6179",
12
- "provenance.py": "d310e13bfd45cb16edcc72e7b2bc28c916e3ff45208e2b44240e589f0ec0558d",
12
+ "provenance.py": "3f611a8c129a2856e2cf76561bde29dc1884719083dfdddea72810e026946b90",
13
13
  "schemas.py": "c47e0138fb8bfeaf1db1725a00d4a37ff4a136a9e8f5907356b1006f301a9ded",
14
14
  "temporal.py": "b69081e9280d578dc1261a276da3178e979a02efaf1137f720b4520ed543a36d",
15
15
  "theme.py": "ded47be13f5c591da95a1bb91ee1b665c1570c929d842bdaa1a41325d0b40caa",
16
16
  "timeline_source.py": "dc875458d69c6135b3ffe0db9c35e511da603b5d52409f780f3e1b74dc464520",
17
- "visualizer.py": "5eb7b863652c51a4a5751bcd8ae735b688e6e3e61089f0c158473ebb52ae6655"
17
+ "visualizer.py": "3213a1cbfe901f320c687b5eafcc09bed46bd2270c6cd427a7f74472c80a3bfe"
18
18
  },
19
19
  "legal": {
20
20
  "LICENSE": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594",
@@ -463,6 +463,9 @@ def _translate_mint(
463
463
  "tenant_id": sidecar_event.get("tenant_id"),
464
464
  "presenter_spiffe_id": sidecar_event.get("actor_spiffe_id"),
465
465
  "originator_human_id_hash": None,
466
+ # The sidecar records the sign-in issuer, never the person's identity;
467
+ # the originator node shows it when no identity hash is available.
468
+ "originator_issuer": sidecar_event.get("originator_issuer"),
466
469
  # T0 has no parent and no parent_token_ids_list (not a composite).
467
470
  }
468
471
  t0_event = UnifiedEvent(
@@ -624,6 +627,7 @@ def _translate_composite_mint(
624
627
  "parent_root_token_ids": sidecar_event.get("parent_root_token_ids") or [],
625
628
  "parent_tenant_ids": sidecar_event.get("parent_tenant_ids") or [],
626
629
  "originator_human_id_hash": sidecar_event.get("originator_human_id_hash"),
630
+ "originator_issuer": sidecar_event.get("originator_issuer"),
627
631
  "task_ref": sidecar_event.get("task_ref"),
628
632
  }
629
633
  t_comp_0_event = UnifiedEvent(
@@ -100,6 +100,7 @@ def _sidecar_types(row: dict, label: str) -> None:
100
100
  "agent_decision_action",
101
101
  "destination",
102
102
  "terminal_attestation_verification",
103
+ "originator_issuer",
103
104
  ):
104
105
  if row.get(key) is not None and not isinstance(row[key], str):
105
106
  raise InputError(f"{label}: {key} must be a string or null")
@@ -232,6 +233,7 @@ def read_trace(body, source: str) -> tuple[str, list[UnifiedEvent]]:
232
233
  "terminal_attestation_verification",
233
234
  "destination",
234
235
  "agent_decision_action",
236
+ "originator_issuer",
235
237
  ):
236
238
  if raw.get(key) is not None and not isinstance(raw[key], str):
237
239
  raise InputError(f"{label}: {key} must be a string or null")
@@ -215,6 +215,11 @@ class TerminalNode:
215
215
  action_payload: dict = field(default_factory=dict)
216
216
  action_taken_by_spiffe_id: str | None = None
217
217
  action_taken_timestamp: str | None = None
218
+ # True only when an action record's actor is the workload the audience
219
+ # names (exact match, or a match of the audience pattern). Any other
220
+ # token-correlated action stays an observation and never proves that the
221
+ # addressed recipient acted.
222
+ recipient_acted: bool = False
218
223
  # Canonical associations observed for this leaf and exact receiver workload.
219
224
  # A trust-domain match, another token, or a wildcard is not such evidence.
220
225
  observed_tenant_ids: tuple[str, ...] = ()
@@ -274,6 +279,21 @@ class TokenRelationGraph:
274
279
  # become unknown. Receiving/reporting observations cannot supply this map.
275
280
  creator_tenant_claims: dict[str, tuple[str, ...]] = field(default_factory=dict)
276
281
 
282
+ @property
283
+ def evidence_complete(self) -> bool:
284
+ """True when the supplied records leave no structural gap.
285
+
286
+ Every referenced token was observed (no placeholder stands in for a
287
+ token only named by another), and each recipient's own records show
288
+ its action. A complete set is still a set of supplied observations,
289
+ not independently verified business truth.
290
+ """
291
+ if not self.tokens or not self.terminals:
292
+ return False
293
+ if any(token.placeholder for token in self.tokens.values()):
294
+ return False
295
+ return all(terminal.recipient_acted for terminal in self.terminals)
296
+
277
297
 
278
298
  # ---------------------------------------------------------------------------
279
299
  # Extraction — events → TokenRelationGraph
@@ -729,14 +749,49 @@ def _extract_originators(
729
749
  if e.source.startswith(TENANT_SOURCE_PREFIX) and e.event_type == "human_authenticated"
730
750
  }
731
751
 
732
- out: list[OriginatorNode] = []
752
+ # Every observation of the same root (local sidecar record, central
753
+ # metadata, a second file) describes ONE originator. Merge them per root so
754
+ # the canvas label and the details are built from the same facts: a SIEM
755
+ # match on any observed hash wins, otherwise the first issuer any
756
+ # observation carries.
757
+ by_root: dict[str, list[UnifiedEvent]] = {}
733
758
  for mint in mint_root_events:
734
- h_hash = mint.raw_event.get("originator_human_id_hash") or ""
735
- root_tid = mint.root_token_id
736
- token = tokens.get(mint.token_id)
737
- tenant_id = token.tenant_id if token else (mint.raw_event.get("tenant_id") or "")
759
+ by_root.setdefault(mint.root_token_id, []).append(mint)
760
+
761
+ out: list[OriginatorNode] = []
762
+ for root_tid, observations in by_root.items():
763
+ h_hash = next(
764
+ (
765
+ str(o.raw_event.get("originator_human_id_hash"))
766
+ for o in observations
767
+ if o.raw_event.get("originator_human_id_hash")
768
+ ),
769
+ "",
770
+ )
771
+ issuer = next(
772
+ (
773
+ o.raw_event.get("originator_issuer")
774
+ for o in observations
775
+ if isinstance(o.raw_event.get("originator_issuer"), str)
776
+ and o.raw_event.get("originator_issuer")
777
+ ),
778
+ None,
779
+ )
780
+ token = next((tokens[o.token_id] for o in observations if o.token_id in tokens), None)
781
+ tenant_id = (
782
+ token.tenant_id
783
+ if token
784
+ else next(
785
+ (
786
+ str(o.raw_event.get("tenant_id"))
787
+ for o in observations
788
+ if o.raw_event.get("tenant_id")
789
+ ),
790
+ "",
791
+ )
792
+ )
738
793
  # Optional enrichment from tenant SIEM.
739
- siem = siem_human_events.get(h_hash)
794
+ siem = siem_human_events.get(h_hash) if h_hash else None
740
795
  if siem:
741
796
  raw = siem.raw_event
742
797
  out.append(
@@ -757,6 +812,9 @@ def _extract_originators(
757
812
  human_id_hash=h_hash,
758
813
  root_token_id=root_tid,
759
814
  tenant_id=tenant_id,
815
+ # The sidecar's mint record names the sign-in issuer even
816
+ # when no identity hash reaches these records.
817
+ human_iss=issuer,
760
818
  )
761
819
  )
762
820
  return out
@@ -812,6 +870,35 @@ def _extract_auth_summary(
812
870
  # ---------------------------------------------------------------------------
813
871
 
814
872
 
873
+ # The AAC audience grammar (AAC Design, "SPIFFE-ID pattern matching for
874
+ # audience"), as the verifier implements it: a concrete SPIFFE ID, or a trailing
875
+ # `/*` that stands for exactly one non-empty path segment. Trust domains match
876
+ # exactly; a SPIFFE ID itself never contains a wildcard.
877
+ _AUDIENCE_PATTERN = re.compile(r"^spiffe://[A-Za-z0-9.\-_]+(?:/[^/*]+)*(?:/\*)?$")
878
+ _CONCRETE_SPIFFE_ID = re.compile(r"^spiffe://[A-Za-z0-9.\-_]+(?:/[^/*]+)+$")
879
+
880
+
881
+ def _actor_matches_audience(actor: object, audience: str) -> bool:
882
+ """Whether an action's actor is the workload the audience constraint names.
883
+
884
+ Only a concrete SPIFFE ID can be the addressed recipient, and only under
885
+ the audience grammar above. Anything else (an opaque identity, a pattern
886
+ used as an actor, an unsupported audience shape) is kept as an observation
887
+ without claiming a match. A shared token or tenant never counts.
888
+ """
889
+ if not isinstance(actor, str) or not _CONCRETE_SPIFFE_ID.fullmatch(actor):
890
+ return False
891
+ if any(character.isspace() for character in actor) or not _AUDIENCE_PATTERN.fullmatch(audience):
892
+ return False
893
+ if audience.endswith("/*"):
894
+ prefix = audience[:-2]
895
+ if not actor.startswith(prefix + "/"):
896
+ return False
897
+ tail = actor[len(prefix) + 1 :]
898
+ return bool(tail) and "/" not in tail
899
+ return actor == audience
900
+
901
+
815
902
  def _extract_terminals(
816
903
  events: list[UnifiedEvent],
817
904
  tokens: dict[str, TokenNode],
@@ -882,6 +969,9 @@ def _extract_terminals(
882
969
  action_by_token.get(tid, []),
883
970
  key=lambda e: (e.timestamp_iso, json.dumps(e.raw_event, sort_keys=True)),
884
971
  )
972
+ recipient_acted = any(
973
+ _actor_matches_audience(e.raw_event.get("actor_spiffe_id"), audience) for e in actions
974
+ )
885
975
  if actions:
886
976
  action = actions[0]
887
977
  raw: dict = (
@@ -911,6 +1001,7 @@ def _extract_terminals(
911
1001
  # alias is needed.
912
1002
  action_taken_by_spiffe_id=raw.get("actor_spiffe_id"),
913
1003
  action_taken_timestamp=action.timestamp_iso,
1004
+ recipient_acted=recipient_acted,
914
1005
  )
915
1006
  )
916
1007
  else:
@@ -50,10 +50,16 @@ class SourceSummary:
50
50
  class Provenance:
51
51
  sources: tuple[SourceSummary, ...]
52
52
  central_status: str
53
+ # Structural completeness of the supplied records (TokenRelationGraph.evidence_complete).
54
+ evidence_complete: bool = False
53
55
 
54
56
 
55
57
  def summarize_sources(
56
- sources: list[SourceInput], selected: list[UnifiedEvent], *, central_status: str
58
+ sources: list[SourceInput],
59
+ selected: list[UnifiedEvent],
60
+ *,
61
+ central_status: str,
62
+ evidence_complete: bool = False,
57
63
  ) -> Provenance:
58
64
  # Selection retains the original event objects. Track membership by identity,
59
65
  # not by a filename, timestamp or caller-controlled event field that could
@@ -81,7 +87,7 @@ def summarize_sources(
81
87
  times[-1] if times else None,
82
88
  )
83
89
  )
84
- return Provenance(tuple(summaries), central_status)
90
+ return Provenance(tuple(summaries), central_status, evidence_complete)
85
91
 
86
92
 
87
93
  def source_panel(provenance: Provenance | None, diagnostics: list[str]) -> str:
@@ -143,11 +149,21 @@ def source_panel(provenance: Provenance | None, diagnostics: list[str]) -> str:
143
149
  "observations for hosted data, not unique business actions. Timestamp spans describe "
144
150
  "available records, not execution start/end or continuous coverage.</p>"
145
151
  )
152
+ if provenance and provenance.evidence_complete:
153
+ coverage = (
154
+ "<li>Every referenced token was observed and each recipient's own records show its "
155
+ "action. Records are supplied, correlated observations, not independently verified "
156
+ "business truth.</li>"
157
+ )
158
+ else:
159
+ coverage = (
160
+ "<li>Partial evidence: only supplied, correlated observations are shown. Missing "
161
+ "records do not establish success or failure.</li>"
162
+ )
146
163
  parts.append(
147
- "<h2>Limitations for these inputs</h2><ul><li>Partial evidence: only supplied, "
148
- "correlated observations are shown. Missing records do not establish success or failure.</li>"
149
- "<li>An intended recipient is named by an audience constraint; that constraint alone "
150
- "does not prove delivery or completion. Mint/transport success does not prove a business result.</li>"
164
+ "<h2>Limitations for these inputs</h2><ul>" + coverage + "<li>A recipient is named by an "
165
+ 'audience constraint ("addressed to"); that constraint alone does not prove delivery or '
166
+ "completion. Mint/transport success does not prove a business result.</li>"
151
167
  )
152
168
  if "Control-plane trace data" in used:
153
169
  parts.append(