Skip to content

docs: document RFC 9457 dual response formats in OpenAPI schema#506

Merged
Aureliolo merged 7 commits intomainfrom
docs/rfc9457-openapi
Mar 17, 2026
Merged

docs: document RFC 9457 dual response formats in OpenAPI schema#506
Aureliolo merged 7 commits intomainfrom
docs/rfc9457-openapi

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add ProblemDetail schema to OpenAPI components.schemas (generated from the Pydantic model with $ref rewriting)
  • Define 8 reusable error responses in components.responses, each with dual content types (application/json envelope + application/problem+json bare RFC 9457)
  • Inject error response refs into all operations based on path/method heuristics (401/403 for authenticated endpoints, 404 for parameterized paths, 400/409 for write methods, 500 for all)
  • Replace Litestar's incorrect default 400 schema (ValidationException) with the actual ApiResponse envelope
  • Add RFC 9457 content negotiation documentation to info.description
  • Update CLAUDE.md package structure and quick commands

Closes #494

Test plan

  • 85 tests (76 unit + 9 new from review) all passing
  • ruff lint + format clean
  • mypy strict clean
  • 94.37% coverage (80% threshold)
  • scripts/export_openapi.py generates schema with ProblemDetail and dual content types
  • Pre-reviewed by 7 agents, 9 findings addressed

Review coverage

7 agents ran in parallel: code-reviewer, python-reviewer, docs-consistency, conventions-enforcer, issue-resolution-verifier, logging-audit, test-quality-reviewer. All 3 acceptance criteria verified as RESOLVED (confidence 95-98).

Add ProblemDetail schema and dual content-type error responses
(application/json envelope + application/problem+json bare) to the
OpenAPI spec via a post-processing step in the export pipeline.

Closes #494
- Fix misleading docstring return description in _build_problem_detail_schema
- Extract _inject_operation_responses helper (function length + nesting)
- Add comment explaining DELETE exclusion from Conflict (409)
- Change test_full_app_schema_enhancement marker to @pytest.mark.integration
- Add tests: DELETE skips 409, public paths skip 503, retryable examples
- Update CLAUDE.md package structure and quick commands for openapi.py

Pre-reviewed by 7 agents, 9 findings addressed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 16, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • OpenAPI export now augments schemas with RFC 9457 dual-format error responses and injects them into operations.
  • Documentation

    • Export docs updated to describe RFC 9457 behavior, examples, and content negotiation.
    • Logging guidance expanded with conventions for INFO/WARNING/ERROR/DEBUG and structured event usage.
  • Tests

    • Added unit and integration tests covering schema augmentation, response injection rules, examples, idempotency, and edge cases.
  • Chores

    • Export tooling updated and a new observability event for enhanced OpenAPI schemas was added.

Walkthrough

Adds an OpenAPI post-processor that injects an RFC 9457 ProblemDetail schema and reusable dual-format error responses into generated OpenAPI output, wires the post-processor into the export script, adds an observability event constant, and includes unit and integration tests validating behavior.

Changes

Cohort / File(s) Summary
Documentation
CLAUDE.md
Clarifies OpenAPI export now documents RFC 9457 error responses and notes an OpenAPI post-processor (inject_rfc9457_responses) is used during export; expands logging conventions and event-constant import guidance.
OpenAPI Export Script
scripts/export_openapi.py
Imports and calls inject_rfc9457_responses() to enhance the generated schema before JSON serialization.
OpenAPI Post-Processor
src/synthorg/api/openapi.py
New module exposing inject_rfc9457_responses(schema: dict[str, Any]) -> dict[str, Any]; adds ProblemDetail component, builds reusable responses (BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests, InternalError, ServiceUnavailable) with both application/json (envelope) and application/problem+json (bare) representations and examples, appends RFC 9457 docs to info.description idempotently, injects response refs into operations based on path/method/context, and returns a deep-copied enhanced schema.
Observability Events
src/synthorg/observability/events/api.py
Adds new event constant API_OPENAPI_SCHEMA_ENHANCED.
Unit Tests
tests/unit/api/test_openapi.py
New comprehensive tests covering ProblemDetail schema, reusable dual-content responses and examples, operation-level injection rules, idempotency/immutability, info.description augmentation, and edge cases.
Integration Tests
tests/integration/api/test_openapi_integration.py
New integration test applying the post-processor to a Litestar-generated schema and asserting presence of ProblemDetail, eight reusable responses with both content types, operation-level injection behavior, and info.description mention of RFC 9457.
Project/meta
manifest_file, requirements.txt, pyproject.toml
Small manifest/metadata updates accompanying tests and new module additions.

Sequence Diagram

sequenceDiagram
    participant Export as scripts/export_openapi.py
    participant Litestar as Litestar App
    participant Processor as inject_rfc9457_responses()
    participant Schema as OpenAPI Schema
    participant Client as API Client

    Export->>Litestar: Generate initial OpenAPI schema_dict
    Litestar-->>Export: Return schema_dict
    Export->>Processor: Call inject_rfc9457_responses(schema_dict)
    Processor->>Schema: Add ProblemDetail component and reusable responses
    Processor->>Schema: Inject response refs into operations (by path/method/context)
    Processor-->>Export: Return enhanced schema_dict
    Export->>Export: Serialize enhanced schema to JSON
    Export-->>Client: Serve OpenAPI spec including RFC 9457 docs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.31% 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
