agentmetry 0.4.0__tar.gz → 0.5.0__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 (232) hide show
  1. {agentmetry-0.4.0 → agentmetry-0.5.0}/.env.example +7 -0
  2. {agentmetry-0.4.0 → agentmetry-0.5.0}/PKG-INFO +2 -2
  3. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/api/main.py +5 -0
  4. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/api/routes/audit.py +5 -1
  5. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/cli/__init__.py +329 -1
  6. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/adapters/agt.py +4 -1
  7. agentmetry-0.5.0/agentmetry/core/audit/adapters/chronicle.py +260 -0
  8. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/adapters/cloudevents.py +2 -0
  9. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/adapters/ecs.py +71 -0
  10. agentmetry-0.5.0/agentmetry/core/audit/atlas.py +274 -0
  11. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/canonical.py +11 -1
  12. agentmetry-0.5.0/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +2 -0
  13. agentmetry-0.5.0/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +2 -0
  14. agentmetry-0.5.0/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +3 -0
  15. agentmetry-0.5.0/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +5 -0
  16. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/corpus.yaml +49 -0
  17. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/disposition.py +2 -1
  18. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/live.py +17 -2
  19. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/rules.py +20 -4
  20. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/traits.py +69 -1
  21. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/yaml_rules.py +14 -0
  22. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/dogfood.py +27 -2
  23. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/external.py +6 -0
  24. agentmetry-0.5.0/agentmetry/core/audit/heartbeat.py +313 -0
  25. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/hook_bootstrap.py +243 -27
  26. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/ingest.py +132 -0
  27. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/mitre.py +11 -6
  28. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/sinks.py +124 -1
  29. agentmetry-0.5.0/agentmetry/core/audit/trail_anchor.py +441 -0
  30. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/trail_chain.py +177 -11
  31. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/audit_exporter.py +5 -0
  32. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/config.py +30 -0
  33. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/diagnostics/doctor.py +153 -20
  34. agentmetry-0.5.0/agentmetry/core/diagnostics/hook_coverage.py +240 -0
  35. agentmetry-0.5.0/agentmetry/core/diagnostics/mcp_inventory.py +256 -0
  36. agentmetry-0.5.0/agentmetry/core/diagnostics/mcp_schema.py +268 -0
  37. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/version.py +1 -1
  38. agentmetry-0.5.0/agentmetry/hooks/__init__.py +1 -0
  39. agentmetry-0.5.0/agentmetry/hooks/ingest.py +1567 -0
  40. {agentmetry-0.4.0 → agentmetry-0.5.0}/pyproject.toml +14 -0
  41. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_agentmetry_audit.py +3 -1
  42. agentmetry-0.5.0/tests/test_atlas_detection_enrichment.py +332 -0
  43. agentmetry-0.5.0/tests/test_atlas_mapping.py +164 -0
  44. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_audit_sinks.py +41 -1
  45. agentmetry-0.5.0/tests/test_chronicle_adapter.py +241 -0
  46. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_cloudevents_adapter.py +2 -0
  47. agentmetry-0.5.0/tests/test_detection_duplicate_emission.py +184 -0
  48. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_evasion.py +91 -0
  49. agentmetry-0.5.0/tests/test_detection_quoted_command_words.py +156 -0
  50. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_doctor.py +135 -0
  51. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dogfood_freeze.py +48 -0
  52. agentmetry-0.5.0/tests/test_ecs_threat.py +132 -0
  53. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_external_ingest.py +2 -1
  54. agentmetry-0.5.0/tests/test_heartbeat.py +249 -0
  55. agentmetry-0.5.0/tests/test_hook_bootstrap.py +348 -0
  56. agentmetry-0.5.0/tests/test_hook_coverage.py +283 -0
  57. agentmetry-0.5.0/tests/test_mcp_audit_proxy.py +134 -0
  58. agentmetry-0.5.0/tests/test_mcp_inventory.py +205 -0
  59. agentmetry-0.5.0/tests/test_mcp_schema.py +233 -0
  60. agentmetry-0.5.0/tests/test_trail_anchor.py +268 -0
  61. agentmetry-0.5.0/tests/test_trail_concurrency.py +182 -0
  62. {agentmetry-0.4.0 → agentmetry-0.5.0}/tools/mcp_audit_proxy.py +53 -6
  63. agentmetry-0.4.0/tests/test_hook_bootstrap.py +0 -144
  64. agentmetry-0.4.0/tests/test_mcp_audit_proxy.py +0 -57
  65. {agentmetry-0.4.0 → agentmetry-0.5.0}/.dockerignore +0 -0
  66. {agentmetry-0.4.0 → agentmetry-0.5.0}/.env.agentmetry-demo +0 -0
  67. {agentmetry-0.4.0 → agentmetry-0.5.0}/.gitignore +0 -0
  68. {agentmetry-0.4.0 → agentmetry-0.5.0}/Dockerfile +0 -0
  69. {agentmetry-0.4.0 → agentmetry-0.5.0}/README.md +0 -0
  70. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/__init__.py +0 -0
  71. {agentmetry-0.4.0/agentmetry/core/diagnostics → agentmetry-0.5.0/agentmetry/api}/__init__.py +0 -0
  72. {agentmetry-0.4.0/agentmetry/core/audit/adapters → agentmetry-0.5.0/agentmetry/api/routes}/__init__.py +0 -0
  73. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/api/websocket.py +0 -0
  74. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/api/ws_bridge.py +0 -0
  75. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/cli/__main__.py +0 -0
  76. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/__init__.py +0 -0
  77. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/__init__.py +0 -0
  78. {agentmetry-0.4.0/agentmetry/api/routes → agentmetry-0.5.0/agentmetry/core/audit/adapters}/__init__.py +0 -0
  79. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/adapters/splunk.py +0 -0
  80. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/alerts.py +0 -0
  81. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/compliance_digest.py +0 -0
  82. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/__init__.py +0 -0
  83. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/benchmark.py +0 -0
  84. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  85. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  86. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  87. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  88. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  89. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  90. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  91. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  92. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  93. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  94. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  95. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  96. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  97. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  98. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  99. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  100. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  101. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  102. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  103. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  104. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  105. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  106. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  107. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  108. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  109. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  110. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  111. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  112. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  113. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  114. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  115. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  116. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  117. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  118. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  119. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  120. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  121. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  122. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  123. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  124. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  125. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  126. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  127. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  128. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  129. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  130. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/engine.py +0 -0
  131. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/live_store.py +0 -0
  132. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/models.py +0 -0
  133. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  134. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/dlp/__init__.py +0 -0
  135. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/dlp/loader.py +0 -0
  136. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/dlp/models.py +0 -0
  137. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/dlp/scanner.py +0 -0
  138. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/evidence_pack.py +0 -0
  139. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/hashing.py +0 -0
  140. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/identity.py +0 -0
  141. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/migrate.py +0 -0
  142. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/policy.py +0 -0
  143. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/redaction.py +0 -0
  144. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/replay.py +0 -0
  145. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/run_context.py +0 -0
  146. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/spool.py +0 -0
  147. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  148. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
  149. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  150. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/tool_policy/models.py +0 -0
  151. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/trail_db.py +0 -0
  152. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/audit/trail_merkle.py +0 -0
  153. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/auth.py +0 -0
  154. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/__init__.py +0 -0
  155. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/bridges.py +0 -0
  156. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/bus.py +0 -0
  157. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/events.py +0 -0
  158. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/bus/outbox.py +0 -0
  159. {agentmetry-0.4.0/agentmetry/api → agentmetry-0.5.0/agentmetry/core/diagnostics}/__init__.py +0 -0
  160. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/diagnostics/autostart.py +0 -0
  161. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  162. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/diagnostics/env_file.py +0 -0
  163. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/drivers/__init__.py +0 -0
  164. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/drivers/host.py +0 -0
  165. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/drivers/permissions.py +0 -0
  166. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/drivers/spec.py +0 -0
  167. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/extensions.py +0 -0
  168. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/core/health.py +0 -0
  169. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/policies/detection/manifest.yaml +0 -0
  170. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/policies/dlp/manifest.yaml +0 -0
  171. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/policies/opa/agent_rules.rego +0 -0
  172. {agentmetry-0.4.0 → agentmetry-0.5.0}/agentmetry/policies/tool/manifest.yaml +0 -0
  173. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/conftest.py +0 -0
  174. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  175. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  176. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/fixtures/fake_mcp_server.py +0 -0
  177. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_agentmetry_ingest_client.py +0 -0
  178. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_agt_adapter.py +0 -0
  179. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_audit_stats.py +0 -0
  180. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_audit_tail.py +0 -0
  181. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_auth.py +0 -0
  182. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_autostart.py +0 -0
  183. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_boot_sequence.py +0 -0
  184. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_burst_windows.py +0 -0
  185. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_chinese_agent_hooks.py +0 -0
  186. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_chinese_agent_sprint_b.py +0 -0
  187. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_chinese_agent_sprint_c.py +0 -0
  188. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_cli_backup.py +0 -0
  189. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_compliance_digest.py +0 -0
  190. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_crewai_adapter.py +0 -0
  191. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_adi.py +0 -0
  192. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_benchmark.py +0 -0
  193. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_default_config.py +0 -0
  194. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_disposition.py +0 -0
  195. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_disposition_api.py +0 -0
  196. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_emit_durability.py +0 -0
  197. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_engine.py +0 -0
  198. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_hf_incident.py +0 -0
  199. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_rule_identity.py +0 -0
  200. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_detection_rules_v2.py +0 -0
  201. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dlp_agent_env_override.py +0 -0
  202. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dlp_invisible_unicode.py +0 -0
  203. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dlp_markdown_exfil.py +0 -0
  204. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dlp_scanner.py +0 -0
  205. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_dogfood.py +0 -0
  206. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_driver_paths.py +0 -0
  207. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_event_bus.py +0 -0
  208. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_evidence_pack.py +0 -0
  209. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_extensions.py +0 -0
  210. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_forwarding_shape.py +0 -0
  211. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_hook_enforcement_timing.py +0 -0
  212. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_hook_spool.py +0 -0
  213. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_isolation.py +0 -0
  214. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_launch_targets.py +0 -0
  215. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_live_detection.py +0 -0
  216. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_live_detection_store.py +0 -0
  217. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_mitre_dlp.py +0 -0
  218. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_opensre_adapter.py +0 -0
  219. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_readme_claims.py +0 -0
  220. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_redaction.py +0 -0
  221. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_replay.py +0 -0
  222. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_static_serving.py +0 -0
  223. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_subagent_lifecycle.py +0 -0
  224. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_tool_policy.py +0 -0
  225. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_tool_policy_agent_cli.py +0 -0
  226. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_tool_policy_git_hooks.py +0 -0
  227. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_trail_chain.py +0 -0
  228. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_trail_merkle.py +0 -0
  229. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_unattended_agent_policy.py +0 -0
  230. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_version.py +0 -0
  231. {agentmetry-0.4.0 → agentmetry-0.5.0}/tests/test_yaml_detection_rules.py +0 -0
  232. {agentmetry-0.4.0 → agentmetry-0.5.0}/tools/vault_fs_server.py +0 -0
