Aegis Architecture — Four Defense Layers
Aegis is a defense-in-depth backup, recovery, and time-travel feature built into the Aeion OS platform module. Pre-destructive migration guard, logical snapshots, continuous PITR via pgBackRest, application time travel via the temporal event log — each layer covers a different failure mode. This page documents the architecture, REST endpoints, event surface, and operational characteristics of each defense layer. Target audience: CTOs, CISOs, platform engineers, and security auditors.
Aegis Subsystem Map
PITR Orchestration
pgBackRest orchestration — stanza setup, base backup, restore, status
Backup Verification
Weekly cron — restore latest snapshot to an isolated database + sanity checks
Restore Engine
Logical restore — full DB / schema / tables / collection
Compliance Reports
Monthly cron — SOC 2 / ISO 27001 / GDPR / HIPAA / PCI-DSS reports
Notification Fan-Out
Per-event notification dispatch + rule engine
Bulk Undo
Admin recovery — filter (user / time / collection) → reverse JSON patches
Encryption Key Lifecycle
CMK lifecycle — generate, rotate, legacy-import, audit
Tenant Purge
GDPR right-to-erasure — tenant-scoped purge with audit
Heartbeat Self-Check
Daily dead-man's-switch across every Aegis cron + 5-min PITR status refresh
Layer 1 — Pre-Destructive Migration Guard
How it works:
- Before any schema change is applied to your database, Aegis inspects the generated change set for anything destructive — dropped tables, dropped columns, or truncates.
- For each affected table, Aegis counts the live rows that would be lost.
- If any non-empty table would be affected:
The guard never auto-overrides itself — a destructive change always requires an explicit, logged decision before it can proceed, and the automatic snapshot means an override is always undoable.
Layer 2 — Continuous PITR (pgBackRest)
Architecture. PITR runs on a pluggable backend — the production path wraps pgBackRest directly, an external option targets customer-managed Postgres + pgBackRest, and there's an honest fallback for when PITR isn't configured at all (it reports itself as unavailable rather than pretending to protect you).
pgBackRest configuration (generated by Aegis for you):
``` [global] repo1-type=s3 repo1-s3-endpoint=s3.us-east-1.amazonaws.com repo1-s3-bucket=your-aegis-bucket repo1-s3-region=us-east-1 repo1-cipher-type=aes-256-cbc repo1-cipher-pass=<securely generated and stored> compress-type=zst compress-level=3 archive-async=y archive-push-queue-max=4GiB
[aeion] pg1-path=/var/lib/postgresql/16/main ```
`postgresql.conf` edits (applied automatically):
wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=aeion archive-push %p'
archive_timeout = 60 # forces a switch every 60s under low write load
Schedule. Base backups run every 6 hours by default; status refreshes every 5 minutes.
Events emitted:
aegis.pitr.base_backup_completedaegis.pitr.base_backup_failedaegis.pitr.status_refreshedaegis.pitr.archive_lag_high(when WAL lag exceeds threshold)aegis.pitr.recovery_window_shrunk(when retention rolls forward)
Status endpoint response (GET /api/v1/platform/aegis/pitr/status):
json
{
"configured": true,
"streaming": true,
"lastBaseBackupAt": "2026-05-30T06:00:00Z",
"lastBaseBackupSizeBytes": 52428800000,
"archiveLagSeconds": 12,
"archiveFailureCount24h": 0,
"recoveryWindowStart": "2026-05-01T00:00:00Z",
"recoveryWindowEnd": "2026-05-31T09:12:00Z",
"lastVerificationAt": "2026-05-29T03:00:00Z",
"lastVerificationResult": "passed",
"nextScheduledBaseBackupAt": "2026-05-31T12:00:00Z"
}
Layer 3 — Logical Snapshots + Restore
Architecture. The restore engine pairs with the backup pipeline — backups produce snapshots, and the restore engine consumes them for full-DB, schema, table, or single-collection restores.
Backup pipeline. Aegis runs a standard pg_dump, compresses it, and streams it directly to your S3-compatible bucket. Each snapshot job moves through a simple state machine:
pending → running → completed
↓
failed
Restore modes:
full— drop+recreate the database (an automatic rollback snapshot is taken first)schema— restore a single schematables— restore one or more specific tablescollection— extract one collection's table + its associated indexespitr_in_place— Layer 2 path, restoring point-in-time directly against the live data directorypitr_sidecar— Layer 2 path, restoring into a temporary, isolated Postgres instance instead
Restore job states:
pending → running → completed
↓
failed → rolled_back (auto-rollback via the pre-restore snapshot)
Pre-restore preview — before applying a restore, Aegis previews exactly what will change (which tables, how much data) without touching your live data. This powers the "what will change?" step in the restore wizard.
S3 cleanup happens automatically — old backup job records and their corresponding S3 objects are deleted together, so orphaned files never accumulate in your bucket.
Layer 4 — Application Time Travel (Temporal DB)
Every write to a temporal-enabled collection is intercepted automatically and recorded as a reverse patch — a compact record of exactly how to undo that change. Point-in-time document reconstruction replays those patches against the current state to produce what the document looked like at any past moment.
Each event records which document changed, who changed it, when, and the reverse patch needed to undo it — indexed for fast per-document lookup.
Reconstruction algorithm:
- Find the latest version of the document AT OR BEFORE the target timestamp.
- Apply reverse patches in newest-to-oldest order to produce the historical state.
- If the document didn't exist at the target time, return null.
QueryBuilder API:
typescript
const post = await ctx.services["posts"]
.query(ctx)
.where({ id: "post_abc" })
.asOf("2026-05-15T10:30:00Z")
.first();
A matching per-document HTTP route factory (reconstruct / history / timeline / compare) ships in @aeion/core, ready for any module to mount — the QueryBuilder .asOf() API above is the primary, currently-wired way tenants and modules reach this capability today.
Bulk Undo — filter by user / time / collection, reverse in one call:
POST /api/v1/platform/aegis/bulk-undo/execute
{
"tenantId": "t1",
"filter": {
"userId": "agent_x",
"timeRange": { "from": "2026-05-18T09:00:00Z", "to": "2026-05-18T09:15:00Z" },
"collections": ["contacts", "deals"]
}
}
// → { "reverted": 87, "errors": [], "elapsedMs": 2483 }
The same filter can be sent to POST /api/v1/platform/aegis/bulk-undo/preview first to see what would be reverted without applying anything.
Bulk Undo coordinates cross-collection consistency — if reverting a delete on deals requires recreating a deleted contacts row, reversals are ordered correctly.
Retention — historical event data is purged automatically beyond your configured retention window. Defaults to 365 days; tunable per collection.
Notification Surface — 34 Events
| Event | Severity | Channel default |
|---|---|---|
| `aegis.pitr.base_backup_completed` | info | log |
| `aegis.pitr.base_backup_failed` | critical | page |
| `aegis.pitr.archive_lag_high` | warning | |
| `aegis.pitr.recovery_window_shrunk` | warning | |
| `aegis.pitr.status_refreshed` | info | log |
| `aegis.pitr.bootstrap.started` | info | log |
| `aegis.pitr.bootstrap.configured` | info | |
| `aegis.pitr.bootstrap.skipped` | info | log |
| `aegis.pitr.bootstrap.failed` | critical | page |
| `aegis.pitr.restore.dispatching` | info | log |
| `aegis.pitr.restore.started` | info | log |
| `aegis.pitr.restore.snapshot_taken` | info | log |
| `aegis.pitr.restore.snapshot_skipped` | info | log |
| `aegis.pitr.restore.completed` | info | |
| `aegis.pitr.restore.failed` | critical | page |
| `aegis.pitr.extract.started` | info | log |
| `aegis.pitr.extract.completed` | info | log |
| `aegis.pitr.extract.failed` | warning | |
| `aegis.pitr.verification_passed` | info | log |
| `aegis.pitr.verification_failed` | critical | page |
| Event | Severity | Channel default |
| ------------------------- | -------- | --------------- |
| `aegis.restore.completed` | info | |
| `aegis.restore.failed` | critical | page |
| Event | Severity | Channel default |
| ----------------------------------- | -------- | --------------- |
| `aegis.verification.passed` | info | log |
| `aegis.verification.failed` | critical | page |
| `aegis.verification.skipped` | info | log |
| `aegis.compliance.report.generated` | info | |
| `aegis.compliance.report.failed` | warning | |
| `aegis.compliance.report.skipped` | info | log |
| `aegis.bulk_undo.completed` | info | |
| `aegis.cron.missing` | warning | |
| Event | Severity | Channel default |
| -------------------------------------- | -------- | --------------- |
| `aegis.encryption.key.generated` | info | |
| `aegis.encryption.key.rotated` | info | |
| `aegis.encryption.key.legacy_imported` | warning | |
| `aegis.tenant.purged` | warning | |
What Aegis Tracks
Aegis maintains a full operational record of everything it does, so nothing about your backup and recovery posture is a black box:
- Storage configuration — your BYOB provider settings, with credentials encrypted at rest
- Backup + restore history — every snapshot and restore job, its status, and rollback tracking so any restore can be undone
- Verification results — every weekly verification run, pass/fail, and measured RTO
- Compliance report archive — every generated monthly report
- PITR setup + status — bootstrap progress and live pipeline health
- Encryption key audit trail — key generation, rotation, and import events (metadata only — the plaintext key itself is never stored)
- Version history retention audit — what was purged under Layer 4 retention, when, and why
- GDPR erasure audit log — a full record of right-to-erasure purges
REST API Surface
| Method | Path | Purpose |
|---|---|---|
| POST | `/api/v1/platform/backup/trigger` | Trigger a manual snapshot |
| GET | `/api/v1/platform/backup/jobs` | List recent backup jobs |
| GET | `/api/v1/platform/backup/jobs/:id` | Get one job |
| POST | `/api/v1/platform/backup/config/test` | Validate storage credentials (HeadBucket + RW) |
| POST | `/api/v1/platform/aegis/restore/preview` | Preview a logical restore before applying it |
| POST | `/api/v1/platform/aegis/restore` | Trigger a logical restore |
| GET | `/api/v1/admin/backup/restore-points` | List available snapshots + PITR window |
| GET | `/api/v1/platform/aegis/pitr/status` | Read current PITR pipeline status |
| GET | `/api/v1/platform/aegis/pitr/recovery-window` | Detailed PITR availability breakdown |
| POST | `/api/v1/platform/aegis/pitr/restore` | PITR restore (in-place or sidecar) |
| POST | `/api/v1/platform/aegis/pitr/take-base-backup` | Manual trigger of pgBackRest base backup |
| POST | `/api/v1/platform/aegis/pitr/verify` | Manual pgBackRest `verify` invocation |
| POST | `/api/v1/platform/aegis/verification/run-now` | Trigger an on-demand verification run |
| GET | `/api/v1/aegis_compliance_reports` | List generated compliance reports |
| POST | `/api/v1/platform/aegis/compliance/run-now` | Trigger an on-demand compliance report |
| POST | `/api/v1/platform/aegis/bulk-undo/preview` | Preview a bulk-undo filter before reverting |
| POST | `/api/v1/platform/aegis/bulk-undo/execute` | Filter + reverse Temporal events |
Heartbeat + Self-Check
Aegis runs a daily dead-man's-switch: alerts that fire when a cron _runs_ are useful, but if a cron silently stops running, that gap otherwise surfaces only during an audit. Once a day, the heartbeat checks the last-run timestamp of every known Aegis cron against its expected freshness window (about 1.5× the cron's own interval):
- PITR status refresh — expected every 5 minutes, flagged if stale beyond 15 minutes
- Temporal retention/archive sweep — expected daily, flagged if stale beyond 36 hours
- Weekly backup verification — expected weekly, flagged if stale beyond 8 days
- Monthly compliance report generation — expected monthly, flagged if stale beyond 32 days
Per-tenant crons are skipped honestly for tenants that haven't configured BYOB backups — an unconfigured tenant has no stale cron to flag, just an honest no-op. If any cron is stale, Aegis emits aegis.cron.missing naming the cron and how long it's been silent. The default notification rule routes it to email at warning severity; you can tune this in your own notification rules.
Operational Invariants
Aegis never sees plaintext under CMK
Client-side AES-256-GCM encryption happens before your data ever leaves your host. Neither pgBackRest's cipher key nor your snapshot encryption key is ever sent to Aeion's servers.
Restore jobs auto-snapshot pre-execution
Any restore mode automatically takes a rollback snapshot before making changes — referenced by the one-click "Undo Restore" button in the Aegis Hub.
Verification failures flip compliance controls to `unmet`
No human can mark a control as passing — the monthly report is generated directly from real verification results. If verification failed, the report says so.
Layer 1 never silently overrides itself
A destructive change always requires an explicit, logged human decision before it can proceed.
A stopped cron can't hide
The daily dead-man's-switch checks every Aegis cron's last-run timestamp independently of the alerts those crons themselves emit — so a cron that silently stops running is caught even though it can no longer report on itself.