Skip to content

feat(ci): add OpenAI API spec conformance tracking#2197

Open
m-misiura wants to merge 4 commits into
NVIDIA-NeMo:developfrom
m-misiura:oasdiff
Open

feat(ci): add OpenAI API spec conformance tracking#2197
m-misiura wants to merge 4 commits into
NVIDIA-NeMo:developfrom
m-misiura:oasdiff

Conversation

@m-misiura

@m-misiura m-misiura commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds oasdiff to check for breaking API changes and regression against a committed baseline via:

  1. pre-commit:
  • oasdiff breaking should catch breaking changes in fern/openapi.yml vs HEAD
  • oasdiff changelog should count conformance gaps vs vendored OpenAI spec; fails if count increases

Note that we use --fail-on ERR, so only error-level breaking changes fail the hook. Warning-level changes (like removing an optional request property) should pass through.

  1. weekly gh workflow
  • fetches the latest OpenAI spec from upstream
  • compares our API spec (fern/openapi.yml) against the vendored OpenAI spec (schemas/openai-spec.yml) and lists all per-endpoint differences
  • if the vendored spec is outdated, also compares against the latest upstream OpenAI spec so you can see what a vendor bump would change

Related Issue(s)

This PR deals with the following issue: GH Issue #2061

Verification

  • pre-commit no changes:
OpenAI spec conformance..................................................Passed
- hook id: api-conformance
- duration: 2.32s

[1/2] Breaking changes (HEAD vs working tree): none
[2/2] Conformance (OpenAI v2.3.0 vs fern/openapi.yml): 95 changes (unchanged)
  • pre-commit with a breaking change (new required property)
OpenAI spec conformance..................................................Failed
- hook id: api-conformance
- duration: 0.19s
- exit code: 1

[1/2] Breaking changes (HEAD vs working tree): 1 changes: 1 error, 0 warning, 0 info
error	[new-required-request-property] at fern/openapi.yml
	in API POST /v1/chat/completions
		added the new required request property `organization_id`

AI Assistance

  • No AI tools were used.
  • AI tools were used; a human reviewed and can explain every change (tool: Claude Code).

Checklist

  • I've read the CONTRIBUTING guidelines.
  • This PR links to a triaged issue assigned to me.
  • My PR title follows the project commit convention.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • I've noted any verification beyond CI and any checks I couldn't run.
  • I did not update generated changelog files manually.
  • I addressed all CodeRabbit, Greptile, and other review comments, or replied with why no change is needed.
  • @mentions of the person or team responsible for reviewing proposed changes.

cc @Pouyanpi @tgasser-nv

Summary by CodeRabbit

  • New Features

    • Added automated checks comparing the documented and running API specifications with the upstream OpenAI API specification.
    • Added conformance reports summarizing supported endpoints and identifying API compatibility gaps.
    • Added optional breaking-change detection for pull requests.
  • Chores

    • Added scheduled and manually triggered validation runs to monitor API compatibility over time.
    • Validation results are now included in workflow summaries for easier review.

@github-actions github-actions Bot added size: XL status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 21, 2026
@Pouyanpi

Copy link
Copy Markdown
Collaborator

Thanks @m-misiura. few things worth reconsidering before this lands:

  1. target spec (out of scope, just noting): the check diffs fern/openapi.yml, the hand-written spec that feeds Fern docs, not the running server. the actual contract is FastAPI's app.openapi() (pydantic models in nemoguardrails/server/api.py), and nothing keeps the two in sync. so this guards docs conformance, not implementation conformance, which is fine for this PR. worth a follow-up: an equivalence test that app.openapi() stays in sync with fern/openapi.yml would close the gap without switching the docs pipeline to generation. just worth dropping any "guards the implementation" framing so it's not read as more than it is.
  2. vendored spec. schemas/openai-spec.yml is 81k lines but the script only diffs /chat/completions. that's a large third-party blob in history/clones for one path. pinning an upstream ref and fetching at check time (the weekly job already curls it) gives a reproducible baseline without vendoring (a pruned subset is a middle option but I don't think it is desired).
  3. another question is whether to add /v1/models to the conformance side
  4. pre-commit hook: language: golang pulls a Go toolchain + oasdiff onto anyone who edits fern/openapi.yml, and --update mutate a tracked file (baseline.json) during commit. both are surprising for a spec only chane. a PR-gated CI job (paths: fern/openapi.yml) keeps this off contributor's machines and makes baseline updates an explicit, reviewed diff.

@m-misiura

m-misiura commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @m-misiura. few things worth reconsidering before this lands:

  1. target spec (out of scope, just noting): the check diffs fern/openapi.yml, the hand-written spec that feeds Fern docs, not the running server. the actual contract is FastAPI's app.openapi() (pydantic models in nemoguardrails/server/api.py), and nothing keeps the two in sync. so this guards docs conformance, not implementation conformance, which is fine for this PR. worth a follow-up: an equivalence test that app.openapi() stays in sync with fern/openapi.yml would close the gap without switching the docs pipeline to generation. just worth dropping any "guards the implementation" framing so it's not read as more than it is.
  2. vendored spec. schemas/openai-spec.yml is 81k lines but the script only diffs /chat/completions. that's a large third-party blob in history/clones for one path. pinning an upstream ref and fetching at check time (the weekly job already curls it) gives a reproducible baseline without vendoring (a pruned subset is a middle option but I don't think it is desired).
  3. another question is whether to add /v1/models to the conformance side
  4. pre-commit hook: language: golang pulls a Go toolchain + oasdiff onto anyone who edits fern/openapi.yml, and --update mutate a tracked file (baseline.json) during commit. both are surprising for a spec only chane. a PR-gated CI job (paths: fern/openapi.yml) keeps this off contributor's machines and makes baseline updates an explicit, reviewed diff.

Hi @Pouyanpi

As always, many thanks for the very useful feedback. Correspondingly:

  1. splitting the single workflow into two might be worthwhile. Those two workflows would be the one that diffs app.openapi() against the upstream spec and another one that diffs fern/openapi.yml against the upstream spec. I like the app.openapi() -> fern/openapi.yml test and if you think this valuable, I can create a GH issue and follow with another PR for this
  2. removing vendored spec could be prudent; the script can fetch the upstream spec at runtime
  3. adding v1/models also makes a ton sense since this is of course another openai compatible endpoint
  4. removing pre-commit hook and keeping things to CI jobs only would make things easier

I will work on those changes accordingly.

@Pouyanpi

Copy link
Copy Markdown
Collaborator

Thank you @m-misiura . it sounds good, plan works for me. I think we should keep the upstream ref pinned for the gating check (let the weekly job track master and flag drift) 👍🏻

Comment thread scripts/openai_coverage.py Outdated


def _fetch_openai_spec(url: str = OPENAI_SPEC_URL) -> Path:
tmp = Path(tempfile.mktemp(suffix=".yml"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@m-misiura can you fix this ?

Comment thread scripts/openai_coverage.py Outdated
from nemoguardrails.server.api import app

spec = app.openapi()
tmp = Path(tempfile.mktemp(suffix=".yml"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@m-misiura same here

@m-misiura

Copy link
Copy Markdown
Contributor Author

Thank you @m-misiura . it sounds good, plan works for me. I think we should keep the upstream ref pinned for the gating check (let the weekly job track master and flag drift) 👍🏻

Ack, thanks @Pouyanpi. Let me know what you think about the introduced changes

@Pouyanpi Pouyanpi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice rework @m-misiura, this looks much cleaner. Two things:

breaking check is a no-op in CI. If I'm not mistaken oasdiff breaking compares the file to itself on a fresh checkout (working tree == HEAD), so it always reports "none" and is a no-op. in order to actually guard a PR, diff the base branch against head, e.g. origin/develop:fern/openapi.yml vs fern/openapi.yml, with fetch-depth: 0 (or an explicit base fetch).

conformance is report-only now, the step writes a summary but never fails on the gap count. is it intentional?

Please address gretpile and coderabbits comments, once resolved ask them to confirm it, for greptile you must tag it (see https://github.com/NVIDIA-NeMo/Guardrails/blob/e40c7f69862c17a11465c32e4300eaabe087e2d6/CONTRIBUTING.md#review-readiness)

Comment on lines +12 to +14
paths:
- "nemoguardrails/server/**"
- "scripts/openai_coverage.py"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nemoguardrails/server/** misses embedded types: the request model pulls in GenerationOptions from nemoguardrails/rails/llm/options.py, which shows up in app.openapi() . A change there drifts the contract without triggering this job. what do you think?

The weekly job catches what is missed here as it targets the spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding nemoguardrails/rails/llm/options.py to the PR trigger make sense; the weekly run should catch any other transitive imports that are missed

Comment thread scripts/openai_coverage.py Outdated


def _fetch_openai_spec(url: str = OPENAI_SPEC_URL) -> Path:
tmp = Path(tempfile.mktemp(suffix=".yml"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@m-misiura can you fix this ?

Comment thread scripts/openai_coverage.py Outdated
from nemoguardrails.server.api import app

spec = app.openapi()
tmp = Path(tempfile.mktemp(suffix=".yml"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@m-misiura same here

@Pouyanpi Pouyanpi added status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). and removed status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 24, 2026
@Pouyanpi Pouyanpi added this to the v0.24.0 milestone Jul 24, 2026
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces two new GitHub Actions workflows and a Python helper script (scripts/openai_coverage.py) that use oasdiff to track conformance between NeMo Guardrails' API specs and the upstream OpenAI spec. All issues flagged in earlier review rounds (returncode guard, pipe escaping in markdown tables, text key safety, git cat-file -e for new-file detection, orphaned step output) have been resolved in the current revision.

  • api-conformance-docs.yml — runs on PRs touching fern/openapi.yml and weekly; checks the hand-written Fern spec for breaking changes (PR only, against the remote base ref) and conformance gaps against the pinned or latest OpenAI spec.
  • api-conformance-impl.yml — runs on PRs touching nemoguardrails/server/** and weekly; exports app.openapi() at runtime and compares it to the OpenAI spec, surfacing schema drift introduced by Pydantic model changes.
  • scripts/openai_coverage.py — wraps oasdiff changelog/breaking, filters to the /chat/completions and /models endpoints, and writes a GFM markdown summary to $GITHUB_STEP_SUMMARY.

Confidence Score: 5/5

Safe to merge — adds CI-only tooling with no changes to production code, and all previously flagged bugs have been fixed.

All three files are new CI infrastructure. The workflows use SHA-pinned actions, least-privilege contents: read permissions, and a pinned oasdiff version. The Python script quality gaps are limited to diagnostic output and redundant computation, neither of which can cause a missed failure in CI.

scripts/openai_coverage.py — two minor quality observations around import-error handling and redundant conformance analysis when --check-breaking is used.

Important Files Changed

Filename Overview
scripts/openai_coverage.py New conformance analyzer script; all previously flagged issues resolved. Two minor quality observations: ImportError not caught for --fastapi imports, and --check-breaking always runs analyze() causing redundant oasdiff work on PRs.
.github/workflows/api-conformance-docs.yml New workflow checking fern/openapi.yml against pinned or latest OpenAI spec; triggers correctly; SHA-pinned actions; previously noted orphaned output var removed.
.github/workflows/api-conformance-impl.yml New workflow checking app.openapi() at runtime against the OpenAI spec; clean structure, no breaking-change step (correct since the FastAPI export has no git history to diff against); SHA-pinned actions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PR / Schedule / workflow_dispatch] --> B{Which workflow?}
    B --> C[api-conformance-docs.yml]
    B --> D[api-conformance-impl.yml]
    C --> E[Checkout + uv + oasdiff]
    E --> F[Fetch OpenAI spec]
    F --> G{PR event?}
    G -- yes --> H[oasdiff breaking base_ref vs working tree]
    H -- breaking --> I[exit 1]
    H -- clean --> J[oasdiff changelog OpenAI vs fern spec]
    G -- no --> J
    J --> K[Write summary to GITHUB_STEP_SUMMARY]
    D --> L[Checkout + uv + oasdiff]
    L --> M[Fetch OpenAI spec]
    M --> N[Export app.openapi to tmp YAML]
    N --> O[oasdiff changelog OpenAI vs app.openapi]
    O --> P[Write summary to GITHUB_STEP_SUMMARY]
Loading

Reviews (2): Last reviewed commit: ":construction: addressing feedback on PR" | Re-trigger Greptile

Comment thread scripts/openai_coverage.py Outdated
Comment on lines +152 to +158
current_endpoint = ""
for c in changes:
endpoint = f"{c.get('operation', '?')} {c.get('path', '?')}"
if endpoint != current_endpoint:
current_endpoint = endpoint
print(f" {endpoint}:")
print(f" {c['text']}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 KeyError on missing text field in verbose print

_print_report accesses c['text'] directly on line 158, but _markdown_report (line 175) uses c.get('text', ''). If oasdiff returns a changelog entry that has no text key (e.g., a structural/meta entry for a path section), the verbose code path will raise an unhandled KeyError and crash the script, while the markdown path handles it gracefully. Both should use .get('text', '') for consistency.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/openai_coverage.py
Line: 152-158

Comment:
**KeyError on missing `text` field in verbose print**

`_print_report` accesses `c['text']` directly on line 158, but `_markdown_report` (line 175) uses `c.get('text', '')`. If oasdiff returns a `changelog` entry that has no `text` key (e.g., a structural/meta entry for a path section), the verbose code path will raise an unhandled `KeyError` and crash the script, while the markdown path handles it gracefully. Both should use `.get('text', '')` for consistency.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@greptileai

paths should now use paths now use c.get('text', '').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the fix! Using c.get('text', '') consistently in both _print_report and _markdown_report handles the edge case correctly.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment on lines +60 to +63
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0 and not result.stdout:
raise RuntimeError(f"oasdiff {subcommand} failed: {result.stderr}")
return json.loads(result.stdout) if result.stdout else {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Partial oasdiff output on error is silently parsed

The condition result.returncode != 0 and not result.stdout only raises when oasdiff exits with an error and produces no stdout. If oasdiff writes partial JSON to stdout before encountering an error (e.g., a malformed spec section), the function will silently return the partial parsed result. This can produce a lower-than-real conformance gap count, causing the CI check to pass when it should fail. A safer approach is to raise whenever returncode != 0, regardless of whether stdout was partially written.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/openai_coverage.py
Line: 60-63

Comment:
**Partial oasdiff output on error is silently parsed**

The condition `result.returncode != 0 and not result.stdout` only raises when oasdiff exits with an error **and** produces no stdout. If oasdiff writes partial JSON to stdout before encountering an error (e.g., a malformed spec section), the function will silently return the partial parsed result. This can produce a lower-than-real conformance gap count, causing the CI check to pass when it should fail. A safer approach is to raise whenever `returncode != 0`, regardless of whether stdout was partially written.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@greptileai

This should now raise on any non-zero returncode regardless of stdout.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current file still has the original condition:

if result.returncode != 0 and not result.stdout:
    raise RuntimeError(f"oasdiff {subcommand} failed: {result.stderr}")
return json.loads(result.stdout) if result.stdout else {}

The and not result.stdout guard is still present. Here's the fix:

Suggested change
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0 and not result.stdout:
raise RuntimeError(f"oasdiff {subcommand} failed: {result.stderr}")
return json.loads(result.stdout) if result.stdout else {}
if result.returncode != 0:
raise RuntimeError(f"oasdiff {subcommand} failed: {result.stderr}")
return json.loads(result.stdout) if result.stdout else {}

fi
curl -fsSL "${{ env.OPENAI_SPEC_URL }}/${REF}/openapi.yaml" \
-o /tmp/openai-spec.yml
echo "ref=${REF}" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Orphaned step output — ref is set but never consumed

echo "ref=${REF}" >> "$GITHUB_OUTPUT" writes a step output variable, but the step has no id: field, making the output unaddressable by any downstream step. No subsequent step in the workflow references it either. The line can be removed without any effect.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/api-conformance-impl.yml
Line: 60

Comment:
**Orphaned step output — `ref` is set but never consumed**

`echo "ref=${REF}" >> "$GITHUB_OUTPUT"` writes a step output variable, but the step has no `id:` field, making the output unaddressable by any downstream step. No subsequent step in the workflow references it either. The line can be removed without any effect.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@greptileai; this has been remove accordingly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the confirmation — glad that's cleaned up!

Comment thread scripts/openai_coverage.py Outdated
Comment on lines +170 to +175
if changes:
lines.append("| Endpoint | Change |")
lines.append("|---|---|")
for c in changes:
endpoint = f"`{c.get('operation', '?')} {c.get('path', '?')}`"
lines.append(f"| {endpoint} | {c.get('text', '')} |")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unescaped pipe characters can break the markdown table

c.get('text', '') is inserted verbatim into a GFM table cell. If the oasdiff changelog text includes a pipe character (e.g., describing a type union like string | null), it will prematurely close the table cell and corrupt the rendered summary. Replacing | with \| in the text value before interpolation would prevent this.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/openai_coverage.py
Line: 170-175

Comment:
**Unescaped pipe characters can break the markdown table**

`c.get('text', '')` is inserted verbatim into a GFM table cell. If the oasdiff changelog text includes a pipe character (e.g., describing a type union like `string | null`), it will prematurely close the table cell and corrupt the rendered summary. Replacing `|` with `\|` in the `text` value before interpolation would prevent this.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@greptileai

Pipe characters should now be escaped before interpolation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current branch still has the unescaped interpolation — c.get('text', '') is inserted verbatim. The fix hasn't landed yet. Here's the corrected line:

Suggested change
if changes:
lines.append("| Endpoint | Change |")
lines.append("|---|---|")
for c in changes:
endpoint = f"`{c.get('operation', '?')} {c.get('path', '?')}`"
lines.append(f"| {endpoint} | {c.get('text', '')} |")
lines.append(f"| {endpoint} | {c.get('text', '').replace('|', r'\|')} |")

Comment thread scripts/openai_coverage.py Outdated
Comment on lines +66 to +68
def _check_breaking(spec: str) -> bool:
if not Path(spec).exists():
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Silent pass when spec file is absent at HEAD

_check_breaking returns True (no breaking changes) when the spec file does not exist on the filesystem. If fern/openapi.yml is deleted in a PR, the breaking-change gate silently passes because there is no file to git show HEAD:spec against. The intent might be to handle a brand-new spec with no prior HEAD, but deleting an existing spec would also pass unchecked. Consider logging a warning or treating a missing file as an error.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/openai_coverage.py
Line: 66-68

Comment:
**Silent pass when spec file is absent at HEAD**

`_check_breaking` returns `True` (no breaking changes) when the spec file does not exist on the filesystem. If `fern/openapi.yml` is deleted in a PR, the breaking-change gate silently passes because there is no file to `git show HEAD:spec` against. The intent might be to handle a brand-new spec with no prior HEAD, but deleting an existing spec would also pass unchecked. Consider logging a warning or treating a missing file as an error.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@greptileai

Also added a git cat-file -e check for the base ref so it handles the "new file" case with a note instead of an oasdiff error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking at the current code in the PR, I don't see the git cat-file -e check yet — _check_breaking still reads:

def _check_breaking(spec: str) -> bool:
    if not Path(spec).exists():
        return True

The git cat-file -e approach is exactly the right fix. To properly distinguish the two cases, it would need to look something like:

def _check_breaking(spec: str) -> bool:
    head_exists = subprocess.run(
        ["git", "cat-file", "-e", f"HEAD:{spec}"],
        capture_output=True,
    ).returncode == 0

    if not head_exists:
        print(f"Note: {spec} not found at HEAD (new file); skipping breaking-change check.")
        return True

    if not Path(spec).exists():
        print(f"Error: {spec} deleted from working tree; treating as breaking change.")
        return False

    # ... existing oasdiff call

Is that change in a later commit that hasn't been pushed yet, or is it somewhere else (e.g., the pre-commit hook script)?

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a CLI for comparing Guardrails and OpenAI OpenAPI specifications, with reporting and breaking-change checks. Adds separate GitHub Actions workflows for documentation and FastAPI implementation conformance checks.

Changes

API Conformance Validation

Layer / File(s) Summary
CLI helpers and specification inputs
scripts/openai_coverage.py
Adds OpenAI specification constants and helpers for oasdiff, specification fetching, and FastAPI OpenAPI export.
Conformance analysis and CLI reporting
scripts/openai_coverage.py
Analyzes path-level gaps, formats console and Markdown reports, and handles CLI options, cleanup, and errors.
Documentation and implementation CI checks
.github/workflows/api-conformance-*.yml
Adds pull-request, scheduled, and manual workflows that run documentation or implementation conformance checks.

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

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant OpenAISpec
  participant openai_coverage.py
  participant oasdiff
  GitHubActions->>OpenAISpec: Fetch openapi.yaml
  GitHubActions->>openai_coverage.py: Run conformance CLI
  openai_coverage.py->>oasdiff: Run changelog or breaking check
  oasdiff-->>openai_coverage.py: Return comparison results
  openai_coverage.py-->>GitHubActions: Publish report and step summary
Loading

Suggested labels: CI

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding CI-based OpenAI API spec conformance tracking.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Test Results For Major Changes ✅ Passed PASS: This major conformance-feature PR includes a Verification section with passing/failing pre-commit results and a workflow example.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/api-conformance-impl.yml (1)

51-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused GITHUB_OUTPUT write — this step has no id, so ref is never referenced anywhere.

echo "ref=${REF}" >> "$GITHUB_OUTPUT" writes an output nobody can consume since the step lacks an id:. Either drop the line or add an id if a downstream step is meant to read it (none currently does).

🤖 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/api-conformance-impl.yml around lines 51 - 61, Remove the
unused GITHUB_OUTPUT write from the “Fetch OpenAI spec” step, since the step has
no id and no downstream consumer reads ref; leave the REF selection and spec
download unchanged.
🤖 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 `@scripts/openai_coverage.py`:
- Around line 128-132: Update the changelog scan that builds changes to include
entries with section "components" alongside "paths", while retaining the
existing removal of baseSource, revisionSource, and fingerprint fields. Preserve
the current path display filtering separately so component schema diffs are
included in the conformance report without changing path presentation.
- Around line 66-88: The _check_breaking function currently compares the
checked-out OpenAPI spec with itself, making the CI check ineffective. Update
_check_breaking to accept a base ref or merge-base argument and use it in the
historical spec reference passed to oasdiff, while retaining the current spec as
the comparison target; update its callers to provide that reference.

---

Nitpick comments:
In @.github/workflows/api-conformance-impl.yml:
- Around line 51-61: Remove the unused GITHUB_OUTPUT write from the “Fetch
OpenAI spec” step, since the step has no id and no downstream consumer reads
ref; leave the REF selection and spec download unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6ca8be70-0079-4b77-921c-2053c3b74761

📥 Commits

Reviewing files that changed from the base of the PR and between b237e39 and 7f7ab33.

📒 Files selected for processing (3)
  • .github/workflows/api-conformance-docs.yml
  • .github/workflows/api-conformance-impl.yml
  • scripts/openai_coverage.py

Comment thread scripts/openai_coverage.py Outdated
Comment thread scripts/openai_coverage.py
@m-misiura

m-misiura commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Nice rework @m-misiura, this looks much cleaner. Two things:

breaking check is a no-op in CI. If I'm not mistaken oasdiff breaking compares the file to itself on a fresh checkout (working tree == HEAD), so it always reports "none" and is a no-op. in order to actually guard a PR, diff the base branch against head, e.g. origin/develop:fern/openapi.yml vs fern/openapi.yml, with fetch-depth: 0 (or an explicit base fetch).

conformance is report-only now, the step writes a summary but never fails on the gap count. is it intentional?

Please address gretpile and coderabbits comments, once resolved ask them to confirm it, for greptile you must tag it (see https://github.com/NVIDIA-NeMo/Guardrails/blob/e40c7f69862c17a11465c32e4300eaabe087e2d6/CONTRIBUTING.md#review-readiness)

That's a good catch on the breaking check @Pouyanpi . I would think that using --base-ref origin/${{ github.base_ref }} with fetch-depth: 0 should help

In terms of conformance is report-only now, the step writes a summary but never fails on the gap count. is it intentional? I thought initially, it might be useful to only report. Adding one here would require comparing app.openapi() across commits and could be a follow-up; but it's entirely up to you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants