Skip to content

Add exact-registry Pydantic AI policy toolset - #39

Merged
Deathcharge merged 2 commits into
mainfrom
codex/pydantic-ai-policy-toolset
Aug 2, 2026
Merged

Add exact-registry Pydantic AI policy toolset#39
Deathcharge merged 2 commits into
mainfrom
codex/pydantic-ai-policy-toolset

Conversation

@Deathcharge

@Deathcharge Deathcharge commented Aug 2, 2026

Copy link
Copy Markdown
Owner

What changed

  • add an optional exact-registry Pydantic AI 2.22.0 WrapperToolset adapter
  • route policy review through native deferred tools while requiring fingerprint-bound Samsarix evidence on resume
  • add atomic single-use approval consumption with a bounded process-local default and an application-owned durable-store protocol
  • add adversarial dependency-free tests, a serialized real Agent/TestModel contract, a runnable example, a hashed slim dependency lock, and a dedicated CI lane
  • document validation timing, reviewer and persistence ownership, sensitive metadata, and unsupported execution paths

Why

Pydantic AI exposes a public toolset execution seam and native deferred approvals, but its approval result is not itself an application authorization boundary. This adapter preserves the framework workflow while keeping current Samsarix policy, exact-call evidence, and one-time consumption authoritative.

Security and compatibility

  • the base package remains dependency-free and lazily imports Pydantic AI
  • the complete real ToolsetTool registry is exact-matched every run step
  • native boolean approval without Samsarix metadata fails closed
  • approved resume recomputes the call fingerprint, atomically consumes stored evidence, and re-enforces current policy with fresh actor/context facts
  • replaying an already-consumed approval fails closed
  • schema/custom argument validation occurs before the wrapper and is explicitly documented
  • other toolsets, direct calls, and provider-side tools are explicitly out of scope

Validation

  • Ruff format/check: pass
  • mypy: pass (38 source files)
  • pytest: 554 passed, 94.90% branch-aware coverage
  • exact real-agent contract: 5 passed on pydantic-ai-slim 2.22.0, including serialized history, denial, rejection, approval, and replay prevention
  • clean hash-locked slim environment and no-network example: pass
  • wheel/sdist build and Twine checks: pass
  • base wheel without optional dependencies: pass
  • optional wheel real-agent contract: pass
  • pip-audit exact optional lock: no known vulnerabilities
  • Bandit new adapter and source (documented B105 result-label false positive excluded): pass
  • workflow YAML and local Markdown links: pass

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added optional Pydantic AI integration with exact tool registration, allow/deny policies, deferred human review, approval resumption, and validated execution.
    • Added public API support, metadata, error reporting, and a no-network demonstration.
  • Documentation
    • Added setup, usage, security, architecture, API, adoption, and release guidance for the integration.
  • Tests
    • Added comprehensive coverage for approvals, rejections, validation, tampering, registry changes, and integration behavior.
    • Added locked CI contract and example checks.

Walkthrough

Changes

The pull request adds an optional Pydantic AI 2.22.0 adapter. It enforces exact tool registries, policy decisions, deferred approvals, resume-time evidence, and no-network contract coverage.

Pydantic AI adapter

Layer / File(s) Summary
Adapter implementation and public contract
src/samsarix_ethics/pydantic_ai.py, src/samsarix_ethics/__init__.py, pyproject.toml, requirements-pydantic-ai.txt
Adds the adapter, dependency pins, policy factory, integration error, metadata constants, and public exports.
Adapter behavior validation
tests/test_pydantic_ai.py, tests/test_public_api.py
Tests registry validation, allow/deny/review outcomes, approval evidence, context handling, argument validation, error propagation, and exports.
Real integration and deferred-review example
integration_tests/test_pydantic_ai_sdk.py, examples/pydantic_ai_policy_toolset_demo.py
Adds TestModel contract tests and a no-network example for approval, rejection, forged evidence, and registry drift.
Integration documentation and boundaries
README.md, docs/ADOPTION.md, docs/API.md, docs/ARCHITECTURE.md, docs/PYDANTIC_AI.md, SECURITY.md, docs/PRODUCTIZATION.md
Documents setup, exact toolset binding, enforcement, deferred approval, security controls, limitations, and integration status.
Locked CI and release contract
.github/workflows/ci.yml, CONTRIBUTING.md, RELEASING.md, CHANGELOG.md, ROADMAP.md
Adds pinned CI and attestation coverage, lock-file guidance, release checks, changelog details, and roadmap evidence.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: an exact-registry Pydantic AI policy toolset.
Description check ✅ Passed The description explains the changes, security and compatibility effects, verification results, and documentation updates.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pydantic-ai-policy-toolset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/ARCHITECTURE.md`:
- Around line 23-24: Update the architecture diagram text to use the public
Pydantic AI type name ToolsetTool instead of ToolsetTools, preserving the
existing binding-flow description.

In `@docs/PYDANTIC_AI.md`:
- Around line 96-99: Update the Pydantic AI resume guidance around the
fingerprint description to include the context-contract version and tool context
version as additional fingerprint-bound fields. Specify that both values must be
stored with the pending call and supplied during resume alongside the existing
metadata.
- Around line 25-49: Update the Pydantic AI example’s Agent declaration to
include DeferredToolRequests in output_type, while preserving the existing
toolsets and deps_type configuration so deferred requests are returned through
first.output rather than raising DeferredToolRequestsPending.

In `@integration_tests/test_pydantic_ai_sdk.py`:
- Around line 89-173: Extend the real-agent integration coverage with a
deny-policy test that triggers ToolCallDeniedError through the agent loop and
verifies the tool was not called, then update the approval test flow to
round-trip first.all_messages() through ModelMessagesTypeAdapter before resuming
and confirm the approval still executes. Anchor both changes to the existing
_agent, test_real_agent_approves_exact_deferred_call_once, and
test_real_agent_rejection_never_calls_tool helpers while preserving current
approval and rejection assertions.

In `@requirements-pydantic-ai.txt`:
- Around line 1-2: Regenerate requirements-pydantic-ai.txt from the
pydantic-ai-slim==2.22.0 input using pip-compile --generate-hashes, including
all resolved transitive dependencies and --hash= entries. Update the
corresponding installation flow to pass --require-hashes when consuming this
requirements file.

In `@src/samsarix_ethics/pydantic_ai.py`:
- Around line 207-227: Update the approval enforcement flow around
_verify_approval so stored approval evidence cannot be reused for a later
enforcement run: bind each ToolCallApproval to the current review run or an
expiry, or track and atomically consume each approval as single-use before
returning it. Preserve the existing metadata, approved-state, tool_call_id, and
fingerprint validations, and reject approvals lacking a valid current-run or
single-use binding.
- Around line 359-369: Normalize the pending call arguments in the metadata
validation block before comparing them with the review payload. When the
deferred call exposes JSON-string arguments, use ToolCallPart.args_as_dict()
rather than raw call.args, while preserving already-structured arguments and the
existing call ID and tool name checks.

In `@tests/test_pydantic_ai.py`:
- Around line 67-72: Add a parametrized test case in the
_DeferredCall/build_results test coverage that supplies args as JSON text
representing the same mapping currently used for dict arguments. Invoke
build_results with this deferred call and assert the intended result for pending
ToolCallPart arguments, while retaining the existing dict-args case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 21267d33-4231-414a-8640-6c03715ecd62

📥 Commits

Reviewing files that changed from the base of the PR and between 47f59c9 and 79b0d85.

⛔ Files ignored due to path filters (1)
  • requirements-pydantic-ai.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • RELEASING.md
  • ROADMAP.md
  • SECURITY.md
  • docs/ADOPTION.md
  • docs/API.md
  • docs/ARCHITECTURE.md
  • docs/PRODUCTIZATION.md
  • docs/PYDANTIC_AI.md
  • examples/pydantic_ai_policy_toolset_demo.py
  • integration_tests/test_pydantic_ai_sdk.py
  • pyproject.toml
  • requirements-pydantic-ai.txt
  • src/samsarix_ethics/__init__.py
  • src/samsarix_ethics/pydantic_ai.py
  • tests/test_public_api.py
  • tests/test_pydantic_ai.py

Comment thread docs/ARCHITECTURE.md Outdated
Comment thread docs/PYDANTIC_AI.md Outdated
Comment thread docs/PYDANTIC_AI.md Outdated
Comment on lines +96 to +99
On approved resume, the adapter recomputes the fingerprint from the current name, validated
arguments, trusted catalog capabilities, and freshly supplied actor. It then re-evaluates the
current policy and context facts. Changed arguments or actor, a different call ID, missing or
malformed evidence, a replay against another call, or a current deny/review never invokes the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^docs/(ARCHITECTURE|PYDANTIC_AI)\.md$|pydantic|fingerprint|tool_call_id|context-contract|catalog capabilities)' || true

echo
echo "== docs/PYDANTIC_AI.md relevant section =="
if [ -f docs/PYDANTIC_AI.md ]; then
  nl -ba docs/PYDANTIC_AI.md | sed -n '70,120p'
else
  echo "missing docs/PYDANTIC_AI.md"
fi

echo
echo "== docs/ARCHITECTURE.md fingerprint references =="
if [ -f docs/ARCHITECTURE.md ]; then
  rg -n "fingerprint|tool_call_id|context-contract|context contract|capabilities|replay|evidence" docs/ARCHITECTURE.md || true
  echo
  nl -ba docs/ARCHITECTURE.md | sed -n '1,220p' | rg -n "fingerprint|tool_call_id|context-contract|context contract|capabilities|replay|evidence" -C 3 || true
else
  echo "missing docs/ARCHITECTURE.md"
fi

echo
echo "== code search fingerprint-bound terms =="
rg -n "fingerprint|tool_call_id|context-contract|context_contract|contextContract|catalog capabilities|replay|evidence" -S . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 575


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs/PYDANTIC_AI.md relevant section =="
if [ -f docs/PYDANTIC_AI.md ]; then
  awk 'NR>=70 && NR<=120 {printf "%7d\t%s\n", NR, $0}' docs/PYDANTIC_AI.md
else
  echo "missing docs/PYDANTIC_AI.md"
fi

echo
echo "== docs/ARCHITECTURE.md fingerprint references =="
if [ -f docs/ARCHITECTURE.md ]; then
  awk '/fingerprint|tool_call_id|context-contract|context contract|capabilities|replay|evidence/ {printf "%7d\t%s\n", NR, $0}' docs/ARCHITECTURE.md
else
  echo "missing docs/ARCHITECTURE.md"
fi

echo
echo "== code search fingerprint-bound terms =="
rg -n "fingerprint|tool_call_id|context-contract|context_contract|contextContract|catalog capabilities|replay|evidence" -S . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300

echo
echo "== pydantic fingerprints function outline/implementation =="
if [ -f src/samsarix_ethics/pydantic_ai.py ]; then
  wc -l src/samsarix_ethics/pydantic_ai.py
  rg -n "fingerprint|fingerprint|tool_call_id|context-contract|context_contract|contextContract|catalog_capabilities|capabilities|replay|evidence|evidence" src/samsarix_ethics/pydantic_ai.py -C 4
fi

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 44185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pydantic fingerprint definitions/usages =="
rg -n "def fingerprint|fingerprint_tool_call|fingerprint\(" src tests | head -200

echo
echo "== approval module =="
fd -i 'approval.*\.(py|json)$' . -t f -d 5 | sort | xargs -r -I{} sh -c 'echo "--- {}"; wc -l "{}"; rg -n "fingerprint|tool_call|context|version|catalog" "{}" -C 3 || true'

echo
echo "== relevant pydantic_ai.py sections =="
awk 'NR>=120 && NR<=285 || NR>=430 && NR<=540 {printf "%7d\t%s\n", NR, $0}' src/samsarix_ethics/pydantic_ai.py

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 25188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== bound tool gateway fingerprint implementation =="
rg -n "def fingerprint|context_contract|tool_context_version|TOOL_CALL_FINGERPRINT_VERSION|_fingerprint_prepared_tool_call" src/samsarix_ethics/gate.py src/samsarix_ethics -C 4

echo
echo "== pydantic_ai py tests around deferred approvals =="
awk 'NR>=300 && NR<=370 {printf "%7d\t%s\n", NR, $0}' tests/test_pydantic_ai.py

echo
echo "== standalone semantic probe for documented omitted fields =="
python3 - <<'PY'
from pathlib import Path
import re

doc = Path("docs/PYDANTIC_AI.md").read_text()
section_re = re.compile(r"(On approved resume.*?tool\.)(?:\n|$)", re.S)
m = section_re.search(doc)
print("found_resume_paragraph=", bool(m))
if m:
    paragraph = m.group(1)
    for field in ["tool name", "validated arguments", "trusted catalog capabilities", "freshly supplied actor", "tool_call_id", "context-contract version", "call ID"]:
        print(f"{field}: {field in paragraph}")

arch = Path("docs/ARCHITECTURE.md").read_text()
for field in ["tool_call_id", "framework call ID", "context-contract version", "context contract"]:
    print(f"arch_mentions_{field}: {bool(re.search(r'[^A-Za-z_]' + re.escape(field) + r'[^A-Za-z_]', arch))}")
PY

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 50390


Document the remaining fingerprint-bound fields.

The Pydantic AI resume guidance lists tool name, arguments, catalog capabilities, actor, and call ID; add the context-contract version and tool context version that the v1 fingerprint includes. Store both values with the pending call so resume metadata is not rejected for an omitted, binding field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/PYDANTIC_AI.md` around lines 96 - 99, Update the Pydantic AI resume
guidance around the fingerprint description to include the context-contract
version and tool context version as additional fingerprint-bound fields. Specify
that both values must be stored with the pending call and supplied during resume
alongside the existing metadata.