@@ -17,4 +17,11 @@ AGENTMETRY_DLP_MODE=log
17
17
  AGENTMETRY_AUDIT_EXPORT_ENABLED=1
18
18
  AGENTMETRY_AUDIT_SINK=file
19
19
 
20
+ # Anchor log, when it is not the file sitting beside the trail. Point this at a
21
+ # working copy of a remote this machine cannot rewrite and `verify --trail` and
22
+ # `doctor` will check against it without being handed --anchors. Unset is fine:
23
+ # the chain still verifies, it just cannot survive an attacker who owns this
24
+ # host. See docs/anchoring.md.
25
+ # AGENTMETRY_ANCHOR_LOG=../agentmetry-anchors/home-lab/agentmetry-trail.anchors.jsonl
26
+
20
27
  # More tuning: see repo root .env.example and docs/agentmetry-dogfood-checklist.md
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
5
5
  Project-URL: Homepage, https://agentmetry.ai
6
6
  Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
@@ -78,6 +78,7 @@ async def lifespan(app: FastAPI):
78
78
  # time — so every hook that fires during the drain is refused and spooled,
79
79
  # which makes the next drain larger. The recorder has to be reachable first
80
80
  # and catch up second.
81
+ from agentmetry.core.audit.heartbeat import heartbeat_forever
81
82
  from agentmetry.core.audit.spool import drain_forever, drain_spool
