Commit Graph
5 Commits
Author SHA1 Message Date
Marc-André Bombeck 3786deb503 feat(api): publish the v1.43 contract with routes that answer 501 until filled
Every new endpoint of the contract exists, wrapped in apiHandler, with its
authentication, module gate and request validation, and answers
501 not_implemented until its behaviour lands. The OpenAPI document carries
the new operations and schemas and the additive response fields on the
changed routes. The four server feature flags are published as false.

contract-stub-inventory.test.ts registers the stubbed routes; each one
leaves the register with the change that fills it. The route, sharing,
session, security, module-gate and outcome inventories name the new routes
deliberately, and the error-code catalogue lists the codes the routes emit
today.
2026-10-15 21:49:02 +02:00
Marc-André Bombeck e63be67b5a feat: lay down the shared v1.42 contract
Land every file the v1.42 work shares before any of it is built, so the
parts built side by side never edit the same file.

Schema and migrations 0379-0386: the HEALTH_CONNECT measurement source,
ImportJob.kind, the air-quality columns and the per-user switch on the
environment tables, sealed environment locations with nullable plaintext,
managed-profile handover links, the per-type HealthKit arrival ledger, lab
unit-conversion provenance, and user-defined vaccines. Every new column is
annotated for the column-reader sweep.

HEALTH_CONNECT reaches every exhaustive list: the source labels, the
read enum (not the write allowlist), the source-priority ladder cap, now
the number of sources rather than a fixed eight, and the write-path smoke
test. RMSSD gets its own priority class and a 300 ms ceiling. The batch
syncTrigger gains `manual`, with its label on the Apple Health card.

Queues for the Health Connect import, the compaction-tombstone purge and
the measurement-table maintenance are registered with stub handlers;
host-metric sampling moves to every five minutes. Environment and work_mem
variables reach the compose whitelist, the production example and the
manifest. The proxy admits the handover link and redirects it at the edge,
and the Health Connect upload bypasses the proxy body limit.

The new routes answer 501 behind their final authentication and are
published in the contract; backup, wipe and key rotation classify the new
models and columns; the seven message bundles carry the base copy. Guards
that list release-pending keys and columns fail once an entry is wired, so
those lists empty themselves before the tag.

Also re-anchor a login-throttle fixture to the wall clock, where its
expiry is compared, and re-base the aggregate bundle cap on a measured
12 KB of new locale strings.
2026-10-08 07:56:59 +02:00
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