# DAMM Tech Debt Register

Running register of debt accrued during tracer-bullet implementations. Each entry: what we shipped fast, what's still owed, and the trigger that means we can't put it off any longer.

The point of this register is **honesty about where we cut corners**. A doc that says "T3 is shipped" when the wire bridge is a stub is worse than no doc — that's exactly the wishful-aspiration trap we said we'd avoid.

Refreshed: 2026-04-29 (against v0.3.3 tracer bullets).

## How to read

Each entry is a row with:
- **What we did fast** — the corner cut
- **What's owed** — the real implementation
- **Why this is OK for now** — context that justifies the corner
- **Trigger to pay it back** — concrete observable signal that says "this is no longer OK"

When a trigger fires, the row moves to `issue-triage.md` as **🔥 Blocking**.

## v0.3.3 tracer-bullet debt

### TD-T1-runtime — AmneziaWG server not installed
- **What we did fast:** the catalog can advertise a `transport: "amneziawg"` frontdoor with the correct params; the CP renders an Amnezia-flavored client `.conf` correctly; the wizard surfaces "this requires Amnezia VPN client, not stock WireGuard" copy.
- **What's owed:** install `amneziawg-go` on hub2; bring up a `wgT1` interface on a different port (51821 proposed) using the same gateway keypair; integrate into the reconciler so the same peer-set serves both wg0 and wgT1.
- **Why this is OK for now:** no client exists in a region that needs T1 yet. Shipping the data shape without the runtime lets us test the catalog-side and wizard-side code paths against synthetic state.
- **Trigger to pay back:** the moment one prospective user reports that T0 doesn't handshake from their network. Or: any preparation for inviting a friend in a censored region.

### TD-T1-amnezia-client-detection — wizard doesn't detect Amnezia VPN
- **What we did fast:** the wizard says "if you're in a region that blocks plain WireGuard, you'll need Amnezia VPN client (not stock WireGuard) — install from F-Droid or Google Play."
- **What's owed:** detect the user's installed VPN clients (impossible from the browser, so really: ask them politely, remember their answer for next time).
- **Why this is OK for now:** the user is the source of truth about what they have. A free-text prompt is fine.
- **Trigger to pay back:** when the wizard's drop-off rate at the import step suggests users are confused about which app to install.

### TD-T3-bridge-stub — WSS tracer logs but does not forward
- **What we did fast:** `gateway/agents/wss-tracer.js` accepts WSS connections, decodes incoming frames as base64-encoded WG packets, logs the first 64 bytes, validates the WG handshake-init magic byte. Proves the wire shape end-to-end with a real WSS upgrade and a real WG packet.
- **What's owed:** forward the packet to `127.0.0.1:51820/udp` and the response back over WS. Equivalently, install `wstunnel` and let it do the bridging.
- **Why this is OK for now:** the bridge is the single most failure-prone component (UDP↔TCP↔WS state machine, WG's stateless-rejection on stale packets). A working stub that proves frames arrive intact is a real milestone before writing the bridge.
- **Trigger to pay back:** any user-facing T3 advertisement. Until the bridge forwards, T3 is internal-only and must not be in any client-visible catalog.

### TD-T3-cover-domain — `raindeck.dev` not configured
- **What we did fast:** the spec names `notes.raindeck.dev` as the T3 endpoint. We own the domain.
- **What's owed:** Caddy vhost for `notes.raindeck.dev` serving real-looking content on `/`, with `/_w` reverse-proxied to the WSS bridge. Static HTML cover surface (a copy of a developer's blog or notes page).
- **Why this is OK for now:** until the bridge forwards, there's no functional reason to expose the domain.
- **Trigger to pay back:** before any T3 frontdoor goes into a published catalog.

### TD-T3-tlsfingerprint — Caddy's TLS fingerprint is itself fingerprintable
- **What we did fast:** rely on Caddy's default TLS profile.
- **What's owed:** uTLS-style fingerprint mimicry so the TLS ClientHello *from us* matches what real Chrome/Firefox produces. (This is a client-side concern more than server.)
- **Why this is OK for now:** Caddy's TLS profile is reasonable; not the most-fingerprinted shape. We don't need to fight the JA3 arms race for friend-scale users.
- **Trigger to pay back:** if any user reports T3 specifically (not T0/T1) is being blocked.

### TD-score-per-frontdoor — orchestrator scores per gateway, not per frontdoor
- **What we did fast:** the v0.3.0 orchestrator scores gateways. Frontdoor scoring inherits the gateway's composite.
- **What's owed:** per-frontdoor probes (e.g., the orchestrator opens a connection to each frontdoor and measures handshake completion). For T1+, this requires speaking AmneziaWG from the prober; for T3, opening a real WSS upgrade.
- **Why this is OK for now:** with one gateway and one frontdoor in production, the score is identical at both levels. The data shape is fine; only the probing logic is missing.
- **Trigger to pay back:** the moment we have two frontdoors on one gateway. Without per-frontdoor scoring, the catalog can't honestly demote T0 while keeping T1.

### TD-score-from-clients — clients don't report their measurements upstream
- **What we did fast:** the wizard's "test connection" call returns the result to the user but does not report it to the CP.
- **What's owed:** a `POST /v1/measurements` endpoint where clients report `{frontdoorId, handshakeCompleted, rttMs, attemptedAt}`. The orchestrator weights its score against client-reported reality, especially for clients in censored regions whose measurements differ from the orchestrator's own probes from hub2.
- **Why this is OK for now:** internal probes from hub2 measure what hub2 sees. With one user, hub2's view ≈ the user's view.
- **Trigger to pay back:** when we have any client in a region where the orchestrator's hub2-side probe disagrees with reality (i.e., everyone in censored regions, eventually).

### TD-multi-region-catalog — catalog filters by region but score ignores region
- **What we did fast:** v0.3.3 will filter catalogs by score; that filter is global, not per-region.
- **What's owed:** per-region scoring. A T0 frontdoor that scores 0.9 in Germany but 0.0 in Iran should be in the German catalog and out of the Iranian one.
- **Why this is OK for now:** one region, one gateway. Per-region is meaningless until we have multi-region.
- **Trigger to pay back:** when we add a second gateway in a different region OR when we have client measurements showing per-region divergence.

### TD-amnezia-params-hardcoded — AmneziaWG params don't rotate
- **What we did fast:** the spec uses fixed `Jc/Jmin/Jmax/H1-H4` params per frontdoor.
- **What's owed:** rotate these params per-region or per-week so static signature databases that do learn ours still don't catch the next batch.
- **Why this is OK for now:** without a deployed T1 server, rotation is hypothetical.
- **Trigger to pay back:** along with TD-T1-runtime; rotation is part of "shipping T1 properly," not an afterthought.

### TD-wizard-tier-picker — wizard doesn't surface the tier choice
- **What we did fast:** the wizard auto-uses T0. Posture panel can be extended to *show* the chosen tier; doesn't offer manual override.
- **What's owed:** a "try a heavier transport" button that re-issues with a T1+ frontdoor. Region hint that auto-selects T1+ for known-restricted regions.
- **Why this is OK for now:** with only T0, there's nothing to pick.
- **Trigger to pay back:** TD-T1-runtime ships.

### TD-catalog-version — catalog format hasn't versioned
- **What we did fast:** v0.3.3 adds `transport` and `params` fields to frontdoors. Old clients reading new catalogs will see fields they don't understand and (we hope) ignore them.
- **What's owed:** a `version: 2` field on the catalog envelope so clients can refuse to parse a future-incompatible format.
- **Why this is OK for now:** the JSON-with-extra-fields contract has held; nothing in v0.3.3's additions changes existing field semantics.
- **Trigger to pay back:** any breaking change to the catalog shape (e.g., when we add the per-region split).

## Older debt still outstanding

(Not introduced by tracer-bullet work but tracked here so it's visible.)

### TD-state-monolithic — state.json mixes durable and hot data
- See `architecture-premortem.md` §1 and `issue-triage.md` (state-monolithic). v0.4.0 target.

### TD-signing-key-spof — catalog signing key has no rotation protocol
- See `architecture-premortem.md` §4 and `issue-triage.md`. Post-v0.4 target.

### TD-rate-limiter-in-process — admission rate limit doesn't survive multi-CP
- See `field-manual.md` §3.14. Post-v0.4 target.

### TD-orchestrator-on-cp-host — same-box colocation
- See `architecture-premortem.md` §2. Long-term separation; not blocking.

## How this register is maintained

1. **Every PR that adds a tracer or stub adds a row here.** No exceptions.
2. **When a trigger fires, the row goes to `issue-triage.md` as 🔥 Blocking** until paid.
3. **When debt is paid, the row moves to a "Resolved" section** for one cycle, then drops out.
4. **Quarterly review:** every row's "Why OK for now" must still hold. If the assumption has changed, escalate.

The cardinal rule: **never delete a row silently**. Either pay the debt or document why the trigger no longer applies.
