Skip to content

feat(audit): surface integrity_status through GET /audit/events - #9

Merged
man4ish merged 2 commits into
mainfrom
feature/audit-events-integrity-status-read-api
Aug 15, 2026
Merged

feat(audit): surface integrity_status through GET /audit/events#9
man4ish merged 2 commits into
mainfrom
feature/audit-events-integrity-status-read-api

Conversation

@man4ish

@man4ish man4ish commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

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 own GET /audit/events API — 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 — adds integrity_status: str (never Optional, matches the NOT NULL DB column).
  • services/audit_query_service.py::list_audit_events — adds an integrity_status filter, 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

man4ish and others added 2 commits August 14, 2026 18:55
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>
@man4ish
man4ish merged commit 71a2593 into main Aug 15, 2026
2 checks passed
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