Skip to content

perf: reduce gateway amplification and correct TTFT/TTFB metrics - #1371

Closed
ding113 wants to merge 1 commit into
devfrom
performance-optimization-roadmap
Closed

perf: reduce gateway amplification and correct TTFT/TTFB metrics#1371
ding113 wants to merge 1 commit into
devfrom
performance-optimization-roadmap

Conversation

@ding113

@ding113 ding113 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • separate request TTFB and TTFT with versioned timing semantics
  • move session detail snapshots to bounded asynchronous filesystem storage by default
  • prevent duplicate multi-replica background aggregation and endpoint probing
  • fix leaderboard success-rate and cache-coefficient calculations
  • tune Kubernetes resource, HPA, and connection-pool defaults without adding pods or external components

Timing Semantics

  • ttfbMs: final winner HTTP response-header latency
  • ttftMs: final winner first protocol-valid content latency
  • timingSemanticsVersion = 2
  • legacy rows are excluded from new TTFB, TTFT, and TPS aggregates
  • TPS uses outputTokens / ((durationMs - ttftMs) / 1000)

Session Snapshot Storage

  • filesystem storage is the default and remains configurable from system settings
  • writes are gzip-compressed, queued asynchronously, and atomically published
  • per-object, pending-queue, and total-storage limits bound disk and memory growth
  • TTL cleanup removes expired snapshots; shutdown waits for queued writes to flush
  • Redis storage remains available as an explicit setting

Multi-Replica Work

  • cache-effectiveness aggregation uses PostgreSQL advisory locking, a persistent cursor, and idempotent windows
  • endpoint probes and other scheduled background tasks use leader locking to avoid replica-wide duplicate work
  • repeated endpoint failures apply bounded backoff

Migration

  • adds migration 0116_lying_marvel_apes
  • preserves the existing dev migration 0115_breezy_polaris
  • migrates recoverable first_byte_ms and legacy ttfb_ms values into v2 timing columns before dropping first_byte_ms
  • guards legacy-column conversion so the migration remains safe to rerun after the old columns have been dropped
  • rebuilds the usage-ledger trigger with v2 timing fields

Verification

  • bun run lint
  • bun run typecheck
  • bun run validate:migrations (118 migrations)
  • bun run openapi:check
  • bun run openapi:lint
  • bun run i18n:audit-messages-no-emoji:fail
  • 359 focused tests, including 10 real loopback proxy hedge integration tests
  • production bun run build
  • two-axis release review: no spec blockers; the migration rerun finding was fixed and revalidated before publication

Deferred

  • request-local absolute first-content deadline
  • per-provider AvailableModels cache, singleflight, and shared concurrency limiting

Greptile Summary

This PR revises gateway timing semantics and reduces replicated background and storage amplification.

  • Separates winner response-header TTFB from protocol-valid-content TTFT and versions persisted timing data.
  • Moves session-detail snapshots to a bounded asynchronous filesystem backend by default.
  • Adds leader/advisory locking, persistent aggregation cursors, and endpoint-probe backoff.
  • Updates leaderboard and public-status calculations plus Kubernetes resource and connection-pool defaults.

Confidence Score: 4/5

The snapshot shutdown path should be fixed before merging because it can acknowledge store termination while accepted writes remain unfinished.

The timing, migration, and distributed aggregation changes are internally coordinated, but filesystem snapshot shutdown has a fixed five-second escape that can lose recently queued session details during process exit or backend replacement.

Files Needing Attention: src/lib/session-snapshot/filesystem-store.ts

Important Files Changed

Filename Overview
src/lib/session-snapshot/filesystem-store.ts Introduces bounded compressed atomic snapshot storage, but its fixed shutdown timeout can leave accepted writes unfinished.
src/lib/session-snapshot/store.ts Adds configurable filesystem, Redis, and disabled snapshot backends with runtime replacement.
src/app/v1/_lib/proxy/forwarder.ts Records final-winner header and content timing across serial and hedged forwarding paths.
src/app/v1/_lib/proxy/response-handler.ts Detects protocol-valid first content and persists versioned timing during response finalization.
drizzle/0116_lying_marvel_apes.sql Migrates recoverable timing data, adds coordination fields, and rebuilds ledger propagation for v2 timing.
src/lib/cache-effectiveness/service.ts Makes aggregation transactionally leader-locked, cursor-driven, and idempotent.
src/lib/provider-endpoints/probe-scheduler.ts Adds failure-based probe backoff and tighter leader-coordinated scheduling.
src/lib/lifecycle/shutdown.ts Expands scheduler quiescence and storage cleanup ordering, while relying on the snapshot store's incomplete five-second stop barrier.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Request[Proxy request] --> Winner[Final provider winner]
  Winner --> TTFB[Record response-header TTFB]
  Winner --> Stream[Process response content]
  Stream --> TTFT[Record protocol-valid-content TTFT]
  TTFB --> Ledger[(Message and usage ledger)]
  TTFT --> Ledger
  Request --> Queue[Bounded snapshot queue]
  Queue --> Gzip[Gzip and atomic filesystem write]
  Gzip --> Snapshot[(Session snapshot files)]
  Ledger --> Aggregation[Leader-locked aggregation]
  Aggregation --> Dashboard[Leaderboard and public status]
Loading
Prompt To Fix All With AI
### Issue 1
src/lib/session-snapshot/filesystem-store.ts:190
**Snapshot shutdown abandons queued writes**

When shutdown or backend reconfiguration occurs while the serial filesystem queue needs more than five seconds to drain, `stop()` returns with accepted writes still pending, causing recent session-detail snapshots to be missing after process exit or store replacement.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "perf: reduce gateway amplification and c..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used (5)

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

性能语义与数据链路

Layer / File(s) Summary
TTFB/TTFT 采集与持久化
src/app/v1/_lib/proxy/..., src/repository/..., src/drizzle/schema.ts
将旧计时字段迁移为 ttfbMsttftMstimingSemanticsVersion,并更新代理、数据库、账本及 Langfuse 链路。
Session 快照存储
src/lib/session-snapshot/*, src/lib/session-manager.ts, src/instrumentation.ts
新增 disabled、Redis 和文件系统快照后端,支持补丁写入、压缩、清理、重配置及生命周期管理。
公共状态与排行榜
src/lib/public-status/*, src/repository/leaderboard.ts, src/app/[locale]/status/*
新增 TTFT 聚合、rollup 字段、排行榜指标、模型级缓存系数和成功率口径展示。
配置与部署
src/actions/system-config.ts, deploy/k8s/*, docs/k8s-deployment.md
新增 Session 快照配置,调整系统设置缓存、Kubernetes 资源、探针、HPA 和 hostPath 部署说明。
验证与测试
tests/unit/*, tests/integration/*
更新计时、快照、聚合、锁、探测调度、排行榜及部署资源相关测试。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: brisbanehuang

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了本次 PR 的核心:修正 TTFT/TTFB 计时并减少重复后台工作。
Description check ✅ Passed 描述与变更内容高度一致,覆盖了时序语义、会话快照、并发控制、迁移和校验结果。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch performance-optimization-roadmap

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size/XL Extra Large PR (> 1000 lines) label Jul 30, 2026
}
if (this.cleanupTimer) clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
await Promise.race([this.waitForDrain(), delay(5_000)]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Snapshot shutdown abandons queued writes

When shutdown or backend reconfiguration occurs while the serial filesystem queue needs more than five seconds to drain, stop() returns with accepted writes still pending, causing recent session-detail snapshots to be missing after process exit or store replacement.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/session-snapshot/filesystem-store.ts
Line: 190

Comment:
**Snapshot shutdown abandons queued writes**

When shutdown or backend reconfiguration occurs while the serial filesystem queue needs more than five seconds to drain, `stop()` returns with accepted writes still pending, causing recent session-detail snapshots to be missing after process exit or store replacement.

**Knowledge Base Used:**
- [Redis Caching and Session Tracking](https://app.greptile.com/ygxz/-/custom-context/knowledge-base/ding113/claude-code-hub/-/docs/redis-caching-and-sessions.md)
- [Server Lifecycle and Background Jobs](https://app.greptile.com/ygxz/-/custom-context/knowledge-base/ding113/claude-code-hub/-/docs/server-lifecycle-and-jobs.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d045f8089

ℹ️ 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".

Comment on lines +23 to +27
UPDATE "message_request"
SET
"ttft_ms" = "ttfb_ms",
"ttfb_ms" = "first_byte_ms",
"timing_semantics_version" = 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve legacy TTFT before firing the ledger trigger

When upgrading a database that already has migration 0114's trg_upsert_usage_ledger, this update fires that old trigger before it is replaced later in this file. The trigger copies the newly reassigned message_request.ttfb_ms into usage_ledger.ttfb_ms, so the subsequent ledger backfill reads an already-overwritten value and stores the old first-byte time in both ttfb_ms and ttft_ms, permanently losing historical TFFT for retained rows. Disable or replace the trigger before this update, or backfill the ledger before updating message_request.

Useful? React with 👍 / 👎.

Comment on lines +378 to 382
if (outcome === "success" && ttfbMs !== null) {
increments.push(
{ groupId, modelKey, metric: "ttfb_sum", value: tfftMs },
{ groupId, modelKey, metric: "ttfb_sum", value: ttfbMs },
{ groupId, modelKey, metric: "ttfb_count", value: 1 }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Version the rollup keys before changing timing semantics

On upgrades with existing public-status:v2 hashes, this adds actual TTFB to ttfb_sum, even though those same fields already contain legacy TFFT samples; tps_sum similarly starts combining TTFT-based rates with the previous first-byte-based rates. Because these hashes have a 32-day TTL and the Redis prefix was not changed or cleared, status rebuilds will publish blended, invalid timing metrics for up to 32 days. Write the new semantics under a new rollup namespace or explicitly rebuild the old buckets.

Useful? React with 👍 / 👎.

Comment on lines +126 to +129
await Promise.allSettled([
schedulerState.__CCH_CACHE_EFFECTIVENESS_CURRENT_PROMISE__,
schedulerState.__CCH_REPLAY_CLEANUP_CURRENT_PROMISE__,
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bound scheduler joins before flushing buffered messages

If SIGTERM arrives while either DB-backed scheduler tick is slow or stuck, this unbounded Promise.allSettled prevents cleanup from reaching async-task shutdown and the message-writer flush. awaitQuiescenceBestEffort only logs after its warning interval and continues waiting, while server.js hard-exits after 28 seconds, so a routine rollout can kill the process with terminal request updates still buffered. Bound or detach these non-critical joins, or move them after the critical writer flush.

Useful? React with 👍 / 👎.

`${t("logs.details.performance.tfft")} ${formatDuration(log.tfftMs)}`,
displayTtfbMs != null &&
displayTtfbMs > 0 &&
`TTFB ${formatDuration(displayTtfbMs)}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Localize the inline TTFB label

When current timing data is displayed, this literal emits user-facing text outside next-intl, even though the translated logs.details.performance.ttfb key is already used in the tooltip below. Build this label from t(...) rather than hardcoding it.

AGENTS.md reference: AGENTS.md:L14-L14

Useful? React with 👍 / 👎.

Comment on lines +66 to +69
await tx.execute(sql`
INSERT INTO background_task_cursor (task_key, cursor_at, updated_at)
VALUES (${TASK_KEY}, ${initialCursor}, NOW())
ON CONFLICT (task_key) DO NOTHING

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Seed the new cursor from the last aggregated window

On upgrades where provider_cache_effectiveness already contains windows, the new cursor table is empty, so this initializes it to one hour ago instead of the previous MAX(window_end). The immediate scheduler tick then writes a broad window overlapping the existing narrower windows; the unique index does not conflict because their bounds differ, and coefficient queries subsequently sum both sets, double-counting up to an hour of samples indefinitely. Seed the cursor from the latest existing window when creating its first row.

Useful? React with 👍 / 👎.

Comment on lines +46 to +48
if (bucket.sampleCount > 0 && bucket.ttftMs !== null) {
weightedSum += bucket.ttftMs * bucket.sampleCount;
sampleTotal += bucket.sampleCount;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Weight TTFT averages by timing-bearing samples

When a timeline bucket mixes streaming requests with non-streaming, failed, or legacy requests, bucket.ttftMs is calculated only from requests that have TTFT, while bucket.sampleCount counts all outcomes. Weighting the bucket average by that total lets a bucket with only a few TTFT measurements but many untimed requests dominate the displayed range average. Carry the TTFT sample count through the payload and use that as the weight.

Useful? React with 👍 / 👎.

Comment on lines +205 to +206
ALTER TABLE "message_request" DROP COLUMN IF EXISTS "first_byte_ms";--> statement-breakpoint
ALTER TABLE "usage_ledger" DROP COLUMN IF EXISTS "first_byte_ms";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain legacy timing columns through the rolling upgrade

During the configured rolling deployment, the new pod runs this migration while old replicas are still serving. Those replicas still select and write first_byte_ms on both request finalization and usage-log reads, so dropping the columns here causes their SQL to fail until every old pod is terminated, losing terminal log updates and breaking dashboard queries during the rollout. Keep the legacy columns for at least one compatibility release and remove them only after all running code has stopped referencing them.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/repository/message.ts (1)

971-1010: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

findMessageRequestBySessionId 主查询路径遗漏 ttfbMs/ttftMs/timingSemanticsVersion。

对比同文件内的 findMessageRequestById(840-882 行,已正确加入新字段)与本函数的 ledger-only 兜底路径(1041-1043 行,已正确加入新字段),本函数针对 messageRequest 表的主查询路径(即最常命中的路径)显式声明了 select 字段列表,却唯独没有选择 ttfbMs/ttftMs/timingSemanticsVersion。由于该处使用了显式列声明的 .select({...})(而非 select() 全列),这三个字段在主路径命中时会始终缺失,只有走到 ledger-only 兜底分支才能拿到正确值 —— 与本 PR 统一 TTFB/TTFT 数据契约的目标直接冲突,可能导致依赖 findMessageRequestBySessionId 的下游逻辑(如会话级性能展示)拿到不完整的计时数据。

🛠️ 建议修复
       model: messageRequest.model,
       originalModel: messageRequest.originalModel,
       durationMs: messageRequest.durationMs,
+      ttfbMs: messageRequest.ttfbMs,
+      ttftMs: messageRequest.ttftMs,
+      timingSemanticsVersion: messageRequest.timingSemanticsVersion,
       costUsd: messageRequest.costUsd,
       costMultiplier: messageRequest.costMultiplier,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/repository/message.ts` around lines 971 - 1010, Update the explicit
select list in findMessageRequestBySessionId to include messageRequest.ttfbMs,
messageRequest.ttftMs, and messageRequest.timingSemanticsVersion, matching
findMessageRequestById and the ledger-only fallback while preserving the
existing query behavior.
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx (1)

344-370: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

avgTtfbMs 为空时会完全隐藏可能有效的 avgTtftMs 数据。

当前逻辑:val == null || val <= 0 时直接返回 "-",不再渲染 Tooltip,因此即便 row.avgTtftMs 有有效值,用户也完全看不到。avgTtfbMs/avgTtftMs 是两个独立的 SQL avg(CASE WHEN timingSemanticsVersion = 2 THEN ... END) 表达式,不能保证同一分组内两者总是同时非空或同时为空。建议分别判断两个值是否可用,而不是让 TTFT(本次 PR 的核心新指标)完全依赖 TTFB 是否有值。

🐛 建议修复
       cell: (row) => {
         const val = row.avgTtfbMs;
-        if (val == null || val <= 0) return "-";
+        const ttft = row.avgTtftMs;
+        if ((val == null || val <= 0) && (ttft == null || ttft <= 0)) return "-";
         return (
           <Tooltip>
             <TooltipTrigger asChild>
-              <span className="cursor-help">{Math.round(val).toLocaleString()} ms</span>
+              <span className="cursor-help">
+                {val != null && val > 0
+                  ? `${Math.round(val).toLocaleString()} ms`
+                  : t("columns.timingUnavailable")}
+              </span>
             </TooltipTrigger>
             <TooltipContent className="space-y-1 text-xs">
               <div>
-                {t("columns.avgTtfbMs")}: {Math.round(val).toLocaleString()} ms
+                {t("columns.avgTtfbMs")}:{" "}
+                {val != null && val > 0
+                  ? `${Math.round(val).toLocaleString()} ms`
+                  : t("columns.timingUnavailable")}
               </div>
               <div>
                 {t("columns.avgTtftMs")}:{" "}
-                {row.avgTtftMs == null
+                {ttft == null || ttft <= 0
                   ? t("columns.timingUnavailable")
-                  : `${Math.round(row.avgTtftMs).toLocaleString()} ms`}
+                  : `${Math.round(ttft).toLocaleString()} ms`}
               </div>
             </TooltipContent>
           </Tooltip>
         );
       },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
around lines 344 - 370, Update the avgTtfbMs column cell renderer to evaluate
avgTtfbMs and avgTtftMs independently. Preserve the existing TTFB display when
valid, but render a Tooltip whenever either timing value is available so valid
row.avgTtftMs remains visible even when avgTtfbMs is null or non-positive; show
the unavailable text only for the individual missing value.
🧹 Nitpick comments (5)
src/instrumentation.ts (1)

275-304: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

建议提取共享的“受控 interval tick”辅助函数以消除重复逻辑。

startCacheEffectivenessSchedulerstartReplayCleanupScheduler 中的 tick 实现(stop-request 检查、current promise 追踪、finally 清空)结构几乎一致,仅内部执行体不同。建议提取一个通用辅助函数(如 createGuardedIntervalTick(fn, { stopFlagKey, promiseKey })),减少未来维护时两处逻辑分叉的风险。

♻️ 提炼共享辅助函数的思路
+function createGuardedTick(options: {
+  isStopRequested: () => boolean;
+  getCurrentPromise: () => Promise<void> | undefined;
+  setCurrentPromise: (p: Promise<void> | undefined) => void;
+  run: () => Promise<void>;
+}): () => void {
+  return () => {
+    if (options.isStopRequested() || options.getCurrentPromise()) return;
+    const current = options
+      .run()
+      .catch(() => undefined)
+      .finally(() => {
+        if (options.getCurrentPromise() === current) options.setCurrentPromise(undefined);
+      });
+    options.setCurrentPromise(current);
+  };
+}

Also applies to: 330-363

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/instrumentation.ts` around lines 275 - 304, 提取一个共享的受控 interval tick
辅助函数,统一封装 stop-request 检查、current promise 记录以及 finally 中清空 promise 的逻辑;然后让
startCacheEffectivenessScheduler 和 startReplayCleanupScheduler
通过该辅助函数仅提供各自的异步执行体、stopFlagKey 和 promiseKey。保持现有停止行为、并发保护、错误处理及调度结果不变,删除两处重复的
tick 实现。
src/lib/utils/performance-formatter.test.ts (1)

5-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

补充非法 TTFT 输入的回归测试。

当前测试覆盖了缺失和超出总耗时的 TTFT,但没有覆盖负数或 NaN。建议在修复格式化器后增加对应断言,防止无效速率重新进入仪表盘。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/performance-formatter.test.ts` around lines 5 - 21, 在
performance formatter 测试中补充负数和 NaN 的非法 TTFT 回归断言,验证 calculateOutputRate 对这些输入返回
null,确保无效速率不会进入仪表盘。
src/app/v1/_lib/proxy/stream-gate/stream-content-gate.ts (1)

336-364: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

为 stream-gate 的增量 SSE parser 设置解码缓冲上限。

SseFrameParser.assertBufferLimit()maxBufferedCharacters === undefined 时直接返回,内容计时观察器和 shadow 观察器都是 new SseFrameParser(),遇到未完结的超长行/多行 data 且未产出 content 帧时,lineTail/dataCharacters 可能持续累积;同时现有 catch 也不会拦截 SseFrameBufferLimitError。建议为这两个 observer 设置固定上限,避免与后续解析逻辑的缓冲保护脱节。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/v1/_lib/proxy/stream-gate/stream-content-gate.ts` around lines 336 -
364, 为 createContentTimingObserver 中的增量 SSE 解析器设置固定的 maxBufferedCharacters
上限,避免未完成的超长行或多行 data 持续累积;同时定位并更新 shadow observer 中的 SseFrameParser
实例,使用相同上限配置,保持现有 SseFrameBufferLimitError 的终止处理行为。
src/repository/provider-cache-effectiveness.ts (1)

136-175: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

建议提取 getProviderCacheCoefficientsgetProviderModelCacheCoefficients 的公共聚合逻辑。

两个函数的 select 投影、computeCoefficientBp 调用与结果组装完全同构,仅 groupBy 维度不同。建议抽取一个内部 helper(按传入的 groupBy 列构造查询并统一组装结果),避免未来两处口径漂移。

♻️ 重构思路示意
-export async function getProviderCacheCoefficients({...}) {
-  const rows = await db.select({...}).from(...).where(...).groupBy(providerCacheEffectiveness.providerId);
-  const coefficients = new Map<number, ProviderCacheCoefficient>();
-  for (const row of rows) { ... }
-  return coefficients;
-}
-
-export async function getProviderModelCacheCoefficients({...}) {
-  const rows = await db.select({...}).from(...).where(...).groupBy(providerId, normalizedModel);
-  const coefficients: ProviderModelCacheCoefficientMap = new Map();
-  for (const row of rows) { ... }
-  return coefficients;
-}
+async function aggregateCacheCoefficientRows({ start, end, byModel }: { start: Date; end: Date; byModel: boolean }) {
+  // 复用相同的 select/groupBy/computeCoefficientBp 逻辑,byModel 控制是否额外按 model 分组
+}

Also applies to: 183-228

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/repository/provider-cache-effectiveness.ts` around lines 136 - 175,
提取一个内部 helper,复用 getProviderCacheCoefficients 与
getProviderModelCacheCoefficients 中相同的聚合查询、computeCoefficientBp
调用及结果组装逻辑;让两个公开函数仅传入各自的 groupBy 列并保留现有返回类型与筛选条件,确保 provider 与 provider-model
两种维度行为不变。
src/app/[locale]/status/_lib/timeline-windows.ts (1)

27-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

computeAvgTtftcomputeAvgTtfb 逻辑重复,建议提取公共辅助函数。

两个函数除字段名(ttfbMs/ttftMs)外完全相同。建议抽取一个按字段名参数化的通用加权平均函数,减少后续两个指标实现走偏的风险。

♻️ 建议重构
-export function computeAvgTtfb(timeline: PublicStatusTimelineBucket[]): number | null {
-  let weightedSum = 0;
-  let sampleTotal = 0;
-  for (const bucket of timeline) {
-    if (bucket.sampleCount > 0 && bucket.ttfbMs !== null) {
-      weightedSum += bucket.ttfbMs * bucket.sampleCount;
-      sampleTotal += bucket.sampleCount;
-    }
-  }
-  if (sampleTotal === 0) {
-    return null;
-  }
-  return Math.round(weightedSum / sampleTotal);
-}
-
-export function computeAvgTtft(timeline: PublicStatusTimelineBucket[]): number | null {
-  let weightedSum = 0;
-  let sampleTotal = 0;
-  for (const bucket of timeline) {
-    if (bucket.sampleCount > 0 && bucket.ttftMs !== null) {
-      weightedSum += bucket.ttftMs * bucket.sampleCount;
-      sampleTotal += bucket.sampleCount;
-    }
-  }
-  if (sampleTotal === 0) {
-    return null;
-  }
-  return Math.round(weightedSum / sampleTotal);
-}
+function computeWeightedAvg(
+  timeline: PublicStatusTimelineBucket[],
+  field: "ttfbMs" | "ttftMs"
+): number | null {
+  let weightedSum = 0;
+  let sampleTotal = 0;
+  for (const bucket of timeline) {
+    const value = bucket[field];
+    if (bucket.sampleCount > 0 && value !== null) {
+      weightedSum += value * bucket.sampleCount;
+      sampleTotal += bucket.sampleCount;
+    }
+  }
+  if (sampleTotal === 0) {
+    return null;
+  }
+  return Math.round(weightedSum / sampleTotal);
+}
+
+export const computeAvgTtfb = (timeline: PublicStatusTimelineBucket[]) =>
+  computeWeightedAvg(timeline, "ttfbMs");
+export const computeAvgTtft = (timeline: PublicStatusTimelineBucket[]) =>
+  computeWeightedAvg(timeline, "ttftMs");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`[locale]/status/_lib/timeline-windows.ts around lines 27 - 55,
提取一个按指标字段参数化的通用加权平均辅助函数,集中复用 computeAvgTtfb 和 computeAvgTtft 中的遍历、样本加权、空样本返回
null 及四舍五入逻辑;让两个公开函数仅传入对应的 ttfbMs 或 ttftMs 字段,并保持现有结果不变。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/k8s/app/deployment.yaml`:
- Around line 138-145: Replace the session-snapshots hostPath volume in the
deployment’s volumeMounts/volumes configuration with a shared RWX PVC, and
configure the PVC-backed storage for SESSION_SNAPSHOT_ROOT so replicas can
access the same snapshots across nodes. Alternatively, when multi-node
deployment uses the Redis snapshot backend, remove this local hostPath
dependency and apply the corresponding Redis configuration.

In `@deploy/k8s/README.md`:
- Around line 57-61: Remove the blank line separating the adjacent blockquote
lines in the README so the entire note remains one continuous blockquote and
satisfies markdownlint MD028.

In `@drizzle/0116_lying_marvel_apes.sql`:
- Around line 13-49: 调整迁移 DO 块中 message_request 的回填顺序,避免旧触发器
trg_upsert_usage_ledger 在更新 ttfb_ms 时污染 usage_ledger.ttfb_ms:在 message_request
回填前禁用该触发器(或先删除),完成两个表的回填后恢复或创建迁移所需的新触发器,并确保触发器状态最终正确。

In `@messages/zh-CN/dashboard.json`:
- Line 366: 统一更新 firstByteToFirstToken 文案:在 messages/zh-CN/dashboard.json
第366行改为“首字节到首个有效内容”,在 messages/ru/dashboard.json 第366行改为“От первого байта до
первого валидного содержимого”,在 messages/zh-TW/dashboard.json
第366行改为“首字節到首個有效內容”。

In `@src/actions/system-config.ts`:
- Around line 219-225: 更新 system settings 保存流程中 reconfigureSessionSnapshotStore
的失败处理:不要仅记录 warn 后继续返回普通的 { ok: true, data: updated },应沿用
publicStatusProjectionWarningCode
的处理路径,将可供前端识别的失败/告警码附加到成功响应中。保留数据库与缓存已更新的行为,并确保成功重配置时响应保持现有结果。

In
`@src/app/`[locale]/dashboard/logs/_components/error-details-dialog/components/LatencyBreakdownBar.tsx:
- Around line 58-66: Replace the hardcoded “TTFB” label in the
LatencyBreakdownBar TTFB segment with the existing t("ttfb") translation,
matching the surrounding translated labels and preserving the current visibility
condition.

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-table.tsx:
- Around line 593-596: 将 usage logs 表格中 secondLine 的内联 TTFB 文案替换为 next-intl
翻译键,传入格式化后的时长插值并保持现有的非空及大于零判断;同时在五种 locale 的翻译资源中补齐该键对应的译文,确保各语言界面不再固定显示英文。

In `@src/app/`[locale]/dashboard/logs/_components/virtualized-logs-table.tsx:
- Around line 1170-1173: Update the ttfbLine construction in the virtualized
logs table to replace the hardcoded “TTFB” label with the existing
t("logs.details.performance.ttfb") translation, while preserving the current
displayTtfbMs validation and formatDuration output.

In `@src/app/`[locale]/status/_components/public-status-view.tsx:
- Around line 518-540: Update the TooltipTrigger child in the TTFB/TTFT metrics
block to use a natively focusable element, or add the required focusability and
keyboard interaction to the rendered element, while preserving the existing
tooltip content and styling.

In `@src/app/api/admin/system-config/route.ts`:
- Around line 131-137: 更新系统配置提交流程,覆盖动态导入与 reconfigureSessionSnapshotStore 的完整
try/catch;重配置失败时不要仅记录警告后继续返回
updated,而应回滚数据库/缓存配置并返回错误,确保运行时存储未成功启动时不会对外显示新配置已生效。同步检查
reconfigureSessionSnapshotStore 中 activeSelection 和 activeStore
的更新时机,采用提交成功后再更新或失败恢复旧状态的语义。

In `@src/app/v1/_lib/proxy/response-handler.ts`:
- Line 3682: 调整 Gemini 透传分支中的 observePassthroughChunk 流程,使其接入现有的
contentTimingObserver,并按首个内容帧而非首个原始字节记录 TTFT;仅在没有 observer 时保留
session.recordTtft() 的回退逻辑,确保 timingSemanticsVersion: 2 的语义与非透传路径一致。

In `@src/lib/ledger-backfill/service.ts`:
- Around line 93-95: Update the ledger backfill query’s selection conditions so
rows with missing timing fields are eligible for legacy timing backfill, even
when success_rate_outcome already exists. In the ON CONFLICT DO UPDATE branch,
persist or merge the incoming ttfb_ms, ttft_ms, and timing_semantics_version
values instead of updating only success_rate_outcome; apply the same behavior to
all corresponding conflict-update branches.

In `@src/lib/public-status/openapi.ts`:
- Around line 51-57: 同步更新 200 响应中的 filteredReady 示例模型,在其对象中补充必填字段
latestTtftMs,并使用与 publicStatusModelSchema.properties 中定义一致的类型和值格式,确保示例满足
publicStatusModelSchema.required 的约束。

In `@src/lib/session-snapshot/filesystem-store.ts`:
- Around line 84-129: 更新 enqueuePatch 中启动 start().then(() => drain()) 的异步链,为
start 失败增加 catch 处理。失败时移除对应队列任务、回滚 pendingBytes,并调用该任务的
completion;同时确保错误被消费,避免未处理拒绝及 keyTails 永久等待。

In `@src/lib/session-snapshot/store.ts`:
- Around line 80-111: 调整 getSessionSnapshotStore 与
reconfigureSessionSnapshotStore 的切换流程:不要在 target.start() 成功前更新 activeSelection 或
activeStore,也不要在后台重配置期间立即返回 resolveStore(selected);应继续返回当前 activeStore。仅当
target.start() 成功且目标仍有效时,再提交 activeSelection/activeStore 并停止其他 store,失败时保留当前可用
store,使后续配置变化仍能重试自愈。

In `@src/lib/utils/performance-formatter.ts`:
- Around line 56-67: Update both performance-formatting functions, including
shouldHideOutputRate and the rate calculation block, to reject non-finite
durationMs, outputTokens, and ttftMs values; require durationMs and outputTokens
to be positive and ttftMs to be non-negative. Preserve the existing null return
behavior for all invalid timing inputs and prevent NaN or incorrect rates from
being produced.

In `@tests/unit/proxy/session-ttfb-tfft.test.ts`:
- Around line 41-50: Update the test case around recordTtfb and recordTtft to
assert that both returned values and the corresponding session fields equal the
explicitly supplied elapsedMs values 200 and 800, rather than only comparing
each field with its returned value. Preserve the existing distinction between
TTFB and TTFT.

In `@tests/unit/proxy/stream-gate-content-gate.test.ts`:
- Around line 260-270: Update the newly added string literals in the
parameterized test around the openai-chat and gemini cases to use Biome’s
configured double-quote style, including the neutral and content values, without
changing their contents or test behavior.

In `@tests/unit/repository/message-session-readback.test.ts`:
- Around line 104-106: 更新 findMessageRequestBySessionId 的主表 messageRequest 查询
projection,补充选择 ttfbMs、ttftMs 和 timingSemanticsVersion,保持与 ledger fallback
的字段一致;同时增加主表 session 回读路径的断言,验证这三个新版计时字段能够正确返回。

---

Outside diff comments:
In `@src/app/`[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx:
- Around line 344-370: Update the avgTtfbMs column cell renderer to evaluate
avgTtfbMs and avgTtftMs independently. Preserve the existing TTFB display when
valid, but render a Tooltip whenever either timing value is available so valid
row.avgTtftMs remains visible even when avgTtfbMs is null or non-positive; show
the unavailable text only for the individual missing value.

In `@src/repository/message.ts`:
- Around line 971-1010: Update the explicit select list in
findMessageRequestBySessionId to include messageRequest.ttfbMs,
messageRequest.ttftMs, and messageRequest.timingSemanticsVersion, matching
findMessageRequestById and the ledger-only fallback while preserving the
existing query behavior.

---

Nitpick comments:
In `@src/app/`[locale]/status/_lib/timeline-windows.ts:
- Around line 27-55: 提取一个按指标字段参数化的通用加权平均辅助函数,集中复用 computeAvgTtfb 和
computeAvgTtft 中的遍历、样本加权、空样本返回 null 及四舍五入逻辑;让两个公开函数仅传入对应的 ttfbMs 或 ttftMs
字段,并保持现有结果不变。

In `@src/app/v1/_lib/proxy/stream-gate/stream-content-gate.ts`:
- Around line 336-364: 为 createContentTimingObserver 中的增量 SSE 解析器设置固定的
maxBufferedCharacters 上限,避免未完成的超长行或多行 data 持续累积;同时定位并更新 shadow observer 中的
SseFrameParser 实例,使用相同上限配置,保持现有 SseFrameBufferLimitError 的终止处理行为。

In `@src/instrumentation.ts`:
- Around line 275-304: 提取一个共享的受控 interval tick 辅助函数,统一封装 stop-request 检查、current
promise 记录以及 finally 中清空 promise 的逻辑;然后让 startCacheEffectivenessScheduler 和
startReplayCleanupScheduler 通过该辅助函数仅提供各自的异步执行体、stopFlagKey 和
promiseKey。保持现有停止行为、并发保护、错误处理及调度结果不变,删除两处重复的 tick 实现。

In `@src/lib/utils/performance-formatter.test.ts`:
- Around line 5-21: 在 performance formatter 测试中补充负数和 NaN 的非法 TTFT 回归断言,验证
calculateOutputRate 对这些输入返回 null,确保无效速率不会进入仪表盘。

In `@src/repository/provider-cache-effectiveness.ts`:
- Around line 136-175: 提取一个内部 helper,复用 getProviderCacheCoefficients 与
getProviderModelCacheCoefficients 中相同的聚合查询、computeCoefficientBp
调用及结果组装逻辑;让两个公开函数仅传入各自的 groupBy 列并保留现有返回类型与筛选条件,确保 provider 与 provider-model
两种维度行为不变。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a5a40c6-207c-45ea-979e-fcadd9c9cc0a

📥 Commits

Reviewing files that changed from the base of the PR and between c7174f6 and 2d045f8.

📒 Files selected for processing (152)
  • .env.example
  • deploy/k8s/README.md
  • deploy/k8s/app/deployment.yaml
  • deploy/k8s/app/hpa.yaml
  • docs/k8s-deployment.md
  • docs/performance-optimization-roadmap.md
  • drizzle/0116_lying_marvel_apes.sql
  • drizzle/meta/0114_snapshot.json
  • drizzle/meta/0116_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/en/settings/config.json
  • messages/en/settings/statusPage.json
  • messages/ja/dashboard.json
  • messages/ja/settings/config.json
  • messages/ja/settings/statusPage.json
  • messages/ru/dashboard.json
  • messages/ru/settings/config.json
  • messages/ru/settings/statusPage.json
  • messages/zh-CN/dashboard.json
  • messages/zh-CN/settings/config.json
  • messages/zh-CN/settings/statusPage.json
  • messages/zh-TW/dashboard.json
  • messages/zh-TW/settings/config.json
  • messages/zh-TW/settings/statusPage.json
  • src/actions/public-status.ts
  • src/actions/system-config.ts
  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/app/[locale]/dashboard/leaderboard/_components/success-rate-display.ts
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LatencyBreakdownBar.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/PerformanceTab.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/[locale]/status/[slug]/page.tsx
  • src/app/[locale]/status/_components/public-status-timeline.tsx
  • src/app/[locale]/status/_components/public-status-view.tsx
  • src/app/[locale]/status/_lib/timeline-windows.ts
  • src/app/[locale]/status/page.tsx
  • src/app/api/admin/system-config/route.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/app/v1/_lib/proxy/stream-gate/stream-content-gate.ts
  • src/app/v1/_lib/proxy/warmup-guard.ts
  • src/drizzle/schema.ts
  • src/instrumentation.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/cache-effectiveness/service.ts
  • src/lib/config/index.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/langfuse/emit-proxy-trace.ts
  • src/lib/langfuse/trace-proxy-request.ts
  • src/lib/ledger-backfill/service.ts
  • src/lib/ledger-backfill/trigger.sql
  • src/lib/lifecycle/shutdown.ts
  • src/lib/observability/discovery-metrics.ts
  • src/lib/price-sync/cloud-price-updater.ts
  • src/lib/provider-endpoints/leader-lock.ts
  • src/lib/provider-endpoints/probe-log-cleanup.ts
  • src/lib/provider-endpoints/probe-scheduler.ts
  • src/lib/public-status/aggregation-core.ts
  • src/lib/public-status/aggregation.ts
  • src/lib/public-status/openapi.ts
  • src/lib/public-status/payload.ts
  • src/lib/public-status/read-store.ts
  • src/lib/public-status/rollup-store.ts
  • src/lib/redis/leaderboard-cache.ts
  • src/lib/session-manager-detail-snapshots.test.ts
  • src/lib/session-manager.ts
  • src/lib/session-snapshot/filesystem-store.ts
  • src/lib/session-snapshot/store.ts
  • src/lib/session-snapshot/types.ts
  • src/lib/utils/performance-formatter.test.ts
  • src/lib/utils/performance-formatter.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/leaderboard.ts
  • src/repository/message-write-buffer.ts
  • src/repository/message.ts
  • src/repository/provider-cache-effectiveness.ts
  • src/repository/provider-endpoints.ts
  • src/repository/system-config.ts
  • src/repository/usage-logs.ts
  • src/types/message.ts
  • src/types/provider.ts
  • src/types/routing-trace.ts
  • src/types/system-config.ts
  • tests/integration/proxy-hedge-lifecycle.test.ts
  • tests/integration/public-status/config-publish.test.ts
  • tests/unit/actions/system-config-fake-streaming-setting.test.ts
  • tests/unit/actions/system-config-non-chat-retry-setting.test.ts
  • tests/unit/actions/system-config-save.test.ts
  • tests/unit/actions/system-config-stream-gate-affinity-settings.test.ts
  • tests/unit/dashboard-logs-virtualized-special-settings-ui.test.tsx
  • tests/unit/dashboard-logs-warmup-ui.test.tsx
  • tests/unit/dashboard/leaderboard-success-rate-display.test.ts
  • tests/unit/dashboard/leaderboard-view-cache-coefficient.test.tsx
  • tests/unit/error-details-dialog-warmup-ui.test.tsx
  • tests/unit/k8s-deploy-assets-review-fixes.test.ts
  • tests/unit/langfuse/langfuse-trace.test.ts
  • tests/unit/lib/cache-effectiveness-service.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • tests/unit/lib/filesystem-session-snapshot-store.test.ts
  • tests/unit/lib/performance-formatter-timing.test.ts
  • tests/unit/lib/provider-endpoints/leader-lock.test.ts
  • tests/unit/lib/provider-endpoints/probe-scheduler.test.ts
  • tests/unit/lib/session-snapshot-store.test.ts
  • tests/unit/price-sync/cloud-price-updater.test.ts
  • tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts
  • tests/unit/proxy/response-handler-abort-listener-cleanup.test.ts
  • tests/unit/proxy/response-handler-client-abort-drain.test.ts
  • tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts
  • tests/unit/proxy/response-handler-exported-finalizers.test.ts
  • tests/unit/proxy/response-handler-lease-decrement.test.ts
  • tests/unit/proxy/response-handler-non200.test.ts
  • tests/unit/proxy/session-ttfb-tfft.test.ts
  • tests/unit/proxy/stream-gate-content-gate.test.ts
  • tests/unit/public-status/aggregation-core-tps.test.ts
  • tests/unit/public-status/aggregation.test.ts
  • tests/unit/public-status/read-store.test.ts
  • tests/unit/public-status/rollup-store.test.ts
  • tests/unit/public-status/system-config-publish.test.ts
  • tests/unit/redis/leaderboard-cache.test.ts
  • tests/unit/repository/leaderboard-cache-coefficient.test.ts
  • tests/unit/repository/leaderboard-provider-metrics.test.ts
  • tests/unit/repository/leaderboard-timezone-parentheses.test.ts
  • tests/unit/repository/leaderboard-tps-basis.test.ts
  • tests/unit/repository/leaderboard-user-model-stats.test.ts
  • tests/unit/repository/message-public-readback.test.ts
  • tests/unit/repository/message-public-status-rollup.test.ts
  • tests/unit/repository/message-session-readback.test.ts
  • tests/unit/repository/message-terminal-public-status-seam.test.ts
  • tests/unit/repository/message-terminal-write-apis.test.ts
  • tests/unit/repository/message-usage-logs-query.test.ts
  • tests/unit/repository/message-write-buffer.test.ts
  • tests/unit/repository/provider-endpoints-probe-result.test.ts
  • tests/unit/repository/system-config-degradation-ladder.test.ts
  • tests/unit/repository/system-config-update-missing-columns.test.ts
  • tests/unit/repository/usage-logs-actual-response-model.test.ts
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • tests/unit/server-shutdown.test.ts
  • tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx
  • vitest.config.ts
💤 Files with no reviewable changes (1)
  • deploy/k8s/app/hpa.yaml

Comment on lines +138 to +145
volumeMounts:
- name: session-snapshots
mountPath: /var/lib/claude-code-hub/session-snapshots
volumes:
- name: session-snapshots
hostPath:
path: /var/lib/claude-code-hub/session-snapshots
type: DirectoryOrCreate

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

不要用 hostPath 作为多副本的共享快照存储。

hostPath 仅在单个节点本地持久化;副本被调度或重调度到不同节点后,Session 快照会丢失或无法读取,与 SESSION_SNAPSHOT_ROOT 的共享持久化契约不符。请改用共享 PVC(RWX),或在多节点部署时选择 Redis 快照后端。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/k8s/app/deployment.yaml` around lines 138 - 145, Replace the
session-snapshots hostPath volume in the deployment’s volumeMounts/volumes
configuration with a shared RWX PVC, and configure the PVC-backed storage for
SESSION_SNAPSHOT_ROOT so replicas can access the same snapshots across nodes.
Alternatively, when multi-node deployment uses the Redis snapshot backend,
remove this local hostPath dependency and apply the corresponding Redis
configuration.

Comment thread deploy/k8s/README.md
Comment on lines +57 to +61
> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

引用块间的空行触发 markdownlint MD028

第 57-60 行与 62-63 行是两个 > 引用块,中间用一个普通空行分隔,markdownlint-cli2 报告 MD028 (no-blanks-blockquote):不同渲染器对此处理不一致,有些会把它们合并为同一个引用块并保留内部空行,有些则视为两个独立块。

📝 建议修复
 > 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
 > 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
-
+>
 > NodePort 回落模式下,`scripts/deploy-k8s.sh` 会自动跳过 `app/networkpolicy.yaml`,

As per static analysis hints: [warning] 61-61: Blank line inside blockquote (MD028, no-blanks-blockquote)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
>
> NodePort 回落模式下,`scripts/deploy-k8s.sh` 会自动跳过 `app/networkpolicy.yaml`,
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 61-61: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/k8s/README.md` around lines 57 - 61, Remove the blank line separating
the adjacent blockquote lines in the README so the entire note remains one
continuous blockquote and satisfies markdownlint MD028.

Source: Linters/SAST tools

Comment on lines +13 to +49
DO $migration$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = current_schema()
AND table_name = 'message_request'
AND column_name = 'first_byte_ms'
) THEN
EXECUTE $sql$
UPDATE "message_request"
SET
"ttft_ms" = "ttfb_ms",
"ttfb_ms" = "first_byte_ms",
"timing_semantics_version" = 2
WHERE "first_byte_ms" IS NOT NULL
$sql$;
END IF;

IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = current_schema()
AND table_name = 'usage_ledger'
AND column_name = 'first_byte_ms'
) THEN
EXECUTE $sql$
UPDATE "usage_ledger"
SET
"ttft_ms" = "ttfb_ms",
"ttfb_ms" = "first_byte_ms",
"timing_semantics_version" = 2
WHERE "first_byte_ms" IS NOT NULL
$sql$;
END IF;
END
$migration$;--> statement-breakpoint

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e sql . drizzle --max-depth 1 | sort
echo "---"
grep -rn "fn_upsert_usage_ledger\|ttfb_ms" drizzle/*.sql | grep -v "0116_lying_marvel_apes"

Repository: ding113/claude-code-hub

Length of output: 7920


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- 0114 trigger definition ---"
sed -n '1,180p' drizzle/0114_overconfident_ronan.sql

echo "--- 0116 migration relevant section ---"
sed -n '1,230p' drizzle/0116_lying_marvel_apes.sql

echo "--- deterministic SQL target/update-of semantics probe from pg docs ---"
python3 - <<'PY'
print("PostgreSQL UPDATE OF target behavior: an AFTER UPDATE OF trigger fires when any listed column is mentioned in SET, INSERT, ON CONFLICT DO UPDATE, ON UPDATE SET regardless of whether value changes.")
PY

Repository: ding113/claude-code-hub

Length of output: 14120


迁移回填顺序存在数据污染风险:usage_ledger.ttft_ms 会被旧触发器污染。

message_requestSET ... "ttfb_ms" = "first_byte_ms" 在 DO 块中执行于旧触发器(0114)仍生效期间;旧触发器监听 ttfb_ms,并将 INSERT ... ON CONFLICT ... SET ttfb_ms = EXCLUDED.ttfb_ms 同步到 usage_ledger.ttfb_ms,覆盖了其原有 TTFB。随后 usage_ledger 自身的回填再执行 "ttft_ms" = "ttfb_ms",会让这部分历史行的 usage_ledger.ttft_msusage_ledger.ttfb_ms 都等于原 first_byte_ms,破坏预期的一致性。

建议在 message_request 交换 UPDATE 前禁用 message_request 上的 trg_upsert_usage_ledger,或在 DO 块前先 DROP TRIGGER IF EXISTS trg_upsert_usage_ledger ON message_request;迁移完成后再 ENABLE / CREATE 新触发器。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drizzle/0116_lying_marvel_apes.sql` around lines 13 - 49, 调整迁移 DO 块中
message_request 的回填顺序,避免旧触发器 trg_upsert_usage_ledger 在更新 ttfb_ms 时污染
usage_ledger.ttfb_ms:在 message_request
回填前禁用该触发器(或先删除),完成两个表的回填后恢复或创建迁移所需的新触发器,并确保触发器状态最终正确。

"segmentTotal": "总计",
"ttfb": "TTFB",
"ttft": "TTFT",
"firstByteToFirstToken": "响应头到首个有效内容",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

统一修正 firstByteToFirstToken 的起点文案。

该字段表示从首字节(TTFB)到首个有效内容(TTFT),不应表述为从响应头开始。

  • messages/zh-CN/dashboard.json#L366-L366: 改为“首字节到首个有效内容”。
  • messages/ru/dashboard.json#L366-L366: 改为“От первого байта до первого валидного содержимого”。
  • messages/zh-TW/dashboard.json#L366-L366: 改为“首字節到首個有效內容”。
📍 Affects 3 files
  • messages/zh-CN/dashboard.json#L366-L366 (this comment)
  • messages/ru/dashboard.json#L366-L366
  • messages/zh-TW/dashboard.json#L366-L366
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@messages/zh-CN/dashboard.json` at line 366, 统一更新 firstByteToFirstToken 文案:在
messages/zh-CN/dashboard.json 第366行改为“首字节到首个有效内容”,在 messages/ru/dashboard.json
第366行改为“От первого байта до первого валидного содержимого”,在
messages/zh-TW/dashboard.json 第366行改为“首字節到首個有效內容”。

Comment on lines +219 to +225
primeSystemSettingsCache(updated);
if (validated.sessionSnapshotStore !== undefined) {
const { reconfigureSessionSnapshotStore } = await import("@/lib/session-snapshot/store");
await reconfigureSessionSnapshotStore(updated.sessionSnapshotStore).catch((error) => {
logger.warn("[SystemSettings] Failed to reconfigure session snapshot store", { error });
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C4 '\bsessionSnapshotStore\b' -g '!tests/**'
rg -nP --type=ts -C3 'reconfigureSessionSnapshotStore'

Repository: ding113/claude-code-hub

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -u

echo "== repo files =="
git ls-files | sed -n '1,120p'

echo "== target file =="
if [ -f src/actions/system-config.ts ]; then
  wc -l src/actions/system-config.ts
  sed -n '180,245p' src/actions/system-config.ts | cat -n -v
else
  fd -a 'system-config\.ts$' .
fi

echo "== broad symbol search without file filter =="
rg -n -C 3 'sessionSnapshotStore|reconfigureSessionSnapshotStore|primeSystemSettingsCache|publicStatusProjectionWarningCode' . || true

Repository: ding113/claude-code-hub

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -u

echo "== src/actions/system-config.ts relevant lines =="
sed -n '32,140p;215,310p' src/actions/system-config.ts | cat -n

echo "== src/app/api/admin/system-config/route.ts relevant lines =="
sed -n '1,180p' src/app/api/admin/system-config/route.ts | cat -n

echo "== runtime session snapshot store implementation =="
sed -n '1,150p' src/lib/session-snapshot/store.ts | cat -n

echo "== return schemas relevant =="
sed -n '250,270p' src/lib/api/v1/schemas/system-config.ts | cat -n
sed -n '1,90p' src/lib/api-client/v1/actions/system-config.ts | cat -n

echo "== saveSystemSettings callers in repo (non-test) =="
rg -n -C 2 'saveSystemSettings|updateSystemSettings|/api/v1/system/settings|/api/admin/system-config|/api/admin/system' src --glob '!**/*test*' || true

