# Building apps on Catalox as the content backend

**Audience:** Teams building web apps, dashboards, and agent platforms on x12i  
**Prerequisite:** [Product overview](./overview.md)

This is the **recommended integration pattern**: your product’s **backend and agents use Catalox as the API for content and metadata** — catalogs purpose-built for that app — so you do not implement custom CRUD, discovery, or parallel “agent config” stores for every entity.

---

## The pattern in one paragraph

Define catalogs (skills, templates, plans, vocabulary, policies) in Catalox with **descriptors** and **bindings** to your `appId`. Your web app’s BFF calls **bootstrap** and **items** APIs (embedded engine or HTTP client). Your agent runtime reads the **same items**, scoped by token or context. Your product backend owns **users, workflows, and transactions**; Catalox owns **what exists in the catalog and who can see it**.

---

## Why this is the main use case

### Problem: two backends that drift

A common anti-pattern:

```text
Admin UI  ──►  Postgres "skills" table  ──►  human-edited metadata
Agent API ──►  Redis / JSON files        ──►  agent-edited metadata  ❌ drift
```

Operators change a skill in the dashboard; agents still see yesterday’s prompt because someone forgot to sync the second store.

### Solution: one catalog layer, many consumers

```text
                    ┌──────────────┐
  Admin UI / SPA ──►│              │
  Mobile app     ──►│  Your BFF    │──► Catalox (skills, templates, …)
  Agent runtime  ──►│  (thin)      │
  CI seed job    ──►│              │
                    └──────────────┘
                           │
              Workflows / runs / orders stay in YOUR services
```

**Why it makes sense:**

1. **Single source of truth** — humans and agents read the same rows  
2. **Descriptors drive UI** — new catalog fields appear in generic screens without frontend deploys for column defs  
3. **Bindings enforce product boundaries** — app A never accidentally lists app B’s catalogs  
4. **Scope narrows exposure** — sales agents see sales skills; HR catalog stays invisible  
5. **Backend time saved** — no per-entity Express routes, Firestore helpers, or migration scripts for every catalog  

---

## What Catalox owns vs what your app owns

| Catalox owns | Your app owns |
|--------------|---------------|
| Catalog records, descriptors, bindings | User authentication (SSO) |
| Item storage (`item.data` + `indexed`) | Sessions, billing, entitlements |
| List/get/filter/search by descriptor rules | Workflow state, execution runs |
| References, smart properties, relation rules | Real-time events, queues |
| Access outcomes (ok / denied / misconfigured) | Business logic that *uses* an item |
| Seed manifests, backup, optional history | Custom UX beyond generic catalog UI |

**Rule:** If the question is “**what options exist?**” or “**what is the definition?**” → Catalox. If the question is “**what happened when the user clicked run?**” → your backend.

---

## Reference architecture

### Web app with admin + picker UI

```text
Browser (React)
    │  SSO cookie / session
    ▼
Your BFF (Node)
    │  1. Authenticate user (your IdP)
    │  2. Issue Authix token OR map user → CataloxContext
    │  3. Call Catalox (embed or HTTP)
    ▼
Catalox engine / catalox-service
    │
    ▼
Firestore (native items + metadata collections)
```

**BFF responsibilities (thin):**

- Map logged-in user to `accountId`, `domainId`, roles  
- Issue or forward Authix token with least-privilege features  
- Proxy or wrap Catalox responses for your SPA  
- **Do not** reimplement list/filter SQL for each catalog  

**SPA responsibilities:**

- Call BFF endpoints that wrap `getAppCatalogBootstrap` + `listCatalogItems`  
- Render from descriptor `queryableFields` (or use catalox-ui)  
- Submit writes as item payloads; let Catalox validate identity and relations  

### Agent platform backend

```text
Control plane
    │  issues Authix token: agentId, domainIds, catalox.item.read
    ▼
Agent worker
    │  GET /v1/catalogs/skills/items (or embedder in-process)
    ▼
Catalox → scoped items only
    │
    ▼
Agent uses item.data in LLM prompt / tool config
    │
    ▼
Run record, tokens, errors → YOUR execution store (not Catalox)
```

Agents need **metadata** (prompts, tool defs, routing rules). They do not need Catalox to store **conversation history** or **run status**.

---

## Step-by-step: adopting the pattern

### Phase 1 — Model your catalogs (product design)

1. List entities that are **definitions**, not transactions (e.g. `skills`, `prompt_templates`, `plan_tiers`).  
2. For each, decide **identity** (natural key, composite `categoryId:code`, etc.).  
3. Mark fields **indexed/filterable** in the descriptor if the UI will filter on them.  
4. Assign catalogs to **`appId`** via bindings when the app goes live.

→ [catalog-format.md](./catalog-format.md) · [smart-properties.md](./smart-properties.md)

### Phase 2 — Provision (once per environment)

```bash
catalox firestore probe
catalox seed validate --file ./manifests/my-app-v1.json
catalox seed apply --app myApp --file ./manifests/my-app-v1.json --god
catalox toolbox check-access --app myApp --catalog skills
```

Manifests are version-controlled; apply is idempotent. CI can validate on every PR.

→ [onboarding-happy-path.md](./onboarding-happy-path.md)

### Phase 3 — Wire the BFF (embedder path — lowest latency)

```ts
import { createCataloxFromEnv } from "@x12i/catalox/firebase";
import { mapAuthixToCataloxContext } from "@x12i/catalox-authix-bridge";

const { catalox } = createCataloxFromEnv();

// After your SSO + Authix issue:
app.get("/api/catalogs/bootstrap", async (req, res) => {
  const context = mapAuthixToCataloxContext(req.authixPayload);
  const bootstrap = await catalox.getAppCatalogBootstrap(context, context.appId!);
  res.json(bootstrap);
});

app.get("/api/catalogs/:catalogId/items", async (req, res) => {
  const context = mapAuthixToCataloxContext(req.authixPayload);
  const result = await catalox.listCatalogItemsWithOutcome(context, req.params.catalogId, {
    limit: Number(req.query.limit) || 50,
    filter: req.query.filter ? JSON.parse(String(req.query.filter)) : undefined,
  });
  // Map outcome → HTTP (403 denied, 409 misconfigured, 200 empty ok)
  res.status(outcomeToStatus(result.outcome)).json(result);
});
```

→ [catalox-ui-contract.md](./catalox-ui-contract.md) · [authorization.md](./authorization.md) · [outcomes.md](./outcomes.md)

### Phase 4 — Wire the BFF (HTTP client path — service boundary)

Use when the catalog API runs as **`catalox-service`** on an internal or public URL:

```ts
import { createCataloxClient } from "@x12i/catalox-client";

const catalox = createCataloxClient({
  baseUrl: process.env.CATALOX_API_URL!,
  getToken: () => issueAuthixTokenForUser(req.user),
});

const bound = catalox.withToken(token);
await bound.getAppCatalogBootstrap("myApp");
await bound.listCatalogItems("skills", { limit: 50 });
```

→ [DEPLOYMENT.md](./DEPLOYMENT.md) · [packages/http/client/README.md](../packages/http/client/README.md)

### Phase 5 — Generic or custom UI

**Option A — Generic catalog UI (catalox-ui):**  
Bootstrap → render list/grid from descriptors; optional custom renderers via `customRenderer` / snippets.

**Option B — Product-native UI:**  
Still use bootstrap for field metadata; build your own components but **do not hardcode catalog ids** in column definitions — read `queryableFields`.

→ [catalog-custom-ui.md](./catalog-custom-ui.md)

### Phase 6 — Agents

1. Control plane registers agent identity in Authix.  
2. Issue token with `catalox.item.read` and scope (`domainIds`, `agentIds`).  
3. Agent fetches items at startup or on cache TTL.  
4. Cache in memory if needed; **invalidate when ops run seed or admin writes** (webhook or poll).

Never duplicate catalog rows into agent-local JSON files as the system of record.

---

## Time saved on the backend (concrete)

For a typical app with **5–10 catalog-like entities**, building in-house usually includes:

| Work item | Rough effort without Catalox | With Catalox |
|-----------|-------------------------------|--------------|
| Schema + migrations per entity | 1–2 days × N | Descriptor + seed manifest |
| CRUD API routes | 0.5–1 day × N | 0 (bootstrap + items API) |
| List/filter/index logic | 1 day × N | Descriptor `indexed` + engine |
| Admin authorization | Ad hoc per route | Bindings + Authix features |
| Agent-facing config API | Separate service | Same items + scope |
| Seed/staging data | Custom scripts | `seed apply` + validate |
| Backup before bulk edit | Custom | `firestore backup` |

**Order of magnitude:** weeks of backend work compressed to **integration + manifests + thin BFF** — especially when multiple apps share operator tooling and the generic UI pattern.

Catalox does not remove the need for a BFF (auth, token issue, product-specific aggregation). It removes the **repeated catalog CRUD layer**.

---

## Content “designed especially for the app”

Each app should curate catalogs that match **its** domain language:

| App type | Example catalogs | Item.data holds |
|----------|------------------|-----------------|
| Agent platform | `skills`, `tools`, `policies` | Prompts, tool schemas, guardrails |
| Knowledge product | `articles`, `roles`, `use_cases` | Content + smart property codes |
| Commerce helper | `plans`, `addons`, `regions` | Pricing metadata (not live carts) |
| Workflow product | `templates`, `signals`, `categories` | Template graphs, routing rules |

**Design practices:**

- One catalog per **cohesive vocabulary** (not one giant “config” blob).  
- Use **smart properties** for cross-catalog FKs (roles, tags) instead of duplicating strings.  
- Put **queryable** fields in the descriptor early — fixing `indexed` later requires re-upsert.  
- Keep **secrets out** of items; use `credentialsRef` ids resolved by your vault.

→ [pagenti-knowledge-usage.md](./pagenti-knowledge-usage.md) for a reference app shape.

---

## Security model (product view)

Three layers for HTTP API (6.0.0):

```text
Authix token     →  Is this caller allowed to call this route?
Engine binding   →  Is this app allowed to use this catalog?
Item scope       →  Which rows match account/domain/agent lens?
```

For embedder mode, your BFF constructs `CataloxContext` with the same semantics after your SSO.

**Never** expose open-mode `catalox-service` to the public internet. Production browser apps use **Authix + CORS allowlist**.

→ [catalox-v6/6.0.0/README.md](./catalox-v6/6.0.0/README.md)

---

## Maturity for this pattern

| Capability | Status | Recommendation |
|------------|--------|----------------|
| Native catalogs + descriptors + bindings | **Stable** | Primary path for app content |
| Bootstrap-driven generic UI | **Stable** | Use for admin and pickers |
| Scoped items for agents | **Stable** | Token scope + native `scope` |
| Embedder in BFF | **Stable** | Preferred for latency |
| HTTP client + private service | **Stable** | Internal microservices |
| Public Authix API | **6.0.0 line** | Partners and browser apps at scale |
| Mapped Mongo/API catalogs | **Stable** | When data already lives elsewhere |
| Record history / audit | **Optional** | Enable when compliance needs per-field history |

Known limitations: [Known-Gaps](../Known-Gaps/README.md). Catalox is not a replacement for your execution/event store.

---

## Anti-patterns to avoid

| Anti-pattern | Why it hurts |
|--------------|--------------|
| Storing orders/runs in catalog items | Wrong tool; high churn breaks catalog model |
| Hardcoding catalog columns in React | Defeats descriptor-driven UI |
| Second “agent copy” of metadata | Drift from admin truth |
| Skipping bindings “because dev is easier” | Production denials and security holes |
| Using `superAdmin` in user-facing paths | Bypasses all governance |
| Giant single catalog for all config | No reusable vocabulary; poor filtering |

---

## Checklist before go-live

- [ ] Catalogs defined with descriptors (`queryableFields`, `identity`)  
- [ ] `catalogBindings` for production `appId`  
- [ ] Seed manifest in git; `seed validate` in CI  
- [ ] BFF maps SSO user → context or Authix token with least privilege  
- [ ] UI uses bootstrap (not hardcoded catalog registry)  
- [ ] Agents use scoped read tokens; runs stored outside Catalox  
- [ ] Backup run before bulk migration (`firestore backup`)  
- [ ] Open mode **not** exposed publicly  

---

## Related documentation

| Topic | Link |
|-------|------|
| Product “why” | [overview.md](./overview.md) |
| Example scenarios | [USE-CASES.md](./USE-CASES.md) |
| BFF contract | [catalox-ui-contract.md](./catalox-ui-contract.md) |
| First setup | [onboarding-happy-path.md](./onboarding-happy-path.md) |
| Deploy | [DEPLOYMENT.md](./DEPLOYMENT.md) |
| FAQ | [FAQ.md](./FAQ.md) |
