Skip to content

docs: generate venue and endpoint tables from registry with drift test - #475

Merged
Miracle656 merged 4 commits into
Divinity-Labs:mainfrom
Godfrey-Delight:docs/229-generate-readme-venues
Oct 23, 2026
Merged

Miracle656 merged 4 commits into
Divinity-Labs:mainfrom
Godfrey-Delight:docs/229-generate-readme-venues

Conversation

@Godfrey-Delight

Copy link
Copy Markdown
Contributor

Summary

  • Replaced the hand-typed venue list and endpoint table in README.md with generated sections built from source code registries.
  • Added type and description metadata to the VenueAdapter interface and populated them on all registered adapters (SDEX, AMM, Soroswap, Aquarius).
  • Created scripts/generate-readme.ts (npm run readme:gen) to render the venues table from venueAdapters and the REST endpoint table from openapi.yaml (adding the previously missing POST /pairs endpoint).
  • Added tests/readme.test.ts to assert that README.md never drifts from the venue registry or OpenAPI specification.

Related issue

Closes #229

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Docs
  • Tests
  • CI / tooling

Checklist

  • I have read CONTRIBUTING.md
  • npx tsc --noEmit passes
  • npm run build passes
  • I added / updated tests where relevant
  • I updated docs where relevant

@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@Godfrey-Delight Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Miracle656 added a commit that referenced this pull request Oct 23, 2026
…229)

The venue table now renders from the `venueAdapters` registry and the endpoint
table from `openapi.yaml`, both into delimited `<!-- VENUES -->` /
`<!-- ENDPOINTS -->` blocks, with `tests/readme.test.ts` failing whenever the
committed README disagrees with what the generator produces or a public route
is missing from the table. That is the right shape for #229: the list cannot
drift because nothing maintains it by hand any more.

Regenerating proved the point — the hand-written table was missing twelve
routes: `/venues/registry`, `/tvl`, `/rates/:asset`, `/movers`, `/correlation`,
`/discovery/search`, `/price/:asset/fiat`, `/usage`, `POST /pairs`,
`/price/twap/...`, `/price/vwap/...` and `/snapshot`.

Four things on top of the contribution.

1. **The branch did not compile.** `type` and `description` are set on four
   adapters but were never declared on `VenueAdapter`, so `tsc` rejected all
   four object literals with TS2353. The fields are now on the interface — and
   **required**, not optional, which is the part that matters: the table was
   hand-written and already stale, and generating it from the registry only
   fixes that if a new adapter cannot be registered without describing itself.
   The generator's `adapter.type ?? 'DEX'` fallbacks are gone with them; an
   adapter that says nothing used to produce a row reading "DEX / X price
   venue", which is the same drift wearing a generator's clothes.

2. **Two adapters added since the branch was cut had no annotation** — Phoenix
   and DeFindex, which is exactly the omission the issue is about. Making the
   fields required surfaced both at compile time rather than as two silently
   absent rows.

3. **Five rows lost detail the README already had**, because the generator's
   `ENDPOINT_METADATA` map was older than the table it replaces: `/live` lost
   its link to `docs/health.md`, `/ready` its dependency note, `/snapshot` its
   query string and full description, `/impact` its `?thresholds=1,5,10`
   example, `/volumes` its trade-size-concentration wording. Carried across,
   along with `/usage/me` and the `/graphql` GraphiQL note. Generating the list
   must not cost the prose.

4. **No way to regenerate.** Added `npm run readme:gen`, beside `openapi:gen`
   and `client:gen` — a drift guard with no documented command to satisfy it
   just blocks the next contributor.

127 test files / 1,521 tests pass; tsc clean.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
@Miracle656
Miracle656 merged commit 1d7f3b2 into Divinity-Labs:main Oct 23, 2026
@Miracle656

Copy link
Copy Markdown
Collaborator

