# AEGIS CYBER — Internal Ops Training Manual

**Audience:** AEGIS CYBER operations and engineering staff running the platform day-to-day
**Date:** 2026-06-09
**Version:** 1.0
**Rule 5 discipline:** Every operational characteristic and gap below is derived from the verified production record. Claims are labelled by their evidence source. Nothing is softened.

---

## How to use this document

This manual is the **operational layer** on top of the engineer onboarding document. Read `docs/AEGIS_CYBER_ENGINEER_ONBOARDING.md` first — it covers the stack, code structure, security substrate architecture, and governance rules. This document does not repeat that material. What it covers that the onboarding doc does not:

- What a normal operating day looks like (health checks, what to watch)
- Deployment procedure and what to observe at each publish
- The platform's real performance characteristics — the numbers that actually came out of production testing, including the architectural finding that will affect how you plan for load
- The audit chain's known rolling-deploy behaviour and how to characterise a fork vs. an actual security event
- A known production defect in KYT persistence that is currently silently failing
- Incident response
- Tenant management, secret rotation, diagnostic endpoints
- The honest DR posture — what you can act on right now and what you cannot

---

# 1. Pre-shift: Daily Health Check

Before touching any code or responding to any alert, verify the following in order. These take under five minutes and are your baseline for the day.

## 1.1 Boot observables — check after every restart or publish

A clean prod boot emits these lines in this order. Any deviation is a stop-and-investigate:

```
[secret-boot-guard] All N required secrets present and within policy
[schema-migration] M1 dsar_purge_jobs.tenant_id: present
[schema-conformance] PASS: 118 columns verified — tenant_id: 70/70, pii: 47/47, migration: 1/1.
[rls-init] current_tenant_id() function created/updated
[rls-init] Roles provisioned: aegis_app (login), aegis_rls_bypass (BYPASSRLS)
[rls-init] Grant matrix applied: … Option B SELECT-only carve-out … Option Y DML carve-out …
[rls-init] RLS policies complete: 70/70 tables ENABLED with tenant_isolation policy
[rls-init] RLS policy assertion: all 70/70 policies verified USING and WITH CHECK in accepted tenant-isolation predicate set …
serving on port 5000
```

**Stop conditions** — if you see any of these before `serving on port 5000`, the platform did not start correctly and is not serving:
- `[schema-conformance] FAIL` — prod/code schema drift. Surface for investigation immediately. Do not attempt a workaround that bypasses this check.
- `[rls-init] FAIL` or `process.exit(1)` — RLS substrate is broken. Platform correctly refused to serve under a broken security control (Rule 14). Investigate `server/lib/rls-init.ts` output.
- `[secret-boot-guard]` listing any missing or weak secret — a required secret is absent or below minimum length. Add/rotate the secret before the next boot.
- Any `ERROR` before `serving on port 5000` — read the full stack trace; do not assume it is benign.

**The grant-matrix line must name BOTH Option B AND Option Y.** If either is absent, the audit-chain access boundary is partially broken.

## 1.2 DSAR worker tick — check every morning

The DSAR purge worker ticks every 5 minutes. Its log line should read:

```
[dsar-worker] tick complete — claimed:0, completed:0, failed:0, skipped:0, durationMs:NNN
```

`failed:0` is the critical number. A non-zero `failed` count means a data-subject erasure request is stuck and PDPO 2019 compliance obligations are accumulating. Investigate `server/lib/dsar-workflow.ts` immediately.

`skipped` is expected to be non-zero if there are DSAR requests currently in flight (skip-gate logic prevents duplicate concurrent processing). `claimed`/`completed` will be non-zero when real DSAR requests are being processed.

## 1.3 Audit chain state — check weekly (or after any publish)

Query the audit chain integrity:

```
GET /api/resilience/chain/validate
```

Expected: `forensicChain: "VERIFIED"`, `brokenLinks: 0` **within a single container's observation window.**

**Important — rolling-deploy forks (see §5 for full explanation):** You will see fork entries (rows where two chain branches diverge from a common parent) correlated with publish events. These are a known artifact of Replit's rolling-deployment model, not evidence of tampering. How to characterise a fork cleanly vs. a genuine security event is covered in §5.

---

# 2. Deployment Procedure (Rule 13 Cadence)

Every substantive publish follows this sequence. Skipping steps has caused production incidents. The record is in `docs/regulatory/FOLLOW-UPS.md`.

