m2-market/specs/S3-storefront-m2store.md
m2 (AI Agent) 8a8db9f2cc specs: m2-market spec discovery — 6 subsystem specs + synthesis index
herdr workforce output (6 codex angle-specialists -> claude synthesis):
S1 ledger/economy, S2 catalog/registry, S3 storefront (cargstore->M2 Store),
S4 Solution Scout (standalone watcher recommended), S5 packaging + 3 seed
Solutions (mm-pdf 25cr, agent-scaffold 35cr, competitor-scan 60cr),
S6 proposal engine + evidence loop.

SPEC-INDEX is the tie-break: 23-row cross-cutting contract table (16 mismatches
resolved, e.g. /v1 ledger paths win, m2store://listing/<id> deep link, one
m2.market.telemetry.v1 envelope, op_<slug> operator ids, tenant ids from m2-gpt),
8 deduped clarifications with defaults, 4-phase build order where the paid-install
wedge does NOT block on fedlearn (local ApplyAdapter until rails land), risk register.

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

25 KiB

S3 — Storefront: cargstore -> M2 Store

1. Scope & non-goals

MVP scope

Revive github.com/machine-machine/cargstore as the in-desktop M2 Store. Keep the Electron + React app and add a Solution install backend beside the existing Flatpak backend.

Grounding inspected 2026-07-02:

  • cargstore main has Electron files electron/main.ts, electron/preload.ts, electron/flatpak.ts, electron/clawdbot-client.ts; React pages/hooks under src/; local JSON catalog catalog/apps.json; Dockerized web/ seed.
  • Current app exposes IPC namespace window.cargstore.flatpak.*, window.cargstore.catalog.get, window.cargstore.app.*, and registers flatpak protocol only.
  • Current catalog fields map closely to listing fields: id/name/summary/description/category/icon/featured/keywords plus flatpakRef|bundleUrl.
  • Existing market repo freezes schemas/listing.schema.json, schemas/solution.schema.json, contracts/cli.md, contracts/catalog-index.md, contracts/apply-adapter.md, and contracts/ledger-api.md.
  • Live host has m2-gpt healthy at https://gpt.machinemachine.ai/health, Coolify network, memory-api, Forgejo, and m2o desktops. chris-m2o runs primus; gunnar-m2o is an agent-latest canary container currently present from Coolify.

MVP delivers:

  1. Rebrand product to M2 Store while preserving cargstore architecture.
  2. Catalog source switch from bundled catalog/apps.json to market:catalog through the m2-market CLI, with local cache fallback.
  3. A new Solution backend that shells m2-market show|install|wallet --json and emits progress/status to the renderer.
  4. Solution detail page showing evidence, permissions diff, price, seller, install status, and install button.
  5. Deep-link protocol m2store://solution/<listing_id> used by Scout/Hermes/toasts to open a listing detail page.
  6. Identity/bootstrap path using m2-gpt-issued agent credentials and runtime-injected marketplace service keys stored in ~/.m2-market/config.toml mode 0600.
  7. Canary desktop deployment path for chris-m2o and one non-client agent-latest desktop; broader fleet uses runtime-sync once proven.

Later

  • Hosted browser marketplace from cargstore web/.
  • Service/resource/capability purchase flows beyond read-only discovery.
  • Seller/admin publishing UI; builder publishing remains m2-market publish.
  • Full self-update through store UI. Keep cargstore self-update disabled unless wired to Forgejo/Coolify release trust.
  • Flatpak removal. Existing Flatpak backend may remain as an "Apps" inventory surface, but Solution install is separate and primary.

Non-goals

  • Ledger internals, pricing policy, wallet accounting, refunds: S1 owns ledger.
  • Listing schema/indexer truth model: S2 owns catalog/registry.
  • Scout matching host and proposal policy: S4 owns Scout. S3 only specifies Store deep-link and proposal event acceptance.
  • Packaging images and runtime-sync implementation: S5 owns packaging. S3 consumes install artifacts.
  • Hermes proposal skill logic: S6 owns proposal engine. S3 exposes open/show/install surfaces.

2. User stories + acceptance criteria

US1 — Browse published Solutions in the desktop Store

Given an operator is inside an m2o desktop with ~/.m2-market/config.toml present, when M2 Store opens, then it loads tenant-visible listings from market:catalog, renders featured/category/search views, and never shows listings outside tenant_visibility.

Acceptance:

  • Store calls m2-market search "" --type solution --limit 50 --json through main-process IPC.
  • Response items validate as m2.listing.v1 payloads.
  • If the catalog backend is unavailable, Store renders the last successful cache from ~/.cache/m2-store/catalog.json with an offline marker and disables install if listing detail cannot be refreshed.
  • No bundled seed listing is treated as authoritative in production.

US2 — Inspect evidence, permissions, price, and install impact

Given a user opens a listing, when the Solution detail page loads, then it shows price in M2 credits, seller, evidence summary, detailed evidence refs, permissions diff, applicability, install state, and rollback note before installation.

Acceptance:

  • Store calls m2-market show <listing_id> --json.
  • Detail payload includes listing + solution excerpt + permissions_diff.
  • Install button text includes the exact credit amount.
  • Install button is disabled when applicability.image_classes excludes the current image class, when inventory_type != "solution", or when wallet balance is insufficient.
  • Permission diff is rendered from structured fields, not free-form Markdown.

US3 — Install a Solution through the standard marketplace path

Given the operator confirms install, when Store invokes install, then money moves before apply, grant is recorded, apply is idempotent, and failures refund through CLI semantics.

Acceptance:

  • Store calls m2-market install <listing_id> --yes --json.
  • Store never calls m2-ledger directly for install.
  • A CLI exit code maps to UI state:
    • 0: installed or no-op.
    • 2: listing not found.
    • 3: insufficient funds, no apply attempted.
    • 4: apply failed and refund attempted.
    • 5: validation/firewall rejection.
    • 6: m2-core-sync rails not landed; show remediation and leave state unchanged.
  • Re-running install on an already-applied listing renders "Installed" and does not create duplicate tx rows.

Given Scout proposes a Solution, when the operator clicks the toast/link, then M2 Store opens directly to the Solution detail page and records that the proposal was accepted only after the user reaches the page or clicks Install, per S4 policy.

Acceptance:

  • Desktop registers m2store://solution/<listing_id>.
  • m2store://solution/lst_competitor-scan?source=scout&proposal_id=prop_... opens detail route /solution/lst_competitor-scan.
  • Unknown listing IDs render a not-found page and do not trigger install.
  • Deep links cannot pass shell arguments or override backend URLs.

US5 — Operate without baking secrets into images

Given a desktop is provisioned, when M2 Store starts, then it obtains identity from runtime config generated from m2-gpt-issued credentials and never ships keys in the Electron bundle, image, repo, or catalog.

Acceptance:

  • Store reads only ~/.m2-market/config.toml and redacted env metadata for identity.
  • Config file mode must be 0600; Store refuses install and catalog writes if permissions are broader.
  • Required config keys: operator_id, tenant, ledger_url, ledger_api_key, memory_api_url, memory_api_key, forgejo_url, forgejo_token, apply_adapter.
  • Store logs redact all *_key, *_token, and bearer-like strings.

3. Interfaces & data contracts

3.1 Repository and branch

  • Upstream repo: github.com/machine-machine/cargstore.
  • Working branch for this revival: m2-store.
  • Product metadata:
    • package.json:name: may remain cargstore internally.
    • build.productName: M2 Store.
    • build.appId: ai.machinemachine.m2store.
    • protocols: add m2store; keep flatpak only if Flatpak surface remains.

3.2 Main-process modules

Keep cargstore modules and add:

electron/
  flatpak.ts              # existing, unchanged except renamed UI surface if desired
  solution.ts             # new: shells m2-market CLI
  market-config.ts        # new: reads/validates ~/.m2-market/config.toml, redacts logs
  catalog-client.ts       # new: CLI-backed market:catalog read + cache
  deep-link.ts            # new: protocol parse/route handoff
  m2-agent-client.ts      # renamed/reworked clawdbot-client.ts, still ws://localhost:18789

3.3 Renderer model

Replace CatalogApp with StoreItem while keeping field compatibility for cards:

type InventoryType = "solution" | "capability" | "resource" | "service" | "flatpak";

