convex-verify

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- GENERATED from convex-agents content/capabilities/convex-verify.json — do not edit by hand. -->
<!-- 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.
通过类型检查可以证明代码能正常解析,但无法证明非所有者确实被拒绝访问、查询返回正确的行,或者变更操作达到了预期效果。该功能通过业内缺失的完整流程填补了这一空白:初始化(seed)→ 操作(drive)→ 断言(assert),使用
convex-test
在进程内运行,无需部署。其中价值最高的断言是负面断言——即验证应被拒绝的调用者——因为根据30个应用的案例分析,这类权限验证缺陷是排名第一的实际bug,而常规的正向演示永远无法发现它们。

Workflow

工作流程

  1. 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.
  2. SET UP
    convex-test
    : ensure
    convex-test
    +
    vitest
    are dev deps AND a
    vitest.config.ts
    sets
    test.environment: "edge-runtime"
    with
    server.deps.inline: ["convex-test"]
    — WITHOUT that config,
    convexTest(schema)
    fails at runtime with
    import.meta.glob is not a function
    (verified). Also install
    @edge-runtime/vm
    . Then
    convexTest(schema)
    gives a
    t
    handle. Reuse the project's existing test setup if present (compose with the
    test
    capability, don't fork it).
  3. 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.
  4. 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 (
    t
    with no identity). Use the real identity shape the app's auth uses (subject/tokenIdentifier), matching how ownership is resolved.
  5. 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).
  6. 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.
  7. 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.
  1. 确定待验证的功能:明确用户刚开发/修改的特定导出查询/变更/操作(或一小组),以及其预期行为——允许哪些用户访问、应返回哪些数据、变更操作应产生什么效果。如果预期行为未明确说明,应提出一个针对性问题,而非猜测约定。
  2. 配置
    convex-test
    :确保
    convex-test
    vitest
    已作为开发依赖安装,且
    vitest.config.ts
    中设置
    test.environment: "edge-runtime"
    并配置
    server.deps.inline: ["convex-test"]
    ——如果没有此配置,
    convexTest(schema)
    在运行时会报错
    import.meta.glob is not a function
    (已验证)。同时安装
    @edge-runtime/vm
    。之后
    convexTest(schema)
    会返回一个
    t
    句柄。如果项目已有测试配置,请复用(与
    test
    功能组合,不要单独搭建)。
  3. 初始化真实数据:尽可能通过应用自身的函数初始化数据(这样初始化过程会像真实用户一样触发相同的验证器/变更操作),如果公共API无法创建某些测试数据,则使用
    t.run(async (ctx) => ctx.db.insert(...))
    作为备选。至少初始化:调用者自己的数据行,以及另一个用户的数据行,以便测试跨用户访问。
  4. 以不同身份操作功能:使用
    t.withIdentity({ subject, tokenIdentifier, ... })
    以不同身份调用功能:(a) 合法所有者,(b) 另一个已认证用户,(c) 未认证用户(不带身份的
    t
    )。使用应用认证系统实际使用的身份格式(subject/tokenIdentifier),与权限解析方式保持一致。
  5. 验证行为——正向与负向
    • 正向验证:所有者能获取预期的数据行 / 变更操作产生了预期效果(
      expect(await t.withIdentity(owner).query(api.x.y, args)).toEqual(...)
      )。
    • 负向验证(核心部分):其他用户调用同一功能时被拒绝——
      await expect(t.withIdentity(other).mutation(api.x.cancel, {id})).rejects.toThrow(/forbidden|not authorized|403/)
      ——且在需要认证的场景下,未认证调用者被拒绝。只有当错误调用者被阻止时,才能证明功能正常。
    • 数据范围验证:列表/查询仅返回调用者自己的数据行,绝不会返回第二个用户的数据行(断言第二个用户的数据行不存在)。
  6. 运行测试
    npx vitest run
    )并报告:已验证的内容(每个通过的正向和负向断言),以及——关键是——任何失败的断言,因为失败的负向断言意味着在发布前发现了真实的权限漏洞。对于不符合预期的行为,在总线(specs/finding.schema.json)上发布发现结果,分类为authz/correctness,证据类型为probe-result,并附上具体的失败调用。
  7. 不要为了让测试通过而弱化测试:如果仅限所有者访问的查询返回了其他用户的数据行,修复应在函数中进行(转交convex-authz处理),而非修改断言。修改到通过的测试无法证明任何内容。

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
    test
    capability'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
    test
    capability.
  • 验证行为而非编译结果:每次验证至少包含一个负向断言(应被拒绝的调用者确实被拒绝)——仅正向路径不足以证明功能正常。
  • 使用
    t.withIdentity
    以多个身份(所有者、其他用户、未认证用户)操作功能,身份格式需与应用的真实subject/tokenIdentifier一致。
  • 同时初始化调用者和另一个用户的数据行,以便实际测试跨用户访问和数据范围限制。
  • 必须配置
    vitest.config.ts
    ,设置环境为'edge-runtime'并内联convex-test,否则convex-test无法运行(需要import.meta.glob);需编写该配置文件,而非仅编写测试文件。
  • 使用convex-test在进程内运行——无需部署;与
    test
    功能的配置组合使用,不要单独搭建。
  • 绝不要为了让测试通过而弱化断言:失败的负向测试意味着真实缺陷→将修复工作转交convex-authz/convex-expert,不要修改测试直到问题解决。
  • 对于任何失败的断言,在总线发布发现结果(authz/correctness,证据:失败的探测调用),以便组合验证或自我修复流程能处理。
  • 该功能针对特定已开发的功能;如果是请求搭建测试框架,属于
    test
    功能的范畴。