{"record":{"id":"b8977ff3c67604a8","repo":"JuliusBrussee/caveman","slug":"configure-exact-post-paths-and-native-llm-protocols","errorCode":null,"errorMessage":"Configure exact POST paths and native LLM protocols","messagePattern":"Configure exact POST paths and native LLM protocols","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/asgi.py","lineNumber":68,"sourceCode":"    raise ValueError(\"non-finite JSON\")\n\n\nclass CavemanASGIMiddleware:\n    \"\"\"Place inside authentication and original-content guard middleware.\n\n    resolve_context may decline by returning None; existing application routing\n    and auth then run unchanged. It must use authenticated server state, not\n    namespace/session headers. This middleware never handles an auth rejection.\n    \"\"\"\n    def __init__(self, app, *, runtime: AsyncMiddlewareRuntime,\n                 routes: Mapping[str, Protocol],\n                 resolve_context: Callable[[ASGIScope], ASGIContext | None | Awaitable[ASGIContext | None]],\n                 max_body_bytes: int = 2 << 20, max_request_chunks: int = 256):\n        if not isinstance(runtime, AsyncMiddlewareRuntime):\n            raise TypeError(\"ASGI requires AsyncMiddlewareRuntime\")\n        if not routes or any(not isinstance(path, str) or not path.startswith(\"/\") or \"*\" in path or \"?\" in path\n                             or protocol not in (\"openai-chat\", \"openai-responses\", \"anthropic-messages\") for path, protocol in routes.items()):\n            raise ValueError(\"Configure exact POST paths and native LLM protocols\")\n        if not callable(resolve_context) or not 1 <= max_body_bytes <= 2 << 20 or not 1 <= max_request_chunks <= 4096:\n            raise ValueError(\"ASGI context resolver or request bounds are invalid\")\n        self.app, self.runtime = app, runtime\n        self.routes, self.resolve_context = dict(routes), resolve_context\n        self.max_body_bytes, self.max_request_chunks = max_body_bytes, max_request_chunks\n        self._version_supported = matches_framework((\"fastapi\", \"0.141\", \"1\"), (\"starlette\", \"1.6\", \"2\"))\n        if not self._version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n\n    async def __call__(self, scope, receive, send):\n        if owner.get() is not None:\n            return await self.app(scope, receive, send)\n\n        protocol = self.routes.get(scope.get(\"path\"))\n        async def passthrough(reader, reason):\n            if scope.get(\"type\") != \"http\" or scope.get(\"method\") != \"POST\" or protocol is None:\n                self.runtime.report(None, reason=reason, adapter=\"asgi\")\n                return await self.app(scope, reader, send)","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/asgi.py#L50-L86","documentation":"CavemanASGIMiddleware validates its `routes` mapping at construction. The middleware only projects exact, literal POST paths onto native LLM wire protocols (openai-chat, openai-responses, anthropic-messages); wildcards, query strings, relative paths, non-string keys, unknown protocol names, or an empty mapping are rejected. This fails fast so misconfigured routing never silently bypasses or misroutes LLM traffic at runtime.","triggerScenarios":"Instantiating CavemanASGIMiddleware with: an empty routes dict; a path key that is not a str; a path not starting with '/'; a path containing '*' or '?' (glob/query style); or a protocol value other than exactly 'openai-chat', 'openai-responses', or 'anthropic-messages' (e.g. 'chat/completions', 'OpenAI-Chat', 'openai_chat').","commonSituations":"Developers copy glob route patterns from web frameworks ('/v1/*'), append query strings ('/v1/chat/completions?model=x'), typo a protocol name, use an internal enum/string from their own config layer, or pass routes=None/empty when disabling routes via feature flags.","solutions":["Use exact literal paths starting with '/', one per native endpoint (e.g. '/v1/chat/completions', '/v1/messages'), with no '*' or '?' characters","Use exactly one of the protocol strings: 'openai-chat', 'openai-responses', 'anthropic-messages'","Ensure routes is a non-empty Mapping[str, Protocol] and every key is a str (not bytes or None)","Remove any query strings from paths; query parameters are part of the scope, not the route key"],"exampleFix":"// before\nmiddleware = CavemanASGIMiddleware(app, runtime=rt, routes={'/v1/*': 'openai-chat'}, resolve_context=ctx)\n// after\nmiddleware = CavemanASGIMiddleware(app, runtime=rt, routes={'/v1/chat/completions': 'openai-chat'}, resolve_context=ctx)","handlingStrategy":"validation","validationCode":"VALID_PROTOCOLS = {'openai-chat', 'openai-responses', 'anthropic-messages'}\nassert routes, 'routes must be non-empty'\nfor path, protocol in routes.items():\n    assert isinstance(path, str) and path.startswith('/'), f'bad path: {path!r}'\n    assert '*' not in path and '?' not in path, f'non-exact path: {path!r}'\n    assert protocol in VALID_PROTOCOLS, f'bad protocol: {protocol!r}'","typeGuard":"def is_valid_routes(routes) -> bool:\n    return bool(routes) and all(\n        isinstance(p, str) and p.startswith('/') and '*' not in p and '?' not in p\n        and proto in ('openai-chat', 'openai-responses', 'anthropic-messages')\n        for p, proto in routes.items())","tryCatchPattern":null,"preventionTips":["Keep route paths as constants, never build them from user input or glob templates","Centralize the protocol names in an enum/Literal in your config layer","Unit-test middleware construction so config errors surface in CI, not at boot"],"tags":["python","asgi","configuration","validation","middleware"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}