docs(serve): generate the endpoint reference from the app, gate it in CI (#656) - #671
Merged
Merged
Conversation
… CI (#656) docs/web-server.md drifted badly: it claimed "150+ REST endpoints" over a 17-row hand-written table while the app served 227 operations across 29 routers, with ten routers (billing, token, notifications, feature, stream, dev-portal, ai-chat, semantic-layer, transformations, documentation) absent entirely. No OpenAPI spec is committed, so only a running server showed the truth and nothing in CI could notice. Replace the hand-maintained enumeration with a generated one: - scripts/gen_endpoint_reference.py renders docs/web-server-endpoints.md from create_app().openapi(). Sections come from the descriptions already in server/app.py::OPENAPI_TAGS (category prefix + "Mirrors kbagent ..."), so the prose stays human-authored and only the enumeration is mechanical. No version stamp: the file is gated, and a stamp would make every release bump it for reasons unrelated to the API. - make endpoints-gen / endpoints-check, wired into make check and ci.yml. The check asserts the file is tracked AND unchanged after regeneration: git diff alone reports nothing for an untracked path, and git status --porcelain alone rejects a staged-new file whose content is correct. Rewrite web-server.md around what a generator cannot produce: it now maps routers to their OPENAPI_TAGS categories and states which surfaces are CLI-only, with no route list and no endpoint count to go stale. Also corrected while verifying against the code: - All six SSE routes are listed; the doc named one and mis-labelled three non-existent ones as "designed-for". Notes that /agents/{id}/runs/{id}/ events looks like a stream but replays as one JSON response. - The tier-1 file tree pointed at server/__init__.py for create_app (it is app.py since the PEP 562 lazy-export split) and omitted run_broadcaster.py and pricing.py. - The `agents` tag described itself as "Server-only feature (no CLI equivalent)", untrue since kbagent agent shipped in 0.44.0 -- what is serve-only is the cron loop. Fixed at the source, so Swagger improves too. README.md and docs/build-your-own-client.md point at the generated file; CONTRIBUTING.md's synchronization map gains both docs with their gate status. Refs #656
Two robustness fixes to the endpoint-reference gate, from PR review:
- The CI step ran the generator as plain `uv run`, while the Makefile passed
`--extra server`. The check job is green, so the extra did survive from the
install step -- but `uv run` re-resolves per invocation, so naming the extra
is what actually guarantees fastapi is importable. Both callers now match.
- Both callers redirected stderr to /dev/null. `bash -e` and make already fail
the step when the generator crashes, so this was never a false pass, but it
discarded the traceback: the failure would surface as a bare non-zero exit
with no way to see the cause. stdout stays dropped ("Wrote ..." is noise),
stderr now propagates.
padak
added a commit
that referenced
this pull request
Aug 23, 2026
This was referenced Aug 23, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #656.
The drift
docs/web-server.mdclaimed "150+ REST endpoints" across a 17-row hand-written table. The app actually serves 227 operations across 29 routers, and ten routers were missing from the doc entirely:billing,token,notifications,feature,stream,dev-portal,ai-chat,semantic-layer(21 routes!),transformations,documentation.No OpenAPI spec is committed, so only a running server exposed the truth and nothing in CI could catch the gap.
The fix: stop hand-maintaining the list
Rather than re-typing 227 rows that would drift again by 0.92.0, the enumeration is now generated and gated — mirroring the established
skill-gen/skill-checkpattern.scripts/gen_endpoint_reference.pyrendersdocs/web-server-endpoints.mdfromcreate_app().openapi().server/app.py::OPENAPI_TAGSalready carries a hand-written description per tag whose bold prefix is a category (**Data.**) and whose tail maps to the CLI (Mirrors `kbagent config *`). The generator groups by that existing metadata, so prose stays human-authored and only the enumeration is mechanical.gen_command_reference.pystamps one because it publishes a release asset; this file is git-tracked and gated, so a stamp would make every release bump the doc for reasons unrelated to the API.make endpoints-gen/make endpoints-check, wired intomake checkandci.yml.Gate correctness
The check asserts two things, because either alone has a blind spot — both found by mutation-testing the gate:
git ls-files --error-unmatch(file is tracked)git diff --quietreports nothing for an untracked path — a doc dropped from the index would pass while documenting nothinggit diff --quiet(content matches after regeneration)git status --porcelainalone flags a staged-new file (A) whose content is perfectly correct — the normal state of the PR that adds a generated fileVerified against all three states: untracked → fail, staged+correct → pass, staged+stale → fail.
web-server.mdrewritten around what a generator can't produceIt now maps routers to their
OPENAPI_TAGScategories and states which surfaces are deliberately CLI-only (auth,sync,permissions,init) — no route list and no endpoint count left to go stale.Other stale facts corrected while verifying against code
GET /agents/{task_id}/runs/{run_id}/eventslooks like a stream but replays as one JSON response.server/__init__.pyforcreate_app()— it moved toapp.pywith the PEP 562 lazy-export split — and omittedrun_broadcaster.pyandpricing.py.agentstag described itself as "Server-only feature (no CLI equivalent)" — untrue sincekbagent agentshipped in 0.44.0. What is serve-only is the cron loop. Fixed at the source inapp.py, so the Swagger UI improves too. This one had been invisible precisely because it only rendered inside Swagger; putting the tag text in a committed file is what surfaced it.Not in scope
The missing REST mirrors (#657) and the fact that
permissionsdoesn't constrain serve at all (#655) are referenced from the doc but remain open.Testing
tests/test_gen_endpoint_reference.py(11 tests): asserts the rendered set equals the app's operation set (the assertion the old table could not make), that the totals line is derived rather than claimed, that no version stamp leaks in, that the committed doc is fresh, and that an undeclared tag or empty router is reported rather than silently dropped.ruff,ruff format,ty,loc-check,command-sync-check,version-gate-checkall clean.No version bump and no changelog entry, per the release process in CLAUDE.md.