# Seeds apply contract (success and failure)

Agents: read this before looping on `POST /v1/seeds/apply`.

## Mandatory safety loop

```text
1. Discover  → GET /v1/seeds/health
               GET /v1/entities/metadata
               GET /v1/entities/metadata/relation-graph   ← required; do not skip
2. Dry-run   → apply_via_api.py --dry-run   (local package only; NOT server validation)
3. Apply     → only on sandbox/dev tenant + human asked
4. Verify    → GET /v1/entities/by-name/{displayName} for each entity
               GET /v1/entities/metadata/relation-graph again
```

`apply_via_api.py` runs discover + relation-graph pre-flight and post-apply verify by default. Writes `DISCOVER_RESULT.json` and `VERIFY_RESULT.json`.

## Endpoint

```text
GIA_ENDPOINT = API origin only (no trailing slash)
  local:  http://127.0.0.1:4000
  cloud:  https://gia.hub8.ai/api     ← NOT https://gia.hub8.ai

POST {GIA_ENDPOINT}/v1/seeds/apply
Authorization: Bearer {GIA_API_KEY}
Content-Type: application/json
```

Scripts refuse bare `https://gia.hub8.ai`.

## Success shape

HTTP **200** and body like:

```json
{
  "ok": true,
  "tenantId": "…",
  "userId": "…",
  "packageName": "Golden Mini",
  "entities": {
    "Demo Categories": "<entityId>",
    "Demo Items": "<entityId>",
    "Demo Items Hub": "<entityId>"
  },
  "menu": { "inserted": 4, "skipped": 0, "total": 4 },
  "records": {
    "Demo Categories": { "inserted": 2, "updated": 0, "total": 2 },
    "Demo Items": { "inserted": 2, "updated": 0, "total": 2 }
  }
}
```

Treat as success only when `status == 200` **and** `ok === true`, **and** verify step passes (by-name + relation-graph).

Re-apply (idempotent) often shows `inserted: 0`, `skipped` / `updated` > 0. That is still success.

## Relation graph

`GET /v1/entities/metadata/relation-graph` returns tenant-wide nodes/edges (lookups, childEntity, embeds).

- **Pre-apply:** see what already links on the tenant; avoid inventing `sourceLabel`s that do not exist.
- **Post-apply:** confirm graph refreshed (`entityCount` / `edgeCount`); new entities should appear in metadata and by-name.

Discover report includes `relationGraph.entityCount`, `edgeCount`, and sample node names.

## Dry-run (important)

`--dry-run` only builds the JSON package on disk. It does **not** call Gia, validate relations, or check admin permissions. Use discover + apply + verify for real checks.

## Staging vs production

Apply always targets the **API key’s tenant**. Use a dedicated sandbox/dev key. Never apply experimental seeds with a production key.

## Export shape (related)

`GET /v1/seeds/export?entities=…` returns `{ "ok": true, "package": { /* apply body */ } }`. Use the inner **`package`**.

## Common failures

| Symptom | Cause | Fix |
|---|---|---|
| HTML / SPA shell | Bare `https://gia.hub8.ai` | Use `https://gia.hub8.ai/api` |
| Script refuses endpoint | SPA host | Use `/api` |
| HTTP 401 | Bad/missing key | Matching env key |
| HTTP 403 | Not admin/manage | Admin key |
| Discover fails | Metadata or relation-graph error | Fix auth; do not `--skip-discover` |
| Link fields broken | Bad `sourceLabel` | Exact display name; check relation-graph |
| Hub table empty | Table not bound to FDS Label | `entityDataSourceName` = Label |
| Wrong upserts | Bad `matchFields` | Unique business keys |
| Wrong order | Bad `publishOrder` | Masters before dependents |
| Metadata 404 | Used slug | Use display name |
| Verify fails | by-name or relation-graph | Inspect `VERIFY_RESULT.json` |

## Seeds vs Entities CRUD

| Use | When |
|---|---|
| `POST /v1/seeds/apply` | Whole package |
| `POST /v1/entities` + publish | One entity |
| `POST /v1/entity-collections/{name}` | One record / chunked jsonl |

## Golden package

https://gia.hub8.ai/vibe-coding/golden-mini.seed.json or `samples/golden-mini-seed/`.
