Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions openspec/changes/audit-mixin-coverage-guard/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ One task group = one GitHub ticket = one PR, merged before the next group starts

## 1. Coverage guard tests — depends on audit-mixin-auto-population, audit-mixin-rollout-tier3, audit-mixin-rollout-tier4 all being merged

- [ ] 1.1 Confirm the full model inventory is at 100% `AuditMixin`/`AuditColumnsMixin` coverage post-Tier 4 (re-run the original survey); identify any remaining genuine exemptions.
- [ ] 1.2 Add `services/ai/tests/test_audit_mixin_coverage.py`: walk `Base.registry.mappers`, assert each mapped class inherits `AuditMixin`/`AuditColumnsMixin` or is in a documented exemption set.
- [ ] 1.3 Add the equivalent test in `services/budget/tests/`.
- [ ] 1.4 Add the equivalent test in `services/chat/tests/`.
- [ ] 1.5 Add the equivalent test in `services/users/tests/`.
- [ ] 1.6 Verify each guard test fails as expected when a temporary non-compliant model is added locally (sanity-check the assertion actually catches the failure mode, then remove the temporary model).
- [x] 1.1 Confirm the full model inventory is at 100% `AuditMixin`/`AuditColumnsMixin` coverage post-Tier 4 (re-run the original survey); identify any remaining genuine exemptions.
- [x] 1.2 Add `services/ai/tests/test_audit_mixin_coverage.py`: walk `Base.registry.mappers`, assert each mapped class inherits `AuditMixin`/`AuditColumnsMixin` or is in a documented exemption set.
- [x] 1.3 Add the equivalent test in `services/budget/tests/`.
- [x] 1.4 Add the equivalent test in `services/chat/tests/`.
- [x] 1.5 Add the equivalent test in `services/users/tests/`.
- [x] 1.6 Verify each guard test fails as expected when a temporary non-compliant model is added locally (sanity-check the assertion actually catches the failure mode, then remove the temporary model).
- [ ] 1.7 Run all 4 services' test suites clean; PR merged.
11 changes: 11 additions & 0 deletions services/ai/tests/test_audit_mixin_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Every model on this service's Base must inherit an audit mixin, or be in _EXEMPT."""

import app.models
from app.models.base import Base
from shared.tests.audit_mixin_coverage import assert_full_audit_mixin_coverage

_EXEMPT: dict[str, str] = {}


def test_every_model_inherits_audit_mixin():
assert_full_audit_mixin_coverage(app.models, Base, _EXEMPT)
13 changes: 13 additions & 0 deletions services/budget/tests/test_audit_mixin_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Every model on this service's Base must inherit an audit mixin, or be in _EXEMPT."""

import app.models
from app.models.base import Base
from shared.tests.audit_mixin_coverage import assert_full_audit_mixin_coverage

_EXEMPT: dict[str, str] = {
"UserProfileModel": "denormalized cache of users-service state, not independently editable",
}


def test_every_model_inherits_audit_mixin():
assert_full_audit_mixin_coverage(app.models, Base, _EXEMPT)
11 changes: 11 additions & 0 deletions services/chat/tests/test_audit_mixin_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Every model on this service's Base must inherit an audit mixin, or be in _EXEMPT."""

import app.models
from app.models.base import Base
from shared.tests.audit_mixin_coverage import assert_full_audit_mixin_coverage

_EXEMPT: dict[str, str] = {}


def test_every_model_inherits_audit_mixin():
assert_full_audit_mixin_coverage(app.models, Base, _EXEMPT)
13 changes: 13 additions & 0 deletions services/users/tests/test_audit_mixin_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Every model on this service's Base must inherit an audit mixin, or be in _EXEMPT."""

import app.models
from app.models.base import Base
from shared.tests.audit_mixin_coverage import assert_full_audit_mixin_coverage

_EXEMPT: dict[str, str] = {
"SessionModel": "system-issued auth session tied 1:1 to user_id, not a user-edited resource",
}


def test_every_model_inherits_audit_mixin():
assert_full_audit_mixin_coverage(app.models, Base, _EXEMPT)
21 changes: 21 additions & 0 deletions shared/tests/audit_mixin_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import importlib
import pkgutil
from types import ModuleType

from shared.db.audit_mixin import AuditColumnsMixin


def assert_full_audit_mixin_coverage(
models_package: ModuleType, base, exempt: dict[str, str]
) -> None:
"""Imports every module under models_package so a missing __init__.py import can't hide it."""
prefix = models_package.__name__ + "."
for module_info in pkgutil.iter_modules(models_package.__path__, prefix):
importlib.import_module(module_info.name)

missing = sorted(
mapper.class_.__name__
for mapper in base.registry.mappers
if not issubclass(mapper.class_, AuditColumnsMixin) and mapper.class_.__name__ not in exempt
)
assert not missing, f"Models missing AuditMixin/AuditColumnsMixin: {missing}"
Loading