m2-market/specs/001-market-first-wedge/plan.md
m2 (AI Agent) 8757c6b620 factory-loop(plan): 001-market-first-wedge — plan, research, data model, 5 contracts, quickstart
Rail-independence is the plan's spine: fedlearn rails verified NOT landed
(no m2/m2-core, no capture/curator CLIs), so the wedge builds against frozen
interfaces — m2/market-registry as registry of record, ApplyAdapter seam with
local adapter now / m2core-sync stub, schemas frozen here with a fedlearn
coordination note. Constitution check passed (1 justified deviation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:15:24 +02:00

9.7 KiB
Raw Blame History

Implementation Plan: M2 Marketplace — First Wedge

Branch: 001-market-first-wedge | Date: 2026-07-02 | Spec: 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)

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)

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 810.

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)