## 2.1 Pre-publish checklist

Before triggering any publish that touches security-critical code (auth, sessions, RLS, encryption, audit chain, multi-tenant isolation — see Rule 9 in `docs/regulatory/STANDING_AGENT_RULES.md`):

1. Confirm all required secrets are present in prod secrets panel (see §7).
2. Confirm `RLS_DIAGNOSTIC_ENABLED` is **absent** from prod secrets (flag-off — the route stays deployed as permanent infrastructure but only activates when the flag is present).
3. Confirm `ENCRYPTION_PROBE_ENABLED` is absent from prod secrets (same discipline).
4. State which standing rules apply to this change and confirm each is satisfied with evidence — not assertion (Rule 20).

## 2.2 Post-publish observation sequence

After every prod publish, observe these in order before declaring the deploy clean:

1. **Boot log** — confirm all eight boot-observable lines (§1.1) are present. No ERROR. Both Option B and Option Y named in grant-matrix line.
2. **Secret-boot-guard count** — confirm the count matches the expected number of required secrets (currently 15 under standard config).
3. **Schema-conformance count** — confirm `118 columns verified` (or the current expected count if columns have been added since this document was written — check `server/lib/schema-conformance.ts`).
4. **Login flow** — log into the prod app. Confirm `source:"session"` and `tenantId:"aegis-sovereign"` (or the relevant tenant) in deployment logs. Confirm login 200. This is the observable that was broken by the T003 RLS landing and the one you most need to confirm.
5. **DSAR worker tick** — wait for the next 5-minute tick. Confirm `failed:0`.

## 2.3 After security-substrate changes

After any change touching RLS, policies, grants, or `current_tenant_id()`:

- Set `RLS_DIAGNOSTIC_ENABLED=true` in prod secrets.
- From a logged-in admin prod browser session: `GET /api/csrf-token`, then `POST /api/internal/rls-diagnostic` with `x-csrf-token` header.
- **17/17 PASS required.** Any failure is a stop-and-investigate (Rule 17).
- After confirming 17/17: remove `RLS_DIAGNOSTIC_ENABLED` from prod secrets (flag-off, route stays deployed).

After any change touching PII encryption:

- Set `ENCRYPTION_PROBE_ENABLED=true` in prod secrets.
- Run `POST /api/internal/encryption-probe` from a logged-in admin prod browser session.
- **20/20 PASS required.**
- After confirming: remove `ENCRYPTION_PROBE_ENABLED` from prod secrets.

---

# 3. Real Performance Characteristics (PTR-002, 2026-06-09)

**Source:** `docs/regulatory/PTR-002_LOAD_TEST_BASELINE_REPORT.md` — first measured prod baseline. Every number below is prod-measured, not estimated.

## 3.1 KYT rule-engine (pure compute)

| Metric | Measured |
|--------|----------|
| Throughput | 281,828 ops/sec |
| Latency P50 | 0.064 ms |
| Latency P95 | **0.081 ms** |
| Latency P99 | 0.146 ms |
| Max spike | 41.3 ms |

**Interpretation:** The rule-engine computation itself is sub-millisecond by a large margin. If a KYT evaluation takes more than a few milliseconds, the bottleneck is not the computation — it is DB persistence, tenant middleware, or network. See §4.1 below for the persistence gap.

## 3.2 Audit log write throughput (single-table INSERT path)

| Metric | Measured |
|--------|----------|
| Throughput | 309 ops/sec |
| Latency P50 | 59.7 ms |
| Latency P95 | 72.3 ms |
| Latency P99 | 262.3 ms |

**⚠ Interpretation caveat — this is a floor, not a ceiling.** The benchmark measures a direct `INSERT INTO audit_logs` only. In production, `storage.createAuditLog()` is the hot path — it is a **two-part operation**: INSERT into `audit_logs` + `chainAuditLog()` which writes a SHA-256 hash-chained entry to `audit_chain_entries`. The full two-part path throughput is lower and has not yet been benchmarked. Do not quote 309 ops/sec as the production audit write throughput — it is the floor for the first INSERT alone.

## 3.3 HTTP request throughput under 20-concurrent-request load — architectural finding

| Metric | Measured |
|--------|----------|
| Throughput | 45 req/sec |
| Latency P50 | **363 ms** |
| Latency P95 | 550 ms |
| Latency P99 | 797 ms |

