feat(gateway): chat routes through the LLM gateway with tier-tagged request priority, plus enforced ast-grep lint gates for Java and frontend - #51
Conversation
Chat moves from GitHub Models / OpenAI direct to the LLM gateway data-plane channel (https://api.llm-gateway.iocloudhost.net/v1) using the gateway's gpt-5.4 alias. .env.example now documents LLM_PRIMARY_PROVIDER=openai and the gateway base-URL/model/key shape. Runtime cutover is env vars only (OPENAI_BASE_URL, OPENAI_API_KEY=lgw-*, OPENAI_MODEL=gpt-5.4, LLM_PRIMARY_PROVIDER=openai, GITHUB_TOKEN unset so Spring AI does not send it to the gateway). Embeddings stay on popos-sf7: the gateway only exposes the 4b embedding model, and changing embedding dimensions would invalidate the Qdrant collection. No code change: OpenAiSdkUrlNormalizer already handles the /v1 suffix.
Adds ~55 rules under config/ast-grep/ and frontend/rules/ast-grep/ covering domain naming (no DTO/Payload/Helper suffixes), silent-catch bans, jOOQ/SQL safety, Zod schema hygiene, and testcontainer pinning; wires them into both sgconfig.yml files, replaces config/prek.toml, and documents the workflow in AGENTS.md / docs/development.md / Makefile
… fallbacks Satisfies the new no-catch-return-fallback [RC1a] and no-silent-catch [EH1a] rules without changing any function's contract: - readCookie: malformed percent-encoded CSRF cookie now logs a warning instead of silently yielding no token (csrf.ts) - readApiErrorResponse / refreshCsrfToken / tryParseJson: fallback null/false now flows out of the function body rather than being returned from inside catch, keeping the error log adjacent to the failure (csrf.ts, sse.ts)
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
Caution Review failedPull request was closed or merged during review 📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThis PR comprehensively upgrades development infrastructure and code quality enforcement across the Java backend and TypeScript frontend. It replaces prek with lefthook for Git hooks, adds LLM gateway tier routing via X-Tier headers, expands AGENTS.md development policies, and introduces 50+ new AST-Grep linting rules to enforce patterns around collections, concurrency, type safety, Zod validation, and naming conventions. Frontend error handling for CSRF and SSE improves error logging and recovery. ChangesGit Hooks and Configuration Infrastructure
Java Linting Rules
Frontend TypeScript/TSX Linting Rules
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly Related PRs
Suggested Labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:379f631254
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
This PR updates developer/operator documentation and local tooling to route chat requests through the LLM gateway (OpenAI-compatible provider) and strengthens guardrails via an expanded ast-grep ruleset, alongside a few frontend error-handling/logging fixes (CSRF + SSE JSON parsing).
Changes:
- Switch local git hook management from prek to lefthook (Makefile/docs + new
.config/lefthook.yml; removesconfig/prek.toml). - Expand ast-grep rulesets/config for both backend + frontend and standardize TS parsing via
language: tsx+languageGlobs. - Improve frontend robustness: warn on malformed CSRF cookie decoding; avoid returning from inside catch blocks while keeping existing null/false contracts.
Reviewed changes
Copilot reviewed 88 out of 88 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile | Switch make hooks to install lefthook hooks. |
| frontend/src/lib/services/sse.ts | Refactor JSON parse helper to avoid returning inside catch. |
| frontend/src/lib/services/csrf.ts | Add warning for malformed CSRF cookie; refactor catch/return flow. |
| frontend/rules/ast-grep/warn-broken-contract-union.yml | New frontend rule: flag primitive+object union schemas. |
| frontend/rules/ast-grep/types-are-not-schemas.yml | New frontend rule: forbid *Schema type/interface names. |
| frontend/rules/ast-grep/schema-types-only-infer.yml | New frontend rule: require z.infer-derived exports in schema dirs. |
| frontend/rules/ast-grep/require-pascal-case-types.yml | Update TS language handling for PascalCase type rule. |
| frontend/rules/ast-grep/prefer-safe-parse.yml | New frontend rule: prefer Zod safeParse over parse. |
| frontend/rules/ast-grep/no-zod-type-any.yml | New frontend rule: prohibit ZodTypeAny. |
| frontend/rules/ast-grep/no-zod-loose-types.yml | New frontend rule: prohibit z.json, z.looseObject, .passthrough(). |
| frontend/rules/ast-grep/no-unsafe-casts.yml | New frontend rule: prohibit unsafe casts at trust boundaries. |
| frontend/rules/ast-grep/no-test-mock-files-outside-mocks.yml | New frontend rule: enforce mocks under __tests__/__mocks__. |
| frontend/rules/ast-grep/no-silent-catch.yml | New frontend rule: forbid silent catch default returns (with exceptions). |
| frontend/rules/ast-grep/no-reexports.yml | New frontend rule: forbid re-exports/barrels. |
| frontend/rules/ast-grep/no-record-string-unknown.yml | New frontend rule: forbid Record<string, unknown> bags. |
| frontend/rules/ast-grep/no-magic-literals.yml | New frontend rule: flag magic numeric literals (warn). |
| frontend/rules/ast-grep/no-logic-in-schemas.yml | New frontend rule: keep src/schemas/** declaration-only. |
| frontend/rules/ast-grep/no-legacy-code.yml | New frontend rule: warn on “Legacy/Deprecated/Old” identifiers. |
| frontend/rules/ast-grep/no-generic-variable-names.yml | New frontend rule: forbid generic variable name data. |
| frontend/rules/ast-grep/no-explicit-any.yml | New frontend rule: forbid explicit any. |
| frontend/rules/ast-grep/no-double-zod-validation.yml | New frontend rule: prohibit validating same payload twice. |
| frontend/rules/ast-grep/no-domain-record-cast.yml | New frontend rule: warn on as Record<string, ...> casts. |
| frontend/rules/ast-grep/no-default-unknown-generic.yml | New frontend rule: forbid default generic = unknown. |
| frontend/rules/ast-grep/no-core-unknown-array.yml | New frontend rule: forbid unknown[] bags in core boundary code. |
| frontend/rules/ast-grep/no-core-record-unknown.yml | New frontend rule: forbid Record<string, unknown> in core boundary code. |
| frontend/rules/ast-grep/no-catch-return-fallback.yml | New frontend rule: forbid returning fallbacks from catch. |
| frontend/rules/ast-grep/no-barrel-files.yml | New frontend rule: forbid index.ts(x) barrels. |
| frontend/rules/ast-grep/no-anemic-type-alias.yml | New frontend rule: warn on pure renaming type aliases. |
| frontend/rules/ast-grep/domain-types-in-schemas.yml | New frontend rule: warn on banned generic suffixes (DTO/Data/etc.). |
| frontend/rules/ast-grep/constants-file-uppercase.yml | New frontend rule: constants must use UPPER_SNAKE_CASE. |
| frontend/rules/ast-grep/ban-i-prefix-interface.yml | Strengthen interface naming rule; switch to tsx. |
| frontend/rules/ast-grep/ban-generic-type-suffixes.yml | Switch type suffix rule to tsx. |
| frontend/config/sgconfig.yml | Add languageGlobs so ts/tsx are scanned as tsx. |
| docs/development.md | Document lefthook-based hook installation. |
| config/sgconfig.yml | Add languageGlobs so ts/tsx are scanned as tsx. |
| config/prek.toml | Remove prek hook configuration (deleted). |
| config/ast-grep/warn-broken-contract-union.yml | New shared rule: flag primitive+object union schemas. |
| config/ast-grep/types-are-not-schemas.yml | New shared rule: forbid *Schema type/interface names. |
| config/ast-grep/schema-types-only-infer.yml | New shared rule: require z.infer-derived exports in schema dirs. |
| config/ast-grep/require-pascal-case-types.yml | New shared rule: PascalCase type/interface/enum names. |
| config/ast-grep/prefer-safe-parse.yml | New shared rule: prefer Zod safeParse over parse. |
| config/ast-grep/no-zod-type-any.yml | New shared rule: prohibit ZodTypeAny. |
| config/ast-grep/no-zod-loose-types.yml | New shared rule: prohibit z.json, z.looseObject, .passthrough(). |
| config/ast-grep/no-unsafe-casts.yml | New shared rule: prohibit unsafe casts at trust boundaries. |
| config/ast-grep/no-test-mock-files-outside-mocks.yml | New shared rule: enforce mocks under __tests__/__mocks__. |
| config/ast-grep/no-silent-catch.yml | New shared rule: forbid silent catch default returns (with exceptions). |
| config/ast-grep/no-reexports.yml | New shared rule: forbid re-exports/barrels. |
| config/ast-grep/no-record-string-unknown.yml | New shared rule: forbid Record<string, unknown> bags. |
| config/ast-grep/no-magic-literals.yml | New shared rule: flag magic numeric literals (warn). |
| config/ast-grep/no-logic-in-schemas.yml | New shared rule: keep src/schemas/** declaration-only. |
| config/ast-grep/no-legacy-code.yml | New shared rule: warn on “Legacy/Deprecated/Old” identifiers. |
| config/ast-grep/no-generic-variable-names.yml | New shared rule: forbid generic variable name data. |
| config/ast-grep/no-explicit-any.yml | New shared rule: forbid explicit any. |
| config/ast-grep/no-double-zod-validation.yml | New shared rule: prohibit validating same payload twice. |
| config/ast-grep/no-domain-record-cast.yml | New shared rule: warn on as Record<string, ...> casts. |
| config/ast-grep/no-default-unknown-generic.yml | New shared rule: forbid default generic = unknown. |
| config/ast-grep/no-core-unknown-array.yml | New shared rule: forbid unknown[] bags. |
| config/ast-grep/no-core-record-unknown.yml | New shared rule: forbid Record<string, unknown> bags. |
| config/ast-grep/no-catch-return-fallback.yml | New shared rule: forbid returning fallbacks from catch. |
| config/ast-grep/no-barrel-files.yml | New shared rule: forbid index.ts(x) barrels. |
| config/ast-grep/no-anemic-type-alias.yml | New shared rule: warn on pure renaming type aliases. |
| config/ast-grep/java-web-boundary-threading-cache-jooq.yml | New Java web-layer boundary rules (threads/cache/jOOQ) + allowlist. |
| config/ast-grep/java-testcontainer-postgres-version.yml | New Java test rule: enforce Postgres 17 image version. |
| config/ast-grep/java-testcontainer-no-dockerhub-postgres.yml | New Java test rule: require ECR Public mirror (no Docker Hub). |
| config/ast-grep/java-test-presence-only-grouped-field.yml | New Java test rule: warn on presence-only grouped subfield assertions. |
| config/ast-grep/java-test-no-pageable-response-assertions.yml | New Java test rule: forbid pageable object assertions. |
| config/ast-grep/java-test-fixture-null-arg-train.yml | New Java test fixture rule: forbid null-arg trains. |
| config/ast-grep/java-sql-unqualified-trigram-operator.yml | New Java rule: require schema-qualified trigram operator usage. |
| config/ast-grep/java-sql-unqualified-extension-functions.yml | New Java rule: require schema-qualified extension function calls. |
| config/ast-grep/java-sql-no-runtime-to-tsvector.yml | New Java rule: forbid runtime to_tsvector(...) in SQL strings. |
| config/ast-grep/java-requestbody-requires-valid.yml | Update rule: require @Valid with @RequestBody. |
| config/ast-grep/java-null-arg-train-warning.yml | New Java warning: detect 3+ consecutive null arguments. |
| config/ast-grep/java-no-magic-literals.yml | New Java warning: detect magic numeric literals in comparisons. |
| config/ast-grep/java-no-magic-literals-index.yml | New Java warning: detect magic numeric literals in .get(n) indexing. |
| config/ast-grep/java-no-loop-query-patterns.yml | New Java rule: block loop-contained port/DSL queries (N+1). |
| config/ast-grep/java-no-legacy-code.yml | New Java warning: “Legacy/Deprecated/Old” identifiers in main code. |
| config/ast-grep/java-jooq-advisory-stringly-typed-record-get.yml | New Java advisory: flag stringly-typed record.get(...). |
| config/ast-grep/java-jooq-advisory-raw-sql-qualified-column.yml | New Java advisory: flag raw SQL qualified column string literals. |
| config/ast-grep/java-database-concurrency-boundaries.yml | New Java rules: typed DB config ownership + persistence fan-out limits. |
| config/ast-grep/java-controller-no-mutable-collection-init.yml | New Java warning: controllers shouldn’t instantiate mutable collections. |
| config/ast-grep/java-controller-no-collection-assembly.yml | New Java warning: controllers shouldn’t build collections in loops. |
| config/ast-grep/domain-types-in-schemas.yml | New shared rule: warn on banned generic suffixes (DTO/Data/etc.). |
| config/ast-grep/constants-file-uppercase.yml | New shared rule: constants must use UPPER_SNAKE_CASE. |
| config/ast-grep/ban-i-prefix-interface.yml | New shared rule: ban I* interface prefix. |
| config/ast-grep/ban-generic-type-suffixes.yml | New shared rule: ban generic type suffixes (Data/Info/etc.). |
| AGENTS.md | Update repo guidance (lefthook note, new clean-code/SSO bullets). |
| .env.example | Document OpenAI-compatible gateway configuration hints. |
| .config/lefthook.yml | New lefthook configuration for pre-commit + pre-push gates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…llback returns Replaces the boolean/result-flag shapes from the previous commit with idiomatic control flow that still satisfies [RC1a]/[EH1a]: - readCookie / tryParseJson: early return stays in the try; catch only logs; the null fallback sits after the block (csrf.ts, sse.ts) - readApiErrorResponse: promise .catch maps a parse failure to undefined, keeping the early return (csrf.ts) - refreshCsrfToken: async IIFE + flag replaced by a plain promise chain with .catch(false) (csrf.ts) No contract changes; all call sites untouched.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
…ork sends X-Tier batch Untagged requests land in the gateway queue's zero-reservation 'default' tier and can starve behind batch jobs. Now: - Streaming chat (OpenAI Java SDK clients) and the Spring AI fallback chat client tag requests with X-Tier from LLM_GATEWAY_TIER, default production-z (OpenAIStreamingService.createClient, spring.ai.openai.chat.options.http-headers in application.properties) - The OpenAI-compatible embedding client tags X-Tier: batch — currently ignored by the sf7-direct endpoint, load-bearing if embeddings ever route through the gateway queue (OpenAiCompatibleEmbeddingClient) - .env.example documents LLM_GATEWAY_TIER
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
…scan web controllers
…in no-magic-literals
…in ban-i-prefix-interface
…in constants-file-uppercase
The 29 TypeScript rules duplicated into config/ast-grep were never loaded against frontend code: the frontend lint resolves config/sgconfig.yml from the frontend working directory (frontend/config/sgconfig.yml -> rules/ast-grep), and the backend scan targets src/main/java only. frontend/rules/ast-grep is the single owner per [SS1a]/[SS1b].
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Chat traffic moves from GitHub Models / OpenAI direct to the LLM gateway's data-plane channel (
https://api.llm-gateway.iocloudhost.net/v1, model aliasgpt-5.4), with every request tagged by priority tier so live chat is never starved behind batch jobs. The PR also lands a ~55-rule ast-grep lint gate for both Java and frontend code at pre-push, and the error-handling fixes those rules required. Embeddings intentionally stay onpopos-sf7to avoid invalidating the Qdrant vector collection.Changes
Features
X-Tier: production-z(configurable viaLLM_GATEWAY_TIER), so they land in the gateway's reserved live tier instead of the zero-reservation default (OpenAIStreamingService.createClient,spring.ai.openai.chat.options.http-headersinapplication.properties)X-Tier: batch— inert against today's sf7-direct endpoint, load-bearing if embeddings ever route through the gateway queue (OpenAiCompatibleEmbeddingClient).env.exampledocumentsLLM_PRIMARY_PROVIDER=openai,LLM_GATEWAY_TIER, and the gateway base-URL/model/key shape (.env.example)Code Quality
config/ast-grep/(single owner), frontend rules infrontend/rules/ast-grep/(single owner per [SS1a]); replacesconfig/prek.tomlwith lefthook wiring (.config/lefthook.yml, bothsgconfig.yml)@RequestBody-requires-@Validrule actually fires now: the original pattern was not a parseable Java AST node and matched nothing; rewritten onkind: formal_parameterso it catches both annotation orders and@RequestBody(required = false), and its file scope now includes the realweb/controllers — surfacing 5 existing unvalidated request bodies as warnings (config/ast-grep/java-requestbody-requires-valid.yml)**/web/*.javaalongside the clean-architecture roots (java-controller-no-collection-assembly.yml,java-controller-no-mutable-collection-init.yml)back-end/docs/api-contracts/...) replaced with AGENTS.md tags [TY1]/[CS1g]/[ND1e]/[ND1f] (frontend/rules/ast-grep/*)Bug Fixes
make hooksfails fast when lefthook is missing: previously it printed a skip notice and exited 0, letting commits proceed without gates despite [GT1a]; now exits 1 with the install command (Makefileviarequire_cmd)XSRF-TOKENvalue logs a warning before the request proceeds tokenless (csrf.ts readCookie)csrf.ts readApiErrorResponse/refreshCsrfToken,sse.ts tryParseJson)Required environment variable updates (not in this PR —
.envis gitignored)OPENAI_BASE_URLhttps://api.llm-gateway.iocloudhost.net/v1OPENAI_API_KEYlgw-*client keysk-svcacct-*direct keyOPENAI_MODELgpt-5.4LLM_PRIMARY_PROVIDERopenaigithub_modelswould skip the gatewayGITHUB_TOKENspring.ai.openai.api-keyprefers it and would send it to the gateway (401)REMOTE_EMBEDDING_*Code changes required for the gateway cutover
None — the streaming service's OpenAI Responses API passes through the gateway natively, and
OpenAiSdkUrlNormalizeralready handles the/v1suffix. Verified live:POST /v1/responseswithgpt-5.4returnsstatus: completedthrough the gateway.Breaking Changes
None