refactor(api): move query-engine to a private internal tier - #461
Open
Makisuo wants to merge 1 commit into
Open
Conversation
The queryEngine group was the largest and busiest thing left on the public `/api/*` surface — 62 endpoints, ~4,500 requests/week — and `/docs` published every one of them as browsable public API. It is not public API: across 30 days and all 62 endpoints, production telemetry (`maple.auth.method`) recorded exactly one API-key request. Everything else is a Clerk session. Publishing it costs real freedom. `docs/http-api-migration.md` already forbids a generic public query endpoint, because those contracts freeze Maple's storage and dashboard internals into the public API. The group now serves from its own `HttpApi` (`MapleInternalApi`) at `/internal/query-engine`, behind a session-only `SessionAuthorization`. The boundary is policy, not protocol: the typed contract, atom retention, the execute batcher and the v1 error envelope are all unchanged, so the frontend keeps working exactly as before. Being a separate `HttpApi` is what drops it from `/docs`, which is generated from `MapleApi`. `SessionAuthorization` refuses an API-key-shaped bearer on the `maple_ak_` prefix rather than by resolving it, so a rejected key costs no Postgres dial on what is the busiest path in the API. The refusal is a typed 403 pointing at `/v2` — a bare 401 would read as "your key is broken" and send people to rotate a key that is fine. Also removes four endpoints that reached zero traffic days ago: - `execute`, superseded by the batcher in dc21b94. The batcher has no single-request fallback at any size, so nothing called it any more. - `service-dependencies`, `service-db-edges`, `service-platforms`, superseded by `serviceMapBundle` in a3128de, which runs the same registry queries in-process. `QueryEngineService.execute` stays — the batch fan-out, the serviceDetailOverview bundle and six v2 telemetry handlers still call it in-process. `runWarehouseQuery` now accepts either v1 client and provides both layers: it is shared with the session-replay adapters, which are still on `/api`. It narrows again when sessionReplays moves.
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.
What
Moves the
queryEnginegroup off the public/api/*surface onto its ownHttpApi(MapleInternalApi) at/internal/query-engine, behind session-only auth, and deletes four endpoints that already had zero traffic.Why
queryEnginewas the largest and busiest thing left on the public surface — 62 endpoints, ~4,500 req/week, more than every other/api/*group combined — and/docspublished all of them as a browsable public API reference.It isn't public API. Using the
maple.auth.methodattribute stamped on every HTTP server span, across 30 days and all 62 endpoints production recorded exactly one API-key request (errors-by-type, 1 call). Everything else is a Clerk session.That matters beyond hygiene:
docs/http-api-migration.mdalready forbids a generic public query endpoint, because those contracts freeze our storage and dashboard internals into the public API.docs/api-v2.mdhad long declared the intended end state; this makes it true.The design call
"Internal tier" reads as Effect
RpcServeron/rpc. I deliberately didn't do that —apps/api/src/worker.tsdocuments thatPOST /mcp, the one existingRpcServer-backed route, hangs indefinitely on Workers whentoWebHandlergets no middleware, attributed to a suspected RpcServer/HttpRouter scope-propagation bug. Putting the busiest surface in the product on that transport buys the same guarantee for a 62-endpoint rewrite plus a known Workers hang.So the boundary is policy, not protocol. The typed contract,
AtomHttpApiretention, the execute batcher and the v1 error envelope are all unchanged — the frontend behaves exactly as before. Being a separateHttpApiis what drops the group from/docs, which is generated fromMapleApi.Notable details
SessionAuthorizationrejects on themaple_ak_prefix, not by resolving the key. A refused key therefore costs no Postgres dial on the busiest path in the API — worth having, since the p95 there is the PG dial. The refusal is a typed 403 pointing at/v2; a bare 401 would read as "your key is broken" and send someone to rotate a key that's fine.Four endpoints deleted after reaching zero traffic:
execute— superseded by the batcher in dc21b94. It has no single-request fallback at any batch size, so nothing called it.service-dependencies/service-db-edges/service-platforms— superseded byserviceMapBundlein a3128de, which runs the same registry queries in-process.QueryEngineService.executestays — the batch fan-out,serviceDetailOverviewand six v2 telemetry handlers still call it in-process.runWarehouseQuerynow accepts either v1 client and provides both layers. It's shared with the session-replay adapters, which are still on/api; a blanket swap broke them. It narrows again whensessionReplaysmoves.Handler moved from
routes/v1/toroutes/internal/so the directory stops lying.Verification
bun typecheckapps/apitestsapps/webtestspackages/domaintestsbun run knipMapleInternalApi= 58 endpoints, all under/internal/query-engine, zero stray;MapleApi= 118, zero query-engineThe last row is the one that matters — a wrong prefix or broken registration would leave typecheck green while 404ing every chart, so I introspected the built API directly rather than trusting the compiler.
Not verified — please exercise before merge
I did not run the browser pass. Outstanding at runtime:
/internal/query-engine/execute-batchreturns the typed 403 (and a session returns 200)./docsno longer lists any query-engine operation.Since sessions loaded before deploy keep calling
/api/query-engine/*until reload, expect a burst of 404s in open tabs at cutover. If that's unacceptable, serve both prefixes from the same handler layer for one release and drop the old one next release — the plan allowed for it; this PR does the direct cutover.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.