Files
template-app/profiles/convex
2026-11-14 09:56:45 +00:00
..
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00
2026-11-14 09:56:45 +00:00

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). Live collections are wired as on the other profiles: a generated mutation records each change of a collection's table in conusai_live_log in its own transaction, the web serves the CLP/1 stream (POST /api/v1/orgs/{org}/live) over convex/live.ts (apps/web/src/server/convex-live.ts: the grants decided by live:open under the caller's identity, the wake-up a reactive subscription to live:head, the hourly live change log prune cron), and a write answers its ConusAI-Txid. That is source and local unit tests with fake Convex clients (test/convex-live.test.ts); no live stream has been run against a Convex backend. 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
Live collections log table filled by a statement trigger, pg_notify wake-up conusai_live_log written by the mutation, live:head reactive subscription (convex/live.ts)
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 synchronizes app-scoped platform variables through bounded HTTP requests, then asks the Convex CLI to deploy the app schema and functions before web and backup start. Existing equal values and unlisted variables are preserved; a conflicting value fails the entire environment batch. The sync uses one 30-second deadline, does not retry automatically and never logs upstream response bodies or variable values. It uses the linked project service credential, never a human owner credential. The environment sync works through HTTP-only credential relays. For the pinned CLI's subsequent deployment APIs, the host relay validates both placeholders and injects the admin key into the bounded JSON request. The key is never delivered to the container. Installed acceptance is separate from the local backend/relay verification.
  • 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.
  • convex-deploy is the Guides publish step for the deprecated compose hand-off: conusai.json declares "guides": { "publish": true } on it, so it alone receives a member-started deployment's short-lived CONUSAI_GUIDES_PUBLISH_TOKEN and CONUSAI_GUIDES_DEPLOYMENT, revoked when the deploy job ends. After convex deploy succeeds, deploy.mjs publishes the code guides and the anchor catalogue the image's build wrote (guides-anchors.json). The smoke deploys the target backend with a publish credential and checks that the fake platform received both with that credential, and that the source deploy, which had none, published nothing.
  • 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. 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.