82
83
 
83
84
  async def _boot_drain() -> None:
@@ -108,6 +109,10 @@ async def lifespan(app: FastAPI):
108
109
  # backlog growing unnoticed whenever ingest is unreachable while the
109
110
  # process itself stays up.
110
111
  asyncio.create_task(drain_forever(), name="spool-drain"),
112
+ # Attest on an interval so a machine that goes quiet is distinguishable
113
+ # from a machine whose hooks were removed. Absence only means something
114
+ # if presence was being asserted.
115
+ asyncio.create_task(heartbeat_forever(), name="heartbeat"),
111
116
  ]
112
117
 
113
118
  # Drivers mount in the background: a slow npx download must not delay boot.
@@ -51,7 +51,7 @@ class ExternalIngestBody(BaseModel):
51
51
  )
52
52
  event_type: str = Field(
53
53
  ...,
54
- description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response",
54
+ description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response | mcp_schema",
55
55
  )
56
56
  correlation_id: str = ""
57
57
  session_id: str = ""
@@ -71,6 +71,10 @@ class ExternalIngestBody(BaseModel):
71
71
  # pydantic drops it and a `log`-mode match is silently lost.
72
72
  dlp: dict[str, Any] | None = None
73
73
  tool_policy: dict[str, Any] | None = None
