stacktrace-cli 0.5.3__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stacktrace_cli-0.6.1/.claude/skills/pr-review-loop/SKILL.md +120 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/PKG-INFO +3 -3
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/README.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0006-trust-boundary-and-detection-upload.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0029-stage-three-returns-to-opt-in.md +2 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0033-a-third-party-analyzer-only-by-a-flag-that-names-it.md +2 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0039-send-usage-telemetry-by-default-with-an-opt-out.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0060-two-upload-postures-not-one-command-with-two-sources.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0065-asset-external-id-resolves-by-a-ladder.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0067-a-webhook-is-an-independent-queue-subscriber.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0068-webhooks-retry-the-retained-backlog.md +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0073-name-what-fired-and-which-component.md +1 -1
- stacktrace_cli-0.6.1/docs/adrs/0076-two-phase-detection-pipeline.md +164 -0
- stacktrace_cli-0.6.1/docs/adrs/0077-the-bom-job-wakes-once-at-startup-staggered.md +208 -0
- stacktrace_cli-0.6.1/docs/adrs/0078-a-seeded-display-prefix-replaces-the-hostname-label.md +74 -0
- stacktrace_cli-0.6.1/docs/adrs/0079-the-monitor-page-renders-findings-not-pipeline-health.md +93 -0
- stacktrace_cli-0.6.1/docs/adrs/0080-enabling-reasoning-does-not-requeue-the-past.md +103 -0
- stacktrace_cli-0.6.1/docs/adrs/0081-reasoning-activation-returns-to-explicit-opt-in.md +136 -0
- stacktrace_cli-0.6.1/docs/adrs/0082-monitor-checks-every-installed-component-by-default.md +72 -0
- stacktrace_cli-0.6.1/docs/adrs/0083-each-block-reason-is-its-own-rule.md +127 -0
- stacktrace_cli-0.6.1/docs/adrs/0084-a-recursive-deletes-evidence-is-the-rm-that-ran.md +85 -0
- stacktrace_cli-0.6.1/docs/adrs/0085-show-when-and-which-finding-slack-delivered.md +88 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/INDEX.md +14 -5
- stacktrace_cli-0.6.1/docs/releases/v0.6.0.md +120 -0
- stacktrace_cli-0.6.1/docs/releases/v0.6.1.md +108 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/daemon-uploads.md +14 -4
- stacktrace_cli-0.6.1/docs/specs/detection-policies.md +368 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/detection-upload.md +11 -7
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/detector.md +85 -20
- stacktrace_cli-0.6.1/docs/specs/monitor-inventory.md +203 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/monitor.md +169 -167
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/notification-delivery.md +21 -18
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/pyproject.toml +2 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/__init__.py +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/analysis.py +33 -31
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/cli.py +5 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/cloud/__init__.py +10 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/cloud/setup.sh +17 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/acquire.py +60 -67
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/composition.py +4 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/observed.py +62 -6
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/orchestrate.py +30 -38
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/cli.py +93 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/config.py +85 -5
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/identity.py +17 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/jobs.py +48 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/observer.py +31 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/presentation.py +34 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/runtime.py +25 -11
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/store.py +60 -15
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/webhook.py +34 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/blocked.py +12 -0
- stacktrace_cli-0.6.1/src/stacktrace_cli/detector/deterministic.py +2414 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/render.py +6 -9
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/rules.py +74 -12
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/run.py +11 -3
- stacktrace_cli-0.6.1/src/stacktrace_cli/monitor/inventory.py +495 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/reasoning.py +8 -6
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/render.py +49 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/server.py +44 -20
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/app.js +386 -369
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/index.html +26 -14
- stacktrace_cli-0.6.1/src/stacktrace_cli/monitor/site/inventory.js +554 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/styles.css +139 -51
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/state.py +45 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/watch.py +294 -129
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/identity.py +41 -12
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/payload.py +10 -5
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/redact.py +100 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/sync.py +11 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/telemetry/events.py +12 -30
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/webhook/cli.py +35 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/conftest.py +25 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_cli.py +201 -13
- stacktrace_cli-0.6.1/tests/daemon/test_config.py +195 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_jobs.py +116 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_observer.py +194 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_runtime.py +97 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_store.py +117 -7
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_the_drained_payload_is_the_cold_one.py +124 -3
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_webhook.py +99 -8
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/detector_session_fixture.py +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/feed_harness.mjs +1078 -284
- stacktrace_cli-0.6.1/tests/monitor/inventory_harness.mjs +429 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_feed_model.py +470 -226
- stacktrace_cli-0.6.1/tests/monitor/test_inventory.py +866 -0
- stacktrace_cli-0.6.1/tests/monitor/test_inventory_page.py +241 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_render.py +49 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_server.py +11 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_site_assets.py +68 -52
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_state.py +37 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_watch.py +821 -60
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_redaction.py +131 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_facade_contract.py +172 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_identity.py +62 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_events.py +24 -34
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_hooks.py +21 -10
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_acquire.py +94 -41
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_adr_cross_references_are_reciprocal.py +15 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_analysis.py +4 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_correlate_orchestrate.py +49 -3
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detection_components.py +4 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detection_event_time.py +2 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detection_subject.py +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_blocked.py +27 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_cache.py +6 -6
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_end_to_end.py +8 -5
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_finding.py +33 -7
- stacktrace_cli-0.6.1/tests/test_detector_new_deterministic.py +3803 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_priors.py +2 -2
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_render.py +21 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_verdict.py +2 -2
- stacktrace_cli-0.6.1/tests/test_detector_vulnerable_dep_invoked.py +477 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_why_it_stopped.py +46 -14
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_evidence_is_dated_not_numbered.py +6 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_monitor_cli.py +10 -14
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_readme_examples_are_real_output.py +1 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_seam_boundary.py +3 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_shell_separators_match_the_shell.py +51 -5
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +63 -1
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -39
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_title_disclosure_invariant.py +4 -4
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_untrusted_content_never_travels.py +104 -6
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_what_jev_is_told_is_what_was_disclosed.py +2 -26
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/webhook/test_cli.py +82 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/uv.lock +5 -5
- stacktrace_cli-0.5.3/src/stacktrace_cli/detector/deterministic.py +0 -506
- stacktrace_cli-0.5.3/tests/daemon/test_config.py +0 -92
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.agents/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.claude/settings.json +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.claude/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.codex/hooks.json +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.github/managed-autofix.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.github/workflows/autofix.yml +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.github/workflows/ci.yml +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.github/workflows/claude.yml +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.github/workflows/publish-pypi.yml +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/.gitignore +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/AGENTS.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/CLAUDE.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0003-runtime-edges.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0004-three-stage-detector.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0011-verdict-cache.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0014-declared-project-mapping.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0021-two-command-kinds.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0030-a-detection-is-dated-by-the-turns-it-cites.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0034-the-drift-grade-is-read-off-a-run-not-a-call.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0041-defer-native-windows-support.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0043-name-the-session-agent-and-host-and-count-errors-by-class.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0045-scan-every-session-and-outlive-them.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0046-the-finding-store-is-an-append-only-queue.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0047-capture-activity-during-correlation-or-lose-it.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0048-the-daemon-keeps-a-leveled-content-free-activity-log.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0056-stop-a-resident-daemon-by-record-and-signal.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0057-grow-the-queue-schema-additively-and-share-only-the-document.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0058-one-word-for-a-finding-columns-for-a-counter-and-a-push-that-does-not-wait.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0059-the-daemon-does-not-decide-whether-it-should-be-running.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0061-the-drain-sends-findings-only.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0062-hold-the-transition-lock-across-remote-side-effects.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0063-underbuilt-debt-does-not-hold-the-scan-watermark.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0064-a-redacted-value-has-one-path-span.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0066-host-owned-daemon-supervision.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0069-webhooks-deliver-what-retention-holds.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0070-package-the-cloud-agent-environment-bootstrap.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0071-render-the-allowlisted-payload-per-destination.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0072-host-supervisors-own-daemon-restarts.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/0074-a-window-selects-findings-by-when-they-happened.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/HOOK-PROMPT.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/adrs/TEMPLATE.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/cutover-openaca-remote.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/002-session-input.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/003-correlation.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/004-detector.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/005-remote-sync.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/006-cli-composition.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/007-detection-upload.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/008-monitor.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/009-reasoning-jev.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/010-session-daemon.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/011-daemon-store.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/012-daemon-scanning.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/013-daemon-uploads.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/plans/014-asset-identity.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.0.1.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.1.0.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.2.0.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.2.1.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.2.2.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.2.3.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.3.0.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.3.1.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.4.0.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.4.1.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.5.0.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.5.1.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.5.2.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/releases/v0.5.3.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/research/001-intent-drift-metric.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/aidr.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/cli-composition.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/correlation.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/daemon-scanning.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/daemon-store.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/event-time.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/reasoning-jev.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/remote-sync.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/docs/specs/session-input.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/scripts/git-hooks/pre-push +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/scripts/install-hooks.sh +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/__main__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/build.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/join.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/project_map.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/record.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/correlate/render.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/activity_log.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/bom.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/client.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/composition.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/db.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/fleet.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/lifecycle.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/paths.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/protocol.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/scan.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/server.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/daemon/service.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/analyzer.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/cache.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/finding.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/history.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/jev.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/priors.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/reasoning.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/secrets.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/verdict.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/detector/windows.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/monitor/verdicts.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/options.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/private_state.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/client.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/config.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/detect_payload.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/policy.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/spool.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/sync_detect.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/remote/upload_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/access.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/index.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/outcome.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/protocols.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/sessions/render.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/telemetry/cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/telemetry/posthog.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/telemetry/state.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/webhook/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/src/stacktrace_cli/webhook/config.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/agent_bom_fixture.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_activity_log.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_client.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_composition.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_db.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_detection_needs_no_subscriber.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_fleet.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_identity.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_lifecycle.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_queue_reader.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_scan.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_service.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_socket.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_the_stored_row_is_sufficient.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/daemon/test_wire_frames_are_bounded.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_reasoning_gates.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/monitor/test_verdicts.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/helpers.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_client.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_config.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_activity.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_client.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_layers_compose.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_spool.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_upload_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_detect_wire_payload.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_no_local_path_survives_redaction.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_payload.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_policy.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_redact_payload.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_seam.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_spool.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_sync.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_sync_detect.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/remote/test_upload_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_activity_log.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_release_gate.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_state.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/telemetry/test_uploader.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_agent_instance_id.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_bom_shape.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_build_commit.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_call_status_conventions.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_cli.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_composition.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_correlate_render.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_correlated_session.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_analyzer_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_analyzer_live.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_deterministic.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_history.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_jev.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_prompts.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_reasoning.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_run.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_secrets.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_detector_windows.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_help_sections.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_join.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_no_test_undoes_every_patch_at_once.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_observed.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_openaca_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_openaidr_contract.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_option_arity_matches_the_binary.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_project_map.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_release_gate_coverage.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_release_readiness.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_session_identity_is_the_pair.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_sessions_access.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_sessions_end_to_end.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_sessions_protocol_typing.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_sessions_protocols.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_sessions_render.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
- {stacktrace_cli-0.5.3 → stacktrace_cli-0.6.1}/tests/test_verification_subcommands_match_the_binary.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-review-loop
|
|
3
|
+
description: Autonomous code → commit → push → PR → review monitoring → fix → thumbsup loop. Use when the user wants end-to-end delivery of code changes through Codex review approval.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# pr-review-loop
|
|
7
|
+
|
|
8
|
+
End-to-end autonomous loop: generate code, commit, push, create or update a PR,
|
|
9
|
+
monitor Codex review, fix findings, and iterate until the PR receives a thumbsup
|
|
10
|
+
or approval with no actionable findings remaining.
|
|
11
|
+
|
|
12
|
+
**Announce at start:** "I'm using the pr-review-loop skill to deliver this change
|
|
13
|
+
through review."
|
|
14
|
+
|
|
15
|
+
## When to invoke
|
|
16
|
+
|
|
17
|
+
User says any of: "ship this", "get this to thumbsup", "run the review loop",
|
|
18
|
+
"deliver through review", or asks for code changes with instructions to
|
|
19
|
+
autonomously handle the PR review cycle.
|
|
20
|
+
|
|
21
|
+
## Inputs
|
|
22
|
+
|
|
23
|
+
1. **Branch name** — inferred from the current branch, or ask the user.
|
|
24
|
+
2. **PR number** — if a PR already exists for the branch, use it. Otherwise
|
|
25
|
+
create a new one.
|
|
26
|
+
3. **Base branch** — defaults to `main`.
|
|
27
|
+
|
|
28
|
+
## Phase 1: Code generation
|
|
29
|
+
|
|
30
|
+
1. Implement the requested changes following the repo's conventions (CLAUDE.md):
|
|
31
|
+
- TDD for business logic (red → green → refactor)
|
|
32
|
+
- Surgical changes — touch only what you must
|
|
33
|
+
- Simplicity first — no speculative abstractions
|
|
34
|
+
2. Run all four gates before committing:
|
|
35
|
+
```bash
|
|
36
|
+
uv run ruff check .
|
|
37
|
+
uv run ruff format --check .
|
|
38
|
+
uv run pyright
|
|
39
|
+
uv run pytest -q
|
|
40
|
+
```
|
|
41
|
+
3. Fix any failures. If a gate reveals a design decision, go with the
|
|
42
|
+
recommendation and log the decision.
|
|
43
|
+
|
|
44
|
+
## Phase 2: Commit, push, PR
|
|
45
|
+
|
|
46
|
+
1. Stage only the files your changes touch — never `git add -A`.
|
|
47
|
+
2. Commit with a descriptive message explaining WHY, not WHAT.
|
|
48
|
+
3. Push to remote. If rejected, stop and report it as a blocker — fetch and
|
|
49
|
+
look at what changed, but do not rebase or force-push to resolve it
|
|
50
|
+
automatically (see "Never force-push, rebase published commits" below).
|
|
51
|
+
4. If no PR exists for this branch, create one with `gh pr create`. Include a
|
|
52
|
+
summary, list of changes, and a test plan.
|
|
53
|
+
5. If a PR already exists, update its description if the scope changed.
|
|
54
|
+
|
|
55
|
+
## Phase 3: Monitor review
|
|
56
|
+
|
|
57
|
+
Poll for review status using a dynamic loop (`/loop`). On each tick:
|
|
58
|
+
|
|
59
|
+
1. Check CI status:
|
|
60
|
+
```bash
|
|
61
|
+
gh pr checks <PR_NUMBER>
|
|
62
|
+
```
|
|
63
|
+
2. Check for new review comments:
|
|
64
|
+
```bash
|
|
65
|
+
gh api repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/comments --paginate
|
|
66
|
+
gh api repos/<OWNER>/<REPO>/pulls/<PR_NUMBER>/reviews
|
|
67
|
+
```
|
|
68
|
+
3. If CI fails, read the logs and fix the issue. Push a new commit.
|
|
69
|
+
4. If Codex or a human reviewer left comments:
|
|
70
|
+
- Read each comment thread.
|
|
71
|
+
- For P0/P1/P2 findings: fix them. Push a new commit.
|
|
72
|
+
- For P3 findings: defer unless the user approved them.
|
|
73
|
+
- If you disagree with a finding: reply on the PR explaining why,
|
|
74
|
+
with evidence. Do not silently skip it.
|
|
75
|
+
5. If a reviewer requested changes, address them and re-request review.
|
|
76
|
+
|
|
77
|
+
### Decisions during review
|
|
78
|
+
|
|
79
|
+
- **Make autonomous decisions** for: code style fixes, test additions,
|
|
80
|
+
lint/format fixes, obvious bug fixes, naming improvements.
|
|
81
|
+
- **Log but proceed** for: architecture choices between reasonable alternatives,
|
|
82
|
+
threshold values, which invariant test to extend.
|
|
83
|
+
- **Stop and ask** for: design-level questions flagged for the user, scope
|
|
84
|
+
changes that would significantly alter the PR, removing existing tests or
|
|
85
|
+
changing public interfaces.
|
|
86
|
+
|
|
87
|
+
### Stopping conditions
|
|
88
|
+
|
|
89
|
+
- **Success:** CI passes, the latest head has been reviewed, and no actionable
|
|
90
|
+
P0/P1/P2 findings remain. Report the result.
|
|
91
|
+
- **Cap reached:** After 7 automated fix rounds without a human corrective
|
|
92
|
+
commit, stop and post a message in the PR (per CLAUDE.md review rules).
|
|
93
|
+
- **Blocked:** A finding requires human judgment (design decision, scope change).
|
|
94
|
+
Post a summary comment and notify the user.
|
|
95
|
+
|
|
96
|
+
## Phase 4: Summary
|
|
97
|
+
|
|
98
|
+
When the loop exits, report:
|
|
99
|
+
1. Total commits pushed.
|
|
100
|
+
2. Review rounds completed.
|
|
101
|
+
3. Decisions made autonomously (with brief justification for each).
|
|
102
|
+
4. Any remaining P3 suggestions that were deferred.
|
|
103
|
+
5. Final PR status (approved / changes requested / commented).
|
|
104
|
+
|
|
105
|
+
## Conventions
|
|
106
|
+
|
|
107
|
+
- Never force-push, rebase published commits, or skip hooks.
|
|
108
|
+
- Every fix is a new commit, not an amend.
|
|
109
|
+
- Batch related fixes into one commit when possible.
|
|
110
|
+
- Run all four gates before every push.
|
|
111
|
+
- Count fix rounds per CLAUDE.md: 7 rounds without a human corrective commit
|
|
112
|
+
triggers the cap.
|
|
113
|
+
|
|
114
|
+
## What this skill does NOT do
|
|
115
|
+
|
|
116
|
+
- **Doesn't merge the PR.** That's always a human action.
|
|
117
|
+
- **Doesn't approve its own PR.** It can only push fixes and respond to review.
|
|
118
|
+
- **Doesn't modify CI/CD, branch protection, or permissions.**
|
|
119
|
+
- **Doesn't override human reviewer decisions.** If a human says "don't fix
|
|
120
|
+
this", it stays unfixed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stacktrace-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: CLI for Stacktrace — Detection and Response platform for AI Agents.
|
|
5
5
|
Project-URL: Homepage, https://stacktrace.ai
|
|
6
6
|
Author-email: "Stacktrace AI, Inc" <founders@stacktrace.ai>
|
|
@@ -21,7 +21,7 @@ Requires-Python: >=3.11
|
|
|
21
21
|
Requires-Dist: click>=8.1
|
|
22
22
|
Requires-Dist: httpx<1.0.dev0,>=0.28.1
|
|
23
23
|
Requires-Dist: openaca==0.7.0
|
|
24
|
-
Requires-Dist: openaidr==0.3.
|
|
24
|
+
Requires-Dist: openaidr==0.3.2
|
|
25
25
|
Requires-Dist: posthog<8,>=7.0
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
@@ -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.6.1 (openaca 0.7.0)
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
## Quick start
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: 0033
|
|
3
3
|
title: A third-party analyzer is reached only by a flag that names it, and its output is numbers
|
|
4
|
-
status:
|
|
4
|
+
status: superseded
|
|
5
5
|
date: 2026-09-17
|
|
6
6
|
supersedes: null
|
|
7
|
-
superseded-by:
|
|
7
|
+
superseded-by: 0076
|
|
8
8
|
amends: 0004, 0011
|
|
9
9
|
amended-by: 0034, 0035
|
|
10
10
|
---
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0076
|
|
3
|
+
title: Collapse detector to two phases with conditional reasoning
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-27
|
|
6
|
+
supersedes: 0029, 0033
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: 0035
|
|
9
|
+
amended-by: 0081
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
The detector has three stages: deterministic (2 rules), priors (0 rules — all
|
|
15
|
+
five withdrawn), and reasoning (cache-only — daemon hardcodes
|
|
16
|
+
`reasoning=False`). Stage 2 contains no detection logic, only routing and two
|
|
17
|
+
data-quality metrics. The daemon never activates new reasoning. Adding new
|
|
18
|
+
rules (10 total) requires cleaning up this structure.
|
|
19
|
+
|
|
20
|
+
Three problems force the change now:
|
|
21
|
+
|
|
22
|
+
1. **Stage 2 is empty.** `run_priors` has no rules. What remains is
|
|
23
|
+
`low_coverage` (a data-quality metric), `_missing_mcp_transport` (same),
|
|
24
|
+
and routing logic that builds `ReasoningRequest`s. None of that is
|
|
25
|
+
detection.
|
|
26
|
+
|
|
27
|
+
2. **The daemon never reasons.** `analyse_batch()` hardcodes
|
|
28
|
+
`reasoning=False`. New reasoning rules (`stacktrace-infra-unsafe-action`,
|
|
29
|
+
`stacktrace-unrequested-publication`) will never fire in the background.
|
|
30
|
+
|
|
31
|
+
3. **Reasoning blocks the tick.** A 1–3s Jev call per session, run
|
|
32
|
+
synchronously in a 5s tick, blocks the daemon.
|
|
33
|
+
|
|
34
|
+
## Decision
|
|
35
|
+
|
|
36
|
+
Collapse to two phases and one orchestrator. Phase 1 is deterministic (no
|
|
37
|
+
model, no vendor boundary — predominantly μs, with the exception of
|
|
38
|
+
`stacktrace-vulnerable-dep-invoked` which queries cached advisory data and degrades
|
|
39
|
+
to async enrichment on a cold cache). Phase 2 is reasoning (seconds,
|
|
40
|
+
requires `TYPESAFE_API_KEY`). The orchestrator sits between them and
|
|
41
|
+
builds the reasoning queue from gate trips, solo routes, and the
|
|
42
|
+
EVERY_SESSION route.
|
|
43
|
+
|
|
44
|
+
### Phase collapse
|
|
45
|
+
|
|
46
|
+
Stage 2's remnants move:
|
|
47
|
+
|
|
48
|
+
| Function | Destination |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `low_coverage` | Session-level metric (not a rule) |
|
|
51
|
+
| `_missing_mcp_transport` | Per-rule coverage metadata on the analysis result |
|
|
52
|
+
| Routing (`_solo_routes`, `ReasoningRequest`) | Orchestrator inside `run_detector` |
|
|
53
|
+
|
|
54
|
+
### Conditional reasoning activation
|
|
55
|
+
|
|
56
|
+
`TYPESAFE_API_KEY` in the environment is the only switch. No `--reasoning`
|
|
57
|
+
flag, no `--analyzer jev`. If the key is absent, `reasoning=False` — no
|
|
58
|
+
model, no cost, no vendor boundary. If present, Phase 2 runs only when
|
|
59
|
+
Phase 1 produces a non-empty reasoning queue.
|
|
60
|
+
|
|
61
|
+
Budget: `DEFAULT_DAEMON_BUDGET = 50`. It caps *sessions* dispatched to
|
|
62
|
+
Phase 2 per tick, not Jev calls — `stacktrace-deceptive-completion` alone
|
|
63
|
+
issues one gate call plus one call per verification call with a covering
|
|
64
|
+
claim, so a session's actual cost scales with its checks. At $0.0006/session
|
|
65
|
+
(one gate call) and typical <20 sessions/tick, normal operation is nowhere
|
|
66
|
+
near the cap; $0.03/tick is that single-call estimate at the budget ceiling,
|
|
67
|
+
not a hard spend cap — a tick of sessions with many verification calls can
|
|
68
|
+
exceed it.
|
|
69
|
+
|
|
70
|
+
### Async Phase 2
|
|
71
|
+
|
|
72
|
+
Phase 1 runs synchronously in the tick. Phase 2 dispatches to
|
|
73
|
+
`ThreadPoolExecutor(_WORKERS=4)` and returns immediately. The next tick
|
|
74
|
+
does not wait. When a verdict arrives, findings are recorded and
|
|
75
|
+
`on_commit` wakes the fleet/webhook jobs.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
tick()
|
|
79
|
+
├─ Synchronous (μs–ms)
|
|
80
|
+
│ ├─ enumerate + collect + correlate
|
|
81
|
+
│ ├─ Phase 1: deterministic → findings recorded immediately
|
|
82
|
+
│ ├─ orchestrator → reasoning queue
|
|
83
|
+
│ └─ record(findings, unknowns, pending=queue)
|
|
84
|
+
│
|
|
85
|
+
├─ Asynchronous (ThreadPoolExecutor, background)
|
|
86
|
+
│ └─ for each ReasoningRequest:
|
|
87
|
+
│ build windows → Jev call → evaluate → store.record_reasoning()
|
|
88
|
+
│
|
|
89
|
+
└─ Scheduler.tick()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Rule placement
|
|
93
|
+
|
|
94
|
+
| Rule | Phase | Rationale |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `stacktrace-credential-egress` | 1 | Regex on call arguments |
|
|
97
|
+
| `stacktrace-agent-blocked` | 1 | Refused-call channel match |
|
|
98
|
+
| `stacktrace-destructive-action` | 1 | Path resolution on delete commands |
|
|
99
|
+
| `stacktrace-credential-persisted` | 1 | Secret match on a Write/Edit call's own content |
|
|
100
|
+
| `stacktrace-mcp-transient-failure` | 1 | MCP call sequence pattern |
|
|
101
|
+
| `stacktrace-tool-retry-storm` | 1 | Counter with reset |
|
|
102
|
+
| `stacktrace-vulnerable-dep-invoked` | 1 | Package list × osv.dev |
|
|
103
|
+
| `stacktrace-deceptive-completion` | 2 | Gate (Jev) + per-call nouls. EVERY_SESSION route. |
|
|
104
|
+
| `stacktrace-infra-unsafe-action` | 1 gate → 2 | Gate: mutating command. Model: beyond_request + shared_target. |
|
|
105
|
+
| `stacktrace-unrequested-publication` | 1 gate → 2 | Gate: publication verb. Model: beyond_request. |
|
|
106
|
+
|
|
107
|
+
## Alternatives considered
|
|
108
|
+
|
|
109
|
+
- **Keep three stages, add rules to stage 2.** Rejected because stage 2 has
|
|
110
|
+
no rules and its abstraction boundary is accidental — it exists because the
|
|
111
|
+
function signature existed, not because the domain has three kinds of work.
|
|
112
|
+
|
|
113
|
+
- **Separate `--reasoning` and `--analyzer` flags.** Rejected because the
|
|
114
|
+
consent decision is setting `TYPESAFE_API_KEY` — the operator named a
|
|
115
|
+
vendor by placing its key. Two flags that must be typed together, where one
|
|
116
|
+
is meaningless without the other, is ceremony that protects nothing.
|
|
117
|
+
|
|
118
|
+
- **Synchronous reasoning in the tick with a short budget.** Rejected because
|
|
119
|
+
even a budget of 3 blocks the tick for 3–9 seconds. The daemon goes deaf,
|
|
120
|
+
and the tick interval (5s) is shorter than a single Jev call.
|
|
121
|
+
|
|
122
|
+
## Consequences
|
|
123
|
+
|
|
124
|
+
- 7 of 10 rules fire on every tick with no model dependency.
|
|
125
|
+
- Reasoning activates automatically when a key is present and a gate trips.
|
|
126
|
+
- The tick never blocks on inference.
|
|
127
|
+
- `ThreadPoolExecutor(4)` caps Jev API pressure at 4 concurrent calls.
|
|
128
|
+
|
|
129
|
+
**Costs:**
|
|
130
|
+
|
|
131
|
+
- Async Phase 2 requires a daemon-owned executor with these invariants:
|
|
132
|
+
(1) bounded in-flight work — cap queued futures, not just the budget;
|
|
133
|
+
(2) in-flight deduplication — a session already pending reasoning is not
|
|
134
|
+
re-queued; (3) revision-aware commits — a reasoning result is committed
|
|
135
|
+
only if its session fingerprint matches the current analysis, so a stale
|
|
136
|
+
result from a slow Jev call never overwrites a newer deterministic pass;
|
|
137
|
+
(4) retry on transient Jev failure, with backoff, capped at 3 attempts;
|
|
138
|
+
(5) on daemon restart, pending reasoning is re-derived from Phase 1 on
|
|
139
|
+
the next tick rather than persisted across restarts;
|
|
140
|
+
(6) on daemon stop or upgrade, cancel queued futures and observe
|
|
141
|
+
cancellation between per-window requests in active workers — the executor
|
|
142
|
+
must not outlive the daemon's stop deadline (600s), so shutdown joins only
|
|
143
|
+
the work already in flight, never the full queue.
|
|
144
|
+
- New rules send data the original consent did not envision (shell commands,
|
|
145
|
+
user requests). Documentation should describe what each rule sends. This
|
|
146
|
+
ADR amends the consent model established in ADR-0033; key-as-consent is a
|
|
147
|
+
product decision that should be documented as such. Cached verdicts from
|
|
148
|
+
prior runs remain readable when the key is present, independent of whether
|
|
149
|
+
new reasoning is commissioned — this preserves the `--analyzer jev`
|
|
150
|
+
read-only behavior. Cached Jev verdicts remain readable without a key:
|
|
151
|
+
the cache is keyed by analyzer identity and prompt version, not by the
|
|
152
|
+
presence of a runtime credential, so removing or rotating
|
|
153
|
+
`TYPESAFE_API_KEY` stops commissioning new work but does not discard
|
|
154
|
+
already-paid findings.
|
|
155
|
+
|
|
156
|
+
## When to revisit
|
|
157
|
+
|
|
158
|
+
- If a second reasoning provider is added (Jev is the only one today), the
|
|
159
|
+
key-as-consent model needs per-provider consent.
|
|
160
|
+
- If Phase 2 latency exceeds 10s per session, the ThreadPoolExecutor design
|
|
161
|
+
may need a persistent queue with retry.
|
|
162
|
+
- If cross-rule incident grouping becomes urgent (credentials written,
|
|
163
|
+
committed, and pushed = three findings for one mistake), the orchestrator
|
|
164
|
+
needs a post-Phase-2 deduplication pass.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0077
|
|
3
|
+
title: The BOM job wakes once at startup, staggered
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-30
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: 0060
|
|
9
|
+
amended-by: null
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
ADR-0060 clause 2 justifies `stacktrace daemon sync-bom` this way: "the drain
|
|
15
|
+
at least has a wake path, and the BOM job has none." That was true when
|
|
16
|
+
ADR-0060 was accepted. This PR gives the BOM job the same one-time startup
|
|
17
|
+
wake `fleet`/`webhook` already had — otherwise a freshly started daemon sits on
|
|
18
|
+
a machine's inventory for up to `--bom-interval` (6h default) plus a
|
|
19
|
+
hostname-derived stagger before its first upload, unlike the other two jobs.
|
|
20
|
+
ADR-0060's sentence is now the one thing in this repository's design contract
|
|
21
|
+
that says the opposite of what the code does.
|
|
22
|
+
|
|
23
|
+
The first attempt at the wake reset `Job._due_at` to `now + interval`
|
|
24
|
+
unconditionally on every run. Codex review caught that this loses the
|
|
25
|
+
hostname stagger `stagger()` computes not just on the first run but forever
|
|
26
|
+
after: every host provisioned from one script converges onto the same
|
|
27
|
+
recurring upload instant, which is the exact fleet-wide burst `stagger()`
|
|
28
|
+
exists to prevent (`docs/specs/daemon-uploads.md`'s "Stagger machines" row).
|
|
29
|
+
|
|
30
|
+
The second attempt preserved the stagger only when `_due_at > now` at the
|
|
31
|
+
moment `run_if_due` executed — reasoning that a run "arrived early" only if
|
|
32
|
+
its deadline hadn't passed yet. A further Codex pass caught that this breaks
|
|
33
|
+
too: `DaemonService.tick()` runs `scanner.run_once()` before `jobs.tick()`, so
|
|
34
|
+
a slow initial scan can push `now` past a low-offset host's `_due_at` before
|
|
35
|
+
the startup-woken `bom.run_if_due()` ever executes, even though that run is
|
|
36
|
+
still the job's first. Comparing `_due_at` to `now` cannot distinguish "ran
|
|
37
|
+
early" from "ran late" — it can only be answered by whether this is the job's
|
|
38
|
+
first run at all.
|
|
39
|
+
|
|
40
|
+
A third Codex pass caught that a single `+ interval` step still isn't enough:
|
|
41
|
+
with a short `--bom-interval`, a delay of more than one interval left the
|
|
42
|
+
preserved deadline in the past, so the job went due again immediately and
|
|
43
|
+
that second run — no longer the first — re-anchored on `now` via the normal
|
|
44
|
+
path anyway. A fourth caught that even advancing by whole intervals wasn't
|
|
45
|
+
enough on its own, because the deadline was computed from the clock read
|
|
46
|
+
*before* `self.run()` — the BOM upload itself can eat into whatever margin
|
|
47
|
+
that pre-run reading assumed was safe, on the same short-interval hosts the
|
|
48
|
+
third pass was about. A fifth caught the last boundary case: rounding the
|
|
49
|
+
delay up with `ceil` leaves the deadline exactly equal to the post-run clock
|
|
50
|
+
when the delay lands on an exact interval multiple, which `_is_due` still
|
|
51
|
+
reads as due.
|
|
52
|
+
|
|
53
|
+
A sixth and seventh Codex pass moved the goalposts again, but this time both
|
|
54
|
+
findings were instances of the same thing: `--bom-interval` (and every other
|
|
55
|
+
daemon interval — `parse_duration`, `config.py`) validated only a maximum,
|
|
56
|
+
never a minimum. A sixth pass constructed an interval small enough (the
|
|
57
|
+
smallest positive `float` at all) that the phase arithmetic's division
|
|
58
|
+
overflowed to infinity, raising `OverflowError` out of `run_if_due` and
|
|
59
|
+
crashing the scheduler loop. Patched locally at first — a `try`/`except`
|
|
60
|
+
around the division, falling back to the plain `after + interval` deadline —
|
|
61
|
+
a seventh pass immediately found the sibling case one `try`/`except` does not
|
|
62
|
+
cover: an interval too small relative to a long-uptime host's `monotonic()`
|
|
63
|
+
reading to survive floating-point addition at all (`now + interval == now`),
|
|
64
|
+
which does not raise, so the `except` never fires, and the job re-runs on
|
|
65
|
+
every tick forever. Two Codex passes finding two failure modes of the same
|
|
66
|
+
missing input check, back to back, is the shape CLAUDE.md names directly:
|
|
67
|
+
"a second finding of the same class means the first fix was at the wrong
|
|
68
|
+
level." The fix moved to the boundary instead of the arithmetic:
|
|
69
|
+
`parse_duration` now refuses any positive duration under one second (closing
|
|
70
|
+
both repro cases directly, `5e-324s` and `1e-20s` alike), for every daemon
|
|
71
|
+
interval it parses, not only the BOM job's. `Job`'s phase arithmetic no
|
|
72
|
+
longer defends against a value that should never have reached it, and the
|
|
73
|
+
`try`/`except` this replaced is removed along with the test that pinned it —
|
|
74
|
+
CLAUDE.md's own bar for removing a test that protected a behavior which was
|
|
75
|
+
"intentionally changed or removed": the behavior here (surviving a
|
|
76
|
+
sub-one-second interval inside `Job`) is removed, not merely relocated, since
|
|
77
|
+
no caller can construct one through the validated path anymore.
|
|
78
|
+
|
|
79
|
+
## Decision
|
|
80
|
+
|
|
81
|
+
The BOM job is woken once, unconditionally, right after it's constructed —
|
|
82
|
+
the same as `fleet`/`webhook`. `Job` gains a `preserve_stagger` flag, set only
|
|
83
|
+
for the BOM job: on that job's first-ever run (`_ran_at is None`, checked
|
|
84
|
+
before this run sets it), the pre-run `_due_at` assignment (`now + interval`)
|
|
85
|
+
is skipped, and the next due time is instead computed once `run()` returns
|
|
86
|
+
(success or failure alike, from a `finally` block) as the original staggered
|
|
87
|
+
slot advanced by as many whole intervals as the delay — measured against the
|
|
88
|
+
clock *at that point*, not before the run — needs to land at or after it:
|
|
89
|
+
`_due_at + interval * max(1, floor((after - _due_at) / interval) + 1)` —
|
|
90
|
+
`floor(x) + 1`, not `ceil(x)`, since `ceil` leaves the result exactly equal to
|
|
91
|
+
`after` when the delay lands on an exact interval multiple, and `floor(x) + 1`
|
|
92
|
+
is strictly greater than `x` for every real `x` with no boundary case left
|
|
93
|
+
over. That division assumes `interval` is representable and well away from
|
|
94
|
+
zero; `parse_duration` (`config.py`) now enforces a one-second floor on every
|
|
95
|
+
positive daemon interval it parses (`0` still means disabled), which is the
|
|
96
|
+
only thing standing between this arithmetic and the two failure modes below,
|
|
97
|
+
placed at the boundary where the value entered the system rather than as a
|
|
98
|
+
defensive check inside `Job` itself. Every run
|
|
99
|
+
after the first behaves exactly as before: `_ran_at` is no longer `None`, so
|
|
100
|
+
`run_if_due` falls back to `now + interval`, computed before the run same as
|
|
101
|
+
any job without `preserve_stagger` set. `fleet`/`webhook` do not set the
|
|
102
|
+
flag: they are woken repeatedly by real events (a scan commit, a flush) for
|
|
103
|
+
the daemon's whole life, where re-anchoring on `now` at each of those wakes is
|
|
104
|
+
the correct behavior, not just tolerable on the first one.
|
|
105
|
+
|
|
106
|
+
This amends ADR-0060, not supersedes it: the two-posture decision, `sync-bom`
|
|
107
|
+
existing in `flush`'s shape, blocking one pass, and serializing with the
|
|
108
|
+
scheduled job are all unchanged. Only the one clause explaining *why*
|
|
109
|
+
`sync-bom` is needed — "the BOM job has none [wake path]" — no longer holds,
|
|
110
|
+
and only for the reason given there: `sync-bom` is still the only way to run
|
|
111
|
+
`sync_endpoint` on demand once the daemon is past its own single startup kick.
|
|
112
|
+
|
|
113
|
+
## Alternatives considered
|
|
114
|
+
|
|
115
|
+
- **Leave ADR-0060 accepted as written, fix only the spec.** Rejected: a
|
|
116
|
+
future reader of ADR-0060 alone would still be told the BOM job has no wake
|
|
117
|
+
at all, with no pointer to what changed or why — the durable-decision
|
|
118
|
+
problem CLAUDE.md's ADR discipline exists to prevent.
|
|
119
|
+
- **Fully supersede ADR-0060.** Rejected: every other clause in it — the two
|
|
120
|
+
postures, `flush`/`sync-bom`'s shape, one blocking pass, serialization with
|
|
121
|
+
the scheduled job — is unaffected by this change. Retiring the whole
|
|
122
|
+
decision over one now-stale sentence would erase context that still binds,
|
|
123
|
+
against the amend/supersede test itself ("does a numbered decision in the
|
|
124
|
+
old ADR still bind — if one does, it's an amendment").
|
|
125
|
+
- **Reset `_due_at` to `now + interval` unconditionally.** Rejected (P1,
|
|
126
|
+
first Codex pass): defeats the fleet-wide stagger on every run after the
|
|
127
|
+
first, not only the first, which was the whole point of `stagger()`.
|
|
128
|
+
- **Preserve the stagger when `_due_at > now` at execution time.** Rejected
|
|
129
|
+
(P2, second Codex pass): a scan slower than a low-offset host's remaining
|
|
130
|
+
stagger makes `_due_at` already `<= now` by the time this job's
|
|
131
|
+
`run_if_due` runs, so exactly the hosts stagger matters most for would fall
|
|
132
|
+
through to the unstaggered path anyway.
|
|
133
|
+
- **Advance the preserved deadline by exactly one interval.** Rejected (P2,
|
|
134
|
+
third Codex pass): with a short `--bom-interval`, a delay of more than one
|
|
135
|
+
interval left the deadline in the past, so the job ran again immediately —
|
|
136
|
+
no longer the first run — and re-anchored on `now` via the normal path,
|
|
137
|
+
losing the stagger the fix was meant to keep.
|
|
138
|
+
- **Compute the advanced deadline from the clock read before `run()`.**
|
|
139
|
+
Rejected (P2, fourth Codex pass): the run itself (an actual upload) can
|
|
140
|
+
outrun the margin that pre-run reading assumed was safe, on the same
|
|
141
|
+
short-interval hosts the third pass was about. Measuring from the clock
|
|
142
|
+
after `run()` returns keeps the deadline honest about time the run itself
|
|
143
|
+
spent.
|
|
144
|
+
- **Round the delay up to whole intervals with `ceil`.** Rejected (P2, fifth
|
|
145
|
+
Codex pass): `ceil` leaves the deadline exactly equal to the post-run clock
|
|
146
|
+
when the delay lands on an exact interval multiple, which `_is_due` still
|
|
147
|
+
reads as due, running the job again immediately on the normal (no longer
|
|
148
|
+
preserved) path. `floor(x) + 1` is strictly greater than `x` always, with
|
|
149
|
+
no boundary left to land on.
|
|
150
|
+
- **Give the BOM job a real, repeating wake** (e.g. on scan commit, like
|
|
151
|
+
`fleet`). Rejected as out of scope: nothing in the queue is BOM input (a
|
|
152
|
+
BOM upload is a full inventory snapshot, not a drain of anything a scan
|
|
153
|
+
writes), so there is no event that should retrigger it before its interval
|
|
154
|
+
— only the one-time startup case this ADR covers.
|
|
155
|
+
- **Catch the division's `OverflowError` inside `Job` and fall back to
|
|
156
|
+
`after + interval` on that run.** Tried first (P2, sixth Codex pass; this
|
|
157
|
+
is what commit `37b1da1` shipped), then rejected once a seventh pass found
|
|
158
|
+
the case it does not cover: an interval too small to move a large
|
|
159
|
+
`monotonic()` reading by floating-point addition at all does not raise —
|
|
160
|
+
it silently drops the deadline back to `now`, so nothing catches it. A
|
|
161
|
+
`try`/`except` guards one arithmetic failure mode of an unvalidated input,
|
|
162
|
+
not the input; a sibling failure mode with no exception to catch was only
|
|
163
|
+
ever one interval choice away. Rejected in favor of refusing the value at
|
|
164
|
+
`parse_duration`, the boundary every daemon interval already passes
|
|
165
|
+
through, which closes both failure modes and any other the same missing
|
|
166
|
+
floor would eventually surface.
|
|
167
|
+
- **Guard the tiny-interval case inside every `Job` caller in `runtime.py`
|
|
168
|
+
instead of in `parse_duration`.** Rejected: `runtime.py` has four call
|
|
169
|
+
sites building a `Job` from a parsed setting; `parse_duration` is the one
|
|
170
|
+
place all four already funnel through, and it is also where `daemon
|
|
171
|
+
status`'s "duration is too small" message reaches an operator's terminal
|
|
172
|
+
at `daemon run` time (`test_a_mistyped_interval_fails_at_resolution_not_at_
|
|
173
|
+
the_first_tick`), rather than a forked process's log file after the fact.
|
|
174
|
+
|
|
175
|
+
## Consequences
|
|
176
|
+
|
|
177
|
+
A freshly started daemon uploads its BOM promptly instead of waiting up to
|
|
178
|
+
`--bom-interval` plus a stagger, on every daemon, without collapsing a
|
|
179
|
+
fleet's recurring upload cadence onto one instant. `sync-bom` remains
|
|
180
|
+
necessary for an on-demand run after startup — the BOM job still has no
|
|
181
|
+
recurring wake of its own. `preserve_stagger`'s "first run ever" key assumes
|
|
182
|
+
the flag is only ever set on a job that is woken exactly once, at
|
|
183
|
+
construction; a job that combines `preserve_stagger` with a real recurring
|
|
184
|
+
wake would need the mechanism revisited. Every daemon interval — not only
|
|
185
|
+
`--bom-interval` — now has a one-second floor: `--scan-interval`,
|
|
186
|
+
`--fleet-interval`, `--webhook-interval`, and `--scan-since` all parse
|
|
187
|
+
through the same `parse_duration`, so a value that was always nonsensical
|
|
188
|
+
(a webhook retry cadence measured in nanoseconds) is now refused at
|
|
189
|
+
resolution for those too, not only for the job whose arithmetic happened to
|
|
190
|
+
expose it.
|
|
191
|
+
|
|
192
|
+
## When to revisit
|
|
193
|
+
|
|
194
|
+
- If the BOM job ever gains an event-driven wake beyond the one-time startup
|
|
195
|
+
kick (mirroring `fleet`'s scan-commit wake), `preserve_stagger`'s
|
|
196
|
+
first-run-only key stops being sufficient, and ADR-0060 clause 2 needs
|
|
197
|
+
another look alongside it.
|
|
198
|
+
- If `DaemonService.tick()`'s ordering changes so scanning no longer runs
|
|
199
|
+
ahead of the jobs it wakes, the delayed-first-tick case this ADR's second
|
|
200
|
+
alternative was rejected for may no longer be reachable — worth confirming
|
|
201
|
+
before simplifying `preserve_stagger` back to a deadline comparison.
|
|
202
|
+
|
|
203
|
+
## References
|
|
204
|
+
|
|
205
|
+
- ADR-0060 — two upload postures, not one command with two sources
|
|
206
|
+
- `docs/specs/daemon-uploads.md` — the BOM job, `sync-bom`, and the stagger
|
|
207
|
+
- `src/stacktrace_cli/daemon/jobs.py` — `Job.preserve_stagger`, `stagger()`
|
|
208
|
+
- `src/stacktrace_cli/daemon/config.py` — `parse_duration`'s one-second floor
|