interface StoreItem {
  id: string;                 // listing_id for market, flatpak app id for flatpak
  backend: "solution" | "flatpak";
  inventory_type: InventoryType;
  name: string;
  summary: string;
  description?: string;
  category: string;
  icon?: string;
  featured?: boolean;
  keywords: string[];
  price?: { amount: number; currency: "m2cr"; model: string };
  seller?: string;
  stats?: { installs: number; rating?: number; proposals_shown: number; proposals_accepted: number };
  install_ref?: string;
}

3.4 Listing/detail payload

IPC solution:show returns:

{
  "listing": {
    "schema_version": "m2.listing.v1",
    "listing_id": "lst_competitor-scan",
    "solution_id": "sol_competitor-scan",
    "solution_version": "1.0.0",
    "inventory_type": "solution",
    "name": "Competitor Scan",
    "summary": "Research report with sources and recommendations",
    "category": "research",
    "keywords": ["research", "competitor", "report"],
    "icon": "solution-research",
    "price": { "amount": 40, "currency": "m2cr", "model": "fixed" },
    "seller": "op_sdjs",
    "evidence_summary": "3 successful report runs, median 42 min",
    "tenant_visibility": ["*"],
    "stats": { "installs": 0, "proposals_shown": 0, "proposals_accepted": 0 },
    "status": "published",
    "install_ref": "lst_competitor-scan-v1.0.0"
  },
  "solution": {
    "schema_version": "m2.solution.v1",
    "permissions": [
      { "kind": "memory", "partition": "market:evidence", "access": "write" },
      { "kind": "filesystem", "path": "~/.local/share/m2-solutions/competitor-scan", "access": "write" }
    ],
    "deployment": {
      "recipe_ref": "recipe.yaml",
      "entrypoint": "hermes skill competitor-scan",
      "verify_command": "m2-solution verify competitor-scan"
    },
    "applicability": {
      "image_classes": ["primus", "agent-latest"],
      "roles": ["any"],
      "tool_requirements": ["m2-market-cli"]
    },
    "evidence": [
      { "source": "forgejo:m2/market-registry/.../evidence.json", "machine": "chris-m2o", "tokens": 120000, "wall_time": 2520 }
    ],
    "license": { "terms": "M2 internal commercial license", "major_version_coverage": true }
  },
  "wallet": { "operator_id": "op_chris", "balance": 100 },
  "install_state": { "status": "not_installed" },
  "permissions_diff": [
    { "kind": "filesystem", "target": "~/.local/share/m2-solutions/competitor-scan", "current": "absent", "requested": "write", "risk": "low" }
  ]
}

The detail page must not fetch Forgejo raw evidence directly from the renderer. The main process/CLI owns network I/O and returns sanitized JSON.

3.5 Electron preload API

Expose a new namespace while keeping old Flatpak namespace for compatibility:

contextBridge.exposeInMainWorld("m2store", {
  window: { minimize, maximize, close },
  catalog: {
    search: (query: string, opts?: { type?: string; limit?: number }) => ipcRenderer.invoke("catalog:search", query, opts),
    getFeatured: () => ipcRenderer.invoke("catalog:featured"),
    getCached: () => ipcRenderer.invoke("catalog:cached")
  },
  solution: {
    show: (listingId: string) => ipcRenderer.invoke("solution:show", listingId),
    install: (listingId: string) => ipcRenderer.invoke("solution:install", listingId),
    onProgress: (cb) => ipcRenderer.on("solution:progress", (_e, data) => cb(data)),
    openEntrypoint: (listingId: string) => ipcRenderer.invoke("solution:open-entrypoint", listingId)
  },
  wallet: {
    get: () => ipcRenderer.invoke("wallet:get")
  },
  app: {
    getVersion,
    getIdentity: () => ipcRenderer.invoke("app:get-identity")
  }
});

IPC contracts:

catalog:search(query, {type, limit}) -> {items: StoreItem[], source: "live"|"cache", as_of}
catalog:featured() -> same, query="" limit=50, featured derived from listing stats/category policy
solution:show(listing_id) -> detail payload above
solution:install(listing_id) -> InstallResult
wallet:get() -> {operator_id,balance,as_of}
app:get-identity() -> {operator_id, tenant, desktop, image_class, apply_adapter, config_path}

InstallResult:

{
  "ok": true,
  "listing_id": "lst_competitor-scan",
  "status": "applied",
  "grant_id": "gr_...",
  "entrypoint": "hermes skill competitor-scan",
  "state_path": "~/.m2-market/state.json"
}

3.6 CLI calls

