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

170 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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) |