# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0a4] - 2026-05-15

### Added

- ``AA_DISCORD_AUDIT_IDEMPOTENCY_KEY_TTL_DAYS`` setting (default
  ``0``, disabled). When positive, ``prune_audit_runs`` NULL-ifies
  ``AuditRun.idempotency_key`` on rows older than the cutoff so a
  caller-supplied dedup token (e.g. a date-derived key on a daily
  Beat) can be reused independently of
  ``AA_DISCORD_AUDIT_RUN_RETENTION_DAYS``. The run row itself is
  preserved for the longer retention window; only the unique slot
  is released.
- ``prune_audit_runs --keys-only`` flag to execute the
  idempotency-key expiry phase without deleting any rows.
- Optional Prometheus instrumentation behind the ``[metrics]``
  extra (``pip install aa-discord-audit[metrics]``). Cooperate-
  don't-depend layer in ``aa_discord_audit/_metrics.py``: when
  ``django-prometheus`` is installed the audit's counters and
  histograms register into the default ``prometheus_client.REGISTRY``
  and are exposed by ``django-prometheus``' ``/metrics`` view alongside
  its own HTTP / cache / migration / Celery series. Without the extra
  every metric reference resolves to a no-op stub at near-zero
  runtime cost. Metric catalog: ``aa_discord_audit_runs_total``,
  ``aa_discord_audit_run_duration_seconds``,
  ``aa_discord_audit_findings_total``,
  ``aa_discord_audit_invocations_total``,
  ``aa_discord_audit_webhook_deliveries_total``,
  ``aa_discord_audit_webhook_delivery_seconds``,
  ``aa_discord_audit_idempotency_keys_expired_total``,
  ``aa_discord_audit_runs_pruned_total``. Full label vocabulary,
  bucket layouts, and Grafana recipes live in ``docs/METRICS.md``
  (and ``docs/METRICS.ru.md``).

## [0.1.0a3] - 2026-05-14

### Fixed

- ``noxfile`` import failed under Python 3.10 with
  ``ModuleNotFoundError: No module named 'tomllib'`` — ``tomllib``
  only joined the stdlib in 3.11, but ``requires-python`` accepts
  3.10. ``_nox.mutation`` (imported as a side effect from
  ``noxfile.py``) now does
  ``import tomllib`` on 3.11+ and falls back to the ``tomli``
  back-port on 3.10. ``tomli>=2.0`` is added to the dev group with
  marker ``python_version < '3.11'``, so 3.11+ environments stay
  lean. Verified against all four interpreters (3.10–3.13). This
  unblocks the 3.10 cells of both AA-4 and AA-5 matrix tracks in
  GitLab CI.

## [0.1.0a2] - 2026-05-14

### Fixed

- ``interrogate`` pre-commit hook was failing in GitLab CI with a
  ``SyntaxError`` from a vendored third-party hook (pre-3.7
  ``foo.async(...)`` syntax). Root cause: ``PRE_COMMIT_HOME`` lands
  under the working directory in CI, and ``interrogate`` without an
  explicit PATHS argument scanned every cached hook's site-packages.
  Hook now scopes scanning to ``aa_discord_audit/`` and the
  ``[tool.interrogate].exclude`` config carries belt-and-braces
  entries for ``.cache`` / ``.venv`` / ``.nox`` / ``dist`` /
  ``build``. Side effect: the hook is now roughly an order of
  magnitude faster.

### i18n

- Translate the one Russian-locale entry that ``make messages``
  surfaced after 0.1.0a1: ``FindingSkipReason.INTERNAL_INCONSISTENCY``
  ("Build/apply disagreed on which findings exist" →
  "Расхождение фаз build/apply по составу находок"). Header bumps on
  both ``ru/`` and ``en/`` catalogues to match the cut.

## [0.1.0a1] - 2026-05-14

### Changed

- Collapse the per-finding apply pipeline into `_ApplyFindingExecutor`
  (a frozen dataclass holding `finding` / `run` / `db_finding` /
  `action`, optionally `plan` / `client`). Module-level shim functions
  preserve
  the `~30 mock.patch("..._finding.<name>")` paths that test code and
  external integrations already bind, so the namespace mapping
  survives the move into class scope.
- Squash the migration chain back into a single `0001_initial`. The
  pre-release-only `0002_alter_auditfinding_skipped_reason` is folded
  into the squash so a fresh install applies one migration. No
  deployment ever applied `0002` — there were no published wheels
  between the two.
- Address pre-release code-review findings in the audit core (terminal
  state clobber prevention, resume/abandon loop-safety hardening).

### Fixed

- Resume-loop priority: when an `auth_failed` outcome and a deadline
  expiry race in the same retry sweep, `auth_failed` now wins, so the
  pending kick is not silently abandoned on a transient 401.
- Terminal-state clobber in the apply pipeline that could re-enter an
  `APPLIED` finding on a partial-failure recovery path.
- Tighten redaction across the resume/abandon path so the webhook
  credential cannot leak through a `cli_argv` re-emit on retry.

### Security

- Webhook-URL redaction now also filters non-string log records before
  the regex pass, closing a gap where a `LogRecord` with a non-string
  message body would skip the filter entirely.

### Tooling