Store backend shells the CLI, never ad hoc HTTP for installation:

m2-market search "$QUERY" --type solution --limit 50 --json
m2-market show "$LISTING_ID" --json
m2-market install "$LISTING_ID" --yes --json
m2-market wallet --json

Execution requirements:

  • Use spawnFile/argument array, not shell interpolation.
  • Hard allowlist command path from config: default m2-market found on PATH; optional absolute override M2_MARKET_CLI=/usr/local/bin/m2-market.
  • Timeout: search/show/wallet 15s, install no hard short timeout but stream heartbeat; UI can cancel only before CLI begins apply.
  • Parse JSON from stdout; stderr is redacted and logged to ~/.local/state/m2-store/logs/main.log.

3.7 Config and identity

MVP config remains the existing CLI config:

# ~/.m2-market/config.toml
operator_id = "op_chris"
tenant = "machine-machine"
ledger_url = "http://m2-ledger:8000"
ledger_api_key = "svc_xxx"
memory_api_url = "https://memory.machinemachine.ai"
memory_api_key = "mem_xxx"
forgejo_url = "https://git.machinemachine.ai"
forgejo_token = "frg_xxx"
apply_adapter = "local"

Identity issuance contract with m2-gpt:

  • m2-gpt remains the authority for tenant/agent identity and budget substrate.
  • Existing pattern: scripts/add-agent.sh <tenant_id> <agent_id> and scripts/mint-agent-key.sh <agent_id> create a tenant-scoped agent and a one-time sk-m2-... bearer, hashed at rest.
  • Add market bootstrap as an m2-gpt-admin operation, not a Store operation:
POST /admin/v1/tenants/{tenant_id}/agents/{agent_id}/market-credentials
Authorization: Bearer <operator-or-tenant-admin-token>
Body: {
  "operator_id": "op_chris",
  "desktop": "chris-m2o",
  "scopes": ["catalog:read", "install:write", "ledger:service", "forgejo:read-release"],
  "ttl_days": 90
}
200 {
  "operator_id": "op_chris",
  "tenant": "machine-machine",
  "ledger_url": "http://m2-ledger:8000",
  "ledger_api_key": "svc_...",
  "memory_api_url": "https://memory.machinemachine.ai",
  "memory_api_key": "mem_...",
  "forgejo_url": "https://git.machinemachine.ai",
  "forgejo_token": "frg_...",
  "expires_at": "2026-09-30T00:00:00Z"
}

The provisioner or a one-shot desktop bootstrap writes this response to ~/.m2-market/config.toml with mode 0600. Store only reads it. It does not mint or refresh admin credentials itself in MVP.

3.8 Catalog source switch

Current cargstore:

  • catalog:get reads bundled catalog/apps.json.
  • Renderer does local string matching in useCatalog.

M2 Store:

  • catalog-client.ts calls m2-market search.
  • StoreItem.id = listing.listing_id.
  • Cache path: ~/.cache/m2-store/catalog.json.
  • Cache shape:
{
  "schema_version": "m2.store.catalog-cache.v1",
  "as_of": "2026-07-02T00:00:00Z",
  "tenant": "machine-machine",
  "items": []
}

The bundled catalog/apps.json is permitted only as dev fallback when NODE_ENV=development.

3.9 Solution backend beside Flatpak

Current FlatpakManager persists Flatpak data under FLATPAK_USER_DIR || /clawdbot_home/flatpak. m2o primus docs say user installs persist under /agent_home/flatpak and home is /agent_home/home/developer.

Add SolutionManager:

class SolutionManager {
  show(listingId: string): Promise<SolutionDetail>;
  install(listingId: string, onProgress?: (event: SolutionProgress) => void): Promise<InstallResult>;
  listInstalled(): Promise<InstalledSolution[]>;
  openEntrypoint(listingId: string): Promise<void>;
}

Installed state comes from ~/.m2-market/state.json:

{
  "installs": {
    "lst_competitor-scan": {
      "version": "1.0.0",
      "grant_id": "gr_...",
      "ts": "2026-07-02T00:00:00Z",
      "changeset_hash": "sha256:...",
      "status": "applied"
    }
  }
}

Register:

{
  "name": "M2 Store",
  "schemes": ["m2store"]
}

Supported URLs:

