Files
selfheal-lab-apps/ai/internal/hitl/i18n.go
T
seon f8e0aa9122 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 フィルタを付け、判定と同じ式にそろえる。
2026-10-09 08:33:41 +09:00

110 lines
5.6 KiB
Go
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.
package hitl
import "fmt"
// Strings holds every user-facing text of the bot. The language is chosen once at start (HITL_LANG=en|ja).
// Identifiers (action ids, labels, run ids, JSON) stay as they are; only the surrounding text changes.
type Strings struct {
ProposalTitle func(actionID string) string
FieldRootCause string
FieldTarget string
FieldEvidence string
FieldRationale string
FieldRun string
FieldStatus string
FieldResult string
FieldAlert string
RootCauseValue func(label string, conf float64) string
ButtonApprove string
ButtonReject string
StatusWaiting func(unixTS int64) string
StatusRejected func(by string) string
StatusExecuting func(by string) string
StatusExecuted func(by string) string
StatusApproveFailed func(err string) string
StatusExecFailed func(by, err string) string
StatusExpiredBefore string
StatusNoDecision string
StatusApprovedAPI func(by string, executed bool) string
ExpiryMessage func(actionID, target, runID, scenarioID string) string
EscalationTitle func(label string) string
EscalationStatus string
ThreadRunbook string
NotAllowed string
Footer func(model, prompt, session string) string
Empty string
}
var langs = map[string]Strings{
"en": {
ProposalTitle: func(a string) string { return "Layer-2 proposal: " + a },
FieldRootCause: "Root cause", FieldTarget: "Target", FieldEvidence: "Evidence", FieldRationale: "Rationale",
FieldRun: "Run / scenario", FieldStatus: "Status", FieldResult: "Result", FieldAlert: "Alert",
RootCauseValue: func(l string, c float64) string { return fmt.Sprintf("%s (confidence %.2f)", l, c) },
ButtonApprove: "Approve", ButtonReject: "Reject",
StatusWaiting: func(t int64) string { return fmt.Sprintf("waiting for approval until <t:%d:T>", t) },
StatusRejected: func(by string) string { return "rejected by " + by },
StatusExecuting: func(by string) string { return "approved by " + by + ", executing..." },
StatusExecuted: func(by string) string { return "approved by " + by + "; executed" },
StatusApproveFailed: func(e string) string { return "approval failed: " + e },
StatusExecFailed: func(by, e string) string { return "approved by " + by + "; execution failed: " + e },
StatusExpiredBefore: "expired before approval",
StatusApprovedAPI: func(by string, executed bool) string {
if executed {
return "approved through the harness API by " + by + "; executed"
}
return "approved through the harness API by " + by
},
StatusNoDecision: "no decision within the window; moved to layer 3",
ExpiryMessage: func(a, t, r, s string) string {
return fmt.Sprintf("Proposal `%s` for `%s` (run %s, scenario %s) received no decision within the approval window. Layer 3: follow the runbook or call the harness `/actions/execute` with the operator token.", a, t, r, s)
},
EscalationTitle: func(l string) string { return "Escalation: " + l },
EscalationStatus: "layer 3: human decision required",
ThreadRunbook: "Runbook:\n",
NotAllowed: "You are not in the approver role.",
Footer: func(m, p, s string) string { return fmt.Sprintf("model %s, prompt %s, session %s", m, p, s) },
Empty: "-",
},
"ja": {
ProposalTitle: func(a string) string { return "第2層の提案: " + a },
FieldRootCause: "推定原因", FieldTarget: "対象", FieldEvidence: "根拠", FieldRationale: "判断理由",
FieldRun: "Run / シナリオ", FieldStatus: "状態", FieldResult: "実行結果", FieldAlert: "アラート",
RootCauseValue: func(l string, c float64) string { return fmt.Sprintf("%s(確信度 %.2f)", l, c) },
ButtonApprove: "承認", ButtonReject: "却下",
StatusWaiting: func(t int64) string { return fmt.Sprintf("承認待ち(期限 <t:%d:T>)", t) },
StatusRejected: func(by string) string { return by + " が却下" },
StatusExecuting: func(by string) string { return by + " が承認、実行中..." },
StatusExecuted: func(by string) string { return by + " が承認、実行済み" },
StatusApproveFailed: func(e string) string { return "承認の記録に失敗: " + e },
StatusExecFailed: func(by, e string) string { return by + " が承認したが実行に失敗: " + e },
StatusExpiredBefore: "承認前に期限切れ",
StatusApprovedAPI: func(by string, executed bool) string {
if executed {
return by + " がハーネス API で承認、実行済み"
}
return by + " がハーネス API で承認"
},
StatusNoDecision: "期限内に判断がなかったため第3層へ引き継ぎ",
ExpiryMessage: func(a, t, r, s string) string {
return fmt.Sprintf("提案 `%s`(対象 `%s`、run %s、シナリオ %s)は承認期限内に判断されませんでした。第3層の対応です。ランブックに従って手動で対処するか、オペレーター用トークンでハーネスの `/actions/execute` を呼び出してください。", a, t, r, s)
},
EscalationTitle: func(l string) string { return "エスカレーション: " + l },
EscalationStatus: "第3層: 人の判断が必要",
ThreadRunbook: "ランブック:\n",
NotAllowed: "承認ロールが付与されていません。",
Footer: func(m, p, s string) string {
return fmt.Sprintf("モデル %s、プロンプト %s、セッション %s", m, p, s)
},
Empty: "-",
},
}
// Lang returns the string set for a language code; unknown codes fall back to English.
func Lang(code string) Strings {
if s, ok := langs[code]; ok {
return s
}
return langs["en"]
}