117 lines
25 KiB
Markdown
117 lines
25 KiB
Markdown
# UniDesk Observability Reference
|
||
|
||
UniDesk 的可观测性优先级高于静默成功。CLI、服务日志、Docker 日志和数据库状态都必须能通过短命令查询。
|
||
|
||
## Capability Boundary
|
||
|
||
可观测性是定位、验收和排障手段,不是功能完成标准。Trace、日志、诊断文案、fallback 标记、只读原因、降级提示和 issue 进展只能说明系统在哪里失败、为什么失败、修复后如何验证;它们不能把缺失能力包装成已完成,也不能把未修复功能包装成可接受状态。
|
||
|
||
当证据表明某条用户路径需要的能力不存在、没有真正接入、没有复用预期资源、没有返回最终业务结果,或仍停留在 mock、fixture、fallback、只读替代和提示文案时,正确动作是补齐能力或修复功能本身。允许先补最小观测来定位根因,但完成条件必须回到原始用户入口的真实行为闭环,并证明能力已经可用。
|
||
|
||
因此,CLI、Web、trace 和 issue 评论中的状态字段必须避免把“已观测到缺口”“已显示失败原因”“已提供诊断入口”表达成“问题已解决”。如果当前阶段只能增加可见性,输出应明确标记为诊断进展或阻塞定位;不得关闭对应功能 issue,不得把观测增强当作验收通过。
|
||
|
||
## 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 哨兵 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 来解释旧记录。
|
||
|
||
## 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` 当作长期证据源。
|
||
|
||
全局 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。
|