m2store://solution/<listing_id>
m2store://solution/<listing_id>?source=scout&proposal_id=<proposal_id>
m2store://search?q=<urlencoded query>

Rules:

  • listing_id must match ^lst_[a-z0-9-]+$.
  • Query params allowed: source, proposal_id, q. Ignore all others.
  • No file:, http:, shell, adapter, or backend override params.
  • If app already running, focus existing window and navigate via renderer event deeplink:navigate.
  • Scout/Hermes use m2store://solution/<id> as the stable contract. Do not use m2store://solution/<solution_id> because listing controls visibility, price, and version.

3.11 Agent WebSocket integration

Current ClawdbotClient connects to ws://localhost:18789 and registers app-store tools. Keep transport but rename semantics:

{
  "type": "register_tools",
  "tools": [
    { "name": "m2_store_search", "description": "Search M2 Store Solutions", "parameters": { "query": "string" } },
    { "name": "m2_store_show", "description": "Open a Solution detail page", "parameters": { "listing_id": "string" } },
    { "name": "m2_store_install", "description": "Request human-confirmed install flow for a Solution", "parameters": { "listing_id": "string" } },
    { "name": "m2_store_wallet", "description": "Show wallet balance", "parameters": {} }
  ]
}

m2_store_install must navigate to detail and require human button click. It must not silently install on tool call.

3.12 Telemetry write-back

Store writes only summarized events through CLI or a future m2-market telemetry verb. Until that verb exists, append local JSONL:

~/.local/share/m2-store/events/YYYY-MM-DD.jsonl

Event:

{
  "schema_version": "m2.store.event.v1",
  "ts": "2026-07-02T00:00:00Z",
  "desktop": "chris-m2o",
  "operator_id": "op_chris",
  "tenant": "machine-machine",
  "event": "detail_opened|install_clicked|install_succeeded|install_failed",
  "listing_id": "lst_competitor-scan",
  "source": "browse|search|scout|hermes|deeplink",
  "proposal_id": "prop_optional",
  "error_code": null
}

Batch ship target, owned by S2/S4 path: memory partition market:evidence.

4. Integration contract with other subsystems

S1 Ledger

Touchpoints:

  • Store calls ledger only through m2-market install|wallet.
  • m2-market install performs POST /tx/install, grant, apply, verify, and refund on apply failure.
  • Store displays 402 insufficient_funds from CLI exit 3 and wallet balance from m2-market wallet.

S3 requires S1 to keep /tx/install idempotent by ref and GET /balance/{operator_id} stable.

S2 Catalog/registry

Touchpoints:

  • Store consumes m2.listing.v1 and m2.solution.v1.
  • Store reads semantic catalog via m2-market search backed by market:catalog.
  • Store detail reads the registry-backed full Solution via m2-market show.
  • Store expects tenant_visibility filtering to happen before results reach renderer.

S3 requires S2 to expose a stable m2-market show --json payload including permissions_diff.

S4 Scout

Touchpoints:

  • Scout opens m2store://solution/<listing_id>?source=scout&proposal_id=<id>.
  • Store emits local events with source=scout and proposal_id.
  • Store never performs install from Scout without human detail-page confirmation.

S5 Packaging/deployment

Touchpoints:

  • Store package can be deployed by bake or runtime-sync, but Solutions always install through m2-market and the apply adapter.
  • MVP canary install path:
    • install M2 Store artifact into /opt/m2-store or ~/.local/share/m2-store;
    • create desktop entry/protocol handler in the user home volume;
    • ensure m2-market CLI and config exist;
    • canary chris-m2o first, then non-client agent-latest.
  • Fleet-wide deployment waits for S5 image/runtime policy; local registry public route remains broken and must not be assumed.

S6 Hermes proposal engine

Touchpoints:

  • Hermes can call m2_store_search/show tools via existing local gateway WebSocket if present.
  • Hermes proposals should link to m2store://solution/<listing_id> or invoke m2_store_show.
  • Store does not own build-vs-install reasoning.

m2-gpt identity

Touchpoints:

  • m2-gpt continues tenant/agent identity and budget/metering authority.
  • Provisioner/admin mints m2-gpt agent key and market service credentials.
  • Store consumes the generated ~/.m2-market/config.toml.

memory-api

Touchpoints:

  • Store reads through CLI-backed market:catalog.
  • Store event summaries eventually ship to market:evidence.
  • Store does not write raw client data or raw session content.

