# Implementation Plan: M2 Marketplace — First Wedge **Branch**: `001-market-first-wedge` | **Date**: 2026-07-02 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/001-market-first-wedge/spec.md` ## Summary Close the commercial loop once: schemas (frozen v1 contract) → `m2-ledger` (append-only credits, FastAPI+SQLite Coolify app) → listings registry on Forgejo (`m2/market-registry`, PR+veto review) → semantic catalog (`market:catalog` Qdrant partition on the existing memory-api) → `m2-market` CLI (`search|show|install`, install = debit→grant→apply) → 3 seeded Solutions → M2 Store (cargstore revival, canaries) → Scout v0 (one canary, after an exploration spike). **Critical sequencing constraint (verified 2026-07-02): the fedlearn rails are NOT landed** — no `m2/m2-core` repo, no capture/curator CLIs. Therefore every component in this wedge is built rail-independent against frozen interfaces; the two fedlearn touchpoints (sync/apply install backend, `commercialize` curation disposition) are isolated behind adapters and integrate when fedlearn lands. ## Technical Context **Language/Version**: Python 3.12 (ledger, CLI, indexer — uv-managed); TypeScript/React + Electron (M2 Store, inherited from cargstore) **Primary Dependencies**: FastAPI + uvicorn (ledger), httpx + click (CLI), memory-api HTTP API (catalog, existing), Forgejo REST API (registry), cargstore codebase (store) **Storage**: SQLite (ledger, WAL mode, single-writer service); Forgejo repo `m2/market-registry` (registry of record + daily ledger snapshots); Qdrant partition `market:catalog` via memory-api (derived index only) **Testing**: pytest (ledger unit + contract, CLI integration against a local ledger + mocked Forgejo/memory-api); scripted end-to-end on canary per quickstart.md **Target Platform**: m2 host via Coolify (`coolify` docker network) for services; m2o desktops (primus + agent-latest) for CLI/Store/Scout **Project Type**: multi-component monorepo (this repo) + one evolved external repo (cargstore → M2 Store) **Performance Goals**: internal-fleet scale — tens of operators, hundreds of installs; ledger correctness ≫ throughput; catalog search < 2s **Constraints**: append-only ledger, balances always derived; no secrets in repos (keys at runtime); tenant firewall enforced at submission and at catalog query; canary-first (chris-m2o, gunnar-m2o); idempotent + reversible applies; fedlearn rails absent → adapters + frozen interfaces (no blocking dependency) **Scale/Scope**: 6 components, ~5 new Python packages/services + 1 Electron app revival; 3 seeded Solutions; 2 canary desktops ## Constitution Check *GATE: evaluated against Constitution v1.0.0 before Phase 0; re-checked after Phase 1.* | Principle | Check | Status | |---|---|---| | I. Assembly over greenfield | Catalog = memory-api partition; store = cargstore evolution; registry = Forgejo; identity = fleet.json operators; the ONLY new service is m2-ledger (sanctioned) | ✅ | | II. Protocol + registry, not one app | Truth in `m2/market-registry` (PRs/labels); `market:catalog` rebuildable via `m2-market-indexer reindex`; CLI/Store/Scout are surfaces | ✅ | | III. Evidence-backed listings | `listing.schema.json` requires `evidence[]` + provenance; validation rejects before human review; conversion signals fields present from v1 | ✅ | | IV. Tenant firewall | `tenant_scope` on solutions; catalog queries tenant-filtered server-side; submission validator enforces owner-initiated for tenant-derived work | ✅ | | V. Ledger integrity | Append-only tx table, derived balances, X-API-Key, daily snapshot committed to registry repo, debit-before-apply with compensating refund | ✅ | | VI. Canary-first, reversible | Store + Scout on chris/gunnar only; installs idempotent via apply adapter contract; ledger is a Coolify app | ✅ | | VII. Simplicity, sequenced wedges | SQLite, fixed pricing, manual payouts; Scout last; deferred items listed in spec; fedlearn integration deferred behind adapters instead of half-built | ✅ | **Deviation from CONCEPT.md §3 asset map (justified)**: CONCEPT assumed the fedlearn `m2-core-manifest` + `m2/m2-core` repo as the package/registry substrate. Verified absent. Rather than block the wedge or fork fedlearn's job, this plan (a) freezes `solution.schema.json` v1 in THIS repo as the contract fedlearn's manifest must be superset-compatible with (coordination note in research.md), and (b) uses a dedicated `m2/market-registry` repo — which was the right registry-of-record shape anyway, since listings are commercial artifacts, not core commons. Post-design re-check: ✅ no violations. ## Project Structure ### Documentation (this feature) ```text specs/001-market-first-wedge/ ├── plan.md # This file ├── research.md # Phase 0 output ├── data-model.md # Phase 1 output ├── quickstart.md # Phase 1 output ├── contracts/ # Phase 1 output │ ├── ledger-api.md # m2-ledger HTTP contract │ ├── registry-layout.md # m2/market-registry repo layout + PR/veto protocol │ ├── catalog-index.md # market:catalog partition contract (memory-api) │ ├── cli.md # m2-market CLI command contract │ └── apply-adapter.md # install/apply adapter interface (fedlearn integration seam) └── tasks.md # Phase 2 output (/speckit-tasks — NOT created by plan) ``` ### Source Code (repository root) ```text schemas/ # frozen v1 JSON Schemas (the contract) ├── solution.schema.json └── listing.schema.json ledger/ # m2-ledger service (FastAPI + SQLite) ├── src/m2_ledger/ │ ├── api.py # routes: tx, balance, grant, install-debit │ ├── models.py # tx record, derived-balance queries │ ├── snapshot.py # daily balance snapshot → registry repo commit │ └── auth.py # X-API-Key ├── tests/ ├── Dockerfile # Coolify app, coolify network └── pyproject.toml cli/ # m2-market CLI (uv package) ├── src/m2_market/ │ ├── cli.py # search | show | install | wallet │ ├── catalog.py # memory-api client (market:catalog) │ ├── registry.py # Forgejo client (listings, bundles, releases) │ ├── ledger.py # m2-ledger client │ └── apply/ # apply-adapter implementations │ ├── base.py # AdapterProtocol (frozen interface, see contracts/) │ ├── local.py # v1: local bundle apply (files + recipe), idempotent │ └── m2core_sync.py # stub → real when fedlearn lands ├── tests/ └── pyproject.toml indexer/ # registry → market:catalog sync (small, cron/webhook) ├── src/m2_market_indexer/ │ ├── reindex.py # full rebuild (constitution II) │ └── watch.py # incremental on merge └── pyproject.toml solutions/ # seeded Solution bundles (sources for the 3 listings) ├── mm-pdf-report/ ├── agent-scaffold/ └── competitor-scan/ scout/ # Scout v0 (AFTER exploration spike; canary-only) └── SPIKE.md # spike findings → host decision (operator: "explore") store/ # NOT here — cargstore fork evolves in its own repo └── README.md # pointer + integration contract references ``` **Structure Decision**: single monorepo (this repo) for schemas/ledger/CLI/indexer/solutions — they version together against the frozen schemas. The M2 Store stays in the cargstore repo (github.com/machine-machine/cargstore, branch `m2-store`) because it is an evolution of a living codebase (constitution I); it consumes the same public contracts (`contracts/`). `m2/market-registry` on Forgejo is data, not code — created by a task, not a directory here. ## Component Plan & Sequencing (rail-independence explicit) | # | Component | Depends on | fedlearn coupling | |---|---|---|---| | 1 | `schemas/` v1 frozen | — | NONE now; coordination note → fedlearn manifest superset-compat | | 2 | `m2/market-registry` repo + PR/veto protocol | Forgejo (live) | replaces curation-pipeline dependency; `commercialize` disposition plugs in later | | 3 | `m2-ledger` service + wallets + grants | Coolify (live) | NONE | | 4 | `market:catalog` partition + indexer | memory-api (live), #2 | NONE | | 5 | `m2-market` CLI (search/show/install) | #1–#4 | apply via AdapterProtocol; v1 `local` adapter, `m2core_sync` stub | | 6 | Seed 3 Solutions + listings | #1, #2, #5 | NONE (manual packaging from proven outcomes) | | 7 | **GATE: SC-001 paid install** (CLI path) | #1–#6 | NONE | | 8 | M2 Store (cargstore → `m2-store` branch), canaries | #4, #5 contracts | NONE | | 9 | Scout spike → Scout v0, one canary | #4; herdr run summaries (live) | NONE | | 10 | fedlearn integration pass | fedlearn lands | swap `m2core_sync` adapter real; wire `commercialize` disposition → registry PR | Row 7 is the wedge's success criterion and does not wait for rows 8–10. ## Complexity Tracking > Constitution Check passed — one recorded deviation (registry substrate), justified above. | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | New repo `m2/market-registry` instead of fedlearn `m2/m2-core` | m2-core does not exist yet; listings are commercial artifacts, not core commons | Waiting for fedlearn blocks the whole wedge on an unlanded dependency; writing into m2-core would entangle free-core and paid-market boundaries (constitution IV) |