74
+ # Compact `tools/list` fingerprint from mcp_audit_proxy. Hash only: the
75
+ # description never leaves the proxy process.
76
+ schema_fingerprint: str = ""
77
+ schema_tool_count: int = 0
74
78
 
75
79
 
76
80
  def _parse_event_ts(event: dict[str, Any]) -> datetime | None:
@@ -395,6 +395,130 @@ def cmd_install(args: argparse.Namespace) -> int:
395
395
  return 0
396
396
 
397
397
 
398
+ #: Agents this CLI can install for. Antigravity is absent on purpose: its hooks
399
+ #: live in ~/.gemini and are written by scripts/install_antigravity_hooks.ps1,
400
+ #: which has no Python equivalent. Listing it here and quietly doing nothing
401
+ #: would be the reporting failure this codebase keeps finding in itself.
402
+ _HOOK_INSTALLERS = ("cursor", "claude", "codex", "qwen", "kimi", "qoder", "codebuddy")
403
+
404
+
405
+ def _hooks_status() -> int:
406
+ """Coverage as an exit code, for a deployment tool rather than a person.
407
+
408
+ Intune Remediations, and every scheduled-check mechanism like it, want a
409
+ detection script whose exit code is the answer. Parsing `doctor` output
410
+ would work until somebody reworded a line, and a coverage check that breaks
411
+ silently on a wording change is worse than no check.
412
+
413
+ The contract:
414
+ 0 every agent present here is recorded, or none is installed
415
+ 1 at least one agent is present and not recorded, and installing helps
416
+ 2 coverage cannot be determined, and installing would not help
417
+
418
+ 2 is separate from 1 on purpose. A service profile sees no developer's
419
+ configuration at all, so remediation there would run forever and fix
420
+ nothing. Reporting it as compliant would be the lie this whole subsystem
421
+ exists to prevent, so it is neither.
422
+ """
423
+ from agentmetry.core.diagnostics import hook_coverage
424
+
425
+ if hook_coverage.is_service_profile():
426
+ print(
427
+ "UNDETERMINABLE: running under a service profile. Hook configuration "
428
+ "lives in each developer's profile and none is visible from here. "
429
+ "Run this in the user's context.",
430
+ file=sys.stderr,
431
+ )
432
+ return 2
433
+
434
+ states = hook_coverage.coverage()
435
+ for line in hook_coverage.summary_lines(states):
436
+ print(line.strip())
437
+
438
+ uncovered = hook_coverage.uncovered(states)
439
+ if uncovered:
440
+ print(f"NEEDS REMEDIATION: {', '.join(uncovered)}")
441
+ return 1
442
+ print("COMPLIANT")
443
+ return 0
444
+
445
+
446
+ def cmd_hooks(args: argparse.Namespace) -> int:
447
+ """Install hook configs, defaulting to the agents actually on this machine.
448
+
449
+ The default matters more than it looks. This is what a per-user deployment
450
+ step runs at first logon, where nobody knows in advance which IDEs that
451
+ developer uses. Installing for all seven would write configs for agents that
452
+ are not there; installing for two would miss the ones they do use.
453
+ """
454
+ from agentmetry.core.audit import hook_bootstrap
455
+ from agentmetry.core.diagnostics import hook_coverage
456
+
457
+ if args.action == "status":
458
+ return _hooks_status()
459
+
460
+ if hook_bootstrap.hook_target() == "none":
461
+ print(
462
+ "No way to reach ingest from here: no checkout, no frozen binary, no "
463
+ "agentmetry-hook script. Installing hook configs now would write "
464
+ "commands that cannot run.",
465
+ file=sys.stderr,
466
+ )
467
+ return 1
468
+
469
+ if args.agent:
470
+ wanted = [a for a in args.agent if a in _HOOK_INSTALLERS]
471
+ unknown = sorted(set(args.agent) - set(wanted))
472
+ if unknown:
473
+ print(f"No installer for: {', '.join(unknown)}", file=sys.stderr)
474
+ return 2
475
+ elif args.all:
476
+ wanted = list(_HOOK_INSTALLERS)
477
+ else:
478
+ states = hook_coverage.coverage()
479
+ wanted = [a for a in _HOOK_INSTALLERS if states.get(a) != hook_coverage.ABSENT]
480
+ if not wanted:
481
+ print("No supported agent found on this machine; nothing to install.")
482
+ return 0
483
+
484
+ failures = 0
485
+ for agent in wanted:
486
+ install = getattr(hook_bootstrap, f"install_{agent}_global_hooks", None)
487
+ if install is None:
488
+ print(f" {agent}: no installer", file=sys.stderr)
489
+ failures += 1
490
+ continue
491
+ try:
492
+ path = install()
493
+ except OSError as exc:
494
+ print(f" {agent}: FAILED ({exc})", file=sys.stderr)
495
+ failures += 1
496
+ continue
497
+ if path is None:
498
+ print(f" {agent}: skipped", file=sys.stderr)
499
+ failures += 1
500
+ else:
501
+ print(f" {agent}: {path}")
502
+
503
+ if "codex" in wanted and not failures:
504
+ print("")
505
+ print("codex: open Codex, run /hooks and approve the entries, or it")
506
+ print("skips them silently and records nothing.")
507
+ return 1 if failures else 0
508
+
509
+
510
+ def cmd_hook(args: argparse.Namespace) -> int:
511
+ """Forward one hook event. Present here for discoverability and docs.
512
+
513
+ Hook configs should name the `agentmetry-hook` console script instead. This
514
+ path imports the whole CLI module, including httpx, on a code path that runs
515
+ once per tool call, and a hook has no reason to pay for that.
516
+ """
517
+ from agentmetry.hooks.ingest import main as ingest_main
518
+
519
+ return ingest_main([args.app, "hook", args.event])
520
+
521
+
398
522
  def cmd_uninstall(args: argparse.Namespace) -> int:
