stacktrace-cli 0.2.2__tar.gz → 0.2.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.2.2 → stacktrace_cli-0.2.3}/PKG-INFO +5 -4
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/README.md +4 -3
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0004-three-stage-detector.md +2 -0
- stacktrace_cli-0.2.3/docs/adrs/0029-stage-three-returns-to-opt-in.md +115 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/INDEX.md +1 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/007-detection-upload.md +2 -2
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/008-monitor.md +1 -1
- stacktrace_cli-0.2.3/docs/releases/v0.2.3.md +102 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/detection-upload.md +1 -1
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/detector.md +5 -5
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/pyproject.toml +1 -1
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/__init__.py +1 -1
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/cli.py +25 -17
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/acquire.py +154 -2
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/render.py +9 -12
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/index.html +1 -5
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/styles.css +0 -5
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/cli.py +3 -4
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/sync_detect.py +7 -1
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_cli.py +3 -3
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_sync_detect.py +89 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_acquire.py +299 -2
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_cli.py +57 -17
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_render.py +29 -12
- stacktrace_cli-0.2.3/tests/test_no_live_document_states_the_old_escalate_default.py +158 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_openaca_contract.py +7 -1
- stacktrace_cli-0.2.3/tests/test_stored_verdicts_are_never_gated_on_escalate.py +74 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/uv.lock +1 -1
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.agents/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.claude/settings.json +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.claude/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.codex/hooks.json +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.github/workflows/autofix.yml +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.github/workflows/ci.yml +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.github/workflows/claude.yml +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.github/workflows/publish-pypi.yml +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/.gitignore +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/AGENTS.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/CLAUDE.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0003-runtime-edges.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0006-trust-boundary-and-detection-upload.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0011-verdict-cache.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0014-declared-project-mapping.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0021-two-command-kinds.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/HOOK-PROMPT.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/adrs/TEMPLATE.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/cutover-openaca-remote.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/002-session-input.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/003-correlation.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/004-detector.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/005-remote-sync.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/plans/006-cli-composition.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/releases/v0.0.1.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/releases/v0.1.0.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/releases/v0.2.0.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/releases/v0.2.1.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/releases/v0.2.2.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/aidr.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/cli-composition.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/correlation.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/monitor.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/remote-sync.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/docs/specs/session-input.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/scripts/git-hooks/pre-push +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/scripts/install-hooks.sh +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/__main__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/analysis.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/composition.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/join.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/observed.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/project_map.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/record.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/correlate/render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/analyzer.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/cache.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/deterministic.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/finding.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/markers.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/priors.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/v1/stacktrace-injected-instruction-followed.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/prompts/v1/stacktrace-intent-drift.md +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/reasoning.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/rules.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/run.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/secrets.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/detector/verdict.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/escalate.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/server.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/app.js +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/state.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/verdicts.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/monitor/watch.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/client.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/config.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/detect_payload.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/payload.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/policy.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/redact.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/spool.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/sync.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/remote/upload_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/sessions/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/sessions/access.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/sessions/outcome.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/sessions/protocols.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/src/stacktrace_cli/sessions/render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/agent_bom_fixture.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/detector_session_fixture.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/feed_harness.mjs +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_escalation_gates.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_feed_model.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_server.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_site_assets.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_state.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_verdicts.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/monitor/test_watch.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/__init__.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/helpers.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_cli.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_client.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_config.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_activity.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_client.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_layers_compose.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_redaction.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_spool.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_upload_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_detect_wire_payload.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_facade_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_payload.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_policy.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_redact_payload.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_seam.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_spool.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_sync.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/remote/test_upload_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_agent_instance_id.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_analysis.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_bom_shape.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_call_status_conventions.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_composition.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_correlate_orchestrate.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_correlate_render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_correlated_session.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detection_components.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detection_subject.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_analyzer_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_analyzer_live.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_cache.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_deterministic.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_end_to_end.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_finding.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_liveness.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_markers.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_priors.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_reasoning.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_run.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_secrets.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_detector_verdict.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_help_sections.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_join.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_monitor_cli.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_observed.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_openaidr_contract.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_option_arity_matches_the_binary.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_project_map.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_readme_examples_are_real_output.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_release_readiness.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_seam_boundary.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_sessions_access.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_sessions_end_to_end.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_sessions_protocol_typing.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_sessions_protocols.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_sessions_render.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_shell_separators_match_the_shell.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_untrusted_content_never_travels.py +0 -0
- {stacktrace_cli-0.2.2 → stacktrace_cli-0.2.3}/tests/test_verification_subcommands_match_the_binary.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stacktrace-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.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>
|
|
@@ -126,9 +126,10 @@ because it is installed.
|
|
|
126
126
|
## What leaves your machine
|
|
127
127
|
|
|
128
128
|
Two of `detect`'s three stages run entirely locally and need no model or
|
|
129
|
-
credential
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
credential, and they are the two a bare `detect` runs. The third sends flagged
|
|
130
|
+
sessions to the agent's *own* CLI — the provider that produced the transcript,
|
|
131
|
+
never a different one — and runs only when you pass `--escalate`, capped by
|
|
132
|
+
`--budget`.
|
|
132
133
|
|
|
133
134
|
`sessions` omits prompts, tool arguments and results unless you pass
|
|
134
135
|
`--include-content`. `monitor` binds to loopback only, refuses a non-loopback
|
|
@@ -101,9 +101,10 @@ because it is installed.
|
|
|
101
101
|
## What leaves your machine
|
|
102
102
|
|
|
103
103
|
Two of `detect`'s three stages run entirely locally and need no model or
|
|
104
|
-
credential
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
credential, and they are the two a bare `detect` runs. The third sends flagged
|
|
105
|
+
sessions to the agent's *own* CLI — the provider that produced the transcript,
|
|
106
|
+
never a different one — and runs only when you pass `--escalate`, capped by
|
|
107
|
+
`--budget`.
|
|
107
108
|
|
|
108
109
|
`sessions` omits prompts, tool arguments and results unless you pass
|
|
109
110
|
`--include-content`. `monitor` binds to loopback only, refuses a non-loopback
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0029
|
|
3
|
+
title: Stage three returns to opt-in
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-09
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: 0004
|
|
9
|
+
amended-by: null
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
ADR-0004 was amended on 2026-09-03 to default `--escalate` **on** for
|
|
15
|
+
`stacktrace detect`. The reasoning was sound and the amendment said so plainly:
|
|
16
|
+
a stage nobody turns on never runs, and rules that depend on it — `injection-
|
|
17
|
+
marker` above all — can only ever be evaluated against zero verdicts. It also
|
|
18
|
+
named its own exit: *"Revisit once there is a measured escalation rate and
|
|
19
|
+
verdict yield to argue from; if the yield does not justify the spend, the
|
|
20
|
+
honest move is back to opt-in rather than a narrower cap."*
|
|
21
|
+
|
|
22
|
+
What forces the revisit now is not the yield. It is that the default was only
|
|
23
|
+
ever changed in one of the three places that carry it, and the other two — plus
|
|
24
|
+
everything we publish — kept saying the opposite:
|
|
25
|
+
|
|
26
|
+
| Surface | Default |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `run_detector()` | `escalate=False` |
|
|
29
|
+
| `stacktrace monitor` | `False` — *"a page left open is the wrong place for that to be implicit"* |
|
|
30
|
+
| `remote sync detect` | `False` (ADR-0023) |
|
|
31
|
+
| `stacktrace detect` | `True` |
|
|
32
|
+
|
|
33
|
+
The published quickstart tells a new user to run `stacktrace detect
|
|
34
|
+
--no-escalate`, annotated *"# Investigate without model escalation"* — a
|
|
35
|
+
documented first command whose only purpose is to undo the default. The site's
|
|
36
|
+
data-handling section makes the same promise in prose: *"Start with detection
|
|
37
|
+
that doesn't send session content to a model."* `docs/plans/004-detector.md`
|
|
38
|
+
still describes the stage as opt-in. One surface out of four, contradicted by
|
|
39
|
+
its own documentation, is not a default; it is a divergence.
|
|
40
|
+
|
|
41
|
+
Be honest about the evidence this ADR does **not** have. The escalation rate is
|
|
42
|
+
low — an audit on 2026-09-02 measured 3 escalations across 635 sessions, 1 on
|
|
43
|
+
evidence and 2 sampled — but that audit predates the amendment and so describes
|
|
44
|
+
behaviour under the opt-in default it is being used to restore. Verdict yield,
|
|
45
|
+
the other half of what ADR-0004 asked for, has not been measured. Stage 3 has
|
|
46
|
+
also still never run against a real analyzer: the `live_cli` test is opt-in and
|
|
47
|
+
deselected. So this is not the evidence-based revisit ADR-0004 anticipated. It
|
|
48
|
+
is a consistency and consent decision taken ahead of that evidence.
|
|
49
|
+
|
|
50
|
+
## Decision
|
|
51
|
+
|
|
52
|
+
`stacktrace detect` defaults `--escalate` off, matching `run_detector`,
|
|
53
|
+
`monitor` and `sync detect`. A bare `stacktrace detect` sends no session content
|
|
54
|
+
off the machine.
|
|
55
|
+
|
|
56
|
+
Two things travel with it. The verdict cache is no longer gated on `escalate`:
|
|
57
|
+
`run_detector` already states that a verdict already paid for is an answer this
|
|
58
|
+
run *has*, and that `escalate` governs whether new ones may be commissioned, not
|
|
59
|
+
whether old ones may be read — `cli.py` contradicted that, and flipping the
|
|
60
|
+
default without fixing it would have made every stored verdict silently
|
|
61
|
+
unreadable on the default path. And a run that declines to escalate still counts
|
|
62
|
+
and names the sessions that qualified, as `reasoning_not_requested`, so a run
|
|
63
|
+
without stage 3 does not read as a clean one.
|
|
64
|
+
|
|
65
|
+
## Alternatives considered
|
|
66
|
+
|
|
67
|
+
- **Leave it on and fix only the docs.** Rejected: the quickstart, the
|
|
68
|
+
data-handling copy and the plan all describe opt-in, and three of four code
|
|
69
|
+
surfaces implement it. Changing four things to match one is the wrong
|
|
70
|
+
direction, and the one is the surface that spends the user's money.
|
|
71
|
+
- **Keep it on until verdict yield is measured, as ADR-0004 asked.** Rejected on
|
|
72
|
+
consent rather than evidence: the measurement requires a bare `detect` to keep
|
|
73
|
+
sending transcript content to a provider by default, and the same measurement
|
|
74
|
+
is obtainable from users who opt in. The amendment accepted that cost
|
|
75
|
+
deliberately; what it did not anticipate is that we would simultaneously
|
|
76
|
+
publish the opposite promise.
|
|
77
|
+
- **A narrower cap instead of opt-in.** Rejected by ADR-0004 itself, in advance:
|
|
78
|
+
*"the honest move is back to opt-in rather than a narrower cap."*
|
|
79
|
+
- **Prompt interactively on first run.** Rejected: `detect` is scriptable and
|
|
80
|
+
runs unattended; a prompt makes the non-interactive path either hang or pick a
|
|
81
|
+
default, which is this decision again with extra machinery.
|
|
82
|
+
|
|
83
|
+
## Consequences
|
|
84
|
+
|
|
85
|
+
A bare `stacktrace detect` is now local plus the osv.dev coordinate lookup, and
|
|
86
|
+
nothing else leaves. That is the promise the site already makes, kept.
|
|
87
|
+
|
|
88
|
+
The cost is the one the amendment identified and it returns in full: stage 3
|
|
89
|
+
runs only when asked, so verdict yield and the rules that depend on it stay
|
|
90
|
+
unmeasured unless we go and measure them. That work does not disappear — it
|
|
91
|
+
moves from "collected passively from every user" to "collected deliberately",
|
|
92
|
+
and it should be done, because `injection-marker` cannot be evaluated at all
|
|
93
|
+
without it.
|
|
94
|
+
|
|
95
|
+
Watch for the stage going dark again. `reasoning_not_requested` in the reasoning
|
|
96
|
+
accounting is the signal: if it is the dominant outcome across real runs for a
|
|
97
|
+
release or two, the stage is not being reached and the rules behind it are still
|
|
98
|
+
untuned. That is the same failure the 2026-09-03 amendment reacted to, and the
|
|
99
|
+
answer will need to be something other than flipping the default a second time.
|
|
100
|
+
|
|
101
|
+
Users relying on the implicit default silently lose stage 3. `--no-escalate`
|
|
102
|
+
keeps working, so no invocation breaks; the release note carries the change.
|
|
103
|
+
|
|
104
|
+
## When to revisit
|
|
105
|
+
|
|
106
|
+
- **Verdict yield gets measured** and justifies the spend — then this is a
|
|
107
|
+
candidate to revert, but with the docs and the other three surfaces changed in
|
|
108
|
+
the same commit, which is what went wrong the first time.
|
|
109
|
+
- **`reasoning_not_requested` dominates** real runs for two releases, per above.
|
|
110
|
+
- **Stage 3 runs against a real analyzer** and the `live_cli` test stops being
|
|
111
|
+
deselected — until then the default-on path was shipping the least-validated
|
|
112
|
+
stage.
|
|
113
|
+
- **The escalation stops costing the user.** The whole argument is that the
|
|
114
|
+
stage spends someone else's quota and crosses a content boundary. A stage that
|
|
115
|
+
did neither would not need to be asked for.
|
|
@@ -61,3 +61,4 @@ supersedes anything yet.
|
|
|
61
61
|
- [0025](0025-a-denied-call-is-activity-never-an-invocation.md) — **A denied call is activity, and never an invocation.** ADR-0017 makes a declined repeat a stall while `outcome.py` keeps a denied call out of every count, so a denied-only session produced a finding with no activity and the Cloud rejected the whole run; a separate `denied` counter resolves it without either rule bending, and coverage keeps summing `invocations` alone. Read before changing what `activity[]` counts, or before folding `denied` into a coverage-like ratio.
|
|
62
62
|
- [0027](0027-a-finding-may-name-what-it-could-not-place.md) — **A finding may name the component it could not place.** Narrows ADR-0006 constraint 3 for one field: an unplaced subject carries the name the session addressed it by, never as an identity. Read before removing a name from a finding on trust-boundary grounds, or before widening this to a second field.
|
|
63
63
|
- [0028](0028-the-console-shows-what-it-placed.md) — **The console shows what it placed, and states no coverage.** Unplaceable rows, the `N/M placed` header fraction and the red on refusals are gone from `stacktrace monitor`: an unplaced row names no component, and `outcome.py`'s rule about a denied call makes reddening one the page contradicting its own counters. The snapshot contract, the upload and every rule are unchanged. Read before adding a page-side filter, before restoring a coverage ratio to the console, or if `unsanctioned-mcp-tool-use` returns.
|
|
64
|
+
- [0029](0029-stage-three-returns-to-opt-in.md) — **Stage three returns to opt-in.** Amends ADR-0004: `stacktrace detect` defaults `--escalate` off, matching `run_detector`, `monitor` and `sync detect`, all of which never changed — the 2026-09-03 amendment landed in one of four surfaces while the quickstart, the data-handling copy and `plans/004` kept promising opt-in. Taken on consistency and consent, not on the verdict yield 0004 asked for, which is still unmeasured. The verdict cache is ungated from `escalate` in the same change. Read before changing an escalation default, or before arguing stage 3 should be on again.
|
|
@@ -243,8 +243,8 @@ rewrite*.
|
|
|
243
243
|
- [x] **Step 1: Keep `sync endpoint`'s side-effect order.** Load config, refuse
|
|
244
244
|
if unconfigured, client, replay spool, **detect**, register, upload per kind.
|
|
245
245
|
Detecting before registering keeps a failed run from creating an asset.
|
|
246
|
-
- [x] **Step 2: `--escalate` defaults off
|
|
247
|
-
(ADR-0023).
|
|
246
|
+
- [x] **Step 2: `--escalate` defaults off** — a scheduled unattended run must
|
|
247
|
+
not spend provider quota or cross a content boundary implicitly (ADR-0023).
|
|
248
248
|
- [x] **Step 3: `--dry-run` prints NDJSON from the same builder.**
|
|
249
249
|
- [x] **Step 4: Cap evidence, refuse everything else.** Keep `evidence[0]` —
|
|
250
250
|
the anchor the Cloud identifies a call-scoped detection by — and order, with
|
|
@@ -177,7 +177,7 @@ def analyse(
|
|
|
177
177
|
return Analysis(run=run, view=acquired.view, window_start=window_start, window_end=window_end)
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
- [x] **Step 4: Rewire `cli.py`'s `detect`.** Replace the three calls with one `analyse(...)`; keep the `try/except ValueError → ClickException` around it and the `VerdictCache(default_directory()) if cache and
|
|
180
|
+
- [x] **Step 4: Rewire `cli.py`'s `detect`.** Replace the three calls with one `analyse(...)`; keep the `try/except ValueError → ClickException` around it and the `VerdictCache(default_directory()) if cache else None` argument — the cache is opened whenever `--cache` allows it and never gated on `escalate`, which governs whether new stage-3 work may be commissioned and not whether a verdict already paid for may be read. Keep `_parse_since` as a thin alias if anything else imports it; delete it if nothing does.
|
|
181
181
|
|
|
182
182
|
- [x] **Step 5: Rewire `sync_detect.py`'s `_collect_detect_run`.** One `analyse(...)` call; keep `SyncError` translation, keep the working-directory collection and the per-kind activity walk, and read `window_start`/`window_end`/`collection_failures` off the `Analysis` instead of computing them.
|
|
183
183
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# 0.2.3 — stage three is opt-in
|
|
2
|
+
|
|
3
|
+
`stacktrace detect` no longer sends anything session-derived off your machine
|
|
4
|
+
unless you ask it to. The third stage — the one that hands flagged sessions to
|
|
5
|
+
the agent's own CLI — is now behind `--escalate`, which is what the quickstart,
|
|
6
|
+
the data-handling page and three of the four code surfaces already said it was.
|
|
7
|
+
Alongside it, a fix worth reading if you installed with `install.sh`: `correlate`
|
|
8
|
+
was reaching whichever OpenACA your PATH happened to name rather than the one
|
|
9
|
+
this package pins.
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- **`stacktrace detect` defaults `--escalate` off.** A bare run is the two
|
|
14
|
+
stages that need no model and no credential, plus the osv.dev advisory
|
|
15
|
+
lookup, which sends package coordinates only. `--escalate` turns the third
|
|
16
|
+
stage on and `--budget` still caps it. The default had been on since
|
|
17
|
+
2026-09-03 while `run_detector`, `stacktrace monitor` and `remote sync detect`
|
|
18
|
+
all defaulted it off and the published quickstart told new users to pass
|
|
19
|
+
`--no-escalate`; one surface out of four, contradicted by its own
|
|
20
|
+
documentation, was a divergence rather than a default. ADR-0029 records the
|
|
21
|
+
decision and is explicit that it is a consent decision taken ahead of the
|
|
22
|
+
verdict-yield evidence ADR-0004 asked for, which is still unmeasured.
|
|
23
|
+
|
|
24
|
+
A run that declines to escalate is not a clean run. Sessions that qualified
|
|
25
|
+
are counted in the reasoning accounting as `reasoning_not_requested`; the
|
|
26
|
+
session ids are in `--format json`, not the text output, which aggregates
|
|
27
|
+
them under the reason.
|
|
28
|
+
|
|
29
|
+
- **A verdict you already paid for is readable on the default path.** Both
|
|
30
|
+
`detect` and `remote sync detect` built their verdict cache only when
|
|
31
|
+
escalation was on, so with the new default every stored verdict would have
|
|
32
|
+
gone unread and the same sessions would have been billed twice on the next
|
|
33
|
+
`--escalate` run. `run_detector` has always held that the flag governs whether
|
|
34
|
+
new verdicts may be commissioned, not whether old ones may be read; the two
|
|
35
|
+
call sites disagreed with it. An exhaustiveness test now covers every cache
|
|
36
|
+
construction site rather than the two that were found by hand.
|
|
37
|
+
|
|
38
|
+
- **`correlate` runs the OpenACA this package pins.** Acquisition invoked the
|
|
39
|
+
bare name `openaca`, a PATH lookup — and `uv tool install stacktrace-cli`,
|
|
40
|
+
which is what `install.sh` runs, links only the `stacktrace` entry point, so
|
|
41
|
+
the pinned OpenACA sat one directory away, reachable but unnamed. A machine
|
|
42
|
+
with a stale global copy silently scanned with the wrong version; a machine
|
|
43
|
+
with none got a bare `FileNotFoundError`. `stacktrace --version` could not
|
|
44
|
+
reveal it, because it reports the metadata inside the environment while the
|
|
45
|
+
subprocess ran something else. The console script is now located through
|
|
46
|
+
`importlib.metadata`, authenticated against the digest its own install
|
|
47
|
+
recorded, and fails closed with a legible error where ownership cannot be
|
|
48
|
+
established. Verified against four real install topologies. The ADR-0007 seam
|
|
49
|
+
is unchanged.
|
|
50
|
+
|
|
51
|
+
- **One default, stated once.** The escalation default had also drifted into
|
|
52
|
+
`docs/specs/detector.md`, `docs/specs/detection-upload.md`, test descriptions,
|
|
53
|
+
wrapped help text, and `docs/plans/008-monitor.md`, which still instructed the
|
|
54
|
+
cache gate this release removes. A test now reads the active specs, plans and
|
|
55
|
+
help text and fails on a stale wording, in any of the three names the repo
|
|
56
|
+
uses for the stage.
|
|
57
|
+
|
|
58
|
+
- **The console names the log column `Agent Session Trace`,** dropping the
|
|
59
|
+
third word from `Agent Session Log Trace` — session, log and trace were three
|
|
60
|
+
words for one thing. The one-item legend above the columns (`MCP = a link to
|
|
61
|
+
an outside service`) is gone: an MCP row already carries its category tag and
|
|
62
|
+
names the server it reached.
|
|
63
|
+
|
|
64
|
+
- **`detect` stops printing the per-reason "Could not settle" breakdown.** It
|
|
65
|
+
listed internal reason ids — `mcp_transport_unavailable` dominates on a normal
|
|
66
|
+
machine — at a reader who cannot act on them one call at a time. The unsettled
|
|
67
|
+
count stays in the summary line, `--format json` still carries every `Unknown`
|
|
68
|
+
whole with its spans and detail, and the reasoning stage still names its own
|
|
69
|
+
outcomes.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
`uv tool install stacktrace-cli==0.2.3` or `pip install stacktrace-cli==0.2.3`.
|
|
74
|
+
|
|
75
|
+
## Compatibility
|
|
76
|
+
|
|
77
|
+
**One behaviour change, and the patch version does not signal it: `stacktrace
|
|
78
|
+
detect` no longer escalates by default.** If you relied on a bare `detect`
|
|
79
|
+
reaching stage 3, pass `--escalate` to restore it. Nothing breaks — the
|
|
80
|
+
`--no-escalate` you may already be passing still means what it meant — but a
|
|
81
|
+
default run now produces no reasoning-stage findings, and sessions that would
|
|
82
|
+
have been analysed appear as `reasoning_not_requested` instead. `monitor` and
|
|
83
|
+
`remote sync detect` are unaffected; they already defaulted off.
|
|
84
|
+
|
|
85
|
+
If you had accumulated verdicts from earlier `--escalate` runs, they are served
|
|
86
|
+
again on the default path rather than ignored, so a `detect` after upgrading may
|
|
87
|
+
report reasoning findings without spending anything.
|
|
88
|
+
|
|
89
|
+
Text output changes in two places: the `Could not settle:` block is gone, and
|
|
90
|
+
the reasoning accounting's not-requested row names a flag rather than a reason
|
|
91
|
+
id. `--format json` is unchanged — no field added, removed or renamed — and the
|
|
92
|
+
upload payload is untouched, so anything parsing either sees what 0.2.2 served.
|
|
93
|
+
|
|
94
|
+
On the console, the log column is renamed and the legend line is removed. No
|
|
95
|
+
rule, finding, severity or payload field moves.
|
|
96
|
+
|
|
97
|
+
If `install.sh` put stacktrace on your machine and you had separately installed
|
|
98
|
+
or upgraded OpenACA to work around a scan failure, that workaround is no longer
|
|
99
|
+
needed; the pinned 0.6.0 is what runs now.
|
|
100
|
+
|
|
101
|
+
Still pre-alpha, with no back-compat hedging: interfaces can change between
|
|
102
|
+
releases without a deprecation window.
|
|
@@ -291,7 +291,7 @@ Detecting before registering keeps a failed run from creating an asset.
|
|
|
291
291
|
|---|---|---|
|
|
292
292
|
| `--agent-kind`, `--bom`, `--budget`, `--sample-budget`, `--cache/--no-cache`, `--project-map`, `--root` | as `stacktrace detect` | |
|
|
293
293
|
| `--since` | `7d` | |
|
|
294
|
-
| `--escalate/--no-escalate` | **`--no-escalate`** |
|
|
294
|
+
| `--escalate/--no-escalate` | **`--no-escalate`** | Stage 3 spends the developer's provider quota and crosses a boundary; unattended is the wrong place for that to be implicit |
|
|
295
295
|
| `--dry-run` | off | NDJSON via the same builder the real path uses, so the preview **is** the payload. ADR-0006 constraint 4 requires it |
|
|
296
296
|
| `--quiet`, `--allow-offline-cache` | off | as `sync endpoint` |
|
|
297
297
|
|
|
@@ -63,11 +63,11 @@ only stage that sends anything *session-derived* off the machine. Package
|
|
|
63
63
|
coordinates travel either way; prompts, arguments and results travel only when
|
|
64
64
|
escalation runs, and then to the session's own provider and no other.
|
|
65
65
|
|
|
66
|
-
**`reasoning` is
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
cap, the verdict cache
|
|
70
|
-
|
|
66
|
+
**`reasoning` is opt-in and capped** (ADR-0029). A stage that spends the
|
|
67
|
+
developer's quota and crosses that content boundary is asked for rather than
|
|
68
|
+
assumed, so it runs only under `--escalate`. Three things bound its cost when it
|
|
69
|
+
is asked for: the `--budget` cap, the verdict cache — so an unchanged session is
|
|
70
|
+
never paid for twice — and the flag itself.
|
|
71
71
|
|
|
72
72
|
**The ordering is the unit economics.** Microseconds, then milliseconds, then —
|
|
73
73
|
only for the residue neither could resolve — seconds of inference. The per-session
|
|
@@ -224,15 +224,17 @@ def sessions(
|
|
|
224
224
|
)
|
|
225
225
|
@click.option(
|
|
226
226
|
"--escalate/--no-escalate",
|
|
227
|
-
default=
|
|
227
|
+
default=False,
|
|
228
228
|
show_default=True,
|
|
229
|
-
help="Send flagged sessions to the agent's own CLI for semantic analysis.
|
|
230
|
-
"by default
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"day
|
|
234
|
-
"either way and matches advisories by
|
|
235
|
-
"the components a session invoked to
|
|
229
|
+
help="Send flagged sessions to the agent's own CLI for semantic analysis. Off "
|
|
230
|
+
"by default: it is the only stage that sends anything session-derived off "
|
|
231
|
+
"this machine, and it spends the developer's own provider quota, so a run "
|
|
232
|
+
"that does neither is the one to reach for first. --escalate turns it on "
|
|
233
|
+
"and --budget caps a noisy day. Leaving it off does not make the run "
|
|
234
|
+
"offline: correlation runs first either way and matches advisories by "
|
|
235
|
+
"sending the package coordinates of the components a session invoked to "
|
|
236
|
+
"osv.dev. Sessions that qualified are still counted, and named in "
|
|
237
|
+
"--format json, so the run says what it did not do.",
|
|
236
238
|
)
|
|
237
239
|
@click.option(
|
|
238
240
|
"--budget",
|
|
@@ -295,18 +297,20 @@ def detect(
|
|
|
295
297
|
families carry their own severity ladders, because a stalled loop and a
|
|
296
298
|
leaked credential cannot share one (ADR-0010).
|
|
297
299
|
|
|
298
|
-
Two stages need no model or credential
|
|
299
|
-
the agent's own CLI:
|
|
300
|
-
--
|
|
300
|
+
Two stages need no model or credential, and they are the two that run by
|
|
301
|
+
default. The third sends flagged sessions to the agent's own CLI:
|
|
302
|
+
--escalate turns it on, capped by --budget. Sessions that qualified for it
|
|
303
|
+
are counted either way, and named in --format json, so a run without it
|
|
304
|
+
says so rather than reading as a clean one.
|
|
301
305
|
|
|
302
306
|
That third stage is the one that sends session content off this machine:
|
|
303
307
|
the agent's own CLI hands the prompts, arguments and results a rule needs
|
|
304
308
|
to the provider it is already authenticated against (ADR-0004 -- provider
|
|
305
309
|
affinity, so a transcript goes back to the vendor that produced it, and
|
|
306
|
-
there is no fallback to any other). --
|
|
307
|
-
that send nothing.
|
|
310
|
+
there is no fallback to any other). Without --escalate the run is the two
|
|
311
|
+
stages that send nothing.
|
|
308
312
|
|
|
309
|
-
One network call happens before any of them
|
|
313
|
+
One network call happens before any of them, with or without --escalate:
|
|
310
314
|
correlation matches advisories by sending the package coordinates of the
|
|
311
315
|
components a session invoked to osv.dev. Coordinates only -- never a
|
|
312
316
|
prompt, an argument or a result.
|
|
@@ -324,9 +328,13 @@ def detect(
|
|
|
324
328
|
escalate=escalate,
|
|
325
329
|
budget=budget,
|
|
326
330
|
sample_budget=sample_budget,
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
|
|
331
|
+
# Not gated on `escalate`. A verdict already paid for is an
|
|
332
|
+
# answer this run has, and `escalate` governs whether new ones may
|
|
333
|
+
# be commissioned, not whether old ones may be read -- the rule
|
|
334
|
+
# `run_detector` states at its cache pass. Gating it here made a
|
|
335
|
+
# session read as graded or ungraded depending on a flag that
|
|
336
|
+
# spent nothing either way.
|
|
337
|
+
cache=VerdictCache(default_directory()) if cache else None,
|
|
330
338
|
)
|
|
331
339
|
except ValueError as error:
|
|
332
340
|
raise click.ClickException(str(error)) from error
|
|
@@ -16,13 +16,23 @@ answer naming a component that was never involved.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import base64
|
|
20
|
+
import hashlib
|
|
19
21
|
import json
|
|
22
|
+
import os
|
|
20
23
|
import subprocess
|
|
24
|
+
import sys
|
|
21
25
|
import tempfile
|
|
22
26
|
from collections.abc import Callable, Sequence
|
|
23
27
|
from collections.abc import Set as AbstractSet
|
|
24
28
|
from dataclasses import dataclass
|
|
25
29
|
from datetime import UTC, datetime
|
|
30
|
+
from importlib.metadata import (
|
|
31
|
+
Distribution,
|
|
32
|
+
PackageNotFoundError,
|
|
33
|
+
PackagePath,
|
|
34
|
+
distribution,
|
|
35
|
+
)
|
|
26
36
|
from pathlib import Path
|
|
27
37
|
from typing import Any
|
|
28
38
|
|
|
@@ -36,6 +46,140 @@ from stacktrace_cli.correlate.composition import (
|
|
|
36
46
|
|
|
37
47
|
_AGENT_KIND_PROPERTY = "openaca:agent_kind"
|
|
38
48
|
|
|
49
|
+
#: The name OpenACA publishes its console script under. `.exe` on Windows
|
|
50
|
+
#: because that is the launcher installers write there, and this package claims
|
|
51
|
+
#: `Operating System :: OS Independent`.
|
|
52
|
+
_SCRIPT = "openaca.exe" if os.name == "nt" else "openaca"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _openaca() -> str:
|
|
56
|
+
"""The OpenACA console script this package pins, addressed by path.
|
|
57
|
+
|
|
58
|
+
The bare string `"openaca"` is a `PATH` lookup, and `PATH` is not where the
|
|
59
|
+
pinned copy lives: `uv tool install stacktrace-cli` links only the
|
|
60
|
+
`stacktrace` entry point out of the tool environment, so the OpenACA that
|
|
61
|
+
`[project.dependencies]` resolved sits in `bin/` reachable but unnamed. A
|
|
62
|
+
bare name therefore selects whichever global, `pipx` or `--user` copy the
|
|
63
|
+
machine carries — a stale one silently does the work and its version need
|
|
64
|
+
not be the one this package depends on — or, on a machine with none,
|
|
65
|
+
nothing at all. This is the rule `docs/specs/cli-composition.md` states for
|
|
66
|
+
the mounted commands, applied to the one path that reaches OpenACA as a
|
|
67
|
+
subprocess.
|
|
68
|
+
|
|
69
|
+
**The path is the one the pinned distribution's own install recorded, not a
|
|
70
|
+
guess from this interpreter's location.** Python defines separate prefix,
|
|
71
|
+
virtual-environment, user and home installation schemes, and only the first
|
|
72
|
+
two put console scripts beside the interpreter: `pip install --user
|
|
73
|
+
stacktrace-cli` puts them under `site.USER_BASE` while `sys.executable`
|
|
74
|
+
stays the base interpreter. Deriving the path from `sys.executable` alone
|
|
75
|
+
therefore names a file that does not exist under a supported install, and
|
|
76
|
+
`detect` dies at process launch before it can build anything.
|
|
77
|
+
`importlib.metadata` answers instead: it resolves the OpenACA on *this*
|
|
78
|
+
interpreter's `sys.path` — the pinned one, by construction — and that
|
|
79
|
+
install's `RECORD` names the script file it wrote, wherever its scheme put
|
|
80
|
+
it.
|
|
81
|
+
|
|
82
|
+
**The only thing run is a file that install recorded writing, still holding
|
|
83
|
+
the contents it recorded.** Nothing is run for being named `openaca` in a
|
|
84
|
+
directory the distribution merely shares: an installation scheme pairs a
|
|
85
|
+
library directory with a scripts directory, but that scripts directory holds
|
|
86
|
+
the launchers of *every* distribution installed into the scheme and records
|
|
87
|
+
nothing about which one wrote any of them, so co-location establishes where
|
|
88
|
+
installers put scripts in general and never that this distribution put this
|
|
89
|
+
one there. Nor does a recorded path settle it on its own, because a record
|
|
90
|
+
describes what an install wrote rather than reserving where it wrote it: the
|
|
91
|
+
scripts directory stays shared afterwards, so a later install of another
|
|
92
|
+
distribution can overwrite that very file while the record still names it.
|
|
93
|
+
A stale, global, independently managed or overwritten launcher of that name
|
|
94
|
+
reintroduces exactly the version skew this addressing exists to remove, and
|
|
95
|
+
it does so invisibly — a wrong-version OpenACA can return a structurally
|
|
96
|
+
valid BOM. Where ownership cannot be established the answer is a
|
|
97
|
+
`RuntimeError` naming why, which `_build` renders as a legible CLI error.
|
|
98
|
+
|
|
99
|
+
Still the published console script, so the ADR-0007 seam is unchanged: this
|
|
100
|
+
module imports no OpenACA internals and `tests/test_seam_boundary.py` holds
|
|
101
|
+
that line.
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
installed = distribution("openaca")
|
|
105
|
+
except PackageNotFoundError as error:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"the OpenACA this package pins is not installed on "
|
|
108
|
+
f"{sys.executable}'s import path, so no {_SCRIPT!r} on this machine "
|
|
109
|
+
"can be known to be it; reinstalling stacktrace-cli restores it"
|
|
110
|
+
) from error
|
|
111
|
+
recorded = _recorded_script(installed)
|
|
112
|
+
if recorded is not None:
|
|
113
|
+
return str(recorded)
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
f"the pinned OpenACA installed at {installed.locate_file('')} records no "
|
|
116
|
+
f"{_SCRIPT!r} console script of its own that still holds the contents it "
|
|
117
|
+
"recorded; a copy found by name alone — by sharing a directory with that "
|
|
118
|
+
"install, or by having replaced a file it wrote — need not be the version "
|
|
119
|
+
"this package depends on, so none is run; reinstalling stacktrace-cli "
|
|
120
|
+
"restores it"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _recorded_script(installed: Distribution) -> Path | None:
|
|
125
|
+
"""The console script the pinned OpenACA's install recorded, if it did.
|
|
126
|
+
|
|
127
|
+
A `RECORD` entry says the distribution `[project.dependencies]` resolved
|
|
128
|
+
wrote a file of these contents at this path — which is more than a directory
|
|
129
|
+
a scheme suggested can say, and less than the path alone would suggest,
|
|
130
|
+
because the two halves age differently: the path stays shared, the contents
|
|
131
|
+
do not. Scripts live outside `site-packages` and are recorded relative to
|
|
132
|
+
it, so the entry resolves correctly under every scheme.
|
|
133
|
+
|
|
134
|
+
Absent when the install enumerated no files: `importlib.metadata` answers
|
|
135
|
+
`None` when the metadata that lists them is missing — `RECORD` for a
|
|
136
|
+
`dist-info` install — and an `egg-info` install lists the sources it was
|
|
137
|
+
built from rather than the scripts it wrote. That is a distribution that
|
|
138
|
+
cannot name its own launcher, not licence to run someone else's: the only
|
|
139
|
+
other thing available about such an install is where it sits, and a
|
|
140
|
+
directory is shared by everything installed alongside it.
|
|
141
|
+
|
|
142
|
+
Absent, too, when the file at the recorded path is not the file that was
|
|
143
|
+
recorded there. Every candidate is authenticated against its own recorded
|
|
144
|
+
digest, because the two halves of an entry answer different questions: the
|
|
145
|
+
path says where the install wrote a launcher, and the digest says what it
|
|
146
|
+
wrote. Only the second survives another install overwriting the shared
|
|
147
|
+
scripts directory afterwards.
|
|
148
|
+
"""
|
|
149
|
+
for recorded in installed.files or ():
|
|
150
|
+
if recorded.name == _SCRIPT:
|
|
151
|
+
candidate = Path(str(installed.locate_file(recorded))).resolve()
|
|
152
|
+
if _holds_recorded_contents(recorded, candidate):
|
|
153
|
+
return candidate
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _holds_recorded_contents(recorded: PackagePath, candidate: Path) -> bool:
|
|
158
|
+
"""Whether the file now at a recorded path hashes to what was recorded for it.
|
|
159
|
+
|
|
160
|
+
Fails closed on every reason the question cannot be answered — no digest,
|
|
161
|
+
a digest naming an algorithm this interpreter does not implement, an
|
|
162
|
+
unreadable file — because "we could not check" and "it matches" differ by
|
|
163
|
+
exactly the guarantee this locator exists to give, and the unchecked case
|
|
164
|
+
fails invisibly: a wrong-version OpenACA returns a structurally valid BOM.
|
|
165
|
+
The digest field is optional in a `RECORD` row, so an install can decline
|
|
166
|
+
to say what it wrote; that install cannot then be shown to own the file
|
|
167
|
+
sitting at the path it names, and a reinstall is what restores the
|
|
168
|
+
evidence.
|
|
169
|
+
|
|
170
|
+
Digests are base64url without padding, so the padding is stripped from
|
|
171
|
+
both sides rather than assumed absent from either.
|
|
172
|
+
"""
|
|
173
|
+
digest = recorded.hash
|
|
174
|
+
if digest is None:
|
|
175
|
+
return False
|
|
176
|
+
try:
|
|
177
|
+
computed = hashlib.new(digest.mode, candidate.read_bytes()).digest()
|
|
178
|
+
except (ValueError, TypeError, OSError):
|
|
179
|
+
return False
|
|
180
|
+
encoded = base64.urlsafe_b64encode(computed).decode("ascii")
|
|
181
|
+
return encoded.rstrip("=") == digest.value.rstrip("=")
|
|
182
|
+
|
|
39
183
|
|
|
40
184
|
def load_bom(path: Path) -> tuple[str, Built]:
|
|
41
185
|
"""Read a supplied Agent BOM, or say why it cannot be used.
|
|
@@ -95,7 +239,7 @@ def build_bom(
|
|
|
95
239
|
with tempfile.TemporaryDirectory() as scratch:
|
|
96
240
|
directory = Path(scratch)
|
|
97
241
|
argv = [
|
|
98
|
-
|
|
242
|
+
_openaca(),
|
|
99
243
|
"bom",
|
|
100
244
|
"endpoint",
|
|
101
245
|
"--kind",
|
|
@@ -291,8 +435,16 @@ def _scan(
|
|
|
291
435
|
array, and a family we do not read is a fact about the scan rather than a
|
|
292
436
|
fault in it.
|
|
293
437
|
"""
|
|
438
|
+
try:
|
|
439
|
+
openaca = _openaca()
|
|
440
|
+
except RuntimeError:
|
|
441
|
+
# The same answer as an unreadable response, for the same reason: with no
|
|
442
|
+
# OpenACA that can be known to be the pinned one, this scan did not run.
|
|
443
|
+
# A `--bom` run reaches here without having built anything, so this is
|
|
444
|
+
# the first place that can be discovered — and `{}` would say we looked.
|
|
445
|
+
return None
|
|
294
446
|
result = subprocess.run(
|
|
295
|
-
[
|
|
447
|
+
[openaca, "scan", "bom", "--input", str(bom_path), "--format", "json"],
|
|
296
448
|
capture_output=True,
|
|
297
449
|
text=True,
|
|
298
450
|
check=False,
|