m2-market/specs/SPEC-INDEX.md
m2 (AI Agent) 8a8db9f2cc specs: m2-market spec discovery — 6 subsystem specs + synthesis index
herdr workforce output (6 codex angle-specialists -> claude synthesis):
S1 ledger/economy, S2 catalog/registry, S3 storefront (cargstore->M2 Store),
S4 Solution Scout (standalone watcher recommended), S5 packaging + 3 seed
Solutions (mm-pdf 25cr, agent-scaffold 35cr, competitor-scan 60cr),
S6 proposal engine + evidence loop.

SPEC-INDEX is the tie-break: 23-row cross-cutting contract table (16 mismatches
resolved, e.g. /v1 ledger paths win, m2store://listing/<id> deep link, one
m2.market.telemetry.v1 envelope, op_<slug> operator ids, tenant ids from m2-gpt),
8 deduped clarifications with defaults, 4-phase build order where the paid-install
wedge does NOT block on fedlearn (local ApplyAdapter until rails land), risk register.

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

19 KiB
Raw Blame History

SPEC-INDEX — M2 Marketplace spec discovery synthesis

Synthesized 2026-07-02 from S1S6 (all six specs present). Individual specs are unmodified; where they disagree, this index is the tie-break and names the owning spec to amend.

1. Executive summary

The M2 Marketplace turns proven, repeatable outcomes into installable Solutions sold for M2 credits. Six subsystems compose it:

  • S1 m2-ledger — standalone Coolify service, SQLite, append-only credit ledger with hash chain. Handles starter grants (100 m2cr), fixed-price install settlement (10% platform cut), license grants keyed (operator, listing, major_version), escrow-backed job settlement for operator-delivered services, route/resource settlement referencing m2-gpt metering, and daily audit snapshots committed to Forgejo. Architected to migrate under extended m2-gpt billing later; ships internal credits first.
  • S2 Catalog/Registry — Forgejo org m2 is the system of record. Listings enter via PR + veto (reusing fedlearn's scored-PR pipeline with a new commercialize disposition); releases carry immutable bundles verified by content_hash. market:catalog in memory-api is a rebuildable semantic index, never truth. m2-market CLI (search/show/install/publish) is the universal programmatic surface, tenant-filtered end to end.
  • S3 M2 Store — revival of the cargstore Electron app. Adds a SolutionManager beside the existing Flatpak backend, shells the m2-market CLI (never raw HTTP to ledger), renders price/evidence/permission-diff before install, registers the m2store:// deep-link protocol, and reads identity from provisioner-written ~/.m2-market/config.toml (0600). Canary-first on chris-m2o, then one agent-latest desktop.
  • S4 Solution Scout — standalone supervised per-desktop watcher (recommended over Hermes or herdr plugin). Reads only already-summarized local sources (herdr run summaries), summarizes on-box, queries the tenant-filtered catalog, and surfaces at most a few non-blocking proposals/day via toast + Store deep link. Propose-only; never installs.
  • S5 Packaging & Seeds — bundle contract (solution.yaml source → canonical solution.json/listing.json, recipe.yaml, permissions.yaml, evidence files), install/ uninstall/upgrade through the runtime-sync (m2-core-sync) apply adapter with a local adapter fallback until fedlearn rails land. Three seed Solutions: mm-pdf (25 m2cr), agent-scaffold (35 m2cr), competitor-scan (60 m2cr).
  • S6 Proposal Enginem2-market-proposer Coolify service plus a thin Hermes market-propose skill. Composes build-vs-install-vs-job proposals with evidence-weighted p50/p80/p95 estimates, cites listings and prior job evidence, and closes the pricing loop by writing m2.market.evidence.v1 records for every serious job.

The paid-install flow that everything serves: publish (PR+veto) → discover (search/Scout/ Hermes) → inspect (price, evidence, permission diff) → settle (ledger debit + license grant) → apply (runtime-sync recipe) → verify → telemetry back into evidence and listing stats. Tenant isolation, no-secrets-in-artifacts, canary-first reversibility, and Forgejo-as-truth are enforced at every hop.

2. Cross-cutting contract table

Legend: ✔ = specs agree; MISMATCH = specs conflict, fix given; GAP = referenced but not yet existing/frozen. "Owner" is the spec whose definition is normative after the fix.

# Interface Owner Consumers Status / resolution
1 solution.schema.json (m2.solution.v1) S2 S3, S4, S5, S6 MISMATCH — S2 specs price.model: fixed|job; frozen repo schema and S5 only validate fixed (S5 invents price_ext/service workaround). Fix: extend the enum to fixed|job in one schema PR before first paid install; delete the price_ext workaround. Job listings remain non-direct-installable.
2 listing.schema.json (m2.listing.v1) S2 S3, S4, S6 MISMATCHseller is an object in S2, a bare string in S3/S5 examples. Fix: object form {operator_id, display_name, ...} is canonical in v1; renderers show display_name.
3 Install settlement endpoint S1 m2-market CLI (S2/S5); indirectly S3 MISMATCH — S1 defines POST /v1/settlements/install; S2/S3/S4/S5 all say POST /tx/install. Fix: S1's versioned path wins (ledger owner). Only the CLI calls the ledger directly, so S2/S5 update their touchpoint lists and contracts/ledger-api.md; S3/S4 are insulated by the CLI.
4 License check S1 S5 install path, S3 installed-state MISMATCH — S1: GET /v1/licenses/{operator_id}/{listing_id}?major_version=; S5: GET /licenses/check?.... Fix: S1 path wins; S5 adapter hook updates.
5 Refund on apply failure S1 S5/CLI (exit code 4 path) MISMATCH — S1 refunds by POST /v1/settlements/install/{tx_group_id}/refund; S5 refunds by ref. Fix: CLI persists tx_group_id in ~/.m2-market/state.json at settle time and refunds by it.
6 Balance / wallet S1 S3 via m2-market wallet MISMATCH — S1: GET /v1/balances/{account_or_operator_id}; S3 assumes GET /balance/{operator_id}. Fix: S1 path; CLI insulates the Store.
7 Jobs API (POST /v1/jobs, milestones, cancel, dispute) S1 S6 (operator_job path), S3 (later checkout), S2 (job-listing refusal handoff) ✔ — post-wedge. Note S2's job-price example says settlement.ledger_reason: "route"; that is wrong — job settlements use tx_type=job. Fix in S2 example.
8 market:catalog partition + search filter contract S2 (writer: m2-market-indexer only) CLI, S3, S4, S6 MISMATCH — filter shapes differ: S2 filters.tenant_visibility_any: ["*", tenant]; S4/S6 pass tenant_id arrays. Fix: S2's filter contract is canonical; server-side filtering when memory-api supports it, mandatory client-side backstop in every reader (drop hits failing tenant_visibility/status=published).
9 market:evidence partition S6 (job evidence) + S2 (stats folding) S4, S5, S3 write telemetry; S2 indexer folds into listing.stats; S6 reads for estimates MISMATCH — five near-colliding schemas, worst being S2 m2.market_evidence.v1 vs S6 m2.market.evidence.v1. Fix: reserve m2.market.evidence.v1 (S6) for job-evidence records; unify all shown/opened/installed/dismissed telemetry (S2/S3/S4/S5 events) under one envelope m2.market.telemetry.v1 with an event_type registry. S2 renames its record.
10 Deep link protocol S3 S4, S6 MISMATCH — S3 registers m2store://solution/<listing_id>; S4 and S6 both emit m2store://listing/<listing_id>. Fix: m2store://listing/<listing_id> (majority, and the ID is a listing_id). S3 amends; params stay ?source=&proposal_id=&q= only, navigate-only semantics.
11 m2-market CLI JSON surface + exit codes 06 S2 (core), S5 (package/install/uninstall/upgrade), S6 (propose/evidence) S3 (sole backend), S4 (fallback) ✔ on verbs and exit codes. GAPshow --json must return a frozen permissions_diff schema before Store implementation (S3 clarification #2). Freeze it in contracts/cli.md.
12 Release bundle format S5 S2 (hash check, release hosting), S3 (display) MISMATCH — S2: solution-bundle.tar.zst + inner manifest.json; S5: <install_ref>.tar.gz, no manifest. Fix: S5 owns packaging → .tar.gz named <install_ref>.tar.gz, but adopt S2's inner manifest.json (per-file sha256 list). content_hash computed over the normalized tarball.
13 Registry repo, paths, PR labels S2 S5 (publish target), S1 (audit), fedlearn commercialize MISMATCH — S2 recommends existing m2/m2-market; S5 assumes m2/market-registry; S1 wants m2/market-audit. Fix: registry = m2/m2-market (exists today; split later is a location change only). Ledger audit = separate m2/market-audit so daily snapshot commits don't pollute registry history. S2's label set is canonical; S5 drops status:in_review label (state lives in listing.status).
14 Identity & credential bootstrap m2-gpt S1 (bearer introspection), S3 (config bootstrap), S6 (tenant keys) GAP — S3's POST /admin/v1/.../market-credentials and S1's introspection endpoint don't exist in m2-gpt yet. Fix (MVP): a scripts/market-bootstrap.sh admin script mints keys via existing m2-gpt admin scripts and writes ~/.m2-market/config.toml (0600); ledger validates service keys from its own keys file and defers bearer introspection to a small m2-gpt verify endpoint added post-wedge.
15 Metering read GET /internal/v1/metering/turn/{correlation_id} m2-gpt S6 evidence capture GAP — endpoint doesn't exist; S6 already specs the degraded path (tokens_unknown=true + herdr run ids). Ship degraded in MVP; add endpoint with the jobs phase.
16 Proposal schemas (m2.market.proposal-request.v1, m2.market.proposal.v1) S6 S4 (may call proposer), S3 (proposal_id passthrough), S1 (settlement ref metadata) MISMATCH — ID prefix prop_ (S3/S6) vs prp_ (S4). Fix: prop_. S4's local matcher is fine for MVP; it adopts the proposer API when S6 lands.
17 Scout→Hermes bridge event m2.hermes.market_proposal.v1 S4 Hermes market-propose skill (S6) ✔ shape; transport unresolved → clarification C8. Best-effort in MVP.
18 ~/.m2-market/config.toml (0600) S2/CLI S3, S4, S5 ✔ — keys: operator_id, tenant, ledger_url, ledger_api_key, memory_api_url, memory_api_key, forgejo_url, forgejo_token, apply_adapter.
19 Install state ~/.m2-market/state.json S5 S3 (installed badge), S1 (grant_id/tx_group_id refs) ✔ — add tx_group_id per fix #5.
20 Apply adapter (m2-core-sync plan/apply/uninstall + m2.recipe.v1) fedlearn / S5 wraps install path (S2 CLI), Scout self-install, Store deploy ✔ contract; GAP on exact uninstall/upgrade verb names (clarification C6). Local ApplyAdapter executes the same recipe shape until rails land.
21 Tenant namespace m2-gpt tenants table everyone MISMATCH — "m2" (S1), "machine-machine" (S2/S3/S4), "m2-core"/"__fleet__" (S6). Fix: canonical tenant IDs come from the m2-gpt tenants table, one documented list in contracts/tenants.md. tenant_visibility: ["*"] means public; m2-core is the shared/commons scope for evidence. No other sentinels (__fleet__ folds into m2-core).
22 Operator ID format S1 registry mapping S2, S5, S6 MISMATCHop_<slug> (S1/S2/S5) vs bare sdjs-operator/m2bd (S6). Fix: op_<slug> everywhere.
23 Ledger events JSONL (m2.ledger.event.v1) S1 S2 indexer (install counts, pricing evidence) ✔.

18 raw markers across S1S6 reduce to 8. Recommended default in bold; build proceeds on the default unless the operator overrides.

  1. Operator identity source of truth (S1#1). Where does op_* come from? Default: m2-gpt is canonical for (tenant_id, agent_id); m2-ledger maintains the operator_id ↔ principal mapping table as the operator registry until m2-gpt billing absorbs it. No new service.
  2. Registry & audit repo naming (S1#2, S2#1). Default: registry of record = existing m2/m2-market; ledger snapshots = new m2/market-audit. Paths already compatible with a later m2/market-registry split.
  3. Credit↔USD conversion for route settlement and estimates (S1#3, S6#3). Default: m2cr_per_usd = 100, stored as ledger config (PLATFORM env / admin endpoint), changeable only by ledger:admin. S6 uses the same constant as its static fallback when sample_count < 2.
  4. Listing review authority & merge policy (S2#2). Default: every commercial listing requires an explicit approved label from the m2 platform operator (mariusz) — no timeout auto-merge for paid listings in MVP; the 72h veto window applies on top, and risk:high always needs manual merge.
  5. Seed seller of record + competitor-scan pricing (S5#1, S5#2). Default: platform-owned op_m2core is seller for all three seeds (revenue → platform account; clean test of the money loop without self-dealing noise). competitor-scan ships as fixed-price install (60 m2cr); the bespoke operator-delivered variant becomes the first job/service listing after the jobs phase.
  6. Scrub attestation authority + m2-core-sync verbs (S6#2, S5#3). Default: double-scrub requires two distinct humans: the tenant owner initiates (owner_initiated=true), the platform operator countersigns — both recorded in scrub-attestation.json reviewers[]. Adapter verbs assumed plan/apply/uninstall (+ apply --upgrade); S5 tracks fedlearn and renames without contract change if needed.
  7. m2-gpt marketplace endpoints: credential issuance + bearer introspection + metering (S3#1, S6#1). Default: MVP uses an admin-run market-bootstrap.sh (no new m2-gpt API); M2_GPT_CORRELATION_ID is injected as an env var into skill execution where available, else evidence records tokens_unknown=true. The three m2-gpt endpoints (market-credentials, token verify, metering-by-turn) land as one small m2-gpt change in the jobs phase.
  8. Standard license text + Hermes bridge transport (S2#3, S4#2, S3#3). Default: write m2-market-standard-v1 as a one-page internal commercial license (per-operator install, major-version coverage, internal modification allowed, no redistribution, best-effort support) committed at m2/m2-market/licenses/; Scout→Hermes bridge = append-only local JSONL at ~/.local/share/m2-market/scout/hermes-bridge.jsonl (file is the simplest thing both sides can poll); Flatpak surface stays hidden behind a secondary "Apps" tab on canaries.

4. Build order

Dependency picture

  • Fedlearn critical path: only the universal fleet install path (m2-core-sync runtime-sync rails, fedlearn T-series) and the commercialize disposition handoff. S5 explicitly provides a local ApplyAdapter that executes the same m2.recipe.v1 shape, so the paid-install wedge does not block on fedlearn — it converges with it.
  • Independent of fedlearn: S1 ledger (fully standalone), S2 schemas/registry/PR-pipeline/ indexer, S3 Store (depends only on CLI), S4 Scout (depends on catalog + deep link), S6 proposer (depends on catalog + memory; metering endpoint optional/degraded).

Ordering constraints: S1 and S2 have no upstream dependencies and unblock everything. S5-bundles need S2 schemas fixed (job enum, seller object) first. S3 needs frozen show --json and exit codes (S2/S5). S4 needs S3's deep link registered. S6 needs S2 catalog and (optionally) S1 quotes.

Phase cut — MVP wedge = smallest paid install end-to-end

Phase 0 — contract fixes (days): apply mismatch fixes #1#13 above: schema PR (job enum, seller object), freeze show --json/permissions_diff, adopt /v1/* ledger paths in contracts/ledger-api.md, deep-link segment, telemetry envelope, tenant namespace doc.

Phase 1 — the wedge (first paid install):

  • S1 core: accounts, starter grant, POST /v1/settlements/install, license check, refund, snapshots. (Jobs endpoints stubbed/deferred.)
  • S2 core: registry layout in m2/m2-market, publish PR pipeline + CI gates, indexer, m2-market search/show/publish.
  • S5 core: bundle builder + mm-pdf seed bundle + local apply adapter + m2-market install/uninstall.
  • Bootstrap: market-bootstrap.sh writes config on chris-m2o.

Exit criterion: on chris-m2o, an operator with a 100-credit starter grant runs m2-market install lst_mm-pdf-report, credits move (buyer→seller→platform), a license grant exists, the skill lands via the recipe, mm-pdf --version verifies, and the ledger snapshot commits to Forgejo. No UI, no Scout, no proposer required.

Phase 2 — storefront + seeds: S3 M2 Store canary (chris-m2o, then one agent-latest), remaining seeds (agent-scaffold, competitor-scan), telemetry→stats folding.

Phase 3 — demand side: S4 Scout canary (propose-only), S6 proposer service + Hermes market-propose skill (degraded metering).

Phase 4 — jobs + convergence: S1 jobs/escrow/dispute, first service listing, the three m2-gpt endpoints (credentials, verify, metering), fedlearn commercialize importer once runtime-sync rails land, swap local adapter → m2-core-sync.

5. Risk register — top 6 (deduped across specs)

# Risk Where Mitigations (already specced)
1 Tenant data leakage — client data (gst/nasr/parlobyg/peter) reaching shared catalog, evidence, telemetry, or proposals S1S6 Tenant filters fail closed (empty filter = hard error); owner_initiated + double_scrubbed CI gate before any commercialization; summary-only indexing (never raw evidence/transcripts); cross-tenant reads return 404 not 403; Scout sends capped on-box summaries only.
2 Paid-but-not-applied split brain — debit succeeds, apply fails S1/S2/S3/S5 Settle-then-apply order with compensating refund rows (never deletes); Idempotency-Key on every mutation; CLI exit code 4 surfaces refund state; tx_group_id persisted in state.json for refund; append-only ledger + hash chain + verify-chain fail-closed.
3 Secrets sprawl — live keys in ~/.m2-market/config.toml, bundles, evidence, logs S2/S3/S4/S5 0600 enforcement (Store refuses to run otherwise); deterministic secret scanner blocks publish; secret_ref only in permissions; log redaction of *_key/*_token/bearer strings; runtime injection only, nothing in repos/images/releases.
4 Catalog/index split-brain — memory-api diverges from Forgejo truth (has happened before) S2/S4/S6 Forgejo is the only truth; market:catalog fully rebuildable via m2-market-indexer reindex; Scout/proposer treat search as best-effort and prefer no proposal over stale ones; evidence JSONL audit copies allow replay.
5 Host/fleet fragility + broken local registry route — mixed primus/agent-latest images, hard-freeze history, 302 registry S3/S4/S5 Everything ships as Forgejo release assets applied by runtime-sync/local adapter (no image bakes, no registry push/pull); canary-first (chris-m2o → one agent-latest) with idempotent, reversible recipes confined to ${AGENT_HOME}; new services are Coolify apps on the coolify network; Scout degrades instead of crash-looping.
6 Marketplace gaming & abuse — self-purchase stat inflation, fake evidence, proposal spam, escrow lockup S1/S2/S4/S6 Self-purchases rejected by default and always excluded from quality stats; publish requires reproducible provenance evidence; Scout rate limits/cooldowns/dismissal suppression; failed jobs recorded but never raise success stats; job terms must define auto-accept timeout or admin resolution so escrow can't lock forever.

Next actions: run Phase 0 contract fixes as a single PR set against m2/m2-market (schemas/, contracts/), then confirm the 8 clarification defaults with the operator.