Files
Aegis/backend/app/services/stale_detection_service.py
T
kitos c99cc4946a refactor(docs+comments): add Google-style docstrings and inline comments across backend
Task D — Google-style docstrings (Args/Returns) on every public function,
method, and class across all 158 Python files in the backend. Zero ruff D
violations (pydocstyle Google convention).

Task E — Explanatory one-line comment before every code line (~11600 new
comments). ruff check passes clean after isort re-sort.
2026-06-10 13:25:14 +02:00

128 lines
4.1 KiB
Python

"""Stale coverage detection — marks techniques whose last validated test is older than a configurable threshold.
This is the simple version. The full Decay Engine (Fase 8) will replace
this with a multi-factor, configurable decay model with confidence scores.
"""
# Import logging
import logging
# Import datetime, timedelta, timezone from datetime
from datetime import datetime, timedelta, timezone
# Import func from sqlalchemy
from sqlalchemy import func
# Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session
# Import settings from app.config
from app.config import settings
# Import TechniqueStatus, TestState from app.models.enums
from app.models.enums import TechniqueStatus, TestState
# Import Technique from app.models.technique
from app.models.technique import Technique
# Import Test from app.models.test
from app.models.test import Test
# Assign logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
# Assign STALE_THRESHOLD_DAYS = settings.STALE_THRESHOLD_DAYS
STALE_THRESHOLD_DAYS = settings.STALE_THRESHOLD_DAYS
# Define function detect_stale_coverage
def detect_stale_coverage(db: Session) -> int:
"""Scan all techniques and flag those with stale coverage.
A technique is considered stale when:
- It has a status other than ``not_evaluated``, AND
- Its most recent *validated* test is older than *STALE_THRESHOLD_DAYS*, OR
- It has never had a validated test (but has been manually marked as
covered/partial).
Args:
db (Session): Active SQLAlchemy database session.
Returns:
int: Number of techniques newly flagged as stale (``review_required``
set to ``True``) in this run.
"""
# Assign cutoff = datetime.now(timezone.utc) - timedelta(days=STALE_THRESHOLD_DAYS)
cutoff = datetime.now(timezone.utc) - timedelta(days=STALE_THRESHOLD_DAYS)
# Assign last_validated = func.coalesce(
last_validated = func.coalesce(
Test.blue_validated_at,
Test.red_validated_at,
Test.created_at,
)
# Subquery: latest validated test date per technique
latest_test = (
db.query(
Test.technique_id,
func.max(last_validated).label("last_tested"),
)
# Chain .filter() call
.filter(Test.state == TestState.validated)
# Chain .group_by() call
.group_by(Test.technique_id)
# Chain .subquery() call
.subquery()
)
# Find techniques that are stale
stale_techniques = (
db.query(Technique)
# Chain .outerjoin() call
.outerjoin(latest_test, Technique.id == latest_test.c.technique_id)
# Chain .filter() call
.filter(
# Either tested before cutoff, or never tested at all
(latest_test.c.last_tested < cutoff)
| (latest_test.c.last_tested.is_(None))
)
# Chain .filter() call
.filter(
# Only flag techniques that have a real status (not never-evaluated ones)
Technique.status_global != TechniqueStatus.not_evaluated
)
# Chain .all() call
.all()
)
# Assign count = 0
count = 0
# Iterate over stale_techniques
for tech in stale_techniques:
# Check: not tech.review_required
if not tech.review_required:
# Assign tech.review_required = True
tech.review_required = True
# Assign count = 1
count += 1
# Log info: "Marked %s as stale coverage", tech.mitre_id
logger.info("Marked %s as stale coverage", tech.mitre_id)
# Check: count > 0
if count > 0:
# Commit all pending changes to the database
db.commit()
# Log info:
logger.info(
# Literal argument value
"Stale coverage detection complete — %d techniques flagged", count
)
# Fallback: handle remaining cases
else:
# Log info: "Stale coverage detection complete — no new stale
logger.info("Stale coverage detection complete — no new stale techniques")
# Return count
return count