Commit Graph
3 Commits
Author SHA1 Message Date
Marc-André Bombeck f4c0eb0ab9 feat(api): publish retirement in the contract, the capability read, and the coverage guard
Three surfaces, one source. The registry the proxy answers 410 from now
also emits the tombstones into `docs/api/openapi.yaml` and rides on
`GET /api/meta/capabilities`, so a retirement cannot be honoured at
runtime and invisible everywhere a client could have read it first.

The contract publishes each retired path with 410 as its only response,
the removing version, and the replacement. `deprecated: true` is
imprecise — the word means "still works, stop using it", and these do not
work — but it is what makes a generator warn at the call site, and the
description carries the exact truth the flag approximates. `security: []`
because the answer is produced before any handler and reads no
credential; publishing it as authenticated would tell a client to sign in
to be told a path is gone.

The capability read carries the same list. That surface is the one a
client already polls on launch, so a retirement can reach it before the
failing call rather than through it.

The coverage guard gains a third state. Published and exempted both
describe a route that exists, so removing one used to leave exactly one
legal move — erase it from the contract as well — and that move is
invisible to every client already calling it. A registered path is now
required to be absent from `src/app` AND present in the contract, which
is the one combination the phantom-path case rejected outright. Both ways
out are named in the failure message.

Its docblock says what the state cannot do, because the first draft
overclaimed: a check over one commit's state cannot see a deletion, so it
cannot insist that a removal should have been registered. Proving that
needs the previous release tag, and the job running this suite checks out
at depth one with no tags, which would make the check one that silently
cannot fail. What is held instead is the registration's integrity, in
three directions and with the mutation for each written down.

The sharing-denial guard learns the same state: a retired path resolves
no record fence because it reaches no handler.
2026-08-23 14:06:52 +02:00
Marc-André Bombeck 965d41f1ed Say how every operation is authenticated
The document defined bearerAuth and cookieAuth and referenced neither,
so the schemes were decoration: a client generated from the contract
wires no credential and reads the whole surface as open.

The default now offers the two as alternatives, and the ten operations
reachable before a credential exists opt out with an empty array. The
opt-out list is derived from what each handler accepts, not from the
proxy's public-path list — /api/ingest/medication is on that list and
requires a Bearer token, so reading it as the answer would publish an
authenticated route as open. Writing the list from the proxy is exactly
the mistake the guard caught: /api/auth/oidc/callback went on it and is
not published at all.
2026-08-22 15:03:28 +02:00
Marc-André Bombeck 79a1395104 Check the routes against the contract, and the contract against itself
The drift check compares the registry to the emitted YAML. It has never
compared the routes on disk to the registry, so a route that was never
registered produced no drift and no failure: the document stayed
internally consistent while going quietly incomplete. Two hundred and
twenty-two paths reached that state.

The coverage guard walks src/app, reads the verbs each module exports,
and requires every verb to be either published or named with a reason.
The reasons are kinds rather than free text, because these fall into a
small number of genuinely different shapes and writing one sentence
seventy-three times would hide the entry whose sentence is different.

The security guard is the same idea one level down. The document defined
bearerAuth and cookieAuth and referenced neither, so a generated client
wired no credential and read the whole surface as open. The schemes
being present is what hid it. The document now declares them as
alternatives, and the guard checks the reference rather than the
declaration.

Both are red until the routes are documented, which is the point.
2026-08-22 13:18:33 +02:00