From e896a60bc00edc498fc6b6c3b87fe228e2d2901a Mon Sep 17 00:00:00 2001 From: "m2 (AI Agent)" Date: Thu, 2 Jul 2026 05:34:07 +0200 Subject: [PATCH] =?UTF-8?q?feat(002-market-web):=20spec,=20plan,=20tasks,?= =?UTF-8?q?=20/api=20contract=20=E2=80=94=20the=20web=20surface=20connecti?= =?UTF-8?q?ng=20catalog,=20wallet,=20governance,=20install=20handoff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- specs/002-market-web/contracts/web-api.md | 13 +++ specs/002-market-web/plan.md | 62 ++++++++++++ specs/002-market-web/spec.md | 117 ++++++++++++++++++++++ specs/002-market-web/tasks.md | 10 ++ 4 files changed, 202 insertions(+) create mode 100644 specs/002-market-web/contracts/web-api.md create mode 100644 specs/002-market-web/plan.md create mode 100644 specs/002-market-web/spec.md create mode 100644 specs/002-market-web/tasks.md diff --git a/specs/002-market-web/contracts/web-api.md b/specs/002-market-web/contracts/web-api.md new file mode 100644 index 0000000..3d054ee --- /dev/null +++ b/specs/002-market-web/contracts/web-api.md @@ -0,0 +1,13 @@ +# Contract: m2-market-web /api (v1) + +Session: POST /api/login {passcode} → 204 + Set-Cookie m2mw_session (signed, 7d) | 401. +All /api/* below require the cookie (401 otherwise). /health is open → {"status":"healthy"}. + +- GET /api/search?q=&limit=10 → [{listing_id, name, summary, category, price:{amount,currency,model}, seller, stats, score}] +- GET /api/listing/{listing_id} → {listing..., evidence_summary, permissions[], install_ref, install_command: "m2-market install --yes"} +- GET /api/wallet/{operator_id} → {operator_id, balance, as_of, transactions:[last 20 {ts,from,to,amount,reason,ref}]} +- GET /api/economy → {operators:[{operator_id,balance}], audit:{date, url}} # url deep-links registry audit file +- GET /api/governance → {open_prs:[{number,title,age_days,labels,url}], listings:[{listing_id,status,url}]} + +DTOs only — never raw upstream JSON. Upstream failure → 502 {"error","panel"} so the +frontend degrades per-panel. No key material in any response (tested). diff --git a/specs/002-market-web/plan.md b/specs/002-market-web/plan.md new file mode 100644 index 0000000..385b263 --- /dev/null +++ b/specs/002-market-web/plan.md @@ -0,0 +1,62 @@ +# 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). diff --git a/specs/002-market-web/spec.md b/specs/002-market-web/spec.md new file mode 100644 index 0000000..d646924 --- /dev/null +++ b/specs/002-market-web/spec.md @@ -0,0 +1,117 @@ +# Feature Specification: M2 Market Web + +**Feature Branch**: `002-market-web` + +**Created**: 2026-07-02 + +**Status**: Draft + +**Input**: Operator: "build the web app connecting everything" — one browser UI over the +wedge's live rails: catalog, listings+evidence, wallet/transactions, registry governance, +install handoff. Motivated by UAT: today money is curl-only and governance is raw Forgejo. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Browse & inspect from any browser (Priority: P1) + +An operator opens market.machinemachine.ai, browses/searches the live catalog +semantically, opens a listing, and sees everything the Store shows (evidence, price, +seller, version, permissions) plus stats. + +**Independent Test**: open the URL, search "pdf report", open the listing, verify the +same data the CLI's `show` returns. + +**Acceptance Scenarios**: +1. **Given** the live catalog, **When** I search "branded pdf", **Then** published + listings render with name/summary/price/installs, ordered by relevance. +2. **Given** a listing page, **When** it loads, **Then** evidence summary, permissions, + seller, version, install_ref and stats are shown — same values as `m2-market show`. +3. **Given** a delisted or tenant-invisible listing, **Then** it does not appear. + +--- + +### User Story 2 - Wallet & economy view (Priority: P1) + +An operator picks their identity and sees their balance and transaction history; a fleet +view shows all operator balances and links each day to its committed audit snapshot. + +**Independent Test**: select m2bd → balance matches `GET /balance/m2bd`; fleet view total +matches the latest audit/*.json in the registry. + +**Acceptance Scenarios**: +1. **Given** operator m2bd selected, **When** the wallet view loads, **Then** balance and + last 20 transactions match the ledger API exactly. +2. **Given** the fleet economy view, **Then** every operator balance is shown and the + "audit" link opens the registry's snapshot file for that day. +3. **Given** the browser, **Then** NO ledger/memory/forgejo key is ever present in + client-side code, network responses, or storage (server-side proxy only). + +--- + +### User Story 3 - Governance view (Priority: P2) + +Curation state at a glance: open listing PRs with their veto window, published/delisted +listings, links into Forgejo for the actual review actions. + +**Acceptance Scenarios**: +1. **Given** an open listing PR, **Then** it appears with title, age, labels, and a link + to the Forgejo PR (review/veto happens ON Forgejo — the web app never merges). +2. **Given** the listings index, **Then** each row shows status (published/delisted) and + links to its registry directory. + +--- + +### User Story 4 - Install handoff (Priority: P2) + +The web cannot apply bundles to a desktop (by design, v1). A listing page gives the +operator a copyable, correct install command and points at their desktops. + +**Acceptance Scenarios**: +1. **Given** a listing page, **When** I click "Install on my desktop", **Then** I get + `m2-market install --yes` copyable + a short "where to run it" note. +2. The page never claims an install happened. + +### Edge Cases +- Ledger or memory-api down → the affected panel shows a clear error, the rest works. +- Unknown operator id typed → empty wallet with a "no such wallet yet" note (balance 0). +- Catalog result whose registry entry vanished → row renders from catalog payload only. + +## Requirements *(mandatory)* + +- **FR-101**: A web app MUST be served at market.machinemachine.ai (Coolify app, + `coolify` network, http-scheme domain per docs/runbook.md redirect-loop lesson). +- **FR-102**: All upstream calls (memory-api, ledger, Forgejo) MUST go through a + server-side backend holding the keys; the browser gets only proxied, minimal JSON. +- **FR-103**: Catalog search MUST use the same semantic path + tenant-visibility filter + as the CLI (shared reader semantics; server-side). +- **FR-104**: Wallet views MUST be read-only in v1 (no grants, no installs, no refunds + from the web). +- **FR-105**: Access MUST be gated by a fleet passcode (FLEET_PASSCODE env → session + cookie); operator identity within a session is a picker, not authentication — + UNCONFIRMED assumption, flagged for the operator (internal-trust model v1). +- **FR-106**: Governance data MUST come live from the Forgejo API (open PRs with labels, + listings dirs + status) with deep links; no write actions. +- **FR-107**: The app MUST degrade panel-by-panel when an upstream is down. +- **FR-108**: Deployment MUST ride the existing CI/CD pattern (push to m2/m2-market → + Coolify deploy; own app + webhook). + +## Success Criteria *(mandatory)* + +- **SC-101**: An operator completes browse → listing → wallet → governance in one + browser session with zero terminal usage. +- **SC-102**: Wallet numbers equal ledger API values at load time (spot-check 3 ops). +- **SC-103**: A secrets scan of served assets + responses shows zero key material. +- **SC-104**: App live at market.machinemachine.ai with health endpoint; panels degrade + gracefully when an upstream is stopped. +- **SC-105**: Lighthouse-basic usability: loads under 3s on the fleet network, usable on + a phone (the operator checks desktops from anywhere). + +## Assumptions + +- v1 auth = fleet passcode + operator picker (FR-105) — OPERATOR TO CONFIRM; upgrade path + is per-operator tokens issued by the ledger admin (post-v1). +- Install-from-web (remote apply to a desktop) is OUT of v1; handoff only (Story 4). +- Stack: FastAPI backend proxy + React/Vite frontend served by the same container — + matches fleet patterns (memory-api FastAPI; m2-gpt admin React) — monorepo `web/`. +- Reuses the wedge's frozen contracts; NO new schema, NO new ledger endpoints. +- cargstore `web/` (a static landing page) is superseded by this app. diff --git a/specs/002-market-web/tasks.md b/specs/002-market-web/tasks.md new file mode 100644 index 0000000..3405065 --- /dev/null +++ b/specs/002-market-web/tasks.md @@ -0,0 +1,10 @@ +# Tasks: M2 Market Web + +**Organization**: lean — the rails exist; this is a two-slice build + deploy. + +- [ ] T101 [P] Backend proxy in web/backend/: FastAPI app per plan.md structure + contracts/web-api.md — auth (FLEET_PASSCODE→signed cookie), catalog proxy (memory-api /memory/search, tenant_visibility filter mirroring cli/src/m2_market/catalog.py), wallet/economy (ledger API + audit links to registry), governance (Forgejo PRs+labels, listings+status), /health; pytest incl. a secrets-leak test (no env key value appears in any response body); uv workspace member +- [ ] T102 [P] Frontend SPA in web/frontend/: Vite+React+TS, pages Search/Listing/Wallet/Economy/Governance + login gate + operator picker; install handoff block on Listing (copyable CLI command); dark navy + cyan (machine.machine brand); panel-level error states (FR-107); talks only to /api/* +- [ ] T103 Dockerfile (multi-stage node build → python serve) + Coolify app m2-market-web on coolify network, domain http://market.machinemachine.ai, env per plan, Forgejo push webhook (CI/CD) +- [ ] T104 Live verification: SC-101 walkthrough, SC-102 wallet spot-check (3 operators), SC-103 secrets scan of served assets+responses, SC-104 degradation (stop ledger briefly → wallet panel errors, catalog still works), SC-105 phone-width render; record in specs/002-market-web/VERIFICATION.md + +Dependencies: T101 ∥ T102 (contract-first) → T103 → T104.