test: add golden wire-contract fixtures and breaking-change CI guard - #332
Conversation
…301) Pins the current http_contract: 1 / mcp_contract: 1 wire shapes as machine-readable fixtures under tests/fixtures/wire/ (ADR 0005 §9), including #216's evidence-assembly package. Rust generates and verifies them against a live server (tests/http_api/contract.rs); Python and TypeScript read the same files (sdk/python/tests/unit/ test_wire_contract.py, sdk/typescript/tests/unit/wire-contract.test.ts). New sdk/spec/check_contract.py classifies fixture changes against a base ref per ADR 0005 §4 and fails CI (new contract-guard job) unless a breaking change lands beside a matching HTTP_CONTRACT/MCP_CONTRACT bump. Claude-Session: https://claude.ai/code/session_011KBpNVCCcdTUtCuPdpXh2n
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 20 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis change adds shared HTTP and MCP wire-contract fixtures, Rust/Python/TypeScript validation, structural compatibility checks, contract-version enforcement, and a CI job for fixture consistency and breaking changes. ChangesWire-contract coverage and enforcement
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CI
participant ContractGuard
participant GitBase
participant SDKTests
CI->>ContractGuard: validate current fixtures
ContractGuard-->>CI: return consistency status
CI->>ContractGuard: compare against base reference
ContractGuard->>GitBase: load historical fixtures and shapes
GitBase-->>ContractGuard: return base contract data
ContractGuard-->>CI: return compatibility and version status
CI->>SDKTests: run Python and TypeScript contract tests
SDKTests-->>CI: return decoding and enum validation results
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)
70-80: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAvoid interpolating
${{ }}expressions directly intorun:shell blocks.Both the base-ref detection step and the breaking-change guard step interpolate GitHub Actions expressions directly into shell scripts:
echo "ref=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" ... run: python3 sdk/spec/check_contract.py --base "${{ steps.base.outputs.ref }}"zizmor flags Line 79 as a template-injection risk.
github.event.pull_request.base.shais a git SHA andsteps.base.outputs.refis derived from it or the literalHEAD^, so the immediate risk is low. Still, the standard mitigation is to pass these throughenv:and reference the environment variable inside the script, so no GitHub Actions expression is ever substituted into shell text before the shell parses it — this keeps the pattern safe even if a future edit changes what feedsbase.sha-like values.🔒 Proposed fix
- name: Determine base ref id: base + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | - if [ -n "${{ github.event.pull_request.base.sha }}" ]; then - echo "ref=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" + if [ -n "$PR_BASE_SHA" ]; then + echo "ref=$PR_BASE_SHA" >> "$GITHUB_OUTPUT" else echo "ref=HEAD^" >> "$GITHUB_OUTPUT" fi - name: Breaking-change guard - run: python3 sdk/spec/check_contract.py --base "${{ steps.base.outputs.ref }}" + env: + BASE_REF: ${{ steps.base.outputs.ref }} + run: python3 sdk/spec/check_contract.py --base "$BASE_REF"🤖 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 @.github/workflows/ci.yml around lines 70 - 80, Update the “Determine base ref” and “Breaking-change guard” steps to pass GitHub Actions expressions through each step’s env configuration, then reference the resulting environment variables inside the shell command or script. Remove direct ${{ }} interpolation from the run blocks while preserving the existing base SHA/HEAD^ selection and check_contract.py invocation.Source: Linters/SAST tools
🤖 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 `@sdk/spec/check_contract.py`:
- Around line 54-70: The collect_by_path traversal must unwrap MCP pass-through
responses by parsing each content[].text string before continuing to nested
paths such as response.result, while preserving normal object and array
traversal. Apply the equivalent change to the TypeScript collector, and update
http_fixtures() in tests/http_api/contract.rs to include MCP fixtures if
required for shapes.json validation.
- Around line 231-249: Update classify_request so required-field promotion only
applies when the finding_path is a direct top-level request field, matching the
required entry by its exact path rather than only the final segment; preserve
existing behavior for genuine top-level additions. Add focused pytest coverage
for the nested request.filter.query collision, documented
classify/classify_request behavior, diff_shapes container-shape changes, and
field additions/removals.
In `@tests/fixtures/wire/shapes.json`:
- Around line 47-60: Add the missing response.result.omitted[].kind enum
declaration alongside the existing response.result.items[].kind entry, using the
identical allowed values: association, passage, and community.
In `@tests/http_api/contract.rs`:
- Around line 48-86: Add rust-version = "1.88" to the workspace package
configuration to declare the compiler minimum required by the let-chain syntax
in normalize_volatile; do not alter the normalization logic.
---
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 70-80: Update the “Determine base ref” and “Breaking-change guard”
steps to pass GitHub Actions expressions through each step’s env configuration,
then reference the resulting environment variables inside the shell command or
script. Remove direct ${{ }} interpolation from the run blocks while preserving
the existing base SHA/HEAD^ selection and check_contract.py invocation.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 54efed42-3919-40d6-b1bd-f83ca45d845c
📒 Files selected for processing (30)
.github/workflows/ci.ymlCHANGELOG.mdCONTRIBUTING.mdsdk/python/tests/unit/test_wire_contract.pysdk/spec/check_contract.pysdk/typescript/tests/unit/wire-contract.test.tstests/fixtures/wire/README.mdtests/fixtures/wire/http/activate.jsontests/fixtures/wire/http/communities_search.jsontests/fixtures/wire/http/contexts_list.jsontests/fixtures/wire/http/error_forbidden.jsontests/fixtures/wire/http/error_malformed_request.jsontests/fixtures/wire/http/error_no_context.jsontests/fixtures/wire/http/error_over_limit.jsontests/fixtures/wire/http/evidence_budget_constrained.jsontests/fixtures/wire/http/evidence_communities_degrade_and_rerank_reason.jsontests/fixtures/wire/http/evidence_contradiction_group.jsontests/fixtures/wire/http/evidence_duplicate_passage.jsontests/fixtures/wire/http/evidence_mixed_lanes.jsontests/fixtures/wire/http/explore.jsontests/fixtures/wire/http/health.jsontests/fixtures/wire/http/recall.jsontests/fixtures/wire/http/sources_search.jsontests/fixtures/wire/http/version.jsontests/fixtures/wire/mcp/assemble_evidence_call.jsontests/fixtures/wire/mcp/assemble_evidence_tool_error.jsontests/fixtures/wire/mcp/assemble_evidence_tool_schema.jsontests/fixtures/wire/shapes.jsontests/http_api/contract.rstests/http_api/main.rs
- collect_by_path (Python, Rust, TypeScript): the enum self-consistency check silently found nothing inside MCP fixtures, since their shape lives a second time as JSON text under content[].text rather than a nested object — now unwraps that pass-through text before continuing the same path. tests/http_api/contract.rs also widens both shapes.json self-consistency tests to cover mcp/ fixtures, not just http/. - check_contract.py's classify_request matched a required field by its last path segment alone, so a NEW nested field sharing a name with a required top-level one (request.filter.query vs. a required top-level query) was misclassified as a breaking change. Now matches the exact top-level path. Added sdk/python/tests/unit/test_check_contract.py to cover this and the module's other documented classification rules. - shapes.json: declared response.result.omitted[].kind (same values as items[].kind) — omitted candidates carry it too. - ci.yml: route the base-ref/PR-SHA values through env: instead of interpolating them directly into shell text (zizmor template-injection nitpick). - Cargo.toml: declare rust-version — clippy's own incompatible_msrv lint puts the true floor at 1.89 (File::try_lock in src/storage.rs), not the 1.88 let-chains alone would suggest. Claude-Session: https://claude.ai/code/session_011KBpNVCCcdTUtCuPdpXh2n
Summary
http_contract: 1/mcp_contract: 1wire shapes as machine-readable golden fixtures undertests/fixtures/wire/(ADR 0005 §9), including retrieval: add optional cross-lane reranking and token-budgeted evidence assembly #216's evidence-assembly package (POST /contexts/{name}/evidence,assemble_evidence) — 20 fixtures across 13 HTTP/MCP operations, generated from a live server and verified/regenerated viatests/http_api/contract.rs(TAGURU_UPDATE_WIRE_FIXTURES=1 cargo test --test http_api contract).sdk/python/tests/unit/test_wire_contract.py), and TypeScript (sdk/typescript/tests/unit/wire-contract.test.ts) all read the same committed fixtures.sdk/spec/check_contract.pyclassifies fixture changes against a base ref per ADR 0005 §4's compatible/breaking table and fails unless a breaking change lands beside a matchingHTTP_CONTRACT/MCP_CONTRACTbump insrc/api.rs. Wired into a newcontract-guardjob in.github/workflows/ci.yml.tests/fixtures/wire/README.mddocuments the update/classification procedure;CONTRIBUTING.mdandCHANGELOG.mdupdated accordingly.Closes #301
Test plan
cargo fmt --check/cargo clippy --all-targets --locked -- -D warnings/cargo test --locked(full suite, incl. the new 19contract::*tests)python sdk/spec/check_contract.py --checkand--base origin/mainfused_rankfrom the evidence response, confirmed detection, then reverted)python -m pytest sdk/python/tests -q(153 passed)npm test --workspace=taguru(148 passed),npm run typecheck,npm run lint,npm run check:surface --workspace=tagurupython sdk/spec/check_versions.pyhttps://claude.ai/code/session_011KBpNVCCcdTUtCuPdpXh2n
Summary by CodeRabbit
New Features
Documentation
Tests