feat: alert dedupe, proposal state API, HITL mirrors API approvals, Jev recovering reads the alert condition

第 4 回キャンペーン(2026-10-08)の 3 つの穴を塞ぐ。
- ハーネス: 同じアラート名が ALERT_DEDUPE_S(既定 300 秒)以内に再着したら
  /diagnose を送らず note(dedupe) に記録する。Grafana は Pod ごとに 1 件ずつ
  出し 30 秒後にグループを再送するため、1 障害が診断 4 件・提案 4 件になっていた。
  全件 resolved になれば窓をリセットする。
- ハーネス: GET /proposals/{token} で承認・実行状態を返す。
- hitl: 60 秒ごとに未決の提案をハーネスへ照会し、API で承認された件は
  メッセージを「ハーネス API で承認」に書き換えてタイマーを外す。期限切れ処理の
  直前にも同じ照会を行う。
- AI: Jev 選別の now に alert_condition_now / alert_condition_clear /
  recovered_now を追加し、recovering は recovered_now を読む。SLO に触れない
  障害(replay 停止、メモリ圧迫)で 0.98 を返していた。now の p95 と 5xx 比率に
  route フィルタを付け、判定と同じ式にそろえる。
This commit is contained in:
seon committed 2026-10-09 08:33:41 +09:00
1 parent 8836b37927
commit f8e0aa9122
9 files changed
+229 -9

No files matched your search

+21
View File
@@ -57,6 +57,27 @@ type Proposal struct {
Approved bool `json:"approved"`
}
// ProposalState is GET /proposals/{token}: the decision as the harness knows it (approval may come through the API).
type ProposalState struct {
Token string `json:"token"`
ActionID string `json:"action_id"`
Approved bool `json:"approved"`
ApprovedAt *string `json:"approved_at"`
ApprovedBy *string `json:"approved_by"`
Executed bool `json:"executed"`
ExecutedAt *string `json:"executed_at"`
ExpiresAt string `json:"expires_at"`
}
// GetProposal reads the harness-side state of a proposal.
func (c *Client) GetProposal(ctx context.Context, token string) (*ProposalState, error) {
var out ProposalState
if _, err := c.call(ctx, "GET", "/proposals/"+token, nil, &out); err != nil {
return nil, err
}
return &out, nil
}
// Propose registers a layer-2 proposal (POST /proposals); the harness records the proposal event and returns the token.
func (c *Client) Propose(ctx context.Context, runID, scenarioID *string, actionID string, target, diagnosis map[string]any, ttl time.Duration) (*Proposal, error) {
body := map[string]any{"run_id": runID, "scenario_id": scenarioID, "action_id": actionID, "target": target, "diagnosis": diagnosis}