117 lines
5.7 KiB
Markdown
117 lines
5.7 KiB
Markdown
# 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 <listing_id> --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.
|