Skip to content

fix: audit storage & migration compatibility for admin/operational su… - #1694

Merged
Baskarayelu merged 2 commits into
Remitwise-Org:mainfrom
yunus-dev-codecrafter:fix/1679-audit-storage-migration-compatibility
Aug 30, 2026
Merged

fix: audit storage & migration compatibility for admin/operational su…#1694
Baskarayelu merged 2 commits into
Remitwise-Org:mainfrom
yunus-dev-codecrafter:fix/1679-audit-storage-migration-compatibility

Conversation

@yunus-dev-codecrafter

Copy link
Copy Markdown
Contributor

Closes #1679
…rfaces
PR title: fix: audit storage & migration compatibility for admin/operational surfaces

Closes #1679

Summary

This PR hardens the storage backing every privileged/operational workflow —
the RBAC guard, admin controllers, and audit review views — so it provides a
deterministic, reviewable guarantee under normal, invalid, repeated,
concurrent, and failure conditions. Previously an admin boundary or recovery
gap could expose operational controls or leave old records unreadable; this
change closes that gap with a forward/backward compatibility contract, a
resumable and observable migration, and guard-level resilience so no rejected,
stale, repeated, or failed operation leaves unauthorized or partial state.

Scope

  • Area: authorization / resilience
  • Starting point: meridian-api/src/auth/guard/rbac/ plus the audit storage it
    writes to (meridian-api/src/audit, meridian-api/src/database/migrations).

What changed

1. Compatibility contract — src/audit/audit-storage.compatibility.ts

  • isSchemaCompatible() / SUPPORTED_SCHEMA_VERSIONS — forward & backward
    version negotiation; legacy rows with no version column are always readable.
  • assertWriteBackwardCompatible() — rejects (degraded, never thrown) writes
    that exceed the column length bounds older readers assume, so a newer writer
    can never emit a row an older reader cannot parse.
  • normalizeLegacyAuditRow() — fills safe defaults for columns introduced after
    a row was written (correlationId, chainHash, contributionXp, epochNumber,
    schemaVersion, …); unknown future enum action values degrade to
    UNKNOWN_ACTION instead of crashing a reader.

2. Resumable, observable migration — 1787400000000-audit-storage-compat.ts

  • Idempotent DDL (IF NOT EXISTS / IF EXISTS) so reruns and post-failure
    resumes are safe.
  • audit_storage_checkpoint table records phase + timestamp (partial-progress
    support) and raises RAISE NOTICE progress for operator observability.
  • Stamps existing rows at schemaVersion = 3 (prior version) and drops in a
    separate schemaVersion column; clean rollback that preserves all audit
    records.

3. RBAC guard resilience — src/auth/guard/rbac/rbac.guard.ts

  • Idempotency: repeated/replayed identical authorization decisions are
    de-duplicated within a 5s window so a retry cannot leave duplicate/partial
    audit state.
  • Degraded mode: when the audit store is unavailable, a structured
    audit.degraded_mode + audit.write_failed marker pair is emitted while the
    authorization decision remains authoritative (deny still denies, allow still
    allows).
  • Forward-compat gate applied before every write; schemaVersion stamped on
    each audit record.
  • Public behavior preserved: exceptions and allow/deny semantics are unchanged.

4. Audit writer & entity

  • AuditService.log() / logContractEvent() stamp schemaVersion.
  • AuditLog entity gains the schemaVersion column.

5. Tests (focused regression at the integration boundary) — all green

  • rbac.guard.resilience.spec.ts — degraded storage still denies/allows with no
    partial state; degraded-mode marker emitted; repeated decisions collapsed to
    one record; distinct decisions recorded separately; schemaVersion stamped.
  • audit-storage.compatibility.spec.ts — legacy fixtures normalize; version
    negotiation; length-bound rejection per column.
  • 1787400000000-audit-storage-compat.spec.ts — migration contract proving
    upgrade, rollback, rerun (idempotent), and partial-progress resume.

6. Documentation — docs/audit-storage-migration-compatibility.md

Design & invariants, failure behavior, compatibility impact, migration/rollback
considerations, operational limitations, and security assumptions.

Acceptance criteria — checklist

  • Define forward and backward compatibility, preserve existing records,
    make migrations resumable and observable (compatibility contract,
    checkpoint-based migration).
  • Preserve compatible public behavior; make migration/error/response-shape
    changes explicit (only additive schemaVersion + checkpoint; documented).
  • Rejected, stale, repeated, and failed operations leave no unauthorized or
    partial state (auth-before-audit, idempotency, degraded-mode handling).
  • Focused regression coverage proving the invariant at the integration
    boundary (guard resilience + migration contract + compatibility specs).
  • Validate upgrade, rollback, rerun, partial-progress, and legacy fixtures.