Merged as b25e975. The shape is right, and it is the shape that actually closes #229: delimited blocks in the README, the venue table rendered from the venueAdapters registry, the endpoint table rendered from openapi.yaml, and tests/readme.test.ts failing when the committed file disagrees with the generator or a public route is missing. Deriving the endpoint list from the spec rather than from a second hand-written list is the key decision — it means a route cannot exist and be undocumented.

And regenerating proved the premise immediately. The hand-written table was missing twelve routes:

/venues/registry, /tvl, /rates/:asset, /movers, /correlation, /discovery/search, /price/:asset/fiat, /usage, POST /pairs, /price/twap/..., /price/vwap/..., /snapshot

Four things I changed on the way in.


1. The branch did not compile

src/ingest/venues/amm.ts(250,3): error TS2353: Object literal may only specify known properties, and 'type' does not exist in type 'VenueAdapter<any, any>'.
src/ingest/venues/aquarius.ts(115,3): error TS2353 …
src/ingest/venues/sdex.ts(179,3): error TS2353 …
src/ingest/venues/soroswap.ts(457,3): error TS2353 …

type and description are set on four adapters, but neither was ever added to the VenueAdapter interface — and the diff does not touch src/ingest/venues/types.ts at all, so this was true on the branch as well as after merging. Worth running npx tsc --noEmit before pushing; it is the whole of CI's build job.

I made both fields required rather than optional, and that is the part I would push back on if you disagree. Your generator had:

const type = adapter.type ?? 'DEX'
const desc = adapter.description ?? `${adapter.name} price venue`

With optional fields and those fallbacks, a new adapter produces a row saying "DEX | Phoenix price venue" — a row that reads as documentation and contains no information. That is the same drift the issue is about, now with a generator's authority behind it. Required fields mean a new adapter cannot be registered without describing itself; the compiler asks the question at the moment the author knows the answer. The fallbacks are gone, since they are unreachable.

2. Two adapters added since your branch had no annotation

Phoenix and DeFindex were both registered in venueAdapters after you cut the branch, and neither carried type/description — so they would have appeared as two of those contentless fallback rows. Exactly the omission #229 exists to prevent, on day one.

Making the fields required surfaced both as compile errors instead. I annotated them:

type: 'Liquidity Pool', description: 'Phoenix AMM pools on Soroban',
type: 'Vault',          description: 'DeFindex yield vaults on Soroban, priced per share',

If either description is wrong, say so and I will fix it — you know these adapters better than I do.

3. Five rows lost detail the README already had

ENDPOINT_METADATA turned out to be older than the table it replaces, so regenerating was a net gain of twelve rows and a small loss on five:

Route Was Became
/live "Liveness probe — no dependency checks (see docs/health.md)" "Liveness probe"
/ready "Readiness probe — boot complete + dependencies reachable" "Readiness probe"
/snapshot full description + query string openapi summary only
/impact ?thresholds=1,5,10 no example
/volumes "volume and trade-size concentration", &network=testnet "traded volume"

All carried across, plus /usage/me's auth note and /graphql's GraphiQL/docs links. Generating the list must not cost the prose — a complete table of thin descriptions is a worse README than an incomplete table of good ones, and there is no reason to accept either.

One note on /snapshot: I used ?network=testnet rather than testnet|mainnet, because a raw | ends the markdown table cell and an escaped \| reads worse than just picking one example.

4. No way to regenerate

The PR adds a drift guard and no command to satisfy it, so the next contributor who touches openapi.yaml gets a failing test and has to read the test to work out what to run. Added npm run readme:gen, beside the existing openapi:gen and client:gen.

For anyone reading this later: Lens now has five generated artifacts under CI guard — openapi.json, clients/typescript/src/schema.d.ts, schema.graphql, prisma/migrations/0_init, and now the README tables. A PR touching openapi.yaml needs npm run openapi:gen, npm run client:gen and npm run readme:gen.


One thing I left alone

Row order now follows openapi.yaml document order, so the old logical grouping (health, then prices, then market structure) is gone — /venues/registry and /tvl lead the table now. The comment in renderEndpointsTable says "Ordered list of endpoints to preserve logical categorization", which the implementation does not actually do.

I did not change it because completeness matters more than curation here and the openapi document is reasonably grouped already. If you want the grouping back, the clean way is an explicit section order in the generator — a list of path prefixes with the remainder appended — rather than relying on the order keys happen to appear in the YAML.

127 test files / 1,521 tests pass. tsc --noEmit clean.

@Miracle656 Miracle656 mentioned this pull request Oct 23, 2026
6 of 11 tasks
Miracle656 added a commit that referenced this pull request Oct 23, 2026
A rate-limited Horizon call returned a body with no `_embedded.records`, the
caller read that as "this pair has no pools", and the venue silently stopped
producing prices until a later cycle happened to land outside the window.
`fetchHorizonJson` now retries a 429 — honouring `Retry-After` when Horizon
sends one, otherwise exponential with full jitter so four ingesters on the same
poll interval do not retry in lockstep — and raises `HorizonRateLimitError`
once the attempts are spent, which the AMM adapter rethrows rather than logging
and swallowing, so the runner counts it and backs the cycle off.
`horizon_rate_limit_remaining` is read from `X-RateLimit-Remaining`, so how
close an instance runs to the limit is visible before it starts failing.

Merged locally; two conflicts, both against work landed since the branch was
cut.

One fix on top of the contribution. **The helper used a bare `fetch(url)`.**
`main` had moved these two call sites to `fetchWithTimeout(url, { signal })`,
which supplies both the configured `HTTP_TIMEOUT_MS` and — since #471 this
afternoon — the cycle deadline's abort signal. Taking the branch's version
verbatim would have reverted both: an unbounded request inside a loop whose
whole purpose is to have an upper bound, in the module that exists because a
slow Horizon is the failure being handled. `fetchHorizonJson` now takes
`{ attempts, signal }` and passes the signal to every request *and* to the
backoff sleep, so an abandoned cycle does not sit out a 30-second retry wait
after it has been told to stop, and gives up rather than spending another
request once it has.

Also kept `main`'s structured `log.error` over the branch's `console.error`
(`src/__tests__/logger.test.ts` rejects `console.*` in non-test source).

Separately, a defect in my own #475 merge this afternoon: `tests/readme.test.ts`
byte-compares README.md against generator output joined with `\n`, so it fails
on every line of an identical table on any Windows checkout, where git supplies
CRLF. It is the fifth guard in this repo to hit that, after `openapi.json`,
`*.yaml`, `*.csv` and `schema.graphql`. README.md is now pinned to LF in
`.gitattributes` and the test normalises line endings as it reads, so a
contributor whose git config differs is not blocked by a diff that is only line
endings.

129 test files / 1,553 tests pass; tsc clean.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
Azintar added a commit to Azintar/Lens that referenced this pull request Oct 24, 2026
Resolve conflicts with Divinity-Labs#475, which landed scripts/generate-readme.ts and the
<!-- ENDPOINTS:START --> generated README table on main:

- README.md: keep main's single generated endpoint table (ENDPOINTS markers)
  instead of a second BEGIN/END GENERATED ENDPOINT TABLE block, regenerate it
  from main's openapi.yaml, and keep the note that the table is generated.
- package.json: keep both sides' scripts (openapi:check, graphql:schema:gen,
  readme:gen, client:gen, client:typecheck).
- scripts/generate-openapi.ts: openapi:gen / openapi:check now delegate the
  README rendering to generate-readme.ts so there is one renderer, while still
  regenerating/checking openapi.json and the README in one command.
- tests/openapi.test.ts: drop the duplicated coverage/generator imports and
  assert openapi:check reports no stale artifacts (row-level README checks
  live in tests/readme.test.ts).
- ci.yml: run openapi:check after prisma generate (the README renderer loads
  the venue registry).
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.

The README's venue list is hand-maintained and already wrong

2 participants