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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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 query entry can now be { type: "integer", min, max } next to the plain type strings. openapi.ts emits it as schema.minimum and schema.maximum, and shared.ts has a small limitTo(max) helper. test/openapi-routes.test.ts now accepts the object form.
  • Bounds: each limit takes 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.ts walks the served document and checks every limit has both bounds. It also calls search (100) and repos (200) at the documented maximum, which answers 200, and at maximum+1, which answers 422 invalid_limit. All three tests fail on main. I did not touch served.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/gates and issue/pull comments. The contract states a literal 100 for 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 keyedWindow helper. 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.

Closes #9087

commented

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 query entry can now be { type: "integer", min, max } next to the plain type strings. openapi.ts emits it as schema.minimum and schema.maximum, and shared.ts has a small limitTo(max) helper. test/openapi-routes.test.ts now accepts the object form.
  • Bounds: each limit takes 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.ts walks the served document and checks every limit has both bounds. It also calls search (100) and repos (200) at the documented maximum, which answers 200, and at maximum+1, which answers 422 invalid_limit. All three tests fail on main. I did not touch served.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/gates and issue/pull comments. The contract states a literal 100 for 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 keyedWindow helper. 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.
Approved

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:

  1. 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.
  2. 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.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs