5.7 KiB
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:
- Given the live catalog, When I search "branded pdf", Then published listings render with name/summary/price/installs, ordered by relevance.
- 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. - 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:
- Given operator m2bd selected, When the wallet view loads, Then balance and last 20 transactions match the ledger API exactly.
- Given the fleet economy view, Then every operator balance is shown and the "audit" link opens the registry's snapshot file for that day.
- 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:
- 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).
- 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:
- Given a listing page, When I click "Install on my desktop", Then I get
m2-market install <listing_id> --yescopyable + a short "where to run it" note. - 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,
coolifynetwork, 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.