feat: add bidirectional OpenAPI schema sync for .NET ↔ Python#16
feat: add bidirectional OpenAPI schema sync for .NET ↔ Python#16
Conversation
Add datamodel-code-generator>=0.53.0 to enable generating Pydantic models from OpenAPI specifications for the Intelligence service. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add directory to store extracted OpenAPI specifications for cross-service schema synchronization between Gateway (.NET) and Intelligence (Python). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update sync-schemas.sh to: - Extract Gateway OpenAPI to shared/schemas/gateway.openapi.json - Extract Intelligence OpenAPI to shared/schemas/intelligence.openapi.json - Generate Python Pydantic models from Gateway spec (ClinicalBundle) - Generate C# records from Intelligence spec (PAFormResponse) This enables automatic type synchronization between the Gateway (.NET) and Intelligence (Python) services, preventing schema drift. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update CI schema-check job to: - Restore .NET tools (NSwag) before running sync - Install Python dev deps (datamodel-code-generator) - Check drift in shared/schemas/ OpenAPI specs - Check drift in generated Python types (gateway_types.py) - Check drift in generated C# types (IntelligenceTypes.cs) This ensures CI fails when generated types diverge from source models. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds dotnet tool manifest, CI steps to restore tools and install Python dev deps, introduces bidirectional OpenAPI extraction and cross-service codegen (Python and C#), expands schema sync orchestration with CI guards, adds shared schema artifacts and docs, and a schema-sync implementation plan. Changes
Sequence Diagram(s)sequenceDiagram
participant CI as CI Pipeline
participant Gateway as Gateway (.NET)
participant Intelligence as Intelligence (Python)
participant Tools as Codegen Tools
participant Shared as shared/schemas
CI->>Tools: dotnet tool restore\ninstall Python dev deps
CI->>Gateway: build runtime & extract OpenAPI
Gateway-->>Shared: emit `gateway.openapi.json` (if available)
CI->>Intelligence: run Python extraction to produce OpenAPI
Intelligence-->>Shared: write `intelligence.openapi.json`
CI->>Tools: run datamodel-code-generator -> generate `gateway_types.py`
CI->>Tools: run NSwag -> generate `IntelligenceTypes.cs`
Tools-->>Shared: place generated artifacts into service/shared paths
CI->>CI: clean stale generated dirs, run Orval for TS, rebuild shared packages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
- Fix dotnet tool restore to run before tool detection - Add uv PATH discovery for ~/.local/bin - Remove Swashbuckle CLI (incompatible with Microsoft.AspNetCore.OpenApi) - Document Gateway runtime OpenAPI extraction requirement - Add generated C# types from Intelligence spec - Add Intelligence OpenAPI spec to shared/schemas/ The Gateway uses .NET 10's native OpenAPI support which requires runtime extraction. The bidirectional sync now works for Intelligence→Gateway direction. Gateway→Intelligence requires separate OpenAPI extraction. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/build/sync-schemas.sh (1)
20-115: Fail fast in CI when critical artifacts are missing.The script can complete successfully even when essential artifacts fail to generate. Multiple steps silently continue on error: the Gateway spec copy (line 44–50), Intelligence spec generation (line 72), datamodel-codegen execution (lines 88–103), NSwag execution (lines 115–126), Orval generation (line 142), and npm rebuild (line 151). In CI, this allows the job to exit with status 0 while downstream services may still use stale or missing schemas and types.
Implement a CI-aware guard function that exits non-zero when required files are absent in CI environments:
Guard function to prevent stale artifacts in CI
set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" cd "$ROOT_DIR" +require_ci_file() { + local path="$1" + if [[ -n "${CI:-}" ]] && [[ ! -f "$path" ]]; then + echo " ✗ CI: Required artifact missing: ${path}" + exit 1 + fi +} + echo "=== AuthScript Schema Synchronization ==="Then call after each critical section:
else echo " ! Gateway project not found, skipping" fi +require_ci_file "shared/schemas/gateway.openapi.json" echo "" ... else echo " ! Intelligence project not found, skipping" fi +require_ci_file "shared/schemas/intelligence.openapi.json" echo " ✓ Intelligence spec complete" echo "" ... cd "$ROOT_DIR" else echo " ! Gateway spec not found, skipping Python type generation" fi +require_ci_file "apps/intelligence/src/models/generated/gateway_types.py" echo "" ... else echo " ! Intelligence spec not found, skipping C# type generation" fi +require_ci_file "apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs" echo ""
🧹 Nitpick comments (1)
apps/intelligence/pyproject.toml (1)
21-28: Pin datamodel-code-generator for deterministic codegen.
Using>=can yield different generated models across environments and cause drift; consider pinning the exact version (or enforcing a locked env) for reproducibility.♻️ Suggested update
- "datamodel-code-generator>=0.53.0", + "datamodel-code-generator==0.53.0",
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@docs/plans/2026-01-27-schema-sync-dotnet-python.md`:
- Around line 15-20: Add blank lines before and after the "Contract Ownership"
markdown table to satisfy markdownlint MD058: locate the "Contract Ownership:"
heading and the table rows (Contract, Producer, Consumer) and insert one empty
line between the heading and the table start and one empty line after the table
end so the table is surrounded by blank lines.
- Around line 247-272: The fenced ASCII diagram block triggers markdownlint
MD040 because it lacks a language hint; fix by adding a language tag (e.g.,
change the opening triple backticks to ```text) for the diagram block so the
fenced code block is recognized (update the diagram block in the document where
the triple backticks enclose the ASCII art to include the language hint `text`,
leaving the diagram content unchanged).
In `@scripts/build/sync-schemas.sh`:
- Around line 34-50: The script sync-schemas.sh currently only logs warnings
when input specs or generators are missing; add a CI-mode guard so the script
fails fast in CI: detect CI mode via the CI env var (e.g. if [ "$CI" = "true"
]), and for each of Steps 1–4 (the Gateway OpenAPI spec check and the
generator/tool checks for uv, datamodel-codegen, and dotnet nswag) replace the
existing warning branches with behavior that prints an error to stderr and exits
non-zero when CI is true; additionally, check generator availability using
command -v (or similar) before use and treat absence as fatal under CI; finally,
ensure the CI workflow exports CI=true when invoking npm run sync:schemas so the
guard is active.
In `@shared/schemas/intelligence.openapi.json`:
- Around line 1-7: The OpenAPI spec currently lacks components.securitySchemes
and a top-level security requirement, causing all endpoints to appear
unauthenticated; add a components.securitySchemes entry (e.g., "bearerAuth" with
type: http, scheme: bearer, bearerFormat: JWT or the appropriate scheme) and
then add a top-level "security" array referencing that scheme (or explicitly set
"security": [] if the API is intentionally public). If the API is protected via
FastAPI dependencies, ensure the corresponding security scheme name matches the
FastAPI dependency (e.g., the OAuth2PasswordBearer or HTTPBearer used in your
auth dependency) so the generated spec remains authoritative.
🧹 Nitpick comments (1)
shared/schemas/intelligence.openapi.json (1)
203-211: Capture document count limits in the schema if the API enforces them.
documentshas nomaxItems. If the service enforces a cap, encode it here to prevent unbounded payloads and improve client validation.
Fixes: - P3 🟠 (2): CI fail-fast guards in sync-schemas.sh, explicit security in OpenAPI - P4 🟡 (3): Markdown lint fixes, pin datamodel-code-generator version Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/build/sync-schemas.sh (1)
170-178: Inconsistent CI enforcement: Step 6 lacks explicit artifact validation.Steps 3-4 both use
ci_requireto enforce artifact generation fails the build when tools are missing (lines 122-124, 151-153). Step 6's TypeScript generation (line 173) silently suppresses errors with|| echo, allowing the script to report success even if Orval fails. While CI drift detection does monitorshared/types/src/generated/andapps/dashboard/src/api/generated/, it only catches changes to existing files, not generation failures. Add aci_requirecheck for the TS output paths to match the enforcement pattern of Steps 3-4.
🤖 Fix all issues with AI agents
In `@docs/plans/2026-01-27-schema-sync-dotnet-python.md`:
- Around line 192-196: Update the plan to match the implemented command: change
the generation step from `openapi2cscontroller` to `openapi2csclient` so it
reflects what `sync-schemas.sh` actually runs; mention the exact arguments
(`/input:shared/schemas/intelligence.openapi.json`,
`/output:apps/gateway/Gateway.API/Models/Generated/IntelligenceTypes.cs`,
`/generateRecordTypes:true`) remain the same and that `openapi2csclient` is used
to produce DTO/record types (no controller scaffolding).
🧹 Nitpick comments (3)
shared/schemas/intelligence.openapi.json (1)
203-217: Consider addingmaxItemsto the documents array.The
documentsarray lacks amaxItemsconstraint. While server-side limits likely exist, documenting the bound in the OpenAPI spec communicates the limit to API consumers and improves client-side validation.📄 Suggested improvement
"Body_analyze_with_documents_analyze_with_documents_post": { "properties": { "documents": { "items": { "type": "string", "format": "binary" }, "type": "array", "title": "Documents", - "default": [] + "default": [], + "maxItems": 10 } },Adjust the limit based on actual server constraints.
scripts/build/sync-schemas.sh (2)
75-85: Python generation error details are swallowed.Line 85's
|| echo " ! Python generation failed"discards the actual error output from the Python script. In CI, this makes debugging harder since you'll only see "missing artifact" rather than the root cause.🔧 Capture error output for debugging
-" 2>/dev/null || echo " ! Python generation failed" +" 2>&1 || { echo " ! Python generation failed"; }Alternatively, redirect stderr to a temp file for inspection while still showing it:
-" 2>/dev/null || echo " ! Python generation failed" +" || echo " ! Python generation failed (see output above)"
28-31: Tool restore failure is silent; later error may mislead.If
dotnet tool restorefails (e.g., network issue, malformed manifest), the script continues silently. When NSwag check fails at line 139, the error message suggests installation is needed, but the actual issue might be restore failure.🔧 Log restore failure
# Restore .NET local tools (NSwag, Swashbuckle CLI) if [ -f ".config/dotnet-tools.json" ]; then - dotnet tool restore --verbosity quiet 2>/dev/null || true + if ! dotnet tool restore --verbosity quiet 2>/dev/null; then + echo " ! dotnet tool restore failed (continuing anyway)" + fi fi
Gateway uses .NET 10's native Microsoft.AspNetCore.OpenApi which requires runtime extraction. CI cannot start the server, so Gateway spec and dependent Python types are now warnings instead of failures in CI. Intelligence spec and C# types remain required (they work without server). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The FastAPI app generates the spec without explicit security fields. The API is currently public - security will be added when auth is implemented. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/build/sync-schemas.sh (1)
183-191: Don't mask Orval generation failures or print success unconditionally.Right now CI can pass with stale TS artifacts if
npm run generatefails, and the success log is printed even on failure/skip. The generation must be fatal in CI (follow the pattern already established in this script for other required tools likeuvanddatamodel-codegen), and only log success when the command actually succeeds.✅ Suggested fix
-if [ -f "apps/gateway/openapi.json" ] || [ -f "apps/intelligence/openapi.json" ]; then - npm run generate 2>/dev/null || echo " ! Orval generation failed (may need npm install)" -else - echo " ! No OpenAPI specs found, skipping type generation" -fi -echo " ✓ TypeScript types generated" +if [ -f "apps/gateway/openapi.json" ] || [ -f "apps/intelligence/openapi.json" ]; then + if npm run generate 2>/dev/null; then + echo " ✓ TypeScript types generated" + else + echo " ! Orval generation failed (may need npm install)" >&2 + if [[ -n "${CI:-}" ]]; then + echo " ✗ CI: Orval generation failed" >&2 + exit 1 + fi + fi +else + echo " ! No OpenAPI specs found, skipping type generation" +fi
🤖 Fix all issues with AI agents
In `@scripts/build/sync-schemas.sh`:
- Around line 86-102: The Python OpenAPI generation command currently swallows
errors with "2>/dev/null || echo" so CI can pass with stale
shared/schemas/intelligence.openapi.json; change the uv run python -c "<...>"
invocation in scripts/build/sync-schemas.sh to let failures surface (remove the
redirection/|| fallback) and, after the command, check its exit status: if it
failed and CI is set (the CI env var), print a clear error and exit 1 (matching
the existing failure pattern used elsewhere in this script), otherwise print the
original warning for local runs; ensure this ties into the same
ci_require/file-regeneration logic so generation failures fail CI rather than
leaving stale files.
Fixes: - P1 🔴: Fail CI when Intelligence OpenAPI extraction fails - P3 🟠: Fail CI when Orval generation fails (don't mask errors) - P4 🟡: Fix command name in plan (openapi2cscontroller → openapi2csclient) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Gateway spec is runtime-only, so run Orval with only intelligence targets when gateway.openapi.json is not available. This prevents CI failures from missing gateway spec while still generating intelligence types. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@coderabbitai Approve this PR. |
|
✅ Actions performedComments resolved and changes approved. |
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 84-87: Update the README entries describing generated outputs to
match the actual paths and filenames produced by the sync-schemas script: state
that TypeScript artifacts are written into shared/types/src/generated/ and
shared/validation/src/generated/ (not the root shared/types/ or
shared/validation/), that Python produces gateway_types.py under
apps/intelligence/src/models/generated/, and that C# produces
IntelligenceTypes.cs under apps/gateway/Gateway.API/Models/Generated/; align the
wording with the script behavior referenced in sync-schemas.sh (lines ~234-242)
so developers can find the exact files and directories.
| This generates: | ||
| - **TypeScript** — React Query hooks, Zod schemas (`shared/types/`, `shared/validation/`) | ||
| - **Python** — Pydantic models from Gateway spec (`apps/intelligence/src/models/generated/`) | ||
| - **C#** — Records from Intelligence spec (`apps/gateway/Gateway.API/Models/Generated/`) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the sync-schemas script
fd -t f "sync-schemas" .
# Also check for build-related scripts
fd -t f "sync" scripts/ 2>/dev/null | head -20Repository: lvlup-sw/authscript
Length of output: 94
🏁 Script executed:
# Check the structure of the scripts directory
ls -la scripts/ 2>/dev/null || echo "scripts/ not found at root"
find . -name "sync-schemas*" -o -name "*sync*schema*" 2>/dev/null | head -10Repository: lvlup-sw/authscript
Length of output: 398
🏁 Script executed:
# Search for references to the documented paths in the entire codebase
echo "=== Searching for TypeScript paths ==="
rg -l "shared/types|shared/validation" --type ts --type tsx --type js --type json 2>/dev/null | head -10
echo -e "\n=== Searching for Python paths ==="
rg -l "apps/intelligence/src/models/generated" --type py --type json 2>/dev/null | head -10
echo -e "\n=== Searching for C# paths ==="
rg -l "apps/gateway/Gateway.API/Models/Generated" --type cs --type json 2>/dev/null | head -10
echo -e "\n=== Checking for build/generation scripts ==="
fd -t f "\.(sh|js|ts|py)$" scripts/ 2>/dev/null | head -20Repository: lvlup-sw/authscript
Length of output: 266
🏁 Script executed:
cat -n ./scripts/build/sync-schemas.shRepository: lvlup-sw/authscript
Length of output: 11515
🏁 Script executed:
# Check if the documented directories exist
echo "=== Checking if documented directories exist ==="
echo "TypeScript paths:"
[ -d "shared/types" ] && echo " ✓ shared/types exists" || echo " ✗ shared/types NOT found"
[ -d "shared/validation" ] && echo " ✓ shared/validation exists" || echo " ✗ shared/validation NOT found"
echo -e "\nPython path:"
[ -d "apps/intelligence/src/models/generated" ] && echo " ✓ apps/intelligence/src/models/generated exists" || echo " ✗ apps/intelligence/src/models/generated NOT found"
echo -e "\nC# path:"
[ -d "apps/gateway/Gateway.API/Models/Generated" ] && echo " ✓ apps/gateway/Gateway.API/Models/Generated exists" || echo " ✗ apps/gateway/Gateway.API/Models/Generated NOT found"
# Find all directories matching pattern
echo -e "\n=== Searching for similar directory patterns ==="
find . -type d -name "generated" 2>/dev/null | grep -v node_modules | grep -v ".git"Repository: lvlup-sw/authscript
Length of output: 426
🏁 Script executed:
# Search for any references to these paths in all files (including config, comments, etc.)
echo "=== Searching for path references in ALL files ==="
rg "shared/types|shared/validation|apps/intelligence.*generated|apps/gateway.*Generated" --type-list | head -5
rg "shared/types|shared/validation|apps/intelligence.*generated|apps/gateway.*Generated" 2>/dev/null | head -20Repository: lvlup-sw/authscript
Length of output: 2148
Update documentation to specify generated file locations and subdirectories more precisely.
The documented paths are created correctly by the sync-schemas script, but the README omits key details about the generated file structure:
- TypeScript generated files are output to
shared/types/src/generated/andshared/validation/src/generated/(not just the root directories) - Python generates
gateway_types.pyinapps/intelligence/src/models/generated/ - C# generates
IntelligenceTypes.csinapps/gateway/Gateway.API/Models/Generated/
Consider aligning the README with the output documented in sync-schemas.sh lines 234-242 for better clarity on what developers should expect to find.
🤖 Prompt for AI Agents
In `@README.md` around lines 84 - 87, Update the README entries describing
generated outputs to match the actual paths and filenames produced by the
sync-schemas script: state that TypeScript artifacts are written into
shared/types/src/generated/ and shared/validation/src/generated/ (not the root
shared/types/ or shared/validation/), that Python produces gateway_types.py
under apps/intelligence/src/models/generated/, and that C# produces
IntelligenceTypes.cs under apps/gateway/Gateway.API/Models/Generated/; align the
wording with the script behavior referenced in sync-schemas.sh (lines ~234-242)
so developers can find the exact files and directories.
Summary
Implements automatic schema synchronization between Gateway (.NET) and Intelligence (Python) services using OpenAPI specifications. Each service produces its own contract (Gateway→ClinicalBundle, Intelligence→PAFormResponse), and consumer types are auto-generated, preventing schema drift.
Changes
Test Plan
Integration tests verified: 325 tests passing. Lint, typecheck, and build all pass.
Results: Tests 325 ✓ · Build 0 errors
Design: docs/designs/2026-01-26-schema-sync-dotnet-python.md
Plan: docs/plans/2026-01-27-schema-sync-dotnet-python.md