From 8360296c0d6e7a4db3dee83c7d339f973426f39b Mon Sep 17 00:00:00 2001 From: bradAGI <46579244+bradAGI@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:17:06 -0400 Subject: [PATCH] test(rules): mirror and cover PYD-013 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engine half of a coordinated pair with trustabl/trustabl-rules#107, on a branch of the same name so the rules-sync job resolves the matching pack rather than main. Neither half should merge alone — check-rules-sync.sh fails if they do. Mirrors pydantic_ai/tool_definition.yaml into testdata/rules-fixture and adds cases to policyRuleCases, as TestPolicyRules_AllRulesCovered requires. Three cases: the generic name, the verb-object remediation, and process_invoice_batch — a name that merely contains a listed word and must stay silent. The third pins that name_in matches the whole name rather than a substring, which is the plausible regression and the one that would otherwise flood well-named tools with findings. --- internal/rules/policies_test.go | 16 +++++++++- .../pydantic_ai/tool_definition.yaml | 31 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/internal/rules/policies_test.go b/internal/rules/policies_test.go index 65af6c0a..7aa0317f 100644 --- a/internal/rules/policies_test.go +++ b/internal/rules/policies_test.go @@ -1986,6 +1986,21 @@ def fetch_data(x: str) -> dict: return {} `, toolConfig: nil, wantFires: false}, + {name: "PYD-013 fires on a generic tool name", ruleID: "PYD-013", kind: models.KindPydanticAITool, src: ` +def process(payload: str) -> str: + """Handle an incoming payload and return the result of handling it.""" + return payload +`, wantFires: true}, + {name: "PYD-013 silent on a verb-object tool name", ruleID: "PYD-013", kind: models.KindPydanticAITool, src: ` +def summarize_invoice(invoice_id: str) -> str: + """Summarize one invoice by its identifier and return the summary text.""" + return invoice_id +`, wantFires: false}, + {name: "PYD-013 silent on a name merely containing a listed word", ruleID: "PYD-013", kind: models.KindPydanticAITool, src: ` +def process_invoice_batch(batch_id: str) -> str: + """Process one batch of invoices and return a per-invoice result summary.""" + return batch_id +`, wantFires: false}, } // policyRepoRuleCases covers repo-scoped rules. @@ -2246,7 +2261,6 @@ var policyRepoRuleCases = []policyRepoCase{ }, models.RepoInventory{SDKsDetected: []models.SDK{models.SDKOpenAIAgents}}, false}, - } // optionsWithPermissionMode builds a ClaudeAgentOptionsDef whose captured diff --git a/testdata/rules-fixture/pydantic_ai/tool_definition.yaml b/testdata/rules-fixture/pydantic_ai/tool_definition.yaml index 2fb60116..91e0ee4b 100644 --- a/testdata/rules-fixture/pydantic_ai/tool_definition.yaml +++ b/testdata/rules-fixture/pydantic_ai/tool_definition.yaml @@ -62,3 +62,34 @@ rules: list[str], a Pydantic model, etc.). Pydantic AI propagates these annotations into the schema the model sees, so the model emits correctly-shaped arguments and validation stops rejecting them. + + - id: PYD-013 + title: Ambiguous Pydantic AI tool name + severity: low + confidence: 0.9 + language: python + applies_to: + - pydantic_ai_tool + scope: tool + match: + name_in: + - process + - handle + - run + - do + - execute + - perform + - work + - go + - thing + - stuff + explanation: > + The tool name is a generic verb that says nothing about what the tool + acts on. The name sits directly beside the description in what the model + sees, so it is half the selection signal, and a name like process or + handle spends that half on nothing — the model either calls the tool for + the wrong job or passes over it entirely. Pydantic AI derives the tool name from the function name unless one is passed explicitly, so a generically named function silently becomes a generically named tool. + fix: > + Rename to a verb-object form that names the thing acted on: + summarize_invoice, refund_charge, fetch_order_status. Keep the + description for the detail and let the name carry the subject.