Skip to content

refactor(api): Compose the HTTP surface without the host, and publish the OpenAPI document (#363) - #373

Merged
bitbiter-dev merged 6 commits into
masterfrom
refactor/363-openapi-document
Oct 30, 2026
Merged

bitbiter-dev merged 6 commits into
masterfrom
refactor/363-openapi-document

Conversation

@bitbiter-dev

Copy link
Copy Markdown
Owner

Closes #363

Summary

The API's HTTP surface can now be built without the host. A generator that never starts a host publishes the OpenAPI document from it.

 Program.cs
   AddAppConfiguration                  # INI bootstrap
-  AddOpenApi, AddDatabase, AddAuthServices (reads + checks JWT secret), …
+  AddApiSurface                        # registers everything; reads no secret
   Build, middleware, MapApiEndpoints
+  IStartupValidator.Validate()         # JWT + database options, before the database is touched
   MigrateAndSeedDatabaseAsync
   RunAsync
src/Anichron.OpenApiGenerator/        # not shipped, IsMutationTarget=false
  ApiDocument.ComposeAsync
    AddApiSurface + MapApiEndpoints   # no host start, no DB, no secrets
    EndpointDataSource := app's own data sources   # no UseEndpoints, so no ASP0014
    keyed "v1" IOpenApiDocumentProvider
docs/openapi/v1.json                  # checked in
scripts/derive-openapi.sh [--check]   # CI runs --check beside the schema check
  • Validation runs before migration. ValidateOnStart alone only fires inside RunAsync, after migration and seeding. The explicit Validate() keeps a bad JWT secret from reaching the database.
  • JWT settings use a JwtSettingsValidator, which reports every failure at once. JwtConfiguration is gone.
  • Database settings: Infrastructure's DatabaseConfiguration.Bind reads the raw values without throwing. DatabaseConnectionSettings lists what is missing and builds the connection string. The API checks it through DatabaseSettingsValidator. The Worker's GetConnectionString behaves as before.
  • GET /api/v1/healthz is not in the document. It was agreed on refactor(api): Compose the HTTP surface without the host, and publish the OpenAPI document #363: ApiExplorer cannot see MapHealthChecks endpoints.

Evidence

  • Before: registering the services read the settings, so they needed a 32-byte JWT secret and Postgres configuration. The document could only come from a host that migrates.
    After: with no configuration at all, scripts/derive-openapi.sh writes 25 paths, every mapped route except healthz.
  • ApiDocumentTests.Document_DescribesEveryRouteButHealthz compares the document with the route table. With the data-source override removed it fails, and with it in place it passes.
  • scripts/derive-openapi.test.sh passes. --check passes on a current file and fails naming docs/openapi/v1.json after a deliberate edit, and regenerating restores the file.
  • Start-up checks run through IStartupValidator:
    • a missing, empty or 31-byte JWT secret fails
    • a missing issuer or audience fails
    • a missing database host fails, and with nothing configured all four database failures are reported
  • Full unit suite green (pre-push). Scoped mutation 100% (API 94, Infrastructure 32). mutation-guard.sh passes.

Merge Danger

Door: two-way

Nothing is deployed, and a revert restores the old wiring.

Blast Radius: start-up

  • Every API start now goes through the options and validators. A configuration mistake fails before migration, as before, and now reports every failure at once.
  • DatabaseConfiguration is shared with the Worker. Its throwing path keeps the old messages and order, and its tests pass unchanged.
  • CI gains one step: a stale v1.json fails the checks job.

🤖 Generated with Claude Code

bitbiter-dev and others added 6 commits October 30, 2026 14:51
… the OpenAPI document (#363)

JWT and database settings are bound through options and validated at
start, so registering the API's services needs no secret. AddApiSurface
registers them; Program.cs validates the options before migrating and
seeding, so a bad secret still stops start-up first.

Anichron.OpenApiGenerator composes only the surface and writes the "v1"
document to docs/openapi/v1.json. It replaces DI's EndpointDataSource
with the app's own data sources, so it needs no UseEndpoints (ASP0014)
and no suppression. scripts/derive-openapi.sh regenerates the document
and, with --check, fails when it moved; CI runs the check beside the
schema one. GET /api/v1/healthz stays out of the document:
MapHealthChecks endpoints are invisible to ApiExplorer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… placeholder is equivalent (#363)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery missing value (#363)

DatabaseConfiguration now binds the raw connection settings without
throwing; DatabaseConnectionSettings lists what is missing and builds
the connection string. The Worker's GetConnectionString keeps throwing
the first missing value, and the API validates the bound settings at
start through DatabaseSettingsValidator.

Review follow-ups: CLAUDE.md describes the surface, the generator and
the document; the CI step loses its comment; the registration tests
share one start-up helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TimelineClock appended TimerArmed before FakeTimeProvider registered the
timer. A test that advanced the clock as soon as it read TimerArmed could
land in between on a slow runner; the timer was then scheduled from the
advanced time, never fired, and the timeline went silent for 10 s. CI hit
this in two WorkExecutorTests on PR #373. Recording after registration
closes the gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bitbiter-dev
bitbiter-dev merged commit 708d246 into master Oct 30, 2026
17 checks passed
@bitbiter-dev
bitbiter-dev deleted the refactor/363-openapi-document branch October 30, 2026 03:12
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.

refactor(api): Compose the HTTP surface without the host, and publish the OpenAPI document

1 participant