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>
9.7 KiB
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 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) |