# Data Model — 001-market-first-wedge (Phase 1) Entities from spec.md §Key Entities, concretized. Schemas frozen in `schemas/` are the normative source; this file is the narrative view. ## Solution (`schemas/solution.schema.json`, v1 frozen) The installable bundle. Manifest-core fields kept field-compatible with the fedlearn submission/manifest design (coordination item R3). | Field | Type | Notes | |---|---|---| | `schema_version` | const `m2.solution.v1` | frozen | | `solution_id` | string `sol_` | unique across registry | | `name`, `summary`, `description` | string | presentation | | `intent` | string | what outcome this delivers | | `behavior` | object | prompts / skills / playbooks refs (paths in payload) | | `tools` | array | MCP/API/connector requirements | | `runtime` | object | required surfaces: hermes / desktop / browser / openclaw | | `memory_schema` | object? | partitions/keys the solution reads/writes | | `permissions` | array | declarative permission requests (shown as diff pre-install) | | `deployment` | object | `recipe.yaml` ref + entrypoint + verify command | | `applicability` | object | `image_classes[]`, `roles[]`, `tool_requirements[]` (fedlearn-compatible) | | `tenant_scope` | string | `m2-core` (shared) or tenant id; drives firewall checks | | `evidence` | array | provenance refs: `{source, excerpt?, machine?, session?, tokens?, wall_time?}` — ≥1 REQUIRED | | `content_hash` | string sha256 | bundle integrity | | `price` | object | `{amount: int>0, currency: "m2cr", model: "fixed"}` — `model` enum extensible (job/metered later, headroom per operator steer) | | `seller` | string operator_id | | | `license` | object | `{terms: string, major_version_coverage: true}` | | `revenue_split` | object | `{platform_pct: number}` (default from ledger config) | **Validation**: evidence non-empty; price.amount ≥ 1; tenant_scope=tenant-id requires `owner_initiated: true` + `scrub_status.double_scrubbed: true`; content_hash matches bundle. ## Listing (`schemas/listing.schema.json`, v1 frozen) Catalog-facing record; truth in `m2/market-registry`, derived copy in `market:catalog`. | Field | Type | Notes | |---|---|---| | `schema_version` | const `m2.listing.v1` | | | `listing_id` | string `lst_` | | | `solution_id` + `solution_version` | refs | what's being sold | | `inventory_type` | enum `solution|capability|resource|service` | only `solution` transacted in v1 | | `name`, `summary`, `category`, `keywords[]`, `icon?` | | cargstore-catalog compatible | | `price` | object | denormalized from solution at listing time | | `seller` | operator_id | | | `evidence_summary` | string | rendered from solution evidence | | `tenant_visibility` | array | tenant ids or `["*"]`; catalog queries filter on this | | `stats` | object | `{installs: int, rating: number?, proposals_shown: int, proposals_accepted: int}` — conversion signals (FR-012), updated by telemetry, start zeroed | | `status` | enum `draft|in_review|published|delisted` | delisted keeps licenses valid | | `install_ref` | string | registry release tag `-v` | **State transitions**: `draft → in_review` (PR opened) `→ published` (PR merged, no veto label) `→ delisted` (delist commit; grants unaffected). Veto label on PR → back to `draft`. ## Transaction (m2-ledger, SQLite `tx` table — append-only) | Column | Type | Notes | |---|---|---| | `id` | integer PK autoincrement | | | `ts` | text ISO-8601 UTC | set by service | | `from_id` | text | operator_id or `platform` or `mint` | | `to_id` | text | operator_id or `platform` | | `amount` | integer > 0 | whole credits in v1 | | `reason` | enum `install|payout|grant|route|earn|refund` | `refund` added for FR-005 compensation | | `ref` | text | listing_id / grant note / install id | **Invariants**: rows never UPDATEd or DELETEd; `balance(op) = Σ to_id=op − Σ from_id=op`; service refuses tx that would take a non-`mint` balance negative. An install produces 2–3 rows atomically (one SQLite transaction): buyer→seller (net), buyer→platform (cut), and on apply-failure a compensating seller/platform→buyer `refund` pair referencing the same install id. ## LicenseGrant (m2-ledger, `grant` table) | Column | Type | Notes | |---|---|---| | `grant_id` | text `gr_` | | | `operator_id` | text | buyer | | `listing_id` | text | | | `solution_version_major` | int | license covers this major (edge case: updates free within major) | | `tx_id` | int FK → tx | the debit that paid for it; NOT NULL (no grant without payment — SC-003) | | `ts` | text | | ## Wallet (derived — no table) Balance is always computed from `tx`. A `balance_cache` may exist for performance but is rebuildable and never authoritative (constitution V). Daily snapshot = full balances dump committed to `m2/market-registry:audit/YYYY-MM-DD.json`. ## Operator Provisioned mapping, not a service: `operator_id` from fleet.json tenancy; CLI config (`~/.m2-market/config.toml`) carries `operator_id` + API keys per machine. Ledger admin endpoint issues starter grants against any operator_id. ## Proposal (Scout v0 — local JSONL + telemetry event) | Field | Notes | |---|---| | `proposal_id`, `ts`, `desktop`, `listing_id`, `confidence` | | | `outcome` | `shown|dismissed|accepted|suppressed_rate|suppressed_optout` | Outcomes are appended locally and shipped (summarized, on-box) to memory as listing evidence; `stats.proposals_*` on the listing updated via the indexer telemetry path. ## InstallState (per machine, `~/.m2-market/state.json`) Mirrors fedlearn state.json design: `{installs: {listing_id: {version, grant_id, ts, changeset_hash, status: applied|failed|rolled_back}}}` — the idempotency + re-apply guard for the local adapter, and the file m2core_sync will merge into when fedlearn lands.