# `backend.md` — logic, data, and identity with `r`

Everything is authored **DB-direct**. There are no spec files and no compile/sync
step: `r` writes rows straight into the hosted DB, which is the only source of truth.
(See `design.md` for the visible app: components/routes/css/assets/behavior.)

> **Scope is always `-S <scope>`** (a bare leaf expands under your workspace root);
> never a positional. Run `r <group> <subverb> --help` for any command's exact shape.

## AGENTS: do not probe known runtime contracts

Do **not** burn the DB with `probe_*` pipelines to rediscover these facts. Read this
doc, and when you need exact usage use `r <group> --help`, `r i`, `r show`,
`r component describe`, `r behavior list|attachments|validate`, and `r head list`
first, then build.

- `snippet` is a full but **DB-ISOLATED** Python subprocess: it reads `input`, sets
  `output`, and may use `tempfile`, `httpx`, `subprocess`, and native binaries
  (`ffmpeg` when installed). It runs with a scrubbed env and the repo OFF its import
  path, so it **cannot** `import db`/`auth`/`runner`/`storage` or open a DB
  connection. To persist, RETURN rows — `output={...}` (one) or
  `output={"rows":[{code,label,meta}, …]}` (many) — and the runner writes them to the
  worker's `writes_to` set (me_*-aware). It is killed by the worker timeout.
- `internal` is only the registered vetted handlers. It is **not** a way to run
  arbitrary binaries or app-authored code.
- `storage` only does byte intent/finalize: presign browser PUT, verify bytes,
  emit storage metadata. It does **not** transform bytes.
- Binary transforms (ffmpeg/image work) run in a `snippet` over bytes it fetches
  itself via `httpx` (e.g. a presigned URL handed in its `input`) and RETURN the
  result row(s). The presign + finalize byte choreography is the `storage` worker
  tier's job, driven from a target-bound `r behavior` attachment (browser read →
  `ctx.run` start_upload → PUT → `ctx.run` finalize).
- `me_*` is per-user and lives under `runner.me.u…`, not the app scope. A normal
  `writes_to=me_*` worker (returning `output={...}`) overwrites that user's
  `current` row. For append-only user galleries, return
  `output={"rows":[{code,label,meta}, …]}` with unique `code`s.
- Browser behaviors are frontend-only `mount(ctx)` snippets stored in
  `behaviors` / `behavior_attachments`. They may read sets and call pipelines
  with `ctx.runPipeline` or polling `ctx.run`, but durable writes still happen in
  pipeline workers.

## Using `me` for user-specific actions

`me` and any value-set code that starts with `me_` are the framework's per-user
channel. They are ordinary `value_set`/`value_option` rows, but the runtime
routes them through the signed-in user's private identity scope instead of the
active app scope. Treat plain `me` as the auth-owned identity set; use
`me_<feature>` for app-authored user state.

On login, the runtime maintains a private scope like `runner.me.u<user-sub>` for each
user, refreshed from identity claims (`name`, `role`, email metadata, etc.). Browser
reads of `/read/me` or `/read/me_profile` always resolve inside that caller's `me`
scope, no matter which app they are using.

To save user-specific state, make the pipeline write to a `me_*` set:

```bash
S=crm
r pipe save_profile -S $S -w me_profile:Profile
r w save_profile snippet -w me_profile \
    -b 'output={"name":input.get("name",""),"bio":input.get("bio",""),"result":input.get("name") or "Profile"}'

r component set editor -S $S --json '{"tag":"div","children":[
    {"kind":"field","name":"name","value":"$name","placeholder":"Name","run":"save_profile","on":"input","debounce":500},
    {"kind":"field","name":"bio","fieldType":"textarea","value":"$bio","placeholder":"Bio","run":"save_profile","on":"input","debounce":500}]}'
r component set home-app -S $S --json '{"tag":"div","children":[{"component":"editor","source":"me_profile"}]}'
r component set_route home home-app -S $S
```

When a signed-in user's worker writes to `me` or `me_*`, the runtime creates that set
in the user's own `me` scope on first write and stores the row under the stable code
`current`. The app-scope `-w me_profile:Profile` declaration is just the target
declaration; the private row is created lazily per user, and the next save overwrites
`current` — so `me_profile`, `me_settings`, `me_avatar` behave like editable per-user
records. Other users get their own isolated `current` row.

