stacktrace-cli 0.4.1__tar.gz → 0.5.1__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 (366) hide show
  1. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.gitignore +3 -0
  2. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/PKG-INFO +2 -2
  3. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/README.md +1 -1
  4. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0006-trust-boundary-and-detection-upload.md +1 -1
  5. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +1 -1
  6. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +1 -1
  7. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +1 -1
  8. stacktrace_cli-0.5.1/docs/adrs/0045-scan-every-session-and-outlive-them.md +137 -0
  9. stacktrace_cli-0.5.1/docs/adrs/0046-the-finding-store-is-an-append-only-queue.md +139 -0
  10. stacktrace_cli-0.5.1/docs/adrs/0047-capture-activity-during-correlation-or-lose-it.md +108 -0
  11. stacktrace_cli-0.5.1/docs/adrs/0048-the-daemon-keeps-a-leveled-content-free-activity-log.md +59 -0
  12. stacktrace_cli-0.5.1/docs/adrs/0056-stop-a-resident-daemon-by-record-and-signal.md +124 -0
  13. stacktrace_cli-0.5.1/docs/adrs/0057-grow-the-queue-schema-additively-and-share-only-the-document.md +117 -0
  14. stacktrace_cli-0.5.1/docs/adrs/0058-one-word-for-a-finding-columns-for-a-counter-and-a-push-that-does-not-wait.md +128 -0
  15. stacktrace_cli-0.5.1/docs/adrs/0059-the-daemon-does-not-decide-whether-it-should-be-running.md +111 -0
  16. stacktrace_cli-0.5.1/docs/adrs/0060-two-upload-postures-not-one-command-with-two-sources.md +121 -0
  17. stacktrace_cli-0.5.1/docs/adrs/0061-the-drain-sends-findings-only.md +118 -0
  18. stacktrace_cli-0.5.1/docs/adrs/0062-hold-the-transition-lock-across-remote-side-effects.md +134 -0
  19. stacktrace_cli-0.5.1/docs/adrs/0063-underbuilt-debt-does-not-hold-the-scan-watermark.md +90 -0
  20. stacktrace_cli-0.5.1/docs/adrs/0064-a-redacted-value-has-one-path-span.md +86 -0
  21. stacktrace_cli-0.5.1/docs/adrs/0065-asset-external-id-resolves-by-a-ladder.md +119 -0
  22. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/INDEX.md +14 -0
  23. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/cutover-openaca-remote.md +8 -6
  24. stacktrace_cli-0.5.1/docs/plans/011-daemon-store.md +2689 -0
  25. stacktrace_cli-0.5.1/docs/plans/012-daemon-scanning.md +172 -0
  26. stacktrace_cli-0.5.1/docs/plans/013-daemon-uploads.md +130 -0
  27. stacktrace_cli-0.5.1/docs/plans/014-asset-identity.md +666 -0
  28. stacktrace_cli-0.5.1/docs/releases/v0.5.0.md +113 -0
  29. stacktrace_cli-0.5.1/docs/releases/v0.5.1.md +209 -0
  30. stacktrace_cli-0.5.1/docs/specs/daemon-scanning.md +251 -0
  31. stacktrace_cli-0.5.1/docs/specs/daemon-store.md +246 -0
  32. stacktrace_cli-0.5.1/docs/specs/daemon-uploads.md +367 -0
  33. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/notification-delivery.md +27 -3
  34. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/remote-sync.md +6 -5
  35. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/pyproject.toml +1 -1
  36. stacktrace_cli-0.5.1/src/stacktrace_cli/__init__.py +10 -0
  37. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/activity_log.py +94 -0
  38. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/bom.py +56 -0
  39. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/cli.py +407 -0
  40. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/client.py +247 -0
  41. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/composition.py +242 -0
  42. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/config.py +182 -0
  43. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/db.py +625 -0
  44. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/fleet.py +660 -0
  45. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/identity.py +83 -0
  46. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/jobs.py +181 -0
  47. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/observer.py +626 -0
  48. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/paths.py +9 -1
  49. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/presentation.py +68 -0
  50. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/protocol.py +127 -0
  51. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/runtime.py +673 -0
  52. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/scan.py +256 -0
  53. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/server.py +63 -2
  54. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/service.py +116 -0
  55. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/store.py +878 -0
  56. stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/version.py +49 -0
  57. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/render.py +23 -0
  58. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/cli.py +28 -19
  59. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/client.py +29 -0
  60. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/detect_payload.py +147 -25
  61. stacktrace_cli-0.5.1/src/stacktrace_cli/remote/identity.py +305 -0
  62. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/payload.py +7 -4
  63. stacktrace_cli-0.5.1/src/stacktrace_cli/remote/redact.py +1328 -0
  64. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/sync.py +330 -98
  65. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/sync_detect.py +134 -64
  66. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/upload_contract.py +10 -1
  67. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/events.py +9 -2
  68. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/posthog.py +34 -5
  69. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/conftest.py +15 -0
  70. stacktrace_cli-0.5.1/tests/daemon/test_activity_log.py +308 -0
  71. stacktrace_cli-0.5.1/tests/daemon/test_cli.py +655 -0
  72. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/test_client.py +4 -10
  73. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/test_composition.py +87 -0
  74. stacktrace_cli-0.5.1/tests/daemon/test_config.py +91 -0
  75. stacktrace_cli-0.5.1/tests/daemon/test_db.py +827 -0
  76. stacktrace_cli-0.5.1/tests/daemon/test_detection_needs_no_subscriber.py +115 -0
  77. stacktrace_cli-0.5.1/tests/daemon/test_fleet.py +735 -0
  78. stacktrace_cli-0.5.1/tests/daemon/test_identity.py +91 -0
  79. stacktrace_cli-0.5.1/tests/daemon/test_jobs.py +329 -0
  80. stacktrace_cli-0.5.1/tests/daemon/test_observer.py +861 -0
  81. stacktrace_cli-0.5.1/tests/daemon/test_queue_reader.py +200 -0
  82. stacktrace_cli-0.5.1/tests/daemon/test_runtime.py +1016 -0
  83. stacktrace_cli-0.5.1/tests/daemon/test_scan.py +412 -0
  84. stacktrace_cli-0.5.1/tests/daemon/test_service.py +181 -0
  85. stacktrace_cli-0.5.1/tests/daemon/test_socket.py +451 -0
  86. stacktrace_cli-0.5.1/tests/daemon/test_store.py +690 -0
  87. stacktrace_cli-0.5.1/tests/daemon/test_the_drained_payload_is_the_cold_one.py +166 -0
  88. stacktrace_cli-0.5.1/tests/daemon/test_the_stored_row_is_sufficient.py +247 -0
  89. stacktrace_cli-0.5.1/tests/daemon/test_version.py +48 -0
  90. stacktrace_cli-0.5.1/tests/daemon/test_wire_frames_are_bounded.py +154 -0
  91. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/helpers.py +4 -3
  92. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_cli.py +33 -0
  93. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_client.py +23 -0
  94. stacktrace_cli-0.5.1/tests/remote/test_detect_redaction.py +692 -0
  95. stacktrace_cli-0.5.1/tests/remote/test_identity.py +347 -0
  96. stacktrace_cli-0.5.1/tests/remote/test_no_local_path_survives_redaction.py +376 -0
  97. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_payload.py +10 -7
  98. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_redact_payload.py +37 -0
  99. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_sync.py +475 -5
  100. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_sync_detect.py +208 -1
  101. stacktrace_cli-0.5.1/tests/telemetry/test_activity_log.py +135 -0
  102. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_hooks.py +30 -30
  103. stacktrace_cli-0.5.1/tests/test_no_test_undoes_every_patch_at_once.py +51 -0
  104. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_session_identity_is_the_pair.py +20 -8
  105. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/uv.lock +1 -1
  106. stacktrace_cli-0.4.1/src/stacktrace_cli/__init__.py +0 -3
  107. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/cli.py +0 -106
  108. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/client.py +0 -134
  109. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/composition.py +0 -133
  110. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/observer.py +0 -173
  111. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/protocol.py +0 -29
  112. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/runtime.py +0 -222
  113. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/service.py +0 -154
  114. stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/store.py +0 -368
  115. stacktrace_cli-0.4.1/src/stacktrace_cli/remote/redact.py +0 -660
  116. stacktrace_cli-0.4.1/tests/daemon/test_cli.py +0 -124
  117. stacktrace_cli-0.4.1/tests/daemon/test_observer.py +0 -407
  118. stacktrace_cli-0.4.1/tests/daemon/test_runtime.py +0 -411
  119. stacktrace_cli-0.4.1/tests/daemon/test_service.py +0 -227
  120. stacktrace_cli-0.4.1/tests/daemon/test_socket.py +0 -183
  121. stacktrace_cli-0.4.1/tests/daemon/test_store.py +0 -188
  122. stacktrace_cli-0.4.1/tests/remote/test_detect_redaction.py +0 -191
  123. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.agents/skills/release-stacktrace/SKILL.md +0 -0
  124. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.claude/settings.json +0 -0
  125. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.claude/skills/release-stacktrace/SKILL.md +0 -0
  126. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.codex/hooks.json +0 -0
  127. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/managed-autofix.md +0 -0
  128. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/autofix.yml +0 -0
  129. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/ci.yml +0 -0
  130. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/claude.yml +0 -0
  131. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/publish-pypi.yml +0 -0
  132. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/AGENTS.md +0 -0
  133. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/CLAUDE.md +0 -0
  134. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
  135. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
  136. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0003-runtime-edges.md +0 -0
  137. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0004-three-stage-detector.md +0 -0
  138. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
  139. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
  140. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
  141. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
  142. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0011-verdict-cache.md +0 -0
  143. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
  144. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
  145. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0014-declared-project-mapping.md +0 -0
  146. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
  147. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
  148. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
  149. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
  150. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
  151. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
  152. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0021-two-command-kinds.md +0 -0
  153. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
  154. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
  155. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
  156. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
  157. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
  158. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
  159. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
  160. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0029-stage-three-returns-to-opt-in.md +0 -0
  161. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0030-a-detection-is-dated-by-the-turns-it-cites.md +0 -0
  162. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
  163. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0033-a-third-party-analyzer-only-by-a-flag-that-names-it.md +0 -0
  164. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0034-the-drift-grade-is-read-off-a-run-not-a-call.md +0 -0
  165. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +0 -0
  166. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
  167. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0039-send-usage-telemetry-by-default-with-an-opt-out.md +0 -0
  168. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0041-defer-native-windows-support.md +0 -0
  169. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
  170. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0043-name-the-session-agent-and-host-and-count-errors-by-class.md +0 -0
  171. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
  172. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/HOOK-PROMPT.md +0 -0
  173. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/TEMPLATE.md +0 -0
  174. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/002-session-input.md +0 -0
  175. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/003-correlation.md +0 -0
  176. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/004-detector.md +0 -0
  177. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/005-remote-sync.md +0 -0
  178. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/006-cli-composition.md +0 -0
  179. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/007-detection-upload.md +0 -0
  180. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/008-monitor.md +0 -0
  181. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/009-reasoning-jev.md +0 -0
  182. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/010-session-daemon.md +0 -0
  183. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.0.1.md +0 -0
  184. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.1.0.md +0 -0
  185. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.0.md +0 -0
  186. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.1.md +0 -0
  187. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.2.md +0 -0
  188. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.3.md +0 -0
  189. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.3.0.md +0 -0
  190. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.3.1.md +0 -0
  191. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.4.0.md +0 -0
  192. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.4.1.md +0 -0
  193. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/research/001-intent-drift-metric.md +0 -0
  194. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/aidr.md +0 -0
  195. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/cli-composition.md +0 -0
  196. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/correlation.md +0 -0
  197. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/detection-upload.md +0 -0
  198. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/detector.md +0 -0
  199. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/event-time.md +0 -0
  200. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/monitor.md +0 -0
  201. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/reasoning-jev.md +0 -0
  202. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/session-input.md +0 -0
  203. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/scripts/git-hooks/pre-push +0 -0
  204. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/scripts/install-hooks.sh +0 -0
  205. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/__main__.py +0 -0
  206. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/analysis.py +0 -0
  207. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/build.py +0 -0
  208. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/cli.py +0 -0
  209. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/__init__.py +0 -0
  210. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/acquire.py +0 -0
  211. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/composition.py +0 -0
  212. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/join.py +0 -0
  213. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/observed.py +0 -0
  214. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
  215. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/project_map.py +0 -0
  216. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/record.py +0 -0
  217. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/render.py +0 -0
  218. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/__init__.py +0 -0
  219. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/__init__.py +0 -0
  220. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/analyzer.py +0 -0
  221. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/blocked.py +0 -0
  222. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/cache.py +0 -0
  223. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/deterministic.py +0 -0
  224. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/finding.py +0 -0
  225. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/history.py +0 -0
  226. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/jev.py +0 -0
  227. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/priors.py +0 -0
  228. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
  229. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
  230. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
  231. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
  232. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
  233. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
  234. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/reasoning.py +0 -0
  235. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/rules.py +0 -0
  236. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/run.py +0 -0
  237. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/secrets.py +0 -0
  238. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/verdict.py +0 -0
  239. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/windows.py +0 -0
  240. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/__init__.py +0 -0
  241. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/reasoning.py +0 -0
  242. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/render.py +0 -0
  243. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/server.py +0 -0
  244. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/app.js +0 -0
  245. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
  246. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
  247. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
  248. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
  249. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/index.html +0 -0
  250. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/styles.css +0 -0
  251. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/state.py +0 -0
  252. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/verdicts.py +0 -0
  253. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/watch.py +0 -0
  254. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/options.py +0 -0
  255. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/private_state.py +0 -0
  256. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/__init__.py +0 -0
  257. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/config.py +0 -0
  258. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/policy.py +0 -0
  259. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/spool.py +0 -0
  260. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/__init__.py +0 -0
  261. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/access.py +0 -0
  262. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/index.py +0 -0
  263. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/outcome.py +0 -0
  264. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/protocols.py +0 -0
  265. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/render.py +0 -0
  266. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/__init__.py +0 -0
  267. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/cli.py +0 -0
  268. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/state.py +0 -0
  269. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/__init__.py +0 -0
  270. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/agent_bom_fixture.py +0 -0
  271. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/__init__.py +0 -0
  272. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/detector_session_fixture.py +0 -0
  273. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/__init__.py +0 -0
  274. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/feed_harness.mjs +0 -0
  275. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_feed_model.py +0 -0
  276. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_reasoning_gates.py +0 -0
  277. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_render.py +0 -0
  278. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_server.py +0 -0
  279. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_site_assets.py +0 -0
  280. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_state.py +0 -0
  281. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_verdicts.py +0 -0
  282. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_watch.py +0 -0
  283. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/__init__.py +0 -0
  284. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_client.py +0 -0
  285. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_config.py +0 -0
  286. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_activity.py +0 -0
  287. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_cli.py +0 -0
  288. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
  289. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
  290. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_layers_compose.py +0 -0
  291. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
  292. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_spool.py +0 -0
  293. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_upload_contract.py +0 -0
  294. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_wire_payload.py +0 -0
  295. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_facade_contract.py +0 -0
  296. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_policy.py +0 -0
  297. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_seam.py +0 -0
  298. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_spool.py +0 -0
  299. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_upload_contract.py +0 -0
  300. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/__init__.py +0 -0
  301. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_cli.py +0 -0
  302. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_events.py +0 -0
  303. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_release_gate.py +0 -0
  304. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_state.py +0 -0
  305. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_uploader.py +0 -0
  306. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_acquire.py +0 -0
  307. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_adr_cross_references_are_reciprocal.py +0 -0
  308. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_agent_instance_id.py +0 -0
  309. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_analysis.py +0 -0
  310. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_bom_shape.py +0 -0
  311. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_build_commit.py +0 -0
  312. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_call_status_conventions.py +0 -0
  313. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_cli.py +0 -0
  314. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_composition.py +0 -0
  315. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlate_orchestrate.py +0 -0
  316. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlate_render.py +0 -0
  317. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlated_session.py +0 -0
  318. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_components.py +0 -0
  319. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_event_time.py +0 -0
  320. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_subject.py +0 -0
  321. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_analyzer_contract.py +0 -0
  322. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_analyzer_live.py +0 -0
  323. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_blocked.py +0 -0
  324. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_cache.py +0 -0
  325. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_deterministic.py +0 -0
  326. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_end_to_end.py +0 -0
  327. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_finding.py +0 -0
  328. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_history.py +0 -0
  329. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_jev.py +0 -0
  330. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_priors.py +0 -0
  331. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_prompts.py +0 -0
  332. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_reasoning.py +0 -0
  333. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_render.py +0 -0
  334. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_run.py +0 -0
  335. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_secrets.py +0 -0
  336. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_verdict.py +0 -0
  337. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_why_it_stopped.py +0 -0
  338. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_windows.py +0 -0
  339. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_evidence_is_dated_not_numbered.py +0 -0
  340. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
  341. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_help_sections.py +0 -0
  342. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_join.py +0 -0
  343. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_monitor_cli.py +0 -0
  344. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
  345. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_observed.py +0 -0
  346. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_openaca_contract.py +0 -0
  347. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_openaidr_contract.py +0 -0
  348. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_option_arity_matches_the_binary.py +0 -0
  349. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_project_map.py +0 -0
  350. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_readme_examples_are_real_output.py +0 -0
  351. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_release_gate_coverage.py +0 -0
  352. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_release_readiness.py +0 -0
  353. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_seam_boundary.py +0 -0
  354. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_access.py +0 -0
  355. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_end_to_end.py +0 -0
  356. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_protocol_typing.py +0 -0
  357. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_protocols.py +0 -0
  358. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_render.py +0 -0
  359. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_shell_separators_match_the_shell.py +0 -0
  360. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
  361. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +0 -0
  362. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
  363. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -0
  364. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_untrusted_content_never_travels.py +0 -0
  365. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_verification_subcommands_match_the_binary.py +0 -0
  366. {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_what_jev_is_told_is_what_was_disclosed.py +0 -0
@@ -6,3 +6,6 @@ __pycache__/
6
6
 
7
7
  # Local git worktrees
8
8
  .worktrees/
9
+
10
+ # Local research handoffs, kept on disk and out of the repo.
11
+ HANDOFF-*.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stacktrace-cli
3
- Version: 0.4.1
3
+ Version: 0.5.1
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.1 (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.1 (openaca 0.7.0)
28
28
  ```
29
29
 
30
30
  ## Quick start
@@ -5,7 +5,7 @@ status: accepted
5
5
  date: 2026-08-28
6
6
  supersedes: null
7
7
  superseded-by: null
8
- amended-by: 0011, 0027, 0031, 0039
8
+ amended-by: 0011, 0027, 0031, 0039, 0064
9
9
  ---
10
10
 
11
11
  ## Context
@@ -6,7 +6,7 @@ date: 2026-09-17
6
6
  supersedes: null
7
7
  superseded-by: null
8
8
  amends: 0006
9
- amended-by: 0032, 0036, 0037, 0038, 0039
9
+ amended-by: 0032, 0036, 0037, 0038, 0039, 0045, 0046
10
10
  ---
11
11
 
12
12
  ## Context
@@ -6,7 +6,7 @@ date: 2026-09-18
6
6
  supersedes: null
7
7
  superseded-by: null
8
8
  amends: 0031
9
- amended-by: 0038
9
+ amended-by: 0038, 0045
10
10
  ---
11
11
 
12
12
  ## Context
@@ -6,7 +6,7 @@ date: 2026-09-21
6
6
  supersedes: null
7
7
  superseded-by: null
8
8
  amends: 0031, 0037
9
- amended-by: null
9
+ amended-by: 0045
10
10
  ---
11
11
 
12
12
  ## Context
@@ -0,0 +1,137 @@
1
+ ---
2
+ id: 0045
3
+ title: Scan every session on a watermark, and outlive all of them
4
+ status: accepted
5
+ date: 2026-09-22
6
+ supersedes: null
7
+ superseded-by: null
8
+ amends: 0031, 0037, 0038
9
+ amended-by: 0056, 0058
10
+ ---
11
+
12
+ ## Context
13
+
14
+ ADR-0031 gave the daemon a subscriber-shaped life. `DaemonService.tick()` takes
15
+ its work list from `server.active_sessions()`, which returns
16
+ `tuple(self._subscriptions)` — literally the set of connected adapters — and
17
+ `should_exit()` ends the process once that set has been non-empty and is now
18
+ empty. Both follow from how the daemon starts: `ensure_daemon()` is called from
19
+ the plugin monitor's reconnect loop, so a session forks it and the process must
20
+ not outlive the session that did.
21
+
22
+ Two things make that shape wrong now. The daemon is becoming the machine's
23
+ detection engine rather than one session's helper, so a finding for a session
24
+ nobody subscribed to is still a finding worth having. And it is acquiring
25
+ scheduled work — uploads on a cadence — which has no session to keep it alive
26
+ and stops entirely on a quiet machine.
27
+
28
+ The subscription set is also doing two unrelated jobs at once: deciding *what
29
+ gets analysed* and deciding *where a notification goes*. Only the second is
30
+ what ADR-0031 argued for.
31
+
32
+ ## Decision
33
+
34
+ The daemon has no concept of an active session. Work comes from the filesystem
35
+ on a cadence, and the process's lifetime is independent of any session.
36
+
37
+ 1. `active_sessions()` is removed from the work path. Every `--scan-interval`
38
+ the daemon enumerates transcripts changed since a **persisted watermark**,
39
+ together with recently-live sessions due ADR-0038's supplemental poll, and
40
+ analyses those.
41
+ 2. The first scan, with no watermark on disk, is bounded by `--scan-since`
42
+ (default 3 days). Every scan after that is bounded by the watermark, which
43
+ advances to *the scan's start time* — not the newest mtime seen, or a file
44
+ written during the scan is jumped over. A few seconds of overlap absorbs
45
+ mtime granularity and clock skew.
46
+ 3. `should_exit()`, `_connected_once` and `startup_grace` are **deleted**, not
47
+ flagged. So are `_last_seen`, `_absent_since` and `disconnect_grace`: they
48
+ exist to track subscribers, and the scan window already keeps a
49
+ just-ended session in view for days rather than five seconds.
50
+ 4. The daemon instead exits when told to (`daemon stop`, a recorded
51
+ `daemon disable`), when the machine goes away, or when its running
52
+ `version_label()` diverges from the installed distribution.
53
+ 5. One `analyse_sessions` call per tick for the whole batch, not one per
54
+ session. `collect()` stays per transcript so a malformed file loses only its
55
+ own session.
56
+ 6. `NotificationServer._subscriptions` is untouched and remains the delivery
57
+ routing table. **Subscriptions decide where a finding goes, never whether it
58
+ is found.**
59
+
60
+ This amends ADR-0031's implicit use of subscription as a work source and its
61
+ process-lifetime rule; its routing, persistence and delivery decisions stay in
62
+ force. ADR-0037's cursor ownership is untouched — the append cursors remain
63
+ OpenAIDR's, in-memory, for the collector's lifetime. ADR-0038's supplemental
64
+ poll survives as the second input to the work list.
65
+
66
+ ## Alternatives considered
67
+
68
+ - **Keep subscription as the work source and union it with a scan.** Rejected:
69
+ two sources for one list, and the union can only ever equal the scan, since
70
+ any subscribed session's transcript is by definition recent. It preserves the
71
+ coupling while adding a second mechanism.
72
+ - **A `--resident` flag that pins `should_exit()` to `False`.** Rejected, and
73
+ this was the first design. A flag keeps the coupling and hides it behind a
74
+ default. Removal is clean instead: `daemon run` is `hidden=True`, an internal
75
+ command `ensure_daemon()` invokes rather than something a user runs expecting
76
+ it to return, and `should_exit` has no production caller outside `run()`. The
77
+ only fallout is assertions in `tests/daemon/test_service.py` that test the
78
+ behaviour being removed.
79
+ - **Keep `disconnect_grace`.** Rejected because it is subsumed, not merely
80
+ unused. Its window was carefully anchored to the first absent poll rather than
81
+ the last-seen time, because a slow `tick()` could otherwise consume the whole
82
+ budget. With a scan window measured in days, a session that ended a minute ago
83
+ is still enumerated; the intricacy and the bug class it guarded both stop
84
+ existing.
85
+ - **`--scan-since` as a standing window rather than a bootstrap horizon.**
86
+ Rejected: it re-reads the same days forever, and it forces a "has this daemon
87
+ run before" special case to avoid doing so. A watermark removes the special
88
+ case and closes downtime gaps for free — a daemon down for a week resumes and
89
+ enumerates a week.
90
+ - **Enumerate by mtime only.** Rejected: it silently regresses ADR-0038. A
91
+ session whose transcript is stable but whose MCP logs moved would never be
92
+ looked at again.
93
+ - **Per-session `analyse_sessions` calls, as today.** Rejected: correlation and
94
+ advisory lookups are per-run work, so N calls repeat them N times.
95
+ `analyse_sessions` already takes tuples and already shares
96
+ `_analyse_sessions` with the cold path.
97
+ - **Keep the per-transcript `mtime - 1 minute` cutoff as `since`.** Rejected as
98
+ unnecessary: `_analyse_sessions` does not filter on `since`; it stamps it as
99
+ `window_start` metadata and analyses the view whole. The watermark is a
100
+ simpler value with the same effect, and batching therefore cannot starve a
101
+ session-wide rule (ADR-0016) of turns.
102
+
103
+ ## Consequences
104
+
105
+ Enables detection on a machine where no adapter ever connects, and gives
106
+ scheduled work a process that is still alive to do it. Removes more code than it
107
+ adds.
108
+
109
+ Costs, honestly:
110
+
111
+ - **Nothing restarts the daemon.** With supervision out of scope, a crash means
112
+ the endpoint is down until the next `ensure_daemon()` fork. The
113
+ version-divergence exit is what stops a long-lived process holding stale code,
114
+ replacing the garbage-collection that exit-on-idle was accidentally providing.
115
+ - **Collector memory now tracks transcripts seen since start**, not sessions
116
+ open now. ADR-0037's collector holds one cursor per file for the process
117
+ lifetime, and the process is now long-lived. If it bites, evict by last-touch
118
+ inside the seam — not by persisting cursors, which ADR-0037 places upstream.
119
+ - **A wedge lasts all day.** No idle exit and no supervisor means a hang
120
+ persists until logout. A maximum lifetime is worth having as a safety valve.
121
+ - **Telemetry's `session_started` changes character.** The bootstrap fires it
122
+ once per session in the horizon. It means "the daemon first saw this session",
123
+ not "a user started one" (ADR-0039).
124
+ - **Scan cost is a new standing cost.** Bounded in steady state by the
125
+ watermark; the bootstrap is the expensive pass.
126
+
127
+ ## When to revisit
128
+
129
+ - When external supervision lands: the version-divergence exit becomes one of
130
+ several restart triggers rather than the only one, and exit codes need
131
+ defining before `KeepAlive` is wired — `run_daemon()` currently returns exit
132
+ `0` on lock contention and on a live socket, which a supervisor would read as
133
+ "do not restart".
134
+ - If enumeration cost on a large `~/.claude` proves material, which would make
135
+ a filesystem-event source worth its complexity.
136
+ - If a second agent kind lands, since the observer map and the scan root are
137
+ both still Claude-shaped.
@@ -0,0 +1,139 @@
1
+ ---
2
+ id: 0046
3
+ title: Make the finding store an append-only queue of detection revisions
4
+ status: accepted
5
+ date: 2026-09-22
6
+ supersedes: null
7
+ superseded-by: null
8
+ amends: 0031
9
+ amended-by: 0057, 0058
10
+ ---
11
+
12
+ ## Context
13
+
14
+ ADR-0031 gave the daemon durable, content-free findings and delivery state, and
15
+ `FindingStore` implements that as one JSON document rewritten whole on every
16
+ `record()`. That is adequate for the sessions a human happened to subscribe to.
17
+ ADR-0045 makes the daemon analyse every session on the machine, and a later
18
+ deliverable will drain findings to Fleet on a cadence — which needs something
19
+ the document cannot express: *what is new since I last looked?*
20
+
21
+ Two properties of the existing store block that.
22
+
23
+ `_durable_finding()` is lossy. `observer.py` produces
24
+ `detection_json(detection)` — the whole detection — and `record()` keeps
25
+ `rule_id`, `severity`, `confidence`, `session_id` and evidence spans, discarding
26
+ the rest. Everything an upload mapper needs (`scope`, `subject`, `involved`,
27
+ component identities, occurrence range) is gone before it reaches disk.
28
+
29
+ And a finding's identity is stable while its content is not. `_event_id()`
30
+ hashes `(agent_kind, session_id, rule_id, anchor)`; for a session-scoped rule
31
+ the anchor *is* the session id, so one identity covers the whole session however
32
+ many times the rule fires. A session-wide rule such as stall grouping (ADR-0016)
33
+ legitimately accumulates cited turns and widens its occurrence range across
34
+ scans. Today that is `stored[event_id] = …`, last-write-wins in place, which is
35
+ invisible because nothing reads by position.
36
+
37
+ ## Decision
38
+
39
+ The finding store becomes a SQLite **queue**: an append-only log of detection
40
+ revisions on a monotonic axis, with a cursor per reader. The interface
41
+ `FindingStore` presents to its callers does not change.
42
+
43
+ 1. `findings(seq INTEGER PRIMARY KEY AUTOINCREMENT, event_id, content_hash,
44
+ agent_kind, session_id, detection_json, delivered_at, acked_at, created_at)`
45
+ with `UNIQUE (event_id, content_hash)` and an index on `(session_id, seq)`.
46
+ `seq` is the cursor axis. `event_id` is **not** unique.
47
+ 2. A row is **one detection revision**. When a scan produces content differing
48
+ from the last stored revision of that `event_id`, a new row is appended with
49
+ a new `seq`. `content_hash` — over canonically serialised `detection_json` —
50
+ is what distinguishes "this finding again, unchanged" from "this finding, now
51
+ different", so an unchanged re-derivation writes nothing.
52
+ 3. The row stores the **detection**, not any consumer's rendering of it. Each
53
+ reader renders at send time.
54
+ 4. Exactly one writer, so insert order is commit order and a reader taking
55
+ `max(seq)` cannot skip a row committed later with a lower value. WAL, so
56
+ `stacktrace findings` and `daemon status` read without blocking it.
57
+ 5. `cursors(subscriber, partition, last_seq)` exists with no rows. A reader that
58
+ is not configured holds **no cursor row at all** — retention prunes below the
59
+ cursor floor, and a row pinned at zero would make a machine accumulate
60
+ findings forever.
61
+ 6. `daemon-v1.json` is **deleted, not imported**. Nothing is carried across:
62
+ the product is new, the file holds a few days of content-free findings, and
63
+ no code outside `daemon/` reads it. A newer `schema_meta` version fails
64
+ closed rather than reading a newer schema partially.
65
+
66
+ This amends ADR-0031's persistence clause. Its routing, delivery and lifecycle
67
+ decisions stay in force, and the socket path is untouched: `record()` still
68
+ publishes inline and `pending()` still drains on subscribe.
69
+
70
+ ## Alternatives considered
71
+
72
+ - **`event_id UNIQUE` with `INSERT OR IGNORE`.** Rejected, and it is the
73
+ obvious-looking choice: `event_id` *is* the stable identity, and it is exactly
74
+ what makes redelivery idempotent. It only breaks once a cursor exists — the
75
+ first thin revision is kept and every later, richer one is lost. The failure
76
+ is silent and presents as "Fleet has the thin version of some findings".
77
+ - **Update the row in place.** Rejected for the mirror-image reason: the content
78
+ changes but `seq` does not move, so a reader that has already passed the row
79
+ never sees the update. Both write strategies lose data; only appending does
80
+ not.
81
+ - **Store a pre-rendered `upload_json`.** Rejected. `detection_json()` and
82
+ `_detection_for_upload()` are two renderings of the same `Detection`, each
83
+ keeping what its own audience needs — the local one adds `stage`, `label` and
84
+ `title`; the Cloud one adds `scope` and participants and deliberately omits
85
+ what the Cloud computes. Neither derives from the other, so a rendered column
86
+ freezes one consumer's wire format into the queue and starves the next one.
87
+ The defect was that `_durable_finding()` is lossy, not that rendering happens
88
+ late. Cost accepted: `_detection_for_upload()` takes a `Detection`, not a
89
+ dict, so either the stored record rehydrates or both renderers learn to read
90
+ the canonical dict.
91
+ - **A `sessions` table.** Rejected. A session is an `(agent_kind, session_id)`
92
+ pair carried on each row — enough to partition a cursor and route a frame.
93
+ More would be a model of the machine, which is what the queue is not. The
94
+ scan watermark (ADR-0045) is separate and no reader touches it.
95
+ - **A per-`(event_id, sink)` delivery table.** Rejected as redundant with a
96
+ watermark: within a partition the single writer produces in `seq` order and
97
+ delivery is sequential, so "below is done, above is pending" is complete.
98
+ Retry needs no column — a failed delivery simply does not advance the cursor.
99
+ - **Import the JSON document's contents.** Rejected as unearned: an importer,
100
+ a dedupe path and a crash-window argument, to preserve state nothing reads
101
+ except `stacktrace findings` on a session that is still open. The cost is a
102
+ notification lost for a session live at the moment of upgrade.
103
+ - **Keep the JSON document and add a sidecar index.** Rejected: the whole-file
104
+ rewrite is the cost that does not survive scanning every session, and two
105
+ stores disagree the first time one write succeeds and the other does not.
106
+
107
+ ## Consequences
108
+
109
+ Enables a reader to ask "what is new", which is the whole point, and stops the
110
+ store discarding fields nobody can recover later.
111
+
112
+ Costs and cautions:
113
+
114
+ - **`seq` has gaps.** Verified against SQLite: an `INSERT OR IGNORE` that hits
115
+ the constraint still consumes the `AUTOINCREMENT` value. Harmless to a
116
+ `seq > last_seq` cursor, but nothing may assume contiguity. Reading the last
117
+ revision before inserting avoids churning the sequence on quiet ticks.
118
+ - **Canonical serialisation is load-bearing.** Key ordering alone would produce
119
+ phantom revisions at scan cadence.
120
+ - **One finding can occupy several rows**, so a batch holding two revisions of
121
+ one `event_id` should send only the later, and pruning must keep the newest
122
+ revision rather than the oldest.
123
+ - **The database grows with no reader to prune behind it**, until the
124
+ deliverable that adds one.
125
+ - Storing more per row widens what sits on disk for a retention window. ADR-0006
126
+ is unchanged — the added fields are catalogue text, identities, counts and
127
+ timestamps — but the invariant now deserves an exhaustive test in the manner
128
+ of `test_untrusted_content_never_travels.py`.
129
+
130
+ ## When to revisit
131
+
132
+ - If a second writer becomes necessary — for instance if `sync detect` becomes
133
+ "drain the cursor now" and is implemented by opening the database rather than
134
+ asking the daemon over its socket. That would break the ordering invariant the
135
+ cursor rests on.
136
+ - If revision churn proves costly on long sessions, which would argue for
137
+ collapsing superseded revisions at write time rather than at prune time.
138
+ - If a reader ever needs a finding's full history rather than its latest state,
139
+ which the append-only shape happens to preserve but nothing currently uses.
@@ -0,0 +1,108 @@
1
+ ---
2
+ id: 0047
3
+ title: Capture activity counters during correlation, or lose them
4
+ status: superseded
5
+ date: 2026-09-22
6
+ supersedes: null
7
+ superseded-by: 0061
8
+ amends: null
9
+ amended-by: 0058
10
+ ---
11
+
12
+ ## Context
13
+
14
+ A detect run uploads three things per agent kind: `detections`, `unknowns` and
15
+ `activity` (`docs/specs/detection-upload.md`). Activity is per-session
16
+ invocation counters — how many times each bucket of tooling actually ran —
17
+ and the Cloud sums composition coverage from it.
18
+
19
+ ADR-0046 makes the daemon's store a queue of detection revisions, and a later
20
+ deliverable drains that queue to Fleet. Detections fit the queue naturally: they
21
+ are events, appended, read by a monotonic cursor. Activity does not. It is a
22
+ **rollup** — counters that change — and a `seq` cursor cannot express "send me
23
+ the counters that moved".
24
+
25
+ The tempting simplification is to leave activity out: keep one mechanism, carry
26
+ findings only, and revisit if the console misses it. That was the working
27
+ recommendation until `_collect_detect_run` was read closely:
28
+
29
+ > Every activity row is built here, while the correlated record is in hand: a
30
+ > `Detection` carries no counters, and the record is the only place the
31
+ > `(call, resolution)` pairs exist.
32
+
33
+ ## Decision
34
+
35
+ Activity counters are extracted during analysis, in the same transaction as
36
+ that session's findings, and persisted as an upserted table keyed
37
+ `(agent_kind, session_id, bucket)` carrying `updated_at`. A reader sends rows
38
+ touched since its last drain — a selection rule alongside, not on, the `seq`
39
+ axis.
40
+
41
+ `agent_kind` is part of the key because `build_detect_payloads` groups activity
42
+ by kind while an individual row carries none: the grouping exists only as a dict
43
+ key at build time, so a reader rebuilding the payload from storage has no other
44
+ way to recover it.
45
+
46
+ The daemon's observer must stop discarding the analysis result after taking
47
+ detections from it.
48
+
49
+ **It lands with the store, not with the reader.** "Cannot be backfilled" is a
50
+ statement about a *scan*, not a release — capture started at any time yields
51
+ counters from then on — so deferring it to the consuming deliverable would be
52
+ defensible. It is not done, for two reasons: the store is meant to hold
53
+ everything the upload needs, so splitting the schema across deliverables buys a
54
+ migration later; and the first upload would otherwise ship against an empty
55
+ counter table, reporting zero coverage until enough scans had run. `_scan_claude_session` currently keeps only
56
+ `tuple(detection_json(d) for d in result.run.detections)`; the correlated record
57
+ that `activity_for_upload()` needs is in hand at that moment and thrown away.
58
+
59
+ ## Alternatives considered
60
+
61
+ - **Leave activity out of the queue and revisit later.** Rejected on a fact,
62
+ having first been the recommendation. The counters exist only while the
63
+ correlated record is live, so omitting them is a **per-scan discard, not a
64
+ deferral**. Recovering them later means re-analysing transcripts that
65
+ retention may have outlived. "We can add it when someone asks" is false here.
66
+ - **Rebuild activity from stored detections at send time.** Rejected because it
67
+ is not possible: a `Detection` carries no counters, and the `(call,
68
+ resolution)` pairs live only in the correlated record.
69
+ - **Send detections with an empty `activity[]`.** Rejected as a silent product
70
+ regression. The Cloud sums coverage from `activity[]`, so every daemon-driven
71
+ run would report zero coverage — indistinguishable, to a reader of the
72
+ console, from an endpoint that genuinely ran nothing.
73
+ - **Force activity onto the `seq` axis as append-only rows.** Rejected: a
74
+ counter that changes would append a row per bucket per scan, and a reader
75
+ would have to reduce the log to get the current value. The rollup is the
76
+ natural shape; pretending otherwise to preserve one mechanism costs more than
77
+ the second selection rule does.
78
+ - **Re-collect sessions at upload time to recompute activity.** Rejected: it
79
+ reintroduces exactly the collection cost the cursor exists to remove, and
80
+ makes the upload path depend on transcripts still being present.
81
+
82
+ ## Consequences
83
+
84
+ Coverage keeps working on a daemon-driven endpoint, and the counters survive
85
+ transcript pruning because they are captured when they exist.
86
+
87
+ Costs:
88
+
89
+ - **A second selection rule.** A reader drains findings by `seq` and activity by
90
+ `updated_at`. The queue is no longer one mechanism, and that is accepted
91
+ deliberately rather than discovered.
92
+ - **The store deliverable is not pure substitution.** It changes the observer,
93
+ not only the persistence layer, because the capture point is upstream of the
94
+ store.
95
+ - Activity rows are not versioned. A drain sends the current value of a counter,
96
+ so a reader that misses a drain sees a jump rather than a series. That matches
97
+ what a rollup is and what the Cloud does with it, but it means activity has no
98
+ equivalent of ADR-0046's revision history.
99
+
100
+ ## When to revisit
101
+
102
+ - If the Cloud ever distinguishes "no activity reported" from "no activity", the
103
+ simplification rejected here becomes available again.
104
+ - If activity acquires a per-drain delta shape rather than a running total, at
105
+ which point it becomes an event and belongs on the `seq` axis after all.
106
+ - If correlation is ever restructured so counters survive on the `Detection`
107
+ itself, which would remove the capture-point constraint this ADR exists to
108
+ record.
@@ -0,0 +1,59 @@
1
+ ---
2
+ id: 0048
3
+ title: The daemon keeps a leveled, content-free, size-capped activity log
4
+ status: accepted
5
+ date: 2026-09-23
6
+ supersedes: null
7
+ superseded-by: null
8
+ amends: null
9
+ amended-by: null
10
+ ---
11
+
12
+ ## Context
13
+
14
+ `daemon.log` held only failures. On 2026-09-22 a daemon that had been healthy
15
+ for hours, tracking 14 sessions, had a 90-byte log. There was no way to tell it
16
+ from a stuck one. Telemetry was worse: `emit` drops events silently (opted out,
17
+ lock busy, inadmissible, no key, refused by the SDK), and the SDK's `on_error`
18
+ was a no-op.
19
+
20
+ ## Decision
21
+
22
+ The daemon logs through stdlib `logging` to `daemon.log`, at DEBUG unless
23
+ `STACKTRACE_DAEMON_LOG_LEVEL` says otherwise.
24
+
25
+ 1. **INFO:** start, stop, subscriber connect and disconnect, notification
26
+ delivered and acknowledged, composition refreshed.
27
+ **DEBUG:** each scan, with its reason and counts.
28
+ **WARNING:** analysis and refresh failures, and failed uploads.
29
+ 2. **Every telemetry event is `queued` or `dropped` with a reason.**
30
+ `queued` means the SDK returned a uuid. The reasons are a closed set:
31
+ `inadmissible`, `underivable`, `opted_out`, `lock_busy`,
32
+ `state_unwritable`, `no_project_key`, `sdk_refused` and `sdk_error`.
33
+ 3. **No content.** A line holds kinds, counts, class names and event ids. It
34
+ never holds a finding's title or body, an exception message, or transcript
35
+ text. Peer-supplied session ids are written with `%r`, so a newline in one
36
+ cannot forge a line.
37
+ 4. **Capped and private.** A 0600 `RotatingFileHandler`, 5 MiB plus one
38
+ backup. Only the daemon holding the lock opens it. After a rollover the
39
+ daemon moves its stdout and stderr onto the new file, so a crash traceback
40
+ is not lost.
41
+ 5. **Silent elsewhere.** The `stacktrace_cli` logger has a `NullHandler`, so
42
+ CLI processes print nothing new.
43
+
44
+ ## Alternatives considered
45
+
46
+ - **Log "sent" on delivery:** not possible. posthog 7.59 reports failed
47
+ uploads through `on_error` and has no success hook, so `queued` is the
48
+ strongest true claim.
49
+ - **Default to INFO:** rejected. The log exists to debug a daemon that looks
50
+ quiet, and the cap bounds what DEBUG costs.
51
+ - **Log finding titles:** rejected. The untrusted-content test polices
52
+ findings, not log files, and the `event_id` is enough to look a finding up.
53
+ - **Log per-session tracking in `DaemonService`:** deferred. ADR-0045 (PR #63)
54
+ removes subscriptions from the work path.
55
+
56
+ ## When to revisit
57
+
58
+ If anything starts parsing `daemon.log`, move it to versioned JSONL. If the
59
+ SDK gains a delivery hook, log `sent`.
@@ -0,0 +1,124 @@
1
+ ---
2
+ id: 0056
3
+ title: Stop a resident daemon by record and signal, and notice an upgrade by metadata
4
+ status: superseded
5
+ date: 2026-09-23
6
+ supersedes: null
7
+ superseded-by: 0059
8
+ amends: 0045
9
+ amended-by: null
10
+ ---
11
+
12
+ ## Context
13
+
14
+ ADR-0045 made the daemon resident and named two ways for it to end: an explicit
15
+ stop, and `version_label()` diverging from the installed distribution. It did
16
+ not say how either is done, and both have a plausible-looking implementation
17
+ that is wrong.
18
+
19
+ **The version check.** `version_label()` is `__version__` plus, on an editable
20
+ install, a commit read by shelling out to `git`. Two things follow. It is
21
+ constant for the life of a process — `__version__` is an imported module
22
+ constant, and the label is `functools.cache`d — so comparing it against itself
23
+ is trivially equal and detects nothing. And re-computing it is a subprocess,
24
+ which `docs/specs/daemon-scanning.md` explicitly forbids putting on the scan
25
+ loop: *"it stays resolved once before the socket opens and cached. The scan
26
+ loop must not move it onto a per-tick path."* The spec names the value to
27
+ compare without naming what to compare it *against*, and the only thing that
28
+ moves under a running process is the installed distribution's own metadata.
29
+
30
+ **The stop.** A resident daemon needs an off switch that is not "kill it and
31
+ hope". The obvious three are a control frame on the existing socket, a marker
32
+ file the scan loop polls, and a signal. The first two each break something the
33
+ deliverable holds fixed.
34
+
35
+ **The status surface.** `daemon status` must report the *effective
36
+ configuration and its source*, because fleet-wide drift is undebuggable without
37
+ it. `daemon run` is `hidden=True` and is forked by `ensure_daemon()`, so its
38
+ settings arrive from whatever environment that fork inherited — which is not
39
+ necessarily the environment of the shell someone later runs `status` in.
40
+
41
+ ## Decision
42
+
43
+ Four things, each the cheapest mechanism that does not break a constraint the
44
+ deliverable already holds.
45
+
46
+ 1. **Version divergence compares the installed distribution version**, re-read
47
+ from `importlib.metadata` on each check, against the `__version__` half of
48
+ the `version_label()` resolved once before the socket opened. A distribution
49
+ with no record answers "not diverged".
50
+ 2. **`daemon stop` is `SIGTERM` to a recorded pid**, handled into setting the
51
+ loop's stop event so the composition thread closes and the socket file is
52
+ removed. It refuses to signal unless something is listening on the socket,
53
+ which is what bounds pid reuse.
54
+ 3. **`daemon disable` is a marker file** under the state directory, read before
55
+ the socket opens by `run_daemon()` *and* before the fork by
56
+ `ensure_daemon()`. `daemon enable` removes it.
57
+ 4. **The running daemon writes its effective configuration to a run-state
58
+ file** at startup — pid, started-at, version, and every setting with its
59
+ source — and `daemon status` reads that. The file is removed on a clean
60
+ exit, so a stale one is the signature of a crash.
61
+
62
+ ## Alternatives considered
63
+
64
+ - **Re-compute `version_label()` on each check**, which is what the spec's
65
+ wording reads as literally. Rejected: it puts a `git` subprocess on the scan
66
+ loop, which the same spec forbids two paragraphs earlier, and it still cannot
67
+ detect a version change, because the `__version__` half is a module constant
68
+ the running process cannot re-import.
69
+ - **Compare the whole label including the commit.** Rejected: the installed
70
+ metadata never carries a commit, so a build from a branch would read as
71
+ diverged on its first tick and the daemon would exit-loop.
72
+ - **Build a maximum lifetime instead of, or as well as, the version check.**
73
+ Not built. The spec asks for the choice to be made deliberately rather than
74
+ for both mechanisms, and a restart trigger belongs with the external
75
+ supervision that is already deferred. The cost is recorded in ADR-0045 and
76
+ stands: a wedged process lasts until logout.
77
+ - **A `stop` control frame on the notification socket.** Rejected: the wire
78
+ protocol and the plugin that speaks it are an explicit non-goal of this
79
+ deliverable, and a stop verb on a subscriber-facing protocol is a stop verb
80
+ any subscriber can send.
81
+ - **A stop marker file the scan loop polls.** Rejected for latency: it costs up
82
+ to one `--scan-interval`, and the point of an off switch is that it works now.
83
+ The *disable* marker is a file for the opposite reason — it must be writable
84
+ when there is no process to signal.
85
+ - **Record the stop pid in the existing flock file.** Rejected: the lock is
86
+ opened append-only and its emptiness is not load-bearing anywhere; a separate
87
+ run-state file carries the pid alongside the configuration `status` needs, so
88
+ one file answers both questions.
89
+ - **Have `daemon status` re-derive the configuration from its own
90
+ environment.** Rejected: it would print the reader's settings labelled as the
91
+ daemon's, which is precisely the drift the row exists to expose. It remains
92
+ the *fallback* when no daemon is running, where the sources it reports are
93
+ honestly its own.
94
+
95
+ ## Consequences
96
+
97
+ An upgrade is picked up within one scan interval of the new distribution
98
+ landing, without a subprocess on the loop. An operator can stop, disable and
99
+ inspect an endpoint without reading a log or hunting a pid.
100
+
101
+ Costs, honestly:
102
+
103
+ - **An editable checkout whose commit moves without a version bump is not
104
+ noticed.** This is the development case, where the developer restarts the
105
+ daemon anyway; it is the case the version check is least needed for.
106
+ - **A second and third file in the state directory** (`daemon.disabled`,
107
+ `daemon-run.json`), both 0600 in the existing 0700 directory.
108
+ - **Pid reuse is bounded, not eliminated.** A live socket is evidence that
109
+ *some* daemon is running, not proof that the recorded pid is that one. A
110
+ crashed daemon leaves a stale run-state file, and the socket check is what
111
+ stops it being acted on.
112
+ - **`SIGINT` is handled the same as `SIGTERM`**, so a foreground `daemon run`
113
+ shuts down cleanly on Ctrl-C rather than leaving a socket file behind.
114
+
115
+ ## When to revisit
116
+
117
+ - When external supervision lands: the stop signal becomes one of several
118
+ things a supervisor does, and exit codes have to be defined first —
119
+ `run_daemon()` still returns `0` on lock contention and on a live socket,
120
+ which `KeepAlive` would read as "do not restart".
121
+ - If a wedged process is observed in the field, which is the trigger for adding
122
+ the maximum lifetime this ADR declined to build.
123
+ - If the daemon ever needs to be stopped by something that is not the same user
124
+ on the same machine, which is the assumption the signal rests on.