Validation (commands & results)

# formatter
npx prettier --write <changed files>          # PASS (no diffs left on changed files)

# lint
npx eslint <changed files>                    # PASS (0 errors/0 warnings on changed files)

# type/build check
npx tsc --noEmit                              # 0 errors in any changed file
# (pre-existing unrelated failures on main remain: AuditAction.LOGOUT/SIGN_IN/... 
#  and a bcrypt native-module build issue in this Windows/node-v24 sandbox)

# tests
npx jest src/auth/guard/rbac src/audit src/database/migrations/1787400000000-audit-storage-compat.spec.ts
# Test Suites: 5 passed, 5 total; Tests: 48 passed, 48 total

Additional checks:

  • Migration contract (upgrade/rollback/rerun/partial-progress/legacy fixtures)
    verified by the migration spec using a fake QueryRunner (no live DB required).
  • No disabled checks, no generated artifacts, no secrets, no unrelated
    refactors or broad dependency upgrades are included.

Compatibility & rollback

  • Deploy the migration (npm run migration:run) before deploying this code so
    the schemaVersion column exists when writers begin stamping it.
  • Rollback (npm run migration:revert) removes the column + checkpoint table
    and preserves all pre-existing records; the new enum values added earlier are
    intentionally not dropped (Postgres restriction) and are harmless.

Security / correctness note

  • Audit storage is non-authoritative: the RBAC decision is computed from JWT
    claims and never depends on audit-health, so a degraded store cannot weaken
    authorization. Normalization never elevates privilege (unknown actions are
    surfaced for review, never mapped to a granted/denied outcome).
  • In-process de-duplication is per-instance (documented limitation); global
    de-duplication would require a shared store and is out of scope.

Add deterministic storage/migration compatibility guarantees for the audit backing of privileged workflows (RBAC guard, admin controllers, audit views):

  • audit-storage.compatibility: forward/backward compatibility contract (isSchemaCompatible, assertWriteBackwardCompatible length bounds, normalizeLegacyAuditRow legacy fixtures, UNKNOWN_ACTION fallback).
  • 1787400000000-audit-storage-compat migration: idempotent, resumable (audit_storage_checkpoint), observable (RAISE NOTICE) schema evolution; stamps existing rows schemaVersion=3, new writes =4; clean rollback.
  • RbacGuard hardening: dedup repeated/replayed decisions (no duplicate/partial audit state), degraded-mode markers when the audit store is unavailable, forward-compat gate, schemaVersion stamped on audit records.
  • AuditService stamps schemaVersion on every write; entity gains the column.
  • Regression coverage at the integration boundary: upgrade/rollback/rerun/ partial-progress migration contract, guard resilience, and compatibility utility specs (48 tests). Verified pre-existing failures (bcrypt native module, unrelated AuditAction enum build errors) are not introduced here.

…rfaces

Closes Remitwise-Org#1679

Add deterministic storage/migration compatibility guarantees for the audit
backing of privileged workflows (RBAC guard, admin controllers, audit views):

- audit-storage.compatibility: forward/backward compatibility contract
  (isSchemaCompatible, assertWriteBackwardCompatible length bounds,
  normalizeLegacyAuditRow legacy fixtures, UNKNOWN_ACTION fallback).
- 1787400000000-audit-storage-compat migration: idempotent, resumable
  (audit_storage_checkpoint), observable (RAISE NOTICE) schema evolution;
  stamps existing rows schemaVersion=3, new writes =4; clean rollback.
- RbacGuard hardening: dedup repeated/replayed decisions (no duplicate/partial
  audit state), degraded-mode markers when the audit store is unavailable,
  forward-compat gate, schemaVersion stamped on audit records.
- AuditService stamps schemaVersion on every write; entity gains the column.
- Regression coverage at the integration boundary: upgrade/rollback/rerun/
  partial-progress migration contract, guard resilience, and compatibility
  utility specs (48 tests). Verified pre-existing failures (bcrypt native
  module, unrelated AuditAction enum build errors) are not introduced here.
Fix the failing nest build in CI by correcting the corrupted AuthModule:
- import from @nestjs/common (was @nestj/common)
- use TypeOrmModule and import Repository from typeorm
- fix malformed private readonly repo declaration
- reference jwtConfig (was wtConfig) and import AuditModule

Add missing auth audit actions (SIGN_IN, REFRESH, LOGOUT, LOGOUT_ALL,
VERIFY_EMAIL, ISSUE_VERIFICATION_TOKEN, RESEND_VERIFICATION) to
AuditAction used by the auth providers.
@Baskarayelu
Baskarayelu merged commit 921fe8e into Remitwise-Org:main Aug 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Quality][High] administration and operational surfaces: storage and migration compatibility — QE-2026-08

2 participants