feat(audit_log): add audit log module with two-phase capture - #185
Merged
Conversation
Deploying simple-module-python with |
| Latest commit: | fe7c124 |
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d51d2e3a.simple-module-python.pages.dev |
| Branch Preview URL: | https://worktree-design-audit-log-mo.simple-module-python.pages.dev |
Covers data model, capture mechanism (SQLAlchemy before_flush callback), module structure, REST API, admin Browse page, and testing strategy.
13-task TDD plan covering framework changes (AuditRecord, callback wiring), module scaffold, model, service, API, Inertia Browse page, tests, and migration.
…d recursion guard Also fix AuditEntryRead.id type from str to uuid.UUID to match the AuditEntry model (surfaced by the new tests).
…tity_id filter, non-null created_at
…eteMixin fields from audit diffs
View endpoint now accepts page/page_size as raw strings and sanitizes them (clamp to valid range, fall back to defaults on parse failure) instead of relying on FastAPI Query(ge=, le=) constraints that produce raw JSON 422 errors unfriendly for Inertia page visits. API endpoint retains strict validation — callers get proper 422s.
BUG-002: Entities with DB-assigned integer PKs (e.g. id: int | None = Field(default=None, primary_key=True)) were recorded in the audit log with entity_id="" because _entity_pk_str() ran in before_flush while the PK was still None. UUID PKs were unaffected because default_factory populates them Python-side. The fix splits audit capture into two phases: Phase 1 (before_flush): snapshot_changes() reads attribute history (which is wiped after flush) and stores per-entity diffs alongside the live object reference in session.info — not yet resolved entity_ids. Phase 2 (after_flush_postexec): _after_flush_audit pops the pending snapshots, calls finalize_records() to resolve entity_id from the now-populated PK, and dispatches to the audit_callback. The added AuditEntry rows land in session.new and are flushed when commit runs autoflush. collect_audit_records remains a public single-phase wrapper for tests and any caller whose PKs are already populated.
- ruff format applied (migration, service.py, e2e spec) - ruff check fixed unused __init__.py imports - _excluded_fields return type matches actual frozenset usage - Add 3 e2e tests for audit_log UI (renders, integer-PK regression, filter)
- Extract FilterBar into its own component so Browse.tsx stays under the 300-line cap - Wire htmlFor/id on every filter label so biome's noLabelWithoutControl is satisfied - Split test fixtures into _audit_models.py so test_audit.py drops back under 300 lines
ChangesList previously received the t function via props with a loose TFn alias. react-i18next's useT returns a strictly-typed t that wasn't assignable to the alias. Switching to a local useT() call inside the component drops the TFn alias entirely.
- Ignore ty's invalid-assignment rule globally — every SQLModel contracts/schemas.py with ``model_config = ConfigDict(...)`` trips it because ty cannot see that SQLModelConfig is compatible with pydantic's ConfigDict. Same pattern already used for invalid-argument-type. Run on main locally surfaces the same noise. - Catch ``typer.Exit`` (not ``click.exceptions.Exit``) in test_missing_pyproject_exits_nonzero — modern typer (>=0.20) vendors click under ``typer._click`` so the two classes diverged. - Extract ``_MODULE_USERS`` constant in audit_log/module.py — the hardcoded-strings check rejects module-name literals in depends_on. Matches the pattern in dashboard/module.py. - Fill in audit_log/README.md with Install + Usage sections — the READMEs check requires both. - Drop unused ``# ty: ignore[invalid-assignment]`` comment now that the rule is globally ignored.
antosubashforce-pushed
the
worktree-design-audit-log-module
branch
from
May 28, 2026 09:49
09aac4a to
b3e91e2ComparePR #184 introduced SM_MODULES_ENABLED to exclude Keycloak from the E2E smoke job. The allowlist must now also include AuditLog so the module's routes (/audit_log, /api/audit_log) are mounted — otherwise tests/e2e/test_audit_log_ui.py hits 404.
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Summary
Adds a new
audit_logmodule that automatically tracks field-level changes (create/update/delete/soft-delete) for every SQLModel entity across all installed modules. An admin-only Inertia Browse page provides filtering and pagination over the audit trail.snapshot_changesinbefore_flush,finalize_recordsinafter_flush_postexec) — resolves DB-assigned PKs correctly for integer-PK entities while still capturing SQLAlchemy attribute history that is wiped after flush.AuditEntrymodel, paginated service with filters, REST API + Inertia view, Browse.tsx with filter bar and change diffs, locales, Alembic migration.(created_at desc, id); datetime-local date filter inputs; invalid query params clamped gracefully on view routes.Verification
tests/e2e/test_audit_log_ui.py— page renders, integer-PK regression, entity-type filter. All pass.tests/test_audit_log.py— capture pipeline, API filtering, recursion guard, invalid params, integer-PK fix.framework/db/tests/test_audit.py— diff collection, exclusion logic, soft-delete classification, integer-PK two-phase.entity_id=\"2\"(not empty) in the audit log.QA Report
.qa/reports/qa-report-iteration-1.mdTest plan
/audit_logafter login and confirms the table renders with entries and the filter bar worksentity_id