For editable UI, bind a component with `source:"me_profile"` and put fields inside
that sourced component. Use `repeat` for read-only lists; an editable field inside a
`repeat` of the same set will be rebuilt on every save.

> **TRAP — `me_*` routes to the USER's zone, not the app scope. Internalize the
> three consequences before reaching for it.** (1) A `me_*` write lands in
> `runner.me.u<sub>`, so the APP can never list "everyone's rows" from a `me_*`
> set — for shared/admin views, keep a second app-scope set (a snapshot log) the
> workers also write. (2) A standard worker write overwrites that user's single
> `current` record — save IS edit. Multi-row per-user galleries need a snippet
> returning `rows` with explicit unique codes. (3) The `read` worker tier runs
> server-side without the caller's zone — it cannot reach `me_*`; only the
> browser's `/read/me_*` and the user-bound write path can.

## 1. Scope & authority

A **scope** is an ltree path (e.g. `__SCOPE_ROOT__.crm`). Every row is scoped, and
row-level security gates every query: you see and write only what lives under your
**workspace root** `__SCOPE_ROOT__`. Reads inherit DOWN the tree (a child scope sees
an ancestor's rows); writes are contained in the addressed subtree. The hosted op
quota is metered per workspace.

## 2. Value sets — the data primitive

A **value set** (`value_set` + `value_option` rows) is a named, scoped collection
of rows: each row has a stable `code`, a `label`, an `ordinal`, and a `meta` jsonb.
Components, css, assets, behavior, routes, records, config — all are value sets.
Inspect with `r i <set> -S <scope>` (`r i -S <scope>` alone lists sets).

A **records** set is just a value set a pipeline writes into (`writes_to`). There
is no separate "collection" concept: author a form's inputs as `field` component
behavior nodes (design.md), and persist them with a worker whose `writes_to` is
the set. To mint and fill value sets directly — your own registries, optionally with
a declared row schema that `put` enforces and `doctor` re-checks — use
`r set create/put/fetch_url/rows/validate/drop` (design.md §Generic sets).

## 3. Pipelines & workers

A pipeline is a `pipeline` row + ordered `worker` rows, authored DB-direct:

```bash
r pipe save_contact -S crm -w contacts:Contacts      # envelope (+ output set)
r w   save_contact snippet -w contacts -b -          # add a worker (body from stdin)
r show save_contact                                  # reconstruct from rows
r checkout crm                                       # the whole app as files in .tmp/
```

`pipe` upserts the envelope (resets workers unless `--keep`) and bootstraps the
scope if new. `w <code>` resolves the pipeline's scope by code and appends (or
`--at <ordinal>` replaces) one worker. Snippet bodies come from `-b '<code>'` or
`-b -` (stdin) and are **Python-syntax-checked** before write — never hand-escaped
into JSON.

### Worker tiers

| tier | what | key flags |
|---|---|---|
| `snippet` | DB-isolated Python subprocess: reads `input`, sets `output` (one row) or `output={"rows":[…]}` (many); files/subprocesses/ffmpeg work; **no** db/storage import | `-b <code>` / `-b -` |
| `internal` | a vetted owned handler | `-H <name>` |
| `httpx` | call a service; user/house keys per header | `-u <url> -k <keys> -f <body-key>` |
| `read` | resolve a value set into the run data | `--set --key --mode --as --read-scope` |
| `llm` | an LLM step (OpenAI-compatible or Anthropic) | `--provider --model --user-from --system-from` |
| `storage` | byte-asset capability: `op=start_upload` / `op=finalize_upload` / `op=fetch_upload` | pass the spec with `-s '{json}'` |

The friendly `w <code> <tier>` form assembles snippet/internal/httpx/read/llm from
flags. Other tiers (storage, and fan-out/agent steps) are authored by passing the
spec directly: `r w <code> storage -s '{"op":"start_upload","accept":["image/*"]}' …`
(the `-s/--spec` blob merges into the worker spec), or via `r op pipe.add_worker`.

