Skip to content

test(session): add multi-backend replay consistency harness#2268

Open
wei-yan1 wants to merge 5 commits into
trpc-group:mainfrom
wei-yan1:codex/issue-2001-replay-consistency
Open

test(session): add multi-backend replay consistency harness#2268
wei-yan1 wants to merge 5 commits into
trpc-group:mainfrom
wei-yan1:codex/issue-2001-replay-consistency

Conversation

@wei-yan1

Copy link
Copy Markdown

What changed

  • Add a reusable backend-independent replay consistency framework under session/replaytest.
  • Add 12 public replay cases covering:
    • single-turn and multi-turn conversations;
    • tool calls, tool responses, arguments, extensions, branches, and tags;
    • session, app, and user state updates, overwrites, deletions, and clears;
    • memory write, search, update, and delete flows;
    • Summary generation, overwrite, ownership, filter-key, and truncation boundaries;
    • Track events, invocation status, and error payloads;
    • concurrent causal ordering;
    • retry and duplicate identity recovery.
  • Add deterministic normalization and comparison for generated IDs, timestamps, JSON/map ordering, state bytes, memory ranking, and backend-specific metadata.
  • Add logical identity tracking for events, invocations, tool calls, and memories to preserve duplicate semantic records.
  • Add strict field-level diffs with session ID, event index, memory ID, Summary ID/filter-key, and track-name locators.
  • Add exact-path allowed_diff handling and explicit unsupported capability reporting.
  • Add 15 deterministic fault injections, including all required Summary failure modes.
  • Add default InMemory + SQLite replay coverage.
  • Add optional Redis, PostgreSQL, MySQL, and ClickHouse integration through environment variables.
  • Add the deterministic sample report test/testdata/session_memory_summary_track_diff_report.json.
  • Add English and Chinese documentation covering design, normalization, reports, lightweight mode, optional integrations, and acceptance metrics.

Why

Session and Memory backends may produce different replay-visible behavior for the same agent trajectory. These differences can involve event ordering, state transitions, memory contents, Summary ownership, truncation boundaries, Track data, retries, or duplicate writes.

Without a reusable replay harness, backend migrations and storage changes can introduce subtle inconsistencies that are difficult to detect with isolated unit tests.

This change provides a deterministic cross-backend validation layer without modifying existing Session or Memory runtime APIs. It distinguishes meaningful mismatches from backend-generated noise and reports unsupported capabilities explicitly instead of silently ignoring them.

Fixes #2001

Testing

  • go build ./...
  • go vet ./session/replaytest
  • cd test && go vet .
  • golangci-lint run --timeout=10m ./session/replaytest/...
  • cd test && golangci-lint run --timeout=10m replay_consistency_test.go replay_consistency_optional_test.go
  • go test ./session/replaytest -count=20
  • go test -race ./session/replaytest -count=1
  • cd test && go test . -run ReplayConsistency -count=10
  • cd test && go test -race . -run ReplayConsistency -count=1
  • cd test && go test ./... -count=1
  • cd session/sqlite && go test ./... -count=1
  • cd memory/sqlite && go test ./... -count=1

Acceptance results:

  • 12/12 normal replay cases passed.
  • Normal-case false-positive rate: 0.00%.
  • 15/15 injected faults detected.
  • Required Summary fault detection: 4/4 (loss, overwrite failure, wrong session, and wrong filter-key).
  • Lightweight normal replay matrix completed in less than one second.
  • Complete replay consistency tests remained below the 30-second limit.
  • Race detector passed.
  • Core replay package passed 20 consecutive runs.
  • The checked-in diff report is deterministic across repeated generation.
  • Redis, PostgreSQL, MySQL, and ClickHouse integration tests were explicitly skipped because their environment variables were not configured.

The repository-wide root test suite remains environment-sensitive on Windows because of unrelated existing tests requiring sh, WSL commands, and strict timestamp-resolution behavior. The affected replay, Session SQLite, Memory SQLite, and test modules pass independently.

