# Capability: Media pipeline — prepare pack

Normalize a **folder of designer masters** into short-lived **public HTTPS URLs** under cms-edit upload limits, then import into Contentful with reuse-first.

**Never** expect the media pipeline service to write Contentful. Pipeline → ready URLs only. **cms-edit** owns assets/entries.

## Intent phrases

- process media folder
- prepare Drive assets for CMS
- media pipeline pack
- normalize oversized images/videos
- process case study assets
- upload from media pipeline

## Requires capabilities

`assetSearch` and `assetUpload`

## Prerequisites

- Hosted cms-edit with media pipeline **configured on the host** (ops env):
  - `MEDIA_PIPELINE_BASE_URL`
  - `MEDIA_PIPELINE_SERVICE_KEY` (server-only — **never ask the agent/user for this**)
  - `MEDIA_PIPELINE_TENANT` (e.g. `se`, not the cms-edit projectKey)
- Drive folder shared **Viewer** with pipeline service account  
  `media-pipeline-reader@se-media-pipeline.iam.gserviceaccount.com`  
  (or “anyone with the link” if using API-key Drive mode on the pipeline).  
  **If prepare/status reports `drive_inaccessible` or `drive_empty_or_unshared`:** fix the share first, then re-prepare **once**. Do not treat “no media files” as a wrong folder path until share is confirmed.
- Profile default: `case-study-v1`

## When to use

| Situation | Action |
|-----------|--------|
| Drive/Figma package with large JPEGs/videos | **This task first**, then structure build |
| Single small image already in library | `task-media-reuse-and-upload` only |
| Masters already under limits and catalogued | Reuse search only |
| Prior prepare already reached `succeeded` + catalog | **Reuse that jobId** — do not re-prepare the whole folder |

## Steps (agent — single auth)

### 1. Prepare

Call MCP tool **`cms_edit_media`**:

```json
{
  "action": "prepare",
  "driveFolderUrl": "https://drive.google.com/drive/folders/…",
  "profile": "case-study-v1"
}
```

CLI: `cms-edit media prepare --drive-folder "https://drive.google.com/drive/folders/…"`

Expect `{ ok: true, jobId, status: "queued", pollHint }`.

If `{ ok: false, code: "pipeline_unconfigured" }` → host ops must set env vars. **Do not** ask the user for a pipeline API key.

### 2. Wait / status

```json
{ "action": "wait", "jobId": "<jobId>" }
```

or

```json
{ "action": "status", "jobId": "<jobId>" }
```

**Pipeline lifecycle** (real pack states): `queued` → `running` → `succeeded` | `partial` | `failed`.

| Status | Meaning | Agent action |
|--------|---------|--------------|
| `queued` / `running` | Pack is processing | Keep `wait` / `status` |
| `succeeded` | All files ready | Proceed to catalog |
| `partial` | Some ready; `gaps[]` lists failures | Catalog ready items; report gaps — **do not invent** assets for gaps |
| `failed` | Nothing usable | Stop; report gaps |
| `provisioning` | **Host soft state only** (not a pipeline phase): job is registered but pack status is not readable yet | Retry wait/status only while age is **under ~90s** |
| `status_unavailable` (`ok: false`) | Soft grace exceeded; concurrent slot released | **Stop this jobId**. At most **one** new prepare. If the second also ends `status_unavailable`, **stop entirely** and report both jobIds (platform issue — the pack may still have completed on the pipeline). |

**Rules:**

- Do **not** invent assets for gaps.
- Do **not** wait 5–10+ minutes on `provisioning`.
- Do **not** thrash prepares (rate limit + concurrent max typically **2** per user).
- On `429` / too many concurrent: call `status` once on any stuck old `jobId` (to free slots if past grace), then prepare again **once**.
- Prefer **reusing** a job that already reached `succeeded` + catalog over preparing again for the same Drive folder.

### 3. Catalog

```json
{ "action": "catalog", "jobId": "<jobId>" }
```

Each asset includes:

| Field | Use |
|-------|-----|
| `readyUrl` | `asset upload --url` into Contentful |
| `previewUrl` / `posterUrl` | Agent vision for **title / fileName / alt** (when present) |
| `altDraft` / `titleDraft` | Filename seeds only — improve before save |

### 4. Import into Contentful (reuse-first)

After drafting titles/alts from **previewUrl** / **posterUrl**:

**Prefer filtering** so unused files in a shared Drive folder are not uploaded:

| Param | Use |
|-------|-----|
| `sourceNames` | Exact catalog `sourceName` allowlist (Drive file names) |
| `excludeSourceNames` | Skip specific files |
| `onlyWithOverrides` | Import only assets that have an `overrides` row (typical case-study path) |

```json
{
  "action": "import",
  "jobId": "<jobId>",
  "withMedia": true,
  "onlyWithOverrides": true,
  "overrides": [
    { "sourceName": "OM1_Hero.jpg", "title": "OM1 hero", "alt": "…", "fileName": "se-om1-hero-v3.jpg" }
  ],
  "dryRun": true
}
```

1. Always **`dryRun: true` first** — check `existing: true/false` per asset (reuse is by **fileName**).
2. When you need **new** assets instead of reusing prior runs, set `overrides[].fileName` to unique names.
3. Then import without `dryRun`.

CLI:  
`cms-edit media import --job <id> [--dry-run] [--with-media] [--only-with-overrides] [--source-names a.jpg,b.jpg] [--exclude-source-names x.jpg] [--overrides-json '[…]']`

Import:

1. Filters catalog (allowlist / exclude / onlyWithOverrides)  
2. Searches existing assets by fileName  
3. Else uploads from `readyUrl` as the authenticated Contentful user  
4. Returns `map`, `skipped[]`, and `counts` for page build  

**Linking Media to components:** use singular Entry links with  
`cms_edit ["set", "@cN", "visual", "<mediaId>", "--link"]`  
— **never** `add … --target visual` (`add` only supports content **arrays**).

Manual `asset upload --url` remains available for one-offs.

### Hardening notes (for agents)

- Jobs are **owned by the OAuth user** who prepared them — you cannot poll another user’s `jobId`.  
- Rate limits apply (prepares/hour, concurrent jobs).  
- Never document or request `MEDIA_PIPELINE_API_KEY` or Blob tokens in chat.
- `provisioning` is **not** a real pipeline phase; do not treat it as “still encoding” for many minutes.
- If the MCP client times out (~180s) on `set`/`rtf`/`import`, **read / catalog / diff before retrying** — the server may already have applied the change.