A worker persists its output as a row when `writes_to` is set. Output is run
through the renderer grammar at the write path, so nothing unsafe lands in a row —
**except** a write to the `assets` set, which goes through asset schema validation
(kind/provider/storage) so storage assets keep a chosen code like `hero-video`
rather than a launch-id code (see §6).

Secrets never travel through run data: a worker that needs a key declares it
(`-k <service>` for a user key, `{"service":"x","house":true}` in spec.keys for
the shared house key) and the kernel injects it as a request header. An httpx worker
missing a **user** key parks the run until the user connects it. Anything
secret-shaped that ends up in a run's data or error text is REDACTED at the moment it
is persisted (`launch.output` is polled live by the browser — treat it as visible).

## 4. Run lifecycle

```bash
r run save_contact -i '{"name":"Ada","email":"ada@x.io"}'
r runs <launch_id>     # status + per-attempt telemetry/output
```

RUN validates required inputs, enqueues a `launch`, the worker walker drives it,
each step's output persists (when `writes_to`), and the live `/feed` repaints any
component bound to a changed set.

## 5. Storage & assets — bytes

App media can be an *external reference* (an https URL, a YouTube id, an inline
SVG, an icon) **or** a *storage-backed object* whose bytes the app owns. For the
external kind, register it with `r asset add` and `--probe` (design.md §Assets).

For storage-backed bytes, the app owns the upload via the **`storage` worker tier** —
two ops around a direct browser→object-store byte transfer, never an app byte API. A
behavior attached to the file input reads the file, hashes it, calls
`ctx.run("<start_upload_pipeline>", meta)`, PUTs to the returned `upload_url`, then
calls `ctx.run("<finalize_pipeline>", {…})`:

```
RUN start_upload      storage worker validates accept/max-bytes, presigns a PUT
                      -> { upload_url, logical_key, object_id }     (no writes_to)
Browser PUT upload_url (the bytes)   -> straight to object storage
RUN finalize_upload   storage worker verifies the bytes landed (HEAD), then writes_to:
                        writes_to=assets        -> a validated asset row
                        writes_to=me_*/records  -> a generic value_option row
```

**Server-side ingest (`fetch_upload`).** When the bytes originate server-side — an
image/video model returns an output **URL** — there is no browser to PUT them.
`fetch_upload` is the third `storage` op: the runner downloads the URL straight into
storage and emits the same asset row as `finalize_upload`, in one worker step. So an
avatar engine is just `httpx`(call the model) → `storage fetch_upload`(persist its
output URL, `writes_to=assets`). The runner enforces https-only, an SSRF guard on the
host, per-hop redirect re-validation, and computes the `sha256` from the fetched bytes
(never trusted from input). Set `allow_hosts` to pin ingest to the model's CDN.

GET (`/read`) resolves a storage row to a byte URL at read time — public keys to a
cacheable public URL, private keys to a short-lived presigned GET re-minted each read.
The DB row stays environment-neutral and the renderer stays pixel-free.

## End-to-end: a feature from scratch

```bash
r app create crm                                               # scope + sets, reachable
r pipe save_contact -S crm -w contacts:Contacts
r w   save_contact snippet -w contacts -b 'output={"result":input.get("name"),"data":input}'
r component set form -S crm --json '{"part":"root","tag":"form","children":[
    {"kind":"field","part":"name","name":"name"},{"kind":"submit","pipeline":"save_contact","label":"Save"}]}'
r component set list -S crm --json '{"part":"root","tag":"div","children":[{"kind":"repeat","valueSet":"contacts"}]}'
r component set_route home form -S crm
r css set_class card -S crm && r css set_rule card base -S crm --json '{"padding":"24px"}' && r css attach card component:form -S crm
r run save_contact -i '{"name":"Ada"}'                         # → a row in contacts, live in the list
```

---

Some capabilities documented for the local `./r` CLI are **not** in the hosted CLI:
identity-provider setup, secrets/OAuth connections,
sandbox→prod promotion, and schema migrations / undo-history. On hosted Runner, your
workspace sits behind its own sign-in and
the per-user `me` channel is always available — you don't wire auth per app.
