# Implementation Plan: M2 Market Web **Branch**: `002-market-web` | **Date**: 2026-07-02 | **Spec**: [spec.md](./spec.md) ## Summary One Coolify-deployed container: FastAPI backend that proxies the three live rails (memory-api catalog, m2-ledger, Forgejo registry) with keys server-side, serving a React/Vite SPA. Read-only + install handoff. Rides the proven CI/CD (push → deploy), http-scheme domain market.machinemachine.ai. ## Technical Context **Language**: Python 3.12 (FastAPI, httpx) + TypeScript React 18/Vite **Storage**: none (stateless proxy; session cookie is signed, in-memory secret) **Testing**: pytest for the backend proxy (httpx MockTransport per upstream, secrets- leak test asserting no key strings in any response); vitest optional for components **Deploy**: `web/Dockerfile` multi-stage (node build → python runtime), Coolify app `m2-market-web`, env: FLEET_PASSCODE, SESSION_SECRET, MEMORY_API_URL/KEY, LEDGER_URL/LEDGER_SERVICE_KEY, FORGEJO_URL/FORGEJO_TOKEN, REGISTRY_REPO **Constraints**: constitution I (assembly: existing APIs only, no new rails), II (web is a surface, never truth), IV (tenant filter server-side, same semantics as CLI reader), VI (no secrets in image; runtime env) ## Constitution Check Passes: no new services beyond the (sanctioned-as-surface) web app itself; all data derived from registry/ledger/catalog; keys never leave the backend; read-only economy. Deviation: none. ## Project Structure ```text web/ ├── backend/ │ ├── src/m2_market_web/ │ │ ├── main.py # FastAPI app: static serve + /api/* + /health │ │ ├── auth.py # FLEET_PASSCODE login -> signed session cookie │ │ ├── catalog.py # /api/search, /api/listing/{id} (memory-api proxy, │ │ │ # tenant filter — mirror cli catalog.py semantics) │ │ ├── ledger.py # /api/wallet/{op}, /api/economy (balances+audit links) │ │ └── registry.py # /api/governance (open PRs+labels, listings+status) │ ├── tests/ │ └── pyproject.toml # uv workspace member ├── frontend/ # Vite + React + TS, dark navy/cyan (mm brand) │ ├── src/{pages,components,api.ts} │ └── package.json └── Dockerfile ``` ## Sequencing 1. Backend proxy + tests [P with 2] → 2. Frontend SPA [P with 1] (against a typed /api contract defined in contracts/web-api.md) → 3. Dockerfile + Coolify app + domain + webhook → 4. Live verification vs SC-101..105. ## Contract `contracts/web-api.md` (written with tasks): /api/login {passcode}; /api/search?q=&limit=; /api/listing/{id}; /api/wallet/{operator}; /api/economy; /api/governance; /health. Every /api/* except /health and /login requires the session cookie. Responses are minimal DTOs — never raw upstream bodies (leak surface).