Why: A2 - answer where a run is stuck, who is responsible, why it waits, the last stable checkpoint, known side effects and the next step, from existing records only (recovery holds, HITL decisions, confirmation cards, audit envelopes). Evidence: rundiag/server/store tests pass (verify-a2.json); per-state responsibility, scoping and next-step mapping cases added. Impact: one read-only GET endpoint plus read helpers; no behavior change to execution or recovery paths. Refs: feature/exec-recovery-diagnostics-a2
33 lines
1.3 KiB
Go
33 lines
1.3 KiB
Go
package server
|
||
|
||
import (
|
||
"net/http"
|
||
"time"
|
||
|
||
"agent-system-backend/internal/rundiag"
|
||
)
|
||
|
||
// getRunDiagnostics 是单次 run 的只读诊断入口(feature unit A2):
|
||
//
|
||
// GET /api/runs/{id}/diagnostics
|
||
//
|
||
// 它回答值班/排障最常问的六个问题:这个 run 卡在哪(responsibility.state)、
|
||
// 谁该动(responsibility.role)、为什么在等(responsibility.wait_reason)、最后一个
|
||
// 稳定落盘点(last_checkpoint)、已知的外部副作用(side_effects)、下一步动作
|
||
// (next_step)。
|
||
//
|
||
// 授权口径与既有 run 读入口逐字同款:owner 只取自认证上下文,绝不接受请求参数;
|
||
// run 由 store.GetRun(owner, id) 做租户过滤,跨租户与不存在同为 404(不泄露存在性)。
|
||
// handler 路径上只有 SELECT——不给写操作、不改状态、不触发任何副作用;聚合逻辑在
|
||
// internal/rundiag,证据缺失一律降级为 unknown + notes,不编造检查点,也不把"没有
|
||
// 记录"读成"没有发生"。
|
||
func (s *Server) getRunDiagnostics(w http.ResponseWriter, r *http.Request) {
|
||
run, err := s.st.GetRun(s.owner(r), r.PathValue("id"))
|
||
if err != nil {
|
||
writeStoreErr(w, err)
|
||
return
|
||
}
|
||
evidence := rundiag.Gather(s.st, run, time.Now().UTC())
|
||
writeJSON(w, http.StatusOK, rundiag.Build(evidence))
|
||
}
|