Repository: ding113/claude-code-hub

Length of output: 35943


把 sessionSnapshotStore 重配置失败转为可观测告警,避免配置已落库但运行时仍使用旧快照存储。

primeSystemSettingsCache(updated) 先提交 DB/内存缓存为新 store;reconfigureSessionSnapshotStore() 失败会只输出 warn,随后 actions/system-config.ts 仍返回 { ok: true, data: updated }。参考 publicStatusProjectionWarningCode 的处理路径,将失败码返回给前端,让管理员知道保存结果需要修复环境后重试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/system-config.ts` around lines 219 - 225, 更新 system settings
保存流程中 reconfigureSessionSnapshotStore 的失败处理:不要仅记录 warn 后继续返回普通的 { ok: true,
data: updated },应沿用 publicStatusProjectionWarningCode
的处理路径,将可供前端识别的失败/告警码附加到成功响应中。保留数据库与缓存已更新的行为,并确保成功重配置时响应保持现有结果。

Comment on lines +80 to +111
export function getSessionSnapshotStore(): SessionSnapshotStore {
if (shuttingDown) return disabledStore;
const selected = getCachedSystemSettingsOnlyCache()?.sessionSnapshotStore ?? activeSelection;
if (selected !== activeSelection) {
void reconfigureSessionSnapshotStore(selected).catch((error) => {
logger.warn("[SessionSnapshot] Runtime store reconfiguration failed", { error });
});
return resolveStore(selected);
}
return activeStore;
}

export function reconfigureSessionSnapshotStore(
selected: SessionSnapshotStoreSetting
): Promise<void> {
if (shuttingDown) return Promise.resolve();

const target = resolveStore(selected);
activeSelection = selected;
activeStore = target;
const previousTransition = reconfigurePromise.catch(() => undefined);
reconfigurePromise = previousTransition.then(async () => {
await target.start();
if (activeStore !== target) return;

const inactiveStores = [filesystemStore, redisStore, disabledStore].filter(
(store) => store !== target
);
await Promise.all(inactiveStores.map((store) => store.stop()));
});
return reconfigurePromise;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)session-snapshot|session-snapshot-store.test|filesystem-store|redis-store|disabled-.*snap' || true

echo "== store outline =="
ast-grep outline src/lib/session-snapshot/store.ts --view compact || true

echo "== store code =="
cat -n src/lib/session-snapshot/store.ts

echo "== tests around store =="
for f in $(git ls-files | rg 'session-snapshot-store\.test\.ts$|session-snapshot.*test\.ts$'); do
  echo "--- $f"
  wc -l "$f"
  cat -n "$f"
done

echo "== filesystem store outline/code relevant =="
for f in $(git ls-files | rg 'filesystem-store\.ts$'); do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" --view compact || true
  cat -n "$f"
done

echo "== redis store outline/code relevant =="
for f in $(git ls-files | rg 'redis.*store.*\.ts$|redis-session.*\.ts$'); do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" --view compact || true
  rg -n "class|start|stop|enqueue|get|active|activeStore|store" "$f"
done

Repository: ding113/claude-code-hub

Length of output: 47369


切换 store 前不要提前提交配置状态

getSessionSnapshotStore() 检测到配置变更时立即返回 resolveStore(selected),并异步启动新 store;如果 target.start() 失败,activeSelection/activeStore 仍然指向失败目标,后面会继续对该失败 store 执行 enqueuePatch()(文件系统实现会先执行 start(),发现非目录则 accepting = false,直接返回 false 丢弃快照),而 disabled 路径同样依赖一次配置/设置变化才切换到可用的 store。

建议:仅在 target.start() 成功后再更新 activeSelection/activeStore;在后台重新配置期间继续返回当前仍在使用的 store,这样失败后下次配置变更仍可自愈。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/session-snapshot/store.ts` around lines 80 - 111, 调整
getSessionSnapshotStore 与 reconfigureSessionSnapshotStore 的切换流程:不要在
target.start() 成功前更新 activeSelection 或 activeStore,也不要在后台重配置期间立即返回
resolveStore(selected);应继续返回当前 activeStore。仅当 target.start() 成功且目标仍有效时,再提交
activeSelection/activeStore 并停止其他 store,失败时保留当前可用 store,使后续配置变化仍能重试自愈。

