# Écorce surface — contracts (v0.1, demo)

Status: DEMO. All catalogue data is illustrative test data ("données d'exemple"). No order is ever transmitted to a partner. Partners are pilot targets ("partenaires pilotes visés"), not signed partners.

## Core rule: draft → confirm → place
| Step | Who | Demo status |
|---|---|---|
| draft | agent (any LLM) via MCP or REST | works |
| confirm | a human, at confirm_url | works (web page) |
| place | the system transmits to the partner | STUBBED: never happens in this demo |

Agents can never confirm or place. Every step writes an audit event.

## Entities
All objects carry `type` and `test_data: true` in this demo.

### spn_product
The shared product language of the network. One SPN identifies a product across partners.
```json
{ "type":"spn_product", "spn":"SPN-EX-SAJA-001", "name":"string", "category":"string", "unit":"string", "tags":["string"], "test_data":true }
```
Demo SPNs start with `SPN-EX-` (EX = example).

### partner
```json
{ "type":"partner", "id":"saja", "role":"distributor|producer|retailer", "name":"string", "city":"string", "province":"QC", "status":"pilot_target|active", "ordering_today":"string", "website":"url" }
```

### offer
A partner's offer for one SPN product.
```json
{ "type":"offer", "id":"offer-…", "partner_id":"saja", "spn":"SPN-EX-…", "unit":"string", "min_qty":1, "price":null, "price_note":"string", "availability":"string" }
```
`price` is always null in the demo. Never invent a price.

### cart
Not stored separately in v0.1: a cart is the `lines` array of an order draft, `[{ "spn":"…", "qty":1 }]`, for exactly one partner.

### order_draft
```json
{ "type":"order_draft", "id":"draft-xxxxxxxx", "partner_id":"saja", "status":"draft|confirmed_demo", "mandate":"mandate-demo-draft-only", "lines":[{"spn":"…","name":"…","unit":"…","qty":1}], "note":"string", "confirm_url":"url", "next_step":"string", "audit":[audit_event] }
```

### mandate
What a human allows an agent to do on their behalf.
```json
{
  "id": "mandate-demo-draft-only",
  "type": "mandate",
  "scope": [
    "read:catalogue",
    "create:order_draft",
    "read:order_draft"
  ],
  "denied": [
    "place:order",
    "pay",
    "confirm:order_draft"
  ],
  "principal": "demo visitor (anonymous)",
  "agent": "any LLM client",
  "expires": "never (demo)",
  "summary_fr": "Mandat de démonstration : l'agent peut lire et rédiger des brouillons. Il ne peut ni confirmer, ni passer, ni payer une commande.",
  "summary_en": "Demo mandate: the agent may read and draft. It may not confirm, place or pay for an order."
}
```
Future mandates (not in demo): per member, per partner, with spending caps and expiry, signed with a passkey.

### audit_event
```json
{ "type":"audit_event", "at":"ISO-8601", "actor":"agent (mcp)|agent (rest)|human (web)", "action":"order_draft.created|order_draft.confirmed", "detail":{} }
```

## MCP tools (https://llm.ecorce.app/mcp)
### list_partners
List network partners (distributors) on the Écorce surface. Demo: pilot targets, not signed partners. Read-only.

Input schema:
```json
{
  "type": "object",
  "properties": {},
  "required": [],
  "additionalProperties": false
}
```

### search_catalogue
Search the SPN-mapped catalogue. Returns offers with SPN product. All data is EXAMPLE/TEST data with no prices. Read-only.

Input schema:
```json
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "free text, FR or EN, e.g. 'savon vrac'"
    },
    "partner_id": {
      "type": "string",
      "enum": [
        "saja",
        "monde-naturel"
      ]
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    }
  },
  "required": [],
  "additionalProperties": false
}
```

### get_offer
Get one offer by offer_id or spn. Read-only. Test data.

Input schema:
```json
{
  "type": "object",
  "properties": {
    "offer_id": {
      "type": "string"
    },
    "spn": {
      "type": "string"
    }
  },
  "required": [],
  "additionalProperties": false
}
```

### create_order_draft
Create an order DRAFT for one partner. Never places an order. Returns confirm_url for the human to review and confirm. Allowed by the demo mandate (draft only).

Input schema:
```json
{
  "type": "object",
  "properties": {
    "partner_id": {
      "type": "string",
      "enum": [
        "saja",
        "monde-naturel"
      ]
    },
    "lines": {
      "type": "array",
      "minItems": 1,
      "maxItems": 50,
      "items": {
        "type": "object",
        "properties": {
          "spn": {
            "type": "string"
          },
          "qty": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "spn",
          "qty"
        ],
        "additionalProperties": false
      }
    },
    "note": {
      "type": "string"
    }
  },
  "required": [
    "partner_id",
    "lines"
  ],
  "additionalProperties": false
}
```

### get_draft
Read an order draft, its status and its audit events.

Input schema:
```json
{
  "type": "object",
  "properties": {
    "draft_id": {
      "type": "string"
    }
  },
  "required": [
    "draft_id"
  ],
  "additionalProperties": false
}
```

## REST mirror
- GET /api/partners
- GET /api/catalogue?query=&partner_id=
- GET /api/offers/{offer_id}
- POST /api/drafts  body: { partner_id, lines:[{spn,qty}], note }
- GET /api/drafts/{id}
- POST /api/drafts/{id}/confirm  (human UI only; agents must not call it)

## Test-data labels
Every response has `test_data: true` and `label`. Display "données d'exemple / example data" to humans.

## Example prompt
"Use the Écorce MCP server at https://llm.ecorce.app/mcp. Find bulk dish soap from SAJA and draft an order of 2 units. Give me the confirm link."