- `cosmic-ray` mutation-testing infrastructure (`mutation.sqlite`
  work-queue, `make mutation` / `make mutation-parallel` /
  `make mutation-html`). The AST-aware filter
  (`_nox/filters/cr_type_annotation_filter.py`) drops the PEP-604
  `X | Y` typing-annotation noise that otherwise dominates the
  survivor list. Mutation score on the trimmed corpus: 96.04 %
  (2399 killed / 2498 ran, 836 filtered as type-annotation
  equivalents, 4 incompetent).
- Pre-commit additions: `deptry`, `interrogate`, `detect-secrets`,
  `conventional-pre-commit`. `django-test-migrations` now covers
  forward-apply invariants in CI.
- `nox -s audit` closed (was a stub); refactored `mutation_parallel`
  to dispatch through nox sessions instead of `bash -c`.

### Tests

- Test count: 325 → 630 (+305). The new tests are mutation-targeted
  (truth-table coverage for boolean conditions, bit-disjoint operands
  for `Add_BitOr` / `Add_BitXor`, edge-distinguishing inputs for
  ordering comparisons, structural assertions on keyword-flag pins).
- Auth-fail and resume-race tests hardened against tautological
  assertions (previously a missing CAS would still pass; now the
  assertion locks the CAS state explicitly).

## [0.1.0a0] - 2026-05-09

### Added

- Reconciliation audit core (`build_plan` + `apply_plan`) with frozen
  `AuditPlan`, per-finding 3-phase atomic dance (lock + reclassify +
  decide → HTTP role-strip → persist), and dead-letter queue
  (`PendingKick`) with CAS-guarded retry beat task.
- Per-category action policy with optional per-group and per-state
  overrides nested inside each `OrphanCategory`.
- Per-uid `FindingActionOverride` admin table layered above the
  policy resolver.
- Operator-managed config tables: `BotAccountUid`,
  `ProtectedDiscordMember`, `ManagedRoleException`,
  `FindingActionOverride`, with full append-only `ConfigChangeLog`
  trail attributed via a `ContextVar`-based middleware.
- Append-only audit-trail rows on `AuditRun`, `AuditFinding`,
  `AuditInvocation`, and `ConfigChangeLog` enforced at both the
  manager (`AppendOnlyManager`) and instance (`AppendOnlyModelMixin`)
  layers; FSM transitions go through compare-and-set.
- Operator dashboard: run list + detail, per-finding explain page,
  invocation list, config-change-log list. Bootstrap 5 templates
  extending `allianceauth/base-bs5.html`.
- `audit_discord_roles` management command with subcommands
  `--resume`, `--abandon`, `--diff`, `--explain`, `--policy-preview`,
  `--from-fixture`. Allowlist-based argv redaction.
- `audit_benchmark` synthetic-load command and `prune_audit_runs`
  retention command.
- Celery `audit_orphan_members` task under
  `QueueOnce(graceful=True)`, sharing the
  `discord.user_actions.<uid>` lock with AA's `update_groups`.
- Per-user 24-hour rolling rate limit on accepted audit triggers
  (refused attempts do not count toward the limit).
- Idempotency-key support on `build_plan` to make the same observed
  state reproducibly map to the same `AuditRun`.
- Operator runbook ([`docs/runbook.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/runbook.md)) and
  performance reference ([`docs/performance.md`](https://gitlab.com/eveo7/aa-discord-audit/-/blob/main/docs/performance.md)).
- Russian translation catalogue under
  `aa_discord_audit/locale/ru/`. Compiled `.mo` ships in the wheel.
- Alliance Auth 5.x compatibility on the same private-API surface
  the AA 4.x build uses; tests exercise both majors via the
  `tests_aa5*` nox sessions.

### Security

- First-run dry-run lock: the very first run after install is coerced
  to `report` regardless of operator settings, until an
  `InitialAuditAcknowledgement` row exists. Re-arming requires shell
  access — intentional friction.
- `ProtectedDiscordMember` admin table coerces any matched finding to
  `report`, above policy and per-uid override.
- Webhook URL is treated as a credential: redaction filter masks it
  from logs, exception messages, persisted `cli_argv`, and
  `AuditRun.summary`.
- `X-Audit-Log-Reason` header builder strips Cc and Cf Unicode
  categories before transmission and validates the 512-byte budget at
  `build_plan` time so apply-time calls cannot raise inside an open
  transaction.
- Allowlist-based `cli_argv` redaction — anything outside the known
  argument keys is recorded as `[REDACTED]` regardless of shape.
- `CurrentUserMiddleware` registration is validated at app load —
  `ImproperlyConfigured` on absence so the audit-the-auditor trail
  cannot silently degrade to `<system>`.
- Migration concurrency guard refuses any post-`0001_initial`
  migration that adds a constraint or index without
  `CONCURRENTLY`. Pre-commit and `nox -s migrations_concurrency_check`
  both enforce.

### Tooling

- `uv`-only toolchain (build, lock, sync, run, publish).
- `nox` sessions: `tests`, `tests_matrix`, `tests_aa5`,
  `tests_aa5_matrix`, `tests_compat`, `lint`, `coverage`,
  `typecheck`, `audit`, `migrations_check`,
  `migrations_concurrency_check`, `makemigrations`, `makemessages`,
  `compilemessages`, `markdown_lint`.
- Pre-commit pipeline: ruff (check + format), mypy, basedpyright,
  vulture, xenon, bandit, `uv lock --check`, migrations concurrency
  guard, editorconfig + validate-pyproject + django-upgrade.
- Test coverage ~87% across 325 tests.

## Translations

- [Русский](CHANGELOG.ru.md)
