10 KiB
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/<module>.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-deployrunsprofiles/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.tsperforms Blob begin/complete/delete calls;convex/files.tskeeps 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-convextests its adapter contracts and isolation behavior underconvex-test(bun run --filter @conusai-kit/data-convex test:run).scripts/vendor/test-template.sh app --profile convexvalidatesconusai.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,roletrait 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:sweepaction 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. 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.