# Tasks: M2 Marketplace — First Wedge **Input**: Design documents from `/specs/001-market-first-wedge/` **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md **Tests**: included where the design demands them (research.md R9: ledger property tests are how SC-003/SC-004 are held; schema validation guards the frozen contract). **Organization**: grouped by user story. The wedge gate (SC-001, plan row 7) is reachable at the end of Phase 5 — Store (US4) and Scout (US5) are NOT on that path. ## Format: `[ID] [P?] [Story] Description` ## Phase 1: Setup (Shared Infrastructure) **Purpose**: monorepo scaffolding per plan.md Project Structure - [x] T001 Create monorepo skeleton: `schemas/`, `ledger/`, `cli/`, `indexer/`, `solutions/`, `scout/`, `store/README.md` (pointer file) per plan.md structure - [x] T002 Initialize uv workspaces: `ledger/pyproject.toml` (fastapi, uvicorn, pytest), `cli/pyproject.toml` (click, httpx, pytest), `indexer/pyproject.toml` (httpx) — Python 3.12 - [x] T003 [P] Configure ruff + pytest defaults at repo root (`pyproject.toml` tool sections shared via workspace) --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: the frozen contracts everything else builds against **⚠️ CRITICAL**: no user story work until this phase completes - [x] T004 [P] Author frozen `schemas/solution.schema.json` v1 per data-model.md Solution table (manifest-core + commercial fields; validation rules incl. evidence non-empty, tenant_scope firewall attestations) - [x] T005 [P] Author frozen `schemas/listing.schema.json` v1 per data-model.md Listing table (incl. `stats` conversion-signal fields, `status` enum, `install_ref`) - [x] T006 Write schema validation test fixtures in `schemas/tests/` (valid + invalid examples for both schemas; invalid must include: missing evidence, tenant_scope violation, bad price) and a `just validate` / pytest runner - [x] T007 Create Forgejo repo `m2/market-registry` per contracts/registry-layout.md: layout dirs, PR template (evidence/price/permissions/rollback), CI validation job (schema-validate listings, content_hash check), veto/approved label protocol — use the forgejo skill/API - [x] T008 [P] Write fedlearn coordination note `docs/fedlearn-coordination.md`: frozen solution-schema core fields that the fedlearn manifest must stay superset-compatible with (research.md R3), the commercialize→registry-PR seam (R2), and the m2core-sync adapter swap acceptance (contracts/apply-adapter.md); deliver a copy to /home/m2/m2o/.planning/federated-learning/proposals/ - [x] T009 [P] Define shared config conventions doc `docs/config.md`: `~/.m2-market/config.toml` keys, env vars for services, key classes (service/admin) — from contracts/cli.md + ledger-api.md **Checkpoint**: schemas frozen, registry live, coordination recorded --- ## Phase 3: User Story 1 — Paid install via CLI (Priority: P1) 🎯 MVP **Goal**: `search → show → install` closes debit→grant→apply on a real machine **Independent test**: quickstart.md Scenarios B + C against a deployed ledger with one published listing ### Ledger core (contracts/ledger-api.md) - [x] T010 [P] [US1] Implement tx + grant models and derived-balance queries in `ledger/src/m2_ledger/models.py` (SQLite WAL, append-only invariants, data-model.md Transaction/LicenseGrant) - [x] T011 [P] [US1] Implement X-API-Key auth (service/admin key classes) in `ledger/src/m2_ledger/auth.py` - [x] T012 [US1] Implement API routes `/health`, `/balance/{op}`, `/tx/install`, `/tx/refund`, `/tx`, `/license/{op}/{listing}` in `ledger/src/m2_ledger/api.py` (402/409 semantics, all-or-nothing install tx, idempotent by ref) - [x] T013 [US1] Ledger unit + property tests in `ledger/tests/test_properties.py`: balance==fold(log) under generated sequences; no grant without tx; install atomicity; duplicate-ref 409 - [x] T014 [US1] Dockerfile + Coolify deployment for m2-ledger on the `coolify` network in `ledger/Dockerfile` + `ledger/README.md` deploy notes; verify `GET /health` from the network - [x] T015 [US1] Create `market:catalog` partition and implement indexer `indexer/src/m2_market_indexer/reindex.py` + `watch.py` per contracts/catalog-index.md (registry → memory-api upsert, tenant_visibility payload, delist removal) ### CLI (contracts/cli.md) - [x] T016 [P] [US1] CLI package scaffold + config loading (`~/.m2-market/config.toml`, operator mapping) in `cli/src/m2_market/cli.py` (click group, `--json`, exit-code map) - [x] T017 [P] [US1] Ledger client in `cli/src/m2_market/ledger.py` (install/refund/balance/license calls, error mapping to exit codes 3/4) - [x] T018 [P] [US1] Registry client in `cli/src/m2_market/registry.py` (fetch listing dirs, download release bundle, verify content_hash) - [x] T019 [P] [US1] Catalog client in `cli/src/m2_market/catalog.py` (memory-api semantic query, server-side tenant filter param) - [x] T020 [P] [US1] ApplyAdapter protocol + InstallState in `cli/src/m2_market/apply/base.py` per contracts/apply-adapter.md (Changeset, plan/apply/verify/rollback, state.json read/write) - [x] T021 [US1] Local adapter in `cli/src/m2_market/apply/local.py`: recipe.yaml-driven file placement into agent-home, post-install checks, idempotent re-apply, rollback from backups - [x] T022 [US1] `m2core_sync` stub adapter in `cli/src/m2_market/apply/m2core_sync.py` raising RailsNotLanded (exit 6) - [x] T023 [US1] `search` + `show` commands in `cli/src/m2_market/cli.py` (catalog query → table/JSON; show renders evidence/price/permissions diff) - [x] T024 [US1] `install` command in `cli/src/m2_market/cli.py`: the FR-005 sequence (confirm → debit → fetch → apply → verify → refund-on-failure → state → entrypoint hint) per contracts/cli.md - [x] T025 [US1] `wallet` command in `cli/src/m2_market/cli.py` (balance + recent tx) - [x] T026 [US1] CLI integration tests in `cli/tests/test_install_flow.py` against a local ledger instance + fixture registry/catalog: funded install, insufficient funds (no side effects), idempotent re-run, apply-failure refund **Checkpoint**: Scenario B/C pass end-to-end with a fixture listing (SC-003/005 held locally) --- ## Phase 4: User Story 2 — Publish a Solution with evidence (Priority: P1) **Goal**: builder → validated listing PR → veto window → published → discoverable **Independent test**: quickstart.md Scenario A - [x] T027 [US2] `publish` command in `cli/src/m2_market/cli.py` + `cli/src/m2_market/publish.py`: validate bundle against schemas (exit 5 on violation incl. tenant firewall), push `listing/` branch, open PR via Forgejo API with template - [x] T028 [US2] Registry CI hardening in `m2/market-registry` CI config: evidence-required, owner-initiated + double-scrub attestations for tenant-scoped submissions, content_hash-vs-release check (Story 2 AC-2/AC-4 mechanical rejection) - [x] T029 [P] [US2] Package seed Solution `solutions/mm-pdf-report/` (from the proven mm-pdf outcome): solution.json, payload/, recipe.yaml, real evidence refs - [x] T030 [P] [US2] Package seed Solution `solutions/agent-scaffold/` (from the agent-scaffold skill outcome): solution.json, payload/, recipe.yaml, real evidence - [ ] T031 [P] [US2] BLOCKED-ON-OPERATOR: competitor-scan seed has no real evidence (constitution III) — t031 report recommends substituting Parlobyg email-triage (tenant-derived: needs owner-initiated + double-scrub sign-off) or another proven outcome - [x] T032 [US2] Publish all three seeds through the real protocol (PRs, review, merge), verify each discoverable via `m2-market search` (SC-002) **Checkpoint**: ≥3 published evidence-backed listings live --- ## Phase 5: User Story 3 — Wallets and starter grants (Priority: P2) **Goal**: bootstrapable economy with auditable balances **Independent test**: quickstart.md Scenario D + grant flow - [x] T033 [US3] `/grant/starter` admin endpoint in `ledger/src/m2_ledger/api.py` + config-driven default amount (100 cr flagged-unconfirmed) with tests in `ledger/tests/test_grants.py` - [x] T034 [US3] Snapshot module `ledger/src/m2_ledger/snapshot.py`: balances dump → commit `audit/YYYY-MM-DD.json` to m2/market-registry via Forgejo API; `/snapshot` admin endpoint + daily cron (Coolify scheduled task or in-app scheduler) - [x] T035 [US3] Issue starter grants to the two pilot operators (m2bd, sdjs-operator) and run Scenario D verification (snapshot committed, properties green) **🎯 WEDGE GATE (plan row 7, SC-001)**: - [x] T036 [US3] Execute the real paid install between two operators: m2bd buys one seeded listing published by sdjs-operator via `m2-market install` on a real desktop; capture ledger rows, grant, applied bundle, and invoke the entrypoint; record evidence in `.m2herd/context/` and the run report --- ## Phase 6: User Story 4 — M2 Store on canaries (Priority: P3) **Goal**: one-click install UI over the same rails (work lands in the cargstore repo, branch `m2-store`; contracts referenced from this repo) **Independent test**: quickstart.md Scenario F on chris-m2o - [ ] T037 [US4] Fork/branch `m2-store` on github.com/machine-machine/cargstore; swap catalog source from Flatpak JSON to `market:catalog` via memory-api HTTP (desktop-provisioned creds per research.md R7) in cargstore `src/` catalog service - [ ] T038 [US4] Add Solution install backend to cargstore that shells `m2-market install --json` and renders progress/result truthfully (incl. refund-on-failure path) in cargstore `src/` install manager - [ ] T039 [US4] Solution detail page: evidence, price, permissions diff (from `m2-market show --json`) in cargstore `src/` UI - [ ] T040 [US4] Rebrand Clawdbot→M2 Store and deploy to canaries chris-m2o + gunnar-m2o only; run Scenario F --- ## Phase 7: User Story 5 — Solution Scout v0 (Priority: P3) **Goal**: in-the-moment proposals on ONE canary, privacy-guarded **Independent test**: quickstart.md Scenario G - [ ] T041 [US5] Scout host exploration spike → `scout/SPIKE.md`: compare standalone supervised watcher vs Hermes plugin vs herdr plugin/hook on: summary access, opt-in enforcement point, on-box summarization cost, notification surface, rollout story per image class (research.md R6; operator fork §14.4 "explore") — time-boxed, ends with a decision - [ ] T042 [US5] Implement Scout v0 per SPIKE.md winner in `scout/` (or plugin location the spike picks): summary ingestion → embed → `market:catalog` match (tenant-scoped) → threshold → toast + deep link; opt-in via pull-policy config; rate cap (default 5/day) - [ ] T043 [US5] Proposal outcome recording (shown/dismissed/accepted/suppressed) locally + summarized to `market:evidence` per data-model.md Proposal; conversion counts folded to listing stats via indexer telemetry pass - [ ] T044 [US5] Deploy Scout v0 to ONE canary opt-in desktop; run Scenario G (incl. opt-out and rate-cap negative checks) --- ## Phase 8: Polish & Cross-Cutting - [ ] T045 [P] Install telemetry write-back (FR-014): CLI posts install outcome records to `market:evidence`; indexer folds into listing `stats.installs` — `cli/src/m2_market/telemetry.py` + indexer pass - [ ] T046 [P] `reindex` operational check task: run Scenario E, document recovery runbook in `docs/runbook.md` (incl. memory-api split-brain caveat from research.md R4) - [ ] T047 Run full quickstart.md scenario sweep A–E on canaries; fix gaps; tick spec.md acceptance boxes in a verification note `specs/001-market-first-wedge/VERIFICATION.md` --- ## Dependencies ``` Phase 1 → Phase 2 → { US1 (P3..) , US2 (needs T007 registry; T027 needs T016 CLI scaffold) } US1 T015 indexer ← T007 registry US2 T032 discoverability ← US1 T015+T023 (search path) US3 ← US1 ledger core (T010-T014) T036 WEDGE GATE ← T024 (install), T032 (real listing), T033/T035 (funded wallets) US4 ← US1 CLI (T023/T024) + T015 catalog US5 ← T015 catalog; T042 ← T041 spike Phase 8 T047 ← everything on its scenario list ``` Story completion order: US1 → US2 → US3 (gate) → US4 ∥ US5 → Polish. US4 and US5 are independent of each other and of the gate. ## Parallel execution examples - Phase 2: T004, T005, T008, T009 in parallel (disjoint files); T006/T007 after T004+T005. - US1 wave 1: T010, T011 ∥ T016, T017, T018, T019, T020 (7 workers, disjoint files). - US1 wave 2 (serial per file): T012→T013→T014; T021→T022 (same dir but disjoint files — T021 and T022 CAN pair if split carefully; keep serial to be safe); T023→T024→T025 (same cli.py — one worker). - US2 seeds: T029, T030, T031 in parallel (disjoint solution dirs). - Post-gate: US4 (T037–T040) and US5 (T041–T044) as two parallel workstreams. ## Implementation strategy MVP = Phase 1 + 2 + US1 with a fixture listing (Scenario B/C locally), then US2 seeds make it real, US3 funds it, T036 closes the wedge commercially (SC-001). US4/US5 extend surfaces after the gate — they improve discovery, they don't block the win. fedlearn integration (plan row 10) is intentionally NOT a task here: it becomes its own feature when the rails land, guided by docs/fedlearn-coordination.md and the frozen apply-adapter contract.