**This is a real architectural finding, not a number to dismiss.** The loopback route tested (`/api/load-test/ping`) returns a single `{ok: true}` — its intrinsic handler cost is microseconds. The 363 ms P50 is driven entirely by **per-request DB transaction cost from the tenant middleware under pool contention**.

Every non-streaming request runs through the Shape 3 GUC writer (T001, 2026-05-29): the tenant middleware checks out a pool connection, executes `BEGIN` + `SET LOCAL app.current_tenant_id = '...'`, calls the route handler, then `COMMIT`, then releases the connection. That is three sequential DB round-trips per request. At unloaded DB latency (~20 ms per round-trip), a single isolated request costs approximately 60 ms of DB overhead regardless of how lightweight the route handler is.

At 20 concurrent workers sharing a pool of approximately 10 connections, the pool exhausts immediately. With 20 workers / 10 connections, approximately two serialisation waves run back-to-back. Two waves × ~60 ms per wave plus Node.js event-loop overhead produces the observed ~363 ms P50. The mechanism is reproducible from first principles.

**What this means for load planning:** The effective throughput ceiling at 20 concurrent in-flight requests is approximately 45 req/sec across all endpoints regardless of handler complexity. This is a consequence of the T001 design decision — a pinned-client per-request transaction is required for GUC correctness under Drizzle's autocommit model. The trade-off was architect-reviewed and accepted; the cost is now empirically characterised.

**What this does not mean:** The 363 ms loopback number does not tell you what a real client calling a representative endpoint at low concurrency experiences. The unloaded per-request cost (60 ms DB overhead, no pool contention) is a separate measurement that has not yet been run. A benchmark with an external client, low concurrency, and a payload-bearing endpoint is needed before any external latency claim can be made.

**Mitigation paths (not yet implemented):**
- Increase `appPool` max connection count to reduce pool exhaustion at high concurrency.
- Defer the tenant-tx checkout to routes that actually perform tenant-scoped DB work (routes with no DB access could bypass the transaction entirely).

Both require architect design review before implementation.

---

# 4. Known Production Defects — Do Not Miss These

## 4.1 FU-060 — KYT results are silently not persisting in production

**Status: OPEN — not yet remediated as of 2026-06-09.**

**Source:** `docs/regulatory/FOLLOW-UPS.md` §FU-060. Surfaced 2026-06-09 during PTR-002 root-cause investigation.

**What is happening:** Every call to `analyzeTransaction()` (the `/api/kyt/analyze` endpoint, `routes.ts` line 1834) fires `persistKytResult()` to save the result to the `kyt_results` table. `persistKytResult()` inserts a row with `tenant_id = NULL`. The `kyt_results` table is RLS-enforced (T003): the `WITH CHECK` policy requires `tenant_id = current_tenant_id()`. `NULL = current_tenant_id()` evaluates to `FALSE` — PostgreSQL rejects the INSERT with a 42501 error. The `.catch(() => {})` in `kyt-engine.ts` line 193 swallows this silently. No log entry is emitted. No error surfaces to the caller.

**What works:** The KYT risk evaluation itself is correct. The rule-engine computes the score correctly and returns it. The KYT scoring UI works. **Only the persistence side effect is failing.**

**What does not work:** The `kyt_results` table is accumulating no records in production. Any audit, dashboard query, historical analysis, or compliance review that queries `kyt_results` is querying an empty table. This has been silent since T003 landed in prod on 2026-06-04.

**Why it was invisible:** The `.catch(() => {})` was correct in intent (persistence failure should not block the evaluation). But the error is not transient — it is a structural RLS rejection that repeats deterministically on every call. There is no monitoring that surfaces it.

**Operational impact:**
- KYT dashboard statistics drawing from `kyt_results` show no historical data.
- KYT audit trails are incomplete.
- If compliance requires `kyt_results` records as evidence, that evidence does not exist for the period from 2026-06-04 to when FU-060 is remediated.

**What you should NOT do:** Do not attempt to backfill `kyt_results` with raw data. The fix path (Options A/B in FU-060) must be implemented and verified first, then a data-remediation plan designed separately.

**Escalation:** Surface immediately to the engineering lead if this impacts a compliance deadline or a pilot demonstration.

## 4.2 T004b — Existing-row PII encryption migration is not yet complete

**Status: OPEN — new rows write correctly encrypted; existing plaintext rows not yet migrated.**

New-row writes on all 17 PII tables have been encrypting correctly since the T004a publish (2026-06-08, ENC-PROBE-1780939715886 PASS, 20/20). However, rows that existed before T004a landed are still plaintext in the database. T004b (the migration arc) is the highest-risk future operation in the platform's engineering backlog.

**Operational implication:** A database export or direct DB query will show unencrypted PII for pre-T004a rows. The application layer reads and decrypts correctly for new rows; it reads raw plaintext for old rows. Both are operational but the data-at-rest protection is partial until T004b closes.

**Do not attempt a manual migration.** T004b requires a formal architect review, a pause-and-resume design, per-table dev testing with explicit rollback procedures, and operator authorisation per Rule 11. The design brief is at `docs/regulatory/T004B_DESIGN_BRIEF.md` (if created). Surface to engineering lead when T004b scheduling is on the agenda.

---

# 5. Audit Chain — Rolling-Deploy Forks: What They Are and How to Characterise Them

**Source:** `docs/regulatory/FOLLOW-UPS.md`, FU-024 and the 2026-06-07 characterisation record.

## 5.1 The mechanism

The audit chain (`audit_chain_entries`) uses SHA-256 hash-chaining: each row's `previousHash` points to its predecessor. `chainAuditLog()` in `server/lib/audit-chain.ts` serialises writes via an in-process promise queue — this prevents intra-process races. It cannot prevent inter-process races.

Replit's rolling deployment model keeps the old container alive while the new container starts and passes health checks. During this overlap window, both containers have independent `audit-chain.ts` module instances with independent in-memory `previousHash` state. When the new container initialises, it reads the last DB row at startup as its chain tip. If the old container wrote rows after that startup read, both containers are now branching from a common ancestor. Both branches are internally valid (correct hash chains within each branch). The chain validator (`GET /api/resilience/chain/validate`) correctly reports these as broken links.

## 5.2 The June 7, 2026 fork window — characterised

On 2026-06-07 between 17:00:41 and 17:55:41 UTC, 23 chain breaks occurred (rows 7516–7544). All 23 are of this exact class: two concurrent branch writers during a rolling deployment, each branch internally valid, zero breaks outside this window, stable offset consistent with rolling-deploy startup lag.

**Chain state is a benign rolling-deployment artifact — not evidence of tampering.** `tamperDetected:true` works correctly (it correctly detects mutations); the 23 forks do not represent tampered data.

## 5.3 Recurrence: expect this on every future publish

This is a **characterised ongoing characteristic, not a one-time event.** Every future publish where the DSAR worker or snapshot scheduler fires during the rolling-deployment overlap window will produce a new fork of the same benign class. Future prod chain walks should expect additional fork windows correlated with publish events.

## 5.4 How to characterise a fork as benign vs. an investigation trigger

A fork is benign if all of the following hold:

| Check | Expected benign state |
|-------|-----------------------|
| Fork window coincides with a publish event | ✅ yes — cross-reference with deployment log timestamps |
| Both branches share a common parent row | ✅ yes — `previousHash` on both fork heads points to the same ancestor |
| Both branches are internally hash-valid | ✅ yes — each branch hashes cleanly within itself |
| Zero breaks outside the fork window | ✅ yes — chain is linear before the fork, converges after |
| Stable offset between branches | ✅ yes — consistent with startup-read lag (~30–60 s) |

If any of those checks fails — particularly breaks outside a publish window, or a fork that does not converge, or a branch with a chain-validation error within itself — treat it as a security incident and escalate.

## 5.5 What cannot be fixed about this

Repairing fork rows would require editing `audit_chain_entries` content — which is exactly the attack the chain is designed to detect. Repair is not a valid operation. The permanent record in the chain is contemporaneous empirical evidence of the rolling-deploy mechanism; it is preserved intentionally.

The long-term mitigation is a database-level distributed-consensus chain-tip mechanism (DB-locked tip read at startup) rather than in-process module state. This requires an architect design review. It is not yet implemented.

---

# 6. Incident Response

## 6.1 Security alert triage

| Alert type | First response |
|-----------|---------------|
| KYT BLOCK (RULE_001: single transaction > UGX 10M) | Verify transaction ID in threat dashboard. Manual review queue. Do NOT assume false positive without checking the transaction. |
| KYT BLOCK (RULE_002: daily total > UGX 50M) | **Operational caveat:** daily velocity totals are tracked in-memory. They reset on process restart. If a deployment occurred mid-day, the counter restarted. Cross-check transaction logs to reconstruct actual daily total before concluding this is a genuine breach. |
| KYT BLOCK (RULE_003: sanctioned country) | This is a 9-country sanctions blocklist — KP, IR, SY, CU, VE, MM, BY, RU, ZW. Not an EAC regional check. Verify the destination country code against this list. |
| KYT BLOCK (RULE_004: large transaction in restricted hours, 00:00–05:00) | Threshold is UGX 5M (not all transactions). Hours are 00:00–05:00 EAT. There is no 6-hour delay step — the action is BLOCK + manual review flag, immediate. |
| Threat score CRITICAL | SOAR response playbook fires (session freeze on critical). Verify session freeze is active. Escalate to CISO if confirmed threat. |
| RLS diagnostic FAIL probe | Stop. Do not serve further requests until diagnosed. Run `POST /api/internal/rls-diagnostic` (requires `RLS_DIAGNOSTIC_ENABLED=true` in prod secrets). Surface full result to engineering lead. |
| Schema-conformance FAIL at boot | Platform refused to start correctly. This is correct behaviour — do not bypass. Diagnose the drift: compare `server/lib/schema-conformance.ts` column list against `information_schema.columns` in prod. |
| DSAR worker `failed > 0` | A data-subject erasure is stuck. PDPO 2019 timeline is running. Investigate `server/lib/dsar-workflow.ts` immediately. Escalate to compliance lead. |

## 6.2 Performance degradation

If request latency is elevated well above the PTR-002 baseline:

1. Check whether a recent publish occurred — the rolling-deploy overlap briefly elevates latency as pool exhausts across two container instances.
2. Check `appPool` connection exhaustion: high 363 ms+ latency under load is the known tenant-middleware pool-contention mechanism (§3.3), not a new incident.
3. Check for a long-running transaction holding a pool connection — a stuck request can exhaust the pool for all subsequent requests.
4. Check DB connection latency directly (separate from tenant-middleware overhead).

## 6.3 Authentication failure

If login returns 403 or users cannot authenticate:

1. Check whether a recent schema migration or publish changed `user_tenant_roles` — the T003 incident (2026-06-03) broke prod login because the login UTR lookup ran under RLS with no tenant GUC set. Probe: does `withBypassRls()` at the login UTR lookup produce a UTR row?
2. Check whether `aegis-sovereign` tenant and all user UTR rows are present (7 rows in prod as of initial seeding; more if new tenants/users have been added).
3. If `source:"default"` appears in prod logs post-login instead of `source:"session"`, the session is not resolving a tenant — investigate session persistence and `user_tenant_roles`.

---

# 7. Secret Management

## 7.1 Required secrets

15 secrets are boot-guarded (`server/lib/secret-boot-guard.ts`). If any is absent at boot, the platform calls `process.exit(1)` before serving. Absent secrets cause a startup failure, not a silent degraded state. Current list (check `secret-boot-guard.ts` `REQUIRED` array for authoritative names and minimum-length requirements):

Key groups:
- Session/CSRF: `SESSION_SECRET`
- RLS: `AEGIS_APP_DB_PASSWORD`
- PII encryption: `PII_AES_MASTER_KEY`, `PII_HMAC_MASTER_KEY`
- Credential encryption: `ENCRYPTION_KEY`
- Signing: `SLSA_SIGNING_SECRET`, `ONBOARD_SIGNING_SECRET`, `CROSS_PILLAR_SIGNING_SECRET`, `DEMO_PACK_SIGNING_SECRET`, `WAVE10_SIGNING_SECRET`
- Examiner: `EXAMINER_TOKEN_SALT`
- Admin credentials: `BOU_ADMIN_PASSWORD`, `BOU_ADMIN_USERNAME`, `REGULATOR_SEED_PASSWORD`
- PDF: `WAVE10_PDF_SECRET`

**Never print, display, or echo any secret value in any shell command, log, or message.** Rule 1 is absolute. Safe operations: existence check, length check, last-bytes-as-hex. This applies even in a debugging context. If you need to verify a secret's correctness, write a runbook for the operator to execute themselves.

## 7.2 Secret rotation procedure

Rotation is an operator action — the agent/engineer proposes, the operator executes:

1. Confirm no long-running process currently holds a connection using the old credential.
2. Update the secret value in the Replit secrets panel.
3. **Do not publish yet** — confirm the new value satisfies the boot-guard minimum-length requirement.
4. Trigger a publish (or restart dev workflow).
5. Observe boot: `[secret-boot-guard] All N required secrets present` — confirm no `missing` or `weak` line for the rotated secret.

For `AEGIS_APP_DB_PASSWORD` specifically: the `aegis_app` database role password must be rotated in the database first (via a DBA-level `ALTER ROLE` statement), then updated in the secrets panel to match. Both sides must be updated together — the boot will fail if they diverge.

For `PII_AES_MASTER_KEY` / `PII_HMAC_MASTER_KEY`: rotation invalidates all existing encrypted PII columns — existing encrypted rows will fail to decrypt with the new key. A key-rotation migration (decrypt with old key, re-encrypt with new key, swap secret) requires a full architect design review before execution. **Do not rotate PII keys without a migration plan.**

---

# 8. Tenant Management

## 8.1 Current prod tenant state

As of 2026-06-09: one tenant — `aegis-sovereign` ("AEGIS SOVEREIGN", industry=sovereign-operator, tier=platform). All 7 seeded users belong to this tenant with the following roles:

| Username | Role in aegis-sovereign |
|----------|------------------------|
| admin | admin |
| ciso | risk_manager |
| auditor | auditor |
| regulator | auditor (defensible default — see FU-053) |
| regulator_uganda | auditor |
| demo.sod.admin_auditor | auditor |
| demo.sod.risk_admin | admin |

## 8.2 Adding a new tenant

New tenants require:
1. A `tenants` table row (via seed function or admin UI — check whether a tenant creation route exists; see `routes.ts`).
2. A `user_tenant_roles` row for each user that should have access, with `is_primary = true` for the user's primary tenant.
3. The `utr_primary_per_user` partial unique index enforces at most one primary tenant per user — a second `is_primary=true` insert for the same user will fail with a unique-constraint violation.
4. No RLS or schema changes required — tenant isolation is already active for all 70 tenant-scoped tables.

**RLS diagnostic re-run is not required for a new tenant** (the isolation substrate is already in place). It is required if you modify the policies, grants, or `current_tenant_id()` function.

## 8.3 Session tenant resolution

After a successful login, the session resolves the tenant from `user_tenant_roles` where `is_primary = true` for the authenticated user. All subsequent requests run under that tenant's GUC context. The tenant is not caller-supplied (the `x-tenant-id` IDOR surface was removed 2026-06-03).

---

# 9. Disaster Recovery — Honest Posture

The current DR posture is documented in the IT Handover Suite (§6) and repeated here for operational clarity.

## 9.1 What you can act on right now

| Action | Capability |
|--------|-----------|
| Point-in-time restore from pg_dump | ✅ — `pg_dump` runs to local storage. Confirm with your hosting/infrastructure team where dumps are stored and how to restore. |
| Audit trail recovery | ✅ — SHA-256 hash chain is in-DB. A restored backup restores the full chain. The chain will show a break at the point of restore (expected). |
| Application restart on same infrastructure | ✅ — platform boots clean from a fresh container with environment variables intact. |

## 9.2 What you cannot act on right now

| Action | Why not |
|--------|---------|
| Failover to a standby/DR site | No standby site or streaming replication is configured. There is no Nairobi DR site. This is not a temporary gap — it is an infrastructure build item. |
| DNS failover to DR endpoint | No DR endpoint exists. |
| mTLS certificate management | mTLS is not deployed at the application layer. TLS is handled by the hosting platform. |
| DR exercise from the AEGIS DR Exercise Runbook | The runbook at `docs/regulatory/AEGIS_CYBER_DR_EXERCISE_RUNBOOK.md` requires bank-IT to configure streaming replication before the exercise can run. |

**Do not describe any DR capability from the above "cannot" list to external parties as available or upcoming** unless engineering has confirmed the infrastructure is in place. These are operational gaps, not aspirational features.

---

# 10. Diagnostic Endpoints — When and How

Both endpoints are permanent production infrastructure (Rule 17). They are admin-gated and flag-controlled.

## 10.1 RLS isolation diagnostic

**When to run:** After any change to RLS policies, grants, roles, or `current_tenant_id()`. Mandatory before declaring a security-substrate change complete in prod.

**How to run:**
1. Set `RLS_DIAGNOSTIC_ENABLED=true` in prod secrets.
2. From logged-in admin prod browser session:
   - `GET /api/csrf-token` → copy `csrfToken`
   - `POST /api/internal/rls-diagnostic` with header `x-csrf-token: <csrfToken>`
3. Paste the full result JSON to the engineering record. **17/17 PASS required.** Any failure is a stop-and-investigate.
4. After confirming 17/17: remove `RLS_DIAGNOSTIC_ENABLED` from prod secrets (set to absent, not "false").

The route stays deployed. Flags off means the route returns 403 for all callers.

## 10.2 PII encryption probe

**When to run:** After any change to PII encryption wiring, key configuration, or PII column schema.

**How to run:**
1. Set `ENCRYPTION_PROBE_ENABLED=true` in prod secrets.
2. From logged-in admin prod browser session: `POST /api/internal/encryption-probe`.
3. **20/20 PASS required** (17 tables, 40 AES-GCM columns + 2 HMAC-lookup columns verified).
4. Remove `ENCRYPTION_PROBE_ENABLED` from prod secrets after confirming.

---

# 11. Gaps Not Yet Built — Reference Card

These are items an ops engineer will be asked about. The honest answers:

| Item | Honest current state |
|------|---------------------|
| **FU-060 — KYT persistence** | **OPEN.** `kyt_results` table not being populated in prod. Silent failure since 2026-06-04. KYT evaluation correct; audit record absent. Fix requires engineering work (see §4.1). |
| **T004b — Existing-row PII migration** | **OPEN.** New rows encrypted correctly. Pre-T004a plaintext rows not yet migrated. Highest-risk future operation — requires architect review + full runbook before execution. |
| **HSM hardware** | **Not connected.** `server/lib/hsm-integration.ts` is a software simulation. Production encryption uses env-var-derived keys (AES-256-GCM, scrypt KDF, boot-guarded). Hardware HSM integration is a future item. |
| **DR / standby replication** | **Not configured.** See §9. No standby site, no streaming replication, no offsite backup transfer. pg_dump to local storage is the current backup. |
| **Bank-prod on-prem deployment** | **Not yet done.** Docker packaging has been verified. Bank-IT hookup is the pilot integration ask. |
| **Performance: external-client latency** | **Not yet measured.** PTR-002 (loopback) is not a proxy for this. A dedicated external-client benchmark is needed before any < 200 ms claim can be made. |
| **AI scoring accuracy** | **Not benchmarked.** Not yet run against labelled bank transaction data. |
| **June-15 security audit** | Rule 3 cadence — next audit run 2026-06-15, carve-outs register v1.2 by 2026-06-22, operator sign-off by 2026-06-30. |

---

# 12. Where to Find Answers

| Question | Source |
|----------|--------|
| Why does it work this way? | `docs/regulatory/FOLLOW-UPS.md` — 9,600+ lines of engineering history. Use `bash sed -n 'NNN,MMMp'` for ranges beyond ~8,000 lines; the read tool silently truncates. |
| What is the current build order? | `docs/regulatory/BUILD_PRIORITY_LIST.md` |
| What are the governance rules? | `docs/regulatory/STANDING_AGENT_RULES.md` — 21 rules, full text, incident anchors, worked examples |
| What is verified vs. simulated vs. not-built? | `docs/regulatory/AEGIS_CYBER_SECURITY_DOSSIER_FOR_BOU_2026-05-23.md` §8 — with evidence citations |
| What is the encryption architecture? | `docs/regulatory/T004A_DESIGN_BRIEF.md` — per-column decision matrix |
| What are the RLS policy details? | `docs/regulatory/T003_DESIGN_BRIEF.md` and `server/lib/rls-init.ts` |
| What is the performance baseline? | `docs/regulatory/PTR-002_LOAD_TEST_BASELINE_REPORT.md` |
| What are the known legacy carve-outs? | `docs/regulatory/AEGIS_CYBER_LEGACY_CARVEOUTS.md` |

---

*AEGIS CYBER — Internal Operations Training Manual v1.0 — 2026-06-09*
*Audience: internal AEGIS CYBER engineering and operations staff only. Not for distribution to clients or regulators.*