Title check ✅ Passed The title accurately describes the main change: documenting RFC 9457 dual response formats in the OpenAPI schema, which is the primary focus of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of the implementation details and test results.
Linked Issues check ✅ Passed All acceptance criteria from issue #494 are met: ProblemDetail schema is included [src/synthorg/api/openapi.py], error responses document both content types [src/synthorg/api/openapi.py], and the OpenAPI export script was updated [scripts/export_openapi.py].
Out of Scope Changes check ✅ Passed All changes are in scope: OpenAPI post-processor implementation, test coverage, documentation updates in CLAUDE.md, and event constant addition are all directly related to RFC 9457 OpenAPI schema documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/rfc9457-openapi
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch docs/rfc9457-openapi
📝 Coding Plan
  • Generate coding plan for human review comments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the OpenAPI schema generation by implementing a robust post-processing step to support RFC 9457 for structured error responses. The changes ensure that the API documentation accurately reflects the dual-format error handling capabilities, providing clearer and more consistent error details for API consumers. This improves the overall developer experience by standardizing error reporting and making the API easier to integrate with.

Highlights

  • OpenAPI Schema Enhancement: Added the ProblemDetail schema to the OpenAPI components.schemas section, generated from the Pydantic model with $ref rewriting.
  • Dual-Format Error Responses: Defined 8 reusable error responses in components.responses, each supporting dual content types: application/json (envelope) and application/problem+json (bare RFC 9457).
  • Automated Error Response Injection: Injected references to these reusable error responses into all API operations based on path and method heuristics (e.g., 401/403 for authenticated endpoints, 404 for parameterized paths, 400/409 for write methods, 500 for all).
  • Improved 400 Error Schema: Replaced Litestar's default 400 schema (ValidationException) with the correct ApiResponse envelope for consistency.
  • Content Negotiation Documentation: Added comprehensive RFC 9457 content negotiation documentation to the OpenAPI info.description.
  • Documentation Updates: Updated the CLAUDE.md file to reflect the new package structure and quick commands related to OpenAPI schema export.
Changelog
  • CLAUDE.md
    • Updated the description for the export_openapi.py script to mention RFC 9457 error responses.
    • Expanded the description of the src/synthorg/api/ directory to include the new OpenAPI schema post-processor.
  • scripts/export_openapi.py
    • Modified to import and call the inject_rfc9457_responses function to enhance the generated OpenAPI schema.
  • src/synthorg/api/openapi.py
    • Added a new module containing the inject_rfc9457_responses function, which handles adding the ProblemDetail schema, defining reusable dual-content-type error responses, injecting these responses into API operations, and updating the API description with RFC 9457 details.
  • tests/unit/api/test_openapi.py
    • Added new unit tests to verify the correct functionality of inject_rfc9457_responses, covering schema addition, reusable response definitions, operation injection logic, and info description updates.
Activity
  • 85 tests (76 unit + 9 new from review) are all passing.
  • Ruff lint and format checks are clean.
  • Mypy strict checks are clean.
  • Achieved 94.37% code coverage, exceeding the 80% threshold.
  • scripts/export_openapi.py successfully generates the schema with ProblemDetail and dual content types.
  • The pull request was pre-reviewed by 7 agents, and 9 findings were addressed.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a post-processing step for the OpenAPI schema to correctly document RFC 9457 error responses. A new module src/synthorg/api/openapi.py contains the logic to inject dual-format error responses (application/json and application/problem+json) into the schema, and it is called from the scripts/export_openapi.py script. The changes are well-implemented and accompanied by a comprehensive new test suite in tests/unit/api/test_openapi.py that covers various scenarios, including unit, integration, and idempotency tests. The documentation in CLAUDE.md is also updated to reflect these changes.

My review includes a couple of suggestions for the new openapi.py module to improve maintainability and performance, but overall this is a high-quality contribution.

