# Cerefox Access Paths

Cerefox is built in three distinct layers. Understanding them tells you which credentials to
configure, what can reach the database, and which path is right for your integration.

> **A note on Supabase keys (2026):** Cerefox needs two API keys for two different transport
> layers. Layer 1 (Edge Functions) uses the **legacy anon JWT** as a Bearer token — the new
> `sb_publishable_…` key is rejected by the Edge Function gateway. Layer 2 (web + CLI REST)
> uses the new **secret key** (`sb_secret_…`) or the legacy `service_role` JWT — either
> works. See [`setup-supabase.md` → Supabase API keys (2026)](setup-supabase.md#supabase-api-keys-2026)
> for the full picture and why this asymmetry exists.

---

## Layer 1 — AI Agents via Edge Functions (HTTPS)

This is the primary integration layer for AI clients. Nine Supabase Edge Functions are
deployed on the Supabase platform and are reachable over HTTPS with nothing more than the
**legacy anon JWT** (a public-facing JWT, `eyJ…`). The Supabase gateway validates the key
before any request reaches a function; individual functions then use the service-role key
internally to call Postgres RPCs. Your anon key is never elevated to database-level access.

> ⚠️ Use the **legacy anon JWT** here, not the new `sb_publishable_…` key. The Edge
> Function gateway rejects non-JWT keys with `UNAUTHORIZED_INVALID_JWT_FORMAT`. See
> [`setup-supabase.md` → Supabase API keys (2026)](setup-supabase.md#supabase-api-keys-2026)
> for why.

### The nine Edge Functions

| Edge Function | Role |
|---|---|
| `cerefox-search` | Hybrid FTS + semantic search; handles server-side embedding |
| `cerefox-ingest` | Ingest a document — chunks, embeds, versions, stores |
| `cerefox-metadata` | List metadata keys with document counts and example values |
| `cerefox-get-document` | Retrieve full document content (current or archived version) |
| `cerefox-list-versions` | List the archived version history for a document |
| `cerefox-get-audit-log` | Query audit log entries with filters |
| `cerefox-metadata-search` | Query documents by metadata key-value criteria without text search |
| `cerefox-list-projects` | List all projects with names, IDs, and descriptions |
| `cerefox-mcp` | Streamable HTTP MCP adapter — calls Postgres RPCs directly |

### How clients connect

**MCP clients** (Claude Code, Cursor, Claude Desktop) connect to `cerefox-mcp`. It speaks
the MCP Streamable HTTP protocol and calls the Postgres RPCs directly (no internal fan-out
to the primitive Edge Functions). The client only ever talks to one URL.

```
MCP client (anon key)
    │
    ▼
cerefox-mcp
    │
    ▼ (service-role key, internal)
Postgres RPCs
```

**ChatGPT Custom GPT Actions** call the eight primitive Edge Functions directly over HTTPS
using an OpenAPI schema. `cerefox-mcp` is not involved (ChatGPT does not support the
Streamable HTTP MCP protocol).

**curl / scripts / custom HTTP clients** can also call the primitives directly using the
same anon key as a Bearer token.

### Credentials needed

- `CEREFOX_SUPABASE_URL` — your Supabase project URL
- **Legacy anon JWT** — found in your Supabase dashboard under **Project Settings → API Keys → Legacy → anon**. (Do not use the new `sb_publishable_…` key — gateway constraint.)

See `docs/guides/connect-agents.md` for step-by-step setup per client.

---

## Layer 2 — Web UI and CLI via Supabase REST

The web UI (TypeScript Hono backend + React/Mantine SPA, served by `cerefox web`) and all
`cerefox` CLI commands (`document ingest`, `search`, `server reindex`, `backup`, etc.) talk
to Supabase over its REST API (PostgREST), authenticating with a **service-role-equivalent
key** rather than the anon key — either the new **secret key** (`sb_secret_…`) or the legacy
`service_role` JWT. Both are accepted by the Data API gateway.

> The legacy Python FastAPI web app and Python CLI are husks slated for removal; only
> `uv run cerefox mcp` survives as a frozen, unmaintained fallback. The TS `cerefox` CLI
> and `cerefox web` are the current implementations.

The service-role key bypasses Supabase Row Level Security (RLS) policies and grants
unrestricted read and write access. This is intentional — the CLI and web app are trusted,
local tools that need to insert, update, and delete freely. Keep this key out of any
public-facing configuration.

> **Local coding agents (Claude Code, Codex CLI, opencode, OpenClaw, Hermes, …) also reach
> Cerefox through this layer**, when the user authorises the agent to invoke `cerefox …`
> via its Bash tool. This is "Path C" in `connect-agents.md`. The agent runs with the same
> service-role privileges as the user — same trust assumption as letting the agent edit
> source code in your repo. See `docs/guides/connect-agents.md` → "Path C — Shell CLI for
> local coding agents" for the setup and caveats.

```
Web UI / CLI (service-role key)
    │
    ▼
Supabase REST API (PostgREST)
    │
    ▼
Postgres RPCs  (same cerefox_* functions called by Edge Functions)
```

This layer calls the same Postgres RPCs as the Edge Functions — the business logic
lives in one place (Postgres) and is shared across all callers.

### Credentials needed

- `CEREFOX_SUPABASE_URL` — your Supabase project URL
- `CEREFOX_SUPABASE_KEY` — the new **secret key** (`sb_secret_…`) from **Project Settings → API Keys → Secret key**, or the legacy `service_role` JWT from the "Legacy" section of the same panel. **Not** the anon / publishable key.

---

## Layer 3 — Direct Postgres (Deployment Scripts Only)

For end users, `cerefox server deploy` (which bundles schema + RPCs + the nine Edge
Functions from the npm package) handles schema deployment directly. For contributors, the
canonical deployment and migration scripts (`bun scripts/db_deploy.ts`,
`bun scripts/db_migrate.ts`, `bun scripts/backup_restore.ts`) connect directly to Postgres
over TCP using the database connection string. (The legacy `.py` equivalents still exist
but are deprecated.) This is the only path that can run DDL statements (`CREATE TABLE`,
`CREATE FUNCTION`) — the REST API does not support them.

```
cerefox server deploy / bun scripts/db_deploy.ts  (DB password via DATABASE_URL)
    │
    ▼
Postgres (direct TCP connection)
```

No application code — not the web app, not the CLI's runtime read/write commands — uses
this path at request time. It is exclusively for schema deployment and data restore
operations.

### Credentials needed

- `CEREFOX_DATABASE_URL` — the direct Postgres connection string. **Use the Session
  Pooler** (port `5432`) from **Project Settings → Database → Connection pooling**, with
  username `postgres.<project-ref>` and `?sslmode=require` appended. Do not use the
  Transaction Pooler (`6543`) — it does not support DDL. See [`setup-supabase.md` →
  Connection pooling (2026)](setup-supabase.md#connection-pooling-2026) for the full
  reference.

---

## Local / self-hosted (World B) — a different access model

Everything above describes the **cloud / Supabase** deployment. The **local / self-hosted**
backend ([`setup-local.md`](setup-local.md)) runs Postgres + PostgREST + the Cerefox server
in one Docker container, and its access model is deliberately simpler:

- **No Layer 1 (Edge Functions) and no anon-JWT.** There are no Edge Functions; the
  `cerefox-server` inside the container exposes `/rest/v1` (a reverse-proxy to the in-container
  PostgREST) plus `/app` + `/api/v1`. Remote agents over HTTP are not a goal of World B.
- **The access token never leaves the container.** db-init self-generates the PostgREST JWT
  secret on boot and mints a `service_role` token into the container's runtime env. The web
  UI (served by the container) and the in-container CLI/MCP read it internally — nothing on
  the host holds it.
- **Agents use stdio over `docker exec`, not a network credential.** `cerefox-local mcp`
  runs `cerefox mcp` inside the container via `docker exec -i`; the MCP client launches that
  as a local subprocess. No URL, no bearer token in the client config.
- **The only host-side secret is `OPENAI_API_KEY`** (in `~/.cerefox/local/.env`), used for
  embeddings — the same as every deployment.
- By default the container publishes on **`127.0.0.1`** (loopback only); set
  `CEREFOX_LOCAL_BIND=0.0.0.0` to expose it on the LAN.

So Layers 1–3 below apply to the cloud deployment; the local backend collapses them into a
single container with an internally-held token.

---

## Summary

| Caller | Transport | Auth credential | Typical use |
|---|---|---|---|
| Claude Code / Cursor | HTTPS → `cerefox-mcp` | Legacy anon JWT | Daily AI assistant access |
| Claude Desktop | HTTPS → `cerefox-mcp` (via `supergateway`) | Legacy anon JWT | Daily AI assistant access |
| ChatGPT Custom GPT | HTTPS → primitive Edge Functions | Legacy anon JWT | AI assistant via GPT Actions |
| curl / HTTP scripts | HTTPS → primitive Edge Functions | Legacy anon JWT | Ad-hoc queries, automation |
| Web UI (`cerefox web`) | Supabase REST API | Secret key (or legacy service_role) | Web UI backend (TS Hono) |
| `cerefox` CLI (human) | Supabase REST API | Secret key (or legacy service_role) | Ingestion, search, reindex, backup |
| Local coding agent via `cerefox` CLI | Supabase REST API | Secret key (or legacy service_role) | User-authorised agent (Claude Code, Codex CLI, opencode, OpenClaw, Hermes, …) acting on user's behalf via Bash tool |
| `cerefox server deploy` / deployment scripts | Direct TCP | DB password | Schema deploy, data restore |

### Key security principle

The (legacy) anon JWT is safe to share with AI agents and client applications — it can
only call the operations exposed by the Edge Functions, and the Supabase gateway
rate-limits and validates it. The secret key / `service_role` JWT and the database
password must never be embedded in client-facing configuration or committed to the
repository.

---

## Destructive operations and the trust model

Cerefox classifies write operations into three tiers based on how irreversible they are.
The access surface for each tier is **not** the same — this asymmetry is a deliberate
architectural property, not an oversight. Future contributors should read this section
before "completing" the parity table by adding purge or restore to agent-facing access
paths.

### The three tiers

| Tier | Operations | Reversible? | Where exposed |
|---|---|---|---|
| 1. Reads + soft mutations | search, get, list-*, ingest (create/update), metadata-search, get-audit-log | n/a (reads) / yes (versioned) | All paths — MCP, Edge Functions, CLI, web UI |
| 2. Soft-destructive | `delete_document` (soft delete to trash), `set_review_status` | yes — restorable via web UI | All paths (CLI: `cerefox document delete`; web UI; **not** MCP or Edge Functions today) |
| 3. **Hard-destructive** | `purge_document` (permanent), `restore_document` (un-trash), `set_version_archived` (toggle version retention) | no (purge) / yes (restore, but recovers from a destructive action) | **Web UI only** |

### Why purge / restore are web-UI-only

The recovery story behind Cerefox depends on a **human-in-the-loop confirmation step
before irreversible action.** Soft-delete on its own is not enough — an agent that
mistakenly soft-deletes a document needs to be unable to silently restore the same
document later (covering its tracks), and certainly unable to escalate from soft-delete
to permanent purge.

So the access model is:

1. **An agent (via MCP, Edge Function, or CLI) can write or soft-delete freely.** Every
   such operation is recorded in `cerefox_audit_log` with `author`, `author_type`, and
   `created_at`. Soft-deleted documents land in trash and are excluded from search.
2. **A human reviews the trash through the Cerefox web UI.** They see the audit history
   for each document, decide whether the agent's action was correct, and either restore
   the document or — only after seeing what they're about to destroy — purge it.
3. **Purge is the only operation that frees database storage.** Soft delete keeps every
   version, every chunk, every audit entry. The "I made a mistake; recover this" workflow
   is therefore always possible until the human explicitly chooses purge.

A `cerefox purge-doc` CLI command, a `cerefox_purge_document` MCP tool, or a
`/documents/{id}/purge` HTTP endpoint accessible via the anon JWT would each break this
property. **Do not add them without a governance design that replaces the human-in-the-
loop step with an equivalent guard** (e.g. a "purge approval queue" the web UI must
clear before the operation actually runs).

### What this means for agent operations

If you're building tooling that uses the CLI (Path C) or any MCP/Edge Function path:

- **Use `cerefox document delete` freely** to soft-delete agent-authored content. Pair it
  with `--author <name> --author-type agent` so the audit trail is correct.
- **Surface the soft-delete to the user.** When your agent decides to delete something,
  tell the user explicitly: "I soft-deleted X (recoverable from the Cerefox trash in
  the web UI)." This gives them the visibility to review and either restore or commit.
- **Do not attempt to purge or restore from agent code.** There is intentionally no
  programmatic path. If your workflow needs purge / restore, that workflow needs human
  intervention — the design is correct, not incomplete.

### CLI delete-doc — interactive vs scripted

`cerefox document delete` prompts for confirmation by default (the prompt requires a TTY, so
an agent's Bash tool gets an abort instead of accidentally deleting). Agents
that legitimately need to soft-delete must pass `--yes` *and* set `--author` /
`--author-type` so the audit log captures who acted:

```bash
cerefox document delete <doc-id> --yes \
  --author "claude-code" --author-type "agent"
```

The success message echoes the resolved values back so the agent can include them in
its response to the user.
