Skip to content

fix: close sentinel audit integrity gaps 1–5 (v3.5.1)#331

Merged
goruck merged 3 commits intomainfrom
fix/sentinel-audit-integrity-gaps
Mar 15, 2026
Merged

fix: close sentinel audit integrity gaps 1–5 (v3.5.1)#331
goruck merged 3 commits intomainfrom
fix/sentinel-audit-integrity-gaps

Conversation

@goruck
Copy link
Owner

@goruck goruck commented Mar 15, 2026

Summary

  • Gap 1 — Suppressed findings now produce an audit record. Previously, findings silently dropped by cooldown or policy gate had no audit trail. Both simple and compound dispatch paths now call _append_finding_audit before returning on suppression.
  • Gap 2ACTION_POLICY_BLOCKED findings skip notification entirely. register_finding (cooldown) is called; register_prompt (pending user action) is not. The BLOCKED outcome is audited with action_policy_path='blocked'. Compound dispatch was reordered to compute exec_result before the cooldown/prompt loops.
  • Gap 3trigger_source ('poll', 'event', 'on_demand') is now threaded from the scheduler call sites through _run_once_dispatch_item/_dispatch_compound_append_finding_audit → every audit record.
  • Gap 4AuditStore max-records cap is config-driven via CONF_AUDIT_HOT_MAX_RECORDS / RECOMMENDED_AUDIT_HOT_MAX_RECORDS (default 200), following the DiscoveryStore pattern. Hardcoded MAX_RECORDS constant removed.
  • Gap 5action_policy_path was written by async_append_finding but missing from _V2_FIELD_DEFAULTS, so the v1→v2 migration silently omitted it for records loaded from storage. Added to the migration defaults.
  • 14 new tests across test_audit_schema.py and test_sentinel_end_to_end.py.

Pre-Landing Review

Pre-Landing Review: 2 issues (0 critical, 2 informational)

  • audit/store.py:71 — Constructor default max_records: int = 200 is a bare literal separate from RECOMMENDED_AUDIT_HOT_MAX_RECORDS. A test covers drift; risk is low.
  • sentinel/engine.py:770 — All-suppressed compound path uses hardcoded suppression_reason_code="suppressed" rather than per-constituent codes. Minor; audit consumers see an opaque value.

Eval Results

No prompt-related files changed — evals skipped.

TODOS

No TODO items completed in this PR (gaps 1–5 close the dependency for "Remove shim" P3/S and "Operational Health Entity" P1/XL — those items are now unblocked but not done).

Test plan

  • All tests pass: 491 passed, 1 warning
  • make lint: All checks passed
  • make typecheck: 0 errors, 0 warnings

🤖 Generated with Claude Code

goruck and others added 3 commits March 15, 2026 15:20
Gap 4: Replace hardcoded MAX_RECORDS=200 with a constructor parameter
(max_records) wired to CONF_AUDIT_HOT_MAX_RECORDS / RECOMMENDED_AUDIT_HOT_MAX_RECORDS,
following the DiscoveryStore pattern.

Gap 5: action_policy_path was written by async_append_finding but absent
from _V2_FIELD_DEFAULTS, so the v1→v2 migration silently omitted it from
records loaded from storage. Added to the defaults dict.

Tests: test_audit_store_respects_config_max_records,
test_audit_store_default_max_records, test_migrate_record_backfills_action_policy_path,
test_migrate_record_preserves_action_policy_path_if_set.
…cations, propagate trigger_source

Gap 1: Suppressed findings (cooldown or policy gate) now produce an audit
record via _append_finding_audit before returning, so every evaluated
finding has an audit trail regardless of outcome. Applied to both simple
(_dispatch_item) and compound (_dispatch_compound) paths.

Gap 2: Findings where ACTION_POLICY_BLOCKED is returned by evaluate_canary
now skip notification (register_finding for cooldown only; no
register_prompt; no async_notify). The BLOCKED state is audited with
action_policy_path='blocked'. The register_finding / register_prompt split
also required reordering the compound dispatch path so exec_result is
computed before the cooldown/prompt loops.

Gap 3: _run_once now accepts a trigger_source parameter ('poll', 'event',
'on_demand'). All three scheduler call sites pass the correct value via
lambda. The value is threaded through _dispatch_item and _dispatch_compound
to _append_finding_audit and written to every audit record.

Tests: test_suppressed_finding_creates_audit_record,
test_suppression_gate_fires_before_triage, test_blocked_finding_no_notification,
test_blocked_finding_creates_audit_record, test_blocked_finding_registers_cooldown_not_prompt,
test_trigger_source_{poll,event,on_demand}_in_audit (10 new tests).
Version bump: 3.5.0 → 3.5.1 (closes audit integrity gaps 1–5).
CLAUDE.md: project-level guidance for Claude Code.
TODOS.md: captures deferred work — shim removal (P3/S), entity-ID fix
for people_home (P2/M), and Operational Health Entity (P1/XL, now
unblocked by this PR).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@goruck goruck merged commit 8297f06 into main Mar 15, 2026
3 checks passed
@goruck goruck deleted the fix/sentinel-audit-integrity-gaps branch March 15, 2026 22:27
mwmdeadpool pushed a commit to mwmdeadpool/home-generative-agent that referenced this pull request Mar 16, 2026
PR goruck#331: Sentinel audit integrity gaps - audit suppressed findings, skip
BLOCKED notifications, propagate trigger_source, config-driven max_records,
v1→v2 migration fix for action_policy_path

PR goruck#332: Remove _supports_suppression_reason_code() introspection shim -
dead code cleanup, direct async_append_finding calls

PR goruck#333: Use entity IDs for people_home/people_away in snapshot derived
context; v2→v3 suppression state migration to drop stale friendly-name
grace keys

Custom files preserved: Mem0 client, intent resolver, lambda registry,
MCP tools, config_flow custom toggles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant