// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Local generated-app proof: deploy the actual functions to local Convex and exercise the // server-only BFF identity contract plus atomic tenancy/notes mutations. import assert from "node:assert/strict" import { createObjectStore } from "@conusai-kit/platform" import { ConvexHttpClient } from "convex/browser" import { makeFunctionReference } from "convex/server" const url = process.env.CONVEX_SELF_HOSTED_URL const adminKey = process.env.CONVEX_SELF_HOSTED_ADMIN_KEY if (!url || !adminKey) throw new Error("local app smoke needs the self-hosted Convex URL and admin key") const ref = (name) => makeFunctionReference(name) const client = new ConvexHttpClient(url) client.setAdminAuth(adminKey) const ready = await client.query(ref("system:ready"), {}) assert.equal(ready.ready, true) assert.deepEqual(await client.action(ref("filesActions:readiness"), {}), { ready: true }) for (const name of ["auth", "files", "notes", "tenancy", "branding"]) assert(ready.modules.includes(name), `missing module ${name}`) await assert.rejects( () => client.mutation(ref("generated/tenancy:tenancy_org_create"), { input: { slug: "qa-org", name: "QA" } }), (error) => error?.data?.code === "unauthenticated", "unauthenticated requests must not run a public tenancy mutation", ) const subject = `local-smoke-${Date.now()}` const issuer = "https://conusai.internal/test" // The BFF's first-login transaction (system:appUserResolve replaced system:userInsert). const user = await client.mutation(ref("system:appUserResolve"), { claims: { issuer, subject, email: `${subject}@example.test`, displayName: "Local smoke", locale: "en", }, create: true, }) const identity = { issuer: "https://conusai.internal/bff", subject: user.id, tokenIdentifier: `https://conusai.internal/bff|${user.id}`, conusaiOrgId: undefined, } client.setAdminAuth(adminKey, identity) const organization = await client.mutation(ref("generated/tenancy:tenancy_org_create"), { input: { slug: "smoke-org", name: "Local smoke organization" }, }) const project = await client.mutation(ref("generated/tenancy:tenancy_project_create"), { input: { org: organization.slug, workspace: organization.workspaceSlug, slug: "smoke-project", name: "Local smoke project", moduleKey: "notes", }, }) identity.conusaiOrgId = organization.orgId client.setAdminAuth(adminKey, identity) const brand = await client.mutation(ref("generated/branding:branding_brand_set"), { input: { org: organization.slug, displayName: "Convex brand smoke", primary: "#1a1a8c", accent: "#f4c430" }, }) assert.equal(brand.displayName, "Convex brand smoke") assert.equal(brand.primary, "#1a1a8c") assert.deepEqual( await client.query(ref("generated/branding:branding_brand_get"), { input: { org: organization.slug }, }), brand, ) const scope = { org: organization.slug, workspace: organization.workspaceSlug, project: project.slug } const note = await client.mutation(ref("generated/notes:notes_note_create"), { input: { ...scope, title: "Convex generated-app smoke", body: "One atomic Convex mutation." }, }) const apiUrl = process.env.CONUSAI_API_URL if (!apiUrl) throw new Error("local app smoke needs the fake platform API URL") const blob = createObjectStore({ CONUSAI_API_URL: apiUrl, CONUSAI_API_TOKEN: process.env.CONUSAI_API_TOKEN ?? "dev-token", CONUSAI_PROJECT_ID: process.env.CONUSAI_PROJECT_ID ?? "1", }) const bytes = new TextEncoder().encode("%PDF-1.4\nConvex local file action proof\n%%EOF\n") const upload = await client.action(ref("filesActions:begin"), { input: { ...scope, filename: "convex-action-proof.pdf", contentType: "application/pdf", size: bytes.length }, }) const partInfo = await client.query(ref("files:partInfo"), { input: { ...scope, fileId: upload.fileId, part: 1 }, }) const sent = await blob.uploadContent(partInfo.uploadId, bytes, { part: 1, size: bytes.length }) const stored = await client.action(ref("filesActions:complete"), { input: { ...scope, fileId: upload.fileId, parts: [{ number: 1, etag: sent.etag }] }, }) assert.equal(stored.size, bytes.length, "the action must finalize the bytes Blob actually accepted") assert.equal(typeof stored.sha256, "string") const firstRelay = await client.mutation(ref("relay:relayPass"), {}) assert.equal(firstRelay.failed, 0, "the Convex file event subscriber must enqueue its OCR job successfully") let extracted for (let i = 0; i < 60; i += 1) { const listing = await client.query(ref("generated/documents:documents_document_list"), { input: scope }) extracted = listing.documents.find((document) => document.fileId === upload.fileId) if (extracted?.status === "extracted") break await new Promise((resolve) => setTimeout(resolve, 500)) } assert.equal(extracted?.status, "extracted", "the stored-file event must dispatch the Convex OCR action") assert(extracted?.pending >= 1, "the OCR action must persist review proposals") const document = await client.query(ref("generated/documents:documents_document_get"), { input: { ...scope, fileId: upload.fileId }, }) assert.equal(document.lines[0]?.quote, "Reference: QR-2026-04") assert.equal(document.lines[0]?.status, "pending") await client.mutation(ref("generated/proposals:proposals_proposal_approve"), { input: { ...scope, proposalId: document.lines[0].lineId, expectedVersion: document.lines[0].expectedVersion, }, }) const reviewed = await client.query(ref("generated/documents:documents_document_get"), { input: { ...scope, fileId: upload.fileId }, }) assert.equal(reviewed.document.approved, 1, "a reviewed proposal must write an approved document line atomically") assert.equal(reviewed.lines[0]?.status, "approved") const attachment = await client.mutation(ref("generated/notes:notes_attachment_add"), { input: { ...scope, noteId: note.noteId, fileId: upload.fileId }, }) const withAttachment = await client.query(ref("generated/notes:notes_note_get"), { input: { ...scope, noteId: note.noteId }, }) assert.equal(withAttachment.attachmentsEnabled, true) assert.equal(withAttachment.attachments[0]?.attachmentId, attachment.attachmentId) await client.mutation(ref("generated/notes:notes_attachment_remove"), { input: { ...scope, noteId: note.noteId, attachmentId: attachment.attachmentId }, }) await client.action(ref("filesActions:remove"), { input: { ...scope, fileId: upload.fileId } }) await assert.rejects( () => client.query(ref("generated/files:files_file_get"), { input: { ...scope, fileId: upload.fileId } }), /no such file/i, "delete must remove the Blob object before finalizing the file row", ) const listed = await client.query(ref("generated/notes:notes_note_list"), { input: scope }) assert( listed.notes.some((item) => item.noteId === note.noteId), "notes mutation must be readable through Convex", ) const details = await client.query(ref("generated/notes:notes_note_get"), { input: { ...scope, noteId: note.noteId } }) assert.equal(details.attachmentsEnabled, true, "the composed file workflow enables notes attachments") assert.equal(details.attachments.length, 0, "removing an attachment must detach the existing file link") // Effect procedures of guides and support are generated Convex actions (convex/lib/effects.ts): the // kernel resolves the member's scope and permission in an internal query, runs the nested // `tenancy.me.get` as its generated query, and calls the platform through the module's port. const effectState = async () => { const response = await fetch(new URL("/__e2e/state", apiUrl), { headers: { "x-conusai-organization": "e2e" } }) return response.json() } // The widget calls these as a signed-in member: the BFF pins an organization only for an API key // (convex-caller.ts), and a pinned credential has no user scope for the nested tenancy.me.get. const memberIdentity = { ...identity } delete memberIdentity.conusaiOrgId client.setAdminAuth(adminKey, memberIdentity) // A procedure without input, called the way the BFF calls it (input undefined, dropped on the wire). const me = await client.query(ref("generated/tenancy:tenancy_me_get"), {}) assert( me.organizations.some((entry) => entry.slug === organization.slug && entry.role === "owner"), "tenancy.me.get answers a call without input", ) // kit:guides:begin const bootstrap = await client.action(ref("generated/guides:guides_runtime_bootstrap"), { input: { org: organization.slug, path: "/notes", locale: "en" }, }) assert.deepEqual(bootstrap, { mode: "platform", etag: '"fake-guides"', guides: [] }) const eventId = globalThis.crypto.randomUUID() const recorded = await client.action(ref("generated/guides:guides_runtime_record"), { input: { org: organization.slug, idempotencyKey: "convex-smoke-batch", events: [ { schema_version: 1, event_id: eventId, type: "guide.started", guide_key: "notes.first-note", guide_version: 1, expected_progress_revision: 0, progress_epoch: 0, occurred_at: new Date().toISOString(), }, ], }, }) assert.equal(recorded.outcomes[0]?.event_id, eventId) assert.equal(recorded.outcomes[0]?.status, "applied") const guideCalls = (await effectState()).guides const sentBootstrap = guideCalls.find((call) => call.route === "bootstrap") assert.equal(sentBootstrap?.body?.identity?.subject, user.id, "the subject is the app's own user id") assert.equal( sentBootstrap?.body?.identity?.app_tenant, organization.orgId, "the app tenant is the resolved organization", ) assert.equal( sentBootstrap?.body?.context?.traits?.role, "owner", "the role trait came from the nested tenancy.me.get query", ) assert(guideCalls.some((call) => call.route === "record" && call.body?.events?.[0]?.event_id === eventId)) // kit:guides:end // kit:support:begin assert.deepEqual( await client.action(ref("generated/support:support_widget_config"), { input: { org: organization.slug } }), { mode: "platform", channel: "convex-smoke-channel" }, ) const supportSession = await client.action(ref("generated/support:support_widgetSession_mint"), { input: { org: organization.slug }, }) assert(supportSession.token.startsWith("sws_"), "a minted support session") const minted = (await effectState()).support.find((call) => call.route === "mint") assert.equal(minted?.body?.subject, user.id) assert.equal(minted?.body?.app_tenant, organization.orgId) assert.equal(minted?.body?.display_name, "Local smoke", "the display name came from the nested tenancy.me.get query") // kit:support:end // A member of no organization: the action's kernel refuses before any platform call. The user is // created with plain admin auth, as the first one was (system:appUserResolve is internal). client.setAdminAuth(adminKey) const stranger = await client.mutation(ref("system:appUserResolve"), { claims: { issuer, subject: `${subject}-stranger`, email: `${subject}-stranger@example.test`, displayName: "Stranger", locale: "en", }, create: true, }) const strangerIdentity = { ...identity, subject: stranger.id, tokenIdentifier: `https://conusai.internal/bff|${stranger.id}`, } delete strangerIdentity.conusaiOrgId client.setAdminAuth(adminKey, strangerIdentity) const callsBefore = await effectState() const strangerCalls = [] strangerCalls.push(["generated/guides:guides_runtime_bootstrap", { org: organization.slug, path: "/notes" }]) // kit:guides strangerCalls.push(["generated/support:support_widgetSession_mint", { org: organization.slug }]) // kit:support for (const [reference, input] of strangerCalls) { await assert.rejects( () => client.action(ref(reference), { input }), (error) => error?.data?.code === "not_found", `${reference}: a stranger to the organization gets the not-found of a missing scope`, ) } const callsAfter = await effectState() assert.equal(callsAfter.guides.length, callsBefore.guides.length, "no platform call for a stranger") assert.equal(callsAfter.support.length, callsBefore.support.length, "no platform call for a stranger") client.setAdminAuth(adminKey, identity) identity.conusaiOrgId = "another-org-id" client.setAdminAuth(adminKey, identity) await assert.rejects( () => client.query(ref("generated/branding:branding_brand_get"), { input: { org: organization.slug } }), /scope|organization|tenant|membership|not found/i, "branding must refuse the same mismatched organization identity as notes", ) await assert.rejects( () => client.query(ref("generated/notes:notes_note_list"), { input: scope }), /scope|organization|tenant|membership|not found/i, "a BFF identity pinned to another organization must not cross the tenant wall", ) // kit:guides:begin await assert.rejects( () => client.action(ref("generated/guides:guides_runtime_bootstrap"), { input: { org: organization.slug, path: "/notes" }, }), (error) => error?.data?.code === "not_found", "an effect action refuses an identity pinned to another organization", ) // kit:guides:end client.setAdminAuth(adminKey) const relayResult = await client.mutation(ref("relay:relayPass"), {}) assert.equal(relayResult.failed, 0, "outbox subscribers must complete without failure") const platformHeaders = { authorization: "Bearer dev-token", "x-conusai-organization": "fixture-org", } const auditHeaders = { ...platformHeaders, "x-conusai-organization": "e2e" } const stateUrl = new URL("/__e2e/state", apiUrl) const auditUrl = new URL("/_conusai/audit/events", apiUrl) let delivered = false let usageCount = 0 let auditCount = 0 for (let i = 0; i < 30; i += 1) { const stateResponse = await fetch(stateUrl, { headers: platformHeaders }) const state = await stateResponse.json() const auditResponse = await fetch(auditUrl, { headers: auditHeaders }) const audit = await auditResponse.json() usageCount = state.usage?.length ?? 0 auditCount = audit.items?.length ?? 0 if (state.usage?.some((event) => event.meter === "notes_created") && audit.items?.length) { delivered = true break } await new Promise((resolve) => setTimeout(resolve, 500)) } assert( delivered, `outbox relay must reach fake platform audit and usage sinks (usage=${usageCount}, audit=${auditCount})`, ) // The account module on Convex (convex/account.ts): export my data and delete my account through the // generated actions, with the ledger, the order and the partial-failure export of the Postgres profile. // Every step is checked against the real backend's rows and what the fake platform received. client.setAdminAuth(adminKey) const outage = async (prefix, down) => { const response = await fetch(new URL("/__e2e/outage", apiUrl), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ prefix, down }), }) assert.equal(response.status, 204, `the fake platform takes ${prefix} ${down ? "down" : "up"}`) } const storeRead = (method, ...args) => client.query(ref("account:storeRead"), { method, args }) const iso = (ms) => new Date(ms).toISOString() // The identity of each person: under the test issuer, or -- in a Better Auth app, whose deletions // refuse an identity under any other namespace (`provider-account:not_mapped`) and end the account // at Better Auth -- a real Better Auth account under the app's namespace. const accounts = { issuer, subjectFor: async (name) => `${subject}-${name}` } // kit:convex-better-auth:begin const betterAuthSite = process.env.SMOKE_BETTER_AUTH_SITE_URL const namespace = process.env.SMOKE_AUTH_IDENTITY_NAMESPACE const appOrigin = process.env.SMOKE_PUBLIC_URL if (!betterAuthSite || !namespace || !appOrigin) throw new Error("the Better Auth smoke needs its deployment's site URL") const betterAuth = (path, body) => fetch(`${betterAuthSite}/api/auth${path}`, { method: "POST", headers: { "content-type": "application/json", origin: appOrigin }, body: JSON.stringify(body), }) const password = `convex-smoke-${Date.now()}-password` const signUpAt = async (name, email) => { const signUp = await betterAuth("/sign-up/email", { name, email, password }) assert.equal(signUp.status, 200, "a person signs up at the Better Auth deployment") return (await signUp.json()).user.id } accounts.issuer = namespace accounts.subjectFor = (name) => signUpAt(name, `${subject}-${name}@example.test`) // kit:convex-better-auth:end /** A person signed in with a kit session row, and the BFF identity of that device. */ const signedIn = async (name) => { const personSubject = await accounts.subjectFor(name) client.setAdminAuth(adminKey) const person = await client.mutation(ref("system:appUserResolve"), { claims: { issuer: accounts.issuer, subject: personSubject, email: `${subject}-${name}@example.test`, displayName: name, locale: "en", }, create: true, }) const now = Date.now() const device = await client.mutation(ref("system:sessionInsert"), { input: { userId: person.id, issuer: accounts.issuer, subject: personSubject, handleHash: Array.from(globalThis.crypto.getRandomValues(new Uint8Array(32)), (b) => b.toString(16).padStart(2, "0"), ).join(""), tokens: "sealed-tokens-of-the-smoke", accessTokenExpiresAt: iso(now + 3_600_000), lastSeenAt: iso(now), absoluteExpiresAt: iso(now + 86_400_000), revokedAt: null, }, }) const browser = { issuer: "https://conusai.internal/bff", subject: person.id, tokenIdentifier: `https://conusai.internal/bff|${person.id}`, conusaiCredential: "interactive", conusaiSessionId: device.id, } return { person, device, browser, email: `${subject}-${name}@example.test` } } const ada = await signedIn("account") client.setAdminAuth(adminKey, ada.browser) const accountOrg = await client.mutation(ref("generated/tenancy:tenancy_org_create"), { input: { slug: "account-smoke", name: "Account smoke" }, }) // The support module knows the person once a widget session was minted for them. await client.action(ref("generated/support:support_widgetSession_mint"), { input: { org: accountOrg.slug } }) // kit:support const exportAs = () => client.action(ref("generated/account:account_data_export"), { input: {} }) // An API credential acting as the person is refused: the export is their own, in full. const { conusaiCredential: _interactive, conusaiSessionId: _session, ...apiIdentity } = ada.browser client.setAdminAuth(adminKey, apiIdentity) await assert.rejects(exportAs, (error) => error?.data?.code === "forbidden", "an API credential cannot export") client.setAdminAuth(adminKey, ada.browser) const exported = await exportAs() assert.equal( exported.complete, true, `every installed module exported its share: ${JSON.stringify(exported.failed)} ${JSON.stringify(Object.fromEntries(Object.entries(exported.sections).map(([key, section]) => [key, section.status === "failed" ? section : section.status])))}`, ) assert.equal(exported.subject, ada.person.id) assert.equal(exported.sections.account?.status, "ok") assert.equal(exported.sections.account.data.profile.email, ada.email, "this app's own rows, from the Convex tables") assert.deepEqual( exported.sections.account.data.organizations.map((org) => org.slug), ["account-smoke"], ) // A module whose service is down is a failed section, named, never left out. await outage("/v1/support/", true) // kit:support const partial = await exportAs() // kit:support:begin assert.equal(partial.complete, false) assert.deepEqual(partial.failed, ["support"]) assert.equal(partial.sections.support?.status, "failed") assert.equal(partial.sections.support.retryable, true) // kit:support:end assert.equal(partial.sections.account?.status, "ok") const status = await client.query(ref("generated/account:account_deletion_status"), {}) assert.equal(status.deletion, null) assert.equal(status.interactive, true) assert.equal(status.recentSignIn, true, "the sign-in of the session row the BFF named is recent") assert.deepEqual( status.onlyMemberOf.map((org) => org.slug), ["account-smoke"], ) const deleteAs = (confirm) => client.action(ref("generated/account:account_deletion_request"), { input: { confirm } }) await assert.rejects( () => deleteAs("someone-else@example.test"), (error) => error?.data?.code === "confirmation_mismatch", "a deletion is confirmed with the account's own address", ) assert.equal(await storeRead("deletion", ada.person.id), null, "a refused deletion writes no ledger row") const supportBefore = (await effectState()).support.length const held = await deleteAs(ada.email.toUpperCase()) const ledger = await storeRead("deletion", ada.person.id) // kit:support:begin assert.equal(held.state, "erasing", "the support module is down: the deletion holds") assert.deepEqual(held.pending, ["support"], `held at ${ledger.lastFailure} after ${JSON.stringify(ledger.steps)}`) assert.match(ledger.lastFailure, /^support:/, "the ledger names the module that held it, by its problem code") // The steps before the erasures completed, except the widget-session revocation the platform refused. assert(!ledger.steps.includes("service-sessions")) // kit:support:end assert( ["credentials", "identity"].every((step) => ledger.steps.includes(step)), "the ledger records the steps", ) assert.equal((await client.query(ref("system:sessionById"), { id: ada.device.id }))?.revokedAt === null, false) assert.equal(await storeRead("emailOf", ada.person.id), `${ada.person.id}@deleted.invalid`, "the identity is detached") assert.deepEqual(await storeRead("identities", ada.person.id), [], "no sign-in reaches the account") // kit:guides:begin assert( (await effectState()).guides.some((call) => call.route === "erase" && call.subject === ada.person.id), "the guides module erased its share by the subject while the user row still existed", ) // kit:guides:end client.setAdminAuth(adminKey) // kit:support:begin await assert.rejects( () => client.action(ref("account:sweep"), {}), (error) => error?.data?.code === "account_deletion_waiting", "the sweep fails while a deletion waits: the line an operator is alerted on", ) // kit:support:end await outage("/v1/support/", false) // kit:support await client.action(ref("account:sweep"), {}) const done = await storeRead("deletion", ada.person.id) assert.equal(done.state, "completed", "the sweep finished the deletion once the module was back") assert.deepEqual(done.pending, []) assert.equal(await storeRead("emailOf", ada.person.id), null, "the user row goes last") assert.equal(await client.query(ref("system:sessionById"), { id: ada.device.id }), null) // kit:support:begin const supportCalls = (await effectState()).support.slice(supportBefore) const revokedAt = supportCalls.findIndex((call) => call.route === "revoke" && call.body?.subject === ada.person.id) const erasedAt = supportCalls.findIndex((call) => call.route === "erase" && call.subject === ada.person.id) assert(revokedAt >= 0 && erasedAt > revokedAt, "the widget sessions were revoked before the contact was erased") assert(done.steps.includes("service-sessions"), "the resumption repeated the refused revocation") assert.deepEqual( [...done.erased].sort(), ["guides", "support"].filter((key) => status.modules.includes(key)), ) // kit:support:end // Asked again, the deletion that has completed answers its state: one deletion, however often asked. client.setAdminAuth(adminKey, ada.browser) assert.equal((await deleteAs(ada.email)).state, "completed") // The identity provider's account events (Better Auth's relay, with the admin key): a ban revokes // every credential and erases nothing; a deletion at the provider erases through the same ledger. const grace = await signedIn("provider-event") client.setAdminAuth(adminKey) await client.action(ref("account:providerEvent"), { reason: "user-disabled", userId: grace.person.id }) assert.notEqual((await client.query(ref("system:sessionById"), { id: grace.device.id }))?.revokedAt, null) assert.equal(await storeRead("deletion", grace.person.id), null, "a ban erases nothing") await client.action(ref("account:providerEvent"), { reason: "account-deleted", userId: grace.person.id }) await client.action(ref("account:providerEvent"), { reason: "account-deleted", userId: grace.person.id }) assert.equal((await storeRead("deletion", grace.person.id))?.state, "completed") assert.equal(await storeRead("emailOf", grace.person.id), null) // kit:support:begin // The sign-out revocation outbox (convex/account.ts): a widget session minted for one device's app // session is ended by that device's sign-out. The web's `account:revokeSession` records it in the // backend and answers while the support platform is down; the cron's `account:revocationSweep` // retries it once due, delivers it once, and a revocation older than 65 minutes lapses unasked. const revocationRead = (method, ...args) => client.query(ref("account:revocationRead"), { method, args }) const revocationWrite = (method, ...args) => client.mutation(ref("account:revocationWrite"), { method, args }) const pendingRevocation = async (session) => (await revocationRead("due", iso(Date.now() + 3_600_000), 100)).find((row) => row.session === session) const revokesOf = async (session) => (await effectState()).support.filter((call) => call.route === "revoke" && call.body?.identity_session === session) // A person under the test issuer: no Better Auth sign-up (its sign-up rate limit is for the rows below). client.setAdminAuth(adminKey) const leaver = await client.mutation(ref("system:appUserResolve"), { claims: { issuer, subject: `${subject}-sign-out`, email: `${subject}-sign-out@example.test`, displayName: "Sign-out", locale: "en", }, create: true, }) const leaving = { browser: { issuer: "https://conusai.internal/bff", subject: leaver.id, tokenIdentifier: `https://conusai.internal/bff|${leaver.id}`, }, } const deviceSession = `4:oidc:${leaver.id.length}:${leaver.id}` client.setAdminAuth(adminKey, leaving.browser) const leavingOrg = await client.mutation(ref("generated/tenancy:tenancy_org_create"), { input: { slug: "revocation-smoke", name: "Revocation smoke" }, }) client.setAdminAuth(adminKey, { ...leaving.browser, conusaiIdentitySession: deviceSession }) await client.action(ref("generated/support:support_widgetSession_mint"), { input: { org: leavingOrg.slug } }) assert.equal( (await effectState()).support.filter((call) => call.route === "mint" && call.body?.identity_session === deviceSession) .length, 1, "the widget session names the device's app session it was minted for", ) client.setAdminAuth(adminKey) await outage("/v1/support/", true) await client.action(ref("account:revokeSession"), { session: deviceSession }) const waiting = await pendingRevocation(deviceSession) assert.equal(waiting?.state, "pending", "the sign-out answered and the revocation is recorded in the backend") assert.deepEqual(waiting.pending, ["support"]) assert.equal(waiting.attempts, 1) assert.match(waiting.lastFailure, /^support:/) assert.deepEqual(await revokesOf(deviceSession), []) await outage("/v1/support/", false) // Not due yet (the row's 30 s backoff): the cron's run asks nobody. await client.action(ref("account:revocationSweep"), {}) assert.deepEqual(await revokesOf(deviceSession), [], "a retry waits for its backoff") // Due: the retry brought forward rather than waited out. await revocationWrite("change", waiting, { nextAttemptAt: iso(Date.now()) }) await client.action(ref("account:revocationSweep"), {}) const revocations = await revokesOf(deviceSession) assert.equal(revocations.length, 1, "the retry delivered the revocation") assert.equal(revocations[0].revoked, 1, "the device's widget session ended") assert.equal(await pendingRevocation(deviceSession), undefined) await client.action(ref("account:revocationSweep"), {}) await client.action(ref("account:revokeSession"), { session: deviceSession }) assert.equal((await revokesOf(deviceSession)).length, 1, "delivered once: a second sweep or notification asks nobody") const stale = `${deviceSession}:stale` const longAgo = iso(Date.now() - 66 * 60_000) await revocationWrite("record", { session: stale, modules: ["support"], at: longAgo, claimedUntil: longAgo }) await client.action(ref("account:revocationSweep"), {}) assert.equal(await pendingRevocation(stale), undefined, "a revocation older than 65 minutes lapses") assert.deepEqual(await revokesOf(stale), [], "and nobody is asked") // kit:support:end // kit:convex-better-auth:begin // With Better Auth, a deletion ends the person's account in the app's Better Auth deployment (a real // backend here), found by the identity mapping, before the identity is detached. const linusEmail = `${subject}-better-auth@example.test` const betterAuthUser = await signUpAt("Linus", linusEmail) client.setAdminAuth(adminKey) const linus = await client.mutation(ref("system:appUserResolve"), { claims: { issuer: namespace, subject: betterAuthUser, email: linusEmail, displayName: "Linus", locale: "en" }, create: true, }) client.setAdminAuth(adminKey, { issuer: "https://conusai.internal/bff", subject: linus.id, tokenIdentifier: `https://conusai.internal/bff|${linus.id}`, conusaiCredential: "interactive", // A Better Auth session has no kit row: the BFF names the sign-in time Better Auth reported. conusaiSignedInAt: iso(Date.now() - 1000), }) assert.equal((await deleteAs(linusEmail)).state, "completed") const linusLedger = await storeRead("deletion", linus.id) assert.equal(linusLedger.provider, "convex-better-auth", "the deletion recorded the provider it ends the account at") assert(linusLedger.steps.includes("provider-account")) assert.notEqual( (await betterAuth("/sign-in/email", { email: linusEmail, password })).status, 200, "the Better Auth account is gone: its password signs nobody in", ) // kit:convex-better-auth:end console.log( "local Convex app smoke green: deployed functions, BFF identity, tenancy, project, file Blob actions, OCR action, proposal approval, notes read/write, guides/support effect actions, account export/deletion/sweep/provider events, sign-out revocation outbox", )