File size: 28,424 Bytes
3cf0daf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# 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.