Conversation
* feat: add K8s health check probes with DB/Redis/Proxy checks Replace the static "ok" health endpoint with proper K8s liveness/readiness probes that check database, Redis, and Hono proxy layer health with timeout protection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings from PR #1019 - Fix timer type: use `| undefined` instead of non-null assertion - Distinguish Redis "not configured" vs "init failed" via REDIS_URL check - Extract shared `handleReadinessRequest()` to eliminate route duplication - Preserve legacy `/api/actions/health` response format (`status: "ok"`) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: support split web/api domains in usage-doc examples * fix: address review feedback on split-domain PR - Fix Windows Test-NetConnection still using resolvedOrigin instead of apiOrigin - Strip trailing slash from NEXT_PUBLIC_API_BASE_URL to prevent double-slash URLs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add NEXT_PUBLIC_API_BASE_URL to .env.example Document the new env variable introduced for split-domain deployments. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: xuesongjun <xuesongjun@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update changelog for v0.6.8 [skip ci] * fix: update model version references in settings and tests * Update userAgent version in providers.ts * Update messages/zh-CN/settings/providers/form/strings.json Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Revoke change in test unit * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ding <44717411+ding113@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Ding <44717411+ding113@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 新增用户缓存命中率排行榜 * 修复排行榜审查反馈 --------- Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>
* fix: 可用性监控仅统计终态请求 * fix: 可用性监控按状态码识别终态请求 * fix: 可用性监控完善终态统计 * fix: 可用性监控收敛终态判断 * perf: 优化供应商可用性统计查询 * chore: 触发 CI 重跑 * test: 收紧可用性终态查询断言 * fix: 收紧可用性查询参数健壮性 * fix: 收紧可用性接口参数校验 * fix: 收紧可用性聚合桶数量上限 * fix: 对齐可用性默认桶上限并澄清兼容字段语义 * docs: 澄清可用性返回桶语义 * test: 补齐可用性收尾边界 * docs: 明确可用性返回窗口语义 * fix: 限制可用性分桶上限 * fix: 收紧可用性 maxBuckets 越界处理 * fix: 补齐可用性分桶边界校验 * perf: 限制可用性查询时间跨度 * refactor: 统一可用性状态分类阈值来源 * refactor: 收敛 availability CTE 状态码别名来源 * docs: 澄清可用性终态筛选的已知限制 * refactor: 为可用性失败条件添加显式括号 * test: 收紧 finalized_requests CTE 断言 * fix: 补齐当前可用性窗口上界与边界测试 * perf: 收紧可用性查询桶预算与状态窗口 * fix: 按请求量加权最近桶状态判定 * docs: 补充高写表索引迁移提示 * fix: 优先保留终态请求写入补丁 --------- Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com>
…#1025) * feat: add provider group cost multiplier and request detail price breakdown Add cost multiplier support for provider groups (compound with provider multiplier), a group management sub-tab in the providers page, and a detailed per-component price breakdown in the request detail billing section. - New `provider_groups` table with name, cost_multiplier, description - New `group_cost_multiplier` and `cost_breakdown` columns on message_request - New `group_cost_multiplier` column on usage_ledger (via trigger update) - Compound multiplier: finalCost = baseCost * providerMultiplier * groupMultiplier - Provider group management UI (Groups tab in provider settings) - Request detail shows per-component costs, base total, and both multipliers - Server actions for group CRUD with admin auth - i18n strings for all 5 locales (en, zh-CN, zh-TW, ja, ru) - Unit tests for cost calculation, repository, and existing test mocks updated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments for provider group multiplier Critical: - trackCostToRedis now includes group multiplier so Redis-tracked costs match DB costs - Split cache_creation into cache_creation_5m / cache_creation_1h in breakdown to avoid duplicated display under mixed-TTL requests Major: - Provider-selector wraps getGroupCostMultiplier in try/catch, falls back to 1.0 on errors - Provider-group actions now reject NaN/Infinity multipliers (Number.isFinite check) - Delete group action blocks when providers still reference the group (new GROUP_IN_USE error code) - Group form handleSave shows toast.error instead of silent return on invalid multiplier Minor / hardening: - Cost-calculation normalizeRequestCostOptions sanitizes multipliers (NaN/Infinity/negative -> 1.0) - Session setGroupCostMultiplier validates value before assignment - SummaryTab replaces hardcoded "tokens" label with i18n key (unit.tokens) - costBreakdown type widened to allow null for explicit column clearing Tests: added 5 new cases covering multiplier sanitization and cache breakdown split. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second round of PR review comments Critical (P1): - Multi-group providerGroup ("premium,enterprise") no longer silently falls back to 1.0x; getGroupCostMultiplier now parses the string and uses the first matching group in the provider_groups table. 4 new unit tests cover single/multi/no-match. Major: - Create / Update errors now map errorCode to localized toast messages (NAME_REQUIRED / DUPLICATE_NAME / INVALID_MULTIPLIER), matching the Delete flow. - Name validation moved out of startSaveTransition to avoid a brief loading flash on validation failures. Minor / hardening: - storedBreakdown now persists sanitizeMultiplier()-clamped values so total === base_total * provider_multiplier * group_multiplier holds for unsanitized inputs. sanitizeMultiplier is now exported from cost-calculation. - Legacy mixed-TTL records (no cache_creation_5m/_1h, cacheTtlApplied="mixed") now split the aggregate proportionally by token counts instead of dumping it into the 5m row. Nitpicks: - Replace hardcoded "default" with PROVIDER_GROUP.DEFAULT constant in repo, provider-group-tab, and SQL ordering. - Don't cache getGroupCostMultiplier misses (1.0 fallback) so newly-created groups propagate on the next request rather than up to 60s later. - Rename local providerGroups variable to parsedGroups to avoid shadowing the Drizzle table identifier. - Remove unreachable default-group catch fallback in deleteProviderGroup action (pre-check already handles this case). - Mirror unit.tokens key into dashboard.logs.billingDetails in all 5 locales so the two billingDetails blocks stay structurally consistent. Tests: +4 new cases for multi-group resolution, first-wins, no-match, and no-cache-on-miss. Total provider-groups repo tests: 10 pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion, adding the “xhigh” option (#1026) * fix: Updated the effort level in Claude's adaptive thinking configuration, adding the “xhigh” option * fix: Update on i18n for Claude Effort xHigh
* feat(ip): schema + unified IP extraction + IP geolocation client Phase 1–4 of IP recording & audit-log feature: Schema: - Add `client_ip` column to `message_request` and `usage_ledger` (plus a filtered index on message_request). The `fn_upsert_usage_ledger` trigger is updated (in-migration) to propagate `client_ip` and the previously-unwired `group_cost_multiplier` from message_request. - Add `ip_extraction_config` (jsonb) and `ip_geo_lookup_enabled` (bool) to `system_settings`. - New `audit_log` table with indexes for category/operator/target/time keyset pagination. - Also bundles the orphaned schema delta from #1025 (provider_groups table, group_cost_multiplier, cost_breakdown) which was committed to schema.ts without a matching migration; migration 0089 brings the on-disk state back in sync. Unified IP extractor (src/lib/ip/): - `extractClientIp(headers, config)` — configurable rule chain supporting leftmost / rightmost / 0-based-index picks for XFF-style headers, with IPv4/IPv6 parsing, port/bracket stripping, and graceful fallback. - `isPrivateIp(ip)` — RFC 1918, loopback, ULA, link-local, CGN classifier. - `getClientIp(headers)` convenience wrapper that reads system settings' `ipExtractionConfig` from the in-memory cache. - 43 unit tests covering header variants, XFF picks, invalid input, IPv6, case-insensitivity, and settings integration. Proxy pipeline: - `ProxySession.clientIp` is populated by `ProxyAuthenticator` using the new unified extractor (replaces the ad-hoc prefer-x-real-ip-then-XFF logic in auth-guard.ts). - `ProxyMessageService` forwards the IP into `createMessageRequest`; the repository persists it and includes it in SELECT paths. - `/api/auth/login` drops its copy of the extractor and uses the unified util too (still respects platform-provided IP first). IP geolocation client (src/lib/ip-geo/): - `lookupIp(ip, { lang })` — Redis-cached (TTL configurable, 3600s default), skips upstream for private IPs, caches negative results for 60s, aborts via AbortController on timeout, never throws. - Env: `IP_GEO_API_URL` (default https://ip.api.claude-code-hub.app), `IP_GEO_API_TOKEN` (optional Bearer), `IP_GEO_CACHE_TTL_SECONDS`, `IP_GEO_TIMEOUT_MS`. - `/api/ip-geo/:ip` route exposes the client to the dashboard for authenticated users; gated by the new `ipGeoLookupEnabled` system setting. - 8 tests covering cache hits, Bearer auth, private short-circuit, upstream 5xx / network / malformed / timeout fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(audit): audit-log infra + login & admin-mutation audit hooks Phase 5–7: Audit infrastructure (src/lib/audit/): - `request-context.ts` — AsyncLocalStorage holding operator IP + UA. Populated by the action adapter once per request so audit hooks don't re-parse headers. - `redact.ts` — deep-copy with sensitive keys (key/apiKey/token/secret/ password/authorization/webhook_secret) replaced by `[REDACTED]`; extraKeys passed through by callers (e.g. provider.key, key.key). - `with-audit.ts` — generic wrapper that snapshots before/after, emits a success row on completion and a failure row + rethrow on exception. - `emit.ts` — one-shot helper used by action authors who prefer inline success/failure emits over function wrapping. Tolerant of partial `@/lib/auth` mocks in tests (falls back to null session). Audit-log repository (src/repository/audit-log.ts): - `createAuditLogAsync` — fire-and-forget insert; never blocks or fails the hot path. - `listAuditLogs` — keyset-paginated read with category/operator/target/time filters. - `getAuditLog`, `countAuditLogs` — single-row + count helpers. Action adapter (src/lib/api/action-adapter-openapi.ts): - Populate `runWithRequestContext` with operator IP + UA around every action execution. Defensive against missing Hono context fields (tests may mock `c.req` with only a subset of methods). Login audit (src/app/api/auth/login/route.ts): - `auth.login.success` on every successful admin-token or user-key login (operator identity captured from the resolved session). - `auth.login.failure` on missing key, invalid key. - `auth.login.rate_limited` on `LoginAbusePolicy` deny. Mutation audit (src/actions/*.ts): - users.ts: addUser / editUser / removeUser - providers.ts: addProvider / editProvider / removeProvider - provider-groups.ts: createProviderGroup / updateProviderGroup / deleteProviderGroup - keys.ts: addKey / editKey / removeKey (raw key never logged; redactor strips `key` from before/after) - system-config.ts: saveSystemSettings (full before/after snapshot) - notifications.ts: updateNotificationSettingsAction - sensitive-words.ts: create / update / delete - model-prices.ts: upsertSingleModelPrice / deleteSingleModelPrice / uploadPriceTable / syncLiteLLMPrices Tests: +11 unit tests for redact + withAudit (`src/lib/audit/*.test.ts`). Full suite: 4468 pass, 1 pre-existing failure unrelated to this change (`tests/api/api-endpoints.test.ts` 健康检查 — fails on dev before this PR because the test environment lacks DSN). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ui): IP details dialog, request-detail IP display, settings form + i18n Phase 8 (partial): IP details dialog (src/app/[locale]/dashboard/_components/ip-details-dialog.tsx): - Reusable Radix Dialog that renders the full `IpGeoLookupResult` from the `/api/ip-geo/:ip` endpoint: country + flag, region / city / postal / coordinates, timezone, ASN + organization + route + RIR, privacy badges (VPN / Proxy / Tor / Relay / Threat / Clean), threat score + risk level, and abuse contact. - useQuery is gated to `open &&` — tests that never open the dialog don't need a QueryClientProvider in their harness. Request-detail drawer (SummaryTab / ErrorDetailsDialog): - Show client IP alongside User-Agent and Endpoint in the client-info card. - IP renders as a clickable underlined button that opens the IP-details dialog. - `clientIp` plumbed through ErrorDetailsDialog → SummaryTab via the TabSharedProps type, and populated from the usage-log rows. System settings form (src/app/[locale]/settings/config/_components/system-settings-form.tsx): - New "IP logging & extraction" section with: - Toggle for `ipGeoLookupEnabled` - JSON textarea for `ipExtractionConfig` + "Reset to default" button - Helper text describing the fallback chain semantics - Wired through `saveSystemSettings`, `UpdateSystemSettingsSchema`, and the system-config repo update/returning columns. i18n: - New `ipDetails` and `auditLogs` namespaces for all 5 locales (en / zh-CN / zh-TW / ja / ru), registered in each locale's index. - New `ipLogging` section under `settings/config.json` for all 5 locales. Repo/types: - `UsageLogRow.clientIp` added; select list on both messageRequest and ledger-fallback query paths updated. - `messageRequest.clientIp` + `usageLedger.clientIp` already added in phase 1. Tests: test fixtures for usage-logs tables updated with `clientIp: null`. Full suite: 4468 pass, 1 pre-existing unrelated failure (tests/api/api-endpoints.test.ts 健康检查 — fails on dev because the test environment lacks DSN, unchanged by this PR). Not yet included in this commit: audit-log dashboard page, IP column in usage-logs table. Those land in phase 9 if time permits; the column in the request-detail drawer already unblocks IP visibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ui): audit-logs dashboard page + IP column on usage logs Phase 8 (complete): Audit logs dashboard: - `src/actions/audit-logs.ts` — admin-only server action pair (`getAuditLogsBatch` + `getAuditLogDetail`) wrapping the existing audit-log repository. ISO date strings on the filter converted to `Date` internally. - `src/app/[locale]/dashboard/audit-logs/page.tsx` — admin-gated server page (redirects non-admins to /dashboard). - `_components/audit-logs-view.tsx` — virtualized TanStack-Query infinite list with category + status filters; IP clicks open `IpDetailsDialog`; row clicks open the detail sheet. - `_components/audit-log-detail-sheet.tsx` — Sheet showing full entry: category, action, target, operator (name + key name + user id with "Admin Token" fallback), IP (clickable), user-agent, error message, and before/after JSON in `<pre>` blocks. - Registered both actions in `src/app/api/actions/[...route]/route.ts` (admin-only). - Added admin-only "Audit logs" nav entry in `dashboard-header.tsx`. IP column on usage logs table: - Non-virtualized (`usage-logs-table.tsx`) and virtualized (`virtualized-logs-table.tsx`) both gain a new IP column between sessionId and provider. Cell is a clickable underlined monospace IP that opens `IpDetailsDialog`; null → `—`. Empty-row colSpan bumped. - `src/lib/column-visibility.ts` — added `"ip"` to the column union + default visible set. - `column-visibility-dropdown.tsx` — added the `ip` label key. Audit-log repository — `createAuditLogAsync` is now an `async` function (returns a Promise callers should `void`) so the file satisfies Next's "use server" constraint. Behaviour unchanged: still fire-and-forget, still swallows errors with a warn-level log. Fixes a production-build failure. i18n: - `dashboard.json` gains `nav.auditLogs` + `logs.columns.ip` across all 5 locales. Pre-commit (per CLAUDE.md): - `bun run build` ✓ (was failing before the async fix; now clean). - `bun run typecheck` ✓. - `bun run lint` ✓ (20 pre-existing warnings, no errors). - `bun run test` — 4468 pass, 1 pre-existing unrelated failure (`tests/api/api-endpoints.test.ts 健康检查`, fails on dev too: needs DSN in test env). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address PR #1027 review comments (security + i18n + robustness) Security / correctness: - P1: remove `cf-connecting-ip` from DEFAULT_IP_EXTRACTION_CONFIG. Trusted by default it was spoofable in non-CF deployments (login / pre-auth rate-limit bypass). Operators fronted by Cloudflare add it explicitly. - Critical: `fn_upsert_usage_ledger` no longer overwrites `created_at` on conflict. Ledger rows' insert time is immutable by design. - Major: `redactSensitive()` now only walks POJOs — Date / Map / Buffer / class instances pass through intact instead of being rewritten to `{}`. - Major: fix `webhookSecret` redaction. DEFAULT_SENSITIVE_KEYS was mixed- case vs the lowercased walker; camelCase webhook secrets slipped through unscrubbed. All keys normalized to lowercase; also added `api-key` and `webhook-secret` variants. +3 tests. - Restrict `/api/ip-geo/:ip` to admin (IP lookup is an operator tool; non-admins going through it would scan IPs against our upstream quota). - Never write raw `error.message` into `audit_log.error_message`. Pg errors can carry SQL fragments, constraint names, or user input (duplicate key values). All mutation actions now persist a stable `UPDATE_FAILED` / `CREATE_FAILED` / `DELETE_FAILED` / `SYNC_FAILED` code instead. - Stricter IP-geo upstream shape validation. Cached-for-1h payloads must now carry `ip`, `location.country.{code,name}`, and `connection.asn`; partial drifts fall into the 60s negative cache instead of blowing up the dashboard dialog. Audit robustness: - Remove dead `withAudit` wrapper. Production only uses `emitActionAudit`, and the wrapper's own `extractAfter` / `redactSensitive` lived inside the success-path try block and could turn a completed mutation into a thrown exception. Flagged by two reviewers. - `emitActionAudit` uses a new `resolveRequestContext()` that falls back to `next/headers` when the OpenAPI adapter's AsyncLocalStorage is empty. This captures operator IP / User-Agent for direct Next.js Server Actions (e.g. the settings form's `startTransition`) that bypass the adapter. - `editUser`: before-snapshot + after-snapshot via `safeFindUser()` (never throws — audit is best-effort, must not break the mutation). - Explicit `void` on `createAuditLogAsync(...)` to mark fire-and-forget intent rather than leaving a floating promise. UX / i18n: - System settings: invalid IP-extraction JSON (or wrong shape) now blocks save with a localized toast error rather than silently reverting to the default chain. `ipLoggingInvalidJson` / `ipLoggingInvalidShape` keys in all 5 locales. - `risk_level` rendered from upstream now goes through `ipDetails.riskLevels.*` (none/low/medium/high/critical) in all 5 locales; unknown future enums fall back to the raw string. - Localize all hardcoded strings in audit dashboard: "Admin Token" fallback, "User-Agent" row label, "id/key/user id:" label prefixes, "Error" loader fallback, `auditLogs.adminTokenOperator` / `loadError` / `errors.*`. - `auditLogs` actions use `getTranslations("errors")` + `ERROR_CODES` so non-admin / failure paths speak the operator's language and expose a stable `errorCode` for the UI. Smaller things: - `/api/ip-geo/:ip` accepts `?lang=`; dashboard's `useIpGeo` forwards the current locale so country/city names come back localized. - `useIpGeo` route params no longer double-decoded (Next already decodes path params; the old `decodeURIComponent` would throw on a lone `%`). - `countAuditLogs` now accepts the same filter set as `listAuditLogs` so paginated counts stay consistent with page contents. - `audit-logs-view` virtualizer `itemCount` includes a trailing loader row when `hasNextPage` is true, so the in-list loader actually renders. - Replace `../../_components/ip-details-dialog` relative imports with the `@/` alias across logs + audit-log views. - Default `IP_GEO_API_URL` switched to `https://ip-api.claude-code-hub.app`. Pre-commit: typecheck ✓ / lint ✓ (20 pre-existing warnings, 0 errors) / build ✓ / test 4466 pass, 1 pre-existing unrelated failure (`tests/api/api-endpoints.test.ts 健康检查`, fails on dev too — missing DSN in test env). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ip-geo): accept partial payloads + hide null fields in IP dialog Upstream returns null for many fields when the IP is CGN / bogon / Tailscale (100.64/10, 198.18/15, etc.) — concrete example from prod: { "ip": "100.85.244.112", "location": { "country": { "code": "ZZ", "name": "Unknown", ... }, "region": null, "city": null, "postal_code": null, "coordinates": { "latitude": 0, "longitude": 0, "accuracy_radius_km": null } }, "connection": { "asn": null, "handle": null, "route": null, "domain": null, "organization": "Carrier-Grade NAT RFC6598", "rir": "UNKNOWN", ... }, ... } Two problems before this commit: 1. `isValidLookupResult` required `connection.asn: number`, so the entire CGN payload was rejected as "unexpected shape" and negative-cached for 60s. Operators never saw the carrier/NAT org info we DO have. 2. The dialog rendered every field unconditionally, producing strings like `ASnull`, a bare "Route:" row with no value, and `0, 0` coordinates. Fixes: - Types: `IpGeoConnection.asn` and `route` are `string | number | null`; `IpGeoFlag.svg` and `png` are `string | null`. Mirrors reality. - Validator: require only the truly UI-critical subtree (`ip`, `location.country.{code,name}`, `timezone.id`, `connection` as an object). Nullable asn / route pass through. Payloads still missing country / timezone are still rejected. - Dialog hides rows when their value is null or the "unknown" sentinel: - `asn === null` → row hidden (instead of `ASnull`) - `route === null` → row hidden - `organization === null` → row hidden - `rir === "UNKNOWN"` → row hidden - `is_anycast === false` → row hidden (it's the common case) - Coordinates hidden when `accuracy_radius_km === null` OR lat/lng are both 0 — the API's "we don't actually know" signal. Pure helper `hasMeaningfulCoordinates()` is exported and unit-tested. Tests (+7 cases): - `client.test.ts`: the full CGN payload is accepted; partial payload missing top-level required subtree is still rejected. - `ip-details-dialog.test.tsx` (new, happy-dom): - `hasMeaningfulCoordinates()` across null accuracy / 0,0 null-island / real lat+lng / accuracy=0 cases (4 cases). - Full-render check with the CGN payload: asserts `ASnull` never appears, coordinates row hidden, `UNKNOWN` RIR hidden, `Anycast` row hidden when false, organization + hostname + timezone still present. Typecheck / lint / build / test pass (1 pre-existing unrelated failure). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: format code (feat-ip-logging-and-audit-8f35b00) * fix: follow-up review comments (round 3) After re-reading every inline review comment against current code, 7 were still valid and worth fixing: Schema: - `provider_groups.{created_at, updated_at}` gain NOT NULL in both schema.ts and the migration CREATE TABLE. Matches the `audit_log.created_at` pattern and prevents an explicit NULL slipping in via raw SQL. Drizzle emitted migration 0090 as the catch-up ALTER for installs that already ran 0089 with nullable columns; it's idempotent on fresh installs. i18n: - All 5 locale hint strings for `ipLogging.extractionConfigHint` now describe the actual safe default chain (`x-real-ip → x-forwarded-for rightmost`), not the old CF-first chain. `cf-connecting-ip` appears only inside the "custom example for Cloudflare deployments" JSON so users aren't misled about what the default trusts. users.ts: - `user.create` audit extracted to `emitUserCreateAudit(user)` and called from BOTH `addUser` and `createUserOnly` (the unified-edit-dialog create path). Previously `createUserOnly` emitted no audit at all. - Success-path `after` snapshot is now the full user record returned by `createUser()` (id, name, role, isEnabled, providerGroup, quota / limit fields, expiresAt, tags, allowed/blocked clients & models, …) instead of the partial shape. - `createUserOnly`'s catch block now emits a `CREATE_FAILED` failure row too. - `editUser` / `removeUser`: hoisted `safeFindUser(userId)` above the try/catch boundary so failure audit events also carry `before` snapshot + `targetName`. The helper never throws, so the hoist is safe. emit.ts: - Wrapped `emitAsync` body in try/catch. `createAuditLogAsync` and `resolveRequestContext` already swallow their own errors, but `redactSensitive()` walks caller-supplied values — a circular ref or a throwing getter would otherwise surface as an unhandled rejection at the `void emitAsync(...)` site. Caught errors are logged at `warn`. ip-geo/client.ts: - `isValidLookupResult` also requires `country.flag` to be an object with a string `emoji`. The dialog dereferences `.flag.emoji` unconditionally, so a drifted-upstream payload without the flag would render-crash. With the extra gate such payloads fall into the 60s negative cache instead. (Note: the review pointed at a phantom "second identical block around line 99-105" — that's the single `isValidLookupResult` caller in `fetchFromUpstream`, not a duplicate. Only one validator needed updating.) - +1 test: payload with flag but no `emoji` is rejected. audit-logs.ts: - `AUDIT_CATEGORY_VALUES` now uses `as const satisfies readonly AuditCategory[]` + an `Exclude<>`-based exhaustiveness type-check. Adding a variant to `AuditCategory` without listing it here becomes a TS error instead of a runtime-only drift. Pre-commit: typecheck ✓ / lint ✓ / build ✓ / test 4476 pass, 1 pre-existing unrelated failure (tests/api/api-endpoints.test.ts 健康检查). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…ion labels and risk levels
…usage page (#1029) * feat: hide IP column by default and reuse VirtualizedLogsTable in my-usage page - IP column is now hidden by default in the request logs table; users can toggle it via the column visibility dropdown - my-usage page now uses the same VirtualizedLogsTable component as the dashboard logs page, with user/key/provider columns always hidden - Detail side-panel dialog is disabled on my-usage page (no decision chain or grouping info exposed) - New server action getMyUsageLogsBatchFull scoped to current key with allowReadOnlyAccess support - VirtualizedLogsTable gains fetchFn, queryKeyPrefix, and disableDetailDialog props for reuse flexibility Amp-Thread-ID: https://ampcode.com/threads/T-019d9b6e-07da-72af-8a57-3aee3dc20f44 Co-authored-by: Amp <amp@ampcode.com> * fix: address PR review comments - catch block in getHiddenColumns returns DEFAULT_HIDDEN_COLUMNS for consistency with missing-storage fallback - getMyUsageLogsBatchFull strips userId/keyId/providerId from client params before spreading (defense-in-depth) - getStatusBadgeClassName simplified with extracted fallback constant and proper null-safe checks - onRedirectClick is undefined when disableDetailDialog is true to avoid clickable-but-no-op elements - Extract parseDateRangeToTimestamps to shared @/lib/utils/date-range - Pass currencyCode and billingModelSource to VirtualizedLogsTable in my-usage page via getMyUsageMetadata fetch Amp-Thread-ID: https://ampcode.com/threads/T-019d9b6e-07da-72af-8a57-3aee3dc20f44 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
- Introduce shared IpDisplayTrigger component to unify IP cell rendering and truncation across audit-logs, usage-logs, virtualized-logs tables and SummaryTab - Add copy-to-clipboard action with i18n (en/ja/ru/zh-CN/zh-TW) in IP details dialog - Ignore stray bunx-*/ cache directories Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… of truth (#1030) * fix(provider-groups): sync provider_groups table with groupTag source of truth Provider group management tab displayed "no groups configured" even when groups existed, because `provider_groups` metadata table was never populated from legacy `providers.groupTag` strings. Also adds per-member priority editing so the tab can manage `groupPriorities` overrides. - repo: add ensureProviderGroupsExist() for idempotent batch upsert with ON CONFLICT DO NOTHING + cache invalidation - action: getProviderGroups read-time self-heal — backfill any groups referenced by providers.groupTag but missing from the table - action: write-time sync in addProvider / single-field edit / batch updateProvider, so new tags immediately materialize group rows - ui: expandable group rows list members with inline per-group priority editing; saves via editProvider.group_priorities merge (only touches the target key, preserving other overrides and global priority) - i18n: add groupMembers / effectivePriority / noMembers / savePriorityFailed / savePrioritySuccess keys across 5 locales Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(provider-groups): address bugbot review feedback - Wrap read-time self-heal in try/catch so Tab stays usable when sync fails (e.g. historical groupTag exceeds provider_groups.name 200-char cap); filter oversized names before upsert - Merge the two provider-iteration passes in getProviderGroups into one - editProvider now also materializes groups referenced only in group_priorities (previously only group_tag triggered sync) - batchUpdateProviders log key mismatch (updateProvider -> batchUpdateProviders) - Member priority editor rejects empty/whitespace input instead of silently saving 0 (Number("") === 0 bypasses all numeric validators) - Members sub-table column header fixed: providerName, not groupName Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ions (#1031) * feat: redesign IP details dialog with hero strip and collapsible sections The old dialog showed only ~15 of the ~50 meaningful fields returned by the IP geolocation API, and buried the most important fact — the risk level — as the smallest text at the bottom. This rewrite flips that priority. At a glance, three hero cards answer the questions admins actually ask when they click an IP in a log row: - Risk: colored tint + score meter + active privacy/threat chips - Location: flag + country + city/region + EU badge - Network: type badge + ASN + organization + domain link Everything else — country deep info, timezone/DST, hostname/route/RIR, company, hosting, carrier, full 8-cell privacy/threat signal grid, blocklists, and abuse contact (name/email/phone/address) — lives in collapsible sections that auto-expand when they have content and hide entirely when they don't. Null handling is systematic: FieldRow, SubCard, and each Section ignore null/empty values so the layout shrinks to fit the available data. No "N/A" placeholders, no "ASnull" garbage, no "0, 0" coordinates for IPs the upstream couldn't locate. i18n expanded across all 5 locales (en, zh-CN, zh-TW, ja, ru) with new keys for network types, subtypes, scopes, company types, blocklist categories, privacy/threat signal descriptions, and per-field labels. Verified with 14 unit tests covering CGN/bogon, full-data (8.8.8.8), and high-risk (Tor + blocklists) payloads. typecheck/lint/build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ip-details): address PR review — consistent signals, safer defaults, shared link atom Applies review feedback on #1031: - Extract hasActiveThreatSignals(privacy, threat) to atoms.tsx and use from both RiskHero (isClean) and SecuritySection (anyActive). The hero previously ignored is_crawler while the section counted it, producing a green "Clean" hero for an IP whose crawler chip was highlighted below. - asRiskLevel now maps unrecognized upstream values to a new "unknown" bucket styled amber, rather than silently coercing them to "none" (the safest state). Adds riskLevels.unknown across all 5 locales. - CopyButton: move the 1.5s reset into a useEffect with cleanup so the timer is cleared on unmount and rapid clicks don't race an older timer into clobbering the checkmark. - formatLocalTime: check for Invalid Date explicitly before formatting — Intl.DateTimeFormat.format(Invalid Date) returns the literal string "Invalid Date" rather than throwing, so the try/catch alone wasn't enough. - Location predicates: apply the same sentinel filtering the render code uses (calling_code !== "+0", tld !== ".unknown", name_native !== name) inside hasLocationContent and showCountrySubCard, so no empty-shell Country sub-card survives when every field is a placeholder. - NetworkSection: swap the three inline <a target="_blank"> anchors for the shared ExternalLink atom (simplified to layout-neutral styling), and drop the duplicate Hostname FieldRow — hostname is already shown as the dialog subtitle. - Remove unused hero.clean and badges.clean keys from all 5 locale files. Adds two regression tests: (1) crawler-only IPs don't show the clean hint; (2) an unrecognized risk_level falls back to "unknown", not "none". All 16 dialog tests pass; typecheck / lint / build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rows The Country sub-card mostly holds encyclopedic trivia (capital, calling code, TLD, area, population, borders, languages, currencies) that isn't useful when scanning a suspicious IP. Collapse it by default via a new `collapsible` option on SubCard, keeping operationally meaningful data (coordinates, timezone, ASN handle/domain/route, company, hosting) fully expanded. Also drops the Continent row from the location body since the hero already shows it, and filters the "-" postal-code sentinel that upstream returns for locations without a real ZIP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Always refetch on dialog mount so users see fresh data; server-side cache still absorbs upstream load. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ip-details): regroup geo details and add map view * chore(review): address follow-up nits * fix(review): tighten DSN and map control handling * fix(review): cover map behaviors directly
* fix: scope my-usage ip geo lookups * test: relax visible ip geo assertion * fix: preserve my-usage ip geo errors * fix: align my-usage ip visibility checks
* feat: align provider group management with provider list * fix: preserve group view context in provider editor
* fix: improve IP extraction config editor * test: clarify IP extraction form fixture setup
* fix: refine usage logs cost tooltip breakdown Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore: ignore local opencode workspace Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * chore: format cost tooltip table component Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix: localize usage logs cost tooltip unit price Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * test: align cost tooltip review follow-ups Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
Important Review skippedToo many files! This PR contains 273 files, which is 123 over the limit of 150. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (273)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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: 11de5dee1c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Walk the user's declared groups in order; the first matching DB row wins. | ||
| let resolved: number | null = null; | ||
| for (const name of parsedGroups) { | ||
| const group = await findProviderGroupByName(name); |
There was a problem hiding this comment.
Respect group priority when resolving cost multipliers
getGroupCostMultiplier chooses the first parsed group as the winning multiplier, but user/key group strings are normalized elsewhere with alphabetical sorting before persistence. That means a configured order like premium,default can be stored/replayed as default,premium, so billing uses lexicographic order instead of intended priority and can systematically over/under-charge multi-group users. The resolver should not depend on reordered string position for precedence.
Useful? React with 👍 / 👎.
| * needed. | ||
| */ | ||
| export async function countProvidersUsingGroup(name: string): Promise<number> { | ||
| const rows = await db.select({ groupTag: providers.groupTag }).from(providers); |
There was a problem hiding this comment.
Ignore soft-deleted providers in group usage checks
countProvidersUsingGroup reads all providers without a deleted_at IS NULL filter, but providers are soft-deleted. As a result, deleted providers still count as references and can block deleteProviderGroup with a false "group in use" error even when no active provider uses that group.
Useful? React with 👍 / 👎.
Co-authored-by: mci77777 <mci77777@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3eb132654d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| <IpDisplayTrigger | ||
| ip={log.operatorIp} | ||
| onClick={() => openIp(log.operatorIp as string)} | ||
| buttonClassName="font-mono" |
There was a problem hiding this comment.
Stop IP-trigger clicks from opening the row detail
The row container is clickable (onClick={() => openDetail(log)}), but the IP trigger inside it calls openIp(...) without stopping event propagation, so clicking an IP opens both the IP dialog and the audit detail sheet. This is reproducible for any row with operatorIp and makes the IP action ambiguous/confusing because users cannot open IP details without also triggering row navigation behavior.
Useful? React with 👍 / 👎.
* fix: repair audit log action i18n labels * fix: align audit log imports with alias rules
🧪 测试结果
总体结果: ✅ 所有测试通过 |
Fixed TypeScript error: - src/actions/users.ts: Removed incompatible index signature from UserCreateAuditSnapshot type The [key: string]: unknown index signature required the User type to also have an index signature, which it doesn't. Removing it is safe because TypeScript's structural typing allows passing objects with more properties than the type requires. CI Run: https://github.com/ding113/claude-code-hub/actions/runs/24651486330
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5bec462c5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!hasPotentialIpHeader(source)) { | ||
| return extractClientIp(source, DEFAULT_IP_EXTRACTION_CONFIG); | ||
| } |
There was a problem hiding this comment.
Fetch settings before short-circuiting unknown IP headers
This early return prevents getClientIpWithFreshSettings from ever loading persisted ipExtractionConfig unless one of the hard-coded header names is present. In deployments that use a custom trusted header (for example from a gateway) and do not send x-real-ip/x-forwarded-for, every request takes this branch, so extraction stays on defaults and the configured header is ignored indefinitely. That causes incorrect client-IP attribution and mis-scoped per-IP abuse controls on login/proxy paths.
Useful? React with 👍 / 👎.
🧪 测试结果
总体结果: ✅ 所有测试通过 |
* fix: stop legacy long-context surcharge fallback * fix: preserve shared pricing fields during merge
* feat: add fake200 retry tooltip explanation * fix: address fake200 tooltip review feedback
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2f04d6779
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const result = await findUsageLogsBatch({ | ||
| ...safeParams, | ||
| keyId: session.key.id, | ||
| }); |
There was a problem hiding this comment.
Preserve ledger fallback in my-usage full log fetch
getMyUsageLogsBatchFull now queries findUsageLogsBatch directly, which only reads message_request; unlike the previous my-usage path (findUsageLogsForKeyBatch), it does not fall back to usage_ledger when isLedgerOnlyMode() is true. In deployments upgraded from older versions where message_request is empty but ledger data exists, the new my-usage table will always return no logs, effectively breaking the page for affected users.
Useful? React with 👍 / 👎.
…1048) * feat(deploy): add k3s/k8s one-click deploy script and cch management CLI 将本地 k3s/k8s 部署资产迁入仓库,对齐现有 scripts/deploy.sh 的一键体验, 支持 k3s 与标准 Kubernetes (EKS/GKE/AKS) 双兼容。 新增: - scripts/deploy-k8s.sh: 一键部署引导(CLI 参数 + 交互回退 + 幂等升级), 自动探测 runtime、StorageClass、Ingress 变体 (Traefik IngressRoute / 标准 Ingress / NodePort 兜底),支持 --install-k3s 自动装 k3s - scripts/cch: 运维管理 CLI,包含 update/restart/rollback/scale/backup/ info/doctor/uninstall 等子命令,支持配置文件与 env 覆盖 - deploy/k8s/**: 15 份 manifest 模板(namespace / app / postgres / redis / ingress),使用 {{VAR}} 占位符,部署时由 python 统一渲染 - docs/k8s-deployment.md: 10 节完整部署运维指南,含架构图、参数参考、 云厂商 StorageClass 映射、故障排查 - README.md / README.en.md: 新增 Kubernetes / k3s 部署章节 关键兼容与安全改造(经三轮 Codex 审查): - BSD/macOS 兼容:去除 cp -RT / date -Iseconds / xargs -r 的 GNU 专属用法; 新增 b64d() 兼容 GNU/BSD base64 - Secret 不再通过 --from-literal 传参(避免进程表泄露),改为 600 权限临时 文件 + --from-file - 去除独立 migration Job(drizzle-kit 为 devDependency 在运行时不可用), 改由应用 AUTO_MIGRATE=true 启动时自动迁移,避免并发竞态 - App rollout 超时 + 升级模式下自动 rollout undo + HPA/副本数恢复 - 非交互场景 backup 失败直接中止;uninstall 需 CCH_CONFIRM_UNINSTALL=<ns> 显式授权 - cch RUNTIME_OVERRIDE=k3s 且无 kubectl 时自动回退 sudo k3s kubectl - 新增 --disable-networkpolicy flag,适配 Ingress Controller 在非标准 namespace 的集群 验证: - bash -n 两个脚本通过 - kubectl apply --dry-run=client 15/15 manifest 通过 - --dry-render 覆盖 NodePort 与 Ingress 两种场景,占位符残留 = 0 - 三轮 Codex 审查最终 10/10,无剩余阻断项 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deploy): address k8s review feedback * fix(deploy): follow up k8s review round 2 * fix(deploy): address final review follow-ups * fix(deploy): resolve review edge cases --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70e3d1f217
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -162,7 +167,7 @@ export async function findUsageLogsBatch( | |||
| } | |||
|
|
|||
| // Fetch limit + 1 to determine if there are more records | |||
| const fetchLimit = limit + 1; | |||
| const fetchLimit = safeLimit + 1; | |||
There was a problem hiding this comment.
Use clamped page size in ledger fallback pagination
findUsageLogsBatch now clamps incoming limit to safeLimit and uses fetchLimit = safeLimit + 1, but the ledger-only fallback still computes ledgerHasMore and slice(...) with the raw limit. In ledger-only mode, requests with out-of-range limits (for example limit=500 or limit=0) can return the wrong page size and incorrect hasMore/cursor behavior, because the fallback compares against a different limit than it fetched with.
Useful? React with 👍 / 👎.
#1049) * fix(pricing): preserve extra pricing fields and expose semantic details * fix(pricing): address PR review follow-ups * fix(pricing): address second-round review findings
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7210c0f7ad
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| - name: FETCH_CONNECT_TIMEOUT | ||
| value: "30" | ||
| - name: FETCH_HEADERS_TIMEOUT | ||
| value: "600" | ||
| - name: FETCH_BODY_TIMEOUT |
There was a problem hiding this comment.
Use millisecond values for FETCH_ timeout env vars*
FETCH_CONNECT_TIMEOUT, FETCH_HEADERS_TIMEOUT, and FETCH_BODY_TIMEOUT are configured here as 30/600/600, but these settings are consumed as milliseconds (see src/lib/config/env.schema.ts defaults 30000/600000/600000 and direct pass-through in src/lib/proxy-agent.ts). In this manifest that effectively sets a 30ms connect timeout and 600ms header/body timeouts, which will cause frequent upstream failures under normal network latency and long-running model responses.
Useful? React with 👍 / 👎.
| readinessProbe: | ||
| httpGet: | ||
| path: /api/actions/health | ||
| port: 3000 |
There was a problem hiding this comment.
Point Kubernetes probes at /api/health endpoints
The probes are wired to /api/actions/health, but that handler (src/app/api/actions/[...route]/route.ts, app.get("/health")) always returns HTTP 200 even when readiness checks report unhealthy; it only changes JSON payload fields. Kubernetes probe success is status-code based, so readiness/liveness can incorrectly pass during backend outages and keep bad pods in service. This should target the dedicated endpoints added in this change (/api/health/ready for readiness/startup and /api/health/live for liveness).
Useful? React with 👍 / 👎.
Previously the dev compose bound 5432/6379 on 0.0.0.0, which combined with Docker's FORWARD/DOCKER chain made the DB and cache reachable from the host's public IP. Prefix the port mappings with 127.0.0.1 so only local processes can connect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🧪 测试结果
总体结果: ✅ 所有测试通过 |
Summary
Release v0.7.0 — a major feature release merging 30 PRs from dev into main. Introduces IP logging & audit trail, provider group cost management, K8s health probes, user cache hit rate leaderboard, and numerous proxy/UI improvements.
Highlights
IP Logging & Audit System (#1027, #1029, #1031, #1032, #1034, #1037)
Provider Group Cost Multiplier (#1025, #1030, #1033)
provider_groupstable with cost multiplier per groupK8s Health Check Probes (#1019)
/api/health/live) and readiness (/api/health/ready) endpoints/api/actions/healthpreserved withstatus: "ok"formatUser Cache Hit Rate Leaderboard (#1015)
Other Notable Changes
Database Migrations
Three new migrations:
0088— Addsclient_ipcolumn tomessage_requestandusage_ledger0089— Createsaudit_logandprovider_groupstables; addsgroup_cost_multiplier,cost_breakdown,client_ipcolumns; creates indexes and updatesfn_upsert_usage_ledgertrigger0090— Minor schema adjustmentNote: Auto-migration will apply these on startup if
AUTO_MIGRATE=true.Included PRs
Breaking Changes
None detected — all new columns are nullable or have defaults; existing API contracts are preserved.
Checklist
Description enhanced by Claude AI
Greptile Summary
This release PR merges ~30 features and fixes from dev into main, introducing IP logging & audit trails, provider group cost multipliers, K8s health probes, a user cache-hit-rate leaderboard, and the security fix to bind dev postgres/redis to
127.0.0.1. The migration set (0088–0090) is non-breaking — all new columns are nullable or have defaults.VERSIONfile, the description, and the version-bump commit all refer to v0.7.0 — please correct the PR title to avoid release-tracking confusion.Confidence Score: 4/5
Safe to merge with one known unresolved P1: the leaderboard cache lock is not released when queryDatabase throws, causing a 10s stall for concurrent requests under DB pressure (flagged in previous review rounds but not yet addressed).
The bulk of the PR is well-structured — migrations are non-breaking, audit redaction is correct, IP extraction defaults are deliberately conservative, and the K8s health probes are properly scoped. The previously flagged issues around audit-log filter duplication and null-safety have been resolved. The outstanding P1 (leaderboard lock not released in try/finally) was raised four times in prior review iterations and remains open, keeping the score at 4.
src/lib/redis/leaderboard-cache.ts — lock release still not wrapped in try/finally (lines 307–329)
Important Files Changed
Sequence Diagram
sequenceDiagram participant Client participant NextJS as Next.js Middleware participant Proxy as Hono Proxy (/v1) participant AuthGuard participant MessageService participant DB as PostgreSQL participant Redis Client->>NextJS: POST /v1/messages (Bearer token) NextJS->>Proxy: forward (no locale/auth check) Proxy->>AuthGuard: extract clientIp via ip_extraction_config AuthGuard->>DB: validate API key + user AuthGuard-->>Proxy: AuthState (user, key, clientIp set on session) Proxy->>MessageService: ensureContext() MessageService->>DB: createMessageRequest(clientIp, groupCostMultiplier, ...) Proxy->>Proxy: forward to upstream provider Proxy->>DB: update messageRequest (statusCode, cost, costBreakdown) Note over DB: fn_upsert_usage_ledger trigger fires DB->>DB: upsert usage_ledger (propagates clientIp, groupCostMultiplier) note over Proxy,Redis: Audit trail (fire-and-forget) Proxy-->>Redis: (optional) write live chain Proxy->>DB: createAuditLogAsync (operatorIp, before/after redacted)Comments Outside Diff (4)
src/lib/redis/leaderboard-cache.ts, line 307-329 (link)queryDatabasethrowsIf
queryDatabasethrows (DB error, schema mismatch, etc.), control jumps directly to the outercatchblock on line 351 —redis.del(lockKey)on line 317 is never executed. The lock then holds for its full 10-second TTL, during which every other concurrent request iterates the 50×100 ms retry loop (5 seconds) before falling back to a direct DB query. Under DB pressure this compounds the problem significantly.Wrap the lock-guarded block in a
try/finally:Prompt To Fix With AI
src/lib/redis/leaderboard-cache.ts, line 307-329 (link)queryDatabasethrowsIf
queryDatabase(line 311) throws — e.g. on a DB error or schema mismatch — execution jumps directly to the outercatchblock (line 351), skippingredis.del(lockKey)on line 317. The lock then holds for its full 10-second TTL, during which every other concurrent request iterates the 50×100 ms retry loop (up to 5 s) before falling back to a direct DB query. Under DB pressure this compounds the problem significantly.Wrap the lock-guarded section in a
try/finally:Prompt To Fix With AI
src/lib/redis/leaderboard-cache.ts, line 307-329 (link)queryDatabasethrowsIf
queryDatabase(line 311) throws — e.g. a DB error or schema mismatch — execution jumps to the outercatchblock at line 351, bypassingredis.del(lockKey)on line 317. The lock then holds for its full 10-second TTL, during which every other concurrent request spins the 50×100 ms retry loop (5 s) before falling back to a direct DB query. Under sustained DB pressure this compounds significantly.Wrap the lock-guarded section in a
try/finally:Prompt To Fix With AI
src/lib/redis/leaderboard-cache.ts, line 307-317 (link)queryDatabasethrowsIf
queryDatabase(line 311) throws — e.g. a DB error or unexpected schema — control jumps directly to thecatchblock at line 351, bypassingredis.del(lockKey)at line 317. The lock then holds for its full 10-second TTL, during which every other concurrent request iterates the 50×100 ms retry loop (5 s) before falling back to a direct DB query. Under sustained DB pressure this compounds significantly.Wrap the lock-guarded block in a
try/finally:Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (11): Last reviewed commit: "fix(dev): bind postgres/redis to 127.0.0..." | Re-trigger Greptile