# R3.1 纯 Kafka 页面刷新重放状态机调查报告

- 上下文：
  - [HWLAB #2486](https://github.com/pikasTech/HWLAB/issues/2486)
  - [AgentRun #283](https://github.com/pikasTech/agentrun/issues/283)
- 固定离线 trace：`trc_fb6031c28d79495e`
- 目标运行面：NC01/v03 + AgentRun NC01/v0.2

## 结论

- 当前产品实时链没有偏离纯 Kafka 目标：
  - `agentrun.event.v1` 由 HWLAB direct mapper 一对一发布到 `hwlab.event.v1`；
  - 产品 SSE 订阅 shared Kafka fanout；
  - Web 已让 `hwlab.event.v1` 进入统一 decode、reducer 和卡片投影。
- 页面刷新缺口位于 transport bootstrap：
  - 当前 SSE 只发送连接建立后的 future event；
  - 浏览器内存清空后没有从 Kafka retention 恢复历史事件。
- 用户消息还存在一个上游正式事件缺口：
  - AgentRun `EventType` 当前没有 `user_message`；
  - command 创建只发布 `backend_status(command-created)`；
  - HWLAB mapper 和 Web live projector 也没有 user event 语义；
  - 因此历史 trace 不得伪造用户消息，根因由 AgentRun #283 沿同一 Kafka 链修复。

## 已确认的可复用基础

- `startLiveHwlabKafkaEventBridge()` 已维护单个 shared `hwlab.event.v1` consumer，并向浏览器 listener fanout；浏览器连接不会创建长期 consumer group。
- `kafkaMessageKey()` 优先使用 `sessionId`，因此同一 HWLAB session 的正式事件使用同一 Kafka key；bootstrap 仍须从实际 transport metadata 证明命中事件只在一个 partition，不能只信任代码假设。
- `queryKafkaEventStream()` 已具备：
  - Kafka admin end-offset snapshot；
  - 从 retention 起点读取；
  - 按 trace/session 过滤；
  - transport `topic/partition/offset` 证据；
  - 到达 barrier 后有界退出。
- 当前查询缺少独立 scanned-record 上限，且默认 limit/timeout 不能作为运行时隐藏策略；refresh 调用必须从 owning YAML 显式传入 group prefix、timeout、scan、event 和 live buffer 预算。
- SSE scope authorization 已先通过 server-side session ownership 校验；数据库仅用于授权，不参与历史状态重建。

## 冻结状态机

1. `authorize`
   - 解析并校验请求的 session/trace；
   - trace-only 请求解析到授权 session；
   - 浏览器不能请求任意外部 trace。
2. `buffer-live`
   - 在抓 barrier 前注册 shared live fanout listener；
   - 只缓冲当前授权 scope 的 envelope；
   - buffer 超过 YAML 预算立即记录 typed failure。
3. `capture-and-replay`
   - 抓取 `hwlab.event.v1` 每个 partition 的 end-offset barrier；
   - 从 Kafka retention 起点读取到该 barrier；
   - scan、matched event 和 timeout 均使用 YAML 预算；
   - 要求扫描完整、至少命中一条、命中事件只属于一个 partition，且每条都有稳定 transport identity 和 event identity。
4. `handoff`
   - replay 按目标 partition offset 递增发送到原有 `hwlab.event.v1` SSE event name；
   - replay payload 保持原始 HWLAB envelope，不生成 snapshot 或第二种业务事件；
   - flush live buffer 时，barrier 内记录必须已由 replay identity 证明并去重；barrier 后记录按 offset 继续发送；
   - flush 完成后切换到 shared live direct delivery，仍保留 barrier/identity 去重以处理 consumer lag。
5. `fail-closed`
   - barrier 缺失、扫描未完成、预算耗尽、多 partition、identity 缺失、buffer 溢出或 barrier overlap 无对应 replay 记录时，通过 `workbench.error` 返回 phase/code/evidence 后关闭 SSE；
   - 禁止回退 projector、read model、snapshot、sync、gap-fill、finalizer、polling 或 HTTP 历史。

## 可组合能力与配置合同

- `directPublish`、`liveKafkaSse`、`kafkaRefreshReplay`、`transactionalProjector`、`projectionOutboxRelay`、`projectionRealtime` 均为独立 boolean capability，不建立二选一 mode。
- `kafkaRefreshReplay=false` 时保留现有纯 live 行为。
- `kafkaRefreshReplay=true` 时显式要求 owning YAML 提供：
  - bootstrap consumer group prefix；
  - timeout；
  - scanned-record limit；
  - matched-event limit；
  - live buffer limit。
- 代码只校验这些字段为所需类型并执行，不增加运行时数值默认。

## 验收边界

- 固定历史 trace 用于验证 retained replay、terminal、Final Response 唯一性、tool summary、timing 和 projection=0。
- 固定历史 trace 生成于 `user_message` 合同之前，不能伪造用户输入；用户消息先用 AgentRun #283 的正式 event fixture 验证一对一 lineage，最终部署后再由 semantic internal web-probe 的真实刷新验收确认。
- CLI 必须直接调用生产 bootstrap、decoder、reducer 和 Markdown renderer；若无法表达 barrier/handoff/user event 证据，先改进 CLI 再继续。
