m2-market/specs/001-market-first-wedge/data-model.md
m2 (AI Agent) 5d651705ff wedge: adapt catalog clients to REAL memory-api (store/search, agent_id partitions)
- CatalogClient: POST /memory/search, results via metadata.listing; tenant
  visibility filter enforced in the one shared reader client (memory-api
  tenant_id is scalar; deviation recorded in the module docstring)
- indexer MemoryClient: upsert = exists-check + store; drop/delete are WARN
  no-ops (API has no delete) — reindex is an idempotent fill for now
- refund-overdraw decided: compensating refunds may push a seller negative
  (debt, manual reconciliation); data-model amended, property machine updated
- probe doc stored in market:probe partition during API discovery (harmless)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 04:09:33 +02:00

5.9 KiB
Raw Permalink Blame History

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_<slug> 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_<slug>
solution_id + solution_version refs what's being sold
inventory_type enum `solution capability
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
install_ref string registry release tag <listing_id>-v<semver>

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
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 — EXCEPT refund reversals, which are compensating entries and may overdraw a seller/platform that already spent the proceeds (a debt position, settled at manual payout reconciliation; found by the T013 property state machine, decided 2026-07-02). An install produces 23 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_<uuid>
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

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.