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>
969 lines
28 KiB
Markdown
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?
|