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