feat(audit): surface integrity_status through GET /audit/events - #9
Merged
Merged
Conversation
HIPAA audit-integrity rollout: closes a real gap in the platform's own
read API. PR2/PR#5 added audit_events.integrity_status and the worker
has classified every event ("valid"/"invalid"/"unsigned") since PR3a's
deployment; all six producers now sign (five merged, one -- API Gateway
-- blocked on an external credential, tracked separately in that repo's
issue #12, not touched here). None of that was visible through this
repo's own GET /audit/events API: a platform_admin querying it had no
way to see whether any event was ever actually verified, or to isolate
the events that matter most -- the ones that failed verification.
schemas/audit.py::AuditEventOut: adds integrity_status: str (never
Optional -- the DB column is NOT NULL with server_default="unsigned",
matching AuditEventRecord exactly).
services/audit_query_service.py::list_audit_events: adds an
integrity_status filter parameter, same SQL-filter style as every
existing one (user_id/service/event_type/decision/timestamp range) --
no new pattern introduced. Lets a caller isolate integrity_status=invalid
specifically (forged/tampered events) or the still-unsigned backlog, not
just see the field per returned row.
api/routes_audit_events.py: adds the matching integrity_status query
param, passed straight through -- no new validation beyond Optional[str],
since the DB column itself is the source of truth for valid values.
Tests: updated the one existing exact-field-set assertion
(test_response_contains_expected_fields) to include the new field and
its default value; added 2 query-service filter tests (including that
'unsigned' correctly matches server_default rows created without an
explicit value) and 1 HTTP-level filter test. 252 passed (249 baseline +
3 new), 0 regressions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lint-only, no behavior change. PR #9's own CI run reported 34 errors across 4 rule categories once the touched files entered ruff's changed-file set (ruff lints changed files whole, not just changed lines, so this also caught pre-existing debt the files already had before PR #9 touched them) -- not just UP045 as initially assumed: - UP045 (19): Optional[X] -> X | None, across api/routes_audit_events.py, schemas/audit.py, services/audit_query_service.py. Safe on this repo's actual Python 3.11 runtime (Dockerfile/Dockerfile.worker both pin python:3.11-slim) -- no `from __future__ import annotations` needed, matching schemas/audit.py's own pre-existing use of dict[str, Any] without one. - F401 (3): typing.Optional left unused in the same three files as a direct, mechanical consequence of the UP045 fix above -- not a separate change. - RUF059 (1): unused unpacked `rows` in tests/test_audit_query_service.py -- renamed to `_rows`, matching ruff's own dummy-variable convention. - B008 (4): Query(...)/Depends(...) as argument defaults in api/routes_audit_events.py. This is FastAPI's own documented, required pattern, not a mutable-default bug -- ruff's suggested "real" fix (move the call inside the function body) would restructure the route signature well beyond a lint fix. Suppressed narrowly with `# noqa: B008` plus a one-line reason on exactly the 4 flagged lines, matching this repo's existing narrow-noqa convention (8153d38's `# noqa: BLE001` for the equivalent situation on PR3b). - DTZ001 (10): naive datetime(...) in the two test files. AuditEventRecord.timestamp is a naive Column(DateTime) (no timezone=True) -- adding tzinfo here would mismatch the column, not fix anything, and the actually-correct fix (a timezone-aware column) is a schema change out of scope for a lint-only commit. Suppressed narrowly with `# noqa: DTZ001` plus a reason on exactly the 10 flagged lines, same convention as B008 above. Zero API-behavior change: request/response shapes, the integrity_status filter's semantics, and the DB query itself are byte-for-byte identical to 52563bf -- confirmed by re-running the PR's own focused tests (tests/test_audit_query_service.py + tests/test_routes_audit_events.py, 27 tests) and the full suite (252 passed, 0 regressions, matching the 252 baseline 52563bf's own commit message already reported). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
audit_events.integrity_status(PR2/#5) has been classified by the worker for every event since PR3a's deployment, and all six producers now sign (five merged; API Gateway blocked on an external credential, tracked in that repo's #12, not touched here). None of it was visible through this repo's ownGET /audit/eventsAPI — a platform_admin querying it had no way to see whether an event was ever verified, or to isolate the ones that failed verification.Fix
schemas/audit.py::AuditEventOut— addsintegrity_status: str(neverOptional, matches the NOT NULL DB column).services/audit_query_service.py::list_audit_events— adds anintegrity_statusfilter, same style as every existing filter (user_id/service/event_type/decision/timestamp range).api/routes_audit_events.py— adds the matching query param, passed straight through.Tests
Updated the one existing exact-field-set assertion to include the new field; added 2 query-service filter tests + 1 HTTP-level filter test. 252 passed (249 baseline + 3 new), 0 regressions.
Non-scope
No API Gateway work, no GHCR/release changes, no producer/signing code touched, no schema/migration changes (column already exists).
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com