RMI Platform
feat(v3): ship new system β€” skip _legacy_main, mount v1 routers
3cf0daf
|
Raw
History Blame Contribute Delete
28.4 kB
# RMI Backend β€” 2026 Architecture Design
## Why this exists
The current backend (`/root/backend/`) has grown organically:
- `main.py` is 10,305 lines
- 124 router files flat in `app/routers/`
- 14 RAG modules scattered at top of `app/`
- `token_scanner.py` is 4,109 lines
- `x402_tools.py` is 5,817 lines
- Cross-cutting concerns (redis, auth, errors) duplicated across modules
- Domain logic entangled with FastAPI
- No tests, no type safety, no clear boundaries
15 mechanical refactor tasks would patch symptoms. This design fixes the architecture.
## Target Layout
```
/root/backend/
β”œβ”€β”€ pyproject.toml uv + ruff + mypy + pytest config (single source)
β”œβ”€β”€ .pre-commit-config.yaml ruff + mypy + size cap + gitleaks
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ alembic/ async migrations
β”‚
β”œβ”€β”€ app/
β”‚ β”œβ”€β”€ main.py <100 lines: app factory + lifespan + middleware ONLY
β”‚ β”œβ”€β”€ config.py pydantic-settings, env loading
β”‚ β”‚
β”‚ β”œβ”€β”€ core/ cross-cutting, NO business logic
β”‚ β”‚ β”œβ”€β”€ logging.py structlog JSON + correlation ID
β”‚ β”‚ β”œβ”€β”€ errors.py AppError hierarchy + FastAPI handlers
β”‚ β”‚ β”œβ”€β”€ redis.py async client + get_redis() Depends()
β”‚ β”‚ β”œβ”€β”€ db.py async SQLAlchemy session
β”‚ β”‚ β”œβ”€β”€ auth.py JWT decode + role guards
β”‚ β”‚ β”œβ”€β”€ lifespan.py startup/shutdown
β”‚ β”‚ β”œβ”€β”€ middleware.py CORS, rate limit, correlation ID
β”‚ β”‚ β”œβ”€β”€ websocket.py WS connection manager
β”‚ β”‚ β”œβ”€β”€ tracing.py OpenTelemetry + Langfuse v4 init
β”‚ β”‚ β”œβ”€β”€ http.py async httpx client
β”‚ β”‚ β”œβ”€β”€ pagination.py cursor-based
β”‚ β”‚ β”œβ”€β”€ metrics.py Prometheus /metrics endpoint (P0 v3)
β”‚ β”‚ └── legacy.py @legacy decorator for deprecation tracking
β”‚ β”‚
β”‚ β”œβ”€β”€ api/ HTTP transport, thin routes
β”‚ β”‚ β”œβ”€β”€ deps.py shared Depends (current_user, redis, etc)
β”‚ β”‚ β”œβ”€β”€ v1/
β”‚ β”‚ β”‚ β”œβ”€β”€ public/ no auth β€” scanner, wallet, token, pricing, health
β”‚ β”‚ β”‚ β”œβ”€β”€ auth/ JWT β€” portfolio, alerts, intel, profile
β”‚ β”‚ β”‚ β”œβ”€β”€ admin/ admin β€” users, system, ops
β”‚ β”‚ β”‚ β”œβ”€β”€ x402/ paid β€” tools, tokens, wallets, defi, security
β”‚ β”‚ β”‚ └── mcp/ MCP β€” tools.py
β”‚ β”‚ └── ws/ WebSocket
β”‚ β”‚ └── alerts.py
β”‚ β”‚
β”‚ β”œβ”€β”€ domain/ pure business logic, NO FastAPI imports
β”‚ β”‚ β”œβ”€β”€ scanner/ core + honeypot + rugcheck + holders + contract + deployer + models + service
β”‚ β”‚ β”œβ”€β”€ wallet/ analyzer + labels + behavior + models + service
β”‚ β”‚ β”œβ”€β”€ token/ discovery + supply + models + service
β”‚ β”‚ β”œβ”€β”€ rag/ embeddings + chunking + search + ingest + firehose + feedback + agentic + evaluation + tracing + router + permanence + models + service
β”‚ β”‚ β”œβ”€β”€ x402/ facilitator + tokens + enforcement + settlement + models + service
β”‚ β”‚ β”œβ”€β”€ intel/ feeds + narratives + graph + models + service
β”‚ β”‚ β”œβ”€β”€ scam/ classifier + patterns + models + service
β”‚ β”‚ β”œβ”€β”€ databus/ client + chains(96) + models + service
β”‚ β”‚ └── bulletin/ board + models + service
β”‚ β”‚
β”‚ β”œβ”€β”€ infra/ external integrations
β”‚ β”‚ β”œβ”€β”€ ollama.py
β”‚ β”‚ β”œβ”€β”€ langfuse.py
β”‚ β”‚ β”œβ”€β”€ vector_store.py
β”‚ β”‚ β”œβ”€β”€ chains/ evm + solana + bitcoin + base + ...
β”‚ β”‚ β”œβ”€β”€ apis/ coingecko + etherscan + birdeye + goplus + arkham + dune + ...
β”‚ β”‚ └── providers/ ollama + openrouter + huggingface + ...
β”‚ β”‚
β”‚ β”œβ”€β”€ agents/ v3 NEW β€” bounded-task AI agent loops (M1)
β”‚ β”‚ β”œβ”€β”€ loop.py frozen: TaskInput/LoopBudget/TaskOutput Pydantic models
β”‚ β”‚ β”œβ”€β”€ fact_store.py Redis-backed long-term memory
β”‚ β”‚ β”œβ”€β”€ kill_switches.py budget enforcers (iterations, tokens, wall, spend)
β”‚ β”‚ └── executor.py bounded loop runner
β”‚ β”‚
β”‚ β”œβ”€β”€ mcp/ v3 NEW β€” MCP mesh v2 (M2)
β”‚ β”‚ β”œβ”€β”€ manifest.py frozen: MCPToolManifest Pydantic model
β”‚ β”‚ β”œβ”€β”€ registry.py FastAPI on :8643 β€” list/resolve tools
β”‚ β”‚ β”œβ”€β”€ auth.py gopass-backed auth scopes
β”‚ β”‚ └── servers/ opencti + dify + n8n FastMCP servers
β”‚ β”‚
β”‚ β”œβ”€β”€ middleware/ v3 NEW β€” Starlette middleware classes (M7)
β”‚ β”‚ β”œβ”€β”€ cost_tracking.py frozen: per-tenant/per-route cost middleware
β”‚ β”‚ β”œβ”€β”€ rate_limit_v2.py per-tier sliding window
β”‚ β”‚ β”œβ”€β”€ auth_deps.py JWT/API key on legacy routes
β”‚ β”‚ β”œβ”€β”€ security_headers.py CSP, HSTS, X-Frame-Options
β”‚ β”‚ └── shadow_traffic.py v1/v2 response diffing
β”‚ β”‚
β”‚ └── workers/ background jobs (separate from API)
β”‚ β”œβ”€β”€ firehose.py
β”‚ β”œβ”€β”€ scanner_queue.py
β”‚ β”œβ”€β”€ ingest_cron.py
β”‚ └── cleanup.py
β”‚
β”œβ”€β”€ docs/
β”‚ β”œβ”€β”€ adr/ v3 NEW β€” Architecture Decision Records (M6)
β”‚ β”œβ”€β”€ runbooks/ v3 NEW β€” incident response (M6)
β”‚ β”œβ”€β”€ postmortems/ v3 NEW β€” incident learnings (M6)
β”‚ β”œβ”€β”€ oncall/ v3 NEW β€” rotation doc (M6)
β”‚ β”œβ”€β”€ load/ v3 NEW β€” k6 baseline reports (P2 #26)
β”‚ β”œβ”€β”€ contract/ v3 NEW β€” Pact contract specs (P2 #27)
β”‚ └── plans/
β”‚ β”œβ”€β”€ UNFUCK-V1.md Frozen β€” superseded
β”‚ β”œβ”€β”€ UNFUCK-V2.md Frozen β€” superseded
β”‚ β”œβ”€β”€ UNFUCK-V3.md ACTIVE β€” AI-Forward Ship Edition (this document's parent)
β”‚ └── INTEGRATION-LOG.md append-only worklog of v3 execution
β”‚
└── tests/
β”œβ”€β”€ conftest.py
β”œβ”€β”€ unit/
β”‚ β”œβ”€β”€ core/ 33+ core tests
β”‚ └── domain/ 58+ domain tests
β”œβ”€β”€ e2e/ v3 NEW β€” Playwright (P2 #25)
β”œβ”€β”€ contract/ v3 NEW β€” Pact (P2 #25)
β”œβ”€β”€ load/ v3 NEW β€” k6 scripts (P2 #25)
└── rag/
β”œβ”€β”€ eval_suite.py v3 NEW β€” retrieval quality regression (M3)
└── prompts/
└── test_snapshots.py v3 NEW β€” prompt regression
```
## Key Design Principles
1. **STRICT LAYERING.** `api β†’ domain β†’ infra`. Never reverse. Domain knows nothing about HTTP.
2. **ONE SOURCE OF TRUTH for cross-cutting.** redis/auth/errors/logging live in `core/` exactly once. Routes import, never redefine.
3. **HARD SIZE CAP.** 500 lines per file. Enforced in pre-commit. No 4,109-line `token_scanner.py` ever again.
4. **THIN ROUTES.** Routes parse β†’ call service β†’ return. No business logic in HTTP layer.
5. **DOMAIN = PURE PYTHON.** `domain/scanner/` can be unit tested without spinning up FastAPI. This is the test that proves the architecture.
6. **WORKERS SEPARATED.** Background jobs don't pollute the API. firehose, scanner_queue, ingest_cron live in `workers/`.
7. **PYDANTIC V2 EVERYWHERE.** Every domain has `models.py`. No `dict` types crossing boundaries.
8. **ASYNC-ONLY.** No sync I/O in handlers. Same shape for the whole codebase.
9. **OBSERVABILITY BY DEFAULT.** structlog JSON + correlation ID + OTel + Langfuse in `core/tracing.py`. Every endpoint instrumented without opt-in.
10. **STRANGLER FIG MIGRATION.** New skeleton co-exists with old code. Old `main.py` keeps importing the old routers. New routes added alongside. Per-domain cutover, not big-bang.
## Migration Order
| Order | Domain | Why |
|-------|--------|-----|
| 0 | `rag_engine` shim | unblock prod crash, temp until `app/rag/` lands |
| 1 | `core/` | foundation everyone depends on |
| 2 | `infra/` | external integrations domain depends on |
| 3 | `alerts` | smallest, well-bounded, has WS + JWT + redis β€” proves full pattern |
| 4 | `wallet` | high-value, used by frontend |
| 5 | `token` | high-value |
| 6 | `scanner` | biggest (4,109 lines), do last when pattern is mature |
| 7 | `x402` | payment system, critical, mature pattern by then |
| 8 | `intel`, `scam`, `databus`, `bulletin` | long tail |
| 9 | `rag` consolidation (was 14 files) | last because it's the most coupled |
---
# THE 14-POINT MODERN BUILDER BAR (v3 β€” non-negotiable quality gate)
Every file that touches the unfuck MUST clear this. Pre-commit enforces what can be enforced.
```
# Standard Status Enforcement
1 Pydantic v2 strict (ConfigDict) PARTIAL pre-commit mypy
2 async-only handlers DONE pre-commit AST scan
3 <500 lines/file (pre-commit) DONE pre-commit file-size-cap
4 pre-commit hooks (ruff+mypy+gitleaks+
detect-secrets+bandit+file-size+pytest) PARTIAL install on dev + CI
5 typed errors (18/18) β€” currently 4/18 4/18 pre-commit AST scan
6 1 type per layer (api→domain→core→infra) PARTIAL architecture test
7 structlog everywhere DONE pre-commit no-print()
8 event bus (Redis pub/sub) PARTIAL manual review
9 per-domain /health/{domain} PARTIAL manual review
10 per-route cost tracking NONE M7 middleware (P2)
11 no from-main-import DONE pre-commit AST scan
12 single get_redis() DONE pre-commit no-new-get-redis
13 OTel trace coverage >85% 0% M4 wiring (P1)
14 Langfuse span coverage >95% LLM calls PARTIAL M4 wiring (P1)
```
**Items 5, 10, 13, 14 are the v3 additions.**
---
# THE 30 MUST-DO ITEMS (v3 β€” ACTIVE PLAN)
## P0 β€” Week 1 (32h total β€” 16h human + 16h M3)
| # | Item | Hours | Owner | Status | File |
|---|------|-------|-------|--------|------|
| 1 | main.py lifespan merge crash fixed | 1 | human | βœ… DONE | `main.py` (94 lines) |
| 2 | Audit 53 uncommitted modifications | 3 | human | ⏳ TODO | `git diff HEAD` |
| 3 | 65 broken crons β†’ Tailscale IP | 4 | human | ⏳ TODO | crons 167.86.116.51 β†’ 100.100.18.18 |
| 4 | /metrics endpoint | 2 | M3 | ⏳ TODO | `app/core/metrics.py` (NEW) |
| 5 | AlertManager β†’ Telegram | 2 | M3 | ⏳ TODO | `docker-compose.yml` + `alertmanager.yml` |
| 6 | Embedder dual-dim wrapper (M3) | 8 | M3 | ⏳ TODO | `app/rag/embedder.py` (NEW) |
| 7 | WalletService proper async impl | 6 | M3 | ⏳ STUBS DONE, REAL PENDING | `app/domain/wallet/service.py` |
| 8 | Frontend SPA migrate Contaboβ†’netcup | 2 | human | ⏳ TODO | `/var/www/rmi/` |
| 9 | GHOST_ADMIN_KEY from gopass | 1 | human | ⏳ TODO | `.env` |
| 10 | Load avg 14.19 β†’ identify cause | 3 | human | ⏳ TODO | Erigon or Ollama? |
## P1 β€” Week 2 (37h total β€” 3h human + 34h M3)
| # | Item | Hours | Owner | Status | File |
|---|------|-------|-------|--------|------|
| 11 | GlitchTip self-host + wire SDK | 4 | M3 | ⏳ TODO | `docker-compose.glitchtip.yml` (NEW) |
| 12 | OTel auto-instrumentation (M4) | 6 | M3 | ⏳ TODO | `app/core/tracing.py` wire into lifespan |
| 13 | GitHub Actions CI | 8 | M3 | ⏳ TODO | `.github/workflows/ci.yml` (NEW) |
| 14 | Renovate + Dependabot | 2 | M3 | ⏳ TODO | `.github/dependabot.yml` (NEW) |
| 15 | Trivy in CI | 2 | M3 | ⏳ TODO | `.github/workflows/ci.yml` |
| 16 | Semgrep SAST in CI | 2 | M3 | ⏳ TODO | `.github/workflows/ci.yml` |
| 17 | Gitleaks + Trufflehog | 2 | M3 | ⏳ TODO | `.pre-commit-config.yaml` + CI |
| 18 | Vault rotation script (gopass) | 3 | human | ⏳ TODO | `scripts/rotate_secrets.sh` (NEW) |
| 19 | Rate limit middleware on all /api/v1/* | 4 | M3 | ⏳ TODO | `app/core/middleware.py` |
| 20 | Auth Depends() on legacy routes | 4 | M3 | ⏳ TODO | `app/core/middleware.py` |
## P2 β€” Week 3 (46h total β€” 12h human + 34h M3)
| # | Item | Hours | Owner | Status | File |
|---|------|-------|-------|--------|------|
| 21 | Security headers (CSP, HSTS, X-Frame) | 3 | M3 | ⏳ TODO | `app/core/middleware.py` |
| 22 | Backup restore drill | 3 | human | ⏳ TODO | R2 bucket test |
| 23 | 4 Grafana dashboards | 5 | M3 | ⏳ TODO | `grafana/dashboards/*.json` |
| 24 | Container memory limits (all 36) | 3 | M3 | ⏳ TODO | `docker-compose.yml` |
| 25 | tests/{e2e,contract,load}/ scaffolding | 4 | M3 | ⏳ TODO | `tests/e2e/` `tests/contract/` `tests/load/` |
| 26 | k6 baselines (top 50 routes) | 6 | M3 | ⏳ TODO | `tests/load/baseline.js` |
| 27 | Pact contracts + openapi-generator SDK | 5 | M3 | ⏳ TODO | `tests/contract/` |
| 28 | Top 10 ADRs | 5 | human | ⏳ TODO | `docs/adr/0001-0010.md` |
| 29 | Runbooks + postmortem + oncall | 4 | human | ⏳ TODO | `docs/runbooks/` `docs/postmortems/` `docs/oncall/` |
| 30 | Agent Loop Spec productionization (M1) | 8 | M3 | ⏳ TODO | `app/agents/loop.py` (NEW, frozen on creation) |
## P3 β€” Defer until revenue
Multi-region read replicas. SOC2 audit. Paid third-party pen-test. SBOM distribution. AsyncAPI WebSocket spec. Frontend RUM (Sentry browser SDK, web vitals). Pyroscope continuous profiling. OWASP ZAP.
---
# THE 8 AI-FORWARD MODULES (v3)
## M1: Agent Loop Productionization (P2 #30)
**Deliverable: `app/agents/loop.py` (frozen on creation)**
```python
from pydantic import BaseModel, Field, ConfigDict
class TaskInput(BaseModel):
model_config = ConfigDict(strict=True, frozen=True)
task_id: str = Field(..., pattern=r"^\d+-[a-z0-9-]+$")
description: str = Field(..., max_length=500)
context_budget_tokens: int = Field(default=8000, le=32000)
allowed_tools: list[str] = Field(..., min_length=1)
success_criteria: str = Field(..., max_length=300)
verify_command: str = Field(..., max_length=200)
class LoopBudget(BaseModel):
model_config = ConfigDict(strict=True, frozen=True)
max_iterations: int = Field(default=20, le=100)
max_tokens: int = Field(default=50_000, le=500_000)
max_wallclock_seconds: int = Field(default=900, le=3600)
max_spend_usd: float = Field(default=5.0, le=50.0)
class TaskOutput(BaseModel):
task_id: str
files_touched: list[str]
lines_added: int
lines_removed: int
verify_passed: bool
worklog_entry: str
spend_usd: float
iterations_used: int
```
**Kill switches** (every iteration):
- max_iterations ≀ 20 (max 100)
- max_tokens ≀ 50K (max 500K)
- max_wallclock ≀ 900s (max 3600s)
- max_spend ≀ $5 (max $50)
- verify_command_failed_3x β†’ human review
**fact_store companion** (`app/agents/fact_store.py`):
- Redis key: `fact:{namespace}:{key}`
- TTL: 86400s
- Loaded at loop start, written at loop end
- Prevents re-discovery of known facts
**Acceptance gate: 7 consecutive days zero runaway.**
## M2: MCP Mesh v2 (P2 #24, D9)
**Deliverable: `app/mcp/manifest.py` (frozen on creation)**
```python
from pydantic import BaseModel, Field
from typing import Any
class MCPToolManifest(BaseModel):
name: str = Field(..., pattern=r"^[a-z_]+:[a-z_]+$") # e.g. "rmi-netcup:docker_ps"
version: str = Field(..., pattern=r"^\d+\.\d+\.\d+$")
server: str = Field(..., pattern=r"^[a-z-]+$")
description: str = Field(..., max_length=200)
input_schema: dict[str, Any]
output_schema: dict[str, Any]
auth_scope: str = Field(..., pattern=r"^[a-z_]+:[a-z_]+$") # gopass scope
deprecated: bool = False
successor: str | None = None
```
**Mesh registry on netcup `:8643`** with:
- GET /tools (list all with filters)
- GET /tools/{name} (full manifest)
- POST /resolve (name+MAJOR version β†’ executor endpoint)
**3 unwired servers to wire in D9:**
- OpenCTI (40 tools) β€” wire with FastMCP
- Dify (18 tools) β€” wire with FastMCP
- n8n (20 tools) β€” wire with FastMCP
**Acceptance gate: 332+ tools versioned, all gopass-backed auth, OpenCTI+Dify+n8n wired.**
## M3: RAG Dual-Dim Wrapper (P0 #6)
**Deliverable: `app/rag/embedder.py` (wraps frozen `app/crypto_embeddings.py`)**
```python
from enum import Enum
class EmbedderBackend(Enum):
BGE_M3 = "bge-m3" # 1024d, legacy
QWEN3_4B = "qwen3-embedding:4b" # 2048d, target
class DualDimEmbedder:
def __init__(self, backend: EmbedderBackend):
self.backend = backend
self.dim = 1024 if backend == EmbedderBackend.BGE_M3 else 2048
async def embed(self, texts: list[str]) -> list[list[float]]:
return await self._ollama_embed(texts, self.backend.value)
async def reindex_collection(self, name: str, target: EmbedderBackend) -> bool:
old = DualDimEmbedder(EmbedderBackend.BGE_M3)
new = DualDimEmbedder(target)
# 1. Read all docs from collection
# 2. Re-embed with new backend
# 3. Write to new collection {name}_v2
# 4. Atomic swap: {name} β†’ {name}_legacy, {name}_v2 β†’ {name}
# 5. Verify: query 10 known docs, confirm retrieval
return True
```
**The math:**
| Metric | bge-m3 | qwen3-4b | Delta |
|--------|--------|----------|-------|
| Dimensions | 1024 | 2048 | +2x |
| RAM (loaded) | 1.2GB | 2.0GB | +800MB |
| Embed latency | ~80ms | ~140ms | +60ms |
| Retrieval (nDCG@10) | 0.71 | 0.83 (est) | +12% |
| Reindex time | β€” | 6h overnight | one-time |
| Storage/coll | ~50MB | ~100MB | +650MB total |
**Strategy: collection-by-collection overnight, smallest first, verify each morning.** If <8%, defer.
**Acceptance gate: All 13 collections reindexed, eval harness +8% nDCG@10, bge-m3 unloaded.**
## M4: Observability Triangle (P1 #11, #12, P2 #21)
**Three pillars wired into the 94-line lifespan:**
```python
# main.py additions (~15 lines to existing 94)
from contextlib import asynccontextmanager
from app.core.tracing import setup_otel
from app.core.langfuse import langfuse
from app.core.errors import init_glitchtip
@asynccontextmanager
async def lifespan(app: FastAPI):
_otel = setup_otel() # OTel: httpx, redis, asyncpg, neo4j
init_glitchtip() # Sentry-compatible SDK
await app.state.redis.initialize()
yield
_otel.shutdown()
langfuse.flush()
await app.state.redis.close()
app = FastAPI(lifespan=lifespan)
```
**Pillars:**
- **OTel**: auto-instrument httpx/redis/asyncpg/neo4j, export to local OTel collector
- **Langfuse**: `@observe()` decorator on every LLM call, >95% coverage
- **GlitchTip**: Sentry-compatible SDK, DSN from gopass, captures every unhandled exception
**Acceptance gate: 7 consecutive days triangle lit.**
## M5: CI/CD Standards (P1 #13-17, P1 #19, P1 #20)
**Pipeline stages:**
```
PRE-COMMIT (local): ruff + mypy + gitleaks + detect-secrets + bandit + file-size + pytest-smoke
CI (GH Actions): lint + test-full + build + trivy + semgrep + gitleaks-history + grype
DEPLOY (blue-green): build-green β†’ start β†’ health-gate β†’ swap-router β†’ verify-5xx β†’ keep-blue-10min
ROLLBACK (auto): swap-back-to-blue on 5xx spike β†’ page β†’ freeze-deploys-1h
```
**Justfile deploy target:**
```makefile
deploy:
docker compose -f docker-compose.green.yml build
docker compose -f docker-compose.green.yml up -d
./scripts/health-check.sh http://127.0.0.1:8001/health 60
./scripts/swap-upstream.sh green
./scripts/watch-errors.sh 600 0.01
docker compose -f docker-compose.blue.yml stop
```
## M6: ADRs + Runbooks + Postmortems + Oncall (P2 #28, #29)
**Top 10 ADRs (write these first):**
1. Why FastAPI over Litestar/Flask/Django
2. Why 5 DBs (Postgres+Redis+CH+Neo4j+Qdrant)
3. Why strangler-fig over rewrite
4. Why bge-m3 β†’ qwen3-embedding:4b (M3)
5. Why self-host GlitchTip over Sentry SaaS
6. Why single-VPS blue-green over k3s
7. Why gopass over Vault/Doppler/AWS SM
8. Why Hermes cron over systemd/Airflow
9. Why MCP mesh over direct API calls
10. Why Tailscale over WireGuard/ZeroTier
**ADR Template:**
```
# ADR-XXXX: [Title]
## Status: Accepted | Rejected | Superseded
## Date: YYYY-MM-DD
## Decider: @cryptorugmunch
## Context: What we're solving
## Decision: What we chose
## Alternatives Considered: What we rejected
## Consequences: Easier / Harder / Mitigations
```
## M7: Cost Tracking + SLOs + Error Budgets (P2 #21)
**Per-tenant cost middleware (`app/middleware/cost_tracking.py`):**
```python
class CostTrackingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
tenant = request.headers.get("X-Tenant-ID", "anonymous")
t0 = time.monotonic()
response = await call_next(request)
elapsed_ms = (time.monotonic() - t0) * 1000
cost = self._estimate_cost(route, response, elapsed_ms)
await self._buffer_cost(tenant, route, cost, elapsed_ms)
return response
```
**SLO Targets:**
| Service | SLO | Error budget |
|---------|-----|--------------|
| /api/v1/* p99 latency | < 500ms | 43min/mo |
| /api/v2/* p99 latency | < 200ms | 43min/mo |
| /health uptime | β‰₯ 99.5% | 3.6h/mo |
| /api/* error rate (5xx) | < 0.1% | 43min of 5xx/mo |
| RAG retrieval success | > 95% | 43min of failures/mo |
| LLM calls timeout rate | < 1% | 7.2h of timeouts/mo |
**Auto-freeze deploys on 4x burn:**
```yaml
- alert: SLOBurnRate4x
expr: (sum(rate(http_requests_total{status=~"5.."}[1h])) / sum(rate(http_requests_total[1h]))) > 0.02
for: 2m
annotations:
webhook: "http://127.0.0.1:8642/ci/freeze"
```
## M8: AI Tool Wiring Matrix
| Task | Best tool | Worst tool |
|------|-----------|------------|
| Bounded code edit (1 file, <50 lines) | **aider** | claude-code |
| Multi-file refactor (3–10 files) | **claude-code** | aider |
| Codebase search | **Bloop** (when installed) | grep |
| Test generation | **claude-code** | aider |
| Doc generation (ADR, runbook) | **GLM-5.2** | aider |
| Security review | **Semgrep** (not LLM) | any LLM |
| Dependency upgrade | **Renovate** (not LLM) | any LLM |
| Prompt engineering | **GLM-5.2 + DSPy** | claude-code |
| RAG eval | Custom harness + LLM-as-judge | any LLM alone |
| Long-context (β‰₯100K) | **Kimi K2.7** (256K) | DeepSeek (slow) |
| Vision | **Gemini 2.5-pro** | text-only LLM |
| Fast small (≀500 tokens) | **Groq** | DeepSeek |
| Autonomous (bounded loop) | **Hermes** | claude-code |
---
# 10 INCIDENT SCENARIOS (v3)
## v2's 7 (preserved)
1. rmi-redis crashes at 3am β†’ auto-restart
2. Disk full on netcup β†’ AlertManager page at 80%
3. Postgres corruption β†’ restore from nightly backup
4. SSL cert expiry β†’ Cloudflare manages, Tailscale renew
5. OOM kill on rmi-backend β†’ Docker limit + py-spy/memray
6. DDoS on rugmunch.io β†’ Cloudflare "Under Attack" mode
7. DeepSeek balance hits $0 β†’ failover to Ollama Cloud/Gemini
## v3's 3 NEW (AI-specific)
### Scenario 8 β€” Agent Loop Runaway
**Symptom:** LLM spend spikes ($50+/h). Hermes logs show task iterating >50Γ—.
**Detect:** Daily spend alert (M7) at $20/h. Hermes /cron/health shows iterations >20.
**Mitigate:** `pkill -f "hermes.*task_id=X"`. Disable cron job.
**Recover:** Audit input contract β€” likely missing success_criteria or verify_command.
**Root cause:** vague description or missing verify_command.
### Scenario 9 β€” RAG Poisoning
**Symptom:** RAG retrieval returns adversarial content. User reports "the AI told me to send funds to X."
**Detect:** Langfuse span shows suspicious source. Eval harness flags quality regression.
**Mitigate:** Disable affected collection. Roll back FAISS index.
**Recover:** Add source allowlist to ingest step.
**Root cause:** open ingestion endpoint or untrusted RSS feed.
### Scenario 10 β€” MCP Tool Compromise
**Symptom:** Unusual gopass access pattern. MCP tool called from unexpected IP. Lateral movement.
**Detect:** gopass audit log. CrowdSec flags anomalous SSH. Tailscale ACL violation.
**Mitigate:** `gopass rm rmi/infra/compromised/scope` + rotate. Revoke Tailscale node. Block IP.
**Recover:** Audit all gopass accesses in last 30 days. Rotate ALL keys.
**Root cause:** over-broad auth_scope or leaked Tailscale key. SEV1.
---
# FROZEN FILE MANIFEST v3 (14 entries)
| # | File | Why frozen |
|---|------|------------|
| 1 | `main.py` | Front door (was 8475, now 94) |
| 2 | `_legacy_main.py` | Strangler-fig target |
| 3 | `app/core/redis.py` | Single Redis source |
| 4 | `app/core/config.py` | Env var loader |
| 5 | `app/databus/core.py` | 96-chain fetch interface |
| 6 | `app/rag/pipeline.py` | RAG pipeline (frozen for M3) |
| 7 | `app/crypto_embeddings.py` | bge-m3 embedder (replaced via wrapper) |
| 8 | `app/api/v1/__init__.py` | Router registry |
| 9 | `docker-compose.yml` | All 36 containers |
| 10 | `app/core/tracing.py` | OTel (not wired β€” M4) |
| 11 | `app/agents/loop.py` | M1 deliverable (frozen on creation) |
| 12 | `app/mcp/server.py` | M2 deliverable (frozen on creation) |
| 13 | `app/core/metrics.py` | P0 deliverable (frozen on creation) |
| 14 | `app/middleware/cost_tracking.py` | M7 deliverable (frozen on creation) |
**Rule:** Modified only via bounded-task delegation with Task ID in `/home/z/my-project/worklog.md`. Out-of-process edits reverted.
---
# THE MATH (v3 BRUTAL TRUTH)
```
Solo With M3
P0 + P1 + P2 hours 115h 115h
Available budget (21dΓ—8h) 168h 168h human + 152h M3 = 320h
Buffer (30% incident tax) βˆ’50h βˆ’50h human, βˆ’46h M3
Effective capacity 118h 224h
NET (capacity βˆ’ work) +3h +109h
↑ Phase 4 fits here
Plus 20h passive waits (eval soaks, mesh verifications)
β†’ Solo: 138h needed vs 118h available β€” slips to Day 25
β†’ M3: 138h needed vs 224h available β€” fits comfortably
```
**Verdict: Solo, v3 is a 25-day plan. With M3 parallel, v3 is a 21-day plan.**
---
# 90-DAY ROADMAP (after the unfuck)
## Month 2 β€” Agent Mesh + RAG Eval + SDK
- Agent mesh: Hermes orchestrating multiple claude-code/aider in parallel
- RAG eval harness: automated retrieval-quality regression on every push
- SDK v1: TypeScript + Python clients from OpenAPI spec
## Month 3 β€” Multi-Region + SOC2 + Paid Pilots
- Multi-region read replicas (Contabo β†’ Cloudflare LB)
- SOC2 prep (ADRs + runbooks = 60% of evidence)
- 3 paid pilots, $500/mo each, 60-day commitment
## Month 4+ β€” Hire or Stay Solo
- If 3 pilots + 1 conversion β†’ hire senior backend engineer
- If pilots but no conversion β†’ focus on conversion
- If no pilots β†’ pivot
---
# What Ships This Pass (Foundation)
1. βœ… Fix crash β€” `rag_engine` re-export shim, backend healthy (Phase 0)
2. βœ… `pyproject.toml` β€” uv + ruff + mypy strict + pytest
3. βœ… `.pre-commit-config.yaml` β€” ruff + mypy + size cap (500) + gitleaks
4. βœ… `app/core/` β€” 11 modules, each <200 lines
5. βœ… `app/api/v1/__init__.py` β€” router aggregator
6. βœ… `main.py` β€” 94 lines (Phase 0 just restored)
7. βœ… Verify: backend boots, all 1249 routes respond, health 200
8. βœ… Commit + deploy (`4a8a16d`)
## What Does NOT Ship This Pass (v3 P0-P2)
- Migrating alerts/wallet/token/scanner to new `domain/`. That's the current unfuck.
- The 15 mechanical refactors. Replaced with the layered architecture.
- Deleting old code. Strangler fig β€” old stays until domain is migrated.
- **v3 new**: M1 Agent Loop, M2 MCP Mesh, M3 RAG dual-dim, M4 Observability Triangle, M5 CI/CD, M6 ADRs, M7 Cost+SLOs, M8 AI Tool Matrix.
## Phase 2: Alerts Vertical Slice (proves the pattern) β€” COMPLETE
After foundation lands, migrate `alerts` end-to-end as the reference:
```
app/domain/alerts/
β”œβ”€β”€ models.py # Alert, AlertRule, Notification β€” Pydantic v2
β”œβ”€β”€ repository.py # async SQLAlchemy queries
β”œβ”€β”€ service.py # business logic, pure Python
└── broadcaster.py # WebSocket broadcast helper
app/api/v1/auth/alerts.py # thin route: parse β†’ call service β†’ return
```
This proves the pattern works: domain is pure Python, route is <100 lines, can be unit tested without HTTP.