399
523
  from agentmetry.core.diagnostics import autostart
400
524
 
@@ -655,6 +779,110 @@ def cmd_prove(args: argparse.Namespace) -> int:
655
779
  return 0
656
780
 
657
781
 
782
+ def cmd_mcp(args: argparse.Namespace) -> int:
783
+ """List the MCP servers the agents on this machine are wired to.
784
+
785
+ The trail answers what an agent did. This answers what it was allowed to
786
+ reach, which is the question asked first in every security review and the one
787
+ a behavioural record cannot reconstruct after the fact.
788
+ """
789
+ from pathlib import Path as _Path
790
+
791
+ from agentmetry.core.diagnostics.mcp_inventory import collect, summary_lines
792
+
793
+ inv = collect(_Path(args.project).resolve() if args.project else _Path.cwd())
794
+ for line in summary_lines(inv):
795
+ print(line)
796
+ from agentmetry.core.diagnostics.mcp_schema import schema_summary_lines
797
+
798
+ for line in schema_summary_lines():
799
+ print(line)
800
+
801
+ if args.digest:
802
+ # Safe to publish beside a trail anchor: it commits to the configured
803
+ # surface without naming a single server.
804
+ print(inv.digest())
805
+ # Findings describe a risk posture, not a broken install, so they are
806
+ # reported rather than turned into a non-zero exit that would break a
807
+ # health check somebody wired into MDM.
808
+ return 0
809
+
810
+
811
+ def cmd_anchor(args: argparse.Namespace) -> int:
812
+ """Publish, list, or check the checkpoints that commit the trail externally.
813
+
814
+ The chain and the Merkle root are both computed on the audited machine from
815
+ the audited file, so an attacker who can rewrite one can rewrite all of them.
816
+ A checkpoint is the escape: publish `(tree_size, root)` off-host and any later
817
+ edit below that size stops matching. This command produces the commitment;
818
+ where it goes is the operator's trust decision, not ours.
819
+ """
820
+ import json
821
+
822
+ from agentmetry.core.audit.trail_anchor import (
823
+ FileAnchorSink,
824
+ anchor_path,
825
+ build_checkpoint,
826
+ coverage_lines,
827
+ read_checkpoints,
828
+ verify_anchors,
829
+ )
830
+
831
+ path = Path(args.path)
832
+ if not path.is_file():
833
+ print(f"No such file: {path}")
834
+ return 1
835
+ anchors = Path(args.anchors) if args.anchors else anchor_path(path)
836
+
837
+ if args.show:
838
+ checkpoints = read_checkpoints(anchors)
839
+ if not checkpoints:
840
+ print(f"No checkpoints in {anchors}")
841
+ return 0
842
+ print(f"{len(checkpoints)} checkpoint(s) in {anchors}")
843
+ for cp in checkpoints:
844
+ print(f" {cp.timestamp} size {cp.tree_size:>7} root {cp.root_sha256[:16]}…")
845
+ return 0
846
+
847
+ if args.verify:
848
+ coverage = verify_anchors(path, anchors)
849
+ for line in coverage_lines(coverage):
850
+ print(line.strip())
851
+ return 0 if coverage.ok else 1
852
+
853
+ try:
854
+ checkpoint = build_checkpoint(path, host_id=args.host_id or "")
855
+ except ValueError as exc:
856
+ print(str(exc))
857
+ return 1
858
+
859
+ # --print emits without recording. The point is to hand the statement to a
860
+ # sink we do not implement (a timestamp authority, a git commit, a log)
861
+ # without also implying it was anchored here.
862
+ if args.print_only:
863
+ print(checkpoint.statement() if not args.json else json.dumps(checkpoint.to_dict(), indent=2))
864
+ return 0
865
+
866
+ try:
867
+ receipt = FileAnchorSink(anchors).publish(checkpoint)
868
+ except (ValueError, OSError) as exc:
869
+ print(f"FAILED — {exc}")
870
+ return 1
871
+
872
+ print(f"Anchored {checkpoint.tree_size} record(s) at root {checkpoint.root_sha256}")
873
+ print(f" checkpoint: {receipt.ref} in {anchors}")
874
+ print(f" statement: {checkpoint.statement()}")
875
+ # Phrased as a condition, not an assertion. With --anchors the log may
876
+ # already be in a working copy of a protected remote, and telling an
877
+ # operator who has done the work that they have not is how a correct
878
+ # warning teaches people to ignore warnings.
879
+ print(
880
+ " Until a copy of this log lives where this machine cannot write it, "
881
+ "the commitment can be rewritten along with the trail."
882
+ )
883
+ return 0
884
+
885
+
658
886
  def cmd_verify(args: argparse.Namespace) -> int:
659
887
  import json
660
888
 
@@ -689,6 +917,36 @@ def cmd_verify(args: argparse.Namespace) -> int:
689
917
  # the file, so it is the more useful of the two to publish.
690
918
  print(f" merkle root: {root}")
691
919
  print(f" tree size: {size} (rfc6962-sha256)")
920
+
921
+ # Everything above was computed from the file being checked, which is
922
+ # the file an attacker with host access would have rewritten. Anchor
923
+ # coverage is the only part of this output that can contradict it, so
924
+ # it prints even when there are no anchors — an operator who never
925
+ # sees the sentence never learns the guarantee has a ceiling.
926
+ from agentmetry.core.audit.trail_anchor import coverage_lines, verify_anchors
927
+
928
+ # Which log this checks is what decides whether the check means
929
+ # anything. Beside the trail, an attacker who rewrote one rewrote
930
+ # the other. On a protected remote, the same comparison is the only
931
+ # thing here they could not forge.
932
+ from agentmetry.core.audit.trail_anchor import resolve_anchor_log
933
+
934
+ anchor_log, source = resolve_anchor_log(path, getattr(args, "anchors", None))
935
+ coverage = verify_anchors(path, anchor_log, local_only=(source == "default"))
936
+ for line in coverage_lines(coverage):
937
+ print(line)
938
+ if source == "config" and not anchor_log.is_file():
939
+ print(f" AGENTMETRY_ANCHOR_LOG points at {anchor_log}, which does not exist")
940
+ if not coverage.ok:
941
+ # The chain printed OK several lines ago and it was telling the
942
+ # truth: the file is internally consistent, because whoever
943
+ # rewrote it made it so. Leaving that as the last word would let
944
+ # a skimmer take the wrong verdict away, so restate it.
945
+ print(
946
+ "FAILED — the chain is internally consistent but contradicts a "
947
+ "published anchor. The file was rewritten."
948
+ )
949
+ return 1
692
950
  return 0
