# 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 `, then the CLI validates schemas, computes the bundle hash, creates a Forgejo branch `listing/-v`, 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 `, 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 `, 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 ` runs, then S2 resolves the listing from registry/catalog, verifies status and tenant visibility, fetches release `-v`, 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.json solution.json evidence/ .json releases: -v 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: `-v`, 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": "", "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: Solution: @ Inventory type: solution|service|capability|resource Price: Seller: Tenant visibility: <["*"] or tenant ids> ## Evidence ## Permission Diff ## Tenant Firewall Owner initiated: true|false|not-applicable Double scrubbed: true|false|not-applicable ## Rollback ## Veto Veto until: Use label `veto` or comment `/veto `. ``` Required labels: - `market:listing` - `disposition:commercialize` - `risk:low|medium|high` - `inventory:solution|capability|resource|service` - `veto-until:` - `tenant:` 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.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": "", "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: 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": "" } ] } ``` 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:", "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//`; - creates release `-v`; - 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/.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.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/-v ``` 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:" } ``` ### 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.json` - `/home/m2/m2-market/listings//solution.json` - `/home/m2/m2-market/listings//evidence/*.json` CLI local state: - `~/.m2-market/config.toml` - `~/.m2-market/state.json` - `~/.cache/m2-market/releases//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?