name: ssh-ops
description: |
  SSH 経由でリモートホストに対するオペレーションを実行する。
  サーバー稼働確認 (health check)、設定ファイル配信とリロード (config push)、
  ログ取得と分析 (log fetch) の 3 軸をカバーする ops piece。

  選ぶべき場合: タスクが「SSH で〜したい」「リモートサーバーで〜を実行」「サーバーから〜を取得」等
  選ぶべきでない場合: ローカル作業のみ、Web 調査のみ、Office 加工のみ

  事前条件:
  - admin が `config.yaml` の `ssh.enabled: true` を設定済み
  - 利用する SSH 接続が登録され、TOFU host key 検証が完了
    (Settings → User Folder → SSH Connections → 該当接続 → Test)
  - ジョブ owner に接続への grant がある (private は owner 自身、global は admin の grant)

  詳細: docs/ssh.md (operator runbook) と docs/tools/ssh-tools.md (LLM 向け)
triggers:
  keywords: ["SSH", "リモート", "サーバー", "デプロイ", "ヘルスチェック", "ログ取得", "remote"]
max_movements: 50
initial_movement: execute

movements:
  - name: execute
    edit: true
    persona: ops-operator
    instruction: |
      ## 最初のステップ: タスク把握と接続の選定

      1. Glob で input/ と output/ の現状を確認する
      2. タスク本文を読み、以下のどの軸かを判定する (複合も可):
         - Health check: uptime / df -h / free -m / process status / journalctl 等で状態確認
         - Config push: ローカルで作成・編集した設定を SshUpload で配信 → SshExec でリロード
         - Log fetch: SshDownload でリモートのログを取得 → ローカルで grep / 集計 / 分析
      3. タスクで指定された SSH 接続 ID を確認する。指定が無く接続候補が複数ある場合は
         `complete({status: "needs_user_input", missing_info: "どの SSH 接続を使うか"})` で確認する

      ## SshExec の使い方

      - 単発コマンド: `SshExec({connection_id, command})`
      - output は JSON envelope (`{stdout, stderr, exit_code, truncated, ...}`)。
        `truncated: true` の場合は出力が大きすぎる → `SshDownload` で file 経由に切り替える
      - 機密値 (token / password) は command 文字列に直接渡さない。リモート側の env や config に置く
      - 接続ごとに deny-list / allow-list が設定されていることがある。`command_rejected` エラーは
        admin に許可パターン追加を相談する (ローカルで回避してはいけない)

      ## SshUpload / SshDownload の使い方

      - リモートパスは接続の `remote_path_prefix` 配下のみ書き換え可能。違反は `path_not_allowed` で reject
      - ローカルパスは workspace の output/ または input/ 配下を推奨
      - 大きなファイル: 上限は接続/グローバル設定の `max_upload_size_mb` / `max_download_size_mb`
      - Download 先のファイルが既にある場合は `local_target_exists` で reject される。
        旧版を消すかリネームしてから再実行する

      ## エラーハンドリング (詳細は docs/tools/ssh-tools.md の error code 表)

      - `host_key_not_verified`: TOFU 未完了。
        `complete({status: "needs_user_input", missing_info: "SSH 接続 <id> の host key を UI で検証してください (Settings → User Folder → SSH Connections → Test)"})` で停止する
      - `host_key_mismatch`: MITM 疑い。**自動でリトライしない**。
        `complete({status: "aborted", abort_reason: "host_key_mismatch: <details>"})` で停止する
      - `abuse_locked`: 連続失敗で接続がロック。
        `complete({status: "needs_user_input", missing_info: "接続が <until> までロックされています。admin に force-unlock を依頼してください"})` で停止する
      - `no_grant` / `access_denied`: 権限不足。admin に grant 追加を依頼するよう user に報告して停止する
      - `connect_timeout` / `auth_failed` 等の一時失敗: 同じ command を最大 2 回まで再試行。
        それ以上は `complete({status: "aborted", abort_reason: "..."})`

      ## 調べ物 (Web 検索)

      - 不明なコマンド・オプション、エラーメッセージ、設定手順が出てきたら WebSearch で調べてよい。
        ヒットしたページや公式 docs の本文は WebFetch、JS レンダリングや操作が要るページは BrowseWeb で読む
      - 設定テンプレート / インストーラ / tarball を URL から取得して配信したい場合は DownloadFile で
        output/ または input/ に落とし、SshUpload でリモートへ送る
      - 検索結果を鵜呑みにしてリモートで破壊的・不可逆なコマンドを実行しない。出典を確認し、不安なら停止して user に確認する

      ## 成果物

      ops の結果は output/report.md にまとめる。**機密値は記録しない**:
      - 実行した command (機密値はマスク) と使用した接続 ID
      - SshExec の場合は stdout/stderr の要点 (全文ではなく要約。重要な行のみ転載)
      - SshUpload/SshDownload の場合は転送したファイル名 + サイズ
      - 観測した状態 / 異常があれば項目立てて記述
      - 推奨アクション (異常があれば「再起動を提案」等) または「異常なし」の明示

      ## 終了 / 遷移方法

      - **次の verify へ**: `transition({next_step: "verify"})`
      - **必要情報不足で停止**: `complete({status: "needs_user_input", missing_info: "...", why_no_default: "..."})`
      - **致命的失敗で打ち切り**: `complete({status: "aborted", abort_reason: "..."})`
    allowed_tools: [SshExec, SshUpload, SshDownload, SshListConnections, WebSearch, WebFetch, DownloadFile, BrowseWeb, Read, Write, Bash, Glob, Grep]
    allowed_ssh_connections: ['*']
    default_next: verify
    rules:
      - condition: output/report.md に ops 結果をまとめた
        next: verify

  - name: verify
    edit: false
    persona: reviewer
    instruction: |
      ops 結果を確認する。

      確認手順:
      1. Glob で output/report.md の存在を確認する
      2. 報告書が無い、または内容が抽象論だけで実行結果が記載されていない場合は execute に差し戻す
      3. Read で report.md を読み、以下をチェック:
         - 実行した command / 接続 ID が記録されているか
         - 観測結果 (stdout 要点 or 転送ファイル一覧) が記載されているか
         - 異常があった場合、推奨アクションが書かれているか
         - **機密値 (token / password / 秘密鍵 fingerprint 全体 / .env 内容等) が漏れていないか**
      4. 不足があれば `transition({next_step: "execute", summary: ...})` で差し戻す:
         [判定] needs_fix
         ## 問題点
         - [報告書の項目] 何が問題か
         ## 期待する修正
         - 何をどう直すべきか
         ## 合格基準
         - 再レビューで何を確認するか
         ## 次にやること
         - execute で最初に着手すべき具体的な作業
      5. **機密値漏れを検出した場合は ABORT** (差し戻さない、ファイルにも残さない):
         `complete({status: "aborted", abort_reason: "secret_leak: report.md contained <field> credential"})`

      ## 合格時のユーザーへの返答

      `complete({status: "success", result: ...})` で output/report.md の要点を会話調で返す。
      result そのものが user に表示される最終回答 (「report.md を確認」のような参照ではなく内容を書く)。
      - 1 行目からいきなり本題: 「✅ 完了」等のメタ文言は禁止
      - 観測した状態 + 推奨アクションを構造化して提示
      - 異常無しなら明示する

      ## 終了方法のまとめ
      - 合格: `complete({status: "success", result: "ユーザー向け最終回答"})`
      - 修正必要: `transition({next_step: "execute", summary: "差し戻し指摘"})`
      - 機密漏れ: `complete({status: "aborted", abort_reason: "secret_leak: ..."})`
      - 技術的失敗: `complete({status: "aborted", abort_reason: "..."})`
    allowed_tools: [Read, Glob, Grep]
    default_next: COMPLETE
    rules:
      - condition: ops 報告が不足している
        next: execute
