# UniDesk Observability Reference

UniDesk 的可观测性优先级高于静默成功。CLI、服务日志、Docker 日志和数据库状态都必须能通过短命令查询。

## Capability Boundary

版本、commit 和微服务契约漂移只属于可观测性信号。OTel、日志、diagnose 和 status 必须把它们投影为非阻塞 warning，并继续以真实业务结果为权威；不得把漂移告警升级为业务失败、admission blocker、readiness failure 或 CI/CD gate。完整边界见 [DevOps Hygiene](devops-hygiene.md#prohibited-deployment-truth)。

可观测性是定位、验收和排障手段，不是功能完成标准。Trace、日志、诊断文案、fallback 标记、只读原因、降级提示和 issue 进展只能说明系统在哪里失败、为什么失败、修复后如何验证；它们不能把缺失能力包装成已完成，也不能把未修复功能包装成可接受状态。

当证据表明某条用户路径需要的能力不存在、没有真正接入、没有复用预期资源、没有返回最终业务结果，或仍停留在 mock、fixture、fallback、只读替代和提示文案时，正确动作是补齐能力或修复功能本身。允许先补最小观测来定位根因，但完成条件必须回到原始用户入口的真实行为闭环，并证明能力已经可用。

因此，CLI、Web、trace 和 issue 评论中的状态字段必须避免把“已观测到缺口”“已显示失败原因”“已提供诊断入口”表达成“问题已解决”。如果当前阶段只能增加可见性，输出应明确标记为诊断进展或阻塞定位；不得关闭对应功能 issue，不得把观测增强当作验收通过。

## Offline Investigation

用户要求“离线调查”时，统一使用这个术语，不再写成“只读流程”“只读调查”或“只读分析路径”。离线调查的边界是用既有证据定位问题：OTel/Tempo 中已经采集的 trace、`platform-infra observability ...` 的离线 analyze/diagnose 输出、已有 `web-probe observe` / `web-probe sentinel` artifact 与 `observe analyze` 报告、代码静态分析、配置/YAML 静态读取，以及必要的 `../opencode` 静态对比。

离线调查不得新开 `web-probe observe/run/script`、不得触发 dashboard/manual quick verify、不得部署、rollout、重启、写数据库、改 Secret 或改变业务运行面。可以读取已有运行记录、状态表、报告索引、source checkout、OTel trace 和离线 artifact；需要 GitHub 写入时只允许在结论阶段通过 `$unidesk-gh` 新建或评论分析 issue，并标明目标合并分支或运行 lane。

离线调查遇到 OTel 或 analyze 不好用时，先改进工具再继续业务结论。典型触发包括：OTel 查询缺少目标运行面、service path、business trace 映射、error span 摘要或 `--grep`/`--limit` drill-down；`diagnose-code-agent` 不能区分 observability gap 与业务失败；`observe collect/analyze` 解析失败、输出不可界定、不能按 run/observer/trace/sample 定位、只给 dump 或缺少 root-cause signal；sentinel report 不能把 runner API、SQLite index 和 artifact findings 对齐。工具改进应优先落在 CLI/analyzer/summary/parser 或 instrumentation 的可见性层，并用既有 trace/artifact 做回归验证；若工具改进需要实际部署或新采样，必须先把离线调查结论停在“工具缺口”，再取得明确授权后进入对应运行面流程。

`platform-infra observability` 的 `serviceConnections` 是诊断覆盖图，不是全局 node/lane registry。每条连接只应引用当前 owning YAML 中稳定存在的字段；当目标节点已经不在该 YAML 中声明，或另一个 domain YAML 不暴露同构的 `node/version/runtime.namespace` 结构时，应在 observability YAML 中显式写入该连接的 `targetNode/lane/namespace`，而不是让全局配置解析被无关节点的陈旧 `configRef` 阻塞。`plan --target <node>` 必须能在查询 Tempo 前完成解析；缺 trace 应表现为受控诊断结论，例如 `otel trace not found`，不能退化为 YAML path missing 的异常栈。

## Zero Implicit Fallback

运行时异常、投影写入异常、序列化异常、ReferenceError/TypeError、不可达上游、provider 不可用、trace 同步失败和状态机非法转移不得被隐式 fallback 吞掉。任何 catch 后继续返回旧值、空值、默认值、legacy 查询、二次来源仲裁或“看起来成功”的行为，都必须改成以下两类之一：

- `hard failure`: 当前用户路径必须失败，并把 trace_id、错误类型、错误消息、阶段和可重试性透传到 CLI/Web/API 诊断面。
- `observed degraded`: 只有业务明确允许降级时才可继续，但必须写入 OTel span event/error、结构化日志、诊断字段和用户可见降级提示；提示必须说明缺失的真实能力，不能把降级输出伪装成正常结果。

禁止用下游 repair、多来源投票、旧缓存补洞、空数组/空对象、默认标题、默认耗时、默认 final response、legacy session store 或浏览器端再计算来掩盖上游异常。尤其是 Workbench/AgentRun/WebProbe 链路中，如果 durable projection、message facts、timing facts、terminal event 或 final response 持久化失败，必须让上游写路径失败并产生 OTel 证据，而不是让前端继续用 fallback 渲染。

每次发现隐式 fallback，都应优先修上游 source of truth：先定位第一个吞错点，删除吞错 fallback，补 OTel/error 透传，再复测原入口。只有确认上游事实已经正确产生后，才允许清理前端展示或 CLI 表格。不得通过增加更多采样器判断、前端兜底字段或 analyzer 自动仲裁来“修好”业务结果。

Web/Workbench trace、Web 哨兵和 `web-probe observe` 的人工判定入口以 `$unidesk-webdev` 为准：先用采样器保存的 artifact 渲染 `turn-summary` 和 `trace-frame` CLI 视图，再解释 analyzer finding。自动判别器、聚合计数或额外截图保存源不能压过同一采样帧的 CLI trace 视图；若二者冲突，应登记 analyzer/tooling 精度问题或上游投影问题，而不是用 fallback 视图修业务结论。

Web 哨兵 dashboard/API 展示问题的第一事实源是 sentinel runner 的 `/api/overview`、`/api/runs`、`/api/runs/{id}`、`/api/findings` 和 `web-probe sentinel dashboard verify|screenshot` 远程浏览器证据。OTel/Tempo 查询不到 `hwlab-web-probe-sentinel` service span 或具体 `sentinel-run-*` id 时，只能说明当前 instrumentation 或保留窗口没有覆盖这条 dashboard/API 路径；不得因此把 UI/API 口径问题判为已追穿，也不得阻塞已由 API/DOM 证据定位的修复。需要继续追 runner 内部链路时，应把缺少 Web 哨兵 span 作为 instrumentation 问题登记到对应治理 issue。

收到 monitor-web 链接、页面上下文或用户消息里的证据 id 时，调查入口必须先走受控 CLI drill-down。URL 用 `web-probe sentinel inspect-url <url>` 解析 `run`、`observer`、`traceId`、`sampleSeq`、`turn`、`commandId`、`artifact`、finding 和 report SHA；裸 id 用 `web-probe sentinel inspect-id <id> --node <node> --lane <lane> --sentinel <id>` 做类型识别和歧义提示。`inspect-*` 应从 YAML/source-of-truth 解析 node/lane/sentinel，并输出短表、摘要和 Next 命令；run id 证据继续落到 `web-probe sentinel report --run <runId> --view summary|findings|trace-frame`，observer/trace/command 证据继续落到 `web-probe observe collect|analyze` 的有界 view。只有明确诊断 monitor-web 公网 shell/API/render 自身故障时，才使用 HTTP 探测，并在结论中标注为 monitor-web 诊断，不得把 public API curl 结果当作 HWLAB 业务证据。

`web-probe sentinel dashboard verify` 必须区分页面渲染证据和 API 可达性证据。远程浏览器已经拿到 HTML shell、`data-monitor-ready=true`、目标 run/曲线 DOM 和内存/request 图表时，页面二次 `fetch("/api/*")` 遇到瞬时 `ERR_NETWORK_CHANGED`、abort 或同类浏览器网络抖动，不得直接归类为 dashboard render failed；应优先保留浏览器 DOM 证据，并用受控 runner service API 读取 `/api/overview`、`/api/runs` 和 `/api/runs/{id}` 作为显式 `service-fallback` 证据。fallback 只能修正 verifier 证据来源和合同判断，不能遮盖真实页面 shell 未 ready、图表未渲染、sentinelId/route 不匹配、目标 run 不一致、页面 error、horizontal overflow 或 service API 本身失败。

monitor-web 的登记标题和用户摘要使用不同净化边界。登记标题来自 check catalog、runner `/api/runs/{id}` 或 `/api/errors/{errorId}` 的 `displayTitleZh`、`errorTitleZh`、`checkTitleZh` 和 `check.titleZh`，只应过滤空值和 `未登记监测项` 占位；不得套用会屏蔽 `trace`、投影、Shell、API 等内部词的摘要净化规则。摘要、根因和处理建议仍必须经过用户可见文本净化，避免把内部 trace、DOM、HTTP、runner 或 analyzer 细节直接当作面向用户的处置文案。

Web 哨兵 findings 可见性要同时核对 runner API 和已有 observe artifact。若某个 run 的公开 `/api/report?view=findings&run=<id>` 只显示 WBC-003，但 `web-probe sentinel report --run <id> --view findings --raw` 能从 `analysis/report.json` 读出 red/amber analyzer findings，根因是索引或 artifact 可见性遮盖，不是业务没有产生 warning/error。此时应回填或重建这条既有 run 的 report index，并保留原有 report views；不要通过启动新的哨兵 run 来解释旧记录。

Web 哨兵 check code 是排障和验收合同，必须保持语义单一且确定。一个 code/id 只能对应一种可执行处置路径；如果一个 finding 可能同时表示“没有采集到业务轮次”“目标轮次缺 traceId”“trace rows/projection 缺失”“Final Response 为空但仍在运行/取消”“Final Response 为空且已失败/终止”等多种状态，就必须拆成多个固定 code/id。不得用同一个 code 的动态标题、动态 summary 或 rootCause 文案来承载多种互斥语义；动态字段只能补充证据，不能改变 code 的语义。`quick-verify` 只能作为运行阶段或证据上下文，不能作为 root-cause code 的宽泛语义；账号/会话命令失败、observer start/stop/cleanup、turn-summary、trace-frame、report analyze、无采样和 artifact/index 缺口必须各自用固定 code 表示。

Web 哨兵请求频率曲线的验收事实源是 runner `/api/runs/{id}.requestRate`、已有 observe artifact 中的 request-rate summary，以及 `web-probe sentinel dashboard screenshot` 的远程浏览器证据。阈值、采样间隔、bucket 大小和红黄线只从 YAML/source-of-truth 读取，长期文档只记录字段族与验证入口。验收时应核对 `bucketSeconds`、总请求曲线、页面曲线、API path 曲线、峰值每分钟计数、数据来源和 chart/DOM 是否显示在内存曲线上方并共享时间轴。若 quick-verify 的业务链路失败，但同一 run 的 `requestRate` API 和截图已经有曲线数据，应把请求频率能力验收与业务阻塞分开记录；反之，`requestRate.source=unavailable` 或曲线为空时要继续检查 analyzer compact 输出、artifact summary、索引回填和 report fallback。除非 `dashboard verify` 已显式输出 request-rate 专用字段，不得把旧的 `API_PAGES` / `API_SAMPLES` 列当作请求频率曲线验收结果。

## Workbench Request Storm And Freeze

Workbench 请求风暴和浏览器无响应的根因调查必须同时使用 OTel、web-probe artifact 和前端 runtime 诊断，不能只看 provider 是否成功或单个 REST route 是否返回 200。最小证据应包含同一用户动作的 `traceId/sessionId/turnId` 或脱敏 scoped key、request family 计数、SSE transport state、recovery action、refresh queue/single-flight 状态、browser memory/freeze sample、observer/run/report SHA，以及用户页面是否仍可操作。缺少其中某个观测面时，先补观测或记录 instrumentation gap，再给出根因结论。

OTel 结论必须区分执行完成与可见投影完成。若 AgentRun/provider span、`turn_status_read` 或 projection write 显示 completed，但 Workbench UI 的 trace rows、final response 或 timeline 为空，这不是 provider 失败的充分证据；应继续检查 Workbench read model、projection revision、SSE cursor replay、trace event page、runtime reducer 和 request fan-out。相反，若 OTel 显示 submit/admission/dispatch/provider 已失败，则 web-probe 的空 UI 只是用户可见症状，不能把根因改写成前端布局或 dashboard 问题。

请求风暴的判定重点是同一 scoped key 或同一 transport error 是否重复触发多个请求族，尤其是 session/message/turn/trace/health 互相拉起、并发重复或 error handler 递归。修复必须让所有恢复动作通过 Workbench realtime SPEC 定义的 keyed queue、single-flight、bounded recovery action 和 typed error 诊断；不得通过增加轮询间隔、隐藏错误、自动刷新页面、减少采样、关闭 web-probe 检测或把红灯降级为 warning 来消除现象。

浏览器 freeze/blocker 的监控阈值、采样间隔和 kill policy 只从 YAML/source-of-truth 读取。调查报告只写字段族、采样来源、baseline 计算方式和可复测入口，不在文档或源码中复制具体数值。页面级有效内存应按 page/page epoch baseline 后增长量解释；多页面或 Chromium 启动基础成本是观测 baseline，不是 Workbench 应用内存泄漏的替代根因。

## CLI Logs

异步 job 的 stdout 和 stderr 位于 `.state/jobs/`。`job|jobs list` 默认只返回最新 50 条摘要，并为已知异步工作流返回轻量 `progress.summary`；`job status <id>` 与兼容别名 `jobs get/read <id>` 会返回结构化 `progress` 与有限尾部，避免输出爆炸，同时保留完整日志文件路径便于继续排查。实现必须只读取日志尾部字节，不得先把完整 job 日志读入 CLI 内存；长时命令的阶段、关键对象名和下一步查询命令应优先沉淀到 `progress`，不能要求调用者先阅读完整日志才能知道是否卡在提交、构建、发布或观测阶段。

## Service Logs

服务日志位于 `logs/{YYYYMMDD}/`，每次 `server start` 都生成新的本地时间戳前缀。新写入的 UniDesk JSONL 日志必须按小时切片：`logs/{YYYYMMDD}/{startStamp}_{YYYYMMDD}_{HH}_{service}.jsonl`，一天一个目录，禁止长期追加到单个巨大 JSONL。所有 UniDesk Bun 服务（frontend、provider-gateway、Code Queue、project-manager、baidu-netdisk 以及后续新增 Bun 服务）必须复用 `src/components/shared/src/rotating-jsonl.ts` 中的 `createHourlyJsonlWriter`；Rust backend-core 必须提供等价的 hourly rotation and retention behavior in `src/components/backend-core/src/logger.rs`。`LOG_FILE` 只作为推导 `logs` 根目录、启动前缀和 service 后缀的 base path，不得长期追加到单个文件。database 通过 PostgreSQL logging collector 写入同一日期目录。

日志保留默认按日志族限制为 `512MiB`：服务写入或 Code Queue 导出日志时必须扫描同一 service 后缀的历史文件，超过上限后自动删除最旧切片；当前活跃切片不能被保留清理删除。全局上限由 `UNIDESK_LOG_RETENTION_BYTES` 控制，服务级上限使用 `UNIDESK_<SERVICE>_LOG_MAX_BYTES`（如 `UNIDESK_FRONTEND_LOG_MAX_BYTES`、`UNIDESK_PROVIDER_GATEWAY_LOG_MAX_BYTES`），历史兼容变量只允许作为过渡入口。主 server Compose 服务必须启用 Docker `json-file` 轮转，默认 `UNIDESK_DOCKER_LOG_MAX_SIZE=20m`、`UNIDESK_DOCKER_LOG_MAX_FILE=3`；该配置在服务重建或重建容器后生效。Codex app-server 的 `logs_*.sqlite` 仅作为 Codex 上游运行时的短暂缓冲，Code Queue 必须周期性导出为同样按小时切片的 `codex-app-server` JSONL，并删除/压缩已导出的 SQLite 行，避免 `logs_2.sqlite` 成为长期大文件。

主 server 应安装 `bun scripts/cli.ts gc policy install` 渲染的低风险防膨胀策略：systemd journal 上限 `512MiB`，并启用每日 `unidesk-gc.timer` 执行文件日志与 allowlisted `/tmp` 低风险 GC。该 timer 不主动 vacuum journal，不触碰数据库、Docker image/volume 或 Baidu staging；输出固定写入 `.state/gc/last-run.json` 和 `.state/gc/last-run.stderr`，不得把全量候选 JSON 打进 systemd journal；数据库 trace 留存仍必须由 `gc db-trace` 显式维护，不得加入默认 timer。

OA Event Flow 的高频 trace 统计不得把每个 `trace-stats-updated` 投影事件长期写入 `oa_events`；持久化真相是 `oa_trace_stats` 与 `oa_trace_steps`，SSE/API 发布时可以返回短暂投影通知用于实时 UI 刷新。需要历史回收时只通过 `gc db-trace plan|run` 做显式维护窗口操作，禁止把数据库 VACUUM FULL 或 trace 表大规模删除接入默认 timer。

新增或迁移服务的长期规范：Dockerfile 必须把 `src/components/shared` 复制到与仓库相同的相对路径，TypeScript 配置必须能解析 shared 引用，Compose 必须传入 `LOG_FILE` 和 `UNIDESK_LOG_RETENTION_BYTES`；如果服务需要在内存中暴露 `/logs`，可以继续维护有限 `recentLogs`，但落盘只能通过统一 hourly writer。业务归档日志（例如 Code Queue task output archive）可以保留 append-only 文件，但不得复用 UniDesk service JSONL 命名族，也不得替代 `/logs` 的结构化服务日志。

`bun scripts/cli.ts check` 必须包含日志轮转门禁：核心 Bun 服务源码不得直接向 `LOG_FILE` append，且必须引用统一 hourly writer；Rust backend-core logger must expose equivalent rotation/pruning markers for the same check. 新增服务如果进入主 Compose，也要纳入该门禁的 checked file 列表。

## Log Access

`bun scripts/cli.ts server logs` 同时读取文件日志和 Docker logs 尾部。文件日志是服务崩溃时的第一现场，Docker logs 是容器启动失败和 stdout/stderr 的辅助来源。默认输出必须包含 tail 字节数、是否截断和完整文件路径；扩大读取范围只能通过显式 `--tail-bytes N`，且 CLI 会对单次 tail 设置硬上限。

## Diagnostic Output Limits

所有诊断型 CLI 输出必须优先摘要化、尾部化或分页化，禁止默认倾倒大 JSON、全量日志、全量 trace 或 `.state`/`logs` 宽泛搜索结果。当前硬限额入口包括：`server logs` 默认 3000 bytes tail、`job list` 默认 50 条、`job status` 默认 12000 bytes tail、`codex task/trace/output` 默认分页与文本预览、`microservice proxy` 默认 body 预览且 `--raw` 仍受硬限额保护。确实需要完整响应时必须显式使用对应的 `--full`、`--full-text`、`--tail-bytes` 或 `--limit` 参数，并在验收记录中说明为什么需要扩大输出。

面向人工的“显式字段选择”也不能把常用长集合默认展开成 stdout dump。`gh issue view/read --json comments` 应返回 comment metadata、body 长度/SHA 和短 preview，完整正文通过显式 full/raw 或 `trans gh:` drill-down 读取；node-scoped `hwlab nodes control-plane status --full` 是运行面 workload/bridge/SecretRef 的有界 drill-down，完整 JSON 只属于显式 `--raw`。Secret/API key 输出只能披露对象名、key 名、presence、fingerprint 或 `valuesPrinted=false`。

当一个 UniDesk CLI 子流程调用另一个 UniDesk CLI 并使用 `--raw`、`--full` 或机器消费输出时，必须识别全局 stdout guard 返回的 `outputTruncated=true` / `data.dump.path` 包装，并跟随 dump 文件读取真实 JSON payload；不能把 bounded wrapper 当成业务 payload。若内部调用不能安全跟随 dump，应把命令改成更窄的显式 view、id-specific drill-down 或 compact schema，而不是提高全局 dump 阈值。人工 closeout 仍引用有界摘要、hash、run id 和 drill-down 命令，不把 `/tmp/unidesk-cli-output` 当作长期证据源。

branch-follower 的 gate 与 TaskRun drill-down 默认输出必须能直接作为 target-side 性能证据使用，不能稳定触发 stdout dump。CI gate 应返回有界的 PipelineRun 总耗时、慢 TaskRun 对象名、阶段 timeline 和性能摘要；成功 TaskRun drill-down 默认只返回容器状态、日志字节数和 `node-cicd-timing` 摘要，不把成功日志正文塞进 JSON。失败、等待或显式 full/raw drill-down 才展开日志 tail。

Web sentinel 的 PaC migrated lane 只能通过只读 status/history 展示自动交付证据。默认 compact 摘要至少包含 node/lane、source commit、外层 PaC PipelineRun 名称与状态、GitOps revision、Argo sync/health、runtime ready、public route readiness，以及 Gitea delivery 与 PaC history drill-down。内层 deterministic publish 只能标记为执行观察，不能代替外层 PaC event 驱动 latest、status 或 closeout。阻塞或失败时必须展示阶段、原因和只读 drill-down，并指向 owning YAML/controller/源码修复；禁止输出 `trigger-current`、sync、flush、人工 PipelineRun 或 recovery Next 来推进当前交付。

全局 stdout guard 不能只返回 dump 元数据。对已知高频长输出命令，bounded wrapper 的 `summary` 必须保留可直接 closeout 的命令特定字段，同时把完整 payload 留在 dump/raw drill-down 中。例如 `debug dispatch ... provider.upgrade` 超阈值时应保留 dispatch task、wait task、plan host root、当前/目标 gateway 版本、scheduler 结果和最终 promoted container 的 `version`、`restartPolicy`、`pidMode`、`heartbeatTimestamp`；`provider triage --full` 超阈值时应保留 `decision`、`scope`、`retryable`、failed/degraded/healthy scopes、signal counts、recommended cross-checks 和问题信号预览。新增会稳定超阈值的诊断命令时，优先补命令特定 compact summary，而不是扩大全局 stdout 阈值。

本地或远端 `AGENTS.md`、`CLAUDE.md`、`SKILL.md` 或同类 agent 入口文档超过 `10 KiB`、超过 YAML dump 阈值，或被 CLI/SSH/trans 读取时触发自动 dump，不能只把 dump 文件路径当成继续工作的正常入口。该现象表示入口文档已经过长，必须按 `docs-spec` 把入口文件拆成短索引：只保留 P0 规则摘要、关键命令入口和指向权威文档的链接；具体流程、背景、判定标准和长篇约束迁入对应职责文件。`SKILL.md` 拆到 `references/` 后禁止再堆成 `references/full.md`、`all.md`、`guide.md` 或其他变相超级 Markdown；必须按职责、生命周期和读取场景拆分成多个可选择的 reference，并在 `SKILL.md` 写清“何时读取哪个文件”。拆分后入口文档、skill 和长期参考必须互相交叉引用，避免同一规则在多个位置重复展开或产生第二真相。

CLI 写 stdout/stderr 遇到下游 pipe 关闭的 `EPIPE` 必须安静退出，不能打印 Bun stack trace。常见验证命令是 `set -o pipefail; bun scripts/cli.ts server status | head -1`，应只看到第一行 JSON 而无额外错误噪声。

## OpenCode Trace Search

HWLAB OpenCode 对话排障时，`platform-infra observability search --grep` 是首选入口，而不是默认手写宽泛 TraceQL。`--grep opencode.proxy.stream.start` 应推断为 span name 查询，`--grep /global/event` 应推断为 route 查询，`--grep opencode.proxy.sse.directory_rewrite_enabled=true` 应推断为属性 bool 查询；候选 trace 取回后还必须在 raw body、span name、status message、route 和 full span attributes 中做二次匹配。默认输出必须披露 `grepCoverage` 和 `grepQueryInference`，无命中时给出显式 TraceQL 与扩大 candidate/window 的下一步。

OpenCode provider 侧至少保留 `opencode.provider.sse.content_chunks`、`content_chars`、`output_data_lines`、`done_lines`、`json_errors` 和 `reasoning_only_choices_dropped` 等属性摘要；Cloud Web `/global/event` 侧至少保留 `opencode.proxy.stream.start`、`opencode.proxy.sse.directory_rewrite_enabled`、directory rewrite from/to、`opencode.proxy.ticket_accepted` 和 streaming 标记。OTel 只能证明链路状态，关闭 UI 卡住类问题仍必须回到 `web-probe opencode-smoke` 或等价浏览器 DOM/EventSource 终态证据。

## Task Liveness

backend-core 必须把 queued、dispatched、running 视为待处理任务，并通过 `TASK_PENDING_TIMEOUT_MS` 对长时间没有 provider 终态回报的任务做超时处理。超时任务转为 failed，result 中保留 timeout、previousStatus 和 previousResult 摘要，避免 `态势总览` 的待处理数量长期卡住且无法解释。

## Performance Metrics

backend-core 必须提供 `/api/performance`，返回滚动窗口内的 HTTP 组件请求统计、最近失败请求、内部操作统计、最近慢操作、进程内存、PGDATA 用量和 Code Queue PostgreSQL 存储摘要。组件统计必须包含请求数、失败数、失败率、平均延迟和 P95，内部操作统计必须包含服务名、操作名、次数、平均延迟和 P95；失败和慢操作记录必须保留时间、状态、耗时、路径或细节，避免只给汇总数字而无法定位。

frontend Bun server 必须提供同源 `/api/frontend-performance`，记录 webui 静态资源、登录/session、API 代理和 frontend->core 代理操作耗时。浏览器中的 `运行总览 / 性能面板` 必须把 frontend 与 backend-core 指标合并展示为 Bwebui 曲线、组件汇总、最近失败请求、内部操作汇总和最近慢操作；完整性能 JSON 只能通过显式 `查看原始JSON` 打开。

## Node Resource Status

节点 CPU、内存、磁盘和进程资源指标的实时真相来自 provider-gateway 上报的 `system_status`，Docker 资源摘要来自 `docker_status`；backend-core 负责落库、历史采样和 `/api/nodes/system-status` 聚合。排查资源面板不同步时，必须同时对照 provider-gateway 日志里的 `system_status_sent` / `docker_status_sent`、backend-core 日志里的 `provider_system_status` / `provider_docker_status`、以及 backend-core 内部 API，而不能只看前端旧值或浏览器缓存。

provider 上报的 `collectedAt` 在 backend-core 入库前必须解析为 typed timestamp。Rust backend-core 使用 PostgreSQL 参数时不得把 RFC3339 字符串传给 `$n::timestamptz` 这类 SQL cast；`tokio-postgres` 会按目标 PostgreSQL 类型序列化参数，字符串参数可能在序列化阶段失败，导致 provider 已发送但数据库仍停在旧采样。等价实现应先解析为 `DateTime<Utc>` 或目标驱动支持的时间类型，再传入 SQL 参数。

`/api/nodes/system-status` 必须区分当前指标和最后已知指标。超过 backend stale window 的采样不得继续作为 `current` 冒充实时状态；接口和前端应暴露 `stale`、`currentCollectedAt` 和 `lastKnown`，并在指标过期时显示过期状态。运维验证优先使用 backend-core 内部只读入口：`docker exec unidesk-backend-core sh -lc 'backend-core --fetch-json http://127.0.0.1:8080/api/nodes/system-status?limit=5'`。外部 frontend 同源 API 可能受登录 session 保护，不应把未登录请求的 401 当成资源同步失败。

## Low-Memory Diagnostics

主 server 是低资源、低抖动控制面，排查内存时必须先区分共享内存、容器 cgroup 占用和进程私有占用。PostgreSQL 后端进程的 RSS 会重复显示 `shared_buffers` 等共享映射，不能把多个 `postgres` 进程 RSS 简单相加当成真实内存消耗；优先看 `docker stats unidesk-database`、cgroup memory、`/proc/<pid>/smaps_rollup` 的 PSS/USS、`pg_stat_activity` 连接数和 `pg_settings` 中的 `shared_buffers`/`work_mem`。

如果 PostgreSQL 容器总占用和 PSS 并不异常，不应优先通过压缩 `shared_buffers` 解决主 server OOM。更高优先级是识别非核心、交互式和开发型进程，例如 web terminal、长驻 agent session、一次性日志调查或大输出 CLI，把它们迁移到 D601、增加 TTL/硬上限，或通过 `server logs`、`job status`、`microservice proxy` 的默认输出限额减少瞬时内存尖峰。只有在连接池、真实 cgroup 占用和慢查询证据都指向 PostgreSQL 时，才调整 PostgreSQL 内存参数。

性能优化必须先用这些指标锁定慢操作名称、路径、耗时和代理层级，再改后端查询或前后端通信策略；不得只凭主观体感改 UI。Code Queue 这类控制面页面出现 `core_proxy`、`GET /api/microservices/code-queue/proxy/api/tasks/overview`、`POST /api/microservices/code-queue/proxy/api/tasks/<id>/read` 等超过 1s 的慢操作时，应保留优化前后的性能面板证据，并同时记录 live API 耗时、容器内存、`/health` 存储摘要和是否仍通过 PostgreSQL/append-only archive 重建历史数据。短 TTL cache、warmup 或页面内存缓存只能作为重复请求抖动保护，性能证据必须证明数据库索引/聚合、分页和渐进式披露本身已把核心路径降到目标内，不能用长缓存遮蔽慢 SQL 或全量 JSON 物化。

当最近失败请求集中出现 frontend `core_proxy` 502/503/504，路径为 `/api/microservices/code-queue/proxy/...` 的 overview、trace 或 summary，且 k3s/k8s Pod 仍在运行时，必须先运行 `bun scripts/cli.ts microservice diagnostics code-queue`，区分 provider-gateway online、WebSocket HTTP tunnel、k3sctl-adapter、Kubernetes API service proxy 和目标 Service 五段状态。provider tunnel 类失败必须记录响应 body/headers 中的 `requestId`、`stage`、`failureReason`、`x-unidesk-request-id` 和 `x-unidesk-tunnel-error`；如需主动验证错误结构，运行 `bun scripts/cli.ts microservice tunnel-self-test code-queue`，该自测应返回预期失败但 `ok=true` 的诊断结果。随后再继续判断“Kubernetes API service proxy 不可达”“Code Queue 进程不可达”和“Code Queue event loop 被热路径同步工作饿死”。如果 `debug health` 或 provider-gateway egress health 显示 `providerGatewayEgressProxyActiveTunnels` 持续偏高、`pendingTunnels` 非零或 `oldestTunnelAgeMs` 长时间增长，应先按 provider-gateway egress tunnel 生命周期排障，确认 `egress_tcp_open`、connect timeout、idle cleanup 与 core socket close 清理是否生效。排障顺序是同时查看 `/api/frontend-performance`、`/api/performance`、`k3sctl-adapter` `/api/control-plane`、Kubernetes Pod `/live`、`/health`、overview/trace-step curl、`kubectl top pod` 或 Docker stats、容器 `RestartCount`/`OOMKilled` 和 Code Queue 日志；如果 Pod 内 `/health` 也超时，应优先检查实时 output 发布、archive 读取、transcript 构建、统计计算、启动维护、历史 OA backfill 和远程 Provider 准备/SSH 子进程是否阻塞 event loop，而不是先调整 frontend 渲染或代理超时。Code Queue 默认不得在启动时自动执行历史 OA backfill 或通知表索引维护；显式 backfill 必须作为运维动作记录，并在运行期间并发证明 `/live`、`/health` 与 `/api/tasks/overview` 仍快速返回。涉及 D601 等远程 Provider 时，还要检查 `runCodeQueueSsh`/开发容器准备是否仍存在同步子进程、无 timeout 的 SSH、无上限 stdout/stderr 或 stale TUN 重建等待；修复后必须在远程准备探针运行期间并发证明 Pod `/health` 与 `/api/tasks/overview` 仍快速返回。

Code Queue task 明明产出最终回复却反复 `retry_wait` 时，应优先用任务详情里的 latest attempt 字段核查 `terminalStatus`、`transportClosedBeforeTerminal`、`appServerExitCode`、`finalResponseChars`、`judge.raw._safetyOverride` 和 attempt output。OpenCode 远程任务中，`opencode completed status=completed exit=0` 加当前 attempt 非空 assistant 输出应对应 `terminalStatus=completed`、`transportClosedBeforeTerminal=false`；如果因为缺少 `step_finish` 事件仍触发 `_safetyOverride=terminal_not_completed`，说明协议终态归一化有回归。相反，当前 attempt 没有最终 assistant response 时即使 tool/read/bash 证据完整，也必须 retry，不能用旧 `task.finalResponse` 或 reasoning/tool evidence 代替可见最终回复。

### Code Queue Liveness

Code Queue 的“任务是否卡死”不能由单一控制面字段判断。排障必须同时看 PostgreSQL 中的 `running`/`judging` 任务、D601 scheduler 本地 active run/active slot/active queue、scheduler-owned heartbeat、Trace/OA 持久化进度和 OA publisher pending/lastError。master `code-queue-mgr` 的 `postgres-control-plane` 视图只证明数据库行存在；当它显示 `activeRunSlotCount=0` 但 D601 heartbeat 仍新鲜时，正确结论是 control-plane/execution-plane 分裂，diagnostics 应显示 `split-brain` 或 `degraded`，不能宣称任务未执行或卡死。诊断输出中的 `effectiveLiveness=live`、`splitBrainLive=true` 和 `recommendedAction=continue-supervision` 表示这是 heartbeat 新鲜的观测分裂，应继续监督；`effectiveLiveness=at-risk` 或 `recommendedAction=investigate-heartbeat-risk` 表示存在 expired/missing/stale heartbeat 风险，需要优先人工确认。

Trace/OA 长时间没有新 seq 但 scheduler heartbeat 正常时，应归类为 trace gap 或 publisher degraded，不得自动 retry。只有 scheduler 本地没有 active run，且对应 owner heartbeat 已过期时，才允许进入 stale recovery candidate；缺失 heartbeat 只能触发 degraded 诊断和人工确认。任何恢复入口都必须由 scheduler 执行，使用条件更新和审计事件区分 user interrupt、admin stale recovery 与 service restart recovery；禁止直接修改 production PostgreSQL 任务状态来“修复” active run。