5. Options compared

Catalog access

Option A: Store talks directly to memory-api.

  • Pros: fewer subprocesses, more interactive search.
  • Cons: duplicates auth, filtering, and payload logic in Electron; larger secret surface.

Option B: Store shells m2-market search/show --json.

  • Pros: reuses frozen CLI contracts, tenant filtering, registry fetch, future adapter changes; lowest blast radius.
  • Cons: subprocess/error parsing and CLI dependency.

Recommendation: B for MVP. Direct HTTP can be added later after CLI contracts harden.

Install backend

Option A: Replace Flatpak with Solution-only.

  • Pros: simpler product story.
  • Cons: throws away working app install feature and cargstore code.

Option B: Add SolutionManager beside FlatpakManager.

  • Pros: preserves existing asset, permits Apps and Solutions tabs, isolates paid install path.
  • Cons: renderer model needs backend discriminator.

Recommendation: B. Make Solutions default surface.

Store deployment

Option A: Bake M2 Store into primus.

  • Pros: available on every new desktop; protocol registration predictable.
  • Cons: slower iteration; agent-latest remains mixed; local registry route is broken.

Option B: Runtime-sync/install into agent-home on canaries.

  • Pros: respects mixed fleet and fedlearn universal path; reversible; canary-first.
  • Cons: protocol/desktop entry must be installed per desktop until image bake.

Recommendation: B for MVP canaries, then bake once S5 certifies it.

Identity storage

Option A: Store calls m2-gpt to mint/refresh credentials.

  • Pros: self-service.
  • Cons: Store would need high-privilege bootstrap token.

Option B: Admin/provisioner calls m2-gpt; Store reads scoped runtime config.

  • Pros: no admin secret in desktop app; matches M2_GPT_API_KEY injection pattern.
  • Cons: requires bootstrap step.

Recommendation: B.

Option A: m2store://solution/<solution_id>.

  • Problem: solution ID omits price, visibility, and version.

Option B: m2store://solution/<listing_id>.

  • Pros: listing is the catalog-facing, tenant-filtered, priced unit.

Recommendation: B.

6. Risks/edge cases

  • Tenant leakage: renderer must never bypass CLI tenant filters; detail fetch must 404 for invisible listings.
  • Secrets: ~/.m2-market/config.toml contains multiple live keys; require 0600, redact logs, and never expose full config through preload.
  • Shell injection: all CLI calls must use argument arrays and validate listing IDs before spawning.
  • Duplicate installs: rely on CLI/ledger idempotency and local state; Store button must debounce while install is running.
  • Partial failure: install may debit then fail apply; Store must surface CLI exit 4 and refund status exactly.
  • Offline catalog: cache is for browsing only; install requires fresh show unless CLI can verify cache freshness.
  • Protocol abuse: deep links must only navigate. No params can trigger installation.
  • Mixed fleet: agent-latest may lack Hermes/WebSocket or protocol handler support; Store must still work through direct UI/CLI.
  • Registry route: do not depend on public local registry push/pull for Store deployment.
  • Self-update trust: cargstore self-update currently downloads GitHub releases and extracts into /opt/cargstore; disable or rework before production to avoid unreviewed mutation path.
  • Flatpak persistence path mismatch: cargstore default /clawdbot_home/flatpak differs from m2o primus /agent_home/flatpak; set FLATPAK_USER_DIR=/agent_home/flatpak if Flatpak surface remains.
  • Pricing models: current solution.schema.json only allows fixed price even brief mentions job-based pricing. Store UI must render unknown future price.model read-only until S1/S2 extend schemas.

7. [NEEDS CLARIFICATION]

  1. [NEEDS CLARIFICATION] Exact m2-gpt endpoint for issuing marketplace service credentials does not exist in inspected code. Spec defines the required contract; implementation owner must decide whether it lands in m2-gpt admin API or a bootstrap script first.
  2. [NEEDS CLARIFICATION] m2-market show --json must include permissions_diff, but current contracts mention it narratively, not as a frozen JSON schema. S2/CLI should freeze the detail payload before Store implementation.
  3. [NEEDS CLARIFICATION] Whether Flatpak apps remain visible in M2 Store MVP or are hidden behind a secondary "Apps" tab for canaries. Backend can support either; product default should be set before UI polish.