693
951
  print(f"FAILED — {result.message}")
694
952
  if result.first_bad_line:
@@ -817,6 +1075,37 @@ def main(argv: list[str] | None = None) -> int:
817
1075
  )
818
1076
  serve.add_argument("--host", default="127.0.0.1")
819
1077
  serve.add_argument("--port", type=int, default=8000)
1078
+ hooks = sub.add_parser(
1079
+ "hooks",
1080
+ help="install IDE hook configs for the agents on this machine",
1081
+ )
1082
+ hooks.add_argument(
1083
+ "action",
1084
+ choices=("install", "status"),
1085
+ help=(
1086
+ "install writes hook configs; status reports coverage and exits "
1087
+ "0 compliant, 1 needs remediation, 2 undeterminable"
1088
+ ),
1089
+ )
1090
+ hooks.add_argument(
1091
+ "--agent",
1092
+ action="append",
1093
+ default=[],
1094
+ help="install only this agent (repeatable). Default: every supported agent present here",
1095
+ )
1096
+ hooks.add_argument(
1097
+ "--all",
1098
+ action="store_true",
1099
+ help="install for every supported agent, present on this machine or not",
1100
+ )
1101
+
1102
+ hook = sub.add_parser(
1103
+ "hook",
1104
+ help="forward one IDE hook event to ingest (see also the agentmetry-hook script)",
1105
+ )
1106
+ hook.add_argument("app", help="cursor | claude | codex | antigravity | qwen | kimi | qoder | codebuddy")
1107
+ hook.add_argument("event", help="the IDE's event name, e.g. PreToolUse")
1108
+
820
1109
  sub.add_parser("status", help="orchestrator health and audit export status")
821
1110
  stats = sub.add_parser("stats", help="audit trail metrics for dogfood (events, detections)")
822
1111
  stats.add_argument("--days", type=int, default=7)
@@ -879,6 +1168,34 @@ def main(argv: list[str] | None = None) -> int:
879
1168
  "--record-root", dest="record_root", action="store_true",
880
1169
  help="recompute the root and store it in the chain sidecar",
881
1170
  )
