Skip to content

[COVAL-5819] Audit CLI request-body fields against the published OpenAPI specs - #122

Merged
callumreid merged 1 commit into
mainfrom
callum/cli-request-field-audit
Sep 3, 2026
Merged

callumreid merged 1 commit into
mainfrom
callum/cli-request-field-audit

Conversation

@callumreid

@callumreid callumreid commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Why

scripts/audit_api_coverage.py mapped operations only — grep -c 'requestBody\|properties' returned 0. Nothing compared the published request-body properties with the serde fields on the Rust request structs the CLI actually sends, and serde silently drops any field a struct does not declare. COVAL-5805 (script_turns) was one instance of a systemic class; this makes the class visible.

What the check does

For every covered POST/PATCH/PUT operation, diff the published application/json request-body properties against the serde field names on the struct the client serializes.

  • The request-body type is read from the client function: a models::X parameter, a let req = models::X { … } binding, or serde_json::Value.
  • A serde_json::Value body counts as full coverage — it forwards caller JSON verbatim and cannot drop a field. That is the metric baseline and threshold endpoints.
  • Operations with no first-class command are skipped. Those are route gaps, already reported by the existing layer; reporting their fields too would double-count the same missing work.
  • Unresolvable input fails loudly rather than reading as zero coverage: an unrecognised body binding, a body struct that is not defined, and an unresolvable $ref all raise.

known_field_gap and allowed_extra_field mirror the existing known_gap / allowed_extra mechanism, staleness detection included, and [snapshot], the Markdown report, and the console summary carry the new counts. The weekly api-parity-audit.yml run now catches field drift as well as route drift.

Current state

313/360 published request fields on covered operations are modeled. The 47 gaps and 18 extras are recorded as reviewed exceptions so this PR is green and new drift fails from here on. Each reason names the issue that closes it: COVAL-5821 (personas), COVAL-5822 (reviews, reports), COVAL-5823 (agents, test-sets, run-templates, conversations).

Rebased onto main after COVAL-5820 (#123) merged. That PR modeled the 33 metric fields this branch had recorded as known_field_gap, so the audit reported them stale; they are removed here and the count moved from 280 to 313.

Two exceptions are not CLI work, and the follow-up PRs must not "fix" them:

  • COVAL-5825POST/PATCH /run-templates document agent_id, persona_id, test_set_id. The served model takes the plural arrays the CLI already sends and wraps them in a schema that forbids extra fields, so modeling the documented names would break the call. The published spec is wrong.
  • COVAL-5824PATCH /review-annotations/{id} sends status and completion_status, exposed as --status / --completion-status. The served contract has neither and ignores unknown fields, so both flags report success and change nothing.

The caveat this work surfaced

The published YAML is hand-written, not generated, so it can be wrong in either direction. coval-ai/backend records its known spec/model divergence in src/services/api/tests/v1/openapi_parity_baseline.txt; a field marked spec_extra_field there is documented but not served. Every gap in this PR was cross-checked against the served Pydantic model before being classified, and the README now says to do that.

Verification

python3 -m unittest scripts/test_audit_api_coverage.py   # 51 tests, was 28
python3 -m ruff check scripts && python3 -m ruff format --check scripts
python3 scripts/audit_api_coverage.py --write-markdown api-coverage-report.md   # PASS
cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features

Patch version bump to 0.8.1, on top of #123's 0.8.0.

Two notes on the test suite:

  • The audit aggregation tests previously reached the real network through audit(); patching the new _published_specs seam closes that. The Python suite went from 61s to 0.02s.
  • cargo test fails test_update_check_notifies_when_outdated under parallel execution and passes with --test-threads=1. Reproduced 3/3 on clean origin/main at 3331efe, so it predates this branch: src/update_check.rs abandons the check after WAIT_LIMIT = 500ms, which a parallel run regularly exceeds. Filed as COVAL-5826, not touched here.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Team

Run ID: 4e92102f-19ca-4095-8879-540801024e14

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

…API specs

The parity audit mapped operations only, so nothing compared the published
request-body properties with the serde fields on the Rust request structs the
CLI actually sends. serde drops any field a struct does not declare, so a
property the API adds is discarded in silence; COVAL-5805 (script_turns) was one
instance of that class.

For every covered POST/PATCH/PUT operation the audit now diffs the published
application/json request-body properties against the serde field names on the
struct the client serializes. A serde_json::Value body counts as full coverage
because it forwards caller JSON verbatim and cannot drop a field. Operations
with no first-class command are skipped; those are route gaps, already reported.

known_field_gap and allowed_extra_field mirror the existing route exception
mechanism, including staleness detection, and the snapshot, Markdown report, and
console summary carry the new counts. The 80 current gaps and 18 current extras
are recorded as reviewed exceptions so the weekly workflow starts failing on new
drift immediately; each reason names the follow-up issue that closes it.

Two of those exceptions are not CLI work. The run-template singular id fields are
documented but not served, so modeling them would break the call (COVAL-5825),
and the review-annotation status flags are advertised by the CLI but ignored by
the API (COVAL-5824).

The specs are fetched once and drive both layers, which also stops the audit
aggregation tests reaching the network.
@callumreid
callumreid force-pushed the callum/cli-request-field-audit branch from 80b0c27 to e33b10e Compare September 3, 2026 15:19
@callumreid
callumreid merged commit ff9bdb9 into main Sep 3, 2026
7 checks passed
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.

2 participants