phase0: freeze marketplace contract fixes
This commit is contained in:
parent
aeba02f287
commit
cd43dac89f
17 changed files with 897 additions and 344 deletions
126
contracts/cli.md
Normal file
126
contracts/cli.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
# m2-market CLI contract v1
|
||||||
|
|
||||||
|
The CLI is the only marketplace surface that calls m2-ledger directly. Store, Scout, and Hermes
|
||||||
|
consume the CLI JSON contract.
|
||||||
|
|
||||||
|
Config: `~/.m2-market/config.toml` mode `0600`.
|
||||||
|
|
||||||
|
Required keys: `operator_id`, `tenant`, `ledger_url`, `ledger_api_key`, `memory_api_url`,
|
||||||
|
`memory_api_key`, `forgejo_url`, `forgejo_token`, `apply_adapter`.
|
||||||
|
|
||||||
|
State: `~/.m2-market/state.json`. Install entries MUST store `grant_id` and `tx_group_id`.
|
||||||
|
|
||||||
|
## Verbs
|
||||||
|
|
||||||
|
- `m2-market search <query> [--type solution|capability|resource|service] [--limit N] [--json]`
|
||||||
|
- `m2-market show <listing_id> [--json]`
|
||||||
|
- `m2-market install <listing_id> [--yes] [--json]`
|
||||||
|
- `m2-market uninstall <listing_id> [--yes] [--json]`
|
||||||
|
- `m2-market upgrade <listing_id> [--to <semver>] [--yes] [--json]`
|
||||||
|
- `m2-market wallet [--operator <operator_id>] [--json]`
|
||||||
|
- `m2-market publish <bundle-dir> [--tenant-visibility <tenant-or-*>]`
|
||||||
|
- `m2-market package init|import-fedlearn|validate|build|diff-permissions|evidence-summary|verify-release`
|
||||||
|
- `m2-market reindex`
|
||||||
|
- `m2-market propose "<intent>" --tenant <tenant> --operator <operator_id> --json`
|
||||||
|
- `m2-market evidence capture|submit|show`
|
||||||
|
- `m2-market telemetry submit <event-jsonl>`
|
||||||
|
|
||||||
|
## Exit codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| 0 | Success, including idempotent no-op install. |
|
||||||
|
| 1 | Usage error, unsupported operation, or direct install refused for `price.model=job`. |
|
||||||
|
| 2 | Listing, release, license, or local install state not found. |
|
||||||
|
| 3 | Insufficient funds or ledger payment declined before apply. |
|
||||||
|
| 4 | Apply, verify, uninstall, or upgrade failed after settlement; CLI attempted refund when applicable. |
|
||||||
|
| 5 | Schema validation, tenant firewall, secret scan, permissions, or publish gate rejection. |
|
||||||
|
| 6 | External dependency unavailable or deferred adapter path not landed. |
|
||||||
|
|
||||||
|
## `show --json`
|
||||||
|
|
||||||
|
The detail payload is frozen for Store and Scout:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "m2.market.show.v1",
|
||||||
|
"listing": {
|
||||||
|
"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.",
|
||||||
|
"category": "reporting",
|
||||||
|
"price": {"model": "fixed", "amount": 25, "currency": "m2cr"},
|
||||||
|
"seller": {"operator_id": "op_m2core", "display_name": "M2 Core"},
|
||||||
|
"content_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"tenant_visibility": ["*"],
|
||||||
|
"status": "published",
|
||||||
|
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||||
|
},
|
||||||
|
"solution": {
|
||||||
|
"schema_version": "m2.solution.v1",
|
||||||
|
"solution_id": "sol_mm-pdf-report",
|
||||||
|
"name": "MM PDF Report",
|
||||||
|
"summary": "Generate branded PDF reports.",
|
||||||
|
"intent": "Turn approved analysis into a styled client-ready PDF report.",
|
||||||
|
"deployment": {
|
||||||
|
"recipe_ref": "recipe.yaml",
|
||||||
|
"entrypoint": "mm-pdf generate <input.md>",
|
||||||
|
"verify_command": "mm-pdf --version"
|
||||||
|
},
|
||||||
|
"applicability": {
|
||||||
|
"image_classes": ["primus", "agent-latest"],
|
||||||
|
"roles": ["operator"],
|
||||||
|
"tool_requirements": ["bash"]
|
||||||
|
},
|
||||||
|
"tenant_scope": "m2-core",
|
||||||
|
"evidence": [{"source": "forgejo:m2/m2-market/listings/lst_mm-pdf-report/evidence/provenance.jsonl"}],
|
||||||
|
"content_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"price": {"model": "fixed", "amount": 25, "currency": "m2cr"},
|
||||||
|
"seller": {"operator_id": "op_m2core", "display_name": "M2 Core"},
|
||||||
|
"license": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wallet": {"operator_id": "op_chris", "balance": 100, "currency": "m2cr", "as_of": "2026-07-02T00:00:00Z"},
|
||||||
|
"install_state": {
|
||||||
|
"status": "not_installed",
|
||||||
|
"installed_version": null,
|
||||||
|
"grant_id": null,
|
||||||
|
"tx_group_id": null
|
||||||
|
},
|
||||||
|
"permissions_diff": {
|
||||||
|
"schema_version": "m2.permissions_diff.v1",
|
||||||
|
"mode": "new_install",
|
||||||
|
"listing_id": "lst_mm-pdf-report",
|
||||||
|
"current_install_ref": null,
|
||||||
|
"target_install_ref": "lst_mm-pdf-report-v1.0.0",
|
||||||
|
"summary": {"added": 2, "removed": 0, "changed": 0, "unchanged": 0, "risk": "medium"},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"permission_id": "fs-write-agent-home",
|
||||||
|
"action": "added",
|
||||||
|
"kind": "filesystem",
|
||||||
|
"access": "write",
|
||||||
|
"scope": "${AGENT_HOME}/.claude/skills/mm-pdf",
|
||||||
|
"target": "skill files",
|
||||||
|
"reason": "install skill files",
|
||||||
|
"risk": "low",
|
||||||
|
"requires_confirmation": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`permissions_diff.mode` is one of `new_install`, `upgrade`, `reinstall`, `uninstall`, or
|
||||||
|
`no_change`. `items[].action` is one of `added`, `removed`, `changed`, or `unchanged`.
|
||||||
24
contracts/deeplink.md
Normal file
24
contracts/deeplink.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# M2 Store deep-link contract v1
|
||||||
|
|
||||||
|
Canonical listing detail URI:
|
||||||
|
|
||||||
|
```text
|
||||||
|
m2store://listing/<listing_id>?source=&proposal_id=&q=
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
m2store://listing/lst_competitor-scan?source=scout&proposal_id=prop_01j2abc
|
||||||
|
m2store://listing/lst_mm-pdf-report?source=hermes&q=pdf%20report
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Path segment is `listing`, not `solution`.
|
||||||
|
- `<listing_id>` uses the `lst_` prefix from `contracts/ids.md`.
|
||||||
|
- Allowed query parameters are exactly `source`, `proposal_id`, and `q`.
|
||||||
|
- The URI is navigate-only. It never installs, purchases, grants licenses, or mutates state.
|
||||||
|
- Unknown listing IDs open Store search/detail error UI without leaking tenant-forbidden records.
|
||||||
|
- Store may keep an internal route such as `/solution/<listing_id>`, but the external protocol
|
||||||
|
stays `m2store://listing/<listing_id>`.
|
||||||
22
contracts/ids.md
Normal file
22
contracts/ids.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Marketplace ID prefixes v1
|
||||||
|
|
||||||
|
Canonical prefixes:
|
||||||
|
|
||||||
|
| Prefix | Object | Example |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `op_` | Operator ID | `op_m2core` |
|
||||||
|
| `prop_` | Proposal ID | `prop_01j2abc` |
|
||||||
|
| `lst_` | Listing ID | `lst_mm-pdf-report` |
|
||||||
|
| `sub_` | Fedlearn submission ID | `sub_01j2candidate` |
|
||||||
|
| `clu_` | Fedlearn cluster ID | `clu_01j2cluster` |
|
||||||
|
| `core_` | Promoted core learning ID | `core_01j2learning` |
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Operators use `op_<slug>` everywhere. Bare names such as `m2bd` or `sdjs-operator` are display
|
||||||
|
names, not marketplace IDs.
|
||||||
|
- Proposals use `prop_`, never `prp_`.
|
||||||
|
- Listings use `lst_` and control price, visibility, version, and license grants.
|
||||||
|
- Solution manifests keep `sol_` for `solution_id`; this is already frozen in schema and is not
|
||||||
|
a replacement for `lst_` in Store links or ledger refs.
|
||||||
|
- Ledger transaction groups, grants, accounts, and jobs keep S1-owned prefixes outside this file.
|
||||||
139
contracts/ledger-api.md
Normal file
139
contracts/ledger-api.md
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# m2-ledger API contract v1
|
||||||
|
|
||||||
|
S1 owns ledger semantics. Marketplace clients MUST use the `/v1/*` paths below; older
|
||||||
|
`/tx/install`, `/tx/refund`, `/license/*`, `/licenses/check`, and `/balance/*` paths are not
|
||||||
|
contractual for m2-market.
|
||||||
|
|
||||||
|
All mutation requests require `Idempotency-Key` or an `idempotency_key` body field. If both are
|
||||||
|
present, they must match. All amounts are integer `m2cr`.
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
`GET /v1/accounts/me`
|
||||||
|
|
||||||
|
Returns the caller's resolved ledger identity:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"account_id": "acct_op_chris",
|
||||||
|
"operator_id": "op_chris",
|
||||||
|
"tenant_id": "m2",
|
||||||
|
"scopes": ["ledger:read", "ledger:install"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Balances
|
||||||
|
|
||||||
|
`GET /v1/balances/{account_or_operator_id}`
|
||||||
|
|
||||||
|
Returns a balance derived from the append-only transaction log. Store and Scout do not call this
|
||||||
|
directly; they use `m2-market wallet`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"account_id": "acct_op_chris",
|
||||||
|
"operator_id": "op_chris",
|
||||||
|
"tenant_id": "m2",
|
||||||
|
"balance": 100,
|
||||||
|
"currency": "m2cr",
|
||||||
|
"as_of": "2026-07-02T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install settlement
|
||||||
|
|
||||||
|
`POST /v1/settlements/install`
|
||||||
|
|
||||||
|
Fixed-price install settlement. Job-priced listings are refused by the CLI before this call and
|
||||||
|
use the jobs API instead.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"buyer_operator_id": "op_chris",
|
||||||
|
"seller_operator_id": "op_m2core",
|
||||||
|
"tenant_id": "m2",
|
||||||
|
"listing_id": "lst_mm-pdf-report",
|
||||||
|
"solution_id": "sol_mm-pdf-report",
|
||||||
|
"solution_version": "1.0.0",
|
||||||
|
"major_version": 1,
|
||||||
|
"install_ref": "lst_mm-pdf-report-v1.0.0",
|
||||||
|
"price": {"model": "fixed", "amount": 25, "currency": "m2cr"},
|
||||||
|
"revenue_split": {"platform_pct": 10, "seller_pct": 90}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Success returns the transaction group and license grant. The CLI MUST persist `tx_group_id` in
|
||||||
|
`~/.m2-market/state.json` before apply so refund can be addressed by group id.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tx_group_id": "txg_01j2install",
|
||||||
|
"grant_id": "gr_01j2license",
|
||||||
|
"balance": 75,
|
||||||
|
"license": {
|
||||||
|
"operator_id": "op_chris",
|
||||||
|
"listing_id": "lst_mm-pdf-report",
|
||||||
|
"major_version": 1,
|
||||||
|
"status": "active"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`402 insufficient_funds` writes no transactions or grant. Repeated idempotency keys return the
|
||||||
|
original settlement.
|
||||||
|
|
||||||
|
## Refund on apply failure
|
||||||
|
|
||||||
|
`POST /v1/settlements/install/{tx_group_id}/refund`
|
||||||
|
|
||||||
|
Refunds by `tx_group_id`, not by listing ref. Refunds are compensating append-only rows and mark
|
||||||
|
the related license grant `refunded`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reason": "apply_failed",
|
||||||
|
"apply_error": "verify command failed",
|
||||||
|
"listing_id": "lst_mm-pdf-report"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
`GET /v1/licenses/{operator_id}/{listing_id}?major_version=1`
|
||||||
|
|
||||||
|
Returns the active/refunded/revoked grant for `(operator_id, listing_id, major_version)`, or `404`
|
||||||
|
for not found or forbidden.
|
||||||
|
|
||||||
|
## Starter grants
|
||||||
|
|
||||||
|
`POST /v1/grants/starter`
|
||||||
|
|
||||||
|
Admin/service scoped. Default amount is `100 m2cr`.
|
||||||
|
|
||||||
|
## Jobs
|
||||||
|
|
||||||
|
`POST /v1/jobs`
|
||||||
|
|
||||||
|
Creates a funded job/escrow for job-priced listings. Job settlements use ledger `tx_type=job`.
|
||||||
|
|
||||||
|
Additional job paths:
|
||||||
|
|
||||||
|
- `POST /v1/jobs/{job_id}/start`
|
||||||
|
- `POST /v1/jobs/{job_id}/milestones/{milestone_id}/submit`
|
||||||
|
- `POST /v1/jobs/{job_id}/milestones/{milestone_id}/accept`
|
||||||
|
- `POST /v1/jobs/{job_id}/cancel`
|
||||||
|
- `POST /v1/jobs/{job_id}/dispute`
|
||||||
|
|
||||||
|
## Route/resource settlement
|
||||||
|
|
||||||
|
`POST /v1/settlements/route`
|
||||||
|
|
||||||
|
Records credit-denominated route/resource rows tied to m2-gpt metering references. It does not
|
||||||
|
modify m2-gpt metering.
|
||||||
|
|
||||||
|
## Audit snapshots
|
||||||
|
|
||||||
|
`POST /v1/admin/snapshots`
|
||||||
|
|
||||||
|
Writes deterministic balance/hash-chain snapshots to Forgejo repo `m2/market-audit`, not to the
|
||||||
|
registry repo `m2/m2-market`.
|
||||||
62
contracts/telemetry.md
Normal file
62
contracts/telemetry.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Marketplace telemetry contract v1
|
||||||
|
|
||||||
|
All shown/opened/installed/dismissed marketplace telemetry uses one envelope:
|
||||||
|
`m2.market.telemetry.v1`.
|
||||||
|
|
||||||
|
`m2.market.evidence.v1` is reserved for S6 job evidence records only. Do not write install,
|
||||||
|
proposal, Store, Scout, or CLI telemetry with the evidence schema.
|
||||||
|
|
||||||
|
## Envelope
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "m2.market.telemetry.v1",
|
||||||
|
"event_id": "evt_01j2telemetry",
|
||||||
|
"event_type": "market.install.succeeded",
|
||||||
|
"occurred_at": "2026-07-02T00:00:00Z",
|
||||||
|
"tenant_id": "m2",
|
||||||
|
"operator_id": "op_chris",
|
||||||
|
"agent_id": "chris-m2o",
|
||||||
|
"listing_id": "lst_mm-pdf-report",
|
||||||
|
"solution_id": "sol_mm-pdf-report",
|
||||||
|
"solution_version": "1.0.0",
|
||||||
|
"proposal_id": "prop_01j2abc",
|
||||||
|
"source": "cli",
|
||||||
|
"content_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required fields: `schema_version`, `event_id`, `event_type`, `occurred_at`, `tenant_id`,
|
||||||
|
`source`, and at least one of `listing_id`, `solution_id`, or `proposal_id`.
|
||||||
|
|
||||||
|
Telemetry must contain summaries and IDs only. No raw client data, secrets, transcript bodies, or
|
||||||
|
private job artifacts.
|
||||||
|
|
||||||
|
## Event type registry
|
||||||
|
|
||||||
|
- `market.listing.shown`
|
||||||
|
- `market.listing.opened`
|
||||||
|
- `market.proposal.generated`
|
||||||
|
- `market.proposal.shown`
|
||||||
|
- `market.proposal.opened`
|
||||||
|
- `market.proposal.accepted`
|
||||||
|
- `market.proposal.dismissed`
|
||||||
|
- `market.install.started`
|
||||||
|
- `market.install.succeeded`
|
||||||
|
- `market.install.failed`
|
||||||
|
- `market.install.refunded`
|
||||||
|
- `market.uninstall.succeeded`
|
||||||
|
- `market.upgrade.succeeded`
|
||||||
|
- `market.invoke.succeeded`
|
||||||
|
- `market.invoke.failed`
|
||||||
|
|
||||||
|
Indexers may fold telemetry into `listing.stats.installs`, `listing.stats.proposals_shown`, and
|
||||||
|
`listing.stats.proposals_accepted`. Self-purchases and self-generated proposal loops are excluded
|
||||||
|
from quality statistics.
|
||||||
|
|
||||||
|
## Job evidence reservation
|
||||||
|
|
||||||
|
`m2.market.evidence.v1` records are S6 job evidence records and carry `evidence_id`, `job_id`,
|
||||||
|
`wanted`, `tools`, `contributors`, `metering`, `resources`, `outcome`, `reusable_verdict`,
|
||||||
|
`scrub_status`, and `content_hash`. They are not a general event envelope.
|
||||||
34
contracts/tenants.md
Normal file
34
contracts/tenants.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Tenant namespace contract v1
|
||||||
|
|
||||||
|
Canonical tenant IDs come from the live m2-gpt `tenants` table. Marketplace contracts must not
|
||||||
|
invent alternate sentinels or display-name aliases.
|
||||||
|
|
||||||
|
Live table read on 2026-07-02:
|
||||||
|
|
||||||
|
| tenant_id | display_name | status |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `erlengrund-m2o` | erlengrund-m2o | active |
|
||||||
|
| `gunnar-m2o` | gunnar-m2o | active |
|
||||||
|
| `m2` | m2 Operator | active |
|
||||||
|
| `m2bd-m2o` | m2bd-m2o | active |
|
||||||
|
| `matrix` | matrix | active |
|
||||||
|
| `parlobyg-m2o` | parlobyg-m2o | active |
|
||||||
|
| `smoke7623` | Smoke smoke7623 | active |
|
||||||
|
| `e2e-verify-0617` | E2E Verify delete me | archived |
|
||||||
|
|
||||||
|
Marketplace-reserved scopes:
|
||||||
|
|
||||||
|
- `*` in `listing.tenant_visibility` means public to all tenants.
|
||||||
|
- `m2-core` is the shared commons scope for reusable, scrubbed marketplace evidence and shared
|
||||||
|
Solutions. It replaces `__fleet__` for marketplace contracts.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `tenant_visibility` is an allowlist of `*` or canonical active tenant IDs.
|
||||||
|
- Empty tenant filters fail closed.
|
||||||
|
- Readers must apply the S2 filter contract:
|
||||||
|
`filters.tenant_visibility_any = ["*", "<caller_tenant>"]`, with client-side backstop that
|
||||||
|
drops hits where `status != "published"` or caller tenant is not visible.
|
||||||
|
- Cross-tenant not-found and forbidden states are surfaced as `404` to avoid existence leaks.
|
||||||
|
- `m2-core` is allowed for `solution.tenant_scope` and evidence scope, but it is not a buyer
|
||||||
|
tenant and is not a replacement for `tenant_visibility: ["*"]`.
|
||||||
47
licenses/m2-market-standard-v1.md
Normal file
47
licenses/m2-market-standard-v1.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# M2 Market Standard License v1
|
||||||
|
|
||||||
|
This license applies to marketplace Solutions that reference
|
||||||
|
`license_id=lic_m2_market_standard_v1` and `terms=m2-market-standard-v1`.
|
||||||
|
|
||||||
|
## Grant
|
||||||
|
|
||||||
|
After successful ledger settlement, M2 grants the purchasing operator a non-exclusive internal
|
||||||
|
commercial license to install and use the purchased Solution for the covered major version.
|
||||||
|
License grants are keyed by `(operator_id, listing_id, major_version)`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The licensed operator may use the Solution inside owned or client workspaces where the operator is
|
||||||
|
authorized to work, subject to the listing's tenant visibility, permissions, and applicable tenant
|
||||||
|
data policy.
|
||||||
|
|
||||||
|
## Major-version coverage
|
||||||
|
|
||||||
|
The grant covers all minor and patch releases in the purchased major version. A new major version
|
||||||
|
requires a new grant unless the listing explicitly states otherwise.
|
||||||
|
|
||||||
|
## Internal modification
|
||||||
|
|
||||||
|
Internal modification is allowed for installation, configuration, debugging, and workflow
|
||||||
|
adaptation. Modified copies remain internal and do not change the seller's support obligations.
|
||||||
|
|
||||||
|
## No redistribution
|
||||||
|
|
||||||
|
Redistribution, resale, public mirroring, or repackaging as another marketplace listing is not
|
||||||
|
allowed without explicit written permission from the seller and platform operator.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Support is best effort. Listings may include verification commands, rollback notes, and evidence,
|
||||||
|
but no uptime, fitness, or outcome warranty is implied.
|
||||||
|
|
||||||
|
## Delisting and refunds
|
||||||
|
|
||||||
|
Delisting does not revoke existing active grants. Refunds for failed apply flows are handled by
|
||||||
|
append-only ledger refund rows and may mark the related grant as refunded.
|
||||||
|
|
||||||
|
## Secrets and tenant data
|
||||||
|
|
||||||
|
The license does not permit embedding secrets, raw client data, or tenant-private artifacts in
|
||||||
|
published bundles, telemetry, or shared evidence. Tenant-derived material must pass the required
|
||||||
|
owner-initiation and double-scrub controls before shared commercial use.
|
||||||
|
|
@ -1,115 +1,9 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://git.machinemachine.ai/m2/market-registry/schemas/listing.schema.json",
|
"$id": "https://git.machinemachine.ai/m2/m2-market/schemas/listing.schema.json",
|
||||||
"title": "Listing",
|
"title": "m2.listing.v1",
|
||||||
"description": "Catalog-facing record for a marketplace listing. Truth lives in m2/market-registry; derived copy indexed into market:catalog.",
|
"description": "Catalog-facing projection of a marketplace listing. Forgejo m2/m2-market is truth; market:catalog is rebuildable.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
|
||||||
"schema_version": {
|
|
||||||
"const": "m2.listing.v1"
|
|
||||||
},
|
|
||||||
"listing_id": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^lst_[a-z0-9-]+$"
|
|
||||||
},
|
|
||||||
"solution_id": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^sol_[a-z0-9-]+$"
|
|
||||||
},
|
|
||||||
"solution_version": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"inventory_type": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["solution", "capability", "resource", "service"]
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"summary": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"keywords": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Denormalized from solution at listing time.",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"const": "m2cr"
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["amount", "currency", "model"],
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"seller": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "operator_id"
|
|
||||||
},
|
|
||||||
"evidence_summary": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tenant_visibility": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"minItems": 1
|
|
||||||
},
|
|
||||||
"stats": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Conversion signals (FR-012), updated by telemetry, start zeroed.",
|
|
||||||
"properties": {
|
|
||||||
"installs": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 5
|
|
||||||
},
|
|
||||||
"proposals_shown": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"proposals_accepted": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["installs", "proposals_shown", "proposals_accepted"],
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["draft", "in_review", "published", "delisted"]
|
|
||||||
},
|
|
||||||
"install_ref": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^lst_[a-z0-9-]+-v\\d+\\.\\d+\\.\\d+$"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
"required": [
|
||||||
"schema_version",
|
"schema_version",
|
||||||
"listing_id",
|
"listing_id",
|
||||||
|
|
@ -121,9 +15,133 @@
|
||||||
"category",
|
"category",
|
||||||
"price",
|
"price",
|
||||||
"seller",
|
"seller",
|
||||||
|
"content_hash",
|
||||||
"tenant_visibility",
|
"tenant_visibility",
|
||||||
"status",
|
"status",
|
||||||
"install_ref"
|
"install_ref"
|
||||||
],
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": { "const": "m2.listing.v1" },
|
||||||
|
"listing_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^lst_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"solution_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sol_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"solution_version": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+\\.\\d+\\.\\d+([+-][0-9A-Za-z.-]+)?$"
|
||||||
|
},
|
||||||
|
"inventory_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["solution", "capability", "resource", "service"]
|
||||||
|
},
|
||||||
|
"name": { "type": "string", "minLength": 1 },
|
||||||
|
"summary": { "type": "string", "minLength": 1 },
|
||||||
|
"category": { "type": "string", "minLength": 1 },
|
||||||
|
"keywords": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"icon": { "type": "string" },
|
||||||
|
"price": { "$ref": "#/$defs/price" },
|
||||||
|
"seller": { "$ref": "#/$defs/seller" },
|
||||||
|
"evidence_summary": { "type": "string" },
|
||||||
|
"content_hash": { "$ref": "#/$defs/sha256" },
|
||||||
|
"tenant_visibility": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"description": "Allowed tenants. [\"*\"] means public. Other values must be canonical tenant ids from contracts/tenants.md."
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["installs", "proposals_shown", "proposals_accepted"],
|
||||||
|
"properties": {
|
||||||
|
"installs": { "type": "integer", "minimum": 0, "default": 0 },
|
||||||
|
"rating": { "type": "number", "minimum": 0, "maximum": 5 },
|
||||||
|
"proposals_shown": { "type": "integer", "minimum": 0, "default": 0 },
|
||||||
|
"proposals_accepted": { "type": "integer", "minimum": 0, "default": 0 }
|
||||||
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["draft", "in_review", "published", "delisted"]
|
||||||
|
},
|
||||||
|
"install_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^lst_[a-z0-9][a-z0-9-]*-v\\d+\\.\\d+\\.\\d+([+-][0-9A-Za-z.-]+)?$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$defs": {
|
||||||
|
"sha256": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["model", "amount", "currency"],
|
||||||
|
"properties": {
|
||||||
|
"model": { "const": "fixed" },
|
||||||
|
"amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"currency": { "const": "m2cr" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["model", "currency", "quote", "settlement"],
|
||||||
|
"properties": {
|
||||||
|
"model": { "const": "job" },
|
||||||
|
"currency": { "const": "m2cr" },
|
||||||
|
"quote": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["min_amount", "max_amount", "expires_after_hours", "requires_acceptance"],
|
||||||
|
"properties": {
|
||||||
|
"min_amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"max_amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"expires_after_hours": { "type": "integer", "minimum": 1 },
|
||||||
|
"requires_acceptance": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"settlement": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["ledger_reason", "escrow_required"],
|
||||||
|
"properties": {
|
||||||
|
"ledger_reason": { "const": "job" },
|
||||||
|
"escrow_required": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"seller": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["operator_id", "display_name"],
|
||||||
|
"properties": {
|
||||||
|
"operator_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^op_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"display_name": { "type": "string", "minLength": 1 },
|
||||||
|
"contact_ref": { "type": "string", "minLength": 1 },
|
||||||
|
"tenant_id": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,200 +1,9 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://git.machinemachine.ai/m2/market-registry/schemas/solution.schema.json",
|
"$id": "https://git.machinemachine.ai/m2/m2-market/schemas/solution.schema.json",
|
||||||
"title": "Solution",
|
"title": "m2.solution.v1",
|
||||||
"description": "The installable bundle manifest for the m2 marketplace. Frozen v1 contract (specs/001-market-first-wedge/data-model.md, research.md R3).",
|
"description": "Commercial Solution manifest. v1 is a marketplace superset of the fedlearn core fields: applicability, tenant_scope, evidence, content_hash, and scrub_status for tenant-derived work.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
|
||||||
"schema_version": {
|
|
||||||
"const": "m2.solution.v1"
|
|
||||||
},
|
|
||||||
"solution_id": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^sol_[a-z0-9-]+$"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"summary": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"intent": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"behavior": {
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"tools": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"runtime": {
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"memory_schema": {
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deployment": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "recipe.yaml ref + entrypoint + verify command",
|
|
||||||
"properties": {
|
|
||||||
"recipe_ref": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"entrypoint": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"verify_command": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["recipe_ref", "entrypoint", "verify_command"],
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"applicability": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "fedlearn-compatible applicability constraints",
|
|
||||||
"properties": {
|
|
||||||
"image_classes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"roles": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tool_requirements": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"tenant_scope": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1,
|
|
||||||
"description": "'m2-core' for shared/free, or a tenant id for tenant-scoped work"
|
|
||||||
},
|
|
||||||
"owner_initiated": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"scrub_status": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"double_scrubbed": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
|
||||||
"evidence": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 1,
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"source": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"excerpt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"machine": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"session": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tokens": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
},
|
|
||||||
"wall_time": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["source"],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"content_hash": {
|
|
||||||
"type": "string",
|
|
||||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"const": "m2cr"
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["fixed"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["amount", "currency", "model"],
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"seller": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1,
|
|
||||||
"description": "operator_id"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"terms": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"major_version_coverage": {
|
|
||||||
"const": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["terms", "major_version_coverage"],
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"revenue_split": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"platform_pct": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
"required": [
|
||||||
"schema_version",
|
"schema_version",
|
||||||
"solution_id",
|
"solution_id",
|
||||||
|
|
@ -210,30 +19,238 @@
|
||||||
"seller",
|
"seller",
|
||||||
"license"
|
"license"
|
||||||
],
|
],
|
||||||
"additionalProperties": false,
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"schema_version": { "const": "m2.solution.v1" },
|
||||||
|
"solution_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sol_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"name": { "type": "string", "minLength": 1 },
|
||||||
|
"summary": { "type": "string", "minLength": 1 },
|
||||||
|
"description": { "type": "string" },
|
||||||
|
"intent": { "type": "string", "minLength": 1 },
|
||||||
|
"behavior": { "type": "object", "additionalProperties": true },
|
||||||
|
"tools": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "object", "additionalProperties": true },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"runtime": { "type": "object", "additionalProperties": true },
|
||||||
|
"memory_schema": { "type": "object", "additionalProperties": true },
|
||||||
|
"permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/$defs/permission" },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"deployment": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["recipe_ref", "entrypoint", "verify_command"],
|
||||||
|
"properties": {
|
||||||
|
"recipe_ref": { "type": "string", "minLength": 1 },
|
||||||
|
"entrypoint": { "type": "string", "minLength": 1 },
|
||||||
|
"verify_command": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"applicability": { "$ref": "#/$defs/applicability" },
|
||||||
"tenant_scope": {
|
"tenant_scope": {
|
||||||
"const": "m2-core"
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical tenant id from contracts/tenants.md. Use m2-core for shared commons/evidence."
|
||||||
|
},
|
||||||
|
"owner_initiated": { "type": "boolean" },
|
||||||
|
"scrub_status": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"double_scrubbed": { "type": "boolean" },
|
||||||
|
"reviewers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": { "$ref": "#/$defs/evidence" }
|
||||||
|
},
|
||||||
|
"content_hash": { "$ref": "#/$defs/sha256" },
|
||||||
|
"price": { "$ref": "#/$defs/price" },
|
||||||
|
"seller": { "$ref": "#/$defs/seller" },
|
||||||
|
"license": { "$ref": "#/$defs/license" },
|
||||||
|
"revenue_split": { "$ref": "#/$defs/revenue_split" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": { "tenant_scope": { "const": "m2-core" } },
|
||||||
"required": ["tenant_scope"]
|
"required": ["tenant_scope"]
|
||||||
},
|
},
|
||||||
"else": {
|
"else": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"owner_initiated": {
|
"owner_initiated": { "const": true },
|
||||||
"const": true
|
|
||||||
},
|
|
||||||
"scrub_status": {
|
"scrub_status": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": ["double_scrubbed"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"double_scrubbed": {
|
"double_scrubbed": { "const": true }
|
||||||
"const": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"required": ["double_scrubbed"]
|
"additionalProperties": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["owner_initiated", "scrub_status"]
|
"required": ["owner_initiated", "scrub_status"]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$defs": {
|
||||||
|
"sha256": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||||
|
},
|
||||||
|
"applicability": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["image_classes", "roles", "tool_requirements"],
|
||||||
|
"properties": {
|
||||||
|
"image_classes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"tool_requirements": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["source"],
|
||||||
|
"properties": {
|
||||||
|
"source": { "type": "string", "minLength": 1 },
|
||||||
|
"excerpt": { "type": "string" },
|
||||||
|
"machine": { "type": "string" },
|
||||||
|
"session": { "type": "string" },
|
||||||
|
"tokens": { "type": "integer", "minimum": 0 },
|
||||||
|
"wall_time": { "type": "number", "minimum": 0 },
|
||||||
|
"content_hash": { "$ref": "#/$defs/sha256" }
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["model", "amount", "currency"],
|
||||||
|
"properties": {
|
||||||
|
"model": { "const": "fixed" },
|
||||||
|
"amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"currency": { "const": "m2cr" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["model", "currency", "quote", "settlement"],
|
||||||
|
"properties": {
|
||||||
|
"model": { "const": "job" },
|
||||||
|
"currency": { "const": "m2cr" },
|
||||||
|
"quote": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["min_amount", "max_amount", "expires_after_hours", "requires_acceptance"],
|
||||||
|
"properties": {
|
||||||
|
"min_amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"max_amount": { "type": "integer", "minimum": 1 },
|
||||||
|
"expires_after_hours": { "type": "integer", "minimum": 1 },
|
||||||
|
"requires_acceptance": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"settlement": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["ledger_reason", "escrow_required"],
|
||||||
|
"properties": {
|
||||||
|
"ledger_reason": { "const": "job" },
|
||||||
|
"escrow_required": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"seller": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["operator_id", "display_name"],
|
||||||
|
"properties": {
|
||||||
|
"operator_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^op_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"display_name": { "type": "string", "minLength": 1 },
|
||||||
|
"contact_ref": { "type": "string", "minLength": 1 },
|
||||||
|
"tenant_id": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["license_id", "terms", "scope", "major_version_coverage", "allows_internal_modification", "allows_redistribution", "support"],
|
||||||
|
"properties": {
|
||||||
|
"license_id": { "const": "lic_m2_market_standard_v1" },
|
||||||
|
"terms": { "const": "m2-market-standard-v1" },
|
||||||
|
"scope": { "const": "operator" },
|
||||||
|
"major_version_coverage": { "const": true },
|
||||||
|
"allows_internal_modification": { "const": true },
|
||||||
|
"allows_redistribution": { "const": false },
|
||||||
|
"support": { "const": "best_effort" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"revenue_split": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["platform_pct", "seller_pct"],
|
||||||
|
"properties": {
|
||||||
|
"platform_pct": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||||
|
"seller_pct": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||||
|
"contributors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["operator_id", "pct"],
|
||||||
|
"properties": {
|
||||||
|
"operator_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^op_[a-z0-9][a-z0-9-]*$"
|
||||||
|
},
|
||||||
|
"pct": { "type": "integer", "minimum": 1, "maximum": 100 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["permission_id", "kind", "access", "scope", "target"],
|
||||||
|
"properties": {
|
||||||
|
"permission_id": { "type": "string", "minLength": 1 },
|
||||||
|
"kind": { "type": "string", "minLength": 1 },
|
||||||
|
"access": { "type": "string", "minLength": 1 },
|
||||||
|
"scope": { "type": "string", "minLength": 1 },
|
||||||
|
"target": { "type": "string", "minLength": 1 },
|
||||||
|
"reason": { "type": "string" },
|
||||||
|
"secret_ref": { "type": "string" }
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core"
|
||||||
|
},
|
||||||
|
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
"tenant_visibility": ["*"],
|
"tenant_visibility": ["*"],
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core"
|
||||||
|
},
|
||||||
|
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
"status": "published",
|
"status": "published",
|
||||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
schemas/tests/fixtures/solution_bad_price.json
vendored
14
schemas/tests/fixtures/solution_bad_price.json
vendored
|
|
@ -26,9 +26,17 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core"
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
"license_id": "lic_m2_market_standard_v1",
|
||||||
"major_version_coverage": true
|
"terms": "m2-market-standard-v1",
|
||||||
|
"scope": "operator",
|
||||||
|
"major_version_coverage": true,
|
||||||
|
"allows_internal_modification": true,
|
||||||
|
"allows_redistribution": false,
|
||||||
|
"support": "best_effort"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,17 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core"
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
"license_id": "lic_m2_market_standard_v1",
|
||||||
"major_version_coverage": true
|
"terms": "m2-market-standard-v1",
|
||||||
|
"scope": "operator",
|
||||||
|
"major_version_coverage": true,
|
||||||
|
"allows_internal_modification": true,
|
||||||
|
"allows_redistribution": false,
|
||||||
|
"support": "best_effort"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,17 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core"
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
"license_id": "lic_m2_market_standard_v1",
|
||||||
"major_version_coverage": true
|
"terms": "m2-market-standard-v1",
|
||||||
|
"scope": "operator",
|
||||||
|
"major_version_coverage": true,
|
||||||
|
"allows_internal_modification": true,
|
||||||
|
"allows_redistribution": false,
|
||||||
|
"support": "best_effort"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
schemas/tests/fixtures/valid_listing.json
vendored
10
schemas/tests/fixtures/valid_listing.json
vendored
|
|
@ -8,14 +8,20 @@
|
||||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||||
"category": "reporting",
|
"category": "reporting",
|
||||||
"keywords": ["pdf", "report", "branded", "slides"],
|
"keywords": ["pdf", "report", "branded", "slides"],
|
||||||
"icon": "https://git.machinemachine.ai/m2/market-registry/assets/mm-pdf-report/icon.png",
|
"icon": "https://git.machinemachine.ai/m2/m2-market/assets/mm-pdf-report/icon.png",
|
||||||
"price": {
|
"price": {
|
||||||
"amount": 500,
|
"amount": 500,
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core",
|
||||||
|
"contact_ref": "forgejo:m2/m2-market",
|
||||||
|
"tenant_id": "m2"
|
||||||
|
},
|
||||||
"evidence_summary": "Generated branded PDF report for SDJS quarterly summary; operator confirmed styling matched brand deck.",
|
"evidence_summary": "Generated branded PDF report for SDJS quarterly summary; operator confirmed styling matched brand deck.",
|
||||||
|
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
"tenant_visibility": ["*"],
|
"tenant_visibility": ["*"],
|
||||||
"stats": {
|
"stats": {
|
||||||
"installs": 0,
|
"installs": 0,
|
||||||
|
|
|
||||||
27
schemas/tests/fixtures/valid_solution.json
vendored
27
schemas/tests/fixtures/valid_solution.json
vendored
|
|
@ -27,8 +27,12 @@
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"scope": "filesystem:write",
|
"permission_id": "fs-write-output",
|
||||||
"path": "/tmp/mm-pdf-output"
|
"kind": "filesystem",
|
||||||
|
"access": "write",
|
||||||
|
"scope": "agent",
|
||||||
|
"target": "/tmp/mm-pdf-output",
|
||||||
|
"reason": "write rendered report output"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"deployment": {
|
"deployment": {
|
||||||
|
|
@ -58,12 +62,23 @@
|
||||||
"currency": "m2cr",
|
"currency": "m2cr",
|
||||||
"model": "fixed"
|
"model": "fixed"
|
||||||
},
|
},
|
||||||
"seller": "sdjs-operator",
|
"seller": {
|
||||||
|
"operator_id": "op_m2core",
|
||||||
|
"display_name": "M2 Core",
|
||||||
|
"contact_ref": "forgejo:m2/m2-market",
|
||||||
|
"tenant_id": "m2"
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
"license_id": "lic_m2_market_standard_v1",
|
||||||
"major_version_coverage": true
|
"terms": "m2-market-standard-v1",
|
||||||
|
"scope": "operator",
|
||||||
|
"major_version_coverage": true,
|
||||||
|
"allows_internal_modification": true,
|
||||||
|
"allows_redistribution": false,
|
||||||
|
"support": "best_effort"
|
||||||
},
|
},
|
||||||
"revenue_split": {
|
"revenue_split": {
|
||||||
"platform_pct": 10
|
"platform_pct": 10,
|
||||||
|
"seller_pct": 90
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,16 @@ def errors(validator, instance):
|
||||||
return list(validator.iter_errors(instance))
|
return list(validator.iter_errors(instance))
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_errors(errs):
|
||||||
|
flat = []
|
||||||
|
stack = list(errs)
|
||||||
|
while stack:
|
||||||
|
err = stack.pop()
|
||||||
|
flat.append(err)
|
||||||
|
stack.extend(err.context)
|
||||||
|
return flat
|
||||||
|
|
||||||
|
|
||||||
# --- valid fixtures ---------------------------------------------------------
|
# --- valid fixtures ---------------------------------------------------------
|
||||||
|
|
||||||
def test_valid_solution_passes():
|
def test_valid_solution_passes():
|
||||||
|
|
@ -76,10 +86,11 @@ def test_solution_tenant_violation_fails_on_else_branch():
|
||||||
def test_solution_bad_price_fails_on_price_amount_minimum():
|
def test_solution_bad_price_fails_on_price_amount_minimum():
|
||||||
errs = errors(solution_validator, fixture("solution_bad_price.json"))
|
errs = errors(solution_validator, fixture("solution_bad_price.json"))
|
||||||
assert errs, "expected validation errors"
|
assert errs, "expected validation errors"
|
||||||
|
flat = flatten_errors(errs)
|
||||||
assert any(
|
assert any(
|
||||||
e.validator == "minimum" and list(e.path) == ["price", "amount"]
|
e.validator == "minimum" and list(e.path) in (["price", "amount"], ["amount"])
|
||||||
for e in errs
|
for e in flat
|
||||||
), [(e.validator, list(e.path), e.message) for e in errs]
|
), [(e.validator, list(e.path), e.message) for e in flat]
|
||||||
|
|
||||||
|
|
||||||
# --- invalid listing fixtures ------------------------------------------------
|
# --- invalid listing fixtures ------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue