convex-verify
get-convex/agent-skills
Verify Convex features work by seeding data, driving as multiple users, and asserting authorization and behavior.
What is convex-verify?
This skill proves a Convex query, mutation, or action works correctly by setting up realistic test data, calling the feature as different identities (owner, other user, unauthenticated), and asserting both positive behavior and negative authorization cases. Use it after building a feature to catch authorization defects before deployment.
- Seed realistic test data through the app's own functions or direct database inserts
- Drive features as multiple identities (owner, other authenticated user, unauthenticated) using the app's real subject/tokenIdentifier shape
- Assert positive behavior: owner gets expected rows or mutation produces expected changes
- Assert negative behavior: wrong caller is refused with authorization error, unauthenticated caller is blocked where required
- Verify data-scope: queries return only the caller's rows, never other users' data
- Run tests in-process with convex-test (no deployment needed) and report findings including any authorization holes discovered
How to install convex-verify
npx skills add https://github.com/get-convex/agent-skills --skill convex-verify- convex-test and vitest as dev dependencies
- vitest.config.ts configured with test.environment: 'edge-runtime' and server.deps.inline: ['convex-test']
- @edge-runtime/vm installed
- Existing Convex project with schema and auth setup
How to use convex-verify
- 1.Identify the specific query/mutation/action to verify and its intended behavior (who should be allowed, what data should return, what should change)
- 2.Ensure vitest.config.ts is set up with edge-runtime environment and convex-test inlined; install @edge-runtime/vm
- 3.Seed test data: create the caller's own rows and a second user's rows using the app's public functions where possible, or t.run(ctx => ctx.db.insert(...)) for fixtures
- 4.Drive the feature as different identities: call as (a) the legitimate owner, (b) a different authenticated user, (c) unauthenticated, using t.withIdentity({subject, tokenIdentifier, ...})
- 5.Assert positive cases: owner receives expected rows or mutation produces expected changes
- 6.Assert negative cases: different user calling the function is refused with authorization error; unauthenticated caller is refused where required; queries return only the caller's rows
- 7.Run npx vitest run and report what was proven (each passing assertion) and any failures, which indicate real authorization holes
- 8.Emit findings to the bus for any failed assertions (authz/correctness class, probe-result evidence) rather than weakening tests to pass
Use cases
- Verify a new query only returns the authenticated user's own data, not other users' rows
- Test that a mutation rejects calls from users who don't own the resource being modified
- Confirm an unauthenticated caller is properly denied access to protected functions
- Validate that a feature's authorization logic matches its intended contract before shipping
- Catch authorization defects in a 30-app corpus's most common real bug: wrong-user access not denied
- Convex backend developers building queries, mutations, or actions
- Teams implementing authorization and data-scoping rules
- Anyone verifying feature correctness before deployment
convex-verify FAQ
No. convex-test runs in-process with vitest, so you get immediate feedback without deployment.
The negative assertions — proving that a wrong caller (different user, unauthenticated) is actually refused — because those catch the #1 real authorization defect that happy-path tests miss.
Do not weaken the test to make it pass. A failing negative assertion is a real defect. Hand the fix to convex-authz or convex-expert; the test is correct.
Create vitest.config.ts with test.environment: 'edge-runtime' and server.deps.inline: ['convex-test']. Without this, convex-test fails at runtime with 'import.meta.glob is not a function'.
Prefer the app's own functions (queries/mutations) so seeding exercises the same validators a real user would. Fall back to t.run(ctx => ctx.db.insert(...)) only for fixtures the public API cannot create.
Full instructions (SKILL.md)
Source of truth, from get-convex/agent-skills.
name: convex-verify description: "Prove a Convex feature works — seed, drive as multiple mocked users via convex-test, assert behavior including the negative authz cases (wrong user refused, data-scope enforced)."
<!-- GENERATED from convex-agents content/capabilities/convex-verify.json — do not edit by hand. -->Prove a feature works — seed, drive, assert
A green typecheck proves the code parses; it does not prove a non-owner is actually denied, that a query returns the right rows, or that a mutation has the effect it claims. This capability closes that gap with the loop the whole field is missing: seed → drive → assert, run in-process with convex-test so it needs no deployment. Its highest-value assertions are the NEGATIVE ones — the caller who should be refused — because those are exactly the authz defects the 30-app corpus shows are the #1 real bug and the ones a happy-path demo never catches.
Workflow
- IDENTIFY the feature to prove: the specific exported query/mutation/action (or a small set) the user just built/changed, and its intended behavior — who should be allowed, what data should come back, what a mutation should change. If the intent is unstated, ask one focused question rather than guessing the contract.
- SET UP
convex-test: ensureconvex-test+vitestare dev deps AND avitest.config.tssetstest.environment: "edge-runtime"withserver.deps.inline: ["convex-test"]— WITHOUT that config,convexTest(schema)fails at runtime withimport.meta.glob is not a function(verified). Also install@edge-runtime/vm. ThenconvexTest(schema)gives athandle. Reuse the project's existing test setup if present (compose with thetestcapability, don't fork it). - SEED realistic data through the app's OWN functions where possible (so the seed exercises the same validators/mutations a real user would), falling back to
t.run(async (ctx) => ctx.db.insert(...))for fixtures the public API can't create. Seed at least: the caller's own rows AND a second user's rows, so cross-user access is testable. - DRIVE the feature as DIFFERENT identities with
t.withIdentity({ subject, tokenIdentifier, ... }): call the function as (a) the legitimate owner, (b) a different authenticated user, and (c) unauthenticated (twith no identity). Use the real identity shape the app's auth uses (subject/tokenIdentifier), matching how ownership is resolved. - ASSERT behavior — POSITIVE and NEGATIVE:
- positive: the owner gets the expected rows / the mutation made the expected change (
expect(await t.withIdentity(owner).query(api.x.y, args)).toEqual(...)). - NEGATIVE (the load-bearing half): a different user calling the same function is REFUSED —
await expect(t.withIdentity(other).mutation(api.x.cancel, {id})).rejects.toThrow(/forbidden|not authorized|403/)— and an unauthenticated caller is refused where auth is required. A feature is not proven until the wrong caller is shown to be blocked. - data-scope: a list/query returns ONLY the caller's rows, never the second user's (assert the second user's row is absent).
- positive: the owner gets the expected rows / the mutation made the expected change (
- RUN the tests (
npx vitest run) and report: what was proven (each positive + negative assertion that passed), and — critically — any assertion that FAILED, because a failed negative assertion is a real authz hole found before ship. Emit findings on the bus (specs/finding.schema.json, class authz/correctness, evidence kind probe-result with the exact failing call) for anything that didn't behave. - Do NOT weaken a test to make it pass: if the owner-only query returns another user's row, the FIX is in the function (hand to convex-authz), not in the assertion. A test changed until it's green proves nothing.
Rules
- Prove behavior, not compilation: every verification includes at least one NEGATIVE assertion (a caller who should be refused is refused) — the happy path alone is not proof.
- Drive the feature as multiple identities with t.withIdentity (owner, other user, unauthenticated) using the app's real subject/tokenIdentifier shape.
- Seed both the caller's rows AND a second user's rows so cross-user access and data-scope are actually testable.
- A vitest.config.ts with environment 'edge-runtime' + convex-test inlined is REQUIRED for convex-test to run (import.meta.glob needs it); author it, don't just author the test file.
- Run in-process with convex-test — no deployment needed; compose with the
testcapability's setup rather than forking it. - Never weaken an assertion to make it pass: a failing negative test is a real defect → hand the fix to convex-authz/convex-expert, don't edit the test until it's green.
- Emit a bus finding for any assertion that failed (authz/correctness, evidence: the failing probe call) so a composite pass or self-heal can pick it up.
- This drives a SPECIFIC built feature; a request to set up a test framework generally is the
testcapability.
Related skills
More from get-convex/agent-skills and the wider catalog.

convex
All-TypeScript reactive backend platform with database, auth, realtime sync, and AI agent components—prototype to production without rewrite.

convex-add
Add backend capabilities (billing, auth, search, crons) to your Convex app with always-current procedures.

convex-advisor
Analyze Convex deployment health: root-cause read limits, OCC contention, and performance issues with evidence-backed fixes.

convex-agent
Add durable AI agent and RAG backend to Convex with built-in message history, tool calls, and vector search.

cargo
Router and entry point for the Cargo CLI skill bundle — load first for multi-domain tasks or when unsure which skill to use.

cargo-ai
Build and configure AI agents in Cargo with prompts, RAG knowledge, MCP servers, and memory management.