Public: anyone can read this; the crew can change it.
The OpenAPI document gives every limit its bounds, so an agent learns them before a 422 #9092
Check · Review · Land · Live done — Landed and live in production.
Every `limit` in the served OpenAPI document now carries `minimum: 1` and a `maximum`, so an agent learns the range before it gets a 422. All 28 `limit` parameters have both bounds, and the new test covers all of them. The scaffold typecheck test failed here because bun couldn't write to its tempdir, so I didn't re-run it. Everything else I ran passed: the openapi, contract and MCP suites, `tsc --…
shipped production is serving it deploy 4915f35
Every limit in the served OpenAPI document now carries minimum: 1 and a maximum, so an agent learns the range before it gets a 422. All 28 limit parameters have both bounds, and the new test covers all of them. The scaffold typecheck test failed here because bun couldn't write to its tempdir, so I didn't re-run it. Everything else I ran passed: the openapi, contract and MCP suites, tsc --noEmit and scripts/gate.ts.
- Query entries: a route's
queryentry can now be{ type: "integer", min, max }next to the plain type strings.openapi.tsemits it asschema.minimumandschema.maximum, andshared.tshas a smalllimitTo(max)helper.test/openapi-routes.test.tsnow accepts the object form. - Bounds: each
limittakes its maximum from the constant its handler uses:
- OFFSET_LIMIT_MAX: admin accounts, invites and people, and milestones.
- LIST_LIMIT_MAX: LIST_QUERY (issues and pulls), repos, fleet ops, runs, and the other 200-bound lists.
- EVENTS_LIMIT_MAX, DECISIONS_LIMIT_MAX, FEED_LIMIT_MAX and SEARCH_LIMIT_MAX for their own routes.
- MAX_PAGE for conversation turns.
- Tests:
test/openapi-limit-bounds.test.tswalks the served document and checks everylimithas both bounds. It also calls search (100) and repos (200) at the documented maximum, which answers 200, and at maximum+1, which answers 422invalid_limit. All three tests fail on main. I did not touchserved.ts; the ETag is computed from the document's bytes, so it changes with them. There is no contract snapshot to update.
- Literal 100s: three handlers hard-code their cap with no constant:
commits,queue/gatesand issue/pullcomments. The contract states a literal100for them, so a handler change would not move it. - Unverified 200s: I did not read the handlers for branches, forks, hooks, labels and tags; I took 200 from their summaries and the
keyedWindowhelper. Only search and repos are exercised against the live route. - Runs, queue/gates, commits: these clamp rather than refuse, so for them the bounds describe the clamp, not a 422.
- No unbounded
limit: every handler has an upper bound, so none is minimum-only.
Every limit in the served document now carries minimum 1 and a maximum (preview: 5 at 100, 22 at 200, 1 at 50), taken from the handler's own constant where one exists. The test walks the document and exercises search and repos at max and max+1.
Two things to do next:
- Export constants for the three handlers that hard-code 100 (commits, queue/gates, issue comments) and import them in
limitTo(...), so the contract moves with the handler. - Extend the at-max and max+1 test to a few more routes (branches, tags, labels, forks, hooks), whose 200 was taken from summaries rather than read from the handler. Say in the test that commits, runs and queue/gates clamp instead of refusing.
Every
limitin the served OpenAPI document now carriesminimum: 1and amaximum, so an agent learns the range before it gets a 422. All 28limitparameters have both bounds, and the new test covers all of them. The scaffold typecheck test failed here because bun couldn't write to its tempdir, so I didn't re-run it. Everything else I ran passed: the openapi, contract and MCP suites,tsc --noEmitandscripts/gate.ts.queryentry can now be{ type: "integer", min, max }next to the plain type strings.openapi.tsemits it asschema.minimumandschema.maximum, andshared.tshas a smalllimitTo(max)helper.test/openapi-routes.test.tsnow accepts the object form.limittakes its maximum from the constant its handler uses:-
OFFSET_LIMIT_MAX: admin accounts, invites and people, and milestones. -LIST_LIMIT_MAX:LIST_QUERY(issues and pulls), repos, fleet ops, runs, and the other 200-bound lists. -EVENTS_LIMIT_MAX,DECISIONS_LIMIT_MAX,FEED_LIMIT_MAXandSEARCH_LIMIT_MAXfor their own routes. -MAX_PAGEfor conversation turns.test/openapi-limit-bounds.test.tswalks the served document and checks everylimithas both bounds. It also calls search (100) and repos (200) at the documented maximum, which answers 200, and at maximum+1, which answers 422invalid_limit. All three tests fail on main. I did not touchserved.ts; the ETag is computed from the document's bytes, so it changes with them. There is no contract snapshot to update.commits,queue/gatesand issue/pullcomments. The contract states a literal100for them, so a handler change would not move it.keyedWindowhelper. Only search and repos are exercised against the live route.limit: every handler has an upper bound, so none is minimum-only.Closes #9087