# Declarative config: `onvibe.json`

An app's configuration — who can access it, which emails are authorized, which routes stay public,
its scheduled jobs and its framing policy — lives in a single declarative file, **`onvibe.json`**,
at the project root. **This is the only way to change those settings: there are no per-setting
tools.** You edit the file and deploy it. A **version lock** stops you from wiping config you never
saw.

## The golden rule: download → edit → redeploy

**Never write `onvibe.json` from scratch.** Always start from the current one:

1. **Download** the live config with `get_config({ project_id })`. It returns the current
   `onvibe.json` including a `version` field (a hash of the live config).
2. **Edit** the parts you want to change. **Keep the `version` field exactly as it is.**
3. **Deploy** the edited `onvibe.json` (stage it and `deploy`). The platform applies it **only if
   `version` still matches the live config**. If it doesn't (someone/something changed the config
   since you downloaded), the deploy is rejected with `CONFIG_OUT_OF_DATE` — call `get_config`
   again, re-apply your edit on top of the fresh file, and redeploy.

This guarantees you can't accidentally delete config by regenerating a partial file.

```jsonc
// onvibe.json — from get_config; edit and redeploy
{
  "version": "d2a37fc5c623712a",        // KEEP AS-IS — the lock
  "title": "My Book Club",              // display name on the login pages (default: the subdomain)
  "access": {
    "policy": "signup",                  // public | signup | allowlist | password
    "publicPaths": ["/", "/d/*"],        // routes open despite auth
    "allowlist": ["a@example.com"]        // only for policy "allowlist"
  },
  "crons": [                             // scheduled jobs (full set; omit to remove all)
    { "name": "daily-digest", "schedule": "0 9 * * *", "path": "/cron/digest" }
  ],
  "security": {
    "frame": "deny"                      // deny (default) | sameorigin | allow
  }
}
```

## What it covers

- `title` — the app's display name, shown on the managed login/signup pages. Omit it to fall back
  to the subdomain (the project id).
- `access.policy` — the managed access policy (see the managed-auth doc).
- `access.publicPaths` — routes open to anonymous visitors even under auth.
- `access.allowlist` — authorized emails (only under `allowlist`).
- `crons` — the **complete** set of scheduled jobs. A versioned deploy makes the crons match this
  list exactly (adds new ones, removes any not listed). Omit the section (or use `[]`) to remove all.
- `security.frame` — anti-framing policy. `deny` (default, the app can't be embedded in an iframe),
  `sameorigin` (only same-origin framing), or `allow` (opt out — embeddable anywhere). Omitting it is
  the safe default (`deny`).

Custom domains and public-fork publishing are **not** part of `onvibe.json` on purpose — they aren't
cloneable config (a fork starts private; a domain is unique to one app). Manage those with their own
tools (`add_custom_domain`, `set_project_public_fork`) or the dashboard.

## What is NOT in `onvibe.json`

These are managed separately and must **not** be put in the file:

- **Environment variables** (keys or values) — use `set_env` / the dashboard. Secrets never go in
  a committed config file.
- **Resources / tier** — set by the plan, not by the app.
- **The shared password** (for `policy: "password"`) — a secret; set it with `set_access_policy`.
  The file can declare `policy: "password"`, but not the password itself.

## No per-setting tools

There is no `set_public_paths`, `add_allowed_email`, `create_cron` or `delete_cron`. Every one of
those settings is a field in this file: download, edit that field, deploy. One mental model, one
audit trail, and the file in your project always matches what's live.

The one exception is **`set_access_policy`**, which stays only because of the *shared password* of
`policy: "password"` — a secret, and secrets never go in the file. Use it just for that.

Two related tools are about **state**, not config:

- `get_project({ project_id })` — schedule, next run and last run of each cron job.
- `run_cron({ project_id, name })` — trigger a job once, now, for testing.