1171
+ anchor = sub.add_parser(
1172
+ "anchor",
1173
+ help="publish a checkpoint committing the trail somewhere the host cannot rewrite",
1174
+ )
1175
+ anchor.add_argument("path", help="JSONL trail file")
1176
+ anchor.add_argument(
1177
+ "--anchors", default=None,
1178
+ help="anchor log to append to (default: <trail>.anchors.jsonl)",
1179
+ )
1180
+ anchor.add_argument(
1181
+ "--print", dest="print_only", action="store_true",
1182
+ help="print the checkpoint statement without recording it, to hand to an external sink",
1183
+ )
1184
+ anchor.add_argument("--json", action="store_true", help="with --print: emit JSON, not one line")
1185
+ anchor.add_argument("--show", action="store_true", help="list recorded checkpoints")
1186
+ anchor.add_argument(
1187
+ "--verify", action="store_true",
1188
+ help="recompute each checkpoint's root from the trail and compare",
1189
+ )
1190
+ anchor.add_argument("--host-id", dest="host_id", default="", help="identity to stamp on the checkpoint")
1191
+ mcp = sub.add_parser(
1192
+ "mcp", help="list MCP servers configured for the agents on this machine"
1193
+ )
1194
+ mcp.add_argument("--project", default=None, help="project root to include (default: cwd)")
1195
+ mcp.add_argument(
1196
+ "--digest", action="store_true",
1197
+ help="also print the config digest, which names no server and is safe to publish",
1198
+ )
882
1199
  verify = sub.add_parser("verify", help="verify evidence pack or JSONL trail chain")
883
1200
  verify.add_argument(
884
1201
  "path",
@@ -887,7 +1204,14 @@ def main(argv: list[str] | None = None) -> int:
887
1204
  verify.add_argument(
888
1205
  "--trail",
889
1206
  action="store_true",
890
- help="verify tamper-evident hash chain on an audit JSONL file",
1207
+ help="verify the hash chain on an audit JSONL file and report anchor coverage",
1208
+ )
1209
+ verify.add_argument(
1210
+ "--anchors",
1211
+ default=None,
1212
+ help="with --trail: check against this anchor log instead of the one beside "
1213
+ "the trail. Point it at an off-host copy; a local one can be rewritten "
1214
+ "by whoever rewrote the trail.",
891
1215
  )
892
1216
  doctor = sub.add_parser(
893
1217
  "doctor", help="SIEM preflight (manifests, trail chain, health, hooks)"
@@ -922,6 +1246,8 @@ def main(argv: list[str] | None = None) -> int:
922
1246
  "start": cmd_start,
923
1247
  "serve": cmd_serve,
924
1248
  "stop": cmd_stop,
1249
+ "hooks": cmd_hooks,
1250
+ "hook": cmd_hook,
925
1251
  "status": cmd_status,
926
1252
  "stats": cmd_stats,
927
1253
  "logs": cmd_logs,
@@ -932,6 +1258,8 @@ def main(argv: list[str] | None = None) -> int:
932
1258
  "export": cmd_export,
933
1259
  "verify": cmd_verify,
934
1260
  "prove": cmd_prove,
1261
+ "anchor": cmd_anchor,
1262
+ "mcp": cmd_mcp,
935
1263
  "import-agt": cmd_import_agt,
936
1264
  "doctor": cmd_doctor,
937
1265
  "benchmark": cmd_benchmark,
@@ -220,6 +220,8 @@ def agt_to_canonical(
220
220
  adds up to.
221
221
  """
222
222
  from agentmetry.core.audit.detection.traits import classify_command
223
+ from agentmetry.core.audit.atlas import attach_atlas
224
+ from agentmetry.core.audit.canonical import SCHEMA_VERSION
223
225
  from agentmetry.core.audit.mitre import get_mitre_mapping
224
226
 
225
227
  data = entry.get("data") if isinstance(entry.get("data"), dict) else {}
@@ -248,11 +250,12 @@ def agt_to_canonical(
248
250
  mapping = get_mitre_mapping(resource or "run", data or command)
249
251
  if mapping:
250
252
  tool["mitre"] = mapping
253
+ attach_atlas(tool)
251
254
 
252
255
  agent_did = str(entry.get("agent_did") or "")
253
256
 
254
257
  return {
255
- "schema_version": "1.1.0",
258
+ "schema_version": SCHEMA_VERSION,
256
259
  # AGT's entry_id is `audit_<16 hex>`, not a UUID, so it cannot be used
257
260
  # as an event_id directly. Derived deterministically so re-importing the
258
261
  # same file does not mint new identities for the same events.