Skip to content

feat: Own and auto-sync the OpenAPI spec extras (RES-14) - #4691

Open
ArmagedonFlamer wants to merge 8 commits into
devfrom
feature/res-14-merge-spec-enhancement
Open

feat: Own and auto-sync the OpenAPI spec extras (RES-14)#4691
ArmagedonFlamer wants to merge 8 commits into
devfrom
feature/res-14-merge-spec-enhancement

Conversation

@ArmagedonFlamer

@ArmagedonFlamer ArmagedonFlamer commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Gives cognee_openapi_spec.json a single owner, then keeps the parts of it that
FastAPI cannot generate honest automatically.

Closes the RES-14 acceptance criteria on spec ownership; the automation is RES-21.

Why

The published spec had two owners that disagreed. tools/sync_release_docs.py
generated it on release. The docs repo's .github/scripts/generate-api-docs.sh
regenerated it on a Wednesday cron and post-processed in everything FastAPI does not
emit — servers, tag descriptions, request examples, security schemes. Both rewrote
the whole file, so every release stripped what the cron added and every Wednesday
put it back
.

The extras were also wrong in a way nobody could see. The docs cron published
CookieAuth with cookie name fastapiusersauth — fastapi-users' default — while
cognee's transport sets auth_token (default_transport.cookie_name). The reference
documented a cookie that has never existed.

What changed

1. enhance_spec() in tools/sync_release_docs.py — the docs cron's
post-processing, ported. This script is now the only generator.

The extras live in tools/spec_extras.json rather than as Python literals, because
two of the three are machine-maintained: the fixer does a JSON load-mutate-dump
instead of splicing source. That also sidesteps what forced router_docstring_sync
to pin ruff@0.15.11 "so the bot never fights the formatter" — there is no Python
for the formatter to reflow.

request_examples is keyed by "METHOD /path", not FastAPI's operationId. The
operationId embeds the handler function name (add_api_v1_add_post), so a rename
silently orphaned an example. Path and method are the API contract.

2. spec_extras_sync.yml + check/fix_spec_extras.py — a detect → fix → PR
automation in the shape of router_docstring_sync.

Drift Treatment
blurb for a tag no route uses deleted (mechanical)
tag with no blurb placeholder inserted (mechanical), then written by Claude from the endpoints carrying that tag
example pinned to a route that no longer exists reported in the PR body
malformed servers entry reported in the PR body

The last two are not guessed at: choosing a route for a stale sample would attach a
wrong body to a live endpoint, and a base URL is a deployment fact.

This matters because the drift is already real — 21 of 32 route tags have no
description
. The 11 that do cover add/cognify/search/delete, the legacy
surface, while remember/recall/forget/improve render as bare sidebar groups.
The automation's first PR clears that backlog.

Verification

Port fidelity. A faithful Python replica of the bash step 3 run against the same
app.openapi() output. Entire diff, 12k lines in:

12343c12343
<         "name": "fastapiusersauth"
---
>         "name": "auth_token"

Against the live published spec, every extra matches byte-for-byte:

servers identical: True
top-level key order identical: True
tags: published 32, mine 32, descriptions agree on all shared: True
  only in published: []      only in mine: []
securitySchemes keys identical: True
all 4 request examples: match=True

The enhance_spec output stayed byte-identical through both the move to JSON and the
re-key.

Pipeline, real app, Claude stubbed:

Detect drift          11/32 described, 21 issues     exit=1 -> drift=true
Fix --describe        43 changes                     exit=0
Convergence re-check  32/32 described, 0 issues      exit=0

Plus, on a cached schema so each case is hermetic: no-op rerun leaves the file
byte-identical, dead key removed, orphaned example reported (exit 1, not fixed),
--warn-only never fails. The PR-lookup logic was exercised against all five states —
no PR / open / closed / merged-only / closed+merged → CREATE, UPDATE,
REOPEN+UPDATE, CREATE, REOPEN+UPDATE.

🤖 Generated with Claude Code

ArmagedonFlamer and others added 2 commits August 27, 2026 12:38
The published cognee_openapi_spec.json had two owners that disagreed. This
script generated it on release; the docs repo's generate-api-docs.sh
regenerated it on a Wednesday cron and post-processed in the extras FastAPI
does not emit - servers, tag descriptions, request examples, security
schemes. Both rewrote the whole file, so every release stripped what the
cron had added and every cron put it back.

Port that post-processing here as enhance_spec(), making this the single
generator. Verified against a faithful replica of the bash step: the entire
diff is one line, the CookieAuth cookie name. The docs cron published
"fastapiusersauth" (fastapi-users' default) while cognee's transport sets
"auth_token", so the reference documented a cookie that never existed;
enhance_spec reads it off default_transport instead.

The extras live in spec_extras.json rather than as literals because two of
them are machine-maintained (see RES-21) - JSON is a load-mutate-dump with
nothing for the formatter to disagree with. request_examples is keyed by
"METHOD /path" rather than FastAPI's operationId, which embeds the handler
function name and so orphaned an example on any rename.

The docs-repo generator has to be deleted in the same window, or the next
cron overwrites this output again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The extras enhance_spec() applies are hand-maintained and keyed off things
that move: the tags routes carry, and the routes themselves. Nothing watched
them. router_docstring_sync does not - both its commit steps stage only
`cognee`, describe_router_params is scoped to cognee/api, and a tag blurb is
not derivable from a docstring or Pydantic metadata anyway. The drift was
already real: 21 of 32 route tags had no description, covering add/cognify/
search while remember/recall/forget/improve rendered bare, and one entry
described a tag no route used.

Add a detect-fix-PR automation in the shape of router_docstring_sync. Dead
tag descriptions are deleted and missing ones get a placeholder
mechanically; Claude then writes the blurbs from the endpoints carrying each
tag. An example pinned to a route that no longer exists, or a malformed
server URL, is reported in the PR body rather than guessed at - picking a
route for a stale sample would attach a wrong body to a live endpoint.

Three things differ from router_docstring_sync deliberately, because each
caused a real bug there:

- A freshness re-check after dependency install. That job checked out dev,
  installed for ~5 minutes, and pushed a fix built from a tree that had
  moved on, opening #4688 - which would have reverted COG-6292.
- The existing-PR lookup uses --state all and reopens a closed-but-unmerged
  PR. Looking only at open PRs is what turned a closed #4668 into a second
  identical #4672.
- The content hash covers only the file the fixer writes. Hashing the whole
  cognee/ subtree meant any unrelated commit invalidated it, so the skip
  path never fired and every run force-pushed over the review.

There is no pull_request trigger: adding one to test router_docstring_sync
from its own PR is what opened #4668 against dev before it had merged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ArmagedonFlamer and others added 4 commits August 27, 2026 12:53
The first dispatch failed after 6m36s with "can't open file
tools/check_spec_extras.py", reported as "Checker failed to import the app".
The job checks out dev, and the scripts only exist on this branch - so the
run was doomed at second zero but spent 6m14s preparing 490 packages before
finding out. Dropping router_docstring_sync's second checkout kept the tree
coherent but left no way to exercise this before merging, which is why a
pull_request trigger got added to test it: exactly the mistake that opened
#4668 against dev from an unmerged workflow.

Give workflow_dispatch a `ref` input instead, selecting both the tree and the
scripts so they stay the same checkout, plus a `dry_run` input that stops
after showing the fixer's diff.

Gate the write path on the tree being dev rather than on the trigger. A fix
built from a non-dev tree is not what dev runs, so a non-dev ref is forced to
a dry run - and because the gate reads the tree, it holds even if a
pull_request trigger is added later. Remove the trigger added for testing;
dispatching with `ref` replaces it.

Move the machinery check ahead of the install so a misconfigured run fails in
seconds with a message naming the cause, and stop reporting a missing file as
an import failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both jobs watched pushes to dev, and the numbers do not support it. Over the
last 30 days dev took 791 commits: 594 of them would have fired the docstring
sync (it watches all of cognee/**) and 205 the extras sync, roughly 20 and 7
runs a day. Against that, a new route tag appears 2-3 times a month, and only
8 commits in 90 days touched a tags=[...] declaration at all. For the extras
sync, pyproject.toml and uv.lock were pure noise: 92 commits in 90 days, 4 of
which touched a router.

The cost was not only runner time. Each push run force-pushes the fix branch
and rewrites the PR body, so a review in progress moved under the reviewer.
Weekly gives one stable PR to read.

Pair them instead: docstring sync Monday 05:00 UTC, extras sync 06:00. Same
morning, so both PRs land in one review session. Docstrings first because the
data flows that way - they become the endpoint descriptions the extras sync
feeds to Claude when writing tag blurbs - and because the second run then
reuses a warm uv cache, which is the difference between a ~6 minute install
and a ~45 second one.

Dropping the push trigger invalidates the extras sync's freshness bail-out,
which was justified by the push that moved dev having its own queued run.
There is no such run now; the next one is a week away. Bailing is still right,
since a fix built from a stale tree must never be pushed, but it now costs a
whole week, so it emits a warning and a step summary instead of skipping
silently.

Trade-off worth naming: drift merged on a Tuesday now waits until the
following Monday. That is a bare sidebar group for the extras sync, but wrong
parameter documentation for the docstring sync - dispatch it manually ahead of
a release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both weekly sweeps exist to catch drift before a release ships it, so they
belong just ahead of one. Of the last 50 stable releases, 35 went out Thursday
through Sunday and Friday was the single most common day, with only 15 landing
Monday through Wednesday. Monday was ahead of more releases in absolute terms
but several days staler for the ones that matter.

Wednesday 05:00 and 06:00 UTC keeps the hour-apart pairing and puts both PRs
in the queue at the start of the European working day, leaving Wednesday to
review and merge them. It is also the slot the docs repo's generate-api-docs
cron used, for the same reason - RES-14 specified Wednesday and the original
implementation drifted to Monday.

A release cut Monday to Wednesday still works from the prior week's sweep.
Closing that remaining gap needs a per-PR check rather than a schedule, which
RES-14 already tracks as a follow-up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ArmagedonFlamer
ArmagedonFlamer marked this pull request as ready for review August 27, 2026 11:46
@ArmagedonFlamer
ArmagedonFlamer force-pushed the feature/res-14-merge-spec-enhancement branch from 1bb93fa to 95a1a9f Compare August 27, 2026 11:57
@Vasilije1990

Copy link
Copy Markdown
Contributor

@ArmagedonFlamer please keep PRs as draft until they pass base checks and ruff

@ArmagedonFlamer

Copy link
Copy Markdown
Contributor Author

@ArmagedonFlamer please keep PRs as draft until they pass base checks and ruff

@Vasilije1990 It seems the ruff issue was from the dev branch. After merging updated dev, the ruff issue is gone.

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.

2 participants