agentmetry 0.9.6__tar.gz → 0.9.7__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 (295) hide show
  1. {agentmetry-0.9.6 → agentmetry-0.9.7}/PKG-INFO +1 -1
  2. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/audit.py +15 -2
  3. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/cli/__init__.py +88 -0
  4. agentmetry-0.9.7/agentmetry/core/audit/cloud_bridge.py +317 -0
  5. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/evidence_pack.py +65 -8
  6. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/external.py +4 -2
  7. agentmetry-0.9.7/agentmetry/core/diagnostics/machine_view.py +101 -0
  8. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/version.py +1 -1
  9. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/demo.py +1 -1
  10. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/ingest.py +10 -1
  11. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agentmetry_ingest_client.py +43 -0
  12. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_commands.py +7 -9
  13. agentmetry-0.9.7/tests/test_cloud_bridge.py +149 -0
  14. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_copilot_hooks.py +8 -2
  15. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_evidence_pack.py +77 -0
  16. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_external_ingest.py +15 -0
  17. agentmetry-0.9.7/tests/test_machine_view.py +111 -0
  18. {agentmetry-0.9.6 → agentmetry-0.9.7}/.dockerignore +0 -0
  19. {agentmetry-0.9.6 → agentmetry-0.9.7}/.env.agentmetry-demo +0 -0
  20. {agentmetry-0.9.6 → agentmetry-0.9.7}/.env.example +0 -0
  21. {agentmetry-0.9.6 → agentmetry-0.9.7}/.gitignore +0 -0
  22. {agentmetry-0.9.6 → agentmetry-0.9.7}/Dockerfile +0 -0
  23. {agentmetry-0.9.6 → agentmetry-0.9.7}/README.md +0 -0
  24. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/__init__.py +0 -0
  25. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/__init__.py +0 -0
  26. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/main.py +0 -0
  27. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/__init__.py +0 -0
  28. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/auth.py +0 -0
  29. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/trusted_host.py +0 -0
  30. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/websocket.py +0 -0
  31. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/ws_bridge.py +0 -0
  32. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/cli/__main__.py +0 -0
  33. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/__init__.py +0 -0
  34. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/api_token.py +0 -0
  35. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/__init__.py +0 -0
  36. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/__init__.py +0 -0
  37. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/agt.py +0 -0
  38. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/chronicle.py +0 -0
  39. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
  40. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/ecs.py +0 -0
  41. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/sentinel.py +0 -0
  42. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/splunk.py +0 -0
  43. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/alerts.py +0 -0
  44. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/atlas.py +0 -0
  45. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/canonical.py +0 -0
  46. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/compliance_digest.py +0 -0
  47. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/__init__.py +0 -0
  48. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/benchmark.py +0 -0
  49. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  50. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  51. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  52. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  53. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  54. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  55. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  56. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  57. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  58. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  59. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  60. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  61. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  62. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  63. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  64. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  65. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
  66. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  67. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  68. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
  69. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  70. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  71. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  72. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  73. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  74. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  75. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  76. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  77. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
  78. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  79. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  80. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  81. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
  82. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  83. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  84. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  85. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
  86. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  87. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
  88. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  89. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  90. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  91. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  92. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  93. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
  94. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  95. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  96. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  97. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  98. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  99. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  100. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
  101. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  102. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  103. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/corpus.yaml +0 -0
  104. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/disposition.py +0 -0
  105. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/engine.py +0 -0
  106. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/live.py +0 -0
  107. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/live_store.py +0 -0
  108. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/models.py +0 -0
  109. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/rules.py +0 -0
  110. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/traits.py +0 -0
  111. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  112. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
  113. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/__init__.py +0 -0
  114. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/loader.py +0 -0
  115. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/models.py +0 -0
  116. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/scanner.py +0 -0
  117. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dogfood.py +0 -0
  118. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/forwarder.py +0 -0
  119. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/hashing.py +0 -0
  120. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/heartbeat.py +0 -0
  121. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/hook_bootstrap.py +0 -0
  122. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/identity.py +0 -0
  123. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/ingest.py +0 -0
  124. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/managed_hooks.py +0 -0
  125. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
  126. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/migrate.py +0 -0
  127. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/mitre.py +0 -0
  128. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/otel_ingest.py +0 -0
  129. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/policy.py +0 -0
  130. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/policy_settings.py +0 -0
  131. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/posture_event.py +0 -0
  132. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/redaction.py +0 -0
  133. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/replay.py +0 -0
  134. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/run_context.py +0 -0
  135. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/signing.py +0 -0
  136. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/sinks.py +0 -0
  137. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/spool.py +0 -0
  138. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  139. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/capability.py +0 -0
  140. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
  141. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  142. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/models.py +0 -0
  143. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_anchor.py +0 -0
  144. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_chain.py +0 -0
  145. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_db.py +0 -0
  146. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_merkle.py +0 -0
  147. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_rotation.py +0 -0
  148. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/auth.py +0 -0
  149. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/__init__.py +0 -0
  150. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/audit_exporter.py +0 -0
  151. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/bridges.py +0 -0
  152. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/bus.py +0 -0
  153. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/events.py +0 -0
  154. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/outbox.py +0 -0
  155. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/config.py +0 -0
  156. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/dashboard_session.py +0 -0
  157. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/__init__.py +0 -0
  158. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/autostart.py +0 -0
  159. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/doctor.py +0 -0
  160. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  161. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/env_file.py +0 -0
  162. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
  163. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
  164. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
  165. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/posture.py +0 -0
  166. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/__init__.py +0 -0
  167. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/host.py +0 -0
  168. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/permissions.py +0 -0
  169. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/spec.py +0 -0
  170. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/extensions.py +0 -0
  171. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/health.py +0 -0
  172. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/operator_identity.py +0 -0
  173. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/paths.py +0 -0
  174. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/__init__.py +0 -0
  175. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/mcp_proxy.py +0 -0
  176. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/detection/manifest.yaml +0 -0
  177. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/dlp/manifest.yaml +0 -0
  178. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/opa/agent_rules.rego +0 -0
  179. {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/tool/manifest.yaml +0 -0
  180. {agentmetry-0.9.6 → agentmetry-0.9.7}/pyproject.toml +0 -0
  181. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/conftest.py +0 -0
  182. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  183. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  184. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/README.md +0 -0
  185. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
  186. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
  187. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/fake_mcp_server.py +0 -0
  188. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/otel_claude_code_session.json +0 -0
  189. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agentmetry_audit.py +0 -0
  190. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agt_adapter.py +0 -0
  191. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ask_gate_phase_a.py +0 -0
  192. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_atlas_detection_enrichment.py +0 -0
  193. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_atlas_mapping.py +0 -0
  194. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_sinks.py +0 -0
  195. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_stats.py +0 -0
  196. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_tail.py +0 -0
  197. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_auth.py +0 -0
  198. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_auth_default.py +0 -0
  199. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_autostart.py +0 -0
  200. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_boot_sequence.py +0 -0
  201. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_burst_windows.py +0 -0
  202. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_hooks.py +0 -0
  203. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_sprint_b.py +0 -0
  204. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_sprint_c.py +0 -0
  205. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chronicle_adapter.py +0 -0
  206. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_backup.py +0 -0
  207. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_env_var_names.py +0 -0
  208. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cloudevents_adapter.py +0 -0
  209. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_compliance_digest.py +0 -0
  210. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_container_and_legacy_runtime.py +0 -0
  211. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_crewai_adapter.py +0 -0
  212. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_demo.py +0 -0
  213. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_adi.py +0 -0
  214. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_benchmark.py +0 -0
  215. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_default_config.py +0 -0
  216. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_disposition.py +0 -0
  217. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_disposition_api.py +0 -0
  218. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_duplicate_emission.py +0 -0
  219. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_emit_durability.py +0 -0
  220. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_engine.py +0 -0
  221. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_evasion.py +0 -0
  222. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_hf_incident.py +0 -0
  223. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_quoted_command_words.py +0 -0
  224. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_rule_identity.py +0 -0
  225. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_rules_v2.py +0 -0
  226. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_agent_env_override.py +0 -0
  227. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_invisible_unicode.py +0 -0
  228. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_markdown_exfil.py +0 -0
  229. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_scanner.py +0 -0
  230. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_doctor.py +0 -0
  231. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dogfood.py +0 -0
  232. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dogfood_freeze.py +0 -0
  233. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_driver_paths.py +0 -0
  234. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ecs_threat.py +0 -0
  235. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_event_bus.py +0 -0
  236. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_event_signing.py +0 -0
  237. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_extensions.py +0 -0
  238. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_forwarder.py +0 -0
  239. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_forwarding_shape.py +0 -0
  240. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_funding_manifest.py +0 -0
  241. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hash_key.py +0 -0
  242. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_heartbeat.py +0 -0
  243. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_bootstrap.py +0 -0
  244. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_coverage.py +0 -0
  245. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_decision_format.py +0 -0
  246. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_enforcement_timing.py +0 -0
  247. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_import_cost.py +0 -0
  248. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_real_process.py +0 -0
  249. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_spool.py +0 -0
  250. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_identity_enrich.py +0 -0
  251. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_identity_provider.py +0 -0
  252. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ingest_roundtrip.py +0 -0
  253. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_isolation.py +0 -0
  254. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_launch_targets.py +0 -0
  255. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_live_detection.py +0 -0
  256. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_live_detection_store.py +0 -0
  257. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_managed_hooks.py +0 -0
  258. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_audit_proxy.py +0 -0
  259. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_concealed_text.py +0 -0
  260. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_inventory.py +0 -0
  261. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_inventory_event.py +0 -0
  262. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_meta_fingerprint.py +0 -0
  263. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_proxy_packaged.py +0 -0
  264. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_schema.py +0 -0
  265. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mitre_dlp.py +0 -0
  266. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_opensre_adapter.py +0 -0
  267. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_operator_attribution.py +0 -0
  268. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_otel_ingest.py +0 -0
  269. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_paths.py +0 -0
  270. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_posture.py +0 -0
  271. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_powershell_encoding.py +0 -0
  272. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_readme_claims.py +0 -0
  273. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_redaction.py +0 -0
  274. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_replay.py +0 -0
  275. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_sentinel.py +0 -0
  276. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_serve_script_binds_loopback.py +0 -0
  277. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_shared_install.py +0 -0
  278. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_sigma_pack.py +0 -0
  279. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_static_serving.py +0 -0
  280. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_subagent_lifecycle.py +0 -0
  281. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_suite_leaves_real_home_alone.py +0 -0
  282. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy.py +0 -0
  283. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy_agent_cli.py +0 -0
  284. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy_git_hooks.py +0 -0
  285. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_anchor.py +0 -0
  286. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_chain.py +0 -0
  287. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_concurrency.py +0 -0
  288. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_merkle.py +0 -0
  289. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trusted_host.py +0 -0
  290. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_unattended_agent_policy.py +0 -0
  291. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_version.py +0 -0
  292. {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_yaml_detection_rules.py +0 -0
  293. {agentmetry-0.9.6 → agentmetry-0.9.7}/tools/generate_sigma_pack.py +0 -0
  294. {agentmetry-0.9.6 → agentmetry-0.9.7}/tools/mcp_audit_proxy.py +0 -0
  295. {agentmetry-0.9.6 → agentmetry-0.9.7}/tools/vault_fs_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.9.6
3
+ Version: 0.9.7
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  from datetime import datetime, timedelta, timezone
6
7
  from pathlib import Path
7
8
  from typing import Any, Literal
@@ -100,7 +101,7 @@ class ExternalIngestBody(BaseModel):
100
101
 
101
102
  source_app: str = Field(
102
103
  ...,
103
- description="cursor | claude | antigravity | codex | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
104
+ description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
104
105
  )
105
106
  event_type: str = Field(
106
107
  ...,
@@ -228,7 +229,7 @@ async def audit_tail(
228
229
  ),
229
230
  sources: str | None = Query(
230
231
  None,
231
- description="Comma-separated source apps: agentmetry,cursor,claude,antigravity,mcp_proxy",
232
+ description="Comma-separated source.app values. Known names include cursor, claude, codex, copilot, antigravity, qwen, kimi, qoder, codebuddy, mcp_proxy, trae, crewai, opensre",
232
233
  ),
233
234
  since_minutes: int | None = Query(
234
235
  None,
@@ -452,6 +453,18 @@ async def audit_export_evidence():
452
453
  )
453
454
 
454
455
 
456
+ @router.get("/machine", dependencies=[Depends(require_api_key)])
457
+ async def machine_view():
458
+ """This machine's agents, posture, MCP servers and tool policy.
459
+
460
+ Read-only. The payload is the local dashboard's "This machine" page. It
461
+ carries no command lines, paths, or secret values.
462
+ """
463
+ from agentmetry.core.diagnostics.machine_view import snapshot
464
+
465
+ return await asyncio.to_thread(snapshot)
466
+
467
+
455
468
  @router.get("/status", dependencies=[Depends(require_api_key)])
456
469
  async def audit_status():
457
470
  """Freshness + per-source counts so the dashboard can show 'last event N min ago'.
@@ -529,6 +529,88 @@ def cmd_install(args: argparse.Namespace) -> int:
529
529
  _HOOK_INSTALLERS = ("cursor", "claude", "codex", "qwen", "kimi", "qoder", "codebuddy", "copilot")
530
530
 
531
531
 
532
+
533
+
534
+ def cmd_cloud(args: argparse.Namespace) -> int:
535
+ """Poll a vendor cloud-agent API once and record what it exposes.
536
+
537
+ Everything this records passes through the same external ingest as the
538
+ hooks: canonicalized server-side, hash-chained, detected against. The
539
+ key comes from the environment (CURSOR_API_KEY, ANTHROPIC_COMPLIANCE_KEY)
540
+ and is never logged.
541
+ """
542
+ import os
543
+
544
+ import httpx
545
+
546
+ from agentmetry.core.audit.cloud_bridge import (
547
+ fetch_claude_messages,
548
+ fetch_claude_sessions,
549
+ fetch_cursor_agents,
550
+ fetch_cursor_runs,
551
+ map_claude_messages,
552
+ map_claude_session,
553
+ map_cursor_agent,
554
+ )
555
+
556
+ base = (args.url or "").strip() or None
557
+ posted = 0
558
+
559
+ def post(payload: dict) -> bool:
560
+ nonlocal posted
561
+ url = os.environ.get("AGENTMETRY_URL", "http://127.0.0.1:8000").rstrip("/")
562
+ key = os.environ.get("AGENTMETRY_API_KEY", "")
563
+ headers = {"Content-Type": "application/json"}
564
+ if key:
565
+ headers["Authorization"] = f"Bearer {key}"
566
+ resp = httpx.post(f"{url}/api/v1/audit/ingest", json=payload, headers=headers, timeout=15)
567
+ ok = resp.status_code < 300
568
+ if ok:
569
+ posted += 1
570
+ else:
571
+ print(f" ingest refused ({resp.status_code}): {resp.text[:120]}")
572
+ return ok
573
+
574
+ if args.provider == "cursor":
575
+ api_key = os.environ.get("CURSOR_API_KEY", "")
576
+ if not api_key:
577
+ print("CURSOR_API_KEY is not set.")
578
+ return 2
579
+ for agent in fetch_cursor_agents(api_key, base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE):
580
+ runs = []
581
+ if not args.skip_runs:
582
+ try:
583
+ runs = fetch_cursor_runs(str(agent.get("id")), api_key, base or "https://api.cursor.com")
584
+ except httpx.HTTPError as exc:
585
+ print(f" runs fetch failed for {agent.get('id')}: {exc}")
586
+ for payload in map_cursor_agent(agent, runs):
587
+ post(payload)
588
+ print(f"cursor: {posted} events recorded.")
589
+ return 0
590
+
591
+ if args.provider == "claude":
592
+ api_key = os.environ.get("ANTHROPIC_COMPLIANCE_KEY", "")
593
+ if not api_key:
594
+ print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
595
+ return 2
596
+ kind = "remote" if args.remote else "local"
597
+ sessions = fetch_claude_sessions(api_key, kind=kind, base=base or "https://api.anthropic.com", limit=args.limit)
598
+ for session in sessions[: args.limit]:
599
+ post(map_claude_session(session))
600
+ try:
601
+ messages = fetch_claude_messages(api_key, kind, str(session.get("id")), base or "https://api.anthropic.com")
602
+ except httpx.HTTPError as exc:
603
+ print(f" messages fetch failed for {session.get('id')}: {exc}")
604
+ continue
605
+ for payload in map_claude_messages(session, messages):
606
+ post(payload)
607
+ print(f"claude ({kind}): {posted} events recorded.")
608
+ return 0
609
+
610
+ print(f"unknown provider: {args.provider}")
611
+ return 2
612
+
613
+
532
614
  def _hooks_status() -> int:
533
615
  """Coverage as an exit code, for a deployment tool rather than a person.
534
616
 
@@ -1419,6 +1501,11 @@ def main(argv: list[str] | None = None) -> int:
1419
1501
  sub.add_parser("status", help="orchestrator health and audit export status")
1420
1502
  stats = sub.add_parser("stats", help="audit trail metrics for dogfood (events, detections)")
1421
1503
  stats.add_argument("--days", type=int, default=7)
1504
+ cloud = sub.add_parser("cloud", help="record what a vendor cloud-agent API exposes (Cursor, Claude)")
1505
+ cloud.add_argument("provider", choices=("cursor", "claude"))
1506
+ cloud.add_argument("--url", default="", help="vendor API base override")
1507
+ cloud.add_argument("--limit", type=int, default=10, help="max sessions/agents per poll")
1508
+ cloud.add_argument("--skip-runs", action="store_true", help="cursor: agents only, skip per-run fetch")
1422
1509
  detections = sub.add_parser("detections", help="list detections for one session")
1423
1510
  detections.add_argument("correlation_id", help="correlation_id / session id")
1424
1511
  disposition = sub.add_parser("disposition", help="close an audit detection from the shell")
@@ -1610,6 +1697,7 @@ def main(argv: list[str] | None = None) -> int:
1610
1697
  "hook": cmd_hook,
1611
1698
  "status": cmd_status,
1612
1699
  "stats": cmd_stats,
1700
+ "cloud": cmd_cloud,
1613
1701
  "detections": cmd_detections,
1614
1702
  "disposition": cmd_disposition,
1615
1703
  "logs": cmd_logs,
@@ -0,0 +1,317 @@
1
+ """Cloud Bridge: vendor cloud-agent surfaces, recorded into the local trail.
2
+
3
+ Cloud agents execute on vendor infrastructure, where no local hook can fire.
4
+ What the vendors expose instead is an API over the agent's lifecycle, and —
5
+ for Cursor — a live stream of its tool calls. This module turns those
6
+ surfaces into the same external-ingest payloads the local hook client sends,
7
+ so cloud sessions land in the same hash-chained trail, the same detections
8
+ and the same SIEM sinks as everything else.
9
+
10
+ Privacy posture, identical to the hook path: tool arguments are reduced to a
11
+ SHA-256 over redacted, canonically serialised JSON; result and response
12
+ bodies are hashed, never stored; no prompt or response text is forwarded.
13
+ Vendors encode parts of their payloads (Claude delivers `tool_use.input` as a
14
+ JSON-encoded *string*); those are decoded here, then hashed — decoding is for
15
+ classification, never for storage.
16
+
17
+ Boundary, stated once: this records what the vendor's API exposes. Cursor's
18
+ stream covers tool calls live during a run and expires afterwards; Claude's
19
+ Compliance API serves transcripts to an admin-scoped key. What happens inside
20
+ a vendor VM beyond those surfaces is the vendor's custody, and this module
21
+ does not pretend otherwise.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import hashlib
27
+ import json
28
+ from typing import Any
29
+
30
+ REDACT_KEYS = frozenset(
31
+ {"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
32
+ )
33
+ _COMMAND_KEYS = ("command", "cmd", "script", "CommandLine")
34
+ _PATH_KEYS = ("path", "filepath", "file_path", "AbsolutePath", "TargetFile")
35
+
36
+
37
+ def redact_arguments(args: Any) -> Any:
38
+ if isinstance(args, dict):
39
+ return {
40
+ k: ("<redacted>" if str(k).lower() in REDACT_KEYS else v)
41
+ for k, v in args.items()
42
+ }
43
+ return {"value": args} if args is not None else {}
44
+
45
+
46
+ def canonical_arguments(args: Any) -> str:
47
+ return json.dumps(redact_arguments(args), sort_keys=True, separators=(",", ":"), default=str)
48
+
49
+
50
+ def hash_arguments(args: Any) -> str:
51
+ """SHA-256 over redacted, canonically serialised arguments.
52
+
53
+ Keep in sync with agentmetry.hooks.ingest.hash_arguments — a sync test
54
+ asserts both produce the same digest for the same input, so a cloud event
55
+ and a local event carrying the same call hash identically.
56
+ """
57
+ return hashlib.sha256(canonical_arguments(args).encode("utf-8")).hexdigest()
58
+
59
+
60
+ def decode_maybe_encoded(value: Any) -> Any:
61
+ """Decode the encodings vendors put around structured fields.
62
+
63
+ Claude's Compliance API delivers `tool_use.input` as a JSON-encoded
64
+ string; some streams wrap payloads in base64. Decode for classification,
65
+ never store the decoded content: everything that reaches a canonical
66
+ event from here is a hash, a length or a name.
67
+ """
68
+ if not isinstance(value, str):
69
+ return value
70
+ text = value.strip()
71
+ if text[:1] in "{[":
72
+ try:
73
+ return json.loads(text)
74
+ except ValueError:
75
+ return value
76
+ try:
77
+ import base64
78
+
79
+ decoded = base64.b64decode(text, validate=True)
80
+ candidate = decoded.decode("utf-8")
81
+ if candidate[:1] in "{[":
82
+ return json.loads(candidate)
83
+ except (ValueError, UnicodeDecodeError):
84
+ # Not base64, or not UTF-8, or not JSON inside: return the original
85
+ # string and let the caller hash it as an opaque value.
86
+ return value
87
+ return value
88
+
89
+
90
+ def tool_summary(args: Any) -> dict[str, Any]:
91
+ """The only argument content that survives: a hashed digest, a length,
92
+ and — for shell-shaped tools — whether the command reached a remote host.
93
+ Command text, paths and values do not pass through this function's output.
94
+ """
95
+ summary: dict[str, Any] = {
96
+ "input_hash": hash_arguments(args),
97
+ "input_size": len(canonical_arguments(args)),
98
+ }
99
+ if isinstance(args, dict):
100
+ for key in _COMMAND_KEYS:
101
+ val = args.get(key)
102
+ if isinstance(val, str) and val.strip():
103
+ summary["has_command"] = True
104
+ break
105
+ else:
106
+ for key in _PATH_KEYS:
107
+ if isinstance(args.get(key), str) and args[key].strip():
108
+ summary["has_path"] = True
109
+ break
110
+ return summary
111
+
112
+
113
+ # --- Cursor Cloud Agents API -------------------------------------------------
114
+
115
+ CURSOR_DEFAULT_BASE = "https://api.cursor.com"
116
+
117
+
118
+ def map_cursor_agent(agent: dict[str, Any], runs: list[dict[str, Any]] | None = None) -> list[dict[str, Any]]:
119
+ """One cloud agent + its runs -> external-ingest payloads.
120
+
121
+ Cursor exposes lifecycle and outcome: agent status, run status, duration,
122
+ pushed branches and the PR URL. Tool-call detail is not in these
123
+ endpoints; when the connector attaches to a live stream (v1.1) those
124
+ arrive as separate tool_called events.
125
+ """
126
+ agent_id = str(agent.get("id") or "unknown")
127
+ status = str(agent.get("status") or "unknown")
128
+ out: list[dict[str, Any]] = [
129
+ {
130
+ "source_app": "cursor_cloud",
131
+ "adapter": "cloud_bridge",
132
+ "event_type": "session_start" if status == "ACTIVE" else "session_end",
133
+ "outcome": "success",
134
+ "correlation_id": f"cursor-cloud-{agent_id}",
135
+ "session_id": agent_id,
136
+ "reason": f"cloud_agent:{status}",
137
+ }
138
+ ]
139
+ for run in runs or []:
140
+ git = run.get("git") or {}
141
+ branches = git.get("branches") or []
142
+ pr_url = str(next((b.get("prUrl") for b in branches if b.get("prUrl")), "") or "")
143
+ out.append(
144
+ {
145
+ "source_app": "cursor_cloud",
146
+ "adapter": "cloud_bridge",
147
+ "event_type": "session_end",
148
+ "outcome": "success" if str(run.get("status")) == "FINISHED" else str(run.get("status") or "unknown").lower(),
149
+ "correlation_id": f"cursor-cloud-{agent_id}",
150
+ "session_id": str(run.get("id") or ""),
151
+ "reason": (
152
+ f"cloud_run:{run.get('status')};duration_ms:{run.get('durationMs', 0)};"
153
+ f"branches:{len(branches)};pr:{'yes' if pr_url else 'no'}"
154
+ ),
155
+ }
156
+ )
157
+ return out
158
+
159
+
160
+ def map_cursor_stream_tool_call(call: dict[str, Any], agent_id: str, run_id: str) -> dict[str, Any] | None:
161
+ """One tool_call event from a live run stream -> a tool_called payload.
162
+
163
+ `args` arrives as an object or an encoded string; either way it becomes a
164
+ hash here. `result` is never forwarded — only its presence and size.
165
+ """
166
+ args = decode_maybe_encoded(call.get("args"))
167
+ summary = tool_summary(args)
168
+ name = str(call.get("name") or "unknown")
169
+ status = str(call.get("status") or "unknown")
170
+ result = call.get("result")
171
+ return {
172
+ "source_app": "cursor_cloud",
173
+ "adapter": "cloud_bridge",
174
+ "event_type": "tool_called",
175
+ "outcome": "success" if status == "completed" else status,
176
+ "correlation_id": f"cursor-cloud-{agent_id}",
177
+ "session_id": run_id,
178
+ "tool": {
179
+ "qualified": f"cursor_cloud.{name}",
180
+ "server": "cursor_cloud",
181
+ "input_hash": summary["input_hash"],
182
+ "input_size": summary["input_size"],
183
+ "has_result": result is not None,
184
+ },
185
+ }
186
+
187
+
188
+ # --- Claude Compliance API ---------------------------------------------------
189
+
190
+ CLAUDE_DEFAULT_BASE = "https://api.anthropic.com"
191
+
192
+
193
+ def map_claude_tool_use(block: dict[str, Any], session_id: str) -> dict[str, Any]:
194
+ """One tool_use transcript block -> a tool_called payload.
195
+
196
+ Claude encodes `input` as a JSON-encoded string; decode_maybe_encoded
197
+ turns it back into structure so the same redact-and-hash pipeline applies
198
+ as for local events. Only the hash, size and tool name are forwarded.
199
+ """
200
+ decoded = decode_maybe_encoded(block.get("input"))
201
+ summary = tool_summary(decoded)
202
+ name = str(block.get("name") or "unknown")
203
+ truncated = bool(block.get("truncated"))
204
+ return {
205
+ "source_app": "claude_cloud",
206
+ "adapter": "cloud_bridge",
207
+ "event_type": "tool_called",
208
+ "outcome": "success",
209
+ "correlation_id": session_id,
210
+ "session_id": session_id,
211
+ "tool": {
212
+ "qualified": f"claude_cloud.{name}",
213
+ "server": "claude_cloud",
214
+ "input_hash": summary["input_hash"],
215
+ "input_size": summary["input_size"],
216
+ "truncated": truncated,
217
+ },
218
+ }
219
+
220
+
221
+ def map_claude_messages(session: dict[str, Any], messages: dict[str, Any]) -> list[dict[str, Any]]:
222
+ """A Compliance API transcript -> tool_called payloads, one per tool_use.
223
+
224
+ Text, thinking and system content are dropped here, not stored: the
225
+ transcript's words belong to the vendor's retention policy, while the
226
+ sequence of tool calls belongs in the customer's trail.
227
+ """
228
+ session_id = str(session.get("id") or "unknown")
229
+ out: list[dict[str, Any]] = []
230
+ for message in messages.get("data", []) or []:
231
+ for block in message.get("content", []) or []:
232
+ if not isinstance(block, dict) or block.get("type") != "tool_use":
233
+ continue
234
+ out.append(map_claude_tool_use(block, session_id))
235
+ return out
236
+
237
+
238
+ def map_claude_session(session: dict[str, Any]) -> dict[str, Any]:
239
+ """A listed session -> a session_start payload (metadata only)."""
240
+ return {
241
+ "source_app": "claude_cloud",
242
+ "adapter": "cloud_bridge",
243
+ "event_type": "session_start",
244
+ "outcome": "success",
245
+ "correlation_id": str(session.get("id") or "unknown"),
246
+ "session_id": str(session.get("id") or ""),
247
+ "reason": f"cloud_session:{session.get('created_at', '')[:10]}",
248
+ }
249
+
250
+
251
+ # --- Grok Build (reserved) ----------------------------------------------------
252
+
253
+ def map_grok_build_session_unsupported() -> None:
254
+ """Reserved for the Grok Build on-disk log parser.
255
+
256
+ Grok Build is a local CLI: its tool executions happen on this machine,
257
+ so the capture surface is its session logs, not a vendor cloud API. The
258
+ parser lands when a real session-log sample exists to write it against —
259
+ an invented format would be a parser that parses nothing.
260
+ """
261
+ raise NotImplementedError(
262
+ "grok_build capture lands with the on-disk log parser; install Grok "
263
+ "Build, run one session, and point the parser at its log directory."
264
+ )
265
+
266
+
267
+ # --- Fetchers (poll-once semantics; the caller owns scheduling) ---------------
268
+
269
+
270
+ def fetch_cursor_agents(api_key: str, base: str = CURSOR_DEFAULT_BASE) -> list[dict[str, Any]]:
271
+ import httpx
272
+
273
+ resp = httpx.get(
274
+ f"{base.rstrip('/')}/v1/agents",
275
+ headers={"Authorization": f"Bearer {api_key}"},
276
+ timeout=30,
277
+ )
278
+ resp.raise_for_status()
279
+ return resp.json().get("items", [])
280
+
281
+
282
+ def fetch_cursor_runs(agent_id: str, api_key: str, base: str = CURSOR_DEFAULT_BASE) -> list[dict[str, Any]]:
283
+ import httpx
284
+
285
+ resp = httpx.get(
286
+ f"{base.rstrip('/')}/v1/agents/{agent_id}/runs",
287
+ headers={"Authorization": f"Bearer {api_key}"},
288
+ timeout=30,
289
+ )
290
+ resp.raise_for_status()
291
+ return resp.json().get("runs", []) or resp.json().get("items", [])
292
+
293
+
294
+ def fetch_claude_sessions(api_key: str, kind: str = "local", base: str = CLAUDE_DEFAULT_BASE, limit: int = 20) -> list[dict[str, Any]]:
295
+ import httpx
296
+
297
+ resp = httpx.get(
298
+ f"{base.rstrip('/')}/v1/compliance/apps/sessions/{kind}",
299
+ headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"},
300
+ params={"limit": limit},
301
+ timeout=30,
302
+ )
303
+ resp.raise_for_status()
304
+ return resp.json().get("data", [])
305
+
306
+
307
+ def fetch_claude_messages(api_key: str, kind: str, session_id: str, base: str = CLAUDE_DEFAULT_BASE, limit: int = 500) -> dict[str, Any]:
308
+ import httpx
309
+
310
+ resp = httpx.get(
311
+ f"{base.rstrip('/')}/v1/compliance/apps/sessions/{kind}/{session_id}/messages",
312
+ headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"},
313
+ params={"limit": limit},
314
+ timeout=30,
315
+ )
316
+ resp.raise_for_status()
317
+ return resp.json()
@@ -27,7 +27,15 @@ from agentmetry.core.audit.run_context import resolve_operator
27
27
  from agentmetry.core.config import settings
28
28
  from agentmetry.core.version import __version__
29
29
 
30
- SCHEMA_VERSION = "2.1"
30
+ SCHEMA_VERSION = "2.2"
31
+ # One evidence export reads at most this many events. Hitting the cap used to
32
+ # drop the rest with no mark on the pack, so a period looked complete. The pack
33
+ # now says so in meta, and the integrity hash covers that statement.
34
+ _EXPORT_EVENT_CAP = 200_000
35
+ # Packs at this version and above hash `meta` (except the digest itself).
36
+ # Older packs hashed the body only, and `verify` still accepts that scope so a
37
+ # pack already handed to an auditor keeps verifying.
38
+ _META_IN_INTEGRITY: tuple[int, ...] = (2, 2)
31
39
 
32
40
 
33
41
  _COMPLIANCE_MAPPING = {
@@ -79,7 +87,9 @@ _COMPLIANCE_MAPPING = {
79
87
  "audit proxy. It does not observe unmanaged assistants (browser "
80
88
  "ChatGPT, unhooked IDEs) and is not a CASB, a sandbox, or a model "
81
89
  "evaluation tool. Absence of an event is not evidence that nothing "
82
- "happened outside the monitored boundary."
90
+ "happened outside the monitored boundary. When meta.events_truncated "
91
+ "is true, the export hit its event cap and this pack is a prefix of "
92
+ "the requested window, not the whole period."
83
93
  ),
84
94
  "disclaimer": (
85
95
  "This pack is an operator-generated audit artifact. It is not legal "
@@ -381,8 +391,39 @@ def _summarize(
381
391
  }
382
392
 
383
393
 
384
- def _integrity_hash(body: dict[str, Any]) -> str:
385
- canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), default=str)
394
+ def _schema_tuple(version: object) -> tuple[int, ...]:
395
+ """`2.2` -> `(2, 2)`. A missing or non-numeric version sorts below every real one."""
396
+ if not isinstance(version, str) or not version:
397
+ return (0,)
398
+ parts: list[int] = []
399
+ for piece in version.split("."):
400
+ if not piece.isdigit():
401
+ break
402
+ parts.append(int(piece))
403
+ return tuple(parts) or (0,)
404
+
405
+
406
+ def _covers_meta(schema_version: object) -> bool:
407
+ return _schema_tuple(schema_version) >= _META_IN_INTEGRITY
408
+
409
+
410
+ def _integrity_subject(
411
+ body: dict[str, Any], meta: dict[str, Any] | None = None
412
+ ) -> dict[str, Any]:
413
+ """What the digest is computed over.
414
+
415
+ Schema 2.2 and later include `meta` with `integrity_sha256` removed, so the
416
+ date range, producer and trail-chain binding cannot be relabelled without
417
+ breaking the hash. Older packs pass `meta=None` and hash the body alone.
418
+ """
419
+ if meta is None:
420
+ return body
421
+ covered = {key: value for key, value in meta.items() if key != "integrity_sha256"}
422
+ return {"meta": covered, **body}
423
+
424
+
425
+ def _integrity_hash(subject: dict[str, Any]) -> str:
426
+ canonical = json.dumps(subject, sort_keys=True, separators=(",", ":"), default=str)
386
427
  return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
387
428
 
388
429
 
@@ -412,7 +453,10 @@ def build_evidence_pack(
412
453
  from agentmetry.core.audit.trail_db import get_trail_db
413
454
 
414
455
  trail_db = get_trail_db()
415
- events = trail_db.read_between(start_ts, end_ts)
456
+ events = trail_db.read_between(start_ts, end_ts, limit=_EXPORT_EVENT_CAP + 1)
457
+ events_truncated = len(events) > _EXPORT_EVENT_CAP
458
+ if events_truncated:
459
+ events = events[:_EXPORT_EVENT_CAP]
416
460
 
417
461
  tool_calls = _extract_tool_calls(events)
418
462
  approvals = _extract_approvals(events)
@@ -449,11 +493,14 @@ def build_evidence_pack(
449
493
  "query_end_ts": end_ts,
450
494
  "source": "audit_trail",
451
495
  "raw_events_included": include_raw_events,
496
+ "events_truncated": events_truncated,
452
497
  "trail_chain": _trail_chain_state(),
453
498
  },
454
499
  **body,
455
500
  }
456
- pack["meta"]["integrity_sha256"] = _integrity_hash(body)
501
+ pack["meta"]["integrity_sha256"] = _integrity_hash(
502
+ _integrity_subject(body, pack["meta"])
503
+ )
457
504
  return pack
458
505
 
459
506
 
@@ -483,8 +530,14 @@ def default_export_path(
483
530
 
484
531
 
485
532
  def verify_evidence_pack(pack: dict[str, Any]) -> tuple[bool, str]:
486
- """Return (ok, message). Recomputes integrity_sha256 over the body."""
533
+ """Return (ok, message). Recomputes integrity_sha256.
534
+
535
+ Schema 2.2 and later cover `meta` except the digest. Earlier schemas cover
536
+ the body only, which is what those packs were hashed with.
537
+ """
487
538
  meta = pack.get("meta") or {}
539
+ if not isinstance(meta, dict):
540
+ return False, "meta must be an object"
488
541
  stored = meta.get("integrity_sha256")
489
542
  if not stored:
490
543
  return False, "missing meta.integrity_sha256"
@@ -495,7 +548,11 @@ def verify_evidence_pack(pack: dict[str, Any]) -> tuple[bool, str]:
495
548
  if not isinstance(body.get(key), dict):
496
549
  body[key] = {}
497
550
 
498
- expected = _integrity_hash(body)
551
+ if _covers_meta(meta.get("schema_version")):
552
+ subject = _integrity_subject(body, meta)
553
+ else:
554
+ subject = _integrity_subject(body)
555
+ expected = _integrity_hash(subject)
499
556
  if expected != stored:
500
557
  return False, f"integrity mismatch (expected {expected[:16]}…, got {stored[:16]}…)"
501
558
  return True, "integrity verified"
@@ -20,12 +20,14 @@ from agentmetry.core.config import settings
20
20
 
21
21
  ExternalApp = Literal[
22
22
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
23
- "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "agentmetry",
23
+ "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
24
+ "cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
24
25
  ]
25
26
 
26
27
  KNOWN_EXTERNAL_APPS = frozenset({
27
28
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
28
- "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot",
29
+ "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
30
+ "cursor_cloud", "claude_cloud", "grok_build",
29
31
  })
30
32
 
31
33
  _ACTION_MAP = {