stacktrace-cli 0.4.1__tar.gz → 0.5.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.4.1 → stacktrace_cli-0.5.1}/.gitignore +3 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/PKG-INFO +2 -2
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/README.md +1 -1
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0006-trust-boundary-and-detection-upload.md +1 -1
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0031-one-host-daemon-observes-native-session-logs.md +1 -1
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0037-reuse-openaidr-append-cursors-in-the-daemon.md +1 -1
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0038-poll-normalized-sessions-for-supplemental-changes.md +1 -1
- stacktrace_cli-0.5.1/docs/adrs/0045-scan-every-session-and-outlive-them.md +137 -0
- stacktrace_cli-0.5.1/docs/adrs/0046-the-finding-store-is-an-append-only-queue.md +139 -0
- stacktrace_cli-0.5.1/docs/adrs/0047-capture-activity-during-correlation-or-lose-it.md +108 -0
- stacktrace_cli-0.5.1/docs/adrs/0048-the-daemon-keeps-a-leveled-content-free-activity-log.md +59 -0
- stacktrace_cli-0.5.1/docs/adrs/0056-stop-a-resident-daemon-by-record-and-signal.md +124 -0
- stacktrace_cli-0.5.1/docs/adrs/0057-grow-the-queue-schema-additively-and-share-only-the-document.md +117 -0
- stacktrace_cli-0.5.1/docs/adrs/0058-one-word-for-a-finding-columns-for-a-counter-and-a-push-that-does-not-wait.md +128 -0
- stacktrace_cli-0.5.1/docs/adrs/0059-the-daemon-does-not-decide-whether-it-should-be-running.md +111 -0
- stacktrace_cli-0.5.1/docs/adrs/0060-two-upload-postures-not-one-command-with-two-sources.md +121 -0
- stacktrace_cli-0.5.1/docs/adrs/0061-the-drain-sends-findings-only.md +118 -0
- stacktrace_cli-0.5.1/docs/adrs/0062-hold-the-transition-lock-across-remote-side-effects.md +134 -0
- stacktrace_cli-0.5.1/docs/adrs/0063-underbuilt-debt-does-not-hold-the-scan-watermark.md +90 -0
- stacktrace_cli-0.5.1/docs/adrs/0064-a-redacted-value-has-one-path-span.md +86 -0
- stacktrace_cli-0.5.1/docs/adrs/0065-asset-external-id-resolves-by-a-ladder.md +119 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/INDEX.md +14 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/cutover-openaca-remote.md +8 -6
- stacktrace_cli-0.5.1/docs/plans/011-daemon-store.md +2689 -0
- stacktrace_cli-0.5.1/docs/plans/012-daemon-scanning.md +172 -0
- stacktrace_cli-0.5.1/docs/plans/013-daemon-uploads.md +130 -0
- stacktrace_cli-0.5.1/docs/plans/014-asset-identity.md +666 -0
- stacktrace_cli-0.5.1/docs/releases/v0.5.0.md +113 -0
- stacktrace_cli-0.5.1/docs/releases/v0.5.1.md +209 -0
- stacktrace_cli-0.5.1/docs/specs/daemon-scanning.md +251 -0
- stacktrace_cli-0.5.1/docs/specs/daemon-store.md +246 -0
- stacktrace_cli-0.5.1/docs/specs/daemon-uploads.md +367 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/notification-delivery.md +27 -3
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/remote-sync.md +6 -5
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/pyproject.toml +1 -1
- stacktrace_cli-0.5.1/src/stacktrace_cli/__init__.py +10 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/activity_log.py +94 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/bom.py +56 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/cli.py +407 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/client.py +247 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/composition.py +242 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/config.py +182 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/db.py +625 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/fleet.py +660 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/identity.py +83 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/jobs.py +181 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/observer.py +626 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/paths.py +9 -1
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/presentation.py +68 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/protocol.py +127 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/runtime.py +673 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/scan.py +256 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/server.py +63 -2
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/service.py +116 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/store.py +878 -0
- stacktrace_cli-0.5.1/src/stacktrace_cli/daemon/version.py +49 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/render.py +23 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/cli.py +28 -19
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/client.py +29 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/detect_payload.py +147 -25
- stacktrace_cli-0.5.1/src/stacktrace_cli/remote/identity.py +305 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/payload.py +7 -4
- stacktrace_cli-0.5.1/src/stacktrace_cli/remote/redact.py +1328 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/sync.py +330 -98
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/sync_detect.py +134 -64
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/upload_contract.py +10 -1
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/events.py +9 -2
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/posthog.py +34 -5
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/conftest.py +15 -0
- stacktrace_cli-0.5.1/tests/daemon/test_activity_log.py +308 -0
- stacktrace_cli-0.5.1/tests/daemon/test_cli.py +655 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/test_client.py +4 -10
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/test_composition.py +87 -0
- stacktrace_cli-0.5.1/tests/daemon/test_config.py +91 -0
- stacktrace_cli-0.5.1/tests/daemon/test_db.py +827 -0
- stacktrace_cli-0.5.1/tests/daemon/test_detection_needs_no_subscriber.py +115 -0
- stacktrace_cli-0.5.1/tests/daemon/test_fleet.py +735 -0
- stacktrace_cli-0.5.1/tests/daemon/test_identity.py +91 -0
- stacktrace_cli-0.5.1/tests/daemon/test_jobs.py +329 -0
- stacktrace_cli-0.5.1/tests/daemon/test_observer.py +861 -0
- stacktrace_cli-0.5.1/tests/daemon/test_queue_reader.py +200 -0
- stacktrace_cli-0.5.1/tests/daemon/test_runtime.py +1016 -0
- stacktrace_cli-0.5.1/tests/daemon/test_scan.py +412 -0
- stacktrace_cli-0.5.1/tests/daemon/test_service.py +181 -0
- stacktrace_cli-0.5.1/tests/daemon/test_socket.py +451 -0
- stacktrace_cli-0.5.1/tests/daemon/test_store.py +690 -0
- stacktrace_cli-0.5.1/tests/daemon/test_the_drained_payload_is_the_cold_one.py +166 -0
- stacktrace_cli-0.5.1/tests/daemon/test_the_stored_row_is_sufficient.py +247 -0
- stacktrace_cli-0.5.1/tests/daemon/test_version.py +48 -0
- stacktrace_cli-0.5.1/tests/daemon/test_wire_frames_are_bounded.py +154 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/helpers.py +4 -3
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_cli.py +33 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_client.py +23 -0
- stacktrace_cli-0.5.1/tests/remote/test_detect_redaction.py +692 -0
- stacktrace_cli-0.5.1/tests/remote/test_identity.py +347 -0
- stacktrace_cli-0.5.1/tests/remote/test_no_local_path_survives_redaction.py +376 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_payload.py +10 -7
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_redact_payload.py +37 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_sync.py +475 -5
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_sync_detect.py +208 -1
- stacktrace_cli-0.5.1/tests/telemetry/test_activity_log.py +135 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_hooks.py +30 -30
- stacktrace_cli-0.5.1/tests/test_no_test_undoes_every_patch_at_once.py +51 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_session_identity_is_the_pair.py +20 -8
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/uv.lock +1 -1
- stacktrace_cli-0.4.1/src/stacktrace_cli/__init__.py +0 -3
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/cli.py +0 -106
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/client.py +0 -134
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/composition.py +0 -133
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/observer.py +0 -173
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/protocol.py +0 -29
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/runtime.py +0 -222
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/service.py +0 -154
- stacktrace_cli-0.4.1/src/stacktrace_cli/daemon/store.py +0 -368
- stacktrace_cli-0.4.1/src/stacktrace_cli/remote/redact.py +0 -660
- stacktrace_cli-0.4.1/tests/daemon/test_cli.py +0 -124
- stacktrace_cli-0.4.1/tests/daemon/test_observer.py +0 -407
- stacktrace_cli-0.4.1/tests/daemon/test_runtime.py +0 -411
- stacktrace_cli-0.4.1/tests/daemon/test_service.py +0 -227
- stacktrace_cli-0.4.1/tests/daemon/test_socket.py +0 -183
- stacktrace_cli-0.4.1/tests/daemon/test_store.py +0 -188
- stacktrace_cli-0.4.1/tests/remote/test_detect_redaction.py +0 -191
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.agents/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.claude/settings.json +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.claude/skills/release-stacktrace/SKILL.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.codex/hooks.json +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/managed-autofix.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/autofix.yml +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/ci.yml +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/claude.yml +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/.github/workflows/publish-pypi.yml +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/AGENTS.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/CLAUDE.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0001-session-telemetry-as-input.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0002-session-collection-in-openaidr.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0003-runtime-edges.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0004-three-stage-detector.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0005-detection-family-and-report-assembly.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0007-proprietary-package-on-open-dependencies.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0008-console-in-separate-demo-package.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0010-detection-severity-and-confidence-ladders.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0011-verdict-cache.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0012-observation-evidence-kinds-and-transport.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0013-rule-catalogue-triage-and-per-rule-context.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0014-declared-project-mapping.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0015-narrow-the-security-catalogue.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0016-stall-grouping-is-session-wide.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0017-a-declined-repeat-is-a-stall.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0018-remote-sync-config-and-facade-consumption.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0019-delegated-commands-are-openaca-objects.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0020-openaca-consumption-boundary.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0021-two-command-kinds.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0022-detection-scope-is-a-catalogue-column.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0023-sync-detect-collects-and-does-not-escalate.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0024-the-upload-carries-observations.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0025-a-denied-call-is-activity-never-an-invocation.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0026-the-console-becomes-a-product-surface.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0027-a-finding-may-name-what-it-could-not-place.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0028-the-console-shows-what-it-placed.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0029-stage-three-returns-to-opt-in.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0030-a-detection-is-dated-by-the-turns-it-cites.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0032-refresh-daemon-compositions-outside-detection.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0033-a-third-party-analyzer-only-by-a-flag-that-names-it.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0034-the-drift-grade-is-read-off-a-run-not-a-call.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0035-naming-an-analyzer-selects-stored-answers.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0036-deliver-eligible-findings-to-slack-from-the-daemon.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0039-send-usage-telemetry-by-default-with-an-opt-out.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0041-defer-native-windows-support.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0042-read-the-build-commit-from-the-install-record.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0043-name-the-session-agent-and-host-and-count-errors-by-class.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/0044-send-the-build-as-version-prints-it.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/HOOK-PROMPT.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/adrs/TEMPLATE.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/002-session-input.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/003-correlation.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/004-detector.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/005-remote-sync.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/006-cli-composition.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/007-detection-upload.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/008-monitor.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/009-reasoning-jev.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/plans/010-session-daemon.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.0.1.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.1.0.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.0.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.1.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.2.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.2.3.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.3.0.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.3.1.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.4.0.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/releases/v0.4.1.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/research/001-intent-drift-metric.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/aidr.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/cli-composition.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/correlation.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/detection-upload.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/detector.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/event-time.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/monitor.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/reasoning-jev.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/docs/specs/session-input.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/scripts/git-hooks/pre-push +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/scripts/install-hooks.sh +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/__main__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/analysis.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/build.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/acquire.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/composition.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/join.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/observed.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/orchestrate.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/project_map.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/record.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/correlate/render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/daemon/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/analyzer.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/blocked.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/cache.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/deterministic.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/finding.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/history.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/jev.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/priors.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/jev-v1/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/jev-v2/stacktrace-deceptive-completion.json +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/exclusions.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/framing.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/prompts/v1/stacktrace-deceptive-completion.md +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/reasoning.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/rules.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/run.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/secrets.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/verdict.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/detector/windows.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/reasoning.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/server.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/app.js +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/OFL.txt +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2 +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-mono-500-latin.woff2 +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/fonts/dm-sans-latin.woff2 +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/index.html +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/site/styles.css +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/state.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/verdicts.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/monitor/watch.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/options.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/private_state.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/config.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/policy.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/remote/spool.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/access.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/index.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/outcome.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/protocols.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/sessions/render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/src/stacktrace_cli/telemetry/state.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/agent_bom_fixture.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/daemon/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/detector_session_fixture.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/feed_harness.mjs +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_feed_model.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_reasoning_gates.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_server.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_site_assets.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_state.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_verdicts.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/monitor/test_watch.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_client.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_config.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_activity.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_contract_is_exhaustive.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_gate_matches_the_cloud.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_layers_compose.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_payload_reaches_the_cloud_model.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_spool.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_upload_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_detect_wire_payload.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_facade_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_policy.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_seam.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_spool.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/remote/test_upload_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/__init__.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_events.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_release_gate.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_state.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/telemetry/test_uploader.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_acquire.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_adr_cross_references_are_reciprocal.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_agent_instance_id.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_analysis.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_bom_shape.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_build_commit.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_call_status_conventions.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_composition.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlate_orchestrate.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlate_render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_correlated_session.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_components.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_event_time.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detection_subject.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_analyzer_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_analyzer_live.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_blocked.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_cache.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_deterministic.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_end_to_end.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_finding.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_history.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_jev.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_priors.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_prompts.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_reasoning.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_run.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_secrets.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_verdict.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_why_it_stopped.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_detector_windows.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_evidence_is_dated_not_numbered.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_fingerprint_covers_what_the_analyzer_reads.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_help_sections.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_join.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_monitor_cli.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_no_live_document_states_the_old_stage_three_default.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_observed.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_openaca_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_openaidr_contract.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_option_arity_matches_the_binary.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_project_map.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_readme_examples_are_real_output.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_release_gate_coverage.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_release_readiness.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_seam_boundary.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_access.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_end_to_end.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_protocol_typing.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_protocols.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_sessions_render.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_shell_separators_match_the_shell.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_stored_verdicts_are_never_gated_on_reasoning.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_detector_spec_lists_the_shipped_catalogue.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_jev_spec_states_the_shipped_thresholds.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_the_locality_claim_is_qualified_everywhere.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_untrusted_content_never_travels.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_verification_subcommands_match_the_binary.py +0 -0
- {stacktrace_cli-0.4.1 → stacktrace_cli-0.5.1}/tests/test_what_jev_is_told_is_what_was_disclosed.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stacktrace-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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>
|
|
@@ -51,7 +51,7 @@ Requires Python 3.11 or newer.
|
|
|
51
51
|
|
|
52
52
|
```console
|
|
53
53
|
$ stacktrace --version
|
|
54
|
-
stacktrace 0.
|
|
54
|
+
stacktrace 0.5.1 (openaca 0.7.0)
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
## Quick start
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0045
|
|
3
|
+
title: Scan every session on a watermark, and outlive all of them
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-22
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: 0031, 0037, 0038
|
|
9
|
+
amended-by: 0056, 0058
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
ADR-0031 gave the daemon a subscriber-shaped life. `DaemonService.tick()` takes
|
|
15
|
+
its work list from `server.active_sessions()`, which returns
|
|
16
|
+
`tuple(self._subscriptions)` — literally the set of connected adapters — and
|
|
17
|
+
`should_exit()` ends the process once that set has been non-empty and is now
|
|
18
|
+
empty. Both follow from how the daemon starts: `ensure_daemon()` is called from
|
|
19
|
+
the plugin monitor's reconnect loop, so a session forks it and the process must
|
|
20
|
+
not outlive the session that did.
|
|
21
|
+
|
|
22
|
+
Two things make that shape wrong now. The daemon is becoming the machine's
|
|
23
|
+
detection engine rather than one session's helper, so a finding for a session
|
|
24
|
+
nobody subscribed to is still a finding worth having. And it is acquiring
|
|
25
|
+
scheduled work — uploads on a cadence — which has no session to keep it alive
|
|
26
|
+
and stops entirely on a quiet machine.
|
|
27
|
+
|
|
28
|
+
The subscription set is also doing two unrelated jobs at once: deciding *what
|
|
29
|
+
gets analysed* and deciding *where a notification goes*. Only the second is
|
|
30
|
+
what ADR-0031 argued for.
|
|
31
|
+
|
|
32
|
+
## Decision
|
|
33
|
+
|
|
34
|
+
The daemon has no concept of an active session. Work comes from the filesystem
|
|
35
|
+
on a cadence, and the process's lifetime is independent of any session.
|
|
36
|
+
|
|
37
|
+
1. `active_sessions()` is removed from the work path. Every `--scan-interval`
|
|
38
|
+
the daemon enumerates transcripts changed since a **persisted watermark**,
|
|
39
|
+
together with recently-live sessions due ADR-0038's supplemental poll, and
|
|
40
|
+
analyses those.
|
|
41
|
+
2. The first scan, with no watermark on disk, is bounded by `--scan-since`
|
|
42
|
+
(default 3 days). Every scan after that is bounded by the watermark, which
|
|
43
|
+
advances to *the scan's start time* — not the newest mtime seen, or a file
|
|
44
|
+
written during the scan is jumped over. A few seconds of overlap absorbs
|
|
45
|
+
mtime granularity and clock skew.
|
|
46
|
+
3. `should_exit()`, `_connected_once` and `startup_grace` are **deleted**, not
|
|
47
|
+
flagged. So are `_last_seen`, `_absent_since` and `disconnect_grace`: they
|
|
48
|
+
exist to track subscribers, and the scan window already keeps a
|
|
49
|
+
just-ended session in view for days rather than five seconds.
|
|
50
|
+
4. The daemon instead exits when told to (`daemon stop`, a recorded
|
|
51
|
+
`daemon disable`), when the machine goes away, or when its running
|
|
52
|
+
`version_label()` diverges from the installed distribution.
|
|
53
|
+
5. One `analyse_sessions` call per tick for the whole batch, not one per
|
|
54
|
+
session. `collect()` stays per transcript so a malformed file loses only its
|
|
55
|
+
own session.
|
|
56
|
+
6. `NotificationServer._subscriptions` is untouched and remains the delivery
|
|
57
|
+
routing table. **Subscriptions decide where a finding goes, never whether it
|
|
58
|
+
is found.**
|
|
59
|
+
|
|
60
|
+
This amends ADR-0031's implicit use of subscription as a work source and its
|
|
61
|
+
process-lifetime rule; its routing, persistence and delivery decisions stay in
|
|
62
|
+
force. ADR-0037's cursor ownership is untouched — the append cursors remain
|
|
63
|
+
OpenAIDR's, in-memory, for the collector's lifetime. ADR-0038's supplemental
|
|
64
|
+
poll survives as the second input to the work list.
|
|
65
|
+
|
|
66
|
+
## Alternatives considered
|
|
67
|
+
|
|
68
|
+
- **Keep subscription as the work source and union it with a scan.** Rejected:
|
|
69
|
+
two sources for one list, and the union can only ever equal the scan, since
|
|
70
|
+
any subscribed session's transcript is by definition recent. It preserves the
|
|
71
|
+
coupling while adding a second mechanism.
|
|
72
|
+
- **A `--resident` flag that pins `should_exit()` to `False`.** Rejected, and
|
|
73
|
+
this was the first design. A flag keeps the coupling and hides it behind a
|
|
74
|
+
default. Removal is clean instead: `daemon run` is `hidden=True`, an internal
|
|
75
|
+
command `ensure_daemon()` invokes rather than something a user runs expecting
|
|
76
|
+
it to return, and `should_exit` has no production caller outside `run()`. The
|
|
77
|
+
only fallout is assertions in `tests/daemon/test_service.py` that test the
|
|
78
|
+
behaviour being removed.
|
|
79
|
+
- **Keep `disconnect_grace`.** Rejected because it is subsumed, not merely
|
|
80
|
+
unused. Its window was carefully anchored to the first absent poll rather than
|
|
81
|
+
the last-seen time, because a slow `tick()` could otherwise consume the whole
|
|
82
|
+
budget. With a scan window measured in days, a session that ended a minute ago
|
|
83
|
+
is still enumerated; the intricacy and the bug class it guarded both stop
|
|
84
|
+
existing.
|
|
85
|
+
- **`--scan-since` as a standing window rather than a bootstrap horizon.**
|
|
86
|
+
Rejected: it re-reads the same days forever, and it forces a "has this daemon
|
|
87
|
+
run before" special case to avoid doing so. A watermark removes the special
|
|
88
|
+
case and closes downtime gaps for free — a daemon down for a week resumes and
|
|
89
|
+
enumerates a week.
|
|
90
|
+
- **Enumerate by mtime only.** Rejected: it silently regresses ADR-0038. A
|
|
91
|
+
session whose transcript is stable but whose MCP logs moved would never be
|
|
92
|
+
looked at again.
|
|
93
|
+
- **Per-session `analyse_sessions` calls, as today.** Rejected: correlation and
|
|
94
|
+
advisory lookups are per-run work, so N calls repeat them N times.
|
|
95
|
+
`analyse_sessions` already takes tuples and already shares
|
|
96
|
+
`_analyse_sessions` with the cold path.
|
|
97
|
+
- **Keep the per-transcript `mtime - 1 minute` cutoff as `since`.** Rejected as
|
|
98
|
+
unnecessary: `_analyse_sessions` does not filter on `since`; it stamps it as
|
|
99
|
+
`window_start` metadata and analyses the view whole. The watermark is a
|
|
100
|
+
simpler value with the same effect, and batching therefore cannot starve a
|
|
101
|
+
session-wide rule (ADR-0016) of turns.
|
|
102
|
+
|
|
103
|
+
## Consequences
|
|
104
|
+
|
|
105
|
+
Enables detection on a machine where no adapter ever connects, and gives
|
|
106
|
+
scheduled work a process that is still alive to do it. Removes more code than it
|
|
107
|
+
adds.
|
|
108
|
+
|
|
109
|
+
Costs, honestly:
|
|
110
|
+
|
|
111
|
+
- **Nothing restarts the daemon.** With supervision out of scope, a crash means
|
|
112
|
+
the endpoint is down until the next `ensure_daemon()` fork. The
|
|
113
|
+
version-divergence exit is what stops a long-lived process holding stale code,
|
|
114
|
+
replacing the garbage-collection that exit-on-idle was accidentally providing.
|
|
115
|
+
- **Collector memory now tracks transcripts seen since start**, not sessions
|
|
116
|
+
open now. ADR-0037's collector holds one cursor per file for the process
|
|
117
|
+
lifetime, and the process is now long-lived. If it bites, evict by last-touch
|
|
118
|
+
inside the seam — not by persisting cursors, which ADR-0037 places upstream.
|
|
119
|
+
- **A wedge lasts all day.** No idle exit and no supervisor means a hang
|
|
120
|
+
persists until logout. A maximum lifetime is worth having as a safety valve.
|
|
121
|
+
- **Telemetry's `session_started` changes character.** The bootstrap fires it
|
|
122
|
+
once per session in the horizon. It means "the daemon first saw this session",
|
|
123
|
+
not "a user started one" (ADR-0039).
|
|
124
|
+
- **Scan cost is a new standing cost.** Bounded in steady state by the
|
|
125
|
+
watermark; the bootstrap is the expensive pass.
|
|
126
|
+
|
|
127
|
+
## When to revisit
|
|
128
|
+
|
|
129
|
+
- When external supervision lands: the version-divergence exit becomes one of
|
|
130
|
+
several restart triggers rather than the only one, and exit codes need
|
|
131
|
+
defining before `KeepAlive` is wired — `run_daemon()` currently returns exit
|
|
132
|
+
`0` on lock contention and on a live socket, which a supervisor would read as
|
|
133
|
+
"do not restart".
|
|
134
|
+
- If enumeration cost on a large `~/.claude` proves material, which would make
|
|
135
|
+
a filesystem-event source worth its complexity.
|
|
136
|
+
- If a second agent kind lands, since the observer map and the scan root are
|
|
137
|
+
both still Claude-shaped.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0046
|
|
3
|
+
title: Make the finding store an append-only queue of detection revisions
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-22
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: 0031
|
|
9
|
+
amended-by: 0057, 0058
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
ADR-0031 gave the daemon durable, content-free findings and delivery state, and
|
|
15
|
+
`FindingStore` implements that as one JSON document rewritten whole on every
|
|
16
|
+
`record()`. That is adequate for the sessions a human happened to subscribe to.
|
|
17
|
+
ADR-0045 makes the daemon analyse every session on the machine, and a later
|
|
18
|
+
deliverable will drain findings to Fleet on a cadence — which needs something
|
|
19
|
+
the document cannot express: *what is new since I last looked?*
|
|
20
|
+
|
|
21
|
+
Two properties of the existing store block that.
|
|
22
|
+
|
|
23
|
+
`_durable_finding()` is lossy. `observer.py` produces
|
|
24
|
+
`detection_json(detection)` — the whole detection — and `record()` keeps
|
|
25
|
+
`rule_id`, `severity`, `confidence`, `session_id` and evidence spans, discarding
|
|
26
|
+
the rest. Everything an upload mapper needs (`scope`, `subject`, `involved`,
|
|
27
|
+
component identities, occurrence range) is gone before it reaches disk.
|
|
28
|
+
|
|
29
|
+
And a finding's identity is stable while its content is not. `_event_id()`
|
|
30
|
+
hashes `(agent_kind, session_id, rule_id, anchor)`; for a session-scoped rule
|
|
31
|
+
the anchor *is* the session id, so one identity covers the whole session however
|
|
32
|
+
many times the rule fires. A session-wide rule such as stall grouping (ADR-0016)
|
|
33
|
+
legitimately accumulates cited turns and widens its occurrence range across
|
|
34
|
+
scans. Today that is `stored[event_id] = …`, last-write-wins in place, which is
|
|
35
|
+
invisible because nothing reads by position.
|
|
36
|
+
|
|
37
|
+
## Decision
|
|
38
|
+
|
|
39
|
+
The finding store becomes a SQLite **queue**: an append-only log of detection
|
|
40
|
+
revisions on a monotonic axis, with a cursor per reader. The interface
|
|
41
|
+
`FindingStore` presents to its callers does not change.
|
|
42
|
+
|
|
43
|
+
1. `findings(seq INTEGER PRIMARY KEY AUTOINCREMENT, event_id, content_hash,
|
|
44
|
+
agent_kind, session_id, detection_json, delivered_at, acked_at, created_at)`
|
|
45
|
+
with `UNIQUE (event_id, content_hash)` and an index on `(session_id, seq)`.
|
|
46
|
+
`seq` is the cursor axis. `event_id` is **not** unique.
|
|
47
|
+
2. A row is **one detection revision**. When a scan produces content differing
|
|
48
|
+
from the last stored revision of that `event_id`, a new row is appended with
|
|
49
|
+
a new `seq`. `content_hash` — over canonically serialised `detection_json` —
|
|
50
|
+
is what distinguishes "this finding again, unchanged" from "this finding, now
|
|
51
|
+
different", so an unchanged re-derivation writes nothing.
|
|
52
|
+
3. The row stores the **detection**, not any consumer's rendering of it. Each
|
|
53
|
+
reader renders at send time.
|
|
54
|
+
4. Exactly one writer, so insert order is commit order and a reader taking
|
|
55
|
+
`max(seq)` cannot skip a row committed later with a lower value. WAL, so
|
|
56
|
+
`stacktrace findings` and `daemon status` read without blocking it.
|
|
57
|
+
5. `cursors(subscriber, partition, last_seq)` exists with no rows. A reader that
|
|
58
|
+
is not configured holds **no cursor row at all** — retention prunes below the
|
|
59
|
+
cursor floor, and a row pinned at zero would make a machine accumulate
|
|
60
|
+
findings forever.
|
|
61
|
+
6. `daemon-v1.json` is **deleted, not imported**. Nothing is carried across:
|
|
62
|
+
the product is new, the file holds a few days of content-free findings, and
|
|
63
|
+
no code outside `daemon/` reads it. A newer `schema_meta` version fails
|
|
64
|
+
closed rather than reading a newer schema partially.
|
|
65
|
+
|
|
66
|
+
This amends ADR-0031's persistence clause. Its routing, delivery and lifecycle
|
|
67
|
+
decisions stay in force, and the socket path is untouched: `record()` still
|
|
68
|
+
publishes inline and `pending()` still drains on subscribe.
|
|
69
|
+
|
|
70
|
+
## Alternatives considered
|
|
71
|
+
|
|
72
|
+
- **`event_id UNIQUE` with `INSERT OR IGNORE`.** Rejected, and it is the
|
|
73
|
+
obvious-looking choice: `event_id` *is* the stable identity, and it is exactly
|
|
74
|
+
what makes redelivery idempotent. It only breaks once a cursor exists — the
|
|
75
|
+
first thin revision is kept and every later, richer one is lost. The failure
|
|
76
|
+
is silent and presents as "Fleet has the thin version of some findings".
|
|
77
|
+
- **Update the row in place.** Rejected for the mirror-image reason: the content
|
|
78
|
+
changes but `seq` does not move, so a reader that has already passed the row
|
|
79
|
+
never sees the update. Both write strategies lose data; only appending does
|
|
80
|
+
not.
|
|
81
|
+
- **Store a pre-rendered `upload_json`.** Rejected. `detection_json()` and
|
|
82
|
+
`_detection_for_upload()` are two renderings of the same `Detection`, each
|
|
83
|
+
keeping what its own audience needs — the local one adds `stage`, `label` and
|
|
84
|
+
`title`; the Cloud one adds `scope` and participants and deliberately omits
|
|
85
|
+
what the Cloud computes. Neither derives from the other, so a rendered column
|
|
86
|
+
freezes one consumer's wire format into the queue and starves the next one.
|
|
87
|
+
The defect was that `_durable_finding()` is lossy, not that rendering happens
|
|
88
|
+
late. Cost accepted: `_detection_for_upload()` takes a `Detection`, not a
|
|
89
|
+
dict, so either the stored record rehydrates or both renderers learn to read
|
|
90
|
+
the canonical dict.
|
|
91
|
+
- **A `sessions` table.** Rejected. A session is an `(agent_kind, session_id)`
|
|
92
|
+
pair carried on each row — enough to partition a cursor and route a frame.
|
|
93
|
+
More would be a model of the machine, which is what the queue is not. The
|
|
94
|
+
scan watermark (ADR-0045) is separate and no reader touches it.
|
|
95
|
+
- **A per-`(event_id, sink)` delivery table.** Rejected as redundant with a
|
|
96
|
+
watermark: within a partition the single writer produces in `seq` order and
|
|
97
|
+
delivery is sequential, so "below is done, above is pending" is complete.
|
|
98
|
+
Retry needs no column — a failed delivery simply does not advance the cursor.
|
|
99
|
+
- **Import the JSON document's contents.** Rejected as unearned: an importer,
|
|
100
|
+
a dedupe path and a crash-window argument, to preserve state nothing reads
|
|
101
|
+
except `stacktrace findings` on a session that is still open. The cost is a
|
|
102
|
+
notification lost for a session live at the moment of upgrade.
|
|
103
|
+
- **Keep the JSON document and add a sidecar index.** Rejected: the whole-file
|
|
104
|
+
rewrite is the cost that does not survive scanning every session, and two
|
|
105
|
+
stores disagree the first time one write succeeds and the other does not.
|
|
106
|
+
|
|
107
|
+
## Consequences
|
|
108
|
+
|
|
109
|
+
Enables a reader to ask "what is new", which is the whole point, and stops the
|
|
110
|
+
store discarding fields nobody can recover later.
|
|
111
|
+
|
|
112
|
+
Costs and cautions:
|
|
113
|
+
|
|
114
|
+
- **`seq` has gaps.** Verified against SQLite: an `INSERT OR IGNORE` that hits
|
|
115
|
+
the constraint still consumes the `AUTOINCREMENT` value. Harmless to a
|
|
116
|
+
`seq > last_seq` cursor, but nothing may assume contiguity. Reading the last
|
|
117
|
+
revision before inserting avoids churning the sequence on quiet ticks.
|
|
118
|
+
- **Canonical serialisation is load-bearing.** Key ordering alone would produce
|
|
119
|
+
phantom revisions at scan cadence.
|
|
120
|
+
- **One finding can occupy several rows**, so a batch holding two revisions of
|
|
121
|
+
one `event_id` should send only the later, and pruning must keep the newest
|
|
122
|
+
revision rather than the oldest.
|
|
123
|
+
- **The database grows with no reader to prune behind it**, until the
|
|
124
|
+
deliverable that adds one.
|
|
125
|
+
- Storing more per row widens what sits on disk for a retention window. ADR-0006
|
|
126
|
+
is unchanged — the added fields are catalogue text, identities, counts and
|
|
127
|
+
timestamps — but the invariant now deserves an exhaustive test in the manner
|
|
128
|
+
of `test_untrusted_content_never_travels.py`.
|
|
129
|
+
|
|
130
|
+
## When to revisit
|
|
131
|
+
|
|
132
|
+
- If a second writer becomes necessary — for instance if `sync detect` becomes
|
|
133
|
+
"drain the cursor now" and is implemented by opening the database rather than
|
|
134
|
+
asking the daemon over its socket. That would break the ordering invariant the
|
|
135
|
+
cursor rests on.
|
|
136
|
+
- If revision churn proves costly on long sessions, which would argue for
|
|
137
|
+
collapsing superseded revisions at write time rather than at prune time.
|
|
138
|
+
- If a reader ever needs a finding's full history rather than its latest state,
|
|
139
|
+
which the append-only shape happens to preserve but nothing currently uses.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0047
|
|
3
|
+
title: Capture activity counters during correlation, or lose them
|
|
4
|
+
status: superseded
|
|
5
|
+
date: 2026-09-22
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: 0061
|
|
8
|
+
amends: null
|
|
9
|
+
amended-by: 0058
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
A detect run uploads three things per agent kind: `detections`, `unknowns` and
|
|
15
|
+
`activity` (`docs/specs/detection-upload.md`). Activity is per-session
|
|
16
|
+
invocation counters — how many times each bucket of tooling actually ran —
|
|
17
|
+
and the Cloud sums composition coverage from it.
|
|
18
|
+
|
|
19
|
+
ADR-0046 makes the daemon's store a queue of detection revisions, and a later
|
|
20
|
+
deliverable drains that queue to Fleet. Detections fit the queue naturally: they
|
|
21
|
+
are events, appended, read by a monotonic cursor. Activity does not. It is a
|
|
22
|
+
**rollup** — counters that change — and a `seq` cursor cannot express "send me
|
|
23
|
+
the counters that moved".
|
|
24
|
+
|
|
25
|
+
The tempting simplification is to leave activity out: keep one mechanism, carry
|
|
26
|
+
findings only, and revisit if the console misses it. That was the working
|
|
27
|
+
recommendation until `_collect_detect_run` was read closely:
|
|
28
|
+
|
|
29
|
+
> Every activity row is built here, while the correlated record is in hand: a
|
|
30
|
+
> `Detection` carries no counters, and the record is the only place the
|
|
31
|
+
> `(call, resolution)` pairs exist.
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
Activity counters are extracted during analysis, in the same transaction as
|
|
36
|
+
that session's findings, and persisted as an upserted table keyed
|
|
37
|
+
`(agent_kind, session_id, bucket)` carrying `updated_at`. A reader sends rows
|
|
38
|
+
touched since its last drain — a selection rule alongside, not on, the `seq`
|
|
39
|
+
axis.
|
|
40
|
+
|
|
41
|
+
`agent_kind` is part of the key because `build_detect_payloads` groups activity
|
|
42
|
+
by kind while an individual row carries none: the grouping exists only as a dict
|
|
43
|
+
key at build time, so a reader rebuilding the payload from storage has no other
|
|
44
|
+
way to recover it.
|
|
45
|
+
|
|
46
|
+
The daemon's observer must stop discarding the analysis result after taking
|
|
47
|
+
detections from it.
|
|
48
|
+
|
|
49
|
+
**It lands with the store, not with the reader.** "Cannot be backfilled" is a
|
|
50
|
+
statement about a *scan*, not a release — capture started at any time yields
|
|
51
|
+
counters from then on — so deferring it to the consuming deliverable would be
|
|
52
|
+
defensible. It is not done, for two reasons: the store is meant to hold
|
|
53
|
+
everything the upload needs, so splitting the schema across deliverables buys a
|
|
54
|
+
migration later; and the first upload would otherwise ship against an empty
|
|
55
|
+
counter table, reporting zero coverage until enough scans had run. `_scan_claude_session` currently keeps only
|
|
56
|
+
`tuple(detection_json(d) for d in result.run.detections)`; the correlated record
|
|
57
|
+
that `activity_for_upload()` needs is in hand at that moment and thrown away.
|
|
58
|
+
|
|
59
|
+
## Alternatives considered
|
|
60
|
+
|
|
61
|
+
- **Leave activity out of the queue and revisit later.** Rejected on a fact,
|
|
62
|
+
having first been the recommendation. The counters exist only while the
|
|
63
|
+
correlated record is live, so omitting them is a **per-scan discard, not a
|
|
64
|
+
deferral**. Recovering them later means re-analysing transcripts that
|
|
65
|
+
retention may have outlived. "We can add it when someone asks" is false here.
|
|
66
|
+
- **Rebuild activity from stored detections at send time.** Rejected because it
|
|
67
|
+
is not possible: a `Detection` carries no counters, and the `(call,
|
|
68
|
+
resolution)` pairs live only in the correlated record.
|
|
69
|
+
- **Send detections with an empty `activity[]`.** Rejected as a silent product
|
|
70
|
+
regression. The Cloud sums coverage from `activity[]`, so every daemon-driven
|
|
71
|
+
run would report zero coverage — indistinguishable, to a reader of the
|
|
72
|
+
console, from an endpoint that genuinely ran nothing.
|
|
73
|
+
- **Force activity onto the `seq` axis as append-only rows.** Rejected: a
|
|
74
|
+
counter that changes would append a row per bucket per scan, and a reader
|
|
75
|
+
would have to reduce the log to get the current value. The rollup is the
|
|
76
|
+
natural shape; pretending otherwise to preserve one mechanism costs more than
|
|
77
|
+
the second selection rule does.
|
|
78
|
+
- **Re-collect sessions at upload time to recompute activity.** Rejected: it
|
|
79
|
+
reintroduces exactly the collection cost the cursor exists to remove, and
|
|
80
|
+
makes the upload path depend on transcripts still being present.
|
|
81
|
+
|
|
82
|
+
## Consequences
|
|
83
|
+
|
|
84
|
+
Coverage keeps working on a daemon-driven endpoint, and the counters survive
|
|
85
|
+
transcript pruning because they are captured when they exist.
|
|
86
|
+
|
|
87
|
+
Costs:
|
|
88
|
+
|
|
89
|
+
- **A second selection rule.** A reader drains findings by `seq` and activity by
|
|
90
|
+
`updated_at`. The queue is no longer one mechanism, and that is accepted
|
|
91
|
+
deliberately rather than discovered.
|
|
92
|
+
- **The store deliverable is not pure substitution.** It changes the observer,
|
|
93
|
+
not only the persistence layer, because the capture point is upstream of the
|
|
94
|
+
store.
|
|
95
|
+
- Activity rows are not versioned. A drain sends the current value of a counter,
|
|
96
|
+
so a reader that misses a drain sees a jump rather than a series. That matches
|
|
97
|
+
what a rollup is and what the Cloud does with it, but it means activity has no
|
|
98
|
+
equivalent of ADR-0046's revision history.
|
|
99
|
+
|
|
100
|
+
## When to revisit
|
|
101
|
+
|
|
102
|
+
- If the Cloud ever distinguishes "no activity reported" from "no activity", the
|
|
103
|
+
simplification rejected here becomes available again.
|
|
104
|
+
- If activity acquires a per-drain delta shape rather than a running total, at
|
|
105
|
+
which point it becomes an event and belongs on the `seq` axis after all.
|
|
106
|
+
- If correlation is ever restructured so counters survive on the `Detection`
|
|
107
|
+
itself, which would remove the capture-point constraint this ADR exists to
|
|
108
|
+
record.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0048
|
|
3
|
+
title: The daemon keeps a leveled, content-free, size-capped activity log
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2026-09-23
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: null
|
|
8
|
+
amends: null
|
|
9
|
+
amended-by: null
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
`daemon.log` held only failures. On 2026-09-22 a daemon that had been healthy
|
|
15
|
+
for hours, tracking 14 sessions, had a 90-byte log. There was no way to tell it
|
|
16
|
+
from a stuck one. Telemetry was worse: `emit` drops events silently (opted out,
|
|
17
|
+
lock busy, inadmissible, no key, refused by the SDK), and the SDK's `on_error`
|
|
18
|
+
was a no-op.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
The daemon logs through stdlib `logging` to `daemon.log`, at DEBUG unless
|
|
23
|
+
`STACKTRACE_DAEMON_LOG_LEVEL` says otherwise.
|
|
24
|
+
|
|
25
|
+
1. **INFO:** start, stop, subscriber connect and disconnect, notification
|
|
26
|
+
delivered and acknowledged, composition refreshed.
|
|
27
|
+
**DEBUG:** each scan, with its reason and counts.
|
|
28
|
+
**WARNING:** analysis and refresh failures, and failed uploads.
|
|
29
|
+
2. **Every telemetry event is `queued` or `dropped` with a reason.**
|
|
30
|
+
`queued` means the SDK returned a uuid. The reasons are a closed set:
|
|
31
|
+
`inadmissible`, `underivable`, `opted_out`, `lock_busy`,
|
|
32
|
+
`state_unwritable`, `no_project_key`, `sdk_refused` and `sdk_error`.
|
|
33
|
+
3. **No content.** A line holds kinds, counts, class names and event ids. It
|
|
34
|
+
never holds a finding's title or body, an exception message, or transcript
|
|
35
|
+
text. Peer-supplied session ids are written with `%r`, so a newline in one
|
|
36
|
+
cannot forge a line.
|
|
37
|
+
4. **Capped and private.** A 0600 `RotatingFileHandler`, 5 MiB plus one
|
|
38
|
+
backup. Only the daemon holding the lock opens it. After a rollover the
|
|
39
|
+
daemon moves its stdout and stderr onto the new file, so a crash traceback
|
|
40
|
+
is not lost.
|
|
41
|
+
5. **Silent elsewhere.** The `stacktrace_cli` logger has a `NullHandler`, so
|
|
42
|
+
CLI processes print nothing new.
|
|
43
|
+
|
|
44
|
+
## Alternatives considered
|
|
45
|
+
|
|
46
|
+
- **Log "sent" on delivery:** not possible. posthog 7.59 reports failed
|
|
47
|
+
uploads through `on_error` and has no success hook, so `queued` is the
|
|
48
|
+
strongest true claim.
|
|
49
|
+
- **Default to INFO:** rejected. The log exists to debug a daemon that looks
|
|
50
|
+
quiet, and the cap bounds what DEBUG costs.
|
|
51
|
+
- **Log finding titles:** rejected. The untrusted-content test polices
|
|
52
|
+
findings, not log files, and the `event_id` is enough to look a finding up.
|
|
53
|
+
- **Log per-session tracking in `DaemonService`:** deferred. ADR-0045 (PR #63)
|
|
54
|
+
removes subscriptions from the work path.
|
|
55
|
+
|
|
56
|
+
## When to revisit
|
|
57
|
+
|
|
58
|
+
If anything starts parsing `daemon.log`, move it to versioned JSONL. If the
|
|
59
|
+
SDK gains a delivery hook, log `sent`.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 0056
|
|
3
|
+
title: Stop a resident daemon by record and signal, and notice an upgrade by metadata
|
|
4
|
+
status: superseded
|
|
5
|
+
date: 2026-09-23
|
|
6
|
+
supersedes: null
|
|
7
|
+
superseded-by: 0059
|
|
8
|
+
amends: 0045
|
|
9
|
+
amended-by: null
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
ADR-0045 made the daemon resident and named two ways for it to end: an explicit
|
|
15
|
+
stop, and `version_label()` diverging from the installed distribution. It did
|
|
16
|
+
not say how either is done, and both have a plausible-looking implementation
|
|
17
|
+
that is wrong.
|
|
18
|
+
|
|
19
|
+
**The version check.** `version_label()` is `__version__` plus, on an editable
|
|
20
|
+
install, a commit read by shelling out to `git`. Two things follow. It is
|
|
21
|
+
constant for the life of a process — `__version__` is an imported module
|
|
22
|
+
constant, and the label is `functools.cache`d — so comparing it against itself
|
|
23
|
+
is trivially equal and detects nothing. And re-computing it is a subprocess,
|
|
24
|
+
which `docs/specs/daemon-scanning.md` explicitly forbids putting on the scan
|
|
25
|
+
loop: *"it stays resolved once before the socket opens and cached. The scan
|
|
26
|
+
loop must not move it onto a per-tick path."* The spec names the value to
|
|
27
|
+
compare without naming what to compare it *against*, and the only thing that
|
|
28
|
+
moves under a running process is the installed distribution's own metadata.
|
|
29
|
+
|
|
30
|
+
**The stop.** A resident daemon needs an off switch that is not "kill it and
|
|
31
|
+
hope". The obvious three are a control frame on the existing socket, a marker
|
|
32
|
+
file the scan loop polls, and a signal. The first two each break something the
|
|
33
|
+
deliverable holds fixed.
|
|
34
|
+
|
|
35
|
+
**The status surface.** `daemon status` must report the *effective
|
|
36
|
+
configuration and its source*, because fleet-wide drift is undebuggable without
|
|
37
|
+
it. `daemon run` is `hidden=True` and is forked by `ensure_daemon()`, so its
|
|
38
|
+
settings arrive from whatever environment that fork inherited — which is not
|
|
39
|
+
necessarily the environment of the shell someone later runs `status` in.
|
|
40
|
+
|
|
41
|
+
## Decision
|
|
42
|
+
|
|
43
|
+
Four things, each the cheapest mechanism that does not break a constraint the
|
|
44
|
+
deliverable already holds.
|
|
45
|
+
|
|
46
|
+
1. **Version divergence compares the installed distribution version**, re-read
|
|
47
|
+
from `importlib.metadata` on each check, against the `__version__` half of
|
|
48
|
+
the `version_label()` resolved once before the socket opened. A distribution
|
|
49
|
+
with no record answers "not diverged".
|
|
50
|
+
2. **`daemon stop` is `SIGTERM` to a recorded pid**, handled into setting the
|
|
51
|
+
loop's stop event so the composition thread closes and the socket file is
|
|
52
|
+
removed. It refuses to signal unless something is listening on the socket,
|
|
53
|
+
which is what bounds pid reuse.
|
|
54
|
+
3. **`daemon disable` is a marker file** under the state directory, read before
|
|
55
|
+
the socket opens by `run_daemon()` *and* before the fork by
|
|
56
|
+
`ensure_daemon()`. `daemon enable` removes it.
|
|
57
|
+
4. **The running daemon writes its effective configuration to a run-state
|
|
58
|
+
file** at startup — pid, started-at, version, and every setting with its
|
|
59
|
+
source — and `daemon status` reads that. The file is removed on a clean
|
|
60
|
+
exit, so a stale one is the signature of a crash.
|
|
61
|
+
|
|
62
|
+
## Alternatives considered
|
|
63
|
+
|
|
64
|
+
- **Re-compute `version_label()` on each check**, which is what the spec's
|
|
65
|
+
wording reads as literally. Rejected: it puts a `git` subprocess on the scan
|
|
66
|
+
loop, which the same spec forbids two paragraphs earlier, and it still cannot
|
|
67
|
+
detect a version change, because the `__version__` half is a module constant
|
|
68
|
+
the running process cannot re-import.
|
|
69
|
+
- **Compare the whole label including the commit.** Rejected: the installed
|
|
70
|
+
metadata never carries a commit, so a build from a branch would read as
|
|
71
|
+
diverged on its first tick and the daemon would exit-loop.
|
|
72
|
+
- **Build a maximum lifetime instead of, or as well as, the version check.**
|
|
73
|
+
Not built. The spec asks for the choice to be made deliberately rather than
|
|
74
|
+
for both mechanisms, and a restart trigger belongs with the external
|
|
75
|
+
supervision that is already deferred. The cost is recorded in ADR-0045 and
|
|
76
|
+
stands: a wedged process lasts until logout.
|
|
77
|
+
- **A `stop` control frame on the notification socket.** Rejected: the wire
|
|
78
|
+
protocol and the plugin that speaks it are an explicit non-goal of this
|
|
79
|
+
deliverable, and a stop verb on a subscriber-facing protocol is a stop verb
|
|
80
|
+
any subscriber can send.
|
|
81
|
+
- **A stop marker file the scan loop polls.** Rejected for latency: it costs up
|
|
82
|
+
to one `--scan-interval`, and the point of an off switch is that it works now.
|
|
83
|
+
The *disable* marker is a file for the opposite reason — it must be writable
|
|
84
|
+
when there is no process to signal.
|
|
85
|
+
- **Record the stop pid in the existing flock file.** Rejected: the lock is
|
|
86
|
+
opened append-only and its emptiness is not load-bearing anywhere; a separate
|
|
87
|
+
run-state file carries the pid alongside the configuration `status` needs, so
|
|
88
|
+
one file answers both questions.
|
|
89
|
+
- **Have `daemon status` re-derive the configuration from its own
|
|
90
|
+
environment.** Rejected: it would print the reader's settings labelled as the
|
|
91
|
+
daemon's, which is precisely the drift the row exists to expose. It remains
|
|
92
|
+
the *fallback* when no daemon is running, where the sources it reports are
|
|
93
|
+
honestly its own.
|
|
94
|
+
|
|
95
|
+
## Consequences
|
|
96
|
+
|
|
97
|
+
An upgrade is picked up within one scan interval of the new distribution
|
|
98
|
+
landing, without a subprocess on the loop. An operator can stop, disable and
|
|
99
|
+
inspect an endpoint without reading a log or hunting a pid.
|
|
100
|
+
|
|
101
|
+
Costs, honestly:
|
|
102
|
+
|
|
103
|
+
- **An editable checkout whose commit moves without a version bump is not
|
|
104
|
+
noticed.** This is the development case, where the developer restarts the
|
|
105
|
+
daemon anyway; it is the case the version check is least needed for.
|
|
106
|
+
- **A second and third file in the state directory** (`daemon.disabled`,
|
|
107
|
+
`daemon-run.json`), both 0600 in the existing 0700 directory.
|
|
108
|
+
- **Pid reuse is bounded, not eliminated.** A live socket is evidence that
|
|
109
|
+
*some* daemon is running, not proof that the recorded pid is that one. A
|
|
110
|
+
crashed daemon leaves a stale run-state file, and the socket check is what
|
|
111
|
+
stops it being acted on.
|
|
112
|
+
- **`SIGINT` is handled the same as `SIGTERM`**, so a foreground `daemon run`
|
|
113
|
+
shuts down cleanly on Ctrl-C rather than leaving a socket file behind.
|
|
114
|
+
|
|
115
|
+
## When to revisit
|
|
116
|
+
|
|
117
|
+
- When external supervision lands: the stop signal becomes one of several
|
|
118
|
+
things a supervisor does, and exit codes have to be defined first —
|
|
119
|
+
`run_daemon()` still returns `0` on lock contention and on a live socket,
|
|
120
|
+
which `KeepAlive` would read as "do not restart".
|
|
121
|
+
- If a wedged process is observed in the field, which is the trigger for adding
|
|
122
|
+
the maximum lifetime this ADR declined to build.
|
|
123
|
+
- If the daemon ever needs to be stopped by something that is not the same user
|
|
124
|
+
on the same machine, which is the assumption the signal rests on.
|