stacktrace-cli 0.5.2__tar.gz → 0.5.3__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 (369) hide show
  1. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/PKG-INFO +2 -2
  2. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/README.md +1 -1
  3. stacktrace_cli-0.5.3/docs/adrs/0074-a-window-selects-findings-by-when-they-happened.md +115 -0
  4. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/INDEX.md +2 -1
  5. stacktrace_cli-0.5.3/docs/releases/v0.5.3.md +70 -0
  6. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/pyproject.toml +1 -1
  7. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/__init__.py +1 -1
  8. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/analysis.py +68 -6
  9. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cloud/setup.sh +35 -8
  10. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/cli.py +20 -2
  11. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/runtime.py +21 -7
  12. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/watch.py +37 -1
  13. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_cli.py +111 -8
  14. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_runtime.py +23 -1
  15. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_render.py +27 -0
  16. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_watch.py +33 -0
  17. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_sync_detect.py +6 -1
  18. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_analysis.py +133 -0
  19. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/uv.lock +1 -1
  20. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.agents/skills/release-stacktrace/SKILL.md +0 -0
  21. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.claude/settings.json +0 -0
  22. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.claude/skills/release-stacktrace/SKILL.md +0 -0
  23. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.codex/hooks.json +0 -0
  24. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/managed-autofix.md +0 -0
  25. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/autofix.yml +0 -0
  26. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/ci.yml +0 -0
  27. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/claude.yml +0 -0
  28. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/publish-pypi.yml +0 -0
  29. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.gitignore +0 -0
  30. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/AGENTS.md +0 -0
  31. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/CLAUDE.md +0 -0
  32. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
  33. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
  34. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0003-runtime-edges.md +0 -0
  35. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0004-three-stage-detector.md +0 -0
  36. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
  37. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0006-trust-boundary-and-detection-upload.md +0 -0
  38. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
  39. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
  40. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
  41. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0011-verdict-cache.md +0 -0
  42. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
  43. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
  44. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0014-declared-project-mapping.md +0 -0
  45. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
  46. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
  47. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
  48. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
  49. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
  50. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
  51. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0021-two-command-kinds.md +0 -0
  52. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
  53. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
  54. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
  55. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
  56. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
  57. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
  58. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
  59. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0029-stage-three-returns-to-opt-in.md +0 -0
  60. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0030-a-detection-is-dated-by-the-turns-it-cites.md +0 -0
  61. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +0 -0
  62. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
  63. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0033-a-third-party-analyzer-only-by-a-flag-that-names-it.md +0 -0
  64. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0034-the-drift-grade-is-read-off-a-run-not-a-call.md +0 -0
  65. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +0 -0
  66. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
  67. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +0 -0
  68. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +0 -0
  69. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0039-send-usage-telemetry-by-default-with-an-opt-out.md +0 -0
  70. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0041-defer-native-windows-support.md +0 -0
  71. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
  72. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0043-name-the-session-agent-and-host-and-count-errors-by-class.md +0 -0
  73. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
  74. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0045-scan-every-session-and-outlive-them.md +0 -0
  75. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0046-the-finding-store-is-an-append-only-queue.md +0 -0
  76. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0047-capture-activity-during-correlation-or-lose-it.md +0 -0
  77. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0048-the-daemon-keeps-a-leveled-content-free-activity-log.md +0 -0
  78. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0056-stop-a-resident-daemon-by-record-and-signal.md +0 -0
  79. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0057-grow-the-queue-schema-additively-and-share-only-the-document.md +0 -0
  80. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0058-one-word-for-a-finding-columns-for-a-counter-and-a-push-that-does-not-wait.md +0 -0
  81. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0059-the-daemon-does-not-decide-whether-it-should-be-running.md +0 -0
  82. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0060-two-upload-postures-not-one-command-with-two-sources.md +0 -0
  83. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0061-the-drain-sends-findings-only.md +0 -0
  84. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0062-hold-the-transition-lock-across-remote-side-effects.md +0 -0
  85. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0063-underbuilt-debt-does-not-hold-the-scan-watermark.md +0 -0
  86. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0064-a-redacted-value-has-one-path-span.md +0 -0
  87. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0065-asset-external-id-resolves-by-a-ladder.md +0 -0
  88. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0066-host-owned-daemon-supervision.md +0 -0
  89. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0067-a-webhook-is-an-independent-queue-subscriber.md +0 -0
  90. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0068-webhooks-retry-the-retained-backlog.md +0 -0
  91. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0069-webhooks-deliver-what-retention-holds.md +0 -0
  92. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0070-package-the-cloud-agent-environment-bootstrap.md +0 -0
  93. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0071-render-the-allowlisted-payload-per-destination.md +0 -0
  94. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0072-host-supervisors-own-daemon-restarts.md +0 -0
  95. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0073-name-what-fired-and-which-component.md +0 -0
  96. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/HOOK-PROMPT.md +0 -0
  97. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/TEMPLATE.md +0 -0
  98. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/cutover-openaca-remote.md +0 -0
  99. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/002-session-input.md +0 -0
  100. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/003-correlation.md +0 -0
  101. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/004-detector.md +0 -0
  102. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/005-remote-sync.md +0 -0
  103. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/006-cli-composition.md +0 -0
  104. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/007-detection-upload.md +0 -0
  105. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/008-monitor.md +0 -0
  106. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/009-reasoning-jev.md +0 -0
  107. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/010-session-daemon.md +0 -0
  108. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/011-daemon-store.md +0 -0
  109. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/012-daemon-scanning.md +0 -0
  110. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/013-daemon-uploads.md +0 -0
  111. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/014-asset-identity.md +0 -0
  112. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.0.1.md +0 -0
  113. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.1.0.md +0 -0
  114. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.0.md +0 -0
  115. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.1.md +0 -0
  116. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.2.md +0 -0
  117. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.3.md +0 -0
  118. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.3.0.md +0 -0
  119. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.3.1.md +0 -0
  120. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.4.0.md +0 -0
  121. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.4.1.md +0 -0
  122. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.0.md +0 -0
  123. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.1.md +0 -0
  124. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.2.md +0 -0
  125. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/research/001-intent-drift-metric.md +0 -0
  126. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/aidr.md +0 -0
  127. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/cli-composition.md +0 -0
  128. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/correlation.md +0 -0
  129. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-scanning.md +0 -0
  130. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-store.md +0 -0
  131. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-uploads.md +0 -0
  132. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/detection-upload.md +0 -0
  133. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/detector.md +0 -0
  134. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/event-time.md +0 -0
  135. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/monitor.md +0 -0
  136. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/notification-delivery.md +0 -0
  137. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/reasoning-jev.md +0 -0
  138. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/remote-sync.md +0 -0
  139. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/session-input.md +0 -0
  140. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/scripts/git-hooks/pre-push +0 -0
  141. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/scripts/install-hooks.sh +0 -0
  142. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/__main__.py +0 -0
  143. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/build.py +0 -0
  144. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cli.py +0 -0
  145. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cloud/__init__.py +0 -0
  146. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/__init__.py +0 -0
  147. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/acquire.py +0 -0
  148. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/composition.py +0 -0
  149. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/join.py +0 -0
  150. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/observed.py +0 -0
  151. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
  152. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/project_map.py +0 -0
  153. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/record.py +0 -0
  154. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/render.py +0 -0
  155. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/__init__.py +0 -0
  156. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/activity_log.py +0 -0
  157. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/bom.py +0 -0
  158. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/client.py +0 -0
  159. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/composition.py +0 -0
  160. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/config.py +0 -0
  161. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/db.py +0 -0
  162. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/fleet.py +0 -0
  163. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/identity.py +0 -0
  164. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/jobs.py +0 -0
  165. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/lifecycle.py +0 -0
  166. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/observer.py +0 -0
  167. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/paths.py +0 -0
  168. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/presentation.py +0 -0
  169. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/protocol.py +0 -0
  170. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/scan.py +0 -0
  171. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/server.py +0 -0
  172. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/service.py +0 -0
  173. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/store.py +0 -0
  174. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/webhook.py +0 -0
  175. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/__init__.py +0 -0
  176. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/analyzer.py +0 -0
  177. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/blocked.py +0 -0
  178. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/cache.py +0 -0
  179. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/deterministic.py +0 -0
  180. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/finding.py +0 -0
  181. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/history.py +0 -0
  182. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/jev.py +0 -0
  183. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/priors.py +0 -0
  184. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
  185. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
  186. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
  187. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
  188. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
  189. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
  190. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/reasoning.py +0 -0
  191. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/render.py +0 -0
  192. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/rules.py +0 -0
  193. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/run.py +0 -0
  194. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/secrets.py +0 -0
  195. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/verdict.py +0 -0
  196. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/windows.py +0 -0
  197. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/__init__.py +0 -0
  198. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/reasoning.py +0 -0
  199. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/render.py +0 -0
  200. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/server.py +0 -0
  201. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/app.js +0 -0
  202. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
  203. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
  204. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
  205. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
  206. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/index.html +0 -0
  207. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/styles.css +0 -0
  208. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/state.py +0 -0
  209. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/verdicts.py +0 -0
  210. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/options.py +0 -0
  211. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/private_state.py +0 -0
  212. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/__init__.py +0 -0
  213. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/cli.py +0 -0
  214. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/client.py +0 -0
  215. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/config.py +0 -0
  216. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/detect_payload.py +0 -0
  217. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/identity.py +0 -0
  218. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/payload.py +0 -0
  219. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/policy.py +0 -0
  220. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/redact.py +0 -0
  221. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/spool.py +0 -0
  222. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/sync.py +0 -0
  223. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/sync_detect.py +0 -0
  224. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/upload_contract.py +0 -0
  225. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/__init__.py +0 -0
  226. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/access.py +0 -0
  227. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/index.py +0 -0
  228. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/outcome.py +0 -0
  229. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/protocols.py +0 -0
  230. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/render.py +0 -0
  231. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/__init__.py +0 -0
  232. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/cli.py +0 -0
  233. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/events.py +0 -0
  234. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/posthog.py +0 -0
  235. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/state.py +0 -0
  236. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/__init__.py +0 -0
  237. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/cli.py +0 -0
  238. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/config.py +0 -0
  239. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/__init__.py +0 -0
  240. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/agent_bom_fixture.py +0 -0
  241. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/conftest.py +0 -0
  242. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/__init__.py +0 -0
  243. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_activity_log.py +0 -0
  244. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_client.py +0 -0
  245. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_composition.py +0 -0
  246. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_config.py +0 -0
  247. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_db.py +0 -0
  248. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_detection_needs_no_subscriber.py +0 -0
  249. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_fleet.py +0 -0
  250. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_identity.py +0 -0
  251. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_jobs.py +0 -0
  252. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_lifecycle.py +0 -0
  253. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_observer.py +0 -0
  254. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_queue_reader.py +0 -0
  255. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_scan.py +0 -0
  256. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_service.py +0 -0
  257. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_socket.py +0 -0
  258. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_store.py +0 -0
  259. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_the_drained_payload_is_the_cold_one.py +0 -0
  260. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_the_stored_row_is_sufficient.py +0 -0
  261. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_webhook.py +0 -0
  262. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_wire_frames_are_bounded.py +0 -0
  263. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/detector_session_fixture.py +0 -0
  264. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/__init__.py +0 -0
  265. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/feed_harness.mjs +0 -0
  266. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_feed_model.py +0 -0
  267. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_reasoning_gates.py +0 -0
  268. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_server.py +0 -0
  269. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_site_assets.py +0 -0
  270. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_state.py +0 -0
  271. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_verdicts.py +0 -0
  272. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/__init__.py +0 -0
  273. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/helpers.py +0 -0
  274. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_cli.py +0 -0
  275. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_client.py +0 -0
  276. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_config.py +0 -0
  277. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_activity.py +0 -0
  278. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_cli.py +0 -0
  279. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_client.py +0 -0
  280. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
  281. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
  282. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_layers_compose.py +0 -0
  283. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
  284. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_redaction.py +0 -0
  285. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_spool.py +0 -0
  286. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_upload_contract.py +0 -0
  287. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_wire_payload.py +0 -0
  288. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_facade_contract.py +0 -0
  289. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_identity.py +0 -0
  290. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_no_local_path_survives_redaction.py +0 -0
  291. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_payload.py +0 -0
  292. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_policy.py +0 -0
  293. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_redact_payload.py +0 -0
  294. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_seam.py +0 -0
  295. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_spool.py +0 -0
  296. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_sync.py +0 -0
  297. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_upload_contract.py +0 -0
  298. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/__init__.py +0 -0
  299. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_activity_log.py +0 -0
  300. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_cli.py +0 -0
  301. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_events.py +0 -0
  302. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_hooks.py +0 -0
  303. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_release_gate.py +0 -0
  304. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_state.py +0 -0
  305. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_uploader.py +0 -0
  306. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_acquire.py +0 -0
  307. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_adr_cross_references_are_reciprocal.py +0 -0
  308. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_agent_instance_id.py +0 -0
  309. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_bom_shape.py +0 -0
  310. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_build_commit.py +0 -0
  311. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_call_status_conventions.py +0 -0
  312. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_cli.py +0 -0
  313. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_composition.py +0 -0
  314. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlate_orchestrate.py +0 -0
  315. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlate_render.py +0 -0
  316. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlated_session.py +0 -0
  317. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_components.py +0 -0
  318. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_event_time.py +0 -0
  319. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_subject.py +0 -0
  320. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_analyzer_contract.py +0 -0
  321. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_analyzer_live.py +0 -0
  322. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_blocked.py +0 -0
  323. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_cache.py +0 -0
  324. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_deterministic.py +0 -0
  325. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_end_to_end.py +0 -0
  326. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_finding.py +0 -0
  327. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_history.py +0 -0
  328. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_jev.py +0 -0
  329. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_priors.py +0 -0
  330. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_prompts.py +0 -0
  331. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_reasoning.py +0 -0
  332. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_render.py +0 -0
  333. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_run.py +0 -0
  334. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_secrets.py +0 -0
  335. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_verdict.py +0 -0
  336. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_why_it_stopped.py +0 -0
  337. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_windows.py +0 -0
  338. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_evidence_is_dated_not_numbered.py +0 -0
  339. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
  340. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_help_sections.py +0 -0
  341. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_join.py +0 -0
  342. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_monitor_cli.py +0 -0
  343. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
  344. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_no_test_undoes_every_patch_at_once.py +0 -0
  345. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_observed.py +0 -0
  346. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_openaca_contract.py +0 -0
  347. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_openaidr_contract.py +0 -0
  348. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_option_arity_matches_the_binary.py +0 -0
  349. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_project_map.py +0 -0
  350. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_readme_examples_are_real_output.py +0 -0
  351. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_release_gate_coverage.py +0 -0
  352. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_release_readiness.py +0 -0
  353. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_seam_boundary.py +0 -0
  354. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_session_identity_is_the_pair.py +0 -0
  355. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_access.py +0 -0
  356. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_end_to_end.py +0 -0
  357. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_protocol_typing.py +0 -0
  358. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_protocols.py +0 -0
  359. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_render.py +0 -0
  360. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_shell_separators_match_the_shell.py +0 -0
  361. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
  362. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +0 -0
  363. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
  364. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -0
  365. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_title_disclosure_invariant.py +0 -0
  366. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_untrusted_content_never_travels.py +0 -0
  367. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_verification_subcommands_match_the_binary.py +0 -0
  368. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_what_jev_is_told_is_what_was_disclosed.py +0 -0
  369. {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/webhook/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stacktrace-cli
3
- Version: 0.5.2
3
+ Version: 0.5.3
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.5.2 (openaca 0.7.0)
54
+ stacktrace 0.5.3 (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.5.2 (openaca 0.7.0)
27
+ stacktrace 0.5.3 (openaca 0.7.0)
28
28
  ```
29
29
 
30
30
  ## Quick start
@@ -0,0 +1,115 @@
1
+ ---
2
+ id: 0074
3
+ title: A window selects findings by when they happened
4
+ status: accepted
5
+ date: 2026-09-28
6
+ supersedes: null
7
+ superseded-by: null
8
+ amends: null
9
+ amended-by: null
10
+ ---
11
+
12
+ ## Context
13
+
14
+ `--since` reads as a claim about findings, and was a claim about files.
15
+ Collection selects whole transcripts by file mtime, so a session written inside
16
+ the window is collected entire and every rule walks all of its turns. What a
17
+ reader gets is therefore *every finding in every session whose file was touched
18
+ in the range*, which is not what any of the three commands say.
19
+
20
+ Found on a live monitor. The window was `06:10:11 → 06:15:11`, five minutes,
21
+ and the page showed two findings: a `credential-egress` at `06:14:12`, correctly
22
+ in range, and an `agent-blocked` at `05:56:52` — eighteen minutes before the
23
+ window began. That finding belongs to a 538-turn session started the previous
24
+ night and still being typed into, so its transcript was written a minute before
25
+ the pass and the whole session came with it. `summary.detections` was 2, so this
26
+ was the pipeline's answer and not the page's.
27
+
28
+ The reverse reading is the same mistake and does not occur: a finding's clock
29
+ comes from the turns it cites and a transcript's mtime is its last write, so
30
+ mtime is never earlier than `occurred_end`. A finding inside the window always
31
+ sits in a file the mtime rule already admitted. The rule over-admits and never
32
+ under-admits.
33
+
34
+ ## Decision
35
+
36
+ A window selects findings by **when they happened**. `analyse` reports a
37
+ finding when its own `occurred_end` reaches into the window, and `detect`,
38
+ `monitor` and `sync detect` all reach it through that one call — `cli.py`
39
+ already states the invariant that the three must not be able to disagree about
40
+ what was found.
41
+
42
+ Overlap decides, and both ends are tested. `occurred_end` against the start, so
43
+ a rule firing across a run of turns belongs in the window its last citation
44
+ reaches — what a reader asking *what is happening now* means. `occurred_start`
45
+ against the end, so a finding straddling the boundary is not dropped for
46
+ finishing late.
47
+
48
+ The upper bound is not ceremony. `analyse` takes `window_end` *before*
49
+ collection, deliberately, so a window cannot claim to cover a session that
50
+ started while the pipeline ran — and collection then reads files that may have
51
+ been appended to in between. A finding can therefore carry a clock later than
52
+ the end the caller is shown, and reporting it would contradict the heading the
53
+ window prints.
54
+
55
+ A finding carrying no clock, or one whose clock is naive and cannot be compared
56
+ against an aware bound, is reported rather than judged; hiding a real finding
57
+ over a missing field is the more expensive mistake.
58
+
59
+ **`analyse_sessions` is deliberately excluded.** The daemon passes a watermark
60
+ deciding which transcripts to re-read, not a range a person chose, and its
61
+ docstring already says it "stamps it as `window_start` and analyses the view
62
+ whole". Its findings are the record that feeds the store, the webhook and Fleet,
63
+ where narrowing by a window would lose a finding rather than hide it.
64
+
65
+ ## Alternatives considered
66
+
67
+ - **Filter while rendering the monitor.** Rejected, and this was tried first.
68
+ It gives the page an admission rule of its own, so `detect --since 5m` prints
69
+ a finding `monitor --since 5m` hides. ADR-0026 clause 2 and the monitor spec's
70
+ robustness bar — "a page that can invent, alter or suppress a detection" —
71
+ both forbid it.
72
+ - **Carry findings forward in the page, or in the snapshot beside it.** Rejected.
73
+ Retention in the browser makes the answer depend on how long a tab has been
74
+ open: a reload and an open tab disagree about the same window. Moving the
75
+ carry-forward into the watcher relocates the same violation from the renderer
76
+ to the server, since a snapshot would then carry findings the current pass did
77
+ not produce. Both were attempted on the branch that became this ADR and both
78
+ are reverted.
79
+ - **Admit sessions by their events rather than their mtime, at collection.**
80
+ Rejected as unnecessary rather than wrong. It would reach the same result by
81
+ reading more files, and the asymmetry above means there is nothing to gain:
82
+ no finding inside the window lives in a file the mtime rule excluded.
83
+ - **Exempt `sync detect`, so only the two read-only views scope.** Rejected. It
84
+ is a view of a range someone asked for, and `cli.py`'s invariant does not
85
+ allow two of the three to mean one thing and the third another. What must not
86
+ be scoped is the daemon's record, and that is a different entry point.
87
+
88
+ ## Consequences
89
+
90
+ `--since` means one thing in all three commands, and the page's window agrees
91
+ with its own heading.
92
+
93
+ The cost is that **`sync detect` uploads become window-scoped**, which is a
94
+ change to something that leaves the machine. A finding older than the window is
95
+ no longer uploaded by that command even when its session was touched inside the
96
+ range. This is the intended reading of `--since 7d` — findings from the last
97
+ seven days — but it is a narrowing, and an operator who wants everything must
98
+ say so with a wider window. The daemon's own upload path is unaffected and
99
+ remains the durable record.
100
+
101
+ One test moved with the semantics rather than around them:
102
+ `test_an_anonymous_session_does_not_take_a_known_agents_findings_with_it` builds
103
+ its session at the shared fixture moment, about a month before any realistic
104
+ window, and now declares a window wide enough to reach it. Its subject is
105
+ routing, not recency.
106
+
107
+ ## When to revisit
108
+
109
+ - **If a finding's clock ever becomes unreliable** — a reader that cannot date
110
+ its turns, an imported or replayed transcript whose mtime is rewritten — the
111
+ asymmetry this rests on ("mtime is never earlier than `occurred_end`") stops
112
+ holding, and admission would need to read events rather than trust the file.
113
+ - **If `sync detect` grows a separate "upload everything" mode**, the
114
+ window-scoping decided here stops being the whole story for that command and
115
+ the split should be recorded rather than inferred from a flag.
@@ -103,4 +103,5 @@ supersedes anything yet.
103
103
  - [0071](0071-render-the-allowlisted-payload-per-destination.md) — **Render the allowlisted payload per destination, without widening it.** Amends ADR-0067. `webhook.toml` gains `format`: `stacktrace` (the ADR-0067 JSON body) or `slack` (the same allowlisted facts as the flat `text` body Slack's Incoming Webhooks require). Absence means `stacktrace`, so existing configs are untouched. Both shapes render from `_payload()`, never the stored row, so rendering for a human reader cannot reach titles, remediation, evidence or spans — which is exactly where someone will next propose widening the allowlist. `configure` resolves the format from the URL host by default, storing the settled value rather than `auto`, because Slack accepts exactly one body shape and requiring `--format slack` only asks the user to restate the single answer that can work. An explicit `--format stacktrace` against a Slack host is still refused: Slack answers that body with HTTP 400 `no_text` and ADR-0068 removed the retry limit, so the pairing retries an identical rejected payload forever. `--format` remains for endpoints that take a Slack body without being Slack (Mattermost, Discord's `/slack` path), which no host detection can recognise. Read before adding a format, before letting a renderer read the stored row, or before treating a destination that needs more than the allowlisted facts as a format rather than an amendment to ADR-0067.
104
104
 
105
105
  - [0072](0072-host-supervisors-own-daemon-restarts.md) — **Host supervisors own daemon restarts.** `daemon run` is the foreground service; `daemon start` fully detaches and execs it once, with readiness but no restart loop. `stop` and `status` use stable controls on the existing Unix socket, while one daemon lock and one short transition lock replace PID records, supervisor locks and disable markers. Read before adding a restart loop, PID signalling, a second control socket, automatic upgrade exits, or lifecycle side effects to an agent adapter.
106
- - [0073](0073-name-what-fired-and-which-component.md) — **Name what fired and which component, and render it where Slack shows colour.** Amends ADR-0067 and ADR-0071. `title`, `subject.component_type`/`subject.name` and deduplicated `evidence[].kind` as `evidence_kinds` join the projection for every format, additively and optionally, because `drain` retries a row whose mapping raises without limit and a required new field would wedge delivery. What makes `title` safe is **not** one title per rule — `stacktrace-agent-blocked` selects among four reason titles, and a corpus holding one of them is how that weaker premise first reached an ADR — but that every title is a string literal this package owns (`_TITLES` by rule id, a per-reason literal in `blocked.py`, or a literal at the detection site, falling back to the rule id), never interpolated from session content; `tests/test_title_disclosure_invariant.py` walks the detector's AST to enforce that over the source rather than a corpus. An `unplaced` subject's name is excluded outright and every mapped name is `find_secrets`-scanned whatever its kind, since a component alias comes from the agent's own `.mcp.json`. `subject.identity`, remediation, evidence `detail` and spans stay out; every mapped string is bounded at 200 characters. For Slack the body becomes an attachment — `attachments[].color` is the only colour Slack exposes anywhere, so the bar carries severity — with no top-level `text` (it rendered the title twice), no inline `code` (Slack tints it a fixed red that would fight the bar) and mrkdwn control characters escaped, because `subject.name` reaches a parser that reads `<url|label>` as a link. The rule id and event id leave the Slack body; the event id stays on `X-Stacktrace-Event-ID` for receivers that deduplicate. Sends real component names to a third party, deliberately. The boundary rule is now *a literal from this package's own catalogue, or a short identifier naming a component or classifier, secret-scanned and bounded* — never a field written about this particular session. Read before mapping a fourth field.
106
+ - [0073](0073-name-what-fired-and-which-component.md) — **Name what fired and which component, and render it where Slack shows colour.** Amends ADR-0067 and ADR-0071. `title`, `subject.component_type`/`subject.name` and deduplicated `evidence[].kind` as `evidence_kinds` join the projection for every format, additively and optionally, because `drain` retries a row whose mapping raises without limit and a required new field would wedge delivery. What makes `title` safe is **not** one title per rule — `stacktrace-agent-blocked` selects among four reason titles, and a corpus holding one of them is how that weaker premise first reached an ADR — but that every title is a string literal this package owns (`_TITLES` by rule id, a per-reason literal in `blocked.py`, or a literal at the detection site, falling back to the rule id), never interpolated from session content; `tests/test_title_disclosure_invariant.py` walks the detector's AST to enforce that over the source rather than a corpus. An `unplaced` subject's name is excluded outright and every mapped name is `find_secrets`-scanned whatever its kind, since a component alias comes from the agent's own `.mcp.json`. `subject.identity`, remediation, evidence `detail` and spans stay out; every mapped string is bounded at 200 characters. For Slack the body becomes an attachment — `attachments[].color` is the only colour Slack exposes anywhere, so the bar carries severity — with no top-level `text` (it rendered the title twice), no inline `code` (Slack tints it a fixed red that would fight the bar) and mrkdwn control characters escaped, because `subject.name` reaches a parser that reads `<url|label>` as a link. The rule id and event id leave the Slack body; the event id stays on `X-Stacktrace-Event-ID` for receivers that deduplicate. Sends real component names to a third party, deliberately. The boundary rule is now *a literal from this package's own catalogue, or a short identifier naming a component or classifier, secret-scanned and bounded* — never a field written about this particular session. Read before mapping a fourth field.
107
+ - [0074](0074-a-window-selects-findings-by-when-they-happened.md) — **A window selects findings by when they happened.** `--since` read as a claim about findings and was a claim about files: collection selects whole transcripts by mtime, so a session written inside the window is collected entire and every rule walks all of its turns. A 538-turn session still being typed into put an 18-minute-old finding in a five-minute window. `analyse` now reports a finding when its own `occurred_end` reaches into the window, so `detect`, `monitor` and `sync detect` cannot disagree about one `--since` (`cli.py`'s invariant). Overlap decides and both ends are tested: `occurred_end` against the start, so a run of turns belongs in the window its last citation reaches, and `occurred_start` against the end, because `window_end` is taken *before* collection and a file appended in between can carry a clock past it. An undated or naive clock is reported rather than judged. Only ever narrows, and that is structural: a transcript's mtime is its last write and a finding's clock comes from the turns it cites, so mtime is never earlier than `occurred_end` and nothing in-window was ever excluded. `analyse_sessions` is excluded deliberately — the daemon's `since` is a watermark, and its findings are the record behind the store, the webhook and Fleet. The cost is stated: `sync detect` uploads become window-scoped. Read before filtering findings in a renderer, carrying them forward in a page or a snapshot, or scoping the daemon's record.
@@ -0,0 +1,70 @@
1
+ # 0.5.3 — a window means what it says
2
+
3
+ `--since` read as a claim about findings and was a claim about files.
4
+ Collection selects whole transcripts by mtime, so a session written inside the
5
+ window was collected entire and every rule walked all of its turns. A
6
+ `credential-egress` at 14:12, correctly in range, beside an `agent-blocked` at
7
+ 05:56 — eighteen minutes before the window began, carried in by a long-running
8
+ session whose file happened to be touched inside the window.
9
+
10
+ This release moves the filter from the file to the finding. `analyse` now
11
+ reports a finding when its own `occurred_end` reaches into the window, and every
12
+ surface that shows a time range — `detect`, `monitor`, `sync detect` — reaches
13
+ the pipeline through that one call. A window heading that says 06:10–06:15
14
+ no longer includes a finding from 05:56.
15
+
16
+ ## Window semantics
17
+
18
+ A finding belongs to the window when its time range overlaps the window's:
19
+ `occurred_end >= window_start` and `occurred_start <= window_end`. Both ends are
20
+ tested, not only the start — a finding straddling the boundary is not dropped
21
+ for finishing late, and a finding whose file was touched inside the window but
22
+ whose clock is entirely outside it is no longer included.
23
+
24
+ Undated findings and findings with naive clocks are unchanged: reported rather
25
+ than judged.
26
+
27
+ ## The monitor answers a range switch immediately
28
+
29
+ Clicking a different range in the monitor queued the window and returned; the
30
+ loop then slept the whole interval before the pass that would answer it. At the
31
+ default four seconds that is up to four seconds of a page doing nothing visible.
32
+ The wait between passes now ends early when a reader asks for a different range,
33
+ so the click is answered on the next pass rather than after the next sleep.
34
+
35
+ ## Cloud webhook configuration moves to the boot hook
36
+
37
+ A cloud VM's webhook was configured in the setup script, which runs once when
38
+ the environment is built. `stacktrace-cloud-setup` now seeds only the boot
39
+ hook, and the boot hook configures the webhook per-session — so a token rotation
40
+ or a new webhook URL takes effect on the next session without rebuilding the
41
+ environment.
42
+
43
+ Webhook-only sessions — those with no Fleet subscription — can now reach
44
+ configuration, where before the configuration path required a subscription
45
+ handshake that a webhook-only session could not complete.
46
+
47
+ ## Bug fixes
48
+
49
+ Supersession claimed before the pass completing it was finished, a retained
50
+ card offering controls its session could no longer serve, the renderer's own
51
+ window rule disagreeing with the pipeline's, daemon stop's progress line
52
+ serialised behind the transition lock, and daemon lifecycle events not visible
53
+ in the log.
54
+
55
+ ## Install
56
+
57
+ `uv tool install stacktrace-cli==0.5.3` or `pip install stacktrace-cli==0.5.3`.
58
+ Python 3.11 or newer; macOS and Linux.
59
+
60
+ ## Compatibility
61
+
62
+ Beta — no back-compat hedging.
63
+
64
+ - **A time window now filters by finding time, not file time.** `detect
65
+ --since`, `monitor` and `sync detect` may return fewer findings than before —
66
+ specifically, the ones whose clock was outside the window but whose file was
67
+ inside it. This is the intended behavior; the previous output was wrong.
68
+ - **The monitor responds to range switches faster.** The improvement is
69
+ user-visible but requires no action.
70
+ - **A monitor and a CLI of the same version are still required.**
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stacktrace-cli"
3
- version = "0.5.2"
3
+ version = "0.5.3"
4
4
  description = "CLI for Stacktrace — Detection and Response platform for AI Agents."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -2,7 +2,7 @@
2
2
 
3
3
  import logging as _logging
4
4
 
5
- __version__ = "0.5.2"
5
+ __version__ = "0.5.3"
6
6
 
7
7
  # Only the daemon attaches a handler (ADR-0048). Without this one, a WARNING
8
8
  # logged in any other process would reach Python's last-resort handler and
@@ -204,7 +204,7 @@ def analyse(
204
204
  root=root,
205
205
  session_ids=session_ids,
206
206
  )
207
- return _analyse_sessions(
207
+ found = _analyse_sessions(
208
208
  sessions,
209
209
  window_start=window_start,
210
210
  window_end=window_end,
@@ -220,6 +220,7 @@ def analyse(
220
220
  jev_key=jev_key,
221
221
  history=history,
222
222
  )
223
+ return scoped_to_window(found)
223
224
 
224
225
 
225
226
  def analyse_sessions(
@@ -373,6 +374,65 @@ def _correlated(
373
374
  )
374
375
 
375
376
 
377
+ def _reaches(detection: Any, window_start: datetime, window_end: datetime) -> bool:
378
+ """Whether this finding's own span overlaps the window.
379
+
380
+ Both ends are tested, because both can be crossed. `occurred_end` against
381
+ the start, so a rule firing across a run of turns belongs in the window
382
+ its last citation reaches; `occurred_start` against the end, so a finding
383
+ straddling the boundary is still reported rather than dropped for
384
+ finishing late.
385
+
386
+ The upper bound is not ceremony. `analyse` takes `window_end` *before*
387
+ collection -- deliberately, so a window cannot claim to cover a session
388
+ that started while the pipeline ran -- and collection then reads files
389
+ that may have been appended to in between. A finding can therefore carry
390
+ a clock later than the end the caller is shown, and reporting it would
391
+ contradict the heading the window prints.
392
+
393
+ A finding with no clock, or one whose clock is naive and cannot be
394
+ compared against an aware bound, is reported rather than judged: hiding a
395
+ real finding over a missing field is the more expensive mistake.
396
+ """
397
+ end = detection.occurred_end or detection.occurred_start
398
+ start = detection.occurred_start or detection.occurred_end
399
+ if end is None or start is None or end.tzinfo is None or start.tzinfo is None:
400
+ return True
401
+ return end >= window_start and start <= window_end
402
+
403
+
404
+ def scoped_to_window(analysis: Analysis) -> Analysis:
405
+ """The same analysis, reporting only the findings the window reaches.
406
+
407
+ A window selects whole transcripts by file mtime, so a session written
408
+ inside it is collected entire and every rule walks all of its turns. A
409
+ long session still being typed into therefore carried its whole history
410
+ into whatever range the caller asked for: a 538-turn session last active a
411
+ minute ago put an 18-minute-old block in a five-minute window.
412
+
413
+ Only ever narrowing, and that is not a coincidence. A finding's clock
414
+ comes from the turns it cites, and a transcript's mtime is its last write,
415
+ so mtime is never earlier than `occurred_end`. A finding inside the window
416
+ therefore always sits in a file the window already admitted -- this can
417
+ drop what the mtime rule over-admitted, and can never need to recover
418
+ anything it excluded.
419
+
420
+ Applied by `analyse`, so `detect`, `monitor` and `sync detect` cannot
421
+ disagree about one `--since` (`cli.py` states that invariant). Not by
422
+ `analyse_sessions`: the daemon passes a watermark rather than a range a
423
+ person chose, and its findings are the record that feeds the store, the
424
+ webhook and Fleet, where a window has no business narrowing anything.
425
+ """
426
+ kept = tuple(
427
+ d
428
+ for d in analysis.run.detections
429
+ if _reaches(d, analysis.window_start, analysis.window_end)
430
+ )
431
+ if len(kept) == len(analysis.run.detections):
432
+ return analysis
433
+ return replace(analysis, run=replace(analysis.run, detections=kept))
434
+
435
+
376
436
  def _unplaced(sessions: SessionView, *, window_start: datetime, window_end: datetime) -> Analysis:
377
437
  """The sessions as read, before anything has been correlated or judged.
378
438
 
@@ -498,11 +558,13 @@ def analyse_progressively(
498
558
  stage carrying fewer sessions than the preview did would make the count
499
559
  on the page go backwards.
500
560
  """
501
- return Analysis(
502
- run=run,
503
- view=replace(view, sessions=sessions),
504
- window_start=window_start,
505
- window_end=window_end,
561
+ return scoped_to_window(
562
+ Analysis(
563
+ run=run,
564
+ view=replace(view, sessions=sessions),
565
+ window_start=window_start,
566
+ window_end=window_end,
567
+ )
506
568
  )
507
569
 
508
570
  # The detector derives these from the view, and a stage that has not judged
@@ -39,9 +39,14 @@
39
39
  # from STACKTRACE_ASSET_EXTERNAL_ID in the environment
40
40
  # --root DIR treat DIR as / (for testing)
41
41
  #
42
- # The remote token and API URL are read by the boot hook at session time from
43
- # STACKTRACE_REMOTE_TOKEN and STACKTRACE_REMOTE_API_URL, not here: they belong
44
- # to the environment's variables, not to the snapshot this seeds.
42
+ # The remote token and API URL, and the Slack webhook URL, are read by the boot
43
+ # hook at session time from STACKTRACE_REMOTE_TOKEN, STACKTRACE_REMOTE_API_URL
44
+ # and STACKTRACE_SLACK_WEBHOOK_URL, not here: they belong to the environment's
45
+ # variables, not to the snapshot this seeds. The webhook in particular must be
46
+ # configured by the boot hook and not by this script, because
47
+ # `webhook configure` writes `$HOME/.config/stacktrace/webhook.toml` and this
48
+ # script runs as root — a webhook configured here would land in /root and the
49
+ # daemon, running as the session user, would read a home that has none.
45
50
  set -uo pipefail
46
51
 
47
52
  ASSET_ID="${STACKTRACE_ASSET_EXTERNAL_ID:-}"
@@ -54,7 +59,7 @@ while [ $# -gt 0 ]; do
54
59
  # the loop spins forever — a malformed invocation would hang setup.
55
60
  --asset-id) [ $# -ge 2 ] || { echo "✗ --asset-id needs a value" >&2; exit 2; }; ASSET_ID="$2"; shift 2 ;;
56
61
  --root) [ $# -ge 2 ] || { echo "✗ --root needs a value" >&2; exit 2; }; ROOT="$2"; shift 2 ;;
57
- -h|--help) sed -n '2,44p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
62
+ -h|--help) sed -n '2,49p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
58
63
  *) echo "✗ unknown argument: $1" >&2; exit 2 ;;
59
64
  esac
60
65
  done
@@ -108,14 +113,36 @@ cat > "$BOOT" <<'BOOTSCRIPT' || { echo "✗ cannot write $BOOT" >&2; exit 1; }
108
113
  set -u
109
114
  [ "${CLAUDE_CODE_REMOTE:-}" = "true" ] || exit 0
110
115
  command -v stacktrace >/dev/null 2>&1 || exit 0
111
- [ -n "${STACKTRACE_REMOTE_TOKEN:-}" ] || exit 0
116
+ # A webhook is an independent subscriber, not a Fleet feature (ADR-0067): a
117
+ # session with only STACKTRACE_SLACK_WEBHOOK_URL set still needs the daemon
118
+ # running to drain it, so the remote token gates `remote configure` alone,
119
+ # never this whole hook.
120
+ if [ -z "${STACKTRACE_REMOTE_TOKEN:-}" ] && [ -z "${STACKTRACE_SLACK_WEBHOOK_URL:-}" ]; then
121
+ exit 0
122
+ fi
112
123
 
113
124
  STATE="$HOME/.local/state/stacktrace"
114
125
  mkdir -p "$STATE" && chmod 700 "$STATE"
115
126
 
116
- stacktrace remote configure \
117
- --token "$STACKTRACE_REMOTE_TOKEN" \
118
- --api-url "${STACKTRACE_REMOTE_API_URL:-https://api.stacktrace.ai}" >/dev/null 2>&1 || true
127
+ if [ -n "${STACKTRACE_REMOTE_TOKEN:-}" ]; then
128
+ stacktrace remote configure \
129
+ --token "$STACKTRACE_REMOTE_TOKEN" \
130
+ --api-url "${STACKTRACE_REMOTE_API_URL:-https://api.stacktrace.ai}" >/dev/null 2>&1 || true
131
+ fi
132
+
133
+ # The Slack endpoint is session-time configuration like the token above, not
134
+ # part of the snapshot: written here it lands in the session user's home, which
135
+ # is the home the daemon started below reads `webhook.toml` from. Rotating the
136
+ # URL is then an environment-variable change, with no snapshot to rebuild.
137
+ # `--secret` is always passed explicitly, empty if unset: the option prompts
138
+ # when neither it nor STACKTRACE_WEBHOOK_SECRET is given, and a prompt in a hook
139
+ # with no terminal aborts the command. Configured before the daemon starts, so
140
+ # the daemon reads the endpoint at startup rather than waiting for a drain.
141
+ if [ -n "${STACKTRACE_SLACK_WEBHOOK_URL:-}" ]; then
142
+ stacktrace webhook configure \
143
+ --url "$STACKTRACE_SLACK_WEBHOOK_URL" \
144
+ --secret "${STACKTRACE_WEBHOOK_SECRET:-}" >/dev/null 2>&1 || true
145
+ fi
119
146
 
120
147
  # Streams redirected because own_std_streams only re-points fd 1/2 on log
121
148
  # rollover; without this the daemon inherits the hook's pipe to Claude Code.
@@ -140,13 +140,27 @@ def run_command(
140
140
  _reject_unobservable_kinds(config)
141
141
  except ValueError as error:
142
142
  raise click.ClickException(str(error)) from error
143
- from .lifecycle import DETACHED_ENV
143
+ from .lifecycle import DETACHED_ENV, StartOutcome
144
+
145
+ detached = os.environ.get(DETACHED_ENV) == "1"
146
+
147
+ def report_ready() -> None:
148
+ if not detached:
149
+ click.echo(
150
+ "Stacktrace daemon is running in the foreground. Press Ctrl-C to stop.",
151
+ err=True,
152
+ )
144
153
 
145
154
  status = run_daemon(
146
155
  paths,
147
156
  config,
148
- own_std_streams=os.environ.get(DETACHED_ENV) == "1",
157
+ own_std_streams=detached,
158
+ on_ready=report_ready,
149
159
  )
160
+ if status is StartOutcome.ALREADY_RUNNING:
161
+ if not detached:
162
+ click.echo("Stacktrace daemon is already running.")
163
+ return
150
164
  if status:
151
165
  raise click.exceptions.Exit(status)
152
166
 
@@ -324,6 +338,7 @@ def start() -> None:
324
338
  from .lifecycle import StartOutcome, start_daemon, transition_lock
325
339
 
326
340
  paths = RuntimePaths.from_environment()
341
+ click.echo("Starting Stacktrace daemon...", err=True)
327
342
  try:
328
343
  with transition_lock(paths.transition_lock):
329
344
  outcome = start_daemon(paths)
@@ -342,6 +357,7 @@ def stop() -> None:
342
357
  from .lifecycle import daemon_lock_held, transition_lock
343
358
 
344
359
  paths = RuntimePaths.from_environment()
360
+ click.echo("Stopping Stacktrace daemon...", err=True)
345
361
  with transition_lock(paths.transition_lock):
346
362
  if not daemon_lock_held(paths.lock):
347
363
  click.echo("Stacktrace daemon is not running.")
@@ -416,6 +432,7 @@ def flush() -> None:
416
432
  "no daemon is running, so there is no queue to drain; this endpoint "
417
433
  "uploads by scanning instead — run `stacktrace remote sync detect`"
418
434
  )
435
+ click.echo("Flushing queued findings...", err=True)
419
436
  try:
420
437
  result = request_flush(paths.socket)
421
438
  except (OSError, ProtocolError) as error:
@@ -451,6 +468,7 @@ def sync_bom() -> None:
451
468
  raise click.ClickException(
452
469
  "no daemon is running; to upload without one, run `stacktrace remote sync endpoint`"
453
470
  )
471
+ click.echo("Syncing endpoint composition...", err=True)
454
472
  try:
455
473
  result = request_sync_bom(paths.socket)
456
474
  except (OSError, ProtocolError) as error:
@@ -15,7 +15,7 @@ from contextlib import ExitStack, contextmanager, suppress
15
15
  from datetime import UTC, datetime
16
16
  from pathlib import Path
17
17
  from types import FrameType
18
- from typing import TextIO, TypeVar
18
+ from typing import Literal, TextIO, TypeVar
19
19
 
20
20
  from stacktrace_cli.build import version_label
21
21
  from stacktrace_cli.correlate.orchestrate import build_compositions
@@ -98,13 +98,18 @@ def follow_notifications(
98
98
  maximum_delay: float = 2.0,
99
99
  ) -> None:
100
100
  reported_disconnect = False
101
+ subscribed_once = False
101
102
  delay = reconnect_delay
102
103
 
103
104
  def _on_subscribed() -> None:
104
- nonlocal reported_disconnect, delay
105
+ nonlocal reported_disconnect, subscribed_once, delay
105
106
  if reported_disconnect:
106
107
  stderr.write("Stacktrace monitor reconnected\n")
107
108
  stderr.flush()
109
+ elif not subscribed_once:
110
+ stderr.write("Stacktrace monitor connected\n")
111
+ stderr.flush()
112
+ subscribed_once = True
108
113
  reported_disconnect = False
109
114
  delay = reconnect_delay
110
115
 
@@ -192,8 +197,12 @@ def _await_initial_compositions(
192
197
 
193
198
 
194
199
  def run_daemon(
195
- paths: RuntimePaths, config: DaemonConfig | None = None, *, own_std_streams: bool = False
196
- ) -> int:
200
+ paths: RuntimePaths,
201
+ config: DaemonConfig | None = None,
202
+ *,
203
+ own_std_streams: bool = False,
204
+ on_ready: Callable[[], None] = lambda: None,
205
+ ) -> int | Literal[StartOutcome.ALREADY_RUNNING]:
197
206
  """The resident process: a socket, a scan cadence, and two ways to stop.
198
207
 
199
208
  `config` is resolved by the caller (`daemon/cli.py`) so a bad value fails
@@ -203,10 +212,10 @@ def run_daemon(
203
212
  with _daemon_lock(paths.lock) as acquired:
204
213
  if not acquired:
205
214
  report_start(StartOutcome.ALREADY_RUNNING)
206
- return 0
215
+ return StartOutcome.ALREADY_RUNNING
207
216
  if daemon_is_running(paths.socket):
208
217
  report_start(StartOutcome.ALREADY_RUNNING)
209
- return 0
218
+ return StartOutcome.ALREADY_RUNNING
210
219
  # Opened only by the lock holder: two processes rotating one file
211
220
  # would each rename the other's writes away (ADR-0048).
212
221
  handler = activity_log.configure(paths.log, own_std_streams=own_std_streams)
@@ -425,7 +434,12 @@ def run_daemon(
425
434
  # that raises must not skip `compositions.close()`.
426
435
  webhook_worker.start()
427
436
  try:
428
- service.run(on_ready=lambda: report_start(StartOutcome.STARTED))
437
+
438
+ def ready() -> None:
439
+ report_start(StartOutcome.STARTED)
440
+ on_ready()
441
+
442
+ service.run(on_ready=ready)
429
443
  finally:
430
444
  webhook_worker.close()
431
445
  try:
@@ -29,6 +29,7 @@ from collections.abc import Callable, Iterable
29
29
  from dataclasses import replace
30
30
  from pathlib import Path
31
31
  from threading import Lock
32
+ from time import monotonic
32
33
  from typing import Any
33
34
 
34
35
  from stacktrace_cli.analysis import Analysis, analyse_progressively
@@ -103,6 +104,13 @@ def _last_active(analysis: Analysis, by_stem: dict[str, float]) -> dict[tuple[st
103
104
  return dated
104
105
 
105
106
 
107
+ #: How long the wait between passes sleeps at a time. Short enough that a
108
+ #: reader clicking a range is answered as if there were no wait at all, long
109
+ #: enough that an idle monitor is not spinning: ten wake-ups a second on a
110
+ #: thread that does nothing with them.
111
+ _WAKE_SLICE = 0.1
112
+
113
+
106
114
  class Watcher:
107
115
  """Runs the pipeline when the machine has changed, and publishes the result."""
108
116
 
@@ -133,6 +141,9 @@ class Watcher:
133
141
  #: because the tick reads those on another thread and a change landing
134
142
  #: mid-pass would give one snapshot two windows.
135
143
  self._pending_window: str | None = None
144
+ #: Set when a reader asks for a different range, so the wait between
145
+ #: passes ends and the new window is read now rather than next tick.
146
+ self._wake = threading.Event()
136
147
  #: Bumped by everything that says "the last answer is stale". The
137
148
  #: fingerprint alone cannot carry that: computing one is a walk of
138
149
  #: every transcript, and an invalidation arriving during the walk would
@@ -254,6 +265,9 @@ class Watcher:
254
265
  return
255
266
  self._pending_window = since
256
267
  self._invalidate_locked()
268
+ # Outside the lock: the waiting thread takes it at the top of a tick,
269
+ # and setting an event is not work worth holding a lock for.
270
+ self._wake.set()
257
271
 
258
272
  def _take_pending_window(self) -> int:
259
273
  """Apply the newest asked-for window, once, at the top of a tick.
@@ -549,10 +563,32 @@ class Watcher:
549
563
  Waits on the event rather than sleeping, so a shutdown is immediate
550
564
  instead of taking up to one interval — the difference between Ctrl-C
551
565
  feeling instant and feeling broken.
566
+
567
+ A range the reader asked for cuts the wait the same way. Without that
568
+ the click was answered by the *next* scheduled pass, so a switch cost
569
+ up to a whole interval of nothing happening before the work even
570
+ started, on top of the pass itself.
552
571
  """
553
572
  while not stop.is_set():
573
+ self._wake.clear()
554
574
  self.tick()
555
- stop.wait(self._interval)
575
+ self._wait_between_passes(stop)
576
+
577
+ def _wait_between_passes(self, stop: threading.Event) -> None:
578
+ """Sleep off the interval, ending early for a stop or a new range.
579
+
580
+ Polled in slices rather than waited on directly: `stop` belongs to the
581
+ caller — a test builds its own — so it cannot be made to signal this
582
+ watcher's wake, and waiting on the wake alone would leave a shutdown
583
+ sitting out the interval. The slice is what buys both, and it is short
584
+ enough that a click is answered as if the wait were not there.
585
+ """
586
+ deadline = monotonic() + self._interval
587
+ while not stop.is_set() and not self._wake.is_set():
588
+ remaining = deadline - monotonic()
589
+ if remaining <= 0:
590
+ return
591
+ stop.wait(min(_WAKE_SLICE, remaining))
556
592
 
557
593
  def start(self) -> tuple[threading.Thread, threading.Event]:
558
594
  stop = threading.Event()