Comment on lines +57 to +132
_ERROR_RESPONSES: Final[
tuple[tuple[int, str, str, ErrorCode, ErrorCategory, str, bool], ...]
] = (
(
400,
"BadRequest",
"Validation error — request body or parameters are invalid.",
ErrorCode.REQUEST_VALIDATION_ERROR,
ErrorCategory.VALIDATION,
"Validation error",
False,
),
(
401,
"Unauthorized",
"Authentication required — missing or invalid credentials.",
ErrorCode.UNAUTHORIZED,
ErrorCategory.AUTH,
"Authentication required",
False,
),
(
403,
"Forbidden",
"Insufficient permissions for this operation.",
ErrorCode.FORBIDDEN,
ErrorCategory.AUTH,
"Forbidden",
False,
),
(
404,
"NotFound",
"Requested resource does not exist.",
ErrorCode.RECORD_NOT_FOUND,
ErrorCategory.NOT_FOUND,
"Resource not found",
False,
),
(
409,
"Conflict",
"Resource conflict — duplicate or invalid state transition.",
ErrorCode.RESOURCE_CONFLICT,
ErrorCategory.CONFLICT,
"Resource conflict",
False,
),
(
429,
"TooManyRequests",
"Rate limit exceeded — back off and retry.",
ErrorCode.RATE_LIMITED,
ErrorCategory.RATE_LIMIT,
"Rate limit exceeded",
True,
),
(
500,
"InternalError",
"Internal server error.",
ErrorCode.INTERNAL_ERROR,
ErrorCategory.INTERNAL,
"Internal server error",
False,
),
(
503,
"ServiceUnavailable",
"Required service is temporarily unavailable.",
ErrorCode.SERVICE_UNAVAILABLE,
ErrorCategory.INTERNAL,
"Service unavailable",
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.

medium

For improved readability and maintainability, consider using a dataclasses.dataclass or typing.NamedTuple for the items in _ERROR_RESPONSES instead of a raw tuple. This would make the data structure self-documenting and accessing fields less dependent on position.

For example:

from dataclasses import dataclass

@dataclass(frozen=True)
class ErrorResponseDef:
    status_code: int
    key: str
    description: str
    error_code: ErrorCode
    error_category: ErrorCategory
    detail: str
    retryable: bool

_ERROR_RESPONSES: Final[tuple[ErrorResponseDef, ...]] = (
    ErrorResponseDef(
        status_code=400,
        key="BadRequest",
        # ...
    ),
    # ...
)

This would change the loops that iterate over _ERROR_RESPONSES to use attribute access (e.g., err.status_code) instead of tuple unpacking, which can be more robust to future changes.

"NotFound": has_params,
# DELETE is excluded — it's idempotent and conflicts are
# a create/update concern in this API's model.
"Conflict": method in {"post", "put", "patch"},
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.

medium

To improve performance and avoid creating a new set on each function call, you can reuse the is_write boolean that is already calculated. The current logic is equivalent to checking if the method is a write method but not 'delete', which is more explicit about the intent described in the comment on line 325.

Suggested change
"Conflict": method in {"post", "put", "patch"},
"Conflict": is_write and method != "delete",

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/openapi.py`:
- Around line 171-178: _build_problem_detail_schema() currently rewrites
internal refs via _rewrite_refs(raw) but pops "$defs" and never materializes
them, leaving inject_rfc9457_responses() with dangling refs; instead, after
calling ProblemDetail.model_json_schema(mode="serialization") and rewriting
refs, take the popped "$defs" (if any) and merge them into the OpenAPI
components under "components":{"schemas":{...}} (renaming keys as needed so
'#/$defs/X' -> '#/components/schemas/X'), ensuring _rewrite_refs() still points
to those component names; apply the same change to the other places where
raw.pop("$defs", None) is used (the other _build_* blocks around the similar
logic) so all rewritten refs have corresponding entries in components.schemas
rather than being discarded.
- Around line 134-153: The current assignment to _INFO_DESCRIPTION overwrites
any existing app schema description; instead preserve the existing
info.description and append the RFC 9457 section idempotently. Update the
OpenAPI generation logic (where _INFO_DESCRIPTION is used and in the related
locations around the other references at lines indicated) so it reads the
current app.openapi_schema["info"]["description"] (or the existing
info.description) and concatenates the RFC 9457 block only if it’s not already
present, leaving the original description intact and avoiding duplicate appends.
- Around line 20-29: Add structured logging to this module by importing
get_logger and instantiating logger = get_logger(__name__) at module scope
(i.e., add from synthorg.observability import get_logger and logger =
get_logger(__name__)). Then locate business-logic functions in this file that
perform schema generation/injection or raise ProblemDetail / use
ErrorCategory/ErrorCode/CATEGORY_TITLES and add logger.warning or logger.error
calls with contextual information (include the function name, relevant
identifiers, and the exception or error details) immediately before raising or
re-raising errors so all error paths log context as required.

In `@tests/unit/api/test_openapi.py`:
- Around line 511-519: The test currently asserts exact equality of
components.responses and iterates over all responses, which breaks when
unrelated reusable responses are added; change the assertions to scope to the
RFC 9457 response set by asserting that _EXPECTED_RESPONSE_KEYS is a subset of
responses.keys() (not equal) and iterate only over _EXPECTED_RESPONSE_KEYS
(e.g., for key in _EXPECTED_RESPONSE_KEYS: resp = responses[key]) when checking
that each response has both "application/json" and "application/problem+json"
content types.
🪄 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

Run ID: 73cddc0c-0ec8-4428-af8e-672d6ce36121

📥 Commits

Reviewing files that changed from the base of the PR and between bd85a8d and 48f2661.

📒 Files selected for processing (4)
  • CLAUDE.md
  • scripts/export_openapi.py
  • src/synthorg/api/openapi.py
  • tests/unit/api/test_openapi.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/unit/api/test_openapi.py
  • src/synthorg/api/openapi.py
  • scripts/export_openapi.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/api/test_openapi.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/api/openapi.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Files:

  • src/synthorg/api/openapi.py
scripts/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Scripts in scripts/ have relaxed ruff rules: print and deferred imports allowed (CI/build utilities, not application code).

Files:

  • scripts/export_openapi.py
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Property-based testing: Python uses Hypothesis (given + settings). Hypothesis profiles: ci (200 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to pyproject.toml : Dependencies: all versions use == in pyproject.toml. Groups: test (pytest + plugins, hypothesis), dev (includes test + ruff, mypy, pre-commit, commitizen, pip-audit). Required: mem0ai (Mem0 memory backend — the default and currently only backend). Install: uv sync installs everything (dev group is default).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.651Z
Learning: Always use `uv sync` to install dependencies. Use `uv sync --group docs` for docs toolchain.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to tests/**/*.py : Use Hypothesis for property-based testing in Python with `given` + `settings`. Control via `HYPOTHESIS_PROFILE` env var (dev: 1000 examples, ci: 200 examples).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to .github/workflows/pages.yml : Pages workflow builds OpenAPI schema (`scripts/export_openapi.py`), Astro landing page, and Zensical docs. Triggered on docs, config, and workflow changes. Deploys to GitHub Pages.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.651Z
Learning: Use source layout: Python source in `src/synthorg/`, tests in `tests/`, web dashboard in `web/`, CLI binary in `cli/`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Applied to files:

  • CLAUDE.md
  • tests/unit/api/test_openapi.py
  • src/synthorg/api/openapi.py
  • scripts/export_openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • CLAUDE.md
🧬 Code graph analysis (3)
tests/unit/api/test_openapi.py (1)
src/synthorg/api/openapi.py (1)
  • inject_rfc9457_responses (360-416)
src/synthorg/api/openapi.py (1)
src/synthorg/api/errors.py (1)
  • category_type_uri (117-126)
scripts/export_openapi.py (1)
src/synthorg/api/openapi.py (1)
  • inject_rfc9457_responses (360-416)
🔇 Additional comments (1)
scripts/export_openapi.py (1)

93-99: Nice placement for the schema augmentation.

Running the post-processor immediately after to_schema() keeps the RFC 9457 rewrite isolated to the exported artifact and leaves the rest of the script flow unchanged.

Comment on lines +20 to +29
import copy
from typing import Any, Final

from synthorg.api.dto import ProblemDetail
from synthorg.api.errors import (
CATEGORY_TITLES,
ErrorCategory,
ErrorCode,
category_type_uri,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add the standard logger to this new src/ module.

This transformer lives under src/ and currently has no structured logging, so schema-generation/injection failures will bubble out without the context the repo expects.

As per coding guidelines, src/**/*.py: "Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__)" and "All error paths must log at WARNING or ERROR level with context before raising."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/openapi.py` around lines 20 - 29, Add structured logging to
this module by importing get_logger and instantiating logger =
get_logger(__name__) at module scope (i.e., add from synthorg.observability
import get_logger and logger = get_logger(__name__)). Then locate business-logic
functions in this file that perform schema generation/injection or raise
ProblemDetail / use ErrorCategory/ErrorCode/CATEGORY_TITLES and add
logger.warning or logger.error calls with contextual information (include the
function name, relevant identifiers, and the exception or error details)
immediately before raising or re-raising errors so all error paths log context
as required.

Comment on lines +171 to +178
raw = ProblemDetail.model_json_schema(mode="serialization")

# Strip $defs — they'll be merged separately.
raw.pop("$defs", None)

# Rewrite $ref from '#/$defs/X' to '#/components/schemas/X'.
result: dict[str, Any] = _rewrite_refs(raw)
return result
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Materialize the rewritten $defs instead of dropping them.

_build_problem_detail_schema() rewrites #/$defs/* into #/components/schemas/* and then discards the defs, while the reusable JSON responses always assume ApiResponse_NoneType_ already exists. On a schema without those preexisting components, inject_rfc9457_responses() returns dangling refs instead of a valid minimal spec.

Also applies to: 257-268, 384-406

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/openapi.py` around lines 171 - 178,
_build_problem_detail_schema() currently rewrites internal refs via
_rewrite_refs(raw) but pops "$defs" and never materializes them, leaving
inject_rfc9457_responses() with dangling refs; instead, after calling
ProblemDetail.model_json_schema(mode="serialization") and rewriting refs, take
the popped "$defs" (if any) and merge them into the OpenAPI components under
"components":{"schemas":{...}} (renaming keys as needed so '#/$defs/X' ->
'#/components/schemas/X'), ensuring _rewrite_refs() still points to those
component names; apply the same change to the other places where
raw.pop("$defs", None) is used (the other _build_* blocks around the similar
logic) so all rewritten refs have corresponding entries in components.schemas
rather than being discarded.

…ocal agents

Source changes (openapi.py):
- Add logger declaration (CodeRabbit + code-reviewer + conventions-enforcer)
- Replace raw tuples with NamedTuple for _ERROR_RESPONSES (Gemini + code-reviewer)
- Extract 4 helpers from inject_rfc9457_responses to fix 50-line limit (code-reviewer)
- Preserve existing info.description and append RFC section idempotently (CodeRabbit)
- Simplify Conflict check: is_write and method != "delete" (Gemini)
- Expand 400-replacement comment documenting intentional behavior (CodeRabbit)
- Update _rewrite_refs and _build_problem_detail_schema docstrings (code-reviewer)

Test changes:
- Add 13 new tests (98 total): status-matches-HTTP-code, PUT method injection,
  BadRequest negative case on GET, unknown key fallback, $defs absence,
  path-level parameters guard, description preservation
- Move integration test from tests/unit/ to tests/integration/api/ (code-reviewer)
- Use issubset for integration response key check (CodeRabbit)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/openapi.py`:
- Around line 37-39: The module imports a logger but doesn't use it; add
logger.debug and logger.warning calls in the OpenAPI schema processing functions
(e.g., inside functions such as transform_schema, build_components, parse_paths
or any helpers that perform schema transformation) to log entry/exit and key
steps, and use logger.warning when encountering unexpected structures (missing
schemas, unsupported types). Ensure each function logs at DEBUG on start/end and
major decisions, and logs contextual details (object names/keys) in WARNING
messages, using the existing logger variable declared as logger =
get_logger(__name__).
- Around line 376-382: The current block that replaces 400 responses (condition
using status_code and op_responses) unconditionally overwrites any 400 entry;
change it to first detect Litestar's auto-generated ValidationException schema
before replacing by inspecting op_responses["400"]'s schema/description for
Litestar's ValidationException signature (e.g., known description text or JSON
structure), and only call _response_ref(key) and assign to
op_responses[status_code] when that heuristic matches; leave existing custom 400
responses intact when the heuristic does not match.
🪄 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

Run ID: fdb0cc2c-cd9a-4c06-8081-ac9d41d4f3e6

📥 Commits

Reviewing files that changed from the base of the PR and between 48f2661 and 3a7b4c2.

📒 Files selected for processing (4)
  • src/synthorg/api/openapi.py
  • tests/integration/api/__init__.py
  • tests/integration/api/test_openapi_integration.py
  • tests/unit/api/test_openapi.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/integration/api/test_openapi_integration.py
  • tests/unit/api/test_openapi.py
  • src/synthorg/api/openapi.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/integration/api/test_openapi_integration.py
  • tests/unit/api/test_openapi.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/api/openapi.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Files:

  • src/synthorg/api/openapi.py
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Every module with business logic must import logger with `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()` in application code.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : DEBUG logging for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` for all logging calls. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : All error paths must log at WARNING or ERROR level with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Always use `logger` as the variable name for loggers. Never use `_logger` or `log`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/openapi.py
🧬 Code graph analysis (2)
tests/integration/api/test_openapi_integration.py (2)
src/synthorg/api/openapi.py (1)
  • inject_rfc9457_responses (418-451)
src/synthorg/api/app.py (1)
  • create_app (475-635)
tests/unit/api/test_openapi.py (1)
src/synthorg/api/openapi.py (2)
  • _should_inject (326-354)
  • inject_rfc9457_responses (418-451)
🔇 Additional comments (14)
src/synthorg/api/openapi.py (6)

41-154: LGTM!

Constants are well-structured using immutable types (Final, tuple, frozenset, NamedTuple). The error response specifications are comprehensive and cover the expected RFC 9457 error scenarios.


178-206: LGTM!

The _build_problem_detail_schema function correctly handles Pydantic schema generation with appropriate $ref rewriting. The docstring properly documents the assumption that referenced schemas (ErrorCode, ErrorCategory) must already exist in components.schemas.


208-227: LGTM!

The _rewrite_refs function is correctly implemented as a recursive, idempotent transformation. It only rewrites #/$defs/-prefixed refs, leaving already-rewritten refs unchanged.


229-278: LGTM!

Example builders correctly construct both envelope and bare RFC 9457 examples with all required fields. The use of CATEGORY_TITLES and category_type_uri ensures consistency with the error system.


407-412: LGTM!

The _update_info_description function correctly implements idempotent append by checking for the presence of the RFC 9457 section header before appending. This preserves any existing description while avoiding duplicate appends.


418-451: LGTM!

The inject_rfc9457_responses function is well-designed:

  • Deep-copies input to ensure immutability
  • Uses setdefault for defensive handling of missing sections
  • Orchestrates all transformation steps clearly
  • Returns the enhanced copy
tests/unit/api/test_openapi.py (6)

1-13: LGTM!

Module docstring clearly describes the test scope. Imports are minimal and appropriate, including the private _should_inject for direct unit testing of injection logic.


39-91: LGTM!

The _minimal_schema helper and base_schema fixture are well-designed. The base schema includes representative paths covering:

  • Public endpoints (/health, /auth/login, /auth/setup)
  • Authenticated endpoints with various methods (/tasks, /agents)
  • Parameterized paths (/tasks/{task_id}, /agents/{agent_name})

This provides comprehensive coverage for injection rule testing.


97-166: LGTM!

TestProblemDetailSchema provides thorough coverage of the ProblemDetail schema generation including:

  • Schema addition to components
  • Required fields validation
  • Property completeness
  • $ref rewriting to #/components/schemas/
  • Preservation of pre-existing custom schemas
  • Absence of leftover $defs

195-295: LGTM!

TestReusableResponses uses @pytest.mark.parametrize effectively to test all eight RFC 9457 responses. Coverage includes:

  • Dual content types (application/json and application/problem+json)
  • Schema references to envelope and ProblemDetail
  • Example presence and structure
  • HTTP status code consistency in ProblemDetail examples

300-536: LGTM!

TestOperationInjection provides exhaustive coverage of the injection rules:

  • Universal 500 injection
  • Authentication responses (401/403) for non-public endpoints
  • 404 for parameterized paths
  • Conflict (409) for write methods (POST, PUT, PATCH but not DELETE)
  • Rate limiting (429) and service unavailability (503) rules
  • Preservation of custom responses vs. replacement of Litestar defaults
  • Handling of non-operation path entries (e.g., parameters)
  • Defensive fallback for unknown response keys

542-603: LGTM!

TestInfoDescription and TestIdempotencyAndImmutability complete the test coverage with:

  • RFC 9457 documentation verification
  • Content negotiation and error reference checks
  • Preservation of existing description text
  • Idempotency guarantee (same result on repeated calls)
  • Immutability guarantee (input unchanged)
  • Edge case handling (empty paths, missing components)
tests/integration/api/test_openapi_integration.py (2)

1-24: LGTM!

Module docstring and imports are appropriate. The _EXPECTED_RESPONSE_KEYS constant duplicates the one from the unit tests, which is acceptable for test isolation.


27-63: LGTM!

The integration test properly exercises the end-to-end flow:

  • Uses @pytest.mark.integration marker
  • Lazy imports create_app to avoid import-time issues
  • Uses subset check for response keys (future-proof against additional responses)
  • Validates ProblemDetail schema, dual content types, operation injection, and public endpoint exclusion rules

The create_app() call without dependencies is intentional for testing and will trigger an expected warning log without failing.

Comment on lines +37 to +39
from synthorg.observability import get_logger

logger = get_logger(__name__)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Logger imported but never used.

The logger is correctly imported per coding guidelines, but there are no logging calls in this module. Consider adding DEBUG-level logging for schema transformation steps or WARNING-level logging if transformation encounters unexpected structures (e.g., missing expected schemas).

💡 Example logging addition
 def _add_problem_detail_schema(schemas: dict[str, Any]) -> None:
     """Add ``ProblemDetail`` to ``components.schemas`` if absent."""
     if "ProblemDetail" not in schemas:
         schemas["ProblemDetail"] = _build_problem_detail_schema()
+        logger.debug("openapi_schema_enhanced", added_schema="ProblemDetail")
+    else:
+        logger.debug("openapi_schema_skipped", reason="ProblemDetail already exists")

As per coding guidelines, src/**/*.py: "DEBUG logging for object creation, internal flow, entry/exit of key functions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/openapi.py` around lines 37 - 39, The module imports a
logger but doesn't use it; add logger.debug and logger.warning calls in the
OpenAPI schema processing functions (e.g., inside functions such as
transform_schema, build_components, parse_paths or any helpers that perform
schema transformation) to log entry/exit and key steps, and use logger.warning
when encountering unexpected structures (missing schemas, unsupported types).
Ensure each function logs at DEBUG on start/end and major decisions, and logs
contextual details (object names/keys) in WARNING messages, using the existing
logger variable declared as logger = get_logger(__name__).

Add API_OPENAPI_SCHEMA_ENHANCED event constant and a debug log call
in inject_rfc9457_responses to satisfy the logger-imported-but-unused
finding from CodeRabbit's second review pass.
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 99.03846% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.08%. Comparing base (22a25f6) to head (f25bf7f).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/openapi.py 99.02% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #506      +/-   ##
==========================================
+ Coverage   93.06%   93.08%   +0.02%     
==========================================
  Files         501      502       +1     
  Lines       24098    24202     +104     
  Branches     2298     2313      +15     
==========================================
+ Hits        22426    22529     +103     
  Misses       1327     1327              
- Partials      345      346       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…sponses

- Add _is_litestar_validation_400() heuristic that checks for the
  "Validation Exception" schema description — custom 400 responses
  are now preserved instead of unconditionally replaced
- Add debug logging to _add_problem_detail_schema for schema
  transformation visibility
- Add test_custom_400_preserved to verify custom 400s survive injection
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/openapi.py`:
- Around line 54-57: The name _WRITE_METHODS is ambiguous because DELETE is
included but treated specially elsewhere (DELETE is excluded from 409 injection
by the existing 409-handling logic); either rename _WRITE_METHODS to
_BODY_METHODS to reflect that it represents HTTP methods that accept a request
body, or add a concise clarifying comment above the frozenset explaining:
"Includes methods that may carry a request body; DELETE is included for 400/403
handling but is intentionally excluded from 409 injection by the 409-handling
logic." Update all references to _WRITE_METHODS accordingly (or keep the symbol
name and only add the comment) so the intent matches the separate
DELETE-special-case code path.

In `@tests/unit/api/test_openapi.py`:
- Around line 509-521: The test test_non_retryable_example_has_retryable_false
omits the InternalError response which should also assert retryable is False;
update the pytest.mark.parametrize "key" list in tests/unit/api/test_openapi.py
to include "InternalError" so the loop covers that case (the behavior is defined
by inject_rfc9457_responses / the responses mapping that marks InternalError as
retryable=False).
🪄 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

Run ID: b2695e65-c29f-40c4-86fc-f422f147c55a

📥 Commits

Reviewing files that changed from the base of the PR and between cce967a and 39ee6af.

📒 Files selected for processing (2)
  • src/synthorg/api/openapi.py
  • tests/unit/api/test_openapi.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/unit/api/test_openapi.py
  • src/synthorg/api/openapi.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/api/test_openapi.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/api/openapi.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Files:

  • src/synthorg/api/openapi.py
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Every module with business logic must import logger with `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()` in application code.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : DEBUG logging for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` for all logging calls. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : All error paths must log at WARNING or ERROR level with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Always use `logger` as the variable name for loggers. Never use `_logger` or `log`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/api/openapi.py
🧬 Code graph analysis (2)
tests/unit/api/test_openapi.py (1)
src/synthorg/api/openapi.py (2)
  • _should_inject (327-355)
  • inject_rfc9457_responses (448-488)
src/synthorg/api/openapi.py (1)
src/synthorg/api/errors.py (1)
  • category_type_uri (117-126)
🔇 Additional comments (12)
src/synthorg/api/openapi.py (6)

1-40: LGTM! Module structure and imports are well-organized.

The module correctly:

  • Uses the required get_logger(__name__) pattern with structured event constants
  • Imports RFC 9457 error types (ErrorCategory, ErrorCode, ProblemDetail, etc.)
  • Has comprehensive Google-style docstrings explaining the module's purpose

179-206: LGTM! Schema building with clear documentation of assumptions.

The function correctly documents the assumption that ErrorCode and ErrorCategory already exist in Litestar's generated components.schemas. The $defs stripping approach is sound given this documented precondition.


230-306: LGTM! Example generation and reusable response building.

The helper functions correctly:

  • Build examples with all RFC 9457 required fields
  • Use the imported CATEGORY_TITLES and category_type_uri for consistency
  • Create dual content-type responses with proper $ref structures

406-421: LGTM! Extracted steps with appropriate logging.

The _add_problem_detail_schema function demonstrates proper structured logging with:

  • Event constant API_OPENAPI_SCHEMA_ENHANCED
  • Meaningful context keys (step, added, reason)
  • DEBUG level for internal flow as per coding guidelines

448-487: LGTM! Main function is well-designed.

The inject_rfc9457_responses function:

  • Uses copy.deepcopy to ensure immutability of input
  • Gracefully handles missing sections with setdefault
  • Has comprehensive docstring explaining inputs/outputs
  • Logs summary metrics for observability

358-369: The concern about custom 400 responses being incorrectly replaced is inaccurate.

The detection logic is actually defensive: if the description doesn't match "Validation Exception", _is_litestar_validation_400() returns False, and the existing 400 response is preserved. Custom responses are already protected by this fallback behavior, which is validated by the test_custom_400_preserved test.

The actual version-dependent risk is narrower: if Litestar changes the description string in a future version, Litestar's auto-generated 400s may not be replaced (they'll simply pass through unchanged). Since Litestar 2.21.1 is pinned in pyproject.toml and the behavior is covered by integration tests, this is acceptable. If Litestar is upgraded, this assumption can be re-validated at that time.

tests/unit/api/test_openapi.py (6)

1-16: LGTM! Test module structure and imports.

The module:

  • Has comprehensive docstring explaining purpose
  • Uses @pytest.mark.unit as required by coding guidelines
  • Imports both the public function and a private helper for targeted testing

18-91: LGTM! Well-designed fixtures for comprehensive testing.

The fixtures provide good coverage of:

  • Public endpoints (/health, /auth/login, /auth/setup)
  • Authenticated endpoints (/tasks, /agents)
  • Parameterized endpoints (/tasks/{task_id}, /agents/{agent_name})
  • Various HTTP methods (GET, POST, PATCH, DELETE)

97-166: LGTM! Comprehensive ProblemDetail schema tests.

The test class covers all critical aspects:

  • Schema addition and required fields
  • Property completeness
  • Reference reuse for existing schemas
  • Idempotent behavior with pre-existing schemas
  • Clean output (no leftover $defs)

195-294: LGTM! Excellent use of parametrized tests.

The test class effectively uses @pytest.mark.parametrize to:

  • Test all 8 error response types without code duplication
  • Verify dual content types (application/json and application/problem+json)
  • Validate schema references and example structures
  • Check status code consistency between HTTP status and ProblemDetail status field

579-607: LGTM! Info description tests.

The tests verify:

  • RFC 9457 documentation is appended
  • Both content types are mentioned
  • Error reference link is included
  • Existing descriptions are preserved (not overwritten)

612-640: LGTM! Idempotency and immutability tests.

The tests verify the critical contract:

  • Function is idempotent (calling twice yields identical results)
  • Input schema is not mutated
  • Graceful handling of edge cases (empty paths, missing components)

…-retryable test

- Update _WRITE_METHODS comment to explain DELETE inclusion for 400/403
  and its intentional exclusion from 409 injection
- Add InternalError to test_non_retryable_example_has_retryable_false
  parametrize list (retryable=False was untested for this response)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/synthorg/api/openapi.py (1)

180-207: ⚠️ Potential issue | 🟠 Major

Preserve rewritten $defs to avoid dangling OpenAPI references.

On Line 203, $defs is dropped after rewriting refs, but _add_problem_detail_schema() only adds ProblemDetail. When the input schema does not already contain ErrorCode/ErrorCategory, this produces unresolved #/components/schemas/* references.

♻️ Proposed fix
-def _build_problem_detail_schema() -> dict[str, Any]:
+def _build_problem_detail_schema() -> tuple[dict[str, Any], dict[str, Any]]:
@@
-    # Strip $defs — referenced types already exist in components.schemas.
-    raw.pop("$defs", None)
+    defs = raw.pop("$defs", {})
@@
-    result: dict[str, Any] = _rewrite_refs(raw)
-    return result
+    result: dict[str, Any] = _rewrite_refs(raw)
+    rewritten_defs: dict[str, Any] = {
+        name: _rewrite_refs(schema_def)
+        for name, schema_def in defs.items()
+    }
+    return result, rewritten_defs
@@
 def _add_problem_detail_schema(schemas: dict[str, Any]) -> None:
@@
     if "ProblemDetail" not in schemas:
-        schemas["ProblemDetail"] = _build_problem_detail_schema()
+        problem_detail_schema, referenced_defs = _build_problem_detail_schema()
+        for name, schema_def in referenced_defs.items():
+            schemas.setdefault(name, schema_def)
+        schemas["ProblemDetail"] = problem_detail_schema

Also applies to: 407-410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/openapi.py` around lines 180 - 207, The code currently drops
raw "$defs" in _build_problem_detail_schema(), causing dangling refs when
referenced types (e.g., ErrorCode/ErrorCategory) aren't already in
components.schemas; instead of popping and discarding "$defs", capture it, run
_rewrite_refs(raw) to rewrite internal "$ref" targets, then merge the captured
"$defs" entries into the OpenAPI components.schemas namespace (e.g.,
result.setdefault("components", {}).setdefault("schemas", {})[name] = def_schema
for each name, schema in defs.items()) so the rewritten refs resolve; apply the
same change pattern where "$defs" is stripped in _add_problem_detail_schema() to
ensure those definitions are preserved and available under components.schemas.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/synthorg/api/openapi.py`:
- Around line 180-207: The code currently drops raw "$defs" in
_build_problem_detail_schema(), causing dangling refs when referenced types
(e.g., ErrorCode/ErrorCategory) aren't already in components.schemas; instead of
popping and discarding "$defs", capture it, run _rewrite_refs(raw) to rewrite
internal "$ref" targets, then merge the captured "$defs" entries into the
OpenAPI components.schemas namespace (e.g., result.setdefault("components",
{}).setdefault("schemas", {})[name] = def_schema for each name, schema in
defs.items()) so the rewritten refs resolve; apply the same change pattern where
"$defs" is stripped in _add_problem_detail_schema() to ensure those definitions
are preserved and available under components.schemas.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e9bdfb8-f055-40dc-baa3-d8ddedc18611

📥 Commits

Reviewing files that changed from the base of the PR and between 39ee6af and f25bf7f.

📒 Files selected for processing (2)
  • src/synthorg/api/openapi.py
  • tests/unit/api/test_openapi.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • src/synthorg/api/openapi.py
  • tests/unit/api/test_openapi.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/api/openapi.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Files:

  • src/synthorg/api/openapi.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/api/test_openapi.py
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : API error responses use RFC 9457 structured errors: ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Every module with business logic must import logger with `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()` in application code.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : DEBUG logging for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` for all logging calls. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : All error paths must log at WARNING or ERROR level with context before raising.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Always use `logger` as the variable name for loggers. Never use `_logger` or `log`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/api/openapi.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/providers/**/*.py : Mark errors as retryable (`is_retryable=True`) for: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. All other errors are non-retryable and raise immediately.

Applied to files:

  • tests/unit/api/test_openapi.py
🧬 Code graph analysis (2)
src/synthorg/api/openapi.py (1)
src/synthorg/api/errors.py (1)
  • category_type_uri (117-126)
tests/unit/api/test_openapi.py (1)
src/synthorg/api/openapi.py (2)
  • _should_inject (328-356)
  • inject_rfc9457_responses (449-489)

@Aureliolo Aureliolo merged commit 8dd2524 into main Mar 17, 2026
33 checks passed
@Aureliolo Aureliolo deleted the docs/rfc9457-openapi branch March 17, 2026 06:28
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 17, 2026 06:28 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 17, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.1](v0.3.0...v0.3.1)
(2026-03-17)


### Features

* **api:** RFC 9457 Phase 2 — ProblemDetail and content negotiation
([#496](#496))
([30f7c49](30f7c49))
* **cli:** verify container image signatures and SLSA provenance on pull
([#492](#492))
([bef272d](bef272d)),
closes [#491](#491)
* **engine:** implement context budget management in execution loops
([#520](#520))
([181eb8a](181eb8a)),
closes [#416](#416)
* implement settings persistence layer (DB-backed config)
([#495](#495))
([4bd99f7](4bd99f7)),
closes [#450](#450)
* **memory:** implement dual-mode archival in memory consolidation
([#524](#524))
([4603c9e](4603c9e)),
closes [#418](#418)
* migrate config consumers to read through SettingsService
([#510](#510))
([32f553d](32f553d))
* **settings:** implement settings change subscriptions for service
hot-reload ([#526](#526))
([53f908e](53f908e)),
closes [#503](#503)
* **settings:** register API config in SettingsService with 2-phase init
([#518](#518))
([29f7481](29f7481))
* **tools:** add SSRF prevention for git clone URLs
([#505](#505))
([492dd0d](492dd0d))
* **tools:** wire RootConfig.git_clone to GitCloneTool instantiation
([#519](#519))
([b7d8172](b7d8172))


### Bug Fixes

* **api:** replace JWT query parameter with one-time ticket for
WebSocket auth
([#493](#493))
([22a25f6](22a25f6)),
closes [#343](#343)


### Documentation

* add uv cache lock contention handling to worktree skill
([#500](#500))
([bd85a8d](bd85a8d))
* document RFC 9457 dual response formats in OpenAPI schema
([#506](#506))
([8dd2524](8dd2524))


### Maintenance

* upgrade jsdom from 28 to 29
([#499](#499))
([1ea2249](1ea2249))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

docs: document RFC 9457 dual response formats in OpenAPI schema

1 participant