Skip to content

feat: 统一前缀亲和 Session 与 Replay 使用记录语义 - #1372

Merged
ding113 merged 4 commits into
devfrom
prefix-affinity-session-metrics
Aug 1, 2026
Merged

feat: 统一前缀亲和 Session 与 Replay 使用记录语义#1372
ding113 merged 4 commits into
devfrom
prefix-affinity-session-metrics

Conversation

@ding113

@ding113 ding113 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

变更概览

  • Usage Logs 虚拟列表的性能列固定显示 TFFT / TTFB 缩写,Tooltip 继续显示完整术语。
  • 在开启“忽略客户端 Session ID”时,Active Session 统一按 prefix-affinity 的命中 fingerprint 聚合;请求数、Token、成本、耗时、详情与终止均使用同一 identity 口径。
  • Prefix Session 终止会原子旋转 scope generation 并失效已知 binding;旧 generation 的在途请求无法重新写回,后续请求会重新进入 provider 决策链。
  • Replay 请求不再标记为已拦截,Usage Logs 中保留 token、cache、实际计费模型与来源关系,成本固定为 0,并支持详情标记与 all | replay | non-replay 筛选。
  • Live Replay 审计只在 source 成功终态且已有 durable request id 后物化;abort、Redis 丢失、stall 与 timeout 不会产生伪成功 200。
  • Billing、quota、Dashboard 全局指标及 availability/cache alert/proxy status 等 operational 聚合排除 Replay;Usage Logs 的默认审计视图仍包含 Replay。
  • Stream Gate 的默认模式从 off 调整为 enforce,显式 off / shadow 配置保持有效。

数据与接口

  • 新增 migration 0116_gigantic_zombie.sql,包含 Session identity、Replay provenance 与查询索引,并在 migration 前执行重复数据 preflight。
  • 保留物理 sessionId / sourceSessionId 用于请求定位;公共 prefix identity 只用于聚合与管理操作。
  • Session REST query 增加 sourceSessionId,Usage Logs API 增加 replayFilter,并重新生成 OpenAPI types。
  • 新增稳定 Session locator error code 与五语言翻译,避免服务端业务 helper 硬编码 UI 文案。

验证

  • bun run lint:fix
  • bun run lint
  • bun run typecheck
  • bun run test: 841 files passed, 8082 tests passed, 2 files / 13 tests skipped
  • bun run build
  • bun run validate:migrations: 118 migrations passed
  • bun run openapi:generate
  • bun run openapi:check
  • bun run openapi:lint
  • bun run test:v1: 91 files / 381 tests passed; critical coverage passed
  • git diff --check

Greptile Summary

The PR unifies prefix-affinity session identity and Replay audit semantics across persistence, APIs, operational aggregates, and dashboard views. The previously reported migration issue is fixed by moving large index installation outside the Drizzle migration transaction.

  • Routes manual and automatic migrations through a shared advisory-lock-protected orchestrator.
  • Builds and validates nine replacement indexes concurrently using resumable temporary indexes.
  • Adds durable session identity and Replay provenance while excluding Replay traffic from billing and operational metrics.
  • Extends APIs and dashboard views with Replay filtering, provenance, and unified session controls.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported synchronous index rebuild has been replaced with transaction-free concurrent index construction used by both manual and automatic migration paths.

Important Files Changed

Filename Overview
src/lib/migrate.ts Routes boot, manual, and restore migration paths through one locked preflight/migration/postflight sequence.
src/lib/migrations/session-replay-index-preflight.ts Installs the nine session and Replay indexes concurrently with validation markers and interruption recovery.
drizzle/0116_gigantic_zombie.sql Adds session identity and Replay provenance without rebuilding the large indexes inside the transactional migration.
package.json Redirects the documented manual migration command to the shared application migration orchestrator.
scripts/migrate.ts Provides the manual entry point for the same migration runner used during application startup.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Acquire migration advisory lock] --> B{Base tables exist and 0116 pending?}
  B -->|Yes| C[Ensure index prerequisite columns]
  C --> D[Build and validate temporary indexes concurrently]
  D --> E[Replace canonical indexes]
  B -->|No| F[Run Drizzle migrations]
  E --> F
  F --> G[Postflight index reconciliation]
  G --> H[Release advisory lock]
Loading

Reviews (4): Last reviewed commit: "fix: exclude replay requests from sessio..." | Re-trigger Greptile

Context used:

@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 Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Session identity 与账本同步

Layer / File(s) Summary
数据库迁移与账本投影
drizzle/0116_gigantic_zombie.sql, src/drizzle/schema.ts, src/lib/ledger-backfill/*, src/lib/ledger-backfill/trigger.sql
新增 Session identity、Affinity 和 Replay 字段。历史 Replay 数据迁移为正式标记。usage_ledger 同步这些字段,并将 Replay 成本设为零。
代理身份与 observed Session
src/app/v1/_lib/proxy/*, src/lib/session-tracker.ts, src/lib/request-identity.ts
代理请求生成聚合 Session identity。Affinity 使用 generation CAS。代理流程记录和刷新 observed Session。
Session 请求定位与 API
src/repository/message.ts, src/lib/session-request-locator.ts, src/actions/*session*, src/app/api/v1/resources/sessions/*, src/lib/api-client/v1/actions/*
Session 查询支持 requestSequencerequestIdsourceSessionId。请求定位器验证身份、物理来源和序列号。会话页面保留物理来源信息。
Replay 日志与界面
src/repository/usage-logs.ts, src/repository/_shared/usage-log-filters.ts, src/app/[locale]/dashboard/logs/*, messages/*
日志查询支持 Replay、非 Replay 和全部请求筛选。Replay 日志显示来源请求 ID,并不显示为 Blocked。
迁移预检与运行时终止
src/lib/migrations/session-replay-index-preflight.ts, src/lib/migrate.ts, src/lib/rate-limit/*, src/lib/session-manager.ts
迁移流程增加 Session Replay 索引预检。Redis 增加物理 Session 强制终止操作。Session 终止增加 API key 所有权校验。
验证测试
tests/integration/*, tests/unit/*, tests/api/*
测试覆盖数据库投影、请求定位、Affinity generation、observed Session、Replay 审计、日志筛选、迁移预检和多语言错误消息。

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.93% 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
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.
Title check ✅ Passed 标题准确概括了统一前缀亲和 Session 与 Replay 使用记录语义这一主要变更。
Description check ✅ Passed 描述详细涵盖 Session、Replay、迁移、API、指标、测试和部署流程,与变更内容高度相关。
✨ 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 prefix-affinity-session-metrics

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 Aug 1, 2026
Comment thread drizzle/0116_gigantic_zombie.sql Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

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: 101c215e7d

ℹ️ 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 thread src/lib/migrate.ts Outdated
Comment thread src/repository/message.ts Outdated
Comment thread src/actions/active-sessions.ts Outdated
Comment thread src/app/v1/_lib/proxy/affinity/affinity-store.ts Outdated

@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: 10

Caution

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

⚠️ Outside diff range comments (1)
src/app/v1/_lib/proxy/provider-selector.ts (1)

205-263: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

会话复用命中时补全 affinity.generation,避免亲和终态写回被 CAS 拒绝。

affinityRoutingEnabled 开启但 affinityIgnoreClientSessionId 关闭时,findReusable(session) 会先选中供应商并设置 session.provider,导致 tryPrefixAffinityNomination(session) 被跳过,session.affinity.generation 保持 null。成功终态调用 recordAffinityWinner(session, providerId) 时,AffinityStore.put()expectedGeneration 为假值直接返回 false,tip 绑定不再随对话推进更新。无论复用是否已选定供应商,只要保留 session.affinity 且即将触发亲和写回,应在写前端补一次 generation 读取。同时建议在 AffinityStore.put()/tombstone() 返回 false 时记录 debug 日志,便于观测 CAS 拒绝。

🤖 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/provider-selector.ts` around lines 205 - 263, Update
provider selection in src/app/v1/_lib/proxy/provider-selector.ts:205-263 so
session reuse preserves affinity state and refreshes session.affinity.generation
before affinity winner write-back, even when findReusable sets session.provider
and skips tryPrefixAffinityNomination. Update
src/app/v1/_lib/proxy/affinity/affinity-recorder.ts:30-36 to record debug logs
whenever AffinityStore.put() or tombstone() returns false because of CAS
rejection.
🧹 Nitpick comments (10)
src/app/[locale]/dashboard/logs/_components/filters/status-filters.test.tsx (1)

10-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

将 hook mock 的导入路径改为 @/ 别名。

第 10 行使用了跨目录相对路径。请改用映射到 src/@/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options 路径。

建议修改
-vi.mock("../../_hooks/use-lazy-filter-options", () => ({
+vi.mock("`@/app/`[locale]/dashboard/logs/_hooks/use-lazy-filter-options", () => ({
🤖 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/logs/_components/filters/status-filters.test.tsx
around lines 10 - 16, Update the useLazyStatusCodes mock in the test to import
the hook through the
`@/app/`[locale]/dashboard/logs/_hooks/use-lazy-filter-options alias instead of
the relative ../../_hooks/use-lazy-filter-options path.

Source: Coding guidelines

src/lib/ledger-backfill/service.ts (1)

190-202: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

建议:让冲突更新列集合与触发器保持一致。

fn_upsert_usage_ledgerON CONFLICT 会同步 status_codeblocked_bycost_multipliersession_id 等列,这里只同步了部分列。结果是:某一行因 session_identity 差异被选入 batch 时,同一行上已存在的 status_codeblocked_by 漂移不会被修复,backfill 与触发器会产生不同的行状态。

建议把两处的更新列集合对齐,或从单一定义生成,避免后续新增列时再次出现遗漏。

🤖 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/ledger-backfill/service.ts` around lines 190 - 202, Update the ON
CONFLICT update clause in the ledger backfill upsert to match the column set
synchronized by fn_upsert_usage_ledger, including status_code, blocked_by,
cost_multiplier, session_id, and any other trigger-managed columns. Prefer
reusing a shared column definition if available so future additions cannot
diverge.
drizzle/0116_gigantic_zombie.sql (1)

30-113: 🚀 Performance & Scalability | 🔵 Trivial

提示:事务内回退路径会长时间持锁。

如果 preflight 未成功执行(marker 缺失),DO 块会在 Drizzle 迁移事务内以非 CONCURRENTLY 方式重建 9 个索引,随后第 93-113 行还会对 usage_ledgermessage_request 做全表关联更新。在大表部署上,这两步都会持有 ACCESS EXCLUSIVE / 行锁并阻塞写入,直到迁移事务提交。

建议在发布说明中标注该回退路径的预期停机时间,并在运维手册中说明先确认 preflight 成功(marker 存在)再执行升级。对于超大 usage_ledger,可考虑把第 93-113 行的回填改为迁移后的分批任务(复用 src/lib/ledger-backfill/service.ts 的分页逻辑),避免单事务长时间运行。

🤖 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_gigantic_zombie.sql` around lines 30 - 113, Document the
transactional fallback in the release notes and operations guidance, including
its expected downtime and the requirement to verify the migration marker before
upgrading. For the existing-row synchronization after the index block, consider
moving the full-table UPDATE into a post-migration batched backfill using the
pagination logic from ledger-backfill service, while preserving all identity,
replay-provenance, and cost normalization updates.
src/drizzle/schema.ts (1)

557-557: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

应用仓库的 Biome 格式。

共享根因是这些变更未遵守配置的双引号和 100 字符行宽规则。

  • src/drizzle/schema.ts#L557-L557: 将类型字面量改为双引号。
  • src/drizzle/schema.ts#L1179-L1179: 将类型字面量改为双引号。
  • tests/unit/drizzle/session-identity-indexes.test.ts#L20-L23: 将参数化测试拆分为不超过 100 字符的行。

As per coding guidelines, use Biome for code formatting with configuration: double quotes, trailing commas, 2-space indent, 100 character line width.

🤖 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/drizzle/schema.ts` at line 557, Apply the configured Biome formatting: in
src/drizzle/schema.ts lines 557-557 and 1179-1179, change the session identity
type literals to double quotes; in
tests/unit/drizzle/session-identity-indexes.test.ts lines 20-23, reformat the
parameterized test so each line stays within 100 characters while preserving
trailing commas and two-space indentation.

Source: Coding guidelines

src/app/api/v1/resources/sessions/handlers.ts (1)

42-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

考虑提取重复的查询解析逻辑。

5 个 handler 中都重复了相同的 SessionSequenceQuerySchema.safeParsefromZodError 判断代码块。可以提取一个共享的辅助函数,减少重复,便于后续统一维护查询参数校验逻辑。

♻️ 提取共享辅助函数的思路
+function parseSessionSequenceQuery(c: Context) {
+  return SessionSequenceQuerySchema.safeParse({
+    requestSequence: c.req.query("requestSequence"),
+    sourceSessionId: c.req.query("sourceSessionId"),
+  });
+}

各 handler 可改为调用 parseSessionSequenceQuery(c) 并在 !query.success 时统一返回 fromZodError(...)

Also applies to: 63-82, 84-102, 126-144, 146-163

🤖 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/api/v1/resources/sessions/handlers.ts` around lines 42 - 61, 提取共享的
parseSessionSequenceQuery 辅助函数,集中处理 requestSequence 和 sourceSessionId 的读取及
SessionSequenceQuerySchema.safeParse 校验;更新 getSessionDetail 及其余 4 个相关 handler
使用该辅助函数,并保留现有 query 失败时通过 fromZodError 返回错误响应的行为。
src/repository/message.ts (1)

1377-1387: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

resolveSessionIdentity 会加载 identity 下的全部请求行。

查询没有 limit,也没有去重。一个长期存在的 identity 可能对应成千上万条 message_request 记录。该函数只需要最新的 sessionIdscopeTagfingerprint 以及指纹集合。终止流程(src/actions/active-sessions.ts Line 1304)在批量循环内对每个 identity 调用一次,成本会叠加。

建议改为聚合查询,例如用 DISTINCT 取指纹集合,并用单独的 limit 1 查询取最新行。

🤖 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 1377 - 1387, 更新
resolveSessionIdentity 中的查询,避免加载 identity 对应的全部 message_request 行:通过单独的 limit 1
查询获取最新请求的 sessionId、scopeTag 和 fingerprint,并使用 DISTINCT 聚合获取 fingerprintChain
所需的指纹集合;保持现有 identity 与未删除请求的过滤条件不变。
src/actions/active-sessions.ts (1)

1301-1323: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

批量终止在循环内串行执行 DB 查询与动态导入。

循环对每个 identity 依次 await resolveSessionIdentity,并在命中 prefix 分支时重复 await import(...)。批量选中大量 Session 时,请求耗时随数量线性增长。

建议把 getAffinityStore 的导入提到循环外,并对各 identity 的处理做有上限的并发(例如分批 Promise.all)。

♻️ 建议的调整方向
     const { SessionTracker } = await import("`@/lib/session-tracker`");
+    const { getAffinityStore } = await import("`@/app/v1/_lib/proxy/affinity/affinity-store`");
     let successCount = 0;
     for (const identity of allowedSessionIds) {
       const resolution = await resolveSessionIdentity(identity);
       if (
         resolution?.identityKind === "prefix_affinity" &&
         resolution.scopeTag &&
         resolution.fingerprint
       ) {
-        const { getAffinityStore } = await import("`@/app/v1/_lib/proxy/affinity/affinity-store`");
         const invalidated = await getAffinityStore().invalidate(resolution.scopeTag, [
🤖 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/active-sessions.ts` around lines 1301 - 1323, 优化批量终止流程:将
getAffinityStore 的动态导入移到 allowedSessionIds 循环外并复用导入结果;重构每个 identity 的处理逻辑,使
resolveSessionIdentity、affinityStore.invalidate、SessionManager.terminateSession
及相关终止操作通过有上限的分批并发执行,避免无限制 Promise.all,同时保持 successCount 统计和 prefix_affinity
分支行为不变。
src/lib/session-tracker.ts (1)

795-809: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

建议提取共用的并发计数实现。

incrementObservedConcurrentCountincrementConcurrentCount 的逻辑相同,只有键前缀不同。decrementObservedConcurrentCountdecrementConcurrentCount 也是这样。请提取一个接受完整键的私有方法,让两组方法复用它。这样可以避免后续只修改一侧导致行为分叉。

同时建议把 increxpire 放入同一个 pipeline。若 expire 单独失败,该计数键会失去 TTL 并长期残留。

♻️ 建议的重构方向
+  private static async incrementCount(key: string): Promise<void> {
+    const redis = getRedisClient();
+    if (redis?.status !== "ready") return;
+    try {
+      const pipeline = redis.pipeline();
+      pipeline.incr(key);
+      pipeline.expire(key, 600);
+      await pipeline.exec();
+    } catch (error) {
+      logger.error("SessionTracker: Failed to increment concurrent count", { error, key });
+    }
+  }
+
   static async incrementObservedConcurrentCount(sessionIdentity: string): Promise<void> {
-    const redis = getRedisClient();
-    if (redis?.status !== "ready" || !sessionIdentity) return;
-
-    try {
-      const key = `observed_session:${sessionIdentity}:concurrent_count`;
-      await redis.incr(key);
-      await redis.expire(key, 600);
-    } catch (error) {
-      logger.error("SessionTracker: Failed to increment observed concurrent count", {
-        error,
-        sessionIdentity,
-      });
-    }
+    if (!sessionIdentity) return;
+    await SessionTracker.incrementCount(`observed_session:${sessionIdentity}:concurrent_count`);
   }

Also applies to: 837-851

🤖 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-tracker.ts` around lines 795 - 809, 提取一个接受完整 Redis key
的私有并发计数增减方法,让
incrementObservedConcurrentCount、incrementConcurrentCount、decrementObservedConcurrentCount
和 decrementConcurrentCount 复用同一实现,仅由调用方构造不同前缀的 key;在该共享方法中使用 Redis pipeline 将
incr/decr 与 expire 一起提交,并保留现有错误日志和输入校验行为。
src/app/v1/_lib/proxy/replay/replay-guard.ts (1)

99-109: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

审计写入阻塞了已完成条目的重放响应。

Line 102-108 和 Line 128-133 在返回 buildStaticResponse 之前 awaitwriteAuditRowwriteAuditRow 内部包含一次 INSERT,若携带 sourceRequestId 还会 await tryMaterializeAudit(一次 UPDATE)。这两次数据库往返都发生在客户端收到已缓存响应之前。

Replay 的设计目标是零成本、低延迟地重放已完成的响应。实时 attach 路径(buildLiveAttachResponse 调用 observeLiveAuditCompletion)已经采用了非阻塞方式记录审计。已完成条目的两条同步路径应保持一致,将审计写入改为 fire-and-forget,不阻塞响应返回。

♻️ 建议修复(以 redis_completed 分支为例,pg_completed 同理)
       if (meta.status === "completed") {
         const chunks = await store.readChunks(identity.replayId, 0);
         if (chunks && chunks.length > 0) {
-          await ProxyReplayGuard.writeAuditRow(
-            session,
-            identity,
-            meta.statusCode,
-            "redis_completed",
-            meta.messageRequestId
-          );
+          void ProxyReplayGuard.writeAuditRow(
+            session,
+            identity,
+            meta.statusCode,
+            "redis_completed",
+            meta.messageRequestId
+          ).catch((error) => {
+            logger.warn("[ReplayGuard] audit row write failed", {
+              error: error instanceof Error ? error.message : String(error),
+            });
+          });
           return ProxyReplayGuard.buildStaticResponse(meta, chunks.join(""));
         }

Also applies to: 125-134

🤖 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/replay/replay-guard.ts` around lines 99 - 109, Update
both completed replay branches in the replay guard, including the paths
returning buildStaticResponse for "redis_completed" and "pg_completed", so
writeAuditRow is triggered without awaiting it. Preserve the existing audit
arguments and return the cached response immediately, matching the non-blocking
behavior used by buildLiveAttachResponse and observeLiveAuditCompletion.
src/app/v1/_lib/proxy/affinity/affinity-recorder.ts (1)

30-36: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

记录 put()/tombstone() 的失败结果。

put()tombstone() 现在返回布尔值,表示 CAS 写入是否成功。当前代码忽略该返回值。当 affinity.generationnull 或已过期时,写入会静默失败,且没有任何日志。

建议在返回 false 时记录一条 debug 日志,包含 scopeTagproviderId,以便观测 CAS 拒绝的发生频率。这与 provider-selector.tssession.affinity.generation 在会话复用路径下始终为 null 的问题相关,此处是排查该问题的关键观测点。

Also applies to: 63-68

🤖 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/affinity/affinity-recorder.ts` around lines 30 - 36, 检查
affinity recorder 中调用 getAffinityStore().put() 和 tombstone() 的结果,并在返回 false 时记录
debug 日志;日志需包含 affinity.scopeTag 和 providerId。保留现有写入参数与流程,仅补充对 CAS 写入失败结果的观测。
🤖 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 `@src/actions/active-sessions.ts`:
- Around line 677-700: Update the existence-check branching in the session
request flow around resolveSessionRequestLocator to use the normalized locator
result rather than the raw requestSequence input. Align the condition with
getSessionDetails by consistently using locatorResult.locator.requestSequence,
while preserving the existing checks for specific-request messages and
any-session messages.
- Around line 25-33: 统一 activeSessions 的身份信息来源:修改 getAllSessions 及其聚合/缓存流程,不要仅按
pfx: 前缀过滤或推导身份,而应保留并传递数据库提供的 sessionIdentityKind 与
sessionFingerprint;列表映射直接使用这些字段,确保与 messageRequest.sessionIdentityKind 和
resolveSessionIdentity 的终止路径一致。

In `@src/app/`[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx:
- Line 172: Update hasStatsFilters in the usage logs view so replayFilter ===
"all" is treated as no filter and does not trigger the statistics panel;
preserve the existing activeFilterCount behavior and all other filter
conditions.

In `@src/app/v1/_lib/proxy-handler.ts`:
- Around line 26-44: 在 trackObservedSession 中为
SessionTracker.trackObservedSession 和 SessionManager.storeSessionInfo 两个
fire-and-forget 调用追加 .catch 错误处理,确保 Promise 拒绝被捕获并按 response-handler.ts
中的既有模式处理。

In `@src/app/v1/_lib/proxy/response-handler.ts`:
- Around line 6515-6521: 在 response handler 的
SessionTracker.refreshObservedSession 调用前校验
session.getSessionIdentityMetadata().identity 非空;仅当 identity 有效时才刷新 observed
session,避免无 Session ID 或 Prefix Affinity 身份的请求使用空字符串键。保持现有错误捕获与日志行为不变。

In `@src/lib/config/system-settings-cache.ts`:
- Around line 57-63: Update getFallbackStreamGateMode so its catch path returns
"enforce" instead of "off" when getEnvConfig() fails, preserving the PR’s
default Stream Gate behavior while leaving successful configuration reads
unchanged.

In `@src/lib/migrations/session-replay-index-preflight.ts`:
- Around line 96-104: Update runSessionReplayIndexPreflight in
src/lib/migrations/session-replay-index-preflight.ts at lines 96-104 to check
whether both message_request and usage_ledger exist before calling
ensurePreflightColumns, returning immediately if either is missing. Update
src/lib/migrate.ts at lines 212-215 to implement the required
MigrationIndexPreflightExecutor table-existence query using to_regclass, while
keeping the preflight invocation before migrate().

In `@src/repository/_shared/ledger-conditions.ts`:
- Line 20: Separate the billing filter from session existence and ownership
checks: keep LEDGER_BILLING_CONDITION with isReplay = false for billing, quota,
dashboard, and operational aggregates, but update aggregateSessionStats and
aggregateMultipleSessionStats or their session-access callers to use a condition
that includes replay ledger rows for getSessionMessages, getSessionDetails,
getSessionRequests, terminateActiveSession, terminateActiveSessionsBatch,
getSessionOriginChain, and getSessionResponse.

In `@src/repository/message.ts`:
- Around line 320-366: Update the WHERE clause in
materializeReplayAuditFromSource to require both replay and source records to
have deleted_at IS NULL, require source.is_replay = FALSE, and require
replayRequestId and sourceRequestId to differ; preserve the existing status and
error filters and return behavior.

In `@src/types/message.ts`:
- Around line 290-296: 在 MessageRequest 接口中补充仓储层 returning 已返回的 isReplay 与
replaySourceRequestId 字段,使调用方能够读取 Replay 标记和来源请求 ID;保持与 CreateMessageRequestData
中 is_replay、replay_source_request_id 的类型和可空性对应。

---

Outside diff comments:
In `@src/app/v1/_lib/proxy/provider-selector.ts`:
- Around line 205-263: Update provider selection in
src/app/v1/_lib/proxy/provider-selector.ts:205-263 so session reuse preserves
affinity state and refreshes session.affinity.generation before affinity winner
write-back, even when findReusable sets session.provider and skips
tryPrefixAffinityNomination. Update
src/app/v1/_lib/proxy/affinity/affinity-recorder.ts:30-36 to record debug logs
whenever AffinityStore.put() or tombstone() returns false because of CAS
rejection.

---

Nitpick comments:
In `@drizzle/0116_gigantic_zombie.sql`:
- Around line 30-113: Document the transactional fallback in the release notes
and operations guidance, including its expected downtime and the requirement to
verify the migration marker before upgrading. For the existing-row
synchronization after the index block, consider moving the full-table UPDATE
into a post-migration batched backfill using the pagination logic from
ledger-backfill service, while preserving all identity, replay-provenance, and
cost normalization updates.

In `@src/actions/active-sessions.ts`:
- Around line 1301-1323: 优化批量终止流程:将 getAffinityStore 的动态导入移到 allowedSessionIds
循环外并复用导入结果;重构每个 identity 的处理逻辑,使
resolveSessionIdentity、affinityStore.invalidate、SessionManager.terminateSession
及相关终止操作通过有上限的分批并发执行,避免无限制 Promise.all,同时保持 successCount 统计和 prefix_affinity
分支行为不变。

In `@src/app/`[locale]/dashboard/logs/_components/filters/status-filters.test.tsx:
- Around line 10-16: Update the useLazyStatusCodes mock in the test to import
the hook through the
`@/app/`[locale]/dashboard/logs/_hooks/use-lazy-filter-options alias instead of
the relative ../../_hooks/use-lazy-filter-options path.

In `@src/app/api/v1/resources/sessions/handlers.ts`:
- Around line 42-61: 提取共享的 parseSessionSequenceQuery 辅助函数,集中处理 requestSequence 和
sourceSessionId 的读取及 SessionSequenceQuerySchema.safeParse 校验;更新 getSessionDetail
及其余 4 个相关 handler 使用该辅助函数,并保留现有 query 失败时通过 fromZodError 返回错误响应的行为。

In `@src/app/v1/_lib/proxy/affinity/affinity-recorder.ts`:
- Around line 30-36: 检查 affinity recorder 中调用 getAffinityStore().put() 和
tombstone() 的结果,并在返回 false 时记录 debug 日志;日志需包含 affinity.scopeTag 和
providerId。保留现有写入参数与流程,仅补充对 CAS 写入失败结果的观测。

In `@src/app/v1/_lib/proxy/replay/replay-guard.ts`:
- Around line 99-109: Update both completed replay branches in the replay guard,
including the paths returning buildStaticResponse for "redis_completed" and
"pg_completed", so writeAuditRow is triggered without awaiting it. Preserve the
existing audit arguments and return the cached response immediately, matching
the non-blocking behavior used by buildLiveAttachResponse and
observeLiveAuditCompletion.

In `@src/drizzle/schema.ts`:
- Line 557: Apply the configured Biome formatting: in src/drizzle/schema.ts
lines 557-557 and 1179-1179, change the session identity type literals to double
quotes; in tests/unit/drizzle/session-identity-indexes.test.ts lines 20-23,
reformat the parameterized test so each line stays within 100 characters while
preserving trailing commas and two-space indentation.

In `@src/lib/ledger-backfill/service.ts`:
- Around line 190-202: Update the ON CONFLICT update clause in the ledger
backfill upsert to match the column set synchronized by fn_upsert_usage_ledger,
including status_code, blocked_by, cost_multiplier, session_id, and any other
trigger-managed columns. Prefer reusing a shared column definition if available
so future additions cannot diverge.

In `@src/lib/session-tracker.ts`:
- Around line 795-809: 提取一个接受完整 Redis key 的私有并发计数增减方法,让
incrementObservedConcurrentCount、incrementConcurrentCount、decrementObservedConcurrentCount
和 decrementConcurrentCount 复用同一实现,仅由调用方构造不同前缀的 key;在该共享方法中使用 Redis pipeline 将
incr/decr 与 expire 一起提交,并保留现有错误日志和输入校验行为。

In `@src/repository/message.ts`:
- Around line 1377-1387: 更新 resolveSessionIdentity 中的查询,避免加载 identity 对应的全部
message_request 行:通过单独的 limit 1 查询获取最新请求的 sessionId、scopeTag 和 fingerprint,并使用
DISTINCT 聚合获取 fingerprintChain 所需的指纹集合;保持现有 identity 与未删除请求的过滤条件不变。
🪄 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: 49f53ecd-66d3-4000-8edb-6cdb15ff5459

📥 Commits

Reviewing files that changed from the base of the PR and between c7174f6 and 101c215.

📒 Files selected for processing (128)
  • drizzle/0116_gigantic_zombie.sql
  • drizzle/meta/0116_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/en/errors.json
  • messages/ja/dashboard.json
  • messages/ja/errors.json
  • messages/ru/dashboard.json
  • messages/ru/errors.json
  • messages/zh-CN/dashboard.json
  • messages/zh-CN/errors.json
  • messages/zh-TW/dashboard.json
  • messages/zh-TW/errors.json
  • src/actions/active-sessions.ts
  • src/actions/concurrent-sessions.ts
  • src/actions/session-origin-chain.ts
  • src/actions/session-response.ts
  • src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.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/filters/active-filters-display.tsx
  • src/app/[locale]/dashboard/logs/_components/filters/status-filters.test.tsx
  • src/app/[locale]/dashboard/logs/_components/filters/status-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/filters/types.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.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]/dashboard/logs/_utils/logs-query.test.ts
  • src/app/[locale]/dashboard/logs/_utils/logs-query.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx
  • src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx
  • src/app/api/v1/resources/sessions/handlers.ts
  • src/app/api/v1/resources/sessions/router.ts
  • src/app/api/v1/resources/usage-logs/handlers.ts
  • src/app/v1/_lib/proxy-handler.ts
  • src/app/v1/_lib/proxy/affinity/affinity-recorder.ts
  • src/app/v1/_lib/proxy/affinity/affinity-store.ts
  • src/app/v1/_lib/proxy/message-service.test.ts
  • src/app/v1/_lib/proxy/message-service.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/replay/replay-guard.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/session-guard.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/drizzle/schema.ts
  • src/lib/api-client/v1/actions/active-sessions.ts
  • src/lib/api-client/v1/actions/session-origin-chain.ts
  • src/lib/api-client/v1/actions/session-response.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/sessions.ts
  • src/lib/api/v1/schemas/usage-logs.ts
  • src/lib/availability/availability-service.ts
  • src/lib/config/env.schema.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/ledger-backfill/service.ts
  • src/lib/ledger-backfill/trigger.sql
  • src/lib/migrate.ts
  • src/lib/migrations/session-replay-index-preflight.ts
  • src/lib/proxy-status-tracker.ts
  • src/lib/redis/active-session-keys.ts
  • src/lib/request-identity.ts
  • src/lib/session-request-locator.ts
  • src/lib/session-tracker.ts
  • src/lib/utils/error-messages.ts
  • src/repository/_shared/ledger-conditions.ts
  • src/repository/_shared/usage-log-filters.ts
  • src/repository/cache-hit-rate-alert.ts
  • src/repository/key.ts
  • src/repository/message.ts
  • src/repository/provider.ts
  • src/repository/usage-logs.ts
  • src/types/message.ts
  • src/types/session.ts
  • tests/api/v1/sessions/sessions.test.ts
  • tests/api/v1/usage-logs/usage-logs.test.ts
  • tests/integration/ledger-consistency.test.ts
  • tests/integration/usage-ledger.test.ts
  • tests/unit/actions/active-sessions-detail-snapshots.test.ts
  • tests/unit/actions/active-sessions-requests.test.ts
  • tests/unit/actions/active-sessions-special-settings.test.ts
  • tests/unit/actions/active-sessions-termination.test.ts
  • tests/unit/actions/session-origin-chain-integration.test.ts
  • tests/unit/actions/session-origin-chain.test.ts
  • tests/unit/actions/session-response.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts
  • tests/unit/drizzle/session-identity-indexes.test.ts
  • tests/unit/drizzle/session-replay-migration.test.ts
  • tests/unit/drizzle/usage-ledger-cost-indexes.test.ts
  • tests/unit/i18n/session-request-errors.test.ts
  • tests/unit/lib/availability-service.test.ts
  • tests/unit/lib/cache-effectiveness-gate.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • tests/unit/lib/env-stream-gate-mode.test.ts
  • tests/unit/lib/proxy-status-tracker.test.ts
  • tests/unit/lib/session-replay-index-preflight.test.ts
  • tests/unit/lib/session-request-locator.test.ts
  • tests/unit/lib/session-tracker-cleanup.test.ts
  • tests/unit/proxy/affinity-recorder.test.ts
  • tests/unit/proxy/affinity-store.test.ts
  • tests/unit/proxy/connected-non-reader-lifetime.test.ts
  • tests/unit/proxy/hedge-error-pipeline.test.ts
  • tests/unit/proxy/provider-selector-affinity-ignore-session.test.ts
  • tests/unit/proxy/provider-selector-affinity-priority.test.ts
  • tests/unit/proxy/provider-selector-select-provider-by-type.test.ts
  • tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts
  • tests/unit/proxy/proxy-handler-concurrency-ownership.test.ts
  • tests/unit/proxy/proxy-handler-public-errors.test.ts
  • tests/unit/proxy/proxy-handler-session-id-error.test.ts
  • tests/unit/proxy/replay-guard.test.ts
  • tests/unit/repository/cache-hit-rate-alert-integer-cast.test.ts
  • tests/unit/repository/message-aggregate-session-stats.test.ts
  • tests/unit/repository/message-replay-audit-terminal.test.ts
  • tests/unit/repository/message-session-readback.test.ts
  • tests/unit/repository/message-session-request-query.test.ts
  • tests/unit/repository/usage-logs-replay-filter.test.ts
  • tests/unit/repository/usage-logs-replay-projection.test.ts
  • tests/unit/repository/warmup-stats-exclusion.test.ts
  • tests/unit/usage-ledger/backfill.test.ts
  • tests/unit/usage-ledger/trigger.test.ts

Comment thread src/actions/active-sessions.ts Outdated
Comment thread src/actions/active-sessions.ts
Comment thread src/app/v1/_lib/proxy-handler.ts
Comment thread src/app/v1/_lib/proxy/response-handler.ts
Comment thread src/lib/config/system-settings-cache.ts
Comment thread src/lib/migrations/session-replay-index-preflight.ts
Comment thread src/repository/_shared/ledger-conditions.ts
Comment thread src/repository/message.ts
Comment thread src/types/message.ts
Comment thread src/lib/config/system-settings-cache.ts Outdated
Comment thread src/repository/message.ts Outdated

@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 XL PR introduces substantial new session/replay semantics, but I found two regressions in the newly added plumbing that should be fixed before merge. One silently disables stream gating during a cold-cache fallback, and the other rewrites replay audit rows into the source session identity, which can misroute session detail and termination flows.

PR Size: XL

  • Lines changed: 10463
  • Files changed: 128
  • Split suggestion: Extract replay audit/materialization + usage-log projection into one PR and active-session/prefix-identity aggregation + session management changes into another PR.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 1 0 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)

  • src/lib/config/system-settings-cache.ts:61 — cold-cache fallback catches environment parse failures and silently downgrades streamGateMode to off, which hides config errors and changes proxy behavior without any log signal.

High Priority Issues (Should Fix)

  • src/repository/message.ts:344 — replay audit materialization overwrites the replay row’s session_identity and affinity metadata with the source request’s values, which can make later replay rows hijack default session detail / locator / termination resolution.

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.

  • Reviewed PR #1372, applied the size/XL label, and submitted the required review summary on the PR.
  • Posted a critical inline comment on src/lib/config/system-settings-cache.ts:61 for a silent fallback that can downgrade streamGateMode to off on a cold-cache settings miss when any unrelated env parse fails.
  • Posted a high-priority inline comment on src/repository/message.ts:344 where replay audit materialization rewrites session_identity/affinity metadata to the source request, which can misroute later session detail and termination lookups.
  • Summary report includes the XL split suggestion to separate replay-audit changes from active-session/prefix-identity aggregation changes.
  • No other findings cleared the reporting threshold after validation against surrounding code and tests.

If you want, I can also draft the concrete code fixes for the two flagged issues.

@ding113

ding113 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

@ding113 I will review pull request #1372.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@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: 4

Caution

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

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

1608-1621: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

统一批量会话聚合的定位条件。

aggregateSessionStats 只用 inArray(ledgerSessionIdentity, sessionIds),只匹配前缀粘附的 observed identity;但 getAllSessions 传入的 SessionManager.getAllSessionIds() 还会包含物理 session_id。把这些物理 ID 传入后,统计、provider、model、cacheTtl 查询都匹配不到,同一条物理 session 在单会话查询中却可能通过 ledgerSessionLookup 返回。批量版本应对每个 sessionId 使用与单会话相同的 ledgerSessionLookup,或改造查询条件为 or(...)

🤖 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 1608 - 1621, 统一 aggregateSessionStats
的会话定位逻辑:不要仅使用 inArray(ledgerSessionIdentity, sessionIds),而应为每个 sessionId 通过
ledgerSessionLookup 解析并构造对应的 OR 条件,使传入的物理 session_id 与前缀粘附的 observed identity
都能匹配。确保统计及相关批量查询沿用单会话查询的定位行为。
🧹 Nitpick comments (2)
src/actions/active-sessions.ts (1)

1310-1316: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

批量终止改为串行执行,可考虑限制并发度。

每个 identity 都要串行执行 resolveSessionIdentity 查询与 Redis 终止操作。批量选中数量较大时,总耗时随 identity 数量线性增长。建议按固定块大小并发处理,与 SessionManager.terminateSessionsBatch 的分块策略保持一致。

♻️ 建议的分块并发写法
     let successCount = 0;
-    for (const identity of allowedSessionIds) {
-      const resolution = await resolveSessionIdentity(identity);
-      if (await terminateResolvedSessionIdentity(identity, resolution)) {
-        successCount += 1;
-      }
-    }
+    const CHUNK_SIZE = 20;
+    for (let i = 0; i < allowedSessionIds.length; i += CHUNK_SIZE) {
+      const chunk = allowedSessionIds.slice(i, i + CHUNK_SIZE);
+      const outcomes = await Promise.all(
+        chunk.map(async (identity) => {
+          const resolution = await resolveSessionIdentity(identity);
+          return terminateResolvedSessionIdentity(identity, resolution);
+        })
+      );
+      successCount += outcomes.filter(Boolean).length;
+    }
🤖 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/active-sessions.ts` around lines 1310 - 1316, Update the batch
termination loop around resolveSessionIdentity and
terminateResolvedSessionIdentity to process allowedSessionIds in fixed-size
concurrent chunks, matching the chunking strategy used by
SessionManager.terminateSessionsBatch. Await each chunk before starting the
next, preserve successCount semantics, and retain per-identity resolution
followed by termination.
src/app/v1/_lib/proxy/session-guard.ts (1)

184-191: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

复用单次 affinity lookup 结果,避免同一请求执行两次 Redis lookup。

session-guard 创建 session.affinity 后先调用 getAffinityStore().lookup,随后 ProxyProviderResolverlookupAffinityState 会再次用相同 scopeTag 和指纹链调用同一 AffinityStore.lookup。lookup 命中活跃绑定时会写 generation、迁移旧值和刷新绑定 TTL;未命中也会生成并写入 fresh generation。建议只在会话 identity 构建时做一次 lookup,将 identityFpgeneration(以及需要的 affinity result)缓存在 session.affinity 上,后续选择/写回路径直接复用。

🤖 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/session-guard.ts` around lines 184 - 191, 在
session.affinity 创建流程中复用已有的 getAffinityStore().lookup 结果,缓存完整 affinity result 及其
identityFp、generation;更新
ProxyProviderResolver.lookupAffinityState,使后续选择和写回路径优先读取 session.affinity
的缓存,避免使用相同 scopeTag 与指纹链再次执行 AffinityStore.lookup,同时保留现有命中、迁移、TTL 刷新和未命中生成
generation 的行为。
🤖 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 `@src/actions/active-sessions.ts`:
- Around line 62-72: Update the physical-session termination loop around
SessionManager.terminateSession so sources with empty providerIds are not
silently skipped; invoke termination using the supported unscoped behavior and
preserve cleanup of session info, concurrency indexes, and provider bindings.
Ensure the invalidate path only reports success according to the actual
termination result, or document the intentional
affinity-invalidated-as-terminated behavior if that is the established contract.

In `@src/app/`[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx:
- Around line 107-117: Remove the hardcoded “Replay source” text from the
ErrorDetailsDialog mocks and expose replaySourceRequestId through a
data-replay-source-request-id attribute instead. Update the related assertions
in src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
lines 107-117 and
src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx lines
48-58 to verify that attribute while retaining the existing isReplay validation.

In `@src/lib/session-manager.ts`:
- Around line 2924-2931: Review the ownership check in the session termination
flow around expectedKeyId and distinguish a missing owner mirror (keyId is null)
from an actual owner change. Ensure expired owner keys do not prevent cleanup of
stale sessions when the intended semantics allow it, while still rejecting
mismatched non-null owners; if fail-closed behavior is intentional, document
that distinction explicitly at this check.

In `@src/repository/message.ts`:
- Around line 1447-1499: Update listPhysicalSessionSourcesForIdentity and its
sourcesBySession aggregation so a physical session cannot silently retain the
first row’s userId/keyId when records span multiple keys. Confirm and enforce
the one-to-one session/key relationship, or group results by the (sessionId,
keyId) pair so each returned PhysicalSessionSource carries the matching key
ownership data while still aggregating providerIds.

---

Outside diff comments:
In `@src/repository/message.ts`:
- Around line 1608-1621: 统一 aggregateSessionStats 的会话定位逻辑:不要仅使用
inArray(ledgerSessionIdentity, sessionIds),而应为每个 sessionId 通过
ledgerSessionLookup 解析并构造对应的 OR 条件,使传入的物理 session_id 与前缀粘附的 observed identity
都能匹配。确保统计及相关批量查询沿用单会话查询的定位行为。

---

Nitpick comments:
In `@src/actions/active-sessions.ts`:
- Around line 1310-1316: Update the batch termination loop around
resolveSessionIdentity and terminateResolvedSessionIdentity to process
allowedSessionIds in fixed-size concurrent chunks, matching the chunking
strategy used by SessionManager.terminateSessionsBatch. Await each chunk before
starting the next, preserve successCount semantics, and retain per-identity
resolution followed by termination.

In `@src/app/v1/_lib/proxy/session-guard.ts`:
- Around line 184-191: 在 session.affinity 创建流程中复用已有的 getAffinityStore().lookup
结果,缓存完整 affinity result 及其 identityFp、generation;更新
ProxyProviderResolver.lookupAffinityState,使后续选择和写回路径优先读取 session.affinity
的缓存,避免使用相同 scopeTag 与指纹链再次执行 AffinityStore.lookup,同时保留现有命中、迁移、TTL 刷新和未命中生成
generation 的行为。
🪄 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: beac7130-0e0f-4a5b-987f-73ff9a3b8b0a

📥 Commits

Reviewing files that changed from the base of the PR and between 101c215 and 88cf76d.

📒 Files selected for processing (63)
  • drizzle/0116_gigantic_zombie.sql
  • package.json
  • scripts/migrate.ts
  • src/actions/active-sessions.ts
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/MetadataTab.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/usage-logs-view-virtualized.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/v1/_lib/proxy-handler.ts
  • src/app/v1/_lib/proxy/affinity/affinity-recorder.ts
  • src/app/v1/_lib/proxy/affinity/affinity-store.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/replay/replay-guard.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/session-guard.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/cache/session-cache.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/ledger-backfill/service.ts
  • src/lib/migrate.ts
  • src/lib/migrations/session-replay-index-preflight.ts
  • src/lib/rate-limit/service.ts
  • src/lib/redis/lua-scripts.ts
  • src/lib/session-manager.ts
  • src/repository/_shared/ledger-conditions.ts
  • src/repository/_shared/transformers.test.ts
  • src/repository/_shared/transformers.ts
  • src/repository/activity-stream.ts
  • src/repository/message.ts
  • src/repository/usage-logs.ts
  • src/types/message.ts
  • tests/unit/actions/active-sessions-detail-snapshots.test.ts
  • tests/unit/actions/active-sessions-termination.test.ts
  • tests/unit/drizzle/session-replay-migration.test.ts
  • tests/unit/lib/cache-effectiveness-gate.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • tests/unit/lib/rate-limit/provider-session-release.test.ts
  • tests/unit/lib/session-replay-index-preflight.test.ts
  • tests/unit/proxy/affinity-recorder.test.ts
  • tests/unit/proxy/affinity-store.test.ts
  • tests/unit/proxy/provider-selector-affinity-ignore-session.test.ts
  • tests/unit/proxy/provider-selector-affinity-priority.test.ts
  • tests/unit/proxy/proxy-handler-concurrency-ownership.test.ts
  • tests/unit/proxy/replay-guard.test.ts
  • tests/unit/proxy/response-handler-lease-decrement.test.ts
  • tests/unit/proxy/session-guard-warmup-intercept.test.ts
  • tests/unit/repository/activity-stream-replay.test.ts
  • tests/unit/repository/message-aggregate-multiple-session-stats.test.ts
  • tests/unit/repository/message-aggregate-session-stats.test.ts
  • tests/unit/repository/message-public-readback.test.ts
  • tests/unit/repository/message-replay-audit-terminal.test.ts
  • tests/unit/repository/message-session-readback.test.ts
  • tests/unit/repository/message-session-request-query.test.ts
  • tests/unit/repository/usage-logs-actual-response-model.test.ts
  • tests/unit/usage-ledger/backfill.test.ts
💤 Files with no reviewable changes (1)
  • drizzle/0116_gigantic_zombie.sql
🚧 Files skipped from review as they are similar to previous changes (20)
  • tests/unit/lib/cache-effectiveness-gate.test.ts
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/types.ts
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/v1/_lib/proxy-handler.ts
  • src/app/v1/_lib/proxy/affinity/affinity-recorder.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • tests/unit/repository/message-replay-audit-terminal.test.ts
  • tests/unit/drizzle/session-replay-migration.test.ts
  • src/lib/ledger-backfill/service.ts
  • tests/unit/proxy/provider-selector-affinity-ignore-session.test.ts
  • src/types/message.ts
  • tests/unit/usage-ledger/backfill.test.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.test.tsx
  • src/app/v1/_lib/proxy/session.ts
  • tests/unit/actions/active-sessions-termination.test.ts
  • tests/unit/actions/active-sessions-detail-snapshots.test.ts
  • src/app/v1/_lib/proxy/replay/replay-guard.ts
  • tests/unit/proxy/replay-guard.test.ts
  • tests/unit/proxy/affinity-store.test.ts
  • src/repository/usage-logs.ts

Comment thread src/actions/active-sessions.ts
Comment thread src/lib/session-manager.ts
Comment thread src/repository/message.ts

@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: 88cf76db09

ℹ️ 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 thread src/repository/usage-logs.ts
Comment thread src/repository/message.ts
Comment thread src/actions/active-sessions.ts Outdated
Comment thread src/app/v1/_lib/proxy/affinity/affinity-store.ts Outdated
Comment thread src/lib/session-request-locator.ts Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@ding113

ding113 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

第二轮 CodeRabbit review body 中的 3 项建议均已在 f653980 完成:

  • physical Session aggregate 保留 physical alias compatibility,同时严格优先 exact public identity,避免同名 identity 冲突。
  • batch termination 按每 20 项分块,并使用 Promise.allSettled 隔离单项异常,确保成功/失败统计与 cache cleanup 继续执行。
  • 同一请求复用已完成的 affinity lookup,避免 guard 链重复读取并产生不一致结果。

相关聚焦测试、全量 bun run testbun run typecheckbun run lint、OpenAPI 检查和 production build 均已通过。

@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: 1

🧹 Nitpick comments (1)
src/repository/message.ts (1)

1722-1741: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

移除永不触发的分支,简化 requestedSessionIds 归并。

Line 1722 已确保 userInfoMap 一定包含 row.session_id。因此 Line 1736 的 continue 永不触发,Line 1737-1740 只在同一 canonical identity 命中多个请求 ID 时生效。建议合并为 else 分支,避免读者误认为存在早退路径。

♻️ 建议的简化
     canonicalByRequested.set(row.requested_session_id, row.session_id);
-    if (!userInfoMap.has(row.session_id)) {
-      userInfoMap.set(row.session_id, {
+    const existing = userInfoMap.get(row.session_id);
+    if (!existing) {
+      userInfoMap.set(row.session_id, {
         sessionId: row.session_id,
         requestedSessionIds: [row.requested_session_id],
         userName: row.user_name,
         userId: row.user_id,
         keyName: row.key_name,
         keyId: row.key_id,
         sessionIdentityKind: row.session_identity_kind,
         sessionFingerprint: row.session_fingerprint,
         userAgent: row.user_agent,
         apiType: row.api_type,
       });
-    }
-    if (!userInfoMap.has(row.session_id)) continue;
-    const requestedIds = userInfoMap.get(row.session_id)?.requestedSessionIds;
-    if (requestedIds && !requestedIds.includes(row.requested_session_id)) {
-      requestedIds.push(row.requested_session_id);
+    } else if (!existing.requestedSessionIds.includes(row.requested_session_id)) {
+      existing.requestedSessionIds.push(row.requested_session_id);
     }
🤖 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 1722 - 1741, 在处理 userInfoMap
的循环中,移除紧随初始化逻辑之后针对 userInfoMap.has(row.session_id) 的无效 continue 分支,并将
requestedSessionIds 的获取与追加逻辑改为对应的 else 分支;保留首次创建记录时的初始化行为,以及同一 session_id 合并不同
requested_session_id 的行为。
🤖 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 `@src/repository/message.ts`:
- Around line 2295-2335: 在 findAdjacentSessionRequests 的 current 查询和
timelineFilter,以及 findRequestsBySessionIdentity 查询中加入对 isReplay 的过滤,仅保留 isReplay
为 false 的请求。确保运营 Session 时间线不会返回或跳转到 Replay 记录,并保持现有非 Replay 请求的排序与邻接逻辑不变。

---

Nitpick comments:
In `@src/repository/message.ts`:
- Around line 1722-1741: 在处理 userInfoMap 的循环中,移除紧随初始化逻辑之后针对
userInfoMap.has(row.session_id) 的无效 continue 分支,并将 requestedSessionIds
的获取与追加逻辑改为对应的 else 分支;保留首次创建记录时的初始化行为,以及同一 session_id 合并不同 requested_session_id
的行为。
🪄 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: cb309ad0-40f7-4e19-bf5c-f4c0b69708a6

📥 Commits

Reviewing files that changed from the base of the PR and between 88cf76d and f653980.

📒 Files selected for processing (33)
  • src/actions/active-sessions-utils.ts
  • src/actions/active-sessions.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/api/v1/resources/sessions/handlers.ts
  • src/app/api/v1/resources/sessions/router.ts
  • src/app/v1/_lib/proxy/affinity/affinity-store.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/session-guard.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/api-client/v1/actions/active-sessions.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/sessions.ts
  • src/lib/session-request-locator.ts
  • src/repository/_shared/usage-log-filters.ts
  • src/repository/message.ts
  • tests/api/v1/sessions/sessions.test.ts
  • tests/unit/actions/active-sessions-detail-snapshots.test.ts
  • tests/unit/actions/active-sessions-special-settings.test.ts
  • tests/unit/actions/active-sessions-termination.test.ts
  • tests/unit/api/v1/api-client-actions.test.ts
  • tests/unit/lib/cache-effectiveness-gate.test.ts
  • tests/unit/lib/session-request-locator.test.ts
  • tests/unit/proxy/affinity-recorder.test.ts
  • tests/unit/proxy/affinity-store.test.ts
  • tests/unit/proxy/provider-selector-affinity-ignore-session.test.ts
  • tests/unit/repository/message-aggregate-multiple-session-stats.test.ts
  • tests/unit/repository/message-session-request-query.test.ts
  • tests/unit/repository/usage-logs-sessionid-filter.test.ts
  • tests/unit/terminate-active-sessions-batch.test.ts
🚧 Files skipped from review as they are similar to previous changes (17)
  • tests/unit/lib/session-request-locator.test.ts
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • tests/unit/api/v1/api-client-actions.test.ts
  • src/app/api/v1/resources/sessions/handlers.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • tests/unit/repository/message-session-request-query.test.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/api/v1/schemas/sessions.ts
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.test.tsx
  • tests/api/v1/sessions/sessions.test.ts
  • tests/unit/proxy/affinity-recorder.test.ts
  • src/app/v1/_lib/proxy/session-guard.ts
  • tests/unit/proxy/affinity-store.test.ts
  • src/app/v1/_lib/proxy/affinity/affinity-store.ts
  • tests/unit/actions/active-sessions-detail-snapshots.test.ts

Comment thread src/repository/message.ts

@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: f653980221

ℹ️ 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 thread src/actions/active-sessions.ts
@ding113

ding113 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

最新 CodeRabbit review body 的可读性建议也已在 55302773 完成:aggregateMultipleSessionStatsuserInfoMap 归并改为明确的 existing / else if 分支,移除了初始化后永不触发的 continue,同时保持首次创建和多 requested alias 去重语义不变。相关 aggregate 与 request-query 聚焦测试共 14/14 通过。

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@ding113
ding113 merged commit 216855c into dev Aug 1, 2026
17 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Aug 1, 2026

@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: 55302773c0

ℹ️ 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 +185 to +189
const lookup = await getAffinityStore().lookup(
session.affinity.scopeTag,
fingerprintsDeepestFirst(chain),
getEnvConfig().PREFIX_AFFINITY_TTL_SECONDS
);

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 Avoid refreshing affinity bindings for replay hits

When affinityIgnoreClientSessionId is enabled and a duplicate request is served from replay, CHAT_PIPELINE executes this session guard before replayAttach. This call is not read-only: AffinityStore.lookup() runs VALIDATE_LOOKUP_HIT_LUA, which extends the binding TTL and refreshes its descendant registry. Repeated cache-only requests can therefore keep a provider affinity alive indefinitely even though no request is routed upstream, causing a later real request to remain pinned beyond PREFIX_AFFINITY_TTL_SECONDS. Resolve the identity with a non-touch lookup here, or defer the sliding-TTL refresh until the replay guard misses.

Useful? React with 👍 / 👎.

Comment thread src/repository/message.ts
Comment on lines +339 to +341
UPDATE message_request AS replay
SET
provider_id = source.provider_id,

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 Preserve the source identity when materializing replay audits

When a replay survives longer than its prefix-affinity binding, or Redis loses that binding while the PostgreSQL replay payload remains valid, the replay request is initially assigned a new tip-rooted sessionIdentity. This source materialization copies usage fields and provenance but leaves that inferred identity unchanged, so the replay appears under a different public session from the request it actually replays and is omitted when logs are filtered by the source identity. Copy the source row's session_identity, identity kind, scope tag, and affinity fingerprint metadata during this update.

Useful? React with 👍 / 👎.

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