Skip to content

Add header API versioning for v1 and v2 - #733

Merged
thomasluizon merged 3 commits into
mainfrom
thomasluizon/ticket-1416-api-versioning
Oct 18, 2026
Merged

thomasluizon merged 3 commits into
mainfrom
thomasluizon/ticket-1416-api-versioning

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Requests without api-version and with api-version: 1.0 keep the existing v1 contract. api-version: 2.0 selects the same handlers under v2, while unsupported versions return HTTP 400 with a clear Problem Details error. Existing routes and DTO shapes are preserved.

Closes thomasluizon/orbit-tickets#1416

The API project pins Asp.Versioning.Mvc and Asp.Versioning.Mvc.ApiExplorer to 10.2.1. All 31 controllers declare both versions. ServiceCollectionExtensions.Infrastructure.cs configures header selection, the implicit v1 default, supported-version reporting, and CORS permission for the new header. References to Orbit's HTTP helper are qualified where the dependency introduces a conflicting type name.

The existing Microsoft OpenAPI generator produces src/Orbit.Api/openapi.json for v1 and src/Orbit.Api/openapi_v2.json for v2. Both describe 203 operations and 158 schemas. Scalar lists both documents, the existing breaking-change gate continues comparing v1 at its original path, and .github/workflows/test.yml checks freshness of both snapshots. src/Orbit.Api/CLAUDE.md documents separate v2 DTOs/actions for breaking changes and v1 retirement through MinSupportedVersion after client adoption.

Test evidence

  • dotnet build Orbit.slnx: exit 0, zero errors. Warnings include existing package/compiler warnings and versioning advice for the deliberately configured default and existing OpenAPI generator.
  • dotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~ApiVersioningTests|FullyQualifiedName~SkipUndoContractTests': 14 passed. Tests invoke the real routing middleware directly without starting a server. They cover omitted/v1/v2 responses for habits, config, and profile; profile errors; v2 selection and v9 rejection for every controller route; malformed versions; header-only selection; production CORS policies; and document version isolation.
  • dotnet test tests/Orbit.Domain.Tests --no-build: 666 passed, exit 0.
  • dotnet test tests/Orbit.Application.Tests --no-build: 5,466 passed, exit 0.
  • dotnet test tests/Orbit.Infrastructure.Tests --no-build: 3,355 passed, exit 0.
  • dotnet test tests/Orbit.Analyzers.Tests --no-build: 32 passed, exit 0.
  • oasdiff breaking origin/main:src/Orbit.Api/openapi.json src/Orbit.Api/openapi.json --fail-on ERR: exit 0, no breaking changes. Both committed snapshots match build output.
  • The first full infrastructure run observed SkipUndoContractTests.GeneratedOpenApi_AppendsUndoAndOptionalSkipId_KeepingSkipNoContent fail because its unchanged assertion expected every parameter to be a path parameter. The updated assertion preserves the exact id/skipId path contract and separately verifies that api-version is an optional header. The focused rerun passed all 14 tests.

External interface evidence

  • Live NuGet catalogs confirmed 10.2.1 for both packages: MVC versions and API Explorer versions. After restore, both installed .nuspec files identified source commit b4e6e0f41d3f9fcf7042dc364a45ecbc38b88e4f. The installed XML API descriptions and matching source confirmed versioning options, header reader, and API Explorer grouping. Re-derive by restoring the API project and inspecting those package files under the NuGet package cache.
  • The error assertions use the actual library producer, rather than a mocked response: EndpointProblem sets code, title, and status. Routing tests observed HTTP 400 with code: UnsupportedApiVersion; successful controller requests reported api-supported-versions: 1.0, 2.0.
  • Microsoft's installed API descriptions and OpenApiOptions source confirm that named documents filter descriptions by GroupName. The document generator confirms the special v1 filename and _v2 suffix; the build emitted both files at those exact paths.
  • Scalar 2.18.0's installed XML descriptions and matching source confirm AddDocument(documentName, title, isDefault: ...). CORS tests evaluate the real ICorsService against production policies; their CorsResult properties were confirmed in the installed ASP.NET Core reference XML.

Assumptions

  • Kept openapi.json as the v1 snapshot and added openapi_v2.json, rejecting a renamed v1 snapshot because that would change the existing compatibility gate's contract path.
  • Used versioned MVC API Explorer with the existing Microsoft generator, rejecting Asp.Versioning.OpenApi: that package also added unrelated validation enum metadata and made the v1 comparison report five breaking parameter restrictions. The selected integration preserves the existing schemas and passes the v1 comparison.

@sonarqubecloud

Copy link
Copy Markdown

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes Checked header-based version selection, existing-client compatibility, all controller declarations, generated contracts, and regression coverage.

  • Version routing: All 31 controllers support v1 and v2 on existing routes, omitted headers default to v1, and unsupported versions return HTTP 400.
  • HTTP compatibility: CORS permits the version header and exposes supported-version headers; existing mobile and web clients continue using implicit v1 without changes.
  • API documents: Separate v1/v2 documents and Scalar entries preserve all 203 operations and 158 schemas. Structural comparison against the base confirmed no changes beyond document metadata and version-header parameters.
  • Verification: All 14 focused routing/contract tests and the full 3,355-test infrastructure suite passed. Regenerating both OpenAPI documents left the worktree clean. Other test suites were not rerun.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@thomasluizon
