# Templates

Templates are an **INFRASTRUCTURE** building block: the platform
ships a library of Liquid templates that other infrastructure
resources consume — interoperability contracts, AI agent prompts,
workflow definitions, status poller bodies. Templates are
**read-only** from the SDK — they ship with the platform and a
datalake's catalog is whatever the platform exposes to it.

SDK namespace: `api.templates`.

For the safety contract that bounds every template body the
platform accepts — the consumer-author trust boundary, the 11
custom Liquid filters, the SQL-fragment boundary, and the
compliance gates that compose with them — see `ai_sandbox.md`.

## 1. Wire shape

Three discovery methods, two complementary response shapes —
structured (for code) and markdown (for agents):

```typescript
// The systemTemplates catalog response has no exported type on the
// public surface yet — `catalog` is inferred from the call.

// Structured catalog
const { data: catalog } = await api.templates.systemTemplates(
  tenantSlug,
  datalakeSlug,
)
// catalog.data — the full in-scope list, NOT paginated
//   { path, content, output_schema }
//     path           category/sub-category/name segment string
//     content        Liquid template body
//     output_schema  null | object — rendered output's expected shape

// Markdown catalog — fake-paginated (default 20/page); see §4 Gotcha 5
const { data: catalogMd } = await api.templates.metadata(
  tenantSlug,
  datalakeSlug,
  { page: 1, page_size: 20 },  // optional; omit for the first page
)
// catalogMd: string — one agent-facing markdown page of in-scope templates

// Markdown body for one template
const { data: templateMd } = await api.templates.metadataDetails(
  tenantSlug,
  datalakeSlug,
  basename,  // e.g. 'delivery_status' or 'contact_message_categorizer'
  intent,    // e.g. 'status_poller' or 'ai_agent' — note: intent
             //                                     != path segment;
             //                                     see §4 Gotcha 1
)
// templateMd: string — agent-facing markdown describing the template
```

## 2. Path structure

Template paths follow `<category>/<sub-category>/<name>`.
Categories the SDK exposes include
`data_activation/interoperability/`, `ai_agents/`, `workflows/`,
`status_poller/`. Path segments below the category are platform-owned and have
outlived at least one naming scheme, so **discover paths via
`api.templates` rather than constructing them.** The
`data_domain` filter that used to select a datalake's slice of
the catalog was removed with GH-859; what remains is
server-decided.

## 3. Lifecycle

Templates are static — they ship with the platform, not created
via SDK. New templates arrive via platform releases. If you need
a custom template, supply it inline (e.g. an interop contract's
`template_config: { type: 'custom', body: '...' }`) rather than
trying to add to the catalog.

## 4. Gotchas

1. **`basename` + `intent` is the composite key.** The `intent`
   value is a fixed set of category identifiers — it does NOT
   map cleanly to path segments. Some intents match (e.g.
   `status_poller/...` → `'status_poller'`); some are
   singularizations (e.g. `ai_agents/<industry>/<name>` →
   `'ai_agent'`); some collapse multiple path segments (e.g.
   `data_activation/interoperability/<industry>/...` →
   `'data_activation_interoperability'`). Check the typed
   parameter for the valid set; multiple templates can share a
   basename across intents. Within a single intent, the basename
   must resolve uniquely under its `<intent>` prefix — if two
   sub-directories under that prefix ship the same basename (e.g.
   two vendors under `data_activation/interoperability/`),
   `metadataDetails` returns `409 Conflict` with the candidate
   paths; a missing basename returns `404`. This bites status
   templates: Mailgun and SES both ship an `email_message_status`
   under `status_poller`, so the bare `email_message_status`
   answers 409 — name the provider's path
   (`status_poller/aws_ses/email_message_status`), as the bare
   `sms_message_status` already required.

2. **`output_schema` may be `null`.** Some templates render plain
   strings without structured output. Test for null before
   treating it as an object.

3. **Templates from other industries never appear.** The
   platform filters per-datalake. Walking the catalog gives only
   in-scope entries; no need to filter by industry in your code.

4. **Read-only from SDK.** For custom template content, use
   inline `template_config` on the consuming resource (interop
   contracts, AI agents, etc.) rather than seeking a catalog
   addition.

5. **The markdown catalog is paginated; the structured list is
   not.** `systemTemplates` returns every in-scope template in one
   response. `metadata` fake-paginates the *same* corpus — sorted
   by `path`, sliced by `page` / `page_size` (default 20), with a
   pagination narrative line at the top of each page. A data
   domain with more than one page's worth of templates only
   surfaces the first page unless you pass `page`. Reach for
   `systemTemplates` when you need a complete enumeration in code;
   `metadata` is the human/agent-readable view.
