docs/api-contract/ — the seam, written down

The backend and the renderer talk only over HTTP on 127.0.0.1. That boundary is the one place where work on either side can break the other, so it is the one place worth freezing. This folder is what “frozen” means in practice.

What is here

File What it is How it changes
openapi.json app.openapi() from resmon_scripts/resmon.py, keys sorted and info.version removed. Every route’s method, path, parameters and request body. Generated. Never edited by hand.
http.md The index: every route against the parity-register row it serves, with its auth class, whether it streams, and a one-line purpose; then the closed vocabularies and the admission 429. Generated. Never edited by hand.
mcp.md The MCP tool contract (v2, at 2.3). What an external harness may call and what it gets back. Hand-written, frozen.
assistant-conversations.md · assistant-choices.md · library.md · evidence.md · selected-evidence.md The five feature contracts, each written before both harnesses started on a slice that needed them to agree. Hand-written, frozen.

openapi.json and http.md are generated by implementation_scripts/api_contract.py and guarded by verification_scripts/test_api_contract.py, which regenerates both in-process and fails on any difference. The other six are prose, written once and left alone.

The security model the auth classes come from is ../local-api-security.md: what a request must carry, where the token lives, and — just as plainly — what none of it defends.

How to change it

A change to the surface takes its own pull request. That is the whole mechanism:

  1. Change the route. The guard goes red, naming the routes that were added, removed or reshaped.
  2. Regenerate, from resmon_scripts/:

    python -m implementation_scripts.api_contract --write
    
  3. Commit the regenerated files in that pull request, and say in the body which routes changed and why. A reviewer reading git diff docs/api-contract/ should be able to see the whole of what moved without reading the implementation.

A new route also needs a line in api_contract.ROUTE_JOURNEYS saying which register row it serves. Until it has one the guard stays red: a route nobody can name a journey for is a route the renderer has no reason to call.

Running the generator to make a red test go green, without looking at what changed, defeats the point of having it. The failure message is the review.

How the renderer rebuild uses it

A screen may call what is in here and nothing else. The 3.0 renderer is measured against this file: for each of the 44 parity-register rows, http.md lists the routes that carry it, so a screen that has been rebuilt can be checked against the routes its journey actually needs rather than against somebody’s memory of the old page. A screen that needs something not in here is asking for a backend change, which is a pull request of its own and arrives before the screen does.

Three things the index is careful about, because a contract that overclaims is worse than none:

  • No response schemas. Not one route declares a response_model, so openapi.json says every 200 body is the empty schema {}. Requests are fully described; responses are described in prose in the root README.md and in the five feature contracts. Nothing here guesses.
  • Purposes are quoted, not written. A route’s one-line purpose is the first line of its docstring. The routes that have no docstring show FastAPI’s generated summary in italics — the function name with the underscores taken out — rather than a sentence somebody invented for them. http.md gives the count both ways.
  • Streaming is marked. Three routes answer text/event-stream. A client that fetches one of them and waits for a JSON body waits for ever.