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>
615 lines
25 KiB
Markdown
615 lines
25 KiB
Markdown
# 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.
|
|
|
|
### US4 — Open Store from Scout/Hermes deep link
|
|
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```toml
|
|
# ~/.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:
|
|
|
|
```http
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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`:
|
|
|
|
```ts
|
|
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`:
|
|
|
|
```json
|
|
{
|
|
"installs": {
|
|
"lst_competitor-scan": {
|
|
"version": "1.0.0",
|
|
"grant_id": "gr_...",
|
|
"ts": "2026-07-02T00:00:00Z",
|
|
"changeset_hash": "sha256:...",
|
|
"status": "applied"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.10 Deep-link protocol
|
|
|
|
Register:
|
|
|
|
```json
|
|
{
|
|
"name": "M2 Store",
|
|
"schemes": ["m2store"]
|
|
}
|
|
```
|
|
|
|
Supported URLs:
|
|
|
|
```text
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
~/.local/share/m2-store/events/YYYY-MM-DD.jsonl
|
|
```
|
|
|
|
Event:
|
|
|
|
```json
|
|
{
|
|
"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**.
|
|
|
|
### Deep-link ID
|
|
|
|
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.
|