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.
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/PKG-INFO +2 -2
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/README.md +1 -1
- stacktrace_cli-0.5.3/docs/adrs/0074-a-window-selects-findings-by-when-they-happened.md +115 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/INDEX.md +2 -1
- stacktrace_cli-0.5.3/docs/releases/v0.5.3.md +70 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/pyproject.toml +1 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/__init__.py +1 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/analysis.py +68 -6
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cloud/setup.sh +35 -8
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/cli.py +20 -2
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/runtime.py +21 -7
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/watch.py +37 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_cli.py +111 -8
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_runtime.py +23 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_render.py +27 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_watch.py +33 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_sync_detect.py +6 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_analysis.py +133 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/uv.lock +1 -1
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.agents/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.claude/settings.json +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.claude/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.codex/hooks.json +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/managed-autofix.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/autofix.yml +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/ci.yml +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/claude.yml +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.github/workflows/publish-pypi.yml +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/.gitignore +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/AGENTS.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/CLAUDE.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0003-runtime-edges.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0004-three-stage-detector.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0006-trust-boundary-and-detection-upload.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0011-verdict-cache.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0014-declared-project-mapping.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0021-two-command-kinds.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0029-stage-three-returns-to-opt-in.md +0 -0
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
- {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
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +0 -0
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0041-defer-native-windows-support.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0045-scan-every-session-and-outlive-them.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0046-the-finding-store-is-an-append-only-queue.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0047-capture-activity-during-correlation-or-lose-it.md +0 -0
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0056-stop-a-resident-daemon-by-record-and-signal.md +0 -0
- {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
- {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
- {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
- {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
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0061-the-drain-sends-findings-only.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0062-hold-the-transition-lock-across-remote-side-effects.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0063-underbuilt-debt-does-not-hold-the-scan-watermark.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0064-a-redacted-value-has-one-path-span.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0065-asset-external-id-resolves-by-a-ladder.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0066-host-owned-daemon-supervision.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0067-a-webhook-is-an-independent-queue-subscriber.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0068-webhooks-retry-the-retained-backlog.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0069-webhooks-deliver-what-retention-holds.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0070-package-the-cloud-agent-environment-bootstrap.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0071-render-the-allowlisted-payload-per-destination.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0072-host-supervisors-own-daemon-restarts.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/0073-name-what-fired-and-which-component.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/HOOK-PROMPT.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/adrs/TEMPLATE.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/cutover-openaca-remote.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/002-session-input.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/003-correlation.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/004-detector.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/005-remote-sync.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/006-cli-composition.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/007-detection-upload.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/008-monitor.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/009-reasoning-jev.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/010-session-daemon.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/011-daemon-store.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/012-daemon-scanning.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/013-daemon-uploads.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/plans/014-asset-identity.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.0.1.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.1.0.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.0.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.1.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.2.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.2.3.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.3.0.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.3.1.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.4.0.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.4.1.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.0.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.1.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/releases/v0.5.2.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/research/001-intent-drift-metric.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/aidr.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/cli-composition.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/correlation.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-scanning.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-store.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/daemon-uploads.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/detection-upload.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/detector.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/event-time.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/monitor.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/notification-delivery.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/reasoning-jev.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/remote-sync.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/docs/specs/session-input.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/scripts/git-hooks/pre-push +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/scripts/install-hooks.sh +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/__main__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/build.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/cloud/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/acquire.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/composition.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/join.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/observed.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/project_map.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/record.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/correlate/render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/activity_log.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/bom.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/client.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/composition.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/config.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/db.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/fleet.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/identity.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/jobs.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/lifecycle.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/observer.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/paths.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/presentation.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/protocol.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/scan.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/server.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/service.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/store.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/daemon/webhook.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/analyzer.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/blocked.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/cache.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/deterministic.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/finding.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/history.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/jev.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/priors.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/reasoning.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/rules.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/run.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/secrets.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/verdict.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/detector/windows.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/reasoning.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/server.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/app.js +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/index.html +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/site/styles.css +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/state.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/monitor/verdicts.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/options.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/private_state.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/client.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/config.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/detect_payload.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/identity.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/payload.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/policy.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/redact.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/spool.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/sync.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/sync_detect.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/remote/upload_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/access.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/index.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/outcome.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/protocols.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/sessions/render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/events.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/posthog.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/telemetry/state.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/src/stacktrace_cli/webhook/config.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/agent_bom_fixture.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/conftest.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_activity_log.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_client.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_composition.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_config.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_db.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_detection_needs_no_subscriber.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_fleet.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_identity.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_jobs.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_lifecycle.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_observer.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_queue_reader.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_scan.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_service.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_socket.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_store.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_the_drained_payload_is_the_cold_one.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_the_stored_row_is_sufficient.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_webhook.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/daemon/test_wire_frames_are_bounded.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/detector_session_fixture.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/feed_harness.mjs +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_feed_model.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_reasoning_gates.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_server.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_site_assets.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_state.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/monitor/test_verdicts.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/helpers.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_client.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_config.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_activity.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_client.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_layers_compose.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_redaction.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_spool.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_upload_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_detect_wire_payload.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_facade_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_identity.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_no_local_path_survives_redaction.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_payload.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_policy.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_redact_payload.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_seam.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_spool.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_sync.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/remote/test_upload_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_activity_log.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_events.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_hooks.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_release_gate.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_state.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/telemetry/test_uploader.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_acquire.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_adr_cross_references_are_reciprocal.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_agent_instance_id.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_bom_shape.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_build_commit.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_call_status_conventions.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_composition.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlate_orchestrate.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlate_render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_correlated_session.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_components.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_event_time.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detection_subject.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_analyzer_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_analyzer_live.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_blocked.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_cache.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_deterministic.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_end_to_end.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_finding.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_history.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_jev.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_priors.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_prompts.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_reasoning.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_run.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_secrets.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_verdict.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_why_it_stopped.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_detector_windows.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_evidence_is_dated_not_numbered.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_help_sections.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_join.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_monitor_cli.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_no_test_undoes_every_patch_at_once.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_observed.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_openaca_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_openaidr_contract.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_option_arity_matches_the_binary.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_project_map.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_readme_examples_are_real_output.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_release_gate_coverage.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_release_readiness.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_seam_boundary.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_session_identity_is_the_pair.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_access.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_end_to_end.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_protocol_typing.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_protocols.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_sessions_render.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_shell_separators_match_the_shell.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_title_disclosure_invariant.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_untrusted_content_never_travels.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_verification_subcommands_match_the_binary.py +0 -0
- {stacktrace_cli-0.5.2 → stacktrace_cli-0.5.3}/tests/test_what_jev_is_told_is_what_was_disclosed.py +0 -0
- {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.
|
|
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.
|
|
54
|
+
stacktrace 0.5.3 (openaca 0.7.0)
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
## 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.**
|
|
@@ -204,7 +204,7 @@ def analyse(
|
|
|
204
204
|
root=root,
|
|
205
205
|
session_ids=session_ids,
|
|
206
206
|
)
|
|
207
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
43
|
-
#
|
|
44
|
-
#
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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=
|
|
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,
|
|
196
|
-
|
|
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
|
|
215
|
+
return StartOutcome.ALREADY_RUNNING
|
|
207
216
|
if daemon_is_running(paths.socket):
|
|
208
217
|
report_start(StartOutcome.ALREADY_RUNNING)
|
|
209
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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()
|