thomasluizon merged commit ac8b34a into main Oct 18, 2026
28 checks passed
@thomasluizon
thomasluizon deleted the thomasluizon/ticket-1416-api-versioning branch October 18, 2026 00:18
thomasluizon added a commit that referenced this pull request Oct 18, 2026
* fix: pin analyzer test Pkcs dependency to patched version (#728)

(cherry picked from commit 61b0090)

* fix: shard pull request domain mutation testing (#729)

(cherry picked from commit bcfd81e)

* Promote the API redesign with production compatibility checks (#723)

* Bring the API redesign tree onto main for #1413

* Preserve calendar connection detection for shipped clients

* Prove API and migrated schema compatibility with shipped clients

* Trim whitespace in the archived main schema proof

* Require the production health response to report the released commit

* Bind initial approval previews to stored pending operations

* fix: return the bulk skip refresh outcome and split the undo skip query

SonarCloud failed the quality gate on two reliability findings in code this pull request brings to main:
the bulk skip conflict check read a flag only a lambda wrote (S2583), and the undo skip check loaded
Tags and Goals in one query (S8733). The transaction now returns whether every habit refreshed, and the
undo skip check splits its query. A new case covers the concurrent change conflict; it failed with the
refresh outcome forced to true and passes with the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5526c9b)

* Add per-account feature flag targeting and admin controls (#724)

* feat: resolve feature flags per account

* feat: administer account feature flags through MCP and REST

* test: exclude feature flag targets from rate limit fixture

* fix: reconcile account flag tests and migration metadata with redesign

(cherry picked from commit 1b886bd)

* Add header API versioning for v1 and v2 (#733)

* Add header API versioning with v1 compatibility

* Verify versioned OpenAPI and browser header compatibility

* Preserve skip undo path contract with optional version header

(cherry picked from commit ac8b34a)

* fix: expose the config response in OpenAPI (#734)

(cherry picked from commit a9d8dd5)

* Route production redesign accounts through Cloudflare (#735)

* Add cookie-based routing for the production redesign web service

* Document web routing rollout, permissions, and rollback checks

* Fix web routing coverage and disable infra install scripts

* chore: regenerate the v2 OpenAPI document on main

orbit-api#733 added openapi_v2.json and orbit-api#734 typed the /api/config
response in parallel, so main carries a v2 document without the typed config
schema and the Build check's stale-document step fails on every merge with
main. dotnet build src/Orbit.Api regenerates it; openapi.json is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9c64770)

* Fix habit edit gating, end dates, and reminder updates (#736)

* fix: validate habit updates with the resulting time range

* fix: preserve end dates when creating root habits

* fix: gate slip alert activation and preserve unchanged reminders

* fix: compare effective reminder rules across stored formats

(cherry picked from commit 7472203)

* Reduce mutation and guard CI jobs (#737)

* Select mutation shards from changed projects

* Consolidate guards and filter artifact generation

* Skip project documentation in mutation selection

* Validate mutation base refs and restrict Git PATH

* Read mutation shard changes from standard input

(cherry picked from commit b802f9e)

* test: strengthen habit and goal ownership coverage (#739)

(cherry picked from commit 0e32628)

* test: verify checkout session user metadata (#740)

(cherry picked from commit da2f53a)

* fix: protect OAuth code delivery with Turnstile (#741)

(cherry picked from commit 1149606)

* fix: enforce chat interaction scope before loading context (#743)

(cherry picked from commit 98faf4e)

* fix: erase accountability pairs during account deletion and reset (#745)

* fix: erase accountability pairs during account deletion and reset

* test: preserve migration compatibility checks after accountability cleanup

(cherry picked from commit a9e5fb9)

* Fix goal sync dates and deadline reminder counts (#747)

* fix: compare streak sync dates in the user timezone

* fix: show actual days remaining in goal deadline reminders

* test: configure timezone for streak scheduler fixtures

(cherry picked from commit 0e63842)

* Manage staging landing routing in Terraform (#742)

(cherry picked from commit abdd738)

* Restrict Web Push endpoints to supported push services (#1503) (#751)

Web Push subscriptions accept only HTTPS endpoints on the FCM, Mozilla, Apple and WNS push hosts, and the push HTTP client follows no redirects. Replaces orbit-api#748 as one commit so no commit carries the high-entropy sample fixture.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d9f847a)

* Erase account uploads before permanent deletion (#1507) (#750)

* fix: erase uploaded objects before completing account deletion

* fix: refuse upload reads after permanent account deletion

* fix: grant uploads bucket deletion permissions and verify provider wiring

* test: align storage startup checks with erasure requirements

* fix: retire deleted Supabase upload storage

* test: configure upload erasure providers in deletion fixtures

Share the existing object-store fake across account deletion and scheduler fixtures after merging main. Preserve accountability assertions and production provider requirements for thomasluizon/orbit-tickets#1507.

(cherry picked from commit fa33a94)

* fix: limit anonymous auth and waitlist by email and source IP (ORB-108) (#749)

* fix: enforce source IP ceilings alongside anonymous email limits

* test: cover both rate limit dimensions before bot verification

* fix: isolate BFF email rate limits by verified client IP

* fix: generate and persist BFF forwarding secrets

(cherry picked from commit de0f42f)

* Limit access tokens to fifteen minutes (#738)

* fix(auth): limit access tokens to fifteen minutes

* fix(auth): align managed JWT lifetime and prove logout rejection

(cherry picked from commit ade7016)

* fix(auth): equalize account provisioning work (ORB-96) (#744)

* test(auth): expose registration dependent sign-in work

* fix(auth): perform uniform registration work before sign-in responses

* test(auth): cover expired codes and tracked upsert conflicts

* fix(auth): isolate signup retries and restore provisioning guards

* fix(auth): recover signup work after dispatch failures

* fix(auth): roll back failed signup referrals before retry

(cherry picked from commit 1bd0d26)

* fix: expose redesign controllers in both API versions

Apply the carried version declarations to the Notion and proactive controllers and regenerate the v2 contract. Refs thomasluizon/orbit-tickets#746.

* fix: honor account flags in proactive engine

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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.

1 participant