Repository navigation
Commit 4e5a02a
authored
feat(audit_log): add audit log module with two-phase capture (#185)
* docs: add audit log module design spec
Covers data model, capture mechanism (SQLAlchemy before_flush callback),
module structure, REST API, admin Browse page, and testing strategy.
* docs: add audit log module implementation plan
13-task TDD plan covering framework changes (AuditRecord, callback wiring),
module scaffold, model, service, API, Inertia Browse page, tests, and migration.
* feat(db): add AuditRecord dataclass and collect_audit_records diff logic
* feat(db): wire audit callback into DatabaseState and before_flush listener
* feat(audit_log): scaffold module package with constants and host wiring
* feat(audit_log): add AuditEntry model and DTO schemas
* feat(audit_log): add capture callback
* feat(audit_log): add service layer and deps
* feat(audit_log): add API and Inertia view endpoints
* feat(audit_log): add AuditLogModule with lifecycle hooks
* feat(audit_log): add i18n locale strings
* feat(audit_log): add Browse.tsx page with filters, pagination, and change diffs
* test(audit_log): add integration tests for capture, API filtering, and recursion guard
Also fix AuditEntryRead.id type from str to uuid.UUID to match the
AuditEntry model (surfaced by the new tests).
* migration(audit_log): add audit_log_audit_entry table
* chore: regenerate i18n and package-lock with audit_log module
* fix(audit_log): use datetime-local inputs and preserve page_size in navigation
* fix(test): ensure test_filter_by_action is non-vacuous
* fix(audit_log): deterministic pagination, error-resilient capture, entity_id filter, non-null created_at
* fix(db): correctly classify soft-deleted entities and exclude SoftDeleteMixin fields from audit diffs
* fix(audit_log): gracefully handle invalid query params in view endpoint
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.
* fix(db): two-phase audit capture resolves DB-assigned integer PKs
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.
* chore(audit_log): format files, fix frozenset typing, add e2e specs
- 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)
* chore: add QA report and verification screenshots
* style(audit_log): align Browse page layout with other module pages
* fix(audit_log): satisfy CI checks for the layout pass
- 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
* fix(audit_log): inline useT in ChangesList to satisfy TS strict typing
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.
* fix(ci): unblock PR checks
- 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.
* ci(e2e): include AuditLog in SM_MODULES_ENABLED allowlist
PR #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.1 parent 66bb303 commit 4e5a02a
42 files changed
Lines changed: 4494 additions & 2 deletions
File tree
- .github/workflows
- .qa
- reports
- screenshots/iteration-1
- .verify
- docs/superpowers
- plans
- specs
- framework/db
- simple_module_db
- tests
- host
- migrations/versions
- modules/audit_log
- audit_log
- contracts
- endpoints
- locales
- pages
- components
- packages/i18n/src
- tests
- e2e
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
| 152 | + | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
Loading
Loading
Loading
0 commit comments