# The convex profile **Status: selectable preview; not production-ready.** `kit template:prune --profile convex` now selects this app composition, and the generated Next app builds. The BFF keeps the Convex admin key server-side, domain queries and mutations execute within real Convex function transactions, jobs use the Convex scheduler, and the one-shot deploy service installs the app's scoped platform runtime variables before pushing functions. The local gate deploys generated functions to a local backend and exercises a trusted identity, tenancy, notes, the outbox audit/usage sinks, and backup restore. It does not yet prove browser login through the generated Next BFF or a native platform deployment. File begin/complete/delete use server-side Convex actions around transactional mutations; streamed parts stay on the Next BFF and go directly to Blob after a Convex authorization query. The profile gate exercises an upload, streamed part, notes attachment, detach and delete against a local backend and fake Blob; deleting rows also have a scheduled recovery action. Documents OCR and proposal review run through a server-side Node action and the Convex scheduler; the local gate exercises extraction, proposal approval, and the resulting document line. The guides and support modules' effect procedures (each one call to a platform service, owning no app row) are generated Convex actions: `convex/lib/effects.ts` runs the kernel in the action with the module's platform port bound from the installed deployment variables, resolves the scope and checks the permission in internal queries over `ctx.db` (`convex/effectScope.ts`), and runs a nested read or write as its generated query or mutation. The account module's export and deletion are generated actions too: they compose those modules' shares and reach the deletion ledger and the person's rows through the account store bound in the action, whose every method is one internal query or mutation of `convex/account.ts` (the Postgres profile's own store over `ctx.db`), so the ledger records each step as it completes, a resumption runs only the steps left, and the five-minute `account deletion sweep` cron finishes what a module held up. Better Auth's account events reach the same flow (`account:providerEvent`). Branding logos remain unavailable; do not treat this preview as a production deployment. The table describes the current and intended profile architecture from spec §5.1 and §5.4. | | postgres (default) | convex (this profile) | |---|---|---| | Primary store | Postgres 18 | Convex (self-hosted, managed link) | | Data adapter | `@conusai-kit/data-kysely` | `@conusai-kit/data-convex` (`ctx.db` repositories) | | Procedures reach handlers via | Hono HTTP mount + `callByName` | generated `convex/generated/.ts` wrappers (`pnpm convex:gen`) | | Jobs | pg-boss (Postgres) | Convex scheduler (`convex/conusaiJobs.ts` dispatcher; job rows and dispatch share a mutation) | | Events | outbox → pg-boss relay | outbox table → scheduled internal mutation (`convex/relay.ts`, cron every minute); audit and usage use server-side platform adapters | | Tenant wall | repository predicate + RLS | `convex-helpers` custom functions (`convex/lib/functions.ts`) + repository predicate | | Migrations | numbered SQL, `migrate` job | `convex/schema.ts` generated from zod, pushed by `convex deploy`; data moves use the migrations component | | Audit | outbox subscribers over HTTP | the same subscribers, audit written via `@conusai-sdk/convex-audit` (registered in `convex/convex.config.ts`) | | Links | `postgres`, `identity` | `convex`, `identity` | | Process env | `POSTGRES_URL`, `POSTGRES_READER_URL`, `POSTGRES_MIGRATE_URL` | `CONVEX_SELF_HOSTED_URL`, `CONVEX_SELF_HOSTED_ADMIN_KEY` (server only) | ## What this folder carries - `convex-deploy` runs `profiles/convex/deploy.mjs`: it writes the app-scoped platform variables to a mode-0600 temporary file, asks the Convex CLI to install them in the backend, then deploys the app schema and functions before web and backup start. It uses the linked project service credential, never a human owner credential. - `convex/filesActions.ts` performs Blob begin/complete/delete calls; `convex/files.ts` keeps each row transition in an internal mutation. A five-minute recovery cron reconciles abandoned uploads and reschedules durable deletes. Streamed part bodies stay in the Next BFF and are guarded before the server-side Blob client sends them; no file bytes or platform token enter Convex arguments or browser bundles. - The BFF uses the same filtered module registry as the generated functions. Its readiness check compares the deployed module and procedure list plus the entity tables, columns, and indexes. ## Work required before this can be enabled This remains a preview because browser login through the generated Next BFF, native platform deployment, and external object-store workflows have not passed yet. The local file and OCR flows use a fake Blob/platform API; production readiness still requires those flows against a project- scoped test service credential and branding workflow composition. ## What the existing tests prove - `@conusai-kit/data-convex` tests its adapter contracts and isolation behavior under `convex-test` (`bun run --filter @conusai-kit/data-convex test:run`). - `scripts/vendor/test-template.sh app --profile convex` validates `conusai.json`, checks generated Convex files for deterministic regeneration, runs the adapter package suite, and tests an app deploy and notes/outbox/file smoke plus backup export/restore against local Convex backend containers. After deploying locally it also typechecks the functions against Convex's generated contexts. The smoke calls Convex functions directly with a local test identity; it does not prove browser login or the generated Next BFF route. - The same smoke calls the guides actions (`guides.runtime.bootstrap`, `guides.runtime.record`) and the support actions (`support.widget.config`, `support.widgetSession.mint`) for a member against the fake platform, checks the subject, app tenant, `role` trait and display name the platform received, and checks that a stranger to the organization and an identity pinned to another organization get the kernel's not-found with no platform call. The guides deploy step (code guides and anchors) still runs only in the Postgres profile's migrate job. - The same smoke runs the account module against the real backend and the fake platform: the export (complete, then a failed, named support section while support is down), the deletion status, a mismatched confirmation (no ledger row), a deletion held by support with its steps recorded, the session revoked and the identity detached, the `account:sweep` action failing while it waits and finishing once support is back (widget sessions revoked before the contact is erased), a repeated request, and a ban and a deletion as provider events. In a Better Auth app it also starts a third backend for the Better Auth deployment: a person signs up there, and their deletion ends that account (the password then signs nobody in). - The derived-session revocation outbox (`account.sessions.revoke`) is still the Postgres worker's: on this profile a sign-out asks the modules directly, bounded, once. - This profile has no Postgres worker or migration process; supported module jobs run in Convex. The local deployed-app smoke uploads, attaches, detaches and deletes a file through the real Convex backend and fake Blob API, and verifies OCR proposals and approval. It does not prove browser login or the generated Next BFF route. ## Backup implementation status The platform does **not** back a Convex backend up (decision AT-D16). The backup runner, compose schedule, Blob retention, and restore runbook are implemented and gated with a local round-trip. Configure `CONVEX_SELF_HOSTED_ADMIN_KEY` only in server containers and use [the backup and restore runbook](../../docs/backup.md). Backups run outside the Convex runtime because export is an admin CLI operation. ## Runtime variables The web and backup services receive `CONVEX_SELF_HOSTED_URL` and `CONVEX_SELF_HOSTED_ADMIN_KEY` from the linked Convex service. The deploy service additionally receives `CONUSAI_API_URL`, the project's scoped `CONUSAI_API_TOKEN`, `CONUSAI_PROJECT_ID`, and `CONUSAI_ORG_SLUG`; it copies those values into the Convex backend so file, audit, and usage actions can reach the platform. The web process also needs `AUTH_SESSION_SECRET`, `OIDC_ISSUER`, `OIDC_WEB_CLIENT_ID`, and `OIDC_WEB_CLIENT_SECRET`; `OIDC_SCOPES`, `OIDC_AUDIENCE`, `AUTH_COOKIE_NAME`, `AUTH_SESSION_IDLE_SECONDS`, and `AUTH_SESSION_ABSOLUTE_SECONDS` are optional auth preferences. `CONVEX_BACKUP_INTERVAL_HOURS` defaults to 24 and `CONVEX_BACKUP_RETENTION` defaults to 30. The platform-injected API token remains server-side and is never sent to browser code. With `AUTH_PROVIDER=convex-better-auth`, Better Auth runs in a second Convex deployment of the app, beside this one, linked to the project under the `better-auth` role (`conusai.json`: `{"type": "convex", "role": "better-auth"}`, which `kit template:prune --auth convex-better-auth` writes in place of the Identity link). The data deployment's `default` link injects `CONVEX_SELF_HOSTED_URL`, `CONVEX_SITE_URL` and `CONVEX_SELF_HOSTED_ADMIN_KEY`; the Better Auth link injects `BETTER_AUTH_CONVEX_URL` (its cloud origin), `BETTER_AUTH_CONVEX_SITE_URL` (its site origin) and `BETTER_AUTH_CONVEX_ADMIN_KEY`, as runtime-only variables. The operator sets only `BETTER_AUTH_SECRET` (a secret) and `AUTH_IDENTITY_NAMESPACE` as project variables (the steps are in docs/how-to/build-an-app-on-the-template.md of the platform repository). `web` receives the two URLs and the key; `convex-deploy` receives the URL, the key, the namespace and the secret, deploys `better-auth/` to that deployment and installs `AUTH_PROVIDER`, `AUTH_IDENTITY_NAMESPACE`, `BETTER_AUTH_CONVEX_URL` and `BETTER_AUTH_CONVEX_ADMIN_KEY` in this backend, whose account actions end a deleted person's Better Auth account. None of them reaches a browser or the image build.