Notes for reviewers

  • No existing Session or Memory runtime API is changed.
  • Please focus review on:
    • normalization boundaries and generated identity handling;
    • Summary ownership, filter-key, and truncation-boundary comparison;
    • concurrent causal-order validation;
    • retry and duplicate-write detection;
    • memory ordering and score normalization;
    • exact-path allowed_diff and unsupported-capability semantics.
  • The default lightweight matrix requires CGO and a C compiler because SQLite uses github.com/mattn/go-sqlite3.
  • Optional external backends are environment-gated and are skipped explicitly when not configured.

Acceptance test screenshot

The screenshot below shows the local InMemory/SQLite acceptance run, fault detection, race detection, 20-run stability check, build verification, and golden report hash.

Replay consistency acceptance test results

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b84751f1-5632-46d6-acb4-1cd23118e442

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdb755 and 4e6228e.

📒 Files selected for processing (2)
  • session/replaytest/core_test.go
  • session/replaytest/operations_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • session/replaytest/operations_test.go
  • session/replaytest/core_test.go

📝 Walkthrough

English

Change overview

  • Added a backend-independent replay consistency framework under session/replaytest.
  • Provides a reusable harness to run deterministic replay “programs” against multiple isolated backends, normalize captured data, and compare results with strict, field-level JSON-path diffs.
  • Includes 12 public replay cases (conversations, tool-call extensions, state lifecycle, memory read/write/update/delete + ordering normalization, summary create/filter/overwrite + boundary recovery, track events, concurrency/happens-before, retry/ack-loss recovery, and duplicate recovery).
  • Includes a fault injection framework with 15 named injected faults plus fault detection that confirms blocking diffs (including required Summary-related fault classes).
  • Adds explicit unsupported-capability reporting (so unsupported sections are tracked rather than silently hidden).
  • Adds deterministic report generation (BuildReport, WriteReport) with concurrency-safe writing and an example golden diff report that is compared by JSON equality.
  • Adds comprehensive unit/integration tests and documentation (EN + ZH) describing lightweight and optional backend test modes.

Compatibility and behavioral risks

  • Test dependency / module surface expanded: test/go.mod updates storage/session-related modules and introduces/bumps sqlite dependencies; this can affect CI toolchains (CGO enabled/disabled, Go version, linker/tooling).
  • Outcome sensitivity to capability contracts: comparisons can be allowed vs blocking based on backend capability declarations and exact allowed_diff section/path rules—misconfiguration may suppress real regressions.
  • Normalization is opinionated: volatile fields (IDs/timestamps/ordering/noise) are intentionally stripped/rounded; differences that fall inside normalization “noise” won’t appear as diffs.
  • Optional backend tests are environment-dependent: Redis/PostgreSQL/MySQL/ClickHouse E2E coverage is only run when required env vars/schemas are available; otherwise tests are skipped.

Recommended validation

  1. Run the lightweight acceptance suite (InMemory + SQLite) and ensure:
    • All public replay cases are healthy/consistent (12/12 normal cases),
    • Injected faults are detected (15/15 faults),
    • Required Summary fault classes are detected (4/4),
    • False-positive rate remains 0.00% (and within the suite threshold).
  2. Run the race detector and the repeated stability run to confirm determinism (no intermittent diffs).
  3. Run go test ./... and re-check that the example golden diff report equality remains unchanged (JSONEq).
  4. If you want broader backend coverage, set the documented environment variables and run the optional backend test; verify those tests run (not skipped) and that cleanup behavior is stable.
  5. When backend capability declarations or replay case definitions change, re-validate:
    • backend capability metadata,
    • exact allowed_diff placement/path constraints,
    • normalization rules that affect diff visibility.
中文

变更概览

  • session/replaytest 新增可复用、与后端无关的 Replay 一致性测试框架。
  • 提供统一 harness:对多个隔离后端执行确定性的 Replay 程序、对捕获数据做确定性归一化,并用严格字段级 JSON-path差异来比较结果。
  • 内置 12 个公开 Replay case(多轮对话、工具调用扩展、Session/App/User 状态生命周期、Memory 的无序归一化与读写/更新/删除、Summary 创建/筛选/覆盖 + 边界恢复、Track 事件、并发/因果有序校验、重试/ack-loss 恢复、重复恢复)。
  • 内置故障注入与检测框架:15 个命名故障 + 检测逻辑验证“阻塞差异”(包含要求的 Summary 故障类)。
  • 不支持能力进行显式报告(不会静默吞掉差异)。
  • 提供确定性报告生成(BuildReport, WriteReport):支持并发安全写出,并用示例 golden diff 报告做 JSON 等价校验。
  • 补齐了大量单测/端到端测试与 EN/ZH 文档,说明 lightweight/可选后端模式。

兼容性与行为风险

  • 测试依赖/模块面扩大test/go.mod 更新了存储/会话相关模块并调整 sqlite 依赖;在 CI 中可能受 Go/CGO/工具链差异影响。
  • 能力契约会影响最终判定:差异可能被标记为 allowed 或 blocking,取决于后端能力声明与 allowed_diff精确 section/path 配置;配置错误可能掩盖真实回归。
  • 归一化策略是“有取舍”的:会剔除/四舍五入/排序部分易变字段;落入归一化噪声范围的差异不会出现在 diff 中。
  • 可选后端测试依赖环境:Redis/PostgreSQL/MySQL/ClickHouse 覆盖需要对应环境变量/Schema,未配置时将跳过。

建议验证步骤

  1. 运行 lightweight 验收(InMemory + SQLite),确保:
    • 12/12 正常 case 一致通过;
    • 15/15 注入故障均被检测到;
    • 要求的 Summary 故障类 4/4 均被检测;
    • 误报率维持 0.00%(并满足套件阈值)。
  2. 运行 race detector 与多次稳定性运行,确认差异可复现(不间歇)。
  3. 运行 go test ./...,并确认示例 golden diff 报告的 JSONEq 校验仍保持一致。
  4. 若要扩展后端覆盖:按文档设置环境变量并运行可选后端测试;确认测试确实执行(未跳过)且清理逻辑稳定。
  5. 当修改后端 capability 声明或 replay case 定义时,重点复核:
    • backend capability 元数据
    • allowed_diff 的精确路径/section 约束
    • 归一化规则对差异可见性的影响

Walkthrough

Adds a reusable cross-backend session replay framework with deterministic operations, normalization, semantic diffing, fault injection, report generation, backend validation, integration tests, and bilingual documentation.

Changes

Replay consistency framework

