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>
170 lines
9.7 KiB
Markdown
170 lines
9.7 KiB
Markdown
# 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) |
|