Files
pikasTech-unidesk/docs/reference/observability.md
T
2026-07-02 08:25:36 +00:00

131 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UniDesk Observability Reference
UniDesk 的可观测性优先级高于静默成功。CLI、服务日志、Docker 日志和数据库状态都必须能通过短命令查询。
## Capability Boundary
可观测性是定位、验收和排障手段,不是功能完成标准。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 signalsentinel report 不能把 runner API、SQLite index 和 artifact findings 对齐。工具改进应优先落在 CLI/analyzer/summary/parser 或 instrumentation 的可见性层,并用既有 trace/artifact 做回归验证;若工具改进需要实际部署或新采样,必须先把离线调查结论停在“工具缺口”,再取得明确授权后进入对应运行面流程。
## 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。
`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 本身失败。
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 的语义。
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 writerRust 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` 当作长期证据源。
全局 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 终态回报的任务做超时处理。超时任务转为 failedresult 中保留 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。