Skip to content

fix(decisioning): re-validate params through platform method's stricter subclass annotation#597

Merged
bokelley merged 1 commit intomainfrom
claude/issue-596-subclass-params-validation
May 9, 2026
Merged

fix(decisioning): re-validate params through platform method's stricter subclass annotation#597
bokelley merged 1 commit intomainfrom
claude/issue-596-subclass-params-validation

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 8, 2026

Closes #596

Summary

_invoke_platform_method received an already-deserialized Pydantic instance typed as the library's base request (e.g. GetProductsRequest with extra="allow") and passed it directly to the platform method without consulting the platform method's own annotation. A DecisioningPlatform subclass that annotated its method with a stricter subclass (e.g. extra="forbid", custom @field_validator) had those rules silently bypassed at the dispatch boundary.

The fix adds _coerce_params_to_platform_type, called from _invoke_platform_method when arg_projector is None. It inspects the platform method's first non-self/non-ctx/non-context typed parameter annotation; when that annotation is a strict Pydantic subclass of the current params type, it re-validates via model_dump(mode="python") → model_validate(). A ValidationError from re-validation surfaces as INVALID_REQUEST / correctable (not INTERNAL_ERROR). The on_failure hook is called before re-raising so proposal-flow callers can release any consumption reservation taken before _invoke_platform_method.

Guard conditions that skip coercion: same type (no-op), unrelated annotation, VAR_POSITIONAL/VAR_KEYWORD parameters, get_type_hints() resolution failure (graceful degradation to dict pass-through).

What was tested

  • pytest tests/test_decisioning_dispatch.py51 passed (6 pre-existing + 8 new coerce tests including: extra="forbid" rejection, same-type no-op, valid subclass passthrough, unrelated annotation no-op, param-name agnosticism, get_type_hints failure graceful degradation, on_failure hook fires on coercion failure, VAR_POSITIONAL guard)
  • Full non-integration suite (excluding pre-existing network flake): 4153 passed, 0 failed
  • mypy src/adcp/decisioning/dispatch.py --ignore-missing-imports — clean
  • ruff check src/adcp/decisioning/dispatch.py — clean

Pre-PR review:

  • code-reviewer: approved — 2 blockers found and fixed (on_failure not called before try block; VAR_POSITIONAL not guarded); nits surfaced in PR body; no remaining blockers
  • dx-expert: approved — 1 blocker found and fixed (same on_failure/reservation-leak issue); doc paragraph for DecisioningPlatform subclass re-validation added to handler-authoring.md; nits: AGENTS.md callout, unrelated-annotation no-op docs note (acknowledged, not blocking)

Known nit (not fixed): The handler-authoring.md "Custom models too" paragraph could benefit from a code example showing the full DecisioningPlatform subclass pattern with extra="forbid" and @field_validator. Noted for a follow-up docs PR.

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_01GUtwfX2NfFEVUuZZBCA5qP


Generated by Claude Code

@bokelley bokelley force-pushed the claude/issue-596-subclass-params-validation branch from 84ffab2 to 73e853a Compare May 9, 2026 08:33
@bokelley bokelley marked this pull request as ready for review May 9, 2026 08:33
@bokelley bokelley closed this May 9, 2026
@bokelley bokelley force-pushed the claude/issue-596-subclass-params-validation branch from 73e853a to 37d2cda Compare May 9, 2026 08:40
…er subclass annotation

Closes #596.

_invoke_platform_method received an already-deserialized Pydantic
instance typed as the library's base request (e.g. GetProductsRequest
with extra="allow") and passed it directly to the platform method
without consulting the platform method's own annotation. A
DecisioningPlatform subclass that annotated its method with a stricter
subclass (e.g. extra="forbid", custom @field_validator) had those rules
silently bypassed at the dispatch boundary.

The fix adds _coerce_params_to_platform_type, called from
_invoke_platform_method when arg_projector is None. It inspects the
platform method's first non-self/non-ctx/non-context typed parameter
annotation; when that annotation is a strict Pydantic subclass of the
current params type, it re-validates via model_dump(mode="python") →
model_validate(). A ValidationError from re-validation surfaces as
INVALID_REQUEST / correctable (not INTERNAL_ERROR). The on_failure hook
is called before re-raising so proposal-flow callers can release any
consumption reservation taken before _invoke_platform_method.

Guard conditions that skip coercion: same type (no-op), unrelated
annotation, VAR_POSITIONAL/VAR_KEYWORD parameters, get_type_hints()
resolution failure (graceful degradation to dict pass-through).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley reopened this May 9, 2026
@bokelley bokelley merged commit 3d269f5 into main May 9, 2026
16 checks passed
@bokelley bokelley deleted the claude/issue-596-subclass-params-validation branch May 9, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_resolve_params_pydantic_model resolves base-class annotation, breaking subclass-extended request validation

1 participant