Comment on lines +56 to 67
if (
outputTokens == null ||
outputTokens <= 0 ||
durationMs == null ||
durationMs <= 0 ||
ttftMs == null
) {
return null;
}
if (firstByteMs == null) return null;
const generationTimeMs = durationMs - firstByteMs;
const generationTimeMs = durationMs - ttftMs;
if (generationTimeMs <= 0) return null;
return outputTokens / (generationTimeMs / 1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

拒绝非法计时值,避免输出 NaN 或错误速率。

当前检查无法拦截 NaN 和负数 ttftMsttftMs = NaN 会使函数返回 NaN,负数则会扩大生成窗口。请在两个函数中校验 Number.isFinite(...),并要求 ttftMs >= 0;同时对 durationMsoutputTokens 做有限数校验。

建议修复
   if (
     outputTokens == null ||
+    !Number.isFinite(outputTokens) ||
     outputTokens <= 0 ||
     durationMs == null ||
+    !Number.isFinite(durationMs) ||
     durationMs <= 0 ||
-    ttftMs == null
+    ttftMs == null ||
+    !Number.isFinite(ttftMs) ||
+    ttftMs < 0
   ) {

shouldHideOutputRate 同样增加 durationMsttftMs 的有限数、非负校验。

Also applies to: 80-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils/performance-formatter.ts` around lines 56 - 67, Update both
performance-formatting functions, including shouldHideOutputRate and the rate
calculation block, to reject non-finite durationMs, outputTokens, and ttftMs
values; require durationMs and outputTokens to be positive and ttftMs to be
non-negative. Preserve the existing null return behavior for all invalid timing
inputs and prevent NaN or incorrect rates from being produced.

Comment on lines +41 to 50
describe("ProxySession TTFB / TTFT", () => {
it("分别记录响应头与首个有效内容耗时", () => {
const session = createSession(Date.now() - 1_200);

const tfft = session.recordTfft();
const ttfb = session.recordTtfb(200);
const ttft = session.recordTtft(800);

expect(session.tfftMs).toBe(tfft);
expect(session.firstByteMs).toBe(tfft);
expect(session.ttfbMs).toBe(ttfb);
expect(session.ttftMs).toBe(ttft);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

首个用例未验证传入的 elapsedMs 是否被实际使用。

该用例只断言 session.ttfbMs === ttfbttfb 就是 recordTtfb 刚返回并存入的值,属于同义反复),并未断言 ttfb/ttft 等于传入的 200/800。若 recordTtfb/recordTtft 忽略了 elapsedMs 参数、退化为始终用 Date.now() - startTime 计算,本用例仍会通过,未能覆盖用例标题所声明的"分别记录响应头与首个有效内容耗时"这一核心契约。

✅ 建议补充具体数值断言
   const ttfb = session.recordTtfb(200);
   const ttft = session.recordTtft(800);

+  expect(ttfb).toBe(200);
+  expect(ttft).toBe(800);
   expect(session.ttfbMs).toBe(ttfb);
   expect(session.ttftMs).toBe(ttft);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("ProxySession TTFB / TTFT", () => {
it("分别记录响应头与首个有效内容耗时", () => {
const session = createSession(Date.now() - 1_200);
const tfft = session.recordTfft();
const ttfb = session.recordTtfb(200);
const ttft = session.recordTtft(800);
expect(session.tfftMs).toBe(tfft);
expect(session.firstByteMs).toBe(tfft);
expect(session.ttfbMs).toBe(ttfb);
expect(session.ttftMs).toBe(ttft);
});
describe("ProxySession TTFB / TTFT", () => {
it("分别记录响应头与首个有效内容耗时", () => {
const session = createSession(Date.now() - 1_200);
const ttfb = session.recordTtfb(200);
const ttft = session.recordTtft(800);
expect(ttfb).toBe(200);
expect(ttft).toBe(800);
expect(session.ttfbMs).toBe(ttfb);
expect(session.ttftMs).toBe(ttft);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/proxy/session-ttfb-tfft.test.ts` around lines 41 - 50, Update the
test case around recordTtfb and recordTtft to assert that both returned values
and the corresponding session fields equal the explicitly supplied elapsedMs
values 200 and 800, rather than only comparing each field with its returned
value. Preserve the existing distinction between TTFB and TTFT.

Comment on lines +260 to +270
it.each([
{
family: "openai-chat" as const,
neutral: 'data: {"choices":[{"delta":{"role":"assistant"}}]}\n\n',
content: 'data: {"choices":[{"delta":{"content":"hello"}}]}\n\n',
},
{
family: "gemini" as const,
neutral: 'data: {"usageMetadata":{"totalTokenCount":1}}\n\n',
content: 'data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}\n\n',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

使用 Biome 的双引号格式。

第 263、264、268、269 行新增字符串使用单引号,和仓库的双引号配置不一致。

建议修改
-      neutral: 'data: {"choices":[{"delta":{"role":"assistant"}}]}\n\n',
-      content: 'data: {"choices":[{"delta":{"content":"hello"}}]}\n\n',
+      neutral: "data: {\"choices\":[{\"delta\":{\"role\":\"assistant\"}}]}\n\n",
+      content: "data: {\"choices\":[{\"delta\":{\"content\":\"hello\"}}]}\n\n",
...
-      neutral: 'data: {"usageMetadata":{"totalTokenCount":1}}\n\n',
-      content: 'data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}\n\n',
+      neutral: "data: {\"usageMetadata\":{\"totalTokenCount\":1}}\n\n",
+      content: "data: {\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"hello\"}]}}]}\n\n",

As per coding guidelines, **/*.{ts,tsx,js,jsx} must use Biome formatting with double quotes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.each([
{
family: "openai-chat" as const,
neutral: 'data: {"choices":[{"delta":{"role":"assistant"}}]}\n\n',
content: 'data: {"choices":[{"delta":{"content":"hello"}}]}\n\n',
},
{
family: "gemini" as const,
neutral: 'data: {"usageMetadata":{"totalTokenCount":1}}\n\n',
content: 'data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}\n\n',
},
it.each([
{
family: "openai-chat" as const,
neutral: "data: {\"choices\":[{\"delta\":{\"role\":\"assistant\"}}]}\n\n",
content: "data: {\"choices\":[{\"delta\":{\"content\":\"hello\"}}]}\n\n",
},
{
family: "gemini" as const,
neutral: "data: {\"usageMetadata\":{\"totalTokenCount\":1}}\n\n",
content: "data: {\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"hello\"}]}}]}\n\n",
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/proxy/stream-gate-content-gate.test.ts` around lines 260 - 270,
Update the newly added string literals in the parameterized test around the
openai-chat and gemini cases to use Biome’s configured double-quote style,
including the neutral and content values, without changing their contents or
test behavior.

Source: Coding guidelines

Comment on lines +104 to +106
ttfbMs: 100,
ttftMs: 200,
timingSemanticsVersion: 2,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

补齐主表 session 回读的新版计时字段。

findMessageRequestBySessionIdmessageRequest 查询仍未选择 ttfbMsttftMstimingSemanticsVersion,但 ledger fallback 已选择它们。普通主表 session 回读会丢失这些值;请在主表 projection 中补齐字段,并增加主表路径断言。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/repository/message-session-readback.test.ts` around lines 104 -
106, 更新 findMessageRequestBySessionId 的主表 messageRequest 查询 projection,补充选择
ttfbMs、ttftMs 和 timingSemanticsVersion,保持与 ledger fallback 的字段一致;同时增加主表 session
回读路径的断言,验证这三个新版计时字段能够正确返回。

return result;
}

async start(): Promise<void> {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [ERROR-SILENT] Redis snapshot backend can be enabled while still unusable

Why this is a problem: The new async start(): Promise<void> {} never validates Redis availability, but reconfigureSessionSnapshotStore("redis") treats it as a successful transition. If Redis is disconnected, later enqueuePatch() / get() calls just return false / null, so the settings update appears to work while request and response snapshots are silently dropped.

Suggested fix:

async start(): Promise<void> {
  const redis = getRedisClient();
  if (redis?.status !== "ready") {
    throw new Error("Redis session snapshot store is not ready");
  }
}

if (shuttingDown) return Promise.resolve();

const target = resolveStore(selected);
activeSelection = selected;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [LOGIC-BUG] Reconfiguration swaps the active store before startup succeeds

Why this is a problem: This new sequence assigns activeSelection = selected and activeStore = target before await target.start(). If start() rejects (for example, the filesystem root is invalid), the process is left pointing at the failed backend even when the previous backend was healthy. Callers only log the reconfiguration failure, so subsequent snapshot reads and writes keep using a broken store until another manual change or restart.

Suggested fix:

const previousSelection = activeSelection;
const previousStore = activeStore;
const previousTransition = reconfigurePromise.catch(() => undefined);

reconfigurePromise = previousTransition.then(async () => {
  await target.start();
  activeSelection = selected;
  activeStore = target;

  const inactiveStores = [filesystemStore, redisStore, disabledStore].filter(
    (store) => store !== target
  );
  await Promise.all(inactiveStores.map((store) => store.stop()));
}).catch((error) => {
  activeSelection = previousSelection;
  activeStore = previousStore;
  throw error;
});

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR is very large and spans runtime snapshot storage, timing metrics, schedulers, migrations, and UI/status surfaces. I validated the highest-risk new code paths against full-file context and found two high-priority issues in the new session snapshot backend switching flow. Because the change set crosses several subsystems, it is difficult to reason about rollout risk as a single unit.

PR Size: XL

  • Lines changed: 10140
  • Files changed: 152
  • Split suggestions:
    • Separate timing semantics and proxy persistence changes from storage backend changes
    • Separate session snapshot backend/runtime reconfiguration from scheduler and Kubernetes tuning
    • Separate leaderboard/public-status reporting changes from migration and instrumentation work

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 1 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

  • None.

High Priority Issues (Should Fix)

  • src/lib/session-snapshot/store.ts:55RedisSessionSnapshotStore.start() is a no-op, so switching the setting to Redis can succeed even when Redis is unavailable and snapshots will then be silently dropped.
  • src/lib/session-snapshot/store.ts:98reconfigureSessionSnapshotStore() swaps activeSelection / activeStore before target.start() succeeds, so a failed backend transition can strand the process on a dead store.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted the PR review for #1371.

  • Applied the size label size/XL.
  • Added 2 inline review comments on src/lib/session-snapshot/store.ts:55 and src/lib/session-snapshot/store.ts:98.
  • Submitted the required summary review with XL split suggestions and the issue matrix.
  • Reported findings:
    • src/lib/session-snapshot/store.ts:55 — Redis backend activation succeeds without validating Redis readiness, which can silently drop snapshots.
    • src/lib/session-snapshot/store.ts:98 — runtime reconfiguration swaps the active backend before startup succeeds, which can leave the process pinned to a broken store.

If you want, I can also export the posted review text into a local markdown artifact for the workflow logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant