stacktrace-cli 0.4.1__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 (301) hide show
  1. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/PKG-INFO +2 -2
  2. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/README.md +1 -1
  3. stacktrace_cli-0.5.0/docs/releases/v0.5.0.md +113 -0
  4. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/notification-delivery.md +27 -3
  5. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/pyproject.toml +1 -1
  6. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/__init__.py +1 -1
  7. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/cli.py +2 -4
  8. stacktrace_cli-0.5.0/src/stacktrace_cli/daemon/presentation.py +68 -0
  9. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/store.py +15 -3
  10. stacktrace_cli-0.5.0/tests/daemon/test_cli.py +224 -0
  11. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_store.py +25 -6
  12. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/uv.lock +1 -1
  13. stacktrace_cli-0.4.1/tests/daemon/test_cli.py +0 -124
  14. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.agents/skills/release-stacktrace/SKILL.md +0 -0
  15. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.claude/settings.json +0 -0
  16. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.claude/skills/release-stacktrace/SKILL.md +0 -0
  17. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.codex/hooks.json +0 -0
  18. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.github/managed-autofix.md +0 -0
  19. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.github/workflows/autofix.yml +0 -0
  20. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.github/workflows/ci.yml +0 -0
  21. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.github/workflows/claude.yml +0 -0
  22. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.github/workflows/publish-pypi.yml +0 -0
  23. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/.gitignore +0 -0
  24. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/AGENTS.md +0 -0
  25. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/CLAUDE.md +0 -0
  26. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
  27. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
  28. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0003-runtime-edges.md +0 -0
  29. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0004-three-stage-detector.md +0 -0
  30. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
  31. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0006-trust-boundary-and-detection-upload.md +0 -0
  32. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
  33. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
  34. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
  35. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0011-verdict-cache.md +0 -0
  36. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
  37. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
  38. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0014-declared-project-mapping.md +0 -0
  39. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
  40. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
  41. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
  42. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
  43. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
  44. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
  45. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0021-two-command-kinds.md +0 -0
  46. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
  47. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
  48. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
  49. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
  50. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
  51. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
  52. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
  53. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0029-stage-three-returns-to-opt-in.md +0 -0
  54. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0030-a-detection-is-dated-by-the-turns-it-cites.md +0 -0
  55. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +0 -0
  56. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
  57. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0033-a-third-party-analyzer-only-by-a-flag-that-names-it.md +0 -0
  58. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0034-the-drift-grade-is-read-off-a-run-not-a-call.md +0 -0
  59. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +0 -0
  60. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
  61. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +0 -0
  62. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +0 -0
  63. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0039-send-usage-telemetry-by-default-with-an-opt-out.md +0 -0
  64. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0041-defer-native-windows-support.md +0 -0
  65. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
  66. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0043-name-the-session-agent-and-host-and-count-errors-by-class.md +0 -0
  67. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
  68. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/HOOK-PROMPT.md +0 -0
  69. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/INDEX.md +0 -0
  70. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/adrs/TEMPLATE.md +0 -0
  71. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/cutover-openaca-remote.md +0 -0
  72. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/002-session-input.md +0 -0
  73. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/003-correlation.md +0 -0
  74. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/004-detector.md +0 -0
  75. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/005-remote-sync.md +0 -0
  76. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/006-cli-composition.md +0 -0
  77. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/007-detection-upload.md +0 -0
  78. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/008-monitor.md +0 -0
  79. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/009-reasoning-jev.md +0 -0
  80. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/plans/010-session-daemon.md +0 -0
  81. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.0.1.md +0 -0
  82. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.1.0.md +0 -0
  83. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.2.0.md +0 -0
  84. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.2.1.md +0 -0
  85. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.2.2.md +0 -0
  86. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.2.3.md +0 -0
  87. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.3.0.md +0 -0
  88. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.3.1.md +0 -0
  89. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.4.0.md +0 -0
  90. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/releases/v0.4.1.md +0 -0
  91. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/research/001-intent-drift-metric.md +0 -0
  92. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/aidr.md +0 -0
  93. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/cli-composition.md +0 -0
  94. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/correlation.md +0 -0
  95. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/detection-upload.md +0 -0
  96. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/detector.md +0 -0
  97. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/event-time.md +0 -0
  98. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/monitor.md +0 -0
  99. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/reasoning-jev.md +0 -0
  100. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/remote-sync.md +0 -0
  101. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/docs/specs/session-input.md +0 -0
  102. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/scripts/git-hooks/pre-push +0 -0
  103. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/scripts/install-hooks.sh +0 -0
  104. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/__main__.py +0 -0
  105. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/analysis.py +0 -0
  106. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/build.py +0 -0
  107. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/cli.py +0 -0
  108. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/__init__.py +0 -0
  109. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/acquire.py +0 -0
  110. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/composition.py +0 -0
  111. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/join.py +0 -0
  112. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/observed.py +0 -0
  113. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
  114. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/project_map.py +0 -0
  115. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/record.py +0 -0
  116. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/correlate/render.py +0 -0
  117. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/__init__.py +0 -0
  118. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/client.py +0 -0
  119. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/composition.py +0 -0
  120. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/observer.py +0 -0
  121. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/paths.py +0 -0
  122. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/protocol.py +0 -0
  123. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/runtime.py +0 -0
  124. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/server.py +0 -0
  125. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/daemon/service.py +0 -0
  126. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/__init__.py +0 -0
  127. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/analyzer.py +0 -0
  128. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/blocked.py +0 -0
  129. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/cache.py +0 -0
  130. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/deterministic.py +0 -0
  131. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/finding.py +0 -0
  132. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/history.py +0 -0
  133. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/jev.py +0 -0
  134. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/priors.py +0 -0
  135. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
  136. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
  137. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
  138. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
  139. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
  140. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
  141. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/reasoning.py +0 -0
  142. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/render.py +0 -0
  143. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/rules.py +0 -0
  144. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/run.py +0 -0
  145. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/secrets.py +0 -0
  146. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/verdict.py +0 -0
  147. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/detector/windows.py +0 -0
  148. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/__init__.py +0 -0
  149. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/reasoning.py +0 -0
  150. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/render.py +0 -0
  151. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/server.py +0 -0
  152. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/app.js +0 -0
  153. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
  154. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
  155. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
  156. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
  157. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/index.html +0 -0
  158. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/site/styles.css +0 -0
  159. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/state.py +0 -0
  160. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/verdicts.py +0 -0
  161. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/monitor/watch.py +0 -0
  162. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/options.py +0 -0
  163. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/private_state.py +0 -0
  164. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/__init__.py +0 -0
  165. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/cli.py +0 -0
  166. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/client.py +0 -0
  167. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/config.py +0 -0
  168. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/detect_payload.py +0 -0
  169. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/payload.py +0 -0
  170. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/policy.py +0 -0
  171. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/redact.py +0 -0
  172. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/spool.py +0 -0
  173. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/sync.py +0 -0
  174. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/sync_detect.py +0 -0
  175. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/remote/upload_contract.py +0 -0
  176. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/__init__.py +0 -0
  177. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/access.py +0 -0
  178. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/index.py +0 -0
  179. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/outcome.py +0 -0
  180. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/protocols.py +0 -0
  181. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/sessions/render.py +0 -0
  182. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/telemetry/__init__.py +0 -0
  183. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/telemetry/cli.py +0 -0
  184. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/telemetry/events.py +0 -0
  185. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/telemetry/posthog.py +0 -0
  186. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/src/stacktrace_cli/telemetry/state.py +0 -0
  187. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/__init__.py +0 -0
  188. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/agent_bom_fixture.py +0 -0
  189. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/conftest.py +0 -0
  190. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/__init__.py +0 -0
  191. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_client.py +0 -0
  192. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_composition.py +0 -0
  193. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_observer.py +0 -0
  194. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_runtime.py +0 -0
  195. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_service.py +0 -0
  196. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/daemon/test_socket.py +0 -0
  197. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/detector_session_fixture.py +0 -0
  198. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/__init__.py +0 -0
  199. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/feed_harness.mjs +0 -0
  200. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_feed_model.py +0 -0
  201. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_reasoning_gates.py +0 -0
  202. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_render.py +0 -0
  203. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_server.py +0 -0
  204. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_site_assets.py +0 -0
  205. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_state.py +0 -0
  206. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_verdicts.py +0 -0
  207. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/monitor/test_watch.py +0 -0
  208. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/__init__.py +0 -0
  209. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/helpers.py +0 -0
  210. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_cli.py +0 -0
  211. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_client.py +0 -0
  212. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_config.py +0 -0
  213. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_activity.py +0 -0
  214. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_cli.py +0 -0
  215. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_client.py +0 -0
  216. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
  217. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
  218. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_layers_compose.py +0 -0
  219. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
  220. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_redaction.py +0 -0
  221. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_spool.py +0 -0
  222. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_upload_contract.py +0 -0
  223. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_detect_wire_payload.py +0 -0
  224. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_facade_contract.py +0 -0
  225. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_payload.py +0 -0
  226. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_policy.py +0 -0
  227. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_redact_payload.py +0 -0
  228. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_seam.py +0 -0
  229. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_spool.py +0 -0
  230. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_sync.py +0 -0
  231. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_sync_detect.py +0 -0
  232. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/remote/test_upload_contract.py +0 -0
  233. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/__init__.py +0 -0
  234. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_cli.py +0 -0
  235. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_events.py +0 -0
  236. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_hooks.py +0 -0
  237. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_release_gate.py +0 -0
  238. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_state.py +0 -0
  239. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/telemetry/test_uploader.py +0 -0
  240. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_acquire.py +0 -0
  241. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_adr_cross_references_are_reciprocal.py +0 -0
  242. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_agent_instance_id.py +0 -0
  243. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_analysis.py +0 -0
  244. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_bom_shape.py +0 -0
  245. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_build_commit.py +0 -0
  246. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_call_status_conventions.py +0 -0
  247. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_cli.py +0 -0
  248. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_composition.py +0 -0
  249. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_correlate_orchestrate.py +0 -0
  250. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_correlate_render.py +0 -0
  251. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_correlated_session.py +0 -0
  252. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detection_components.py +0 -0
  253. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detection_event_time.py +0 -0
  254. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detection_subject.py +0 -0
  255. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_analyzer_contract.py +0 -0
  256. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_analyzer_live.py +0 -0
  257. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_blocked.py +0 -0
  258. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_cache.py +0 -0
  259. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_deterministic.py +0 -0
  260. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_end_to_end.py +0 -0
  261. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_finding.py +0 -0
  262. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_history.py +0 -0
  263. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_jev.py +0 -0
  264. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_priors.py +0 -0
  265. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_prompts.py +0 -0
  266. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_reasoning.py +0 -0
  267. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_render.py +0 -0
  268. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_run.py +0 -0
  269. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_secrets.py +0 -0
  270. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_verdict.py +0 -0
  271. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_why_it_stopped.py +0 -0
  272. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_detector_windows.py +0 -0
  273. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_evidence_is_dated_not_numbered.py +0 -0
  274. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
  275. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_help_sections.py +0 -0
  276. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_join.py +0 -0
  277. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_monitor_cli.py +0 -0
  278. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
  279. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_observed.py +0 -0
  280. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_openaca_contract.py +0 -0
  281. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_openaidr_contract.py +0 -0
  282. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_option_arity_matches_the_binary.py +0 -0
  283. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_project_map.py +0 -0
  284. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_readme_examples_are_real_output.py +0 -0
  285. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_release_gate_coverage.py +0 -0
  286. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_release_readiness.py +0 -0
  287. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_seam_boundary.py +0 -0
  288. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_session_identity_is_the_pair.py +0 -0
  289. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_sessions_access.py +0 -0
  290. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_sessions_end_to_end.py +0 -0
  291. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_sessions_protocol_typing.py +0 -0
  292. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_sessions_protocols.py +0 -0
  293. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_sessions_render.py +0 -0
  294. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_shell_separators_match_the_shell.py +0 -0
  295. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
  296. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +0 -0
  297. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
  298. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -0
  299. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_untrusted_content_never_travels.py +0 -0
  300. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_verification_subcommands_match_the_binary.py +0 -0
  301. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.0}/tests/test_what_jev_is_told_is_what_was_disclosed.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stacktrace-cli
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: CLI for Stacktrace — Detection and Response platform for AI Agents.
5
5
  Project-URL: Homepage, https://stacktrace.ai
6
6
  Author-email: "Stacktrace AI, Inc" <founders@stacktrace.ai>
@@ -51,7 +51,7 @@ Requires Python 3.11 or newer.
51
51
 
52
52
  ```console
53
53
  $ stacktrace --version
54
- stacktrace 0.4.1 (openaca 0.7.0)
54
+ stacktrace 0.5.0 (openaca 0.7.0)
55
55
  ```
56
56
 
57
57
  ## Quick start
@@ -24,7 +24,7 @@ Requires Python 3.11 or newer.
24
24
 
25
25
  ```console
26
26
  $ stacktrace --version
27
- stacktrace 0.4.1 (openaca 0.7.0)
27
+ stacktrace 0.5.0 (openaca 0.7.0)
28
28
  ```
29
29
 
30
30
  ## Quick start
@@ -0,0 +1,113 @@
1
+ # 0.5.0 — the alert says what happened, and a stalled agent earns one
2
+
3
+ 0.4.0 put a daemon behind the session and gave it a socket back to the agent
4
+ that caused a finding. This release is about what arrives over that socket and
5
+ what you get when you follow it. Two things it could not do: an alert read
6
+ `Stacktrace detected a high-severity finding` and named a rule id, which tells
7
+ a reader the severity of something they still have to go and identify; and the
8
+ `/stacktrace:findings` page it sent them to answered with one line per finding
9
+ in the same shape, so following the alert returned the alert.
10
+
11
+ Both now say what happened and what to do about it. The eligibility gate also
12
+ widens by one severity, which is what lets a blocked agent page you at all.
13
+
14
+ ## An alert names the rule in plain language
15
+
16
+ Notification text is selected by **rule id only** — a Stacktrace-owned lookup,
17
+ not the finding's own title. That is the same boundary ADR-0031 draws for
18
+ everything the daemon retains: detector prose, component names and evidence
19
+ never supply notification text, and nothing read from a transcript travels into
20
+ an alert.
21
+
22
+ | Rule | The alert now says |
23
+ | --- | --- |
24
+ | `stacktrace-credential-egress` | Credential-shaped material appeared in an outbound tool call |
25
+ | `stacktrace-agent-blocked` | The agent encountered a block while working |
26
+ | `stacktrace-deceptive-completion` | A success claim conflicts with verification evidence |
27
+
28
+ The body carries the severity and the rule id as fields rather than as the
29
+ headline, and points at `/stacktrace:findings` for evidence and next steps.
30
+
31
+ The credential wording is deliberately about *shape*: credential-shaped
32
+ material reached an outbound call. It does not claim the credential was valid,
33
+ and it does not claim the destination received it — neither is something the
34
+ detector knows, and an alert that asserts either is one a reader learns to
35
+ discount.
36
+
37
+ ## `stacktrace findings` explains the record it kept
38
+
39
+ `findings` printed `HIGH stacktrace-agent-blocked: <title>` and stopped. It now
40
+ renders each retained record as a block: the plain-language explanation, the
41
+ confidence, every evidence descriptor with the span it sits at, and a next step
42
+ written for that rule.
43
+
44
+ For `stacktrace-agent-blocked` the block reason does better than the generic
45
+ line. When the first evidence descriptor's kind is a code from the existing
46
+ reason catalogue — `quota_exhausted`, `policy_blocked`, `provider_throttled`,
47
+ `upstream_refused` — the finding is titled and remedied from that catalogue, so
48
+ a throttle reads as a throttle that clears by itself and a spend limit reads as
49
+ one that does not.
50
+
51
+ Two properties this output holds to:
52
+
53
+ - **Stored descriptors are quoted as data.** `kind`, `detail` and `span` are
54
+ JSON-encoded on the way to the terminal, with non-ASCII and control
55
+ characters escaped. A ledger is written from a machine's session; it is not
56
+ a trusted source of terminal escapes.
57
+ - **It says what it does not know.** The ledger retains no tool or component
58
+ names and no raw call content, so the text output states that attribution was
59
+ not retained and directs the reader to the cited session locations instead of
60
+ quietly printing a finding that looks complete.
61
+
62
+ `--format json` is unchanged — the same stored-record contract 0.4.0 shipped.
63
+
64
+ Old records need no migration. Explanations are derived at display time from
65
+ the rule id and the retained reason code, so a finding written by 0.4.0 or
66
+ 0.4.1 renders under the new templates with nothing rewritten on disk. Reading
67
+ findings does not rerun analysis or touch the ledger.
68
+
69
+ ## A medium-severity finding now notifies
70
+
71
+ The eligibility gate took `{high, critical}`, so a high-confidence medium
72
+ finding was persisted and never announced.
73
+
74
+ `stacktrace-agent-blocked` is what this mostly opens up. Its severity is
75
+ derived from who can act on the block: `user` is high, `upstream` and `env` are
76
+ medium, `agent` is low. Both of the catalogue's upstream reasons —
77
+ `provider_throttled` and `upstream_refused` — are therefore medium, and the
78
+ gate was the only thing between a stalled agent and a page about it.
79
+
80
+ **The confidence gate is untouched.** A finding still needs `confidence ==
81
+ "high"` to notify; this widens severity only, and `low` stays silent.
82
+
83
+ The store test that covered this used `medium` as its negative case, so it
84
+ would have passed for the wrong reason after the change. It now records four
85
+ findings — high/high, medium/high, low/high, high/low — and asserts exactly the
86
+ first two notify.
87
+
88
+ See `docs/specs/notification-delivery.md`.
89
+
90
+ ## Install
91
+
92
+ `uv tool install stacktrace-cli==0.5.0` or `pip install stacktrace-cli==0.5.0`.
93
+ Python 3.11 or newer; macOS and Linux.
94
+
95
+ ## Compatibility
96
+
97
+ Beta — no back-compat hedging.
98
+
99
+ - **You will get more notifications.** Any high-confidence `medium` finding now
100
+ pages the session where it was silently persisted before; in practice that is
101
+ a throttled or refused upstream stopping the agent. Nothing that was silent
102
+ for want of *confidence* has become noisy.
103
+ - **Notification title and body text changed.** Anything matching on the old
104
+ `Stacktrace detected a {severity}-severity finding` string will stop matching.
105
+ The severity and rule id are still both present, in the body.
106
+ - **`stacktrace findings --format text` is multi-line per finding.** It was one
107
+ line; a consumer parsing that output should read `--format json`, whose shape
108
+ is unchanged.
109
+ - **Already-queued notifications and delivery acknowledgements are unaffected.**
110
+ Text is generated when a notification is built, so anything in flight keeps
111
+ the wording it was built with, under the same stable event id.
112
+ - **A monitor and a CLI of the same version are still required.**
113
+ - **Dependencies are unchanged**: `openaca==0.7.0` and `openaidr==0.3.0`.
@@ -61,6 +61,29 @@ fired in which session. It does not carry transcript text, detector-written
61
61
  titles, evidence detail, spans or remediation. Widening that is a separate
62
62
  decision about the data boundary, not about delivery.
63
63
 
64
+ ### Readable alerts and local details
65
+
66
+ The notification title explains the rule in plain language, using a
67
+ Stacktrace-owned lookup rather than the finding's supplied title. Its body
68
+ names severity and rule ID and points to `/stacktrace:findings`. A credential
69
+ alert describes credential-shaped material in an outbound call; it does not
70
+ claim the credential was valid or prove that the destination received it.
71
+
72
+ `stacktrace findings` renders the retained record with a readable explanation,
73
+ confidence, evidence descriptors and span locations, and a rule-specific next
74
+ step. A known agent-blocked reason uses the existing reason catalogue's title
75
+ and remedy. Raw arguments, credentials, component names, and detector-supplied
76
+ prose are not added to notification text or persistence. The local evidence
77
+ descriptors are quoted as data, with terminal control characters escaped.
78
+
79
+ Old records need no migration: explanations are derived at display time from
80
+ rule IDs and retained block-reason codes. JSON output keeps the existing
81
+ stored-record contract. Text output explicitly states that component/tool
82
+ attribution was not retained and directs the reader to the cited local session
83
+ locations. Reading findings does not rerun analysis or change the ledger.
84
+ Already queued notification text and delivery acknowledgements are unchanged;
85
+ new notifications use the readable templates.
86
+
64
87
  ## Design
65
88
 
66
89
  ```
@@ -230,6 +253,7 @@ mean the same thing whether they come from the harness or from Slack, so the
230
253
  mute key belongs to the policy system's vocabulary. Until that key exists, a
231
254
  mute from Slack and a mute from the terminal are two unrelated records.
232
255
 
233
- `Severity` is `low | medium | high`. The eligibility gate also tests for
234
- `"critical"`, which appears nowhere else in the codebase, so that branch cannot
235
- run. This is recorded, not fixed here.
256
+ `Severity` is `low | medium | high`, and the gate notifies on `medium` and
257
+ above at high confidence. It also tests for `"critical"`, which appears nowhere
258
+ else in the codebase, so that branch cannot run. This is recorded, not fixed
259
+ here.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stacktrace-cli"
3
- version = "0.4.1"
3
+ version = "0.5.0"
4
4
  description = "CLI for Stacktrace — Detection and Response platform for AI Agents."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """The `stacktrace` command-line interface — detection and response for AI agents."""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.5.0"
@@ -9,6 +9,7 @@ import sys
9
9
  import click
10
10
 
11
11
  from .paths import RuntimePaths
12
+ from .presentation import render_finding
12
13
  from .store import FindingStore, SessionKey
13
14
 
14
15
 
@@ -100,7 +101,4 @@ def findings(agent_kind: str, session_id: str, output_format: str) -> None:
100
101
  click.echo("No Stacktrace findings for this session.")
101
102
  return
102
103
  for finding in stored:
103
- click.echo(
104
- f"{str(finding.get('severity', 'unknown')).upper()} "
105
- f"{finding.get('rule_id', 'unknown-rule')}: {finding.get('title', 'Finding')}"
106
- )
104
+ click.echo(render_finding(finding))
@@ -0,0 +1,68 @@
1
+ """Explain durable findings without reintroducing discarded transcript prose."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from stacktrace_cli.detector.blocked import REASON_CODES, reason_for
8
+
9
+ # Notification prose is selected by rule ID only (ADR-0031). In particular,
10
+ # finding titles, component names and evidence never supply notification text.
11
+ _GUIDANCE = {
12
+ "stacktrace-credential-egress": (
13
+ "Credential-shaped material appeared in an outbound tool call",
14
+ (
15
+ "Treat the credential as potentially exposed: revoke or rotate it, "
16
+ "then inspect why the outbound tool received it."
17
+ ),
18
+ ),
19
+ "stacktrace-deceptive-completion": (
20
+ "A success claim conflicts with verification evidence",
21
+ "Inspect the cited verification results before relying on the completion claim.",
22
+ ),
23
+ "stacktrace-agent-blocked": (
24
+ "The agent encountered a block while working",
25
+ "Inspect the recorded block reason before retrying the task.",
26
+ ),
27
+ }
28
+
29
+
30
+ def rule_guidance(rule_id: str) -> tuple[str, str]:
31
+ return _GUIDANCE.get(
32
+ rule_id,
33
+ ("A finding was recorded", "Inspect the retained evidence in the local session."),
34
+ )
35
+
36
+
37
+ def render_finding(finding: dict[str, object]) -> str:
38
+ """Render the old, title-less ledger as well as newly recorded findings."""
39
+ rule_id = str(finding.get("rule_id", "unknown-rule"))
40
+ title, remedy = rule_guidance(rule_id)
41
+ evidence = finding.get("evidence", [])
42
+ items = (
43
+ [item for item in evidence if isinstance(item, dict)] if isinstance(evidence, list) else []
44
+ )
45
+ if rule_id == "stacktrace-agent-blocked" and items:
46
+ code = items[0].get("kind")
47
+ if isinstance(code, str) and code in REASON_CODES:
48
+ reason = reason_for(code)
49
+ title, remedy = reason.title, reason.remedy
50
+
51
+ lines = [
52
+ f"{str(finding.get('severity', 'unknown')).upper()} {rule_id}: {title}",
53
+ f" Confidence: {finding.get('confidence', 'unknown')}",
54
+ ]
55
+ for item in items:
56
+ # Quote stored descriptors as data, including escaped terminal controls.
57
+ kind = json.dumps(item.get("kind"), ensure_ascii=True)
58
+ detail = json.dumps(item.get("detail"), ensure_ascii=True)
59
+ span = json.dumps(item.get("span"), ensure_ascii=True)
60
+ lines.extend((f" Evidence ({kind}): {detail}", f" Location: {span}"))
61
+ if not items:
62
+ lines.append(" Evidence: no descriptors were retained.")
63
+ lines.append(f" Next step: {remedy}")
64
+ lines.append(
65
+ " Tool/component names and raw call content are not retained in this ledger; "
66
+ "inspect the cited locations in the local session for attribution."
67
+ )
68
+ return "\n".join(lines)
@@ -17,6 +17,8 @@ from stacktrace_cli.detector.rules import scope_of
17
17
  from stacktrace_cli.private_state import ensure_private_directory
18
18
  from stacktrace_cli.telemetry import emit_derived
19
19
 
20
+ from .presentation import rule_guidance
21
+
20
22
 
21
23
  @dataclass(frozen=True)
22
24
  class SessionKey:
@@ -266,10 +268,16 @@ class FindingStore:
266
268
  identity = f"{session.agent_kind}\0{session.session_id}\0{rule_id}\0{anchor}".encode()
267
269
  return f"stn_{hashlib.sha256(identity).hexdigest()[:24]}"
268
270
 
271
+ #: Severities that earn a notification. `Severity` is `low | medium | high`;
272
+ #: `critical` is carried because the gate has always tested for it, though
273
+ #: no rule emits it. `low` stays silent.
274
+ _NOTIFIABLE_SEVERITIES = frozenset({"medium", "high", "critical"})
275
+
269
276
  @staticmethod
270
277
  def _eligible(finding: dict[str, object]) -> bool:
271
278
  return (
272
- finding.get("severity") in {"high", "critical"} and finding.get("confidence") == "high"
279
+ finding.get("severity") in FindingStore._NOTIFIABLE_SEVERITIES
280
+ and finding.get("confidence") == "high"
273
281
  )
274
282
 
275
283
  @staticmethod
@@ -302,13 +310,17 @@ class FindingStore:
302
310
  severity = finding["severity"]
303
311
  assert isinstance(rule_id, str)
304
312
  assert isinstance(severity, str)
313
+ explanation, _ = rule_guidance(rule_id)
305
314
  return Notification(
306
315
  event_id=event_id,
307
316
  session=session,
308
317
  rule_id=rule_id,
309
318
  severity=severity,
310
- title=f"Stacktrace detected a {severity}-severity finding",
311
- body=f"{rule_id} in this agent session. Run /stacktrace:findings for details.",
319
+ title=f"Stacktrace: {explanation}",
320
+ body=(
321
+ f"Severity: {severity}. Rule: {rule_id}. "
322
+ "Run /stacktrace:findings for evidence and next steps."
323
+ ),
312
324
  )
313
325
 
314
326
  @staticmethod
@@ -0,0 +1,224 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import pytest
8
+ from click.testing import CliRunner
9
+
10
+ from stacktrace_cli.cli import main
11
+ from stacktrace_cli.daemon import cli as daemon_cli
12
+ from stacktrace_cli.daemon.store import FindingStore, SessionKey, TranscriptFingerprint
13
+ from stacktrace_cli.detector.rules import RULE_IDS
14
+
15
+
16
+ def test_subscribe_uses_the_native_session_environment(monkeypatch, tmp_path: Path) -> None:
17
+ seen: dict[str, Any] = {}
18
+ monkeypatch.setenv("STACKTRACE_DAEMON_SOCKET", str(tmp_path / "daemon.sock"))
19
+ monkeypatch.setenv("STACKTRACE_DAEMON_STATE", str(tmp_path / "state.json"))
20
+
21
+ def follow(paths, session, stdout, stderr) -> None:
22
+ seen.update(paths=paths, session=session, stdout=stdout, stderr=stderr)
23
+
24
+ monkeypatch.setattr(
25
+ daemon_cli,
26
+ "_runtime",
27
+ lambda: (lambda _path: True, follow, lambda _paths: None),
28
+ )
29
+
30
+ result = CliRunner().invoke(
31
+ main,
32
+ ["daemon", "subscribe", "--agent-kind", "claude-code"],
33
+ env={"CLAUDE_CODE_SESSION_ID": "native-session"},
34
+ )
35
+
36
+ assert result.exit_code == 0
37
+ assert seen["session"] == SessionKey("claude-code", "native-session")
38
+ assert seen["paths"].socket == tmp_path / "daemon.sock"
39
+
40
+
41
+ def test_subscribe_requires_a_native_session_id() -> None:
42
+ result = CliRunner().invoke(
43
+ main,
44
+ ["daemon", "subscribe", "--agent-kind", "claude-code"],
45
+ env={"CLAUDE_CODE_SESSION_ID": None},
46
+ )
47
+
48
+ assert result.exit_code == 2
49
+ assert "--session-id" in result.output
50
+
51
+
52
+ def test_findings_reads_only_the_requested_session(monkeypatch, tmp_path: Path) -> None:
53
+ state_path = tmp_path / "state.json"
54
+ monkeypatch.setenv("STACKTRACE_DAEMON_STATE", str(state_path))
55
+ store = FindingStore(state_path)
56
+ store.record(
57
+ SessionKey("claude-code", "wanted"),
58
+ TranscriptFingerprint(size=1, mtime_ns=1),
59
+ (
60
+ {
61
+ "rule_id": "stacktrace-credential-egress",
62
+ "severity": "high",
63
+ "confidence": "high",
64
+ "title": "A credential left the session.",
65
+ "session": {"session_id": "claude-code:wanted"},
66
+ "evidence": [
67
+ {
68
+ "span": "claude-code:wanted:u1:0",
69
+ "kind": "credential",
70
+ "detail": "token",
71
+ }
72
+ ],
73
+ },
74
+ ),
75
+ )
76
+
77
+ result = CliRunner().invoke(
78
+ main,
79
+ [
80
+ "findings",
81
+ "--agent-kind",
82
+ "claude-code",
83
+ "--session",
84
+ "wanted",
85
+ "--format",
86
+ "json",
87
+ ],
88
+ )
89
+
90
+ assert result.exit_code == 0
91
+ document = json.loads(result.output)
92
+ assert [finding["rule_id"] for finding in document["findings"]] == [
93
+ "stacktrace-credential-egress"
94
+ ]
95
+
96
+
97
+ def test_daemon_commands_are_visible_but_run_is_internal() -> None:
98
+ root_help = CliRunner().invoke(main, ["--help"])
99
+ daemon_help = CliRunner().invoke(main, ["daemon", "--help"])
100
+ subscribe_help = CliRunner().invoke(main, ["daemon", "subscribe", "--help"])
101
+ findings_help = CliRunner().invoke(main, ["findings", "--help"])
102
+
103
+ assert "daemon" in root_help.output
104
+ assert "status" in daemon_help.output
105
+ assert "subscribe" in daemon_help.output
106
+ assert "\n run " not in daemon_help.output
107
+ assert "--agent-kind" in subscribe_help.output
108
+ assert "--agent-kind" in findings_help.output
109
+
110
+
111
+ def test_findings_explains_a_persisted_credential_finding(monkeypatch, tmp_path: Path) -> None:
112
+ state_path = tmp_path / "state.json"
113
+ monkeypatch.setenv("STACKTRACE_DAEMON_STATE", str(state_path))
114
+ store = FindingStore(state_path)
115
+ store.record(
116
+ SessionKey("claude-code", "wanted"),
117
+ TranscriptFingerprint(size=1, mtime_ns=1),
118
+ (
119
+ {
120
+ "rule_id": "stacktrace-credential-egress",
121
+ "severity": "high",
122
+ "confidence": "high",
123
+ "title": "do not render this untrusted title",
124
+ "session": {"session_id": "claude-code:wanted"},
125
+ "evidence": [
126
+ {
127
+ "span": "claude-code:wanted:u1:0",
128
+ "kind": "jwt",
129
+ "detail": "at offset 0 in the arguments of an MCP connector",
130
+ }
131
+ ],
132
+ },
133
+ ),
134
+ )
135
+ before = state_path.read_bytes()
136
+
137
+ result = CliRunner().invoke(
138
+ main, ["findings", "--agent-kind", "claude-code", "--session", "wanted"]
139
+ )
140
+
141
+ assert result.exit_code == 0
142
+ assert "Credential-shaped material appeared in an outbound tool call" in result.output
143
+ assert "Confidence: high" in result.output
144
+ assert "jwt" in result.output
145
+ assert "at offset 0 in the arguments of an MCP connector" in result.output
146
+ assert "claude-code:wanted:u1:0" in result.output
147
+ assert "rotate" in result.output
148
+ assert "not retained" in result.output
149
+ assert "do not render" not in result.output
150
+ assert ": Finding" not in result.output
151
+ assert state_path.read_bytes() == before
152
+
153
+
154
+ @pytest.mark.parametrize("rule_id", sorted(RULE_IDS))
155
+ def test_every_current_rule_has_readable_notification_and_findings_output(
156
+ rule_id: str, monkeypatch, tmp_path: Path
157
+ ) -> None:
158
+ path = tmp_path / "state.json"
159
+ monkeypatch.setenv("STACKTRACE_DAEMON_STATE", str(path))
160
+ (notification,) = FindingStore(path).record(
161
+ SessionKey("claude-code", "wanted"),
162
+ TranscriptFingerprint(size=1, mtime_ns=1),
163
+ (
164
+ {
165
+ "rule_id": rule_id,
166
+ "severity": "high",
167
+ "confidence": "high",
168
+ "title": "UNTRUSTED-TITLE",
169
+ "remediation": "UNTRUSTED-REMEDIATION",
170
+ "session": {"session_id": "claude-code:wanted"},
171
+ "evidence": [
172
+ {
173
+ "span": "claude-code:wanted:u1:0",
174
+ "kind": "quota_exhausted"
175
+ if rule_id == "stacktrace-agent-blocked"
176
+ else "jwt",
177
+ "detail": "RETAINED-DESCRIPTOR",
178
+ }
179
+ ],
180
+ },
181
+ ),
182
+ )
183
+ result = CliRunner().invoke(
184
+ main, ["findings", "--agent-kind", "claude-code", "--session", "wanted"]
185
+ )
186
+
187
+ assert result.exit_code == 0
188
+ assert "A finding was recorded" not in notification.title
189
+ assert "A finding was recorded" not in result.output
190
+ assert "Next step:" in result.output
191
+ assert "RETAINED-DESCRIPTOR" in result.output
192
+ assert "RETAINED-DESCRIPTOR" not in notification.body + notification.title
193
+ assert "UNTRUSTED" not in result.output + notification.title + notification.body
194
+ if rule_id == "stacktrace-agent-blocked":
195
+ assert "spend limit" in result.output
196
+ elif rule_id == "stacktrace-deceptive-completion":
197
+ assert "success claim conflicts with verification evidence" in result.output
198
+
199
+
200
+ def test_empty_findings_output_is_unchanged(monkeypatch, tmp_path: Path) -> None:
201
+ monkeypatch.setenv("STACKTRACE_DAEMON_STATE", str(tmp_path / "missing.json"))
202
+ result = CliRunner().invoke(
203
+ main, ["findings", "--agent-kind", "claude-code", "--session", "empty"]
204
+ )
205
+ assert result.exit_code == 0
206
+ assert result.output == "No Stacktrace findings for this session.\n"
207
+
208
+
209
+ def test_importing_the_root_cli_does_not_load_the_unix_daemon_runtime() -> None:
210
+ result = subprocess.run(
211
+ [
212
+ sys.executable,
213
+ "-c",
214
+ (
215
+ "import sys; import stacktrace_cli.cli; "
216
+ "assert 'stacktrace_cli.daemon.runtime' not in sys.modules"
217
+ ),
218
+ ],
219
+ check=False,
220
+ capture_output=True,
221
+ text=True,
222
+ )
223
+
224
+ assert result.returncode == 0, result.stderr
@@ -24,7 +24,7 @@ def _finding(
24
24
  }
25
25
 
26
26
 
27
- def test_record_persists_every_finding_and_enqueues_only_high_confidence_high_severity(
27
+ def test_record_persists_every_finding_and_enqueues_high_confidence_medium_and_above(
28
28
  tmp_path: Path,
29
29
  ) -> None:
30
30
  store = FindingStore(tmp_path / "state.json")
@@ -42,11 +42,27 @@ def test_record_persists_every_finding_and_enqueues_only_high_confidence_high_se
42
42
  confidence="high",
43
43
  span="claude-code:s1:u2:0",
44
44
  ),
45
+ _finding(
46
+ "s1",
47
+ rule_id="stacktrace-deceptive-completion",
48
+ severity="low",
49
+ confidence="high",
50
+ span="claude-code:s1:u3:0",
51
+ ),
52
+ _finding(
53
+ "s1",
54
+ severity="high",
55
+ confidence="low",
56
+ span="claude-code:s1:u4:0",
57
+ ),
45
58
  ),
46
59
  )
47
60
 
48
- assert len(store.findings(session)) == 2
49
- assert [notification.rule_id for notification in created] == ["stacktrace-credential-egress"]
61
+ assert len(store.findings(session)) == 4
62
+ assert [notification.rule_id for notification in created] == [
63
+ "stacktrace-credential-egress",
64
+ "stacktrace-agent-blocked",
65
+ ]
50
66
  assert store.pending(session) == created
51
67
  assert store.fingerprint(session) == TranscriptFingerprint(size=100, mtime_ns=200)
52
68
 
@@ -67,6 +83,9 @@ def test_notification_text_is_owned_by_stacktrace_not_the_finding_title(tmp_path
67
83
  )
68
84
 
69
85
  rendered = f"{notification.title}\n{notification.body}"
86
+ assert "Credential-shaped material appeared in an outbound tool call" in rendered
87
+ assert "stacktrace-credential-egress" in rendered
88
+ assert "/stacktrace:findings" in rendered
70
89
  assert "SECRET-123" not in rendered
71
90
  stored = store.findings(session)
72
91
  assert "SECRET-123" not in repr(stored)
@@ -143,7 +162,7 @@ def test_an_existing_finding_becomes_notifiable_when_its_grade_increases(tmp_pat
143
162
  store.record(
144
163
  session,
145
164
  TranscriptFingerprint(size=1, mtime_ns=1),
146
- (_finding("s1", severity="medium"),),
165
+ (_finding("s1", severity="low"),),
147
166
  )
148
167
  == ()
149
168
  )
@@ -151,11 +170,11 @@ def test_an_existing_finding_becomes_notifiable_when_its_grade_increases(tmp_pat
151
170
  created = store.record(
152
171
  session,
153
172
  TranscriptFingerprint(size=2, mtime_ns=2),
154
- (_finding("s1", severity="high"),),
173
+ (_finding("s1", severity="medium"),),
155
174
  )
156
175
 
157
176
  assert len(created) == 1
158
- assert store.findings(session)[0]["severity"] == "high"
177
+ assert store.findings(session)[0]["severity"] == "medium"
159
178
 
160
179
 
161
180
  def test_failed_persistence_does_not_advance_in_memory_state(
@@ -885,7 +885,7 @@ wheels = [
885
885
 
886
886
  [[package]]
887
887
  name = "stacktrace-cli"
888
- version = "0.4.1"
888
+ version = "0.5.0"
889
889
  source = { editable = "." }
890
890
  dependencies = [
891
891
  { name = "click" },