m2-market/specs/S2-catalog-registry.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

969 lines
28 KiB
Markdown

# S2 — Catalog & Registry SPEC
Grounded in live inspection on 2026-07-02: `/home/m2/m2-market` already contains v1
`schemas/solution.schema.json` and `schemas/listing.schema.json`, first-wedge contracts for
registry/catalog/CLI, and a local `/home/m2/m2-core` clone with fedlearn schemas. Live
services are reachable: `https://gpt.machinemachine.ai/health`, `https://memory.machinemachine.ai/health`,
and `https://git.machinemachine.ai/api/healthz`; Docker shows Forgejo, memory-api, Qdrant,
m2o desktops, and Coolify on the `coolify` network.
## 1. Scope & non-goals
### MVP scope
- Freeze the marketplace catalog/registry contracts for installable **Solutions**.
- Treat Forgejo org `m2` as system of record:
- registry repo: `git.machinemachine.ai/m2/m2-market` unless split later into
`m2/market-registry`;
- releases carry immutable Solution bundles;
- listing PRs ride the fedlearn scored PR + human veto pipeline and add the
`commercialize` disposition.
- Define `solution.schema.json` as a commercial superset of fedlearn
`m2-core-manifest.schema.json` and the fedlearn candidate fields already assumed by
m2-market.
- Define `listing.schema.json` as the catalog-facing projection used by cargstore/M2 Store,
Scout, Hermes, and CLI.
- Define `market:catalog` in memory-api as a rebuildable semantic index, not source of truth.
- Define tenant-scoped search/show/install/publish CLI verbs for `m2-market`.
- Support v1 inventory types `solution|capability|resource|service`, but only `solution`
is installable in MVP. `service` may be published and discovered for job-priced work.
### Non-goals
- Ledger internals, wallet accounting, starter grants, refunds, payout mechanics: S1 owns
them. S2 defines only catalog-side fields and ledger touchpoints.
- Store UI design and Electron packaging: S3 owns cargstore/M2 Store.
- Scout matching host, thresholds, and notification UX: S4 owns it. S2 defines the catalog
query contract Scout consumes.
- Bundle build format, runtime-sync implementation, signing, and packaging details beyond
registry paths and manifest fields: S5 owns packaging.
- Hermes build-vs-install proposal policy and scoring: S6 owns proposal engine behavior.
## 2. User stories + acceptance criteria
### Story S2.1 — Publish a reviewed listing
As a builder, I package a repeatable outcome with evidence, price, seller identity, license,
permissions, and deployment metadata, then submit it for review.
- Given a valid bundle directory containing `solution.json`, `listing.json`, `recipe.yaml`,
payload files, and evidence files, when I run `m2-market publish <bundle-dir>`, then the
CLI validates schemas, computes the bundle hash, creates a Forgejo branch
`listing/<listing_id>-v<semver>`, uploads a release asset, and opens a PR.
- Given a listing PR, when CI runs, then it must validate both schemas, verify evidence is
non-empty, verify `solution.content_hash` matches the release asset, verify tenant firewall
rules, and render price, permission diff, evidence summary, and rollback note in the PR.
- Given a PR with no veto after the configured review window and an `approved` label, when it
merges, then `listing.status` becomes `published` and the indexer upserts it to
`market:catalog`.
- Given a PR with label `veto` or `/veto <reason>`, when the watcher processes it, then the
PR is closed without indexing and the listing remains `draft` or `rejected`.
### Story S2.2 — Search tenant-allowed listings
As an operator, I search by outcome rather than package id and see only listings my tenant may
use.
- Given a published listing with `tenant_visibility: ["*"]`, when any tenant searches a
semantically related query, then the listing may appear.
- Given a published listing with `tenant_visibility: ["gst"]`, when a caller from tenant
`nasr` searches, then the listing must not be returned by CLI, Store, Scout, or Hermes.
- Given `market:catalog` is deleted or stale, when `m2-market-indexer reindex` runs, then the
catalog is rebuilt from Forgejo `main` without needing any memory-api-only data.
### Story S2.3 — Show installable evidence and permissions
As a buyer, I inspect a listing before spending credits.
- Given a listing id, when I run `m2-market show <listing_id>`, then output includes name,
version, seller, price model, license, evidence summary, applicability, permissions,
install ref, and status.
- Given the listing is delisted, when I run `show`, then it is visible only by exact id or
license/install context and is not returned by broad search.
### Story S2.4 — Install through catalog contract
As a buyer, I install a published solution from the registry release that the listing points
at.
- Given a published fixed-price listing, when `m2-market install <listing_id>` runs, then S2
resolves the listing from registry/catalog, verifies status and tenant visibility, fetches
release `<listing_id>-v<semver>`, verifies `content_hash`, and hands the commercial charge
request to S1 before calling the S5 apply adapter.
- Given a job-priced listing, when `install` is attempted, then the CLI must refuse direct
install with exit code `1` and print the required proposal/job flow handoff; job settlement
is S1/S6 territory.
### Story S2.5 — Commercialize a fedlearn candidate
As a curator, I mark a scored fedlearn candidate as commercially listable rather than free
core.
- Given a fedlearn cluster passed scoring, when the reviewer chooses `commercialize`, then the
pipeline opens the same listing PR shape as `m2-market publish`.
- Given a fedlearn candidate is tenant-derived, when `commercialize` is selected without
`owner_initiated: true` and `scrub_status.double_scrubbed: true`, then CI rejects it before
human review.
- Given the item is fleet-infra learning, when reviewed, then it must use
`promote-to-core`, not `commercialize`.
## 3. Interfaces & data contracts
### 3.1 Forgejo registry of record
Canonical URL:
- MVP repo: `https://git.machinemachine.ai/m2/m2-market`
- Registry paths in that repo:
```text
schemas/
solution.schema.json
listing.schema.json
listings/<listing_id>/
listing.json
solution.json
evidence/
<evidence_id>.json
releases:
<listing_id>-v<semver>
assets:
solution-bundle.tar.zst
audit/
catalog-index/YYYY-MM-DDTHHMMSSZ.json
```
If operations later split a clean `m2/market-registry` repo, paths and release tags stay
identical. The split is a remote/location change, not a contract change.
Release bundle shape:
```text
solution-bundle.tar.zst
solution.json
listing.json
recipe.yaml
payload/
evidence/
manifest.json
```
`manifest.json` inside the bundle:
```json
{
"schema_version": "m2.solution_bundle.v1",
"listing_id": "lst_competitor-scan",
"solution_id": "sol_competitor-scan",
"solution_version": "1.0.0",
"created_at": "2026-07-02T00:00:00Z",
"files": [
{"path": "solution.json", "sha256": "hex"},
{"path": "recipe.yaml", "sha256": "hex"}
]
}
```
Release tag format: `<listing_id>-v<semver>`, matching `listing.install_ref`.
### 3.2 `solution.schema.json` v1
Normative schema lives at `schemas/solution.schema.json`. S2 requires the existing schema be
tightened before implementation to cover the locked decisions below.
Required top-level fields:
```json
{
"schema_version": "m2.solution.v1",
"solution_id": "sol_mm-pdf-report",
"name": "MM PDF Report",
"summary": "Generate branded PDF reports from structured input.",
"intent": "Turn approved analysis into a styled client-ready PDF report.",
"behavior": {},
"tools": [],
"runtime": {},
"memory_schema": {},
"permissions": [],
"deployment": {
"recipe_ref": "recipe.yaml",
"entrypoint": "m2-solution run mm-pdf-report",
"verify_command": "m2-solution verify mm-pdf-report"
},
"applicability": {
"image_classes": ["primus", "agent-latest"],
"roles": ["operator"],
"tool_requirements": []
},
"tenant_scope": "m2-core",
"evidence": [],
"content_hash": "sha256:<64-hex>",
"price": {},
"seller": "op_sdjs",
"license": {},
"revenue_split": {}
}
```
Fedlearn-compatible core subset:
- `schema_version`
- `applicability.image_classes[]`
- `applicability.roles[]`
- `applicability.tool_requirements[]`
- `tenant_scope`
- `evidence[]`
- `content_hash`
- `scrub_status` when tenant-derived
Commercial superset fields:
#### Price
The current local schema only permits fixed price. MVP must support the locked v1 models:
```json
{
"model": "fixed",
"amount": 40,
"currency": "m2cr"
}
```
```json
{
"model": "job",
"currency": "m2cr",
"quote": {
"min_amount": 120,
"max_amount": 300,
"expires_after_hours": 72,
"requires_acceptance": true
},
"settlement": {
"ledger_reason": "route",
"escrow_required": true
}
}
```
Rules:
- `fixed` is directly installable.
- `job` is discoverable and showable, but install starts a proposal/job route rather than a
direct apply.
- `currency` is always `m2cr` in MVP.
- `amount` is an integer number of credits; no fractional credits.
#### Seller
```json
{
"operator_id": "op_sdjs",
"display_name": "sdjs operator",
"contact_ref": "forgejo:m2/sdjs",
"tenant_id": "machine-machine"
}
```
The existing schema allows a bare string. For implementation, accept both during migration,
but normalize registry records to the object form above.
#### License
```json
{
"license_id": "lic_m2_market_standard_v1",
"terms": "m2-market-standard-v1",
"scope": "operator",
"major_version_coverage": true,
"allows_internal_modification": true,
"allows_redistribution": false,
"support": "best_effort"
}
```
Rules:
- License grants are keyed by `(operator_id, listing_id, major_version)`.
- Delisting does not revoke existing grants.
- Redistribution is false unless explicitly allowed.
#### Revenue split
```json
{
"platform_pct": 10,
"seller_pct": 90,
"contributors": [
{"operator_id": "op_researcher", "pct": 10}
]
}
```
Rules:
- Percentages must total 100.
- Default platform cut is 10 percent per locked brief.
- Ledger execution and payout are S1, but registry stores the split used to explain pricing
and reproduce the charge request.
#### Permissions
```json
[
{
"permission_id": "perm_memory_read_project",
"kind": "memory",
"access": "read",
"scope": "tenant",
"target": "project_context",
"reason": "Read project brief and prior approved notes.",
"required": true
},
{
"permission_id": "perm_browser_control",
"kind": "desktop",
"access": "take-control",
"scope": "operator-approved",
"target": "browser",
"reason": "Operate browser only after user approval.",
"required": false
}
]
```
Allowed `kind`: `memory|filesystem|network|desktop|mcp|secret_ref|ledger|browser`.
Allowed `access`: `read|write|execute|observe|suggest|take-control|hand-back`.
Rules:
- No raw secret values. Use `secret_ref` identifiers only.
- Permission diff is required in listing PR body and `m2-market show`.
- Tenant-scoped permissions cannot target another tenant.
#### Evidence
```json
[
{
"evidence_id": "ev_mm_pdf_2026w27_001",
"source": "herdr",
"ref": "evidence/ev_mm_pdf_2026w27_001.json",
"machine": "chris-m2o",
"session": "sess_2026w27_pdf",
"summary": "Generated a branded PDF report from structured findings.",
"tokens": 32000,
"wall_time_seconds": 840,
"cost_estimate_m2cr": 12,
"outcome": "success",
"scrub_status": {
"secrets_redacted": true,
"pii_redacted": true,
"double_scrubbed": true
}
}
]
```
Rules:
- At least one evidence item is required for publish.
- Evidence files in `evidence/` must not contain raw tenant data or secrets.
- Tenant-derived work requires owner initiation and double scrub.
### 3.3 `listing.schema.json` v1
Normative schema lives at `schemas/listing.schema.json`. Required implementation shape:
```json
{
"schema_version": "m2.listing.v1",
"listing_id": "lst_mm-pdf-report",
"solution_id": "sol_mm-pdf-report",
"solution_version": "1.0.0",
"inventory_type": "solution",
"name": "MM PDF Report",
"summary": "Generate branded PDF reports from structured input.",
"category": "reporting",
"keywords": ["pdf", "report", "brand"],
"icon": "icons/mm-pdf.png",
"price": {"model": "fixed", "amount": 40, "currency": "m2cr"},
"seller": {"operator_id": "op_sdjs", "display_name": "sdjs operator"},
"license_ref": "lic_m2_market_standard_v1",
"evidence_summary": "Validated on chris-m2o with one successful report run.",
"tenant_visibility": ["*"],
"applicability": {
"image_classes": ["primus", "agent-latest"],
"roles": ["operator"],
"tool_requirements": []
},
"permissions_summary": [
{"kind": "filesystem", "access": "write", "target": "~/.m2-market/solutions/mm-pdf"}
],
"stats": {
"installs": 0,
"rating": 0,
"proposals_shown": 0,
"proposals_accepted": 0
},
"status": "published",
"install_ref": "lst_mm-pdf-report-v1.0.0",
"registry": {
"repo": "m2/m2-market",
"commit": "<git-sha>",
"release_url": "https://git.machinemachine.ai/m2/m2-market/releases/tag/lst_mm-pdf-report-v1.0.0"
}
}
```
State machine:
```text
draft -> in_review -> published -> delisted
| |
v v
rejected delisted
```
State rules:
- `draft`: local or branch-only; not indexed.
- `in_review`: PR exists; not indexed.
- `published`: merged to main; indexed.
- `delisted`: exact-id show remains possible for license/install history; broad search hides it.
- `rejected`: optional audit state; not indexed.
### 3.4 Listing PR template
Every publish/commercialize PR body must include:
```markdown
## Commercialize Review
Listing: <listing_id>
Solution: <solution_id>@<version>
Inventory type: solution|service|capability|resource
Price: <fixed amount m2cr | job quote range>
Seller: <operator_id>
Tenant visibility: <["*"] or tenant ids>
## Evidence
<summary with evidence refs>
## Permission Diff
<new permissions vs current installed state or "new install">
## Tenant Firewall
Owner initiated: true|false|not-applicable
Double scrubbed: true|false|not-applicable
## Rollback
<how to uninstall/rollback release asset>
## Veto
Veto until: <timestamp>
Use label `veto` or comment `/veto <reason>`.
```
Required labels:
- `market:listing`
- `disposition:commercialize`
- `risk:low|medium|high`
- `inventory:solution|capability|resource|service`
- `veto-until:<iso8601>`
- `tenant:<tenant-id|public>`
Merge gates:
- schema validation green;
- content hash check green;
- secret scan green;
- tenant firewall check green;
- no `veto` label;
- `approved` label present;
- high-risk listings require manual merge, never timeout auto-merge.
### 3.5 `market:catalog` partition in memory-api
Partition name: `market:catalog`.
Source: published `listings/<listing_id>/listing.json` plus selected fields from
`solution.json`.
Writer: only `m2-market-indexer`.
Readers: `m2-market` CLI, M2 Store, Scout, Hermes market-propose skill.
Record shape stored in memory-api:
```json
{
"agent_id": "market:catalog",
"text": "MM PDF Report\nGenerate branded PDF reports...\nintent...\nkeywords...\nevidence summary...",
"metadata": {
"schema_version": "m2.catalog_record.v1",
"listing_id": "lst_mm-pdf-report",
"solution_id": "sol_mm-pdf-report",
"solution_version": "1.0.0",
"inventory_type": "solution",
"name": "MM PDF Report",
"summary": "Generate branded PDF reports from structured input.",
"category": "reporting",
"keywords": ["pdf", "report", "brand"],
"price": {"model": "fixed", "amount": 40, "currency": "m2cr"},
"seller_operator_id": "op_sdjs",
"tenant_visibility": ["*"],
"status": "published",
"install_ref": "lst_mm-pdf-report-v1.0.0",
"registry_commit": "<git-sha>",
"content_hash": "sha256:<64-hex>",
"applicability": {
"image_classes": ["primus", "agent-latest"],
"roles": ["operator"],
"tool_requirements": []
},
"stats": {
"installs": 0,
"rating": 0,
"proposals_shown": 0,
"proposals_accepted": 0
}
}
}
```
Index text fields:
- `listing.name`
- `listing.summary`
- `listing.category`
- `listing.keywords`
- `solution.intent`
- `solution.description`
- `solution.behavior` summaries only
- `listing.evidence_summary`
- evidence summaries, never raw evidence payloads
Tenant-scoped query contract:
```http
POST /memory/search
X-API-Key: <runtime key>
Content-Type: application/json
{
"agent_id": "market:catalog",
"query": "generate branded PDF reports",
"limit": 10,
"filters": {
"status": "published",
"tenant_visibility_any": ["*", "machine-machine"],
"inventory_type": ["solution"],
"price_model": ["fixed", "job"],
"image_class": "primus"
}
}
```
Response normalized by `m2-market-indexer`/CLI:
```json
{
"items": [
{
"listing_id": "lst_mm-pdf-report",
"score": 0.83,
"listing": {},
"registry_commit": "<git-sha>"
}
]
}
```
Indexing rules:
- `published` records are upserted.
- `delisted` records are deleted from broad catalog index but retained in registry.
- `draft`, `in_review`, and `rejected` are never indexed.
- All writes require memory-api `X-API-Key`.
- If memory-api split-brain recurs, registry remains truth and reindex is the recovery path.
Indexer commands:
```bash
m2-market-indexer watch --repo /home/m2/m2-market --poll-seconds 300
m2-market-indexer reindex --repo /home/m2/m2-market --partition market:catalog
m2-market-indexer validate --repo /home/m2/m2-market
```
Telemetry partition: `market:evidence`.
Install/proposal telemetry record:
```json
{
"agent_id": "market:evidence",
"text": "Install succeeded for lst_mm-pdf-report on chris-m2o in 52 seconds.",
"metadata": {
"schema_version": "m2.market_evidence.v1",
"event_id": "evt_01h...",
"event_type": "install_succeeded",
"listing_id": "lst_mm-pdf-report",
"operator_id": "op_m2bd",
"tenant_id": "machine-machine",
"desktop": "chris-m2o",
"ts": "2026-07-02T00:00:00Z",
"duration_seconds": 52,
"ledger_ref": "install:lst_mm-pdf-report:<uuid>",
"scrubbed": true
}
}
```
Telemetry folding:
- A scheduled indexer job folds aggregate counts into `listing.stats`.
- Stats changes may be committed directly by an indexer bot or batched into a PR; registry
remains truth.
- Self-purchases are excluded from quality metrics.
### 3.6 CLI contract
Config file: `~/.m2-market/config.toml`, mode `0600`.
```toml
operator_id = "op_m2bd"
tenant = "machine-machine"
image_class = "primus"
ledger_url = "http://m2-ledger:8000"
ledger_api_key = "..."
memory_api_url = "https://memory.machinemachine.ai"
memory_api_key = "..."
forgejo_url = "https://git.machinemachine.ai"
forgejo_token = "..."
registry_repo = "m2/m2-market"
apply_adapter = "m2core-sync"
```
All commands support `--json`.
#### `m2-market search`
```bash
m2-market search "branded pdf report" --type solution --limit 10
```
Behavior:
- queries `market:catalog`;
- applies tenant filter server-side when memory-api supports it and client-side as a backstop;
- hides delisted records;
- returns id, name, summary, inventory type, price, seller, installs, rating, score.
#### `m2-market show`
```bash
m2-market show lst_mm-pdf-report
```
Behavior:
- resolves exact listing from registry first, catalog second;
- shows evidence, price, seller, license, permissions, applicability, status, install ref;
- for delisted listing, shows delisted warning and existing license status if available.
#### `m2-market install`
```bash
m2-market install lst_mm-pdf-report --yes
```
Behavior for `price.model=fixed`:
1. Resolve listing and verify `status=published`.
2. Verify tenant visibility and applicability.
3. Display price, license, and permission diff unless `--yes`.
4. Ask S1 ledger to debit buyer and create grant.
5. Fetch release asset named by `install_ref`.
6. Verify `solution.content_hash`.
7. Call S5 apply adapter (`m2-core-sync` when landed; local adapter only for pre-rails wedge).
8. Write install telemetry to `market:evidence`.
Behavior for `price.model=job`:
- no direct apply;
- prints proposal handoff instructions and exits non-zero until S6 job flow exists.
Exit codes:
- `0`: success or idempotent no-op
- `1`: usage or unsupported flow such as direct install of job listing
- `2`: listing not found
- `3`: insufficient funds from ledger
- `4`: apply failed after charge; refund/remediation delegated to S1/S5 flow
- `5`: validation, tenant firewall, or permission rejection
- `6`: sync rails not landed
#### `m2-market publish`
```bash
m2-market publish ./bundle-dir
```
Behavior:
- validates `solution.json`, `listing.json`, evidence, release manifest, content hash;
- enforces tenant/firewall checks before pushing;
- creates or updates `listings/<listing_id>/`;
- creates release `<listing_id>-v<semver>`;
- opens a PR using the listing template.
Publish options:
```bash
m2-market publish ./bundle-dir --draft
m2-market publish ./bundle-dir --tenant-visibility '*'
m2-market publish ./bundle-dir --price fixed:40
m2-market publish ./bundle-dir --price-job 120:300
```
#### `m2-market wallet`
Catalog CLI may expose `wallet` for operator convenience, but S1 owns its API semantics.
#### `m2-market reindex`
Admin-only command:
```bash
m2-market reindex --full
```
Invokes `m2-market-indexer reindex` and emits audit record under
`audit/catalog-index/<timestamp>.json`.
### 3.7 APIs S2 consumes or exposes
S2 does not add a public long-lived service in MVP. It uses these APIs:
Forgejo:
```http
GET /api/v1/repos/m2/m2-market
GET /api/v1/repos/m2/m2-market/contents/listings/<listing_id>/listing.json?ref=main
POST /api/v1/repos/m2/m2-market/pulls
POST /api/v1/repos/m2/m2-market/releases
GET /api/v1/repos/m2/m2-market/releases/tags/<listing_id>-v<semver>
```
memory-api:
```http
GET /health
POST /memory/store
POST /memory/search
```
ledger S1 touchpoints:
```http
POST /tx/install
GET /license/{operator_id}/{listing_id}
```
The install charge request S2 sends to S1:
```json
{
"buyer_operator_id": "op_m2bd",
"listing_id": "lst_mm-pdf-report",
"solution_version": "1.0.0",
"price": {"model": "fixed", "amount": 40, "currency": "m2cr"},
"seller": {"operator_id": "op_sdjs"},
"revenue_split": {"platform_pct": 10, "seller_pct": 90},
"ref": "install:lst_mm-pdf-report:<uuid>"
}
```
### 3.8 File paths
Registry:
- `/home/m2/m2-market/schemas/solution.schema.json`
- `/home/m2/m2-market/schemas/listing.schema.json`
- `/home/m2/m2-market/listings/<listing_id>/listing.json`
- `/home/m2/m2-market/listings/<listing_id>/solution.json`
- `/home/m2/m2-market/listings/<listing_id>/evidence/*.json`
CLI local state:
- `~/.m2-market/config.toml`
- `~/.m2-market/state.json`
- `~/.cache/m2-market/releases/<install_ref>/solution-bundle.tar.zst`
Applied solution target:
- S5 decides exact targets, but S2 requires runtime-sync compatibility across `primus` and
`agent-latest`; no image rebuild and no local registry push/pull.
## 4. Integration contract with other subsystems
### S1 ledger
S2 provides:
- listing id, seller, price, revenue split, license terms, and install ref.
- install charge request for fixed-price installs.
S1 provides:
- debit/credit/grant transaction;
- license lookup;
- refund/remediation path on apply failure;
- ledger audit snapshot.
Boundary: S2 never computes authoritative balances and never writes ledger rows.
### S3 store
S2 provides:
- `market:catalog` search contract;
- `listing.json` presentation fields compatible with cargstore basics:
`id/name/summary/category/icon/featured-ish keywords/install_ref`;
- `m2-market show/install --json` as stable Store backend.
S3 provides:
- UI rendering, deep links, and install button behavior.
### S4 Scout
S2 provides:
- tenant-filtered semantic search;
- catalog fields needed in proposal toast: listing name, summary, price, rating, installs,
evidence summary, install deep link;
- telemetry sink `market:evidence`.
S4 provides:
- on-box summarization, opt-in/rate limit enforcement, confidence threshold, notification UX.
### S5 packaging/runtime-sync
S2 provides:
- registry release tag and bundle hash contract;
- `solution.deployment` fields;
- permission manifest and applicability constraints.
S5 provides:
- bundle builder;
- signing if added;
- `recipe.yaml`;
- `m2-core-sync`/runtime-sync apply, verify, rollback.
### S6 proposal engine
S2 provides:
- catalog query and listing detail API/CLI;
- job-priced listing discovery;
- evidence and stats fields for build-vs-install proposals.
S6 provides:
- proposal composition, cost comparison, job routing, and when to recommend install vs build.
### Fedlearn
S2 integrates with fedlearn by:
- preserving fedlearn-compatible manifest fields;
- adding curation disposition `commercialize`;
- reusing scored PR + veto semantics;
- using runtime-sync as universal install path.
Fedlearn dispositions:
```text
promote-to-core -> m2/m2-core, free commons
commercialize -> m2/m2-market listings PR
reject/park -> no catalog listing
```
## 5. Options compared
### Registry repo: `m2/m2-market` vs separate `m2/market-registry`
- `m2/m2-market`: already present locally, contains schemas, docs, and seed solution dirs;
simplest MVP.
- `m2/market-registry`: cleaner source of truth, smaller clone for desktops, cleaner audit
and release history.
Recommendation: use `m2/m2-market` for MVP because it exists and the brief names repo layout
`m2/m2-market`; keep paths compatible with a later split to `m2/market-registry`.
### Catalog truth: Forgejo vs memory-api
- Forgejo: versioned, reviewable, release assets, PR/veto, audit.
- memory-api: semantic search and tenant-scoped retrieval, but rebuildable and exposed to
split-brain risk.
Recommendation: Forgejo is truth; `market:catalog` is derived.
### Publish flow: direct push vs PR/veto
- Direct push is faster but bypasses tenant and evidence review.
- PR/veto reuses fedlearn gates and gives commercial listings an audit trail.
Recommendation: all published listings enter through PR/veto; no direct publish to main.
### Price v1: fixed only vs fixed + job
- Fixed only is easiest to install and ledger-settle.
- Fixed + job is locked by the brief and needed for operator-delivered services.
Recommendation: support both in schemas and catalog; only fixed is direct-installable in MVP.
## 6. Risks/edge cases
- Tenant data leakage: enforce `tenant_visibility`, `tenant_scope`, owner initiation, double
scrub, evidence summary-only indexing, and CI secret scans.
- Registry/index divergence: registry wins; reindex from main is mandatory operational
recovery.
- Broken local registry route: release assets are fetched from Forgejo, not Docker
push/pull.
- Mixed desktop images: install path must go through runtime-sync/apply adapter, not baked
images.
- Paid but not applied: S2 records enough install ref and telemetry for S1/S5 refund and
remediation, but S1/S5 own transaction compensation and rollback.
- Self-purchase metric gaming: allow install but exclude from quality stats.
- Job listing confusion: direct install must refuse job-priced listings until proposal/job
flow is implemented.
- Evidence bloat or raw data indexing: catalog text embeds summaries only; evidence files
remain scrubbed in Forgejo.
- Permission creep: every permission must include kind/access/scope/target/reason and render
in PR plus `show`.
- Stats churn in registry: batch stats updates to avoid noisy PRs; retain ability to rebuild
stats from `market:evidence`.
## 7. [NEEDS CLARIFICATION]
1. [NEEDS CLARIFICATION] Registry remote: should MVP use the existing `m2/m2-market` repo as
the registry of record, or create/split `m2/market-registry` before the first paid install?
S2 recommends existing `m2/m2-market` for MVP.
2. [NEEDS CLARIFICATION] Veto authority and timeout: which operator/team receives listing PR
notifications, and is low-risk auto-merge after 72h allowed for commercial listings or
should every commercial listing require explicit `approved`?
3. [NEEDS CLARIFICATION] Standard license text: what exact `m2-market-standard-v1` terms
should be stored behind `license.terms` before real paid installs?