Phase 0: apply SPEC-INDEX contract fixes #1

Open
m2 wants to merge 1 commit from phase0/contract-fixes into master
17 changed files with 897 additions and 344 deletions

126
contracts/cli.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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: ["*"]`.

View 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.

View file

@ -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"
], ],
"additionalProperties": false "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
},
"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
}
}
} }

View file

@ -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, "properties": {
"if": { "schema_version": { "const": "m2.solution.v1" },
"properties": { "solution_id": {
"tenant_scope": { "type": "string",
"const": "m2-core" "pattern": "^sol_[a-z0-9][a-z0-9-]*$"
}
}, },
"required": ["tenant_scope"] "name": { "type": "string", "minLength": 1 },
}, "summary": { "type": "string", "minLength": 1 },
"else": { "description": { "type": "string" },
"properties": { "intent": { "type": "string", "minLength": 1 },
"owner_initiated": { "behavior": { "type": "object", "additionalProperties": true },
"const": 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 }
}, },
"scrub_status": { "additionalProperties": false
"type": "object", },
"applicability": { "$ref": "#/$defs/applicability" },
"tenant_scope": {
"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"]
},
"else": {
"properties": { "properties": {
"double_scrubbed": { "owner_initiated": { "const": true },
"const": true "scrub_status": {
"type": "object",
"required": ["double_scrubbed"],
"properties": {
"double_scrubbed": { "const": true }
},
"additionalProperties": true
} }
}, },
"required": ["double_scrubbed"] "required": ["owner_initiated", "scrub_status"]
} }
}
],
"$defs": {
"sha256": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
}, },
"required": ["owner_initiated", "scrub_status"] "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
}
} }
} }

View file

@ -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"

View file

@ -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"
} }

View file

@ -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"
} }
} }

View file

@ -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"
} }
} }

View file

@ -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"
} }
} }

View file

@ -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,

View file

@ -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
} }
} }

View file

@ -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 ------------------------------------------------