Layer / File(s) Summary
Contracts, identity, and normalization
session/replaytest/types.go, session/replaytest/ledger.go, session/replaytest/normalize.go
Defines capability, snapshot, trace, diff, identity-ledger, and normalization contracts for stable cross-backend comparison.
Replay operations and public cases
session/replaytest/operation.go, session/replaytest/cases.go
Adds deterministic operations and public scenarios for events, state, memory, summaries, tracks, concurrency, recovery, and identity preservation.
Trace comparison and suite execution
session/replaytest/compare.go, session/replaytest/harness.go, session/replaytest/core_test.go
Runs cases across isolated backends, validates event ordering, compares checkpoints and final snapshots, handles capabilities, and tests cleanup and status behavior.
Fault injection and report publication
session/replaytest/fault.go, session/replaytest/report.go
Adds trace and service fault modes, blocking-diff expectations, report aggregation, health checks, and synchronized atomic report writing.
Backend integration and acceptance validation
test/replay_consistency_test.go, test/replay_consistency_optional_test.go, test/go.mod, test/testdata/*
Validates lightweight InMemory/SQLite replay, optional Redis/PostgreSQL/MySQL/ClickHouse integration, injected-fault detection, and deterministic report output.
Replay consistency documentation
docs/mkdocs.yml, docs/mkdocs/en/session/*, docs/mkdocs/zh/session/*
Documents execution modes, acceptance metrics, normalization rules, capability allowances, and report fields in both navigation trees.

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

Possibly related PRs

Suggested labels: 腾讯犀牛鸟开源专属, 犀牛鸟-中高难度

Suggested reviewers: winechord

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% 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 The title is concise and accurately summarizes the main change: a multi-backend replay consistency harness for session testing.
Description check ✅ Passed The description is clearly related to the changeset and matches the replay harness, cases, docs, tests, and optional backends.
Linked Issues check ✅ Passed The PR appears to satisfy issue #2001: it adds the harness, 12 cases, normalization, diffs, faults, reports, docs, and env-gated backends.
Out of Scope Changes check ✅ Passed No significant unrelated changes stand out; the dependency and doc updates support the replay testing framework's scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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.

🧹 Nitpick comments (1)
session/replaytest/compare.go (1)

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

Missing break diverges from the other allowed-rule loops. Unlike the loops at Lines 61-67 and 202-208 (which break on first match), this loop keeps iterating, so a last-matching rule's Reason wins. Exact-match validation makes this only reachable via duplicate rules, so it's cosmetic today, but aligning the three loops avoids surprise later.

中文

此处缺少 break,与其余两处允许规则循环不一致。 相较 Lines 61-67 和 202-208(首次匹配即 break),此循环会继续遍历,导致最后一个匹配规则的 Reason 生效。由于校验禁止通配符,仅在重复规则时才可触达,目前仅属外观问题,但统一三处逻辑可避免后续踩坑。

♻️ Suggested change
 			for _, rule := range allowed {
 				if allowedDiffMatches(rule, diff) {
 					diff.AllowedDiff = true
 					diff.Explanation = rule.Reason
+					break
 				}
 			}
🤖 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 `@session/replaytest/compare.go` around lines 123 - 128, Add a break to the
allowed-rule loop containing allowedDiffMatches so it stops after the first
matching rule, preserving the first match’s Reason and aligning it with the
other allowed-rule loops.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@session/replaytest/compare.go`:
- Around line 123-128: Add a break to the allowed-rule loop containing
allowedDiffMatches so it stops after the first matching rule, preserving the
first match’s Reason and aligning it with the other allowed-rule loops.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 145d5d87-f329-4d6c-aa6e-206351796ad4

📥 Commits

Reviewing files that changed from the base of the PR and between b7b6c8e and 3ee5b82.

⛔ Files ignored due to path filters (2)
  • docs/mkdocs/images/issue-2001-replay-acceptance.png is excluded by !**/*.png, !**/*.png
  • test/go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • docs/mkdocs.yml
  • docs/mkdocs/en/session/replay_consistency.md
  • docs/mkdocs/zh/session/replay_consistency.md
  • session/replaytest/cases.go
  • session/replaytest/compare.go
  • session/replaytest/core_test.go
  • session/replaytest/doc.go
  • session/replaytest/fault.go
  • session/replaytest/harness.go
  • session/replaytest/ledger.go
  • session/replaytest/normalize.go
  • session/replaytest/operation.go
  • session/replaytest/report.go
  • session/replaytest/types.go
  • test/go.mod
  • test/replay_consistency_optional_test.go
  • test/replay_consistency_test.go
  • test/testdata/session_memory_summary_track_diff_report.json

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.20018% with 329 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.80597%. Comparing base (12c70ff) to head (4e6228e).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
session/replaytest/operation.go 79.09836% 55 Missing and 47 partials ⚠️
session/replaytest/harness.go 81.97970% 42 Missing and 29 partials ⚠️
session/replaytest/compare.go 86.64773% 26 Missing and 21 partials ⚠️
session/replaytest/fault.go 88.54489% 19 Missing and 18 partials ⚠️
session/replaytest/normalize.go 90.85714% 18 Missing and 14 partials ⚠️
session/replaytest/report.go 65.62500% 14 Missing and 8 partials ⚠️
session/replaytest/ledger.go 80.28169% 7 Missing and 7 partials ⚠️
session/replaytest/types.go 88.23529% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@                 Coverage Diff                 @@
##                main       #2268         +/-   ##
===================================================
- Coverage   89.85102%   89.80597%   -0.04506%     
===================================================
  Files           1149        1159         +10     
  Lines         203321      205640       +2319     
===================================================
+ Hits          182686      184677       +1991     
- Misses         12918       13100        +182     
- Partials        7717        7863        +146     
Flag Coverage Δ
unittests 89.80597% <85.20018%> (-0.04506%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@wei-yan1

Copy link
Copy Markdown
Author

recheck

@wei-yan1

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@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.

🧹 Nitpick comments (1)
session/replaytest/operations_test.go (1)

219-223: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the exact stored-event count.

require.GreaterOrEqual(t, len(stored.Events), 7) is satisfied even if a fault mode silently stops appending its event (as long as ≥7 remain), so a real regression in one mode could go undetected. Since the appended sequence here is fully deterministic, asserting the exact count would tighten the guarantee.

中文

require.GreaterOrEqual(t, len(stored.Events), 7) 属于弱断言:即使某个 fault 模式悄悄不再追加事件(只要剩余 ≥7),断言仍会通过,可能漏掉真实回归。此处追加序列是完全确定的,建议改为断言精确数量以增强保证。

As per path instructions: "Whether assertions are strong enough to ensure the intended behavior is actually verified."

🤖 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 `@session/replaytest/operations_test.go` around lines 219 - 223, In the session
replay test’s final stored-session assertions, replace the lower-bound check on
len(stored.Events) with an exact-count assertion for the deterministic sequence,
while leaving the state assertion unchanged.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@session/replaytest/operations_test.go`:
- Around line 219-223: In the session replay test’s final stored-session
assertions, replace the lower-bound check on len(stored.Events) with an
exact-count assertion for the deterministic sequence, while leaving the state
assertion unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 42b0c854-e96b-483a-952a-cd60d7bfe7bf

📥 Commits

Reviewing files that changed from the base of the PR and between 3ee5b82 and 8fdb755.

📒 Files selected for processing (3)
  • session/replaytest/compare.go
  • session/replaytest/core_test.go
  • session/replaytest/operations_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • session/replaytest/compare.go

@Rememorio Rememorio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I reviewed the changed lines and left 7 inline comments below. The comments focus on issues worth addressing before merge.

Fresh review found three correctness/reliability issues in the replay framework and four important test/lifecycle gaps. The main risks are destructive report replacement, false positives for unordered duplicate memories, and capability declarations that do not actually prevent unsupported state APIs from being called. Targeted validation should cover destination-directory safety, reversed equal-memory ordering, unsupported state services, nested parallel operations, real timeouts, external-backend cleanup, and pre-normalization fault injection.

中文

我看过这次变更的相关代码,在下面留下 7 条行内评论。评论聚焦在合并前值得处理的问题。

本次审查发现 3 个 replay 框架正确性/可靠性问题,以及 4 个重要的测试与生命周期缺口。主要风险包括:报告替换路径可能产生破坏性删除、无序重复 Memory 可能误报差异,以及 capability 声明无法阻止框架调用不受支持的状态接口。建议补充针对目标目录安全、等价 Memory 逆序、状态能力缺失、嵌套并发操作、真实超时、外部后端清理和归一化前故障注入的验证。

if err != nil {
return CaptureInput{}, fmt.Errorf("load session from %s: %w", r.Backend.Name, err)
}
appState, err := r.Backend.Session.ListAppStates(ctx, r.Backend.SessionKey.AppName)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1: Honor unsupported state capabilities during capture

The default loader invokes ListAppStates and ListUserStates unconditionally. A backend that supports the event case but correctly declares app or user state unsupported will therefore abort during capture instead of producing the documented unsupported result; SessionWindowCheckpointOperation repeats the same unconditional reads.

Gate these calls on CapabilityAppState and CapabilityUserState, as the memory read already does, and leave the corresponding capture section empty for normalization. A fake service whose unsupported methods return an error would close this compatibility gap.

中文

状态捕获应遵守 unsupported capability

默认 loader 无条件调用 ListAppStatesListUserStates。因此,一个能够执行 Event case、但正确声明不支持 App/User State 的后端,会在捕获阶段直接报错,而不是按设计输出 unsupported 结果;SessionWindowCheckpointOperation 也存在同样的无条件读取。

建议像 Memory 读取一样,分别根据 CapabilityAppStateCapabilityUserState 决定是否调用,并在不支持时为对应捕获区段保留空值。可增加一个 fake service,让不支持的方法返回错误,以覆盖这一兼容性场景。

for i := range values {
values[i].item.Rank = -1
}
sort.SliceStable(values, func(i, j int) bool {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1: Canonicalize equal unordered memories by logical identity

MemoryOrderUnordered still retains backend input order when two records have the same semantic fingerprint and rounded score: this stable comparator returns neither record as smaller, and logical IDs are assigned only after sorting. If two backends return equal-content logical memories in opposite orders, their snapshots become [memory:a, memory:b] and [memory:b, memory:a], producing a blocking false positive even though multiplicity and contents match.

Resolve the logical identity before sorting and use it as the final deterministic tie-breaker, or compare an actual multiset. A regression test should register two equal-content/equal-score memories and normalize reversed backend result orders.

中文

无序 Memory 相等时应按逻辑身份规范排序

当两条记录的语义指纹和舍入后 score 都相同时,MemoryOrderUnordered 仍会保留后端原始顺序,因为当前稳定排序无法区分两者,而逻辑 ID 又是在排序后才写入。若两个后端以相反顺序返回内容相同但逻辑身份不同的 Memory,快照会分别成为 [memory:a, memory:b][memory:b, memory:a],从而产生 blocking 误报。

建议在排序前解析逻辑身份,并把它作为最终确定性排序键,或者直接进行多重集合比较。回归测试应注册两条内容、score 均相同的 Memory,并让两个后端按相反顺序返回。

}
if err := os.Rename(temporaryPath, path); err != nil {
// Windows cannot replace an existing destination with os.Rename.
if removeErr := os.Remove(path); removeErr != nil && !os.IsNotExist(removeErr) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P1: Do not delete the destination after an arbitrary rename failure

os.Rename can fail for reasons other than Windows refusing to replace an existing file. This fallback removes path before establishing that the failure is the expected file-collision case. For example, if path is an empty directory, the first rename fails, os.Remove deletes the directory, and the retry replaces it with the report file; an existing valid report can likewise be lost if the second rename fails.

Only use a replacement fallback after identifying the platform-specific destination-file collision, and preserve the old destination until replacement is guaranteed. Please add a regression test showing that an existing directory or an unrelated rename error leaves the destination untouched.

中文

不要在任意 rename 失败后删除目标路径

os.Rename 失败并不只代表 Windows 无法覆盖已有文件。当前逻辑没有确认错误类型就先删除 path:如果目标恰好是空目录,第一次 rename 会失败,随后目录会被删除并被报告文件替代;如果目标是已有的有效报告,而第二次 rename 仍失败,也会丢失旧报告。

建议仅在确认属于特定平台的“目标文件已存在”冲突时执行替换逻辑,并保证替换成功前旧目标保持不变。同时增加回归测试,验证目标为已有目录或出现其他 rename 错误时不会修改目标。

for _, operation := range o.Operations {
operation := operation
group.Add(1)
go func() { defer group.Done(); <-start; errorsCh <- operation.Execute(ctx, runtime) }()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P2: Validate child operations before starting parallel execution

validateReplayCase validates only top-level operations, so an exported ParallelOperation containing a nil child passes validation and panics in this goroutine when Execute is called. An empty child list also silently turns a concurrency case into a no-op.

Recursively validate parallel children, rejecting empty lists, nil entries, missing IDs, and conflicting IDs before any goroutine starts. Add malformed nested-program tests alongside the existing top-level validation cases.

中文

并发执行前应校验子 Operation

validateReplayCase 目前只校验顶层 Operation。公开的 ParallelOperation 如果包含 nil 子项,仍能通过校验,并在该 goroutine 调用 Execute 时触发 panic;空的子操作列表则会悄悄把并发 case 变成无操作。

建议在启动 goroutine 前递归校验并发子项,拒绝空列表、nil 项、缺失 ID 和冲突 ID,并在现有顶层非法程序测试旁增加嵌套场景。

Capabilities: capabilities.Clone(),
}
cleanup := func() error {
return errors.Join(memoryService.Close(), sessionService.Close())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P2: Clean persistent external-backend data, not just clients

This cleanup only closes the services. Each optional run creates a never-reused table/key prefix, configures session/app/user TTLs as zero, and writes sessions plus scoped state, so Redis keys and SQL/ClickHouse rows and tables remain after the test. Repeated opt-in or CI runs will accumulate a fresh persistent namespace every time.

Delete the case session and scoped state and drop or reuse the test tables before closing, or require and manage a disposable schema/database. Redis should also use a bounded TTL or explicit prefix cleanup. Document the cleanup contract for users supplying these DSNs.

中文

外部后端清理不能只关闭客户端

这里的 cleanup 只关闭服务。每次可选集成测试都会生成一个不再复用的新 table/key prefix,同时把 Session/App/User TTL 配置为 0,并写入会话及分层状态。因此测试结束后,Redis key 以及 SQL/ClickHouse 的数据和表都会继续存在;反复执行会不断累积新的持久命名空间。

建议在关闭服务前删除 case 会话和分层状态,并删除或复用测试表;也可以明确要求并管理一次性 schema/database。Redis 还应设置有限 TTL 或显式清理整个 prefix,并在文档中说明调用方提供 DSN 时的清理契约。

cases := replaytest.PublicCases()
require.GreaterOrEqual(t, len(cases), 10)
report, err := replaytest.RunSuite(
context.Background(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P2: Enforce the advertised replay deadline with context cancellation

The 30-second assertion runs only after RunSuite returns, while the suite receives context.Background(). A blocked session, memory, summary, or database call can therefore hang CI indefinitely and never reach the asserted upper bound; the optional two-minute check has the same issue.

Create a context with the advertised deadline and pass it to RunSuite in both tests. The resulting cancellation error should also be asserted so a timeout is reported as a bounded failure rather than a stuck job.

中文

通过 Context 真正执行 replay 超时限制

当前 30 秒断言只有在 RunSuite 返回后才会执行,而 suite 使用的是 context.Background()。如果 Session、Memory、Summary 或数据库调用卡住,CI 会无限等待,根本到不了耗时断言;可选后端的两分钟检查也有同样问题。

建议为两组测试创建带对应 deadline 的 Context 并传给 RunSuite,同时校验取消后的错误,使超时表现为有界失败,而不是卡死任务。


baseline := result.Traces["inmemory"]
source := result.Traces["sqlite"]
faulty, err := fault.Inject(source)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P2: Inject required faults before capture and normalization

All 15 acceptance faults mutate a Trace after the backend state has already passed through Runtime.Capture and Normalizer. The metric therefore proves that CompareTraces notices edits to its own normalized DTO, but a regression that erases or aliases the backend discrepancy during capture still reports 15/15. For example, summary_wrong_session writes SummarySnapshot.SessionID directly even though normalization synthesizes that field from the enclosing session.

Keep these mutations as comparator tests, but exercise the required summary ownership/filter and boundary faults in a raw CaptureInput or service fake before calling Runtime.Capture. Assert that the resulting normalized traces still contain a blocking, precisely located diff.

中文

必要故障应在捕获和归一化之前注入

这 15 个验收故障都是在后端状态已经经过 Runtime.CaptureNormalizer 后,直接修改 Trace。因此该指标只能证明 CompareTraces 能发现对归一化 DTO 的修改;如果捕获或归一化阶段错误地丢弃、覆盖了后端差异,测试仍会得到 15/15。例如 summary_wrong_session 直接修改 SummarySnapshot.SessionID,但真实归一化逻辑会从外层 Session 合成这个字段。

可以保留这些 mutation 作为比较器单元测试,但必要的 Summary 归属、filter 和 boundary 故障应通过原始 CaptureInput 或 service fake 在 Runtime.Capture 前注入,并断言归一化后的 trace 仍产生可精确定位的 blocking diff。

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

构建 Session / Memory 多后端回放一致性测试框架

2 participants