# Écorce surface — contracts & architecture reference (v1.0.0, demo)

Status: DEMO. All catalogue items, prices, and network pools are INVENTED illustrative data ("données d'exemple — inventées"). No order or pool participation ever reaches a real partner. Partners are pilot targets ("partenaires pilotes visés").

## 1. Core workflow: draft → confirm → place
| Step | Actor | Mechanism | Demo status |
|---|---|---|---|
| draft | agent | `invoke_manifest("ecorce:order-draft-engine@v1")` / `create_order_draft` / `join_network_opportunity` | works |
| confirm | human | confirmation button at `confirm_url` (anonymous: /essayer; member: /compte, passkey) | works |
| place | system, after member confirmation | written to the DEMO SINK + audit event `order.placed` | works (sink only; never transmitted) |
| pay | none | not offered | absent |

## 2. Two MCP endpoints
| URL | Auth | Scope | Extra tools |
|---|---|---|---|
| https://ecorce.app/mcp | none | `mandate-demo-draft-only` (draft only) | — |
| https://ecorce.app/member/mcp | OAuth 2.1 (PKCE, dynamic client registration at /oauth/register). Human signs in with a passkey at /compte and approves on /authorize. | per member, with per-partner limits | `get_mandate`, `list_my_drafts` |

Transport: Streamable HTTP, stateless. POST JSON-RPC 2.0 returns JSON or SSE (`text/event-stream`). Capabilities: tools, prompts (7 recipes), resources (docs, capability catalogue, manifests, recipes, partners, opportunities, shared-tools).

## 3. Entities & First-Principles Opportunities
Every object has `type`; demo objects carry `test_data: true` and `label`.
- `manifest@v1`: 5-block contract (`actor`, `intent`, `trust_gate`, `function`, `projection`) backed by an isolated function in `ecorce-partner-tools`.
- `spn_product` & `offer`: shared product language (`SPN-EX-…`). A producer's products (`ferme-exemple`) are distributed through a distributor (`monde-naturel`) and carry `producer_id`.
- `order_draft`: grouped by distributor, with `advisor_notes` and `opportunities` (`unit_economics`, `logistics_consolidation`, `b2c_activation_each`, `fleet_media_co_op`).
- `shared_tool`: a partner-built tool template + configuration shared with the network (`GET /api/shared-tools`, MCP `list_shared_tools`). Another partner can clone it with attribution (`POST /api/shared-tools/clone`, MCP `clone_tool`).
- `network_opportunity` & `opportunity_draft`: a 3-actor coordination pool where producer, distributor, and retailers pool volume or co-fund B2C activation (`GET /api/opportunities`, MCP `list_network_opportunities`). Joining creates a draft (`POST /api/opportunities/join`, MCP `join_network_opportunity`) that a human confirms at `confirm_url`.

## 4. Error codes
| code | meaning | resolution |
|---|---|---|
| `unknown_spn` | line did not use an exact SPN id | select from `candidates` returned in the error |
| `ambiguous_offer` | several distributors offer that SPN | specify `partner_id` on the line |
| `invalid_qty` | quantity missing or out of range | provide an integer between 1 and 9999 |
| `unknown_partner` / `unknown_pool` / `unknown_shared_tool` / `manifest_not_found` | invalid identifier | list valid ids with the corresponding list tool |
| `mandate_limit_exceeded` | open drafts + new draft exceed per-partner limit | confirm existing drafts or adjust limit at /compte |
| `not_permitted_by_mandate` | attempted to confirm, place, or pay via agent | create a draft and return `confirm_url` to the human |

## 5. MCP tools (v1.0.0)
### publish_shared_page
Publier en 1 appel une page web dédiée et partageable au nom d'un acteur sur https://ecorce.app/p/{actor}/{slug} (et sa vitrine https://ecorce.app/p/{actor}) pour présenter un fait partagé, une offre, un lot groupé, une tournée ou un outil interactif sur la surface d'Écorce. Crée et déploie automatiquement le couple Fonction + Manifeste (manifest@v1 en 5 blocs) dans ecorce-partner-tools, initialise son manifest_state dans le registre d'état d'Écorce, et retourne page_url et actor_url.
```json
{"type":"object","properties":{"manifest_id":{"type":"string","description":"optional full manifest ID, e.g. 'ferme-exemple:panier-racines@v1'"},"actor":{"type":"object","description":"{handle, name, kind/role, sector, location}"},"actor_handle":{"type":"string"},"actor_name":{"type":"string"},"actor_kind":{"type":"string"},"headline":{"type":"string","description":"grand titre de la page partagée"},"story":{"type":"string","description":"récit ou explication du fait partagé / de l'offre"},"facts":{"type":"array","description":"liste de faits vérifiés ou chiffres clés ({label, value, detail} ou strings)","items":{"type":["object","string"]}},"shared_facts":{"type":"array","items":{"type":["object","string"]}},"sections":{"type":"array","description":"sections additionnelles ({title, body, items})","items":{"type":"object"}},"cta_label":{"type":"string","description":"libellé du bouton d'action sur la page"},"custom_html_card":{"type":"string","description":"bloc HTML optionnel sanitisé (sans script)"},"intent":{"type":"object","description":"{protocol, title, summary, description, tags}"},"protocol":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"trust_gate":{"type":"object","description":"{visibility, allowed_actors, call_policy, mode, human_approval_on_commit, scopes}"},"initial_state":{"type":"object","description":"état vivant initial écrit dans manifest_state et affiché en direct sur la page"},"stateful_rule_spec":{"type":"object","description":"règle déclarative de mise à jour d'état: {state_key, quantity_input_field, committed_field, remaining_field, target_or_capacity_field, unit_price_cents_field, collection_field, record_type}"},"projection":{"type":"object"},"handler_js":{"type":"string"},"reset_state":{"type":"boolean"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### publish_manifest
Create, compile, and deploy a brand-new 5-block manifest@v1 + isolated function on the Écorce programmatic surface ('ecorce-partner-tools') in a single call, and seed its initial_state in the Écorce state registry (manifest_state). Supports declarative stateful_rule_spec (state_key, quantity_input_field, committed_field, remaining_field, target_or_capacity_field, unit_price_cents_field, collection_field, record_type) or custom handler_js.
```json
{"type":"object","properties":{"manifest_id":{"type":"string","description":"optional full manifest ID, e.g. 'producteur-demo:lot-saison@v1'"},"actor":{"type":"object","description":"{handle, name, kind/role, sector, location}"},"actor_handle":{"type":"string"},"actor_name":{"type":"string"},"actor_kind":{"type":"string"},"intent":{"type":"object","description":"{protocol, title, summary, description, tags}"},"protocol":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"trust_gate":{"type":"object","description":"{visibility, allowed_actors, call_policy, mode, human_approval_on_commit, scopes}"},"initial_state":{"type":"object","description":"initial key-value object written immediately to manifest_state in the Écorce state registry"},"stateful_rule_spec":{"type":"object","description":"declarative state mutation rules: {state_key, quantity_input_field, committed_field, remaining_field, target_or_capacity_field, unit_price_cents_field, collection_field, record_type, read_actions}"},"function":{"type":"object"},"projection":{"type":"object"},"handler_js":{"type":"string"},"reset_state":{"type":"boolean"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### list_manifests
Discover 5-block universal manifests (manifest@v1: actor, intent, trust_gate, function, projection) registered on the Écorce programmatic surface ('ecorce-partner-tools'), including the 3 official Écorce pillars, A2A partner manifests (saja:tournee-pool-g9a@v1, lamoisson:besoins-hebdo@v1), and live partner tools. Filterable by actor, protocol, tag, or q.
```json
{"type":"object","properties":{"actor":{"type":"string","description":"optional actor filter, e.g. 'ecorce', 'saja', 'lamoisson', 'monde-naturel'"},"protocol":{"type":"string","description":"optional protocol filter, e.g. 'ecorce.route_pool@v1', 'ecorce.restock_needs@v1'"},"tag":{"type":"string","description":"optional tag filter, e.g. 'a2a', 'vrac', 'g9a', 'palettes'"},"q":{"type":"string","description":"optional free-text search query"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### invoke_manifest
Invoke a 5-block manifest@v1 function on the Écorce programmatic surface ('ecorce-partner-tools'). Enforces trust_gate.allowed_actors, injects the owner's manifest_state into the fonction, creates a transactional manifest_record when record_to_commit/state_patch is returned, and returns status='pending_human_approval', commit_token, and confirm_url when human_approval_on_commit=true.
```json
{"type":"object","properties":{"manifest_id":{"type":"string","description":"manifest ID, e.g. 'saja:tournee-pool-g9a@v1', 'lamoisson:besoins-hebdo@v1', 'ecorce:spn-catalogue@v1'"},"caller_actor":{"type":"string","description":"handle of the calling actor/agent, e.g. 'saja', 'lamoisson', 'ecorce'"},"input":{"type":"object","description":"input payload matching the manifest's function.input_schema"},"actor_context":{"type":"object","description":"optional caller actor context"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["manifest_id"],"additionalProperties":false}
```

### compose_manifests
Execute a sequential pipeline of 2 or 3+ manifest@v1 fonctions on the Écorce programmatic surface (e.g. ['ecorce:spn-catalogue@v1', 'ecorce:unit-economics-advisor@v1', 'ecorce:order-draft-engine@v1']), passing step N output into step N+1, and return the full execution steps_trace, latencies, final_output, and commit_token/confirm_url when human_approval_on_commit=true.
```json
{"type":"object","properties":{"pipeline":{"type":"array","minItems":1,"maxItems":6,"items":{"type":["string","object"]},"description":"ordered list of manifest IDs"},"caller_actor":{"type":"string","description":"optional calling actor handle"},"initial_input":{"type":"object","description":"initial input passed to step 1 and carried across steps"},"actor_context":{"type":"object","description":"optional caller actor context"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["pipeline"],"additionalProperties":false}
```

### read_manifest_state
Read the owner's private/shared manifest_state key-value store for a manifest (injected into its compiled function on the Écorce surface on every invoke). Auto-seeds initial state if empty.
```json
{"type":"object","properties":{"manifest_id":{"type":"string","description":"manifest ID, e.g. 'saja:tournee-pool-g9a@v1' or 'lamoisson:besoins-hebdo@v1'"},"actor_context":{"type":"object"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["manifest_id"],"additionalProperties":false}
```

### update_state
Alias of write_manifest_state: update or reset manifest_state (requires partner invite_code to persist).
```json
{"type":"object","properties":{"manifest_id":{"type":"string"},"key":{"type":"string"},"value":{},"patch":{"type":"object"},"reset":{"type":"boolean"},"updated_by":{"type":"string"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["manifest_id"],"additionalProperties":false}
```

### submit_record
Alias of confirm_manifest_record: confirm and commit a manifest_record (requires partner invite_code to persist).
```json
{"type":"object","properties":{"record_id":{"type":"string"},"commit_token":{"type":"string"},"approved_by":{"type":"string"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["record_id"],"additionalProperties":false}
```

### write_manifest_state
Update or reset the owner's manifest_state for a manifest (single {key, value}, multi-key {patch}, or {reset: true}).
```json
{"type":"object","properties":{"manifest_id":{"type":"string","description":"manifest ID"},"key":{"type":"string","description":"optional single state key to update"},"value":{"description":"JSON value for 'key'"},"patch":{"type":"object","description":"optional object of key/value pairs to upsert"},"reset":{"type":"boolean","description":"true to reset manifest_state to its initial seed"},"updated_by":{"type":"string","description":"actor handle performing the update"},"actor_context":{"type":"object"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["manifest_id"],"additionalProperties":false}
```

### list_manifest_records
Read the A2A cross-actor transactional mailbox (manifest_records). Filter by actor (returns records where owner_actor=actor OR caller_actor=actor), owner_actor, caller_actor, manifest_id, or status.
```json
{"type":"object","properties":{"actor":{"type":"string","description":"actor handle to view both incoming (owner_actor) and outgoing (caller_actor) records, e.g. 'saja' or 'lamoisson'"},"owner_actor":{"type":"string"},"caller_actor":{"type":"string"},"manifest_id":{"type":"string"},"status":{"type":"string","enum":["pending_human_approval","committed"]},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### confirm_manifest_record
Confirm a pending A2A manifest_record with its commit_token, transitioning status to 'committed' and automatically applying its state_patch to the manifest's manifest_state.
```json
{"type":"object","properties":{"record_id":{"type":"string","description":"record_id, e.g. 'rec_...'"},"commit_token":{"type":"string","description":"commit_token returned when the record was created"},"approved_by":{"type":"string","description":"optional human/owner actor confirming the record"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["record_id"],"additionalProperties":false}
```

### list_partners
List the distributors and producers on the Écorce network surface (demo: targeted pilot partners and fictional producer, example data). Read-only.
```json
{"type":"object","properties":{"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### search_catalogue
Search products and offers (FR or EN). Runs via compiled function 'fn-ecorce-spn-catalogue' (ecorce:spn-catalogue@v1). Returns exact 'spn' ids (e.g. SPN-EX-SAJA-001), unit/format (4 L, 10 L, 20 L…), invented demo prices, and deterministic advisor_notes / opportunities. Read-only.
```json
{"type":"object","properties":{"query":{"type":"string","description":"free text, FR or EN"},"partner_id":{"type":"string","description":"optional filter: a partner id from list_partners"},"postal_code":{"type":"string","description":"optional postal code or prefix, e.g. G9A"},"limit":{"type":"integer","minimum":1,"maximum":50},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### get_offer
Get one offer by its spn (e.g. SPN-EX-SAJA-001) or offer_id. Read-only. Invented example data.
```json
{"type":"object","properties":{"spn":{"type":"string"},"offer_id":{"type":"string"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### create_order_draft
Create order DRAFT(s) via compiled function 'fn-ecorce-order-draft' (ecorce:order-draft-engine@v1, result: DRAFT_ONLY; a human confirms at confirm_url). lines = JSON array of {spn, qty} using exact spn ids from search_catalogue.
```json
{"type":"object","properties":{"lines":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","properties":{"spn":{"type":"string","description":"exact SPN id, e.g. SPN-EX-SAJA-001"},"qty":{"type":"integer","minimum":1,"maximum":9999},"partner_id":{"type":"string","description":"distributor id when the same SPN is offered by several distributors"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["spn","qty"],"additionalProperties":false}},"partner_id":{"type":"string","description":"optional default distributor"},"postal_code":{"type":"string","description":"optional postal prefix, e.g. G9A"},"note":{"type":"string","maxLength":500},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["lines"],"additionalProperties":false}
```

### get_draft
Read one order draft: status (draft, confirmed_demo, placed_demo_sink), lines, total, advisor_notes and audit events.
```json
{"type":"object","properties":{"draft_id":{"type":"string","description":"format draft-xxxxxxxx"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["draft_id"],"additionalProperties":false}
```

### list_tool_templates
List the 4 Écorce programmatic surface function templates (custom_function, minimum_order, availability, promo), their JSON config schemas, tool input/output schemas, and isolation limits (10 ms CPU, 0 bindings, outbound network blocked). Read-only.
```json
{"type":"object","properties":{"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### publish_worker_function
Build and deploy a live isolated function onto the Écorce programmatic surface ('ecorce-partner-tools'). Accepts a partner_slug (e.g. 'muse-demo') and either {template, config, name} OR a plain-language {description} (which the Écorce model converts into a validated config). Returns the deployed script_name, the full generated ES module fonction source code, and the live mcp_tool_name.
```json
{"type":"object","properties":{"partner_slug":{"type":"string","description":"sandbox partner slug, e.g. 'muse-demo' or 'mon-distributeur'"},"partner_name":{"type":"string","description":"optional display name for the sandbox partner"},"template":{"type":"string","enum":["custom_function","minimum_order","availability","promo"],"description":"Écorce surface function template (default: custom_function)"},"name":{"type":"string","description":"human-readable function title"},"description":{"type":"string","description":"optional plain-language description of the function rules (if config is omitted, the Écorce model builds the config from this description)"},"config":{"type":"object","description":"template configuration object (see list_tool_templates)"},"publish":{"type":"boolean","description":"true (default) to upload and activate live on the Écorce surface; false to preview generated fonction code only"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### call_worker_function
Invoke a live partner fonction function running on the Écorce programmatic surface ('ecorce-partner-tools') and return its JSON output.
```json
{"type":"object","properties":{"mcp_tool_name":{"type":"string","description":"full MCP tool name returned by publish_worker_function, e.g. 'muse_demo__run_custom_function'"},"partner_id":{"type":"string","description":"alternative: partner_slug, e.g. 'muse-demo'"},"tool":{"type":"string","description":"alternative: tool name, e.g. 'run_custom_function', 'check_minimum_order', 'check_availability', 'get_current_promo'"},"arguments":{"type":"object","description":"JSON arguments passed to the fonction function"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### unpublish_worker_function
Unpublish and delete a sandbox partner fonction function from the Écorce programmatic surface.
```json
{"type":"object","properties":{"partner_slug":{"type":"string","description":"sandbox partner slug, e.g. 'muse-demo'"},"template":{"type":"string","enum":["custom_function","minimum_order","availability","promo"]},"tool_id":{"type":"string","description":"optional full tool_id, e.g. 'muse-demo.custom_function'"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["partner_slug"],"additionalProperties":false}
```

### list_shared_tools
List partner tools shared across the Écorce network with creator attribution and clone counts. Read-only.
```json
{"type":"object","properties":{"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### clone_tool
Clone a shared network tool for a partner with optional config_overrides and custom_name, preserving creator credit in the audit log.
```json
{"type":"object","properties":{"shared_tool_id":{"type":"string","description":"id from list_shared_tools, e.g. shared-min-order-g9a"},"target_partner_id":{"type":"string","description":"partner id cloning the tool"},"custom_name":{"type":"string"},"config_overrides":{"type":"object"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["shared_tool_id"],"additionalProperties":false}
```

### list_network_opportunities
List active 3-actor coordinated network pools (route/free-shipping consolidation in G9A/H2X, co-funded Écorce fleet-as-media campaign, and direct B2C shopper activation via Each.app). Read-only.
```json
{"type":"object","properties":{"postal_prefix":{"type":"string","description":"optional 3-char postal prefix, e.g. G9A or H2X"},"kind":{"type":"string","description":"optional filter: logistics_consolidation | fleet_media_co_op | b2c_activation_each"},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":[],"additionalProperties":false}
```

### join_network_opportunity
Create a DRAFT participation in a 3-actor network opportunity pool (result: DRAFT_ONLY; a human confirms at confirm_url).
```json
{"type":"object","properties":{"pool_id":{"type":"string","description":"id from list_network_opportunities, e.g. pool-mauricie-g9a, pool-flotte-ecorce, pool-activation-each"},"role":{"type":"string","enum":["retailer","distributor","producer"]},"actor_name":{"type":"string"},"commitment_cents":{"type":"integer","minimum":1000,"maximum":5000000,"description":"amount in cents CAD, e.g. 12000 for $120.00"},"postal_prefix":{"type":"string"},"spns":{"type":"array","items":{"type":"string"}},"note":{"type":"string","maxLength":300},"invite_code":{"type":"string","description":"Optional partner invite code (e.g. ECORCE-...) required only to persist state/records or publish live pages; omit for $0 sandbox simulation"},"dry_run":{"type":"boolean","description":"Optional: true to force in-memory simulation without writing to the Écorce state registry"}},"required":["pool_id"],"additionalProperties":false}
```

### Live partner tools (Écorce programmatic surface)
- monde-naturel.connector by monde-naturel (stub (example data)): `monde_naturel__check_availability`, `monde_naturel__get_delivery_windows`. Permissions: read:own_catalogue.
- distribution-exemple-outils.minimum_order by distribution-exemple-outils (self-serve (example data)): `distribution_exemple_outils__check_minimum_order`. Permissions: read:own_config.
- muse-demo.custom_function by muse-demo (self-serve (example data)): `muse_demo__run_custom_function`. Permissions: read:own_config.