Comment thread integration_tests/test_pydantic_ai_sdk.py
Comment on lines +1 to +2
# Exact optional Pydantic AI contract used by CI and the no-network example.
pydantic-ai-slim==2.22.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find every consumer of the optional Pydantic AI requirements file and check for hash enforcement.
set -euo pipefail

fd -t f 'requirements-pydantic-ai.txt'
rg -n -C 5 'requirements-pydantic-ai' --glob '!requirements-pydantic-ai.txt'
rg -n 'require-hashes|generate-hashes|--hash=' || echo "no hash enforcement found"

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 200


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'FILE:\n'
wc -l requirements-pydantic-ai.txt
cat -n requirements-pydantic-ai.txt
printf '\nREPO-WIDE MATCHES EXCLUDING FILE:\n'
rg -n -C 5 'requirements-pydantic-ai|pydantic-ai-slim|require-hashes|generate-hashes|--hash=' --glob '!requirements-pydantic-ai.txt' || true
printf '\nALL HASH-RELATED MATCHES:\n'
rg -n --line-number --no-heading 'require[-_]hashes|generate[-_]hashes|--hash=' . || true

Repository: Deathcharge/samsarix-agent-ethics

Length of output: 492


Generate the hashed Pydantic AI requirements file.

requirements-pydantic-ai.txt currently pins only pydantic-ai-slim==2.22.0 and contains no --hash= entries or transitive pins, so installing from it still resolves dependencies freely. Generate the file with pip-compile --generate-hashes and install with --require-hashes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements-pydantic-ai.txt` around lines 1 - 2, Regenerate
requirements-pydantic-ai.txt from the pydantic-ai-slim==2.22.0 input using
pip-compile --generate-hashes, including all resolved transitive dependencies
and --hash= entries. Update the corresponding installation flow to pass
--require-hashes when consuming this requirements file.

Comment thread src/samsarix_ethics/pydantic_ai.py
Comment thread src/samsarix_ethics/pydantic_ai.py
Comment thread tests/test_pydantic_ai.py
@Deathcharge
Deathcharge merged commit b7c00b1 into main Aug 2, 2026
10 checks passed
@Deathcharge
Deathcharge deleted the codex/pydantic-ai-policy-toolset branch August 2, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant