From 609eaf52e497e95e144dcc7600bedf958db7d7a9 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Tue, 23 Jun 2026 16:11:49 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat(agent):=20Gemini/Anthropic=20=E3=82=92?= =?UTF-8?q?=20Vertex=20AI=EF=BC=88SA=E2=86=92ADC=EF=BC=89=E7=B5=8C?= =?UTF-8?q?=E7=94=B1=E3=81=AB=E7=A7=BB=E8=A1=8C=EF=BC=88ADR-0015=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 職務経歴書(PII)の学習リスク回避のため Gemini Developer API 無料枠を廃し、 Gemini と Anthropic を Vertex AI(Cloud Run の SA → ADC)経由に切り替える。 OpenAI のみ GCP に存在しないため API キーを継続する。 - google_client: genai.Client(vertexai=True, project, location) 化 - anthropic_client: AsyncAnthropicVertex(project_id, region) 化(generate は無変更) - model_catalog: Haiku を Vertex 版 ID(claude-haiku-4-5@20251001)に (Sonnet 4.6 / Gemini は同 ID) - settings/env_keys: API キー getter を GCP_PROJECT_ID + VERTEX_LOCATION / VERTEX_ANTHROPIC_LOCATION に置換(Gemini=Tokyo / Claude=Singapore) - requirements: anthropic[vertex](ADC 用 google-auth) - infra(cloud_run): ANTHROPIC_API_KEY/GOOGLE_API_KEY 注入停止・Vertex location 注入追加 (secret コンテナ削除は別 PR) - docker-compose / docs/api.md 更新・ADR-0015 起票 - tests: key 未設定→project 未設定に置換、billing の Haiku model_id assert を更新 Co-Authored-By: Claude Opus 4.8 --- backend/app/core/env_keys.py | 18 +++--- backend/app/core/settings.py | 23 +++++-- .../services/agent/llm/anthropic_client.py | 20 ++++-- backend/app/services/agent/llm/base.py | 12 ++++ .../app/services/agent/llm/google_client.py | 13 ++-- backend/app/services/agent/model_catalog.py | 4 +- backend/requirements.txt | 4 +- backend/tests/test_billing.py | 5 +- backend/tests/test_llm_clients.py | 36 +++++++---- docker-compose.yml | 8 ++- .../0015-vertex-ai-for-gemini-anthropic.md | 61 +++++++++++++++++++ docs/api.md | 6 +- infra/modules/cloud_run/main.tf | 23 +++++-- infra/modules/cloud_run/variables.tf | 12 ++++ 14 files changed, 193 insertions(+), 52 deletions(-) create mode 100644 docs/adr/0015-vertex-ai-for-gemini-anthropic.md diff --git a/backend/app/core/env_keys.py b/backend/app/core/env_keys.py index 0381e10e..c1b75211 100644 --- a/backend/app/core/env_keys.py +++ b/backend/app/core/env_keys.py @@ -88,19 +88,23 @@ LOG_FORMAT = "LOG_FORMAT" LOG_LEVEL = "LOG_LEVEL" -# --- LLM(DevForge Agent / ADR-0010・ADR-0013) --- +# --- LLM(DevForge Agent / ADR-0010・ADR-0013・ADR-0015) --- # ローカル Ollama 上書き("1"/"true"/"yes" で有効)。選択モデルに関わらず全リクエストを # ローカル Ollama に通す無料パス。本番(Cloud Run)では未設定=無効。 # プロバイダ選択はモデルエイリアスに紐づくため、グローバルな LLM_PROVIDER は廃止(ADR-0013) LLM_LOCAL_OLLAMA = "LLM_LOCAL_OLLAMA" +# Gemini / Anthropic は Vertex AI(Cloud Run の SA → ADC)経由で叩く(ADR-0015)。 +# 認証は SA + GCP_PROJECT_ID(Cloud Tasks と共用)で行い、API キーは持たない。 +# ロケーションは provider 別: Gemini=asia-northeast1、Claude=asia-southeast1(Tokyo に +# Claude が無いため)。infra(cloud_run)が plaintext env として注入する。 +VERTEX_LOCATION = "VERTEX_LOCATION" +VERTEX_ANTHROPIC_LOCATION = "VERTEX_ANTHROPIC_LOCATION" +# OpenAI のみ GCP に存在しないため API キーを継続使用(ADR-0015)。 # 本番(Cloud Run)では Secret Manager から注入する。ログ出力禁止。 -# 注: LLM API キー(ANTHROPIC/GOOGLE/OPENAI)はテストがプロバイダを _FakeLLM で -# モックするため CI(.github/workflows/ci.yml)には注入不要。env_keys の 5 箇所同期 -# のうち ci.yml だけは意図的に対象外とする(実 API を CI から呼ばないため)。 -ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY" -# Google Gemini / OpenAI GPT の API キー(ADR-0013)。Secret Manager 注入・ログ出力禁止 -GOOGLE_API_KEY = "GOOGLE_API_KEY" +# 注: OPENAI_API_KEY はテストがプロバイダを _FakeLLM でモックするため CI +# (.github/workflows/ci.yml)には注入不要。env_keys の 5 箇所同期のうち ci.yml +# だけは意図的に対象外とする(実 API を CI から呼ばないため)。 OPENAI_API_KEY = "OPENAI_API_KEY" OLLAMA_BASE_URL = "OLLAMA_BASE_URL" OLLAMA_MODEL = "OLLAMA_MODEL" diff --git a/backend/app/core/settings.py b/backend/app/core/settings.py index 3954a688..abfb78f7 100644 --- a/backend/app/core/settings.py +++ b/backend/app/core/settings.py @@ -180,14 +180,25 @@ def use_local_ollama() -> bool: return os.getenv(env_keys.LLM_LOCAL_OLLAMA, "").strip().lower() in {"1", "true", "yes"} -def get_anthropic_api_key() -> str: - """Anthropic API キーを取得する。値はログや例外メッセージに含めないこと。""" - return os.getenv(env_keys.ANTHROPIC_API_KEY, "").strip() +def get_gcp_project_id() -> str: + """Vertex AI(Gemini / Anthropic)が使う GCP プロジェクト ID を取得する(ADR-0015)。 + Cloud Tasks と同じ ``GCP_PROJECT_ID`` を再利用する。Cloud Run へは infra が注入する。 + """ + return os.getenv(env_keys.GCP_PROJECT_ID, "").strip() + + +def get_vertex_location() -> str: + """Gemini を叩く Vertex AI のロケーションを取得する(既定: asia-northeast1 / ADR-0015)。""" + return os.getenv(env_keys.VERTEX_LOCATION, "asia-northeast1").strip() -def get_google_api_key() -> str: - """Google Generative AI(Gemini)API キーを取得する。値はログに含めないこと。""" - return os.getenv(env_keys.GOOGLE_API_KEY, "").strip() + +def get_vertex_anthropic_location() -> str: + """Claude を叩く Vertex AI のロケーションを取得する(既定: asia-southeast1 / ADR-0015)。 + + Claude は Tokyo(asia-northeast1)で提供されないため、最寄りの Singapore を既定にする。 + """ + return os.getenv(env_keys.VERTEX_ANTHROPIC_LOCATION, "asia-southeast1").strip() def get_openai_api_key() -> str: diff --git a/backend/app/services/agent/llm/anthropic_client.py b/backend/app/services/agent/llm/anthropic_client.py index d056d2fd..8cb71992 100644 --- a/backend/app/services/agent/llm/anthropic_client.py +++ b/backend/app/services/agent/llm/anthropic_client.py @@ -1,12 +1,18 @@ -"""Anthropic API クライアント(本番用。モデルは model_catalog.py で解決)。""" +"""Anthropic API クライアント(本番用。モデルは model_catalog.py で解決)。 + +認証は Vertex AI Model Garden(Cloud Run の SA → ADC)経由で、API キーは使わない +(ADR-0015)。Messages API のロジック(tool use 強制 / usage 抽出)は Vertex +クライアントでも共通のため `generate()` は据え置く。 +""" import json import anthropic +from anthropic import AsyncAnthropicVertex from ....core import settings from ..output_schema import TOOL_NAME, build_tool_definition -from .base import LLMClient, LLMError, LLMResult, require_api_key, wrap_api_error +from .base import LLMClient, LLMError, LLMResult, require_gcp_project, wrap_api_error # operations JSON(最大 4500 文字のテキスト置換 + 説明文)に十分な上限 _MAX_TOKENS = 4096 @@ -19,10 +25,12 @@ class AnthropicClient(LLMClient): """Anthropic Messages API を呼び出すクライアント。""" def __init__(self) -> None: - """ANTHROPIC_API_KEY を検証し、非同期クライアントを初期化する。""" - api_key = require_api_key(settings.get_anthropic_api_key(), "ANTHROPIC_API_KEY") - self._client = anthropic.AsyncAnthropic( - api_key=api_key, timeout=_TIMEOUT_SECONDS + """GCP プロジェクトを検証し、Vertex AI 経由の非同期クライアントを初期化する。""" + project = require_gcp_project(settings.get_gcp_project_id()) + self._client = AsyncAnthropicVertex( + project_id=project, + region=settings.get_vertex_anthropic_location(), + timeout=_TIMEOUT_SECONDS, ) async def generate( diff --git a/backend/app/services/agent/llm/base.py b/backend/app/services/agent/llm/base.py index 23669d8b..e4284044 100644 --- a/backend/app/services/agent/llm/base.py +++ b/backend/app/services/agent/llm/base.py @@ -84,6 +84,18 @@ def require_api_key(value: str, label: str) -> str: return value +def require_gcp_project(value: str) -> str: + """Vertex AI クライアント(Gemini / Anthropic)の GCP プロジェクト ID を検証する。 + + Vertex 経由のプロバイダは API キーの代わりに SA(ADC) + プロジェクト ID で認証する + (ADR-0015)。空なら LLMError、非空ならそのまま返す。ローカルは LLM_LOCAL_OLLAMA で + Vertex クライアントを生成しないため、この検証は本番(Cloud Run)経路でのみ効く。 + """ + if not value: + raise LLMError("GCP_PROJECT_ID が設定されていません") + return value + + def wrap_api_error(provider: str, exc: Exception) -> "LLMError": """プロバイダ SDK 例外を LLMError へ変換する(ログ + 整形)。 diff --git a/backend/app/services/agent/llm/google_client.py b/backend/app/services/agent/llm/google_client.py index f9dc9392..a7e125ba 100644 --- a/backend/app/services/agent/llm/google_client.py +++ b/backend/app/services/agent/llm/google_client.py @@ -1,6 +1,7 @@ -"""Google Generative AI(Gemini)クライアント(ADR-0013)。 +"""Google Generative AI(Gemini)クライアント(ADR-0013 / Vertex 化は ADR-0015)。 モデルは model_catalog.py で解決した実 ID(gemini-2.5-flash / gemini-2.5-pro)を使う。 +認証は Vertex AI(Cloud Run の SA → ADC)経由で、API キーは使わない(ADR-0015)。 構造化出力は ``response_mime_type=application/json`` + ``response_schema``(controlled generation)で強制する。Anthropic の tool use とは機構が異なるが、応答テキストは同じく スキーマに従う JSON のシリアライズ文字列として返す。 @@ -12,7 +13,7 @@ from ....core import settings from ..output_schema import to_portable_schema -from .base import LLMClient, LLMResult, require_api_key, require_text, wrap_api_error +from .base import LLMClient, LLMResult, require_gcp_project, require_text, wrap_api_error # 職務経歴書の改善提案は事実忠実性が最優先のため低温度に固定する _TEMPERATURE = 0.2 @@ -23,10 +24,12 @@ class GoogleClient(LLMClient): """Gemini API(google-genai SDK)を呼び出すクライアント。""" def __init__(self) -> None: - """GOOGLE_API_KEY を検証し、非同期クライアントを初期化する。""" - api_key = require_api_key(settings.get_google_api_key(), "GOOGLE_API_KEY") + """GCP プロジェクトを検証し、Vertex AI 経由の非同期クライアントを初期化する。""" + project = require_gcp_project(settings.get_gcp_project_id()) self._client = genai.Client( - api_key=api_key, + vertexai=True, + project=project, + location=settings.get_vertex_location(), # HttpOptions.timeout はミリ秒単位(google-genai 仕様)。60s = 60000ms http_options=genai_types.HttpOptions(timeout=int(_TIMEOUT_SECONDS * 1000)), ) diff --git a/backend/app/services/agent/model_catalog.py b/backend/app/services/agent/model_catalog.py index 1e70870b..abba9813 100644 --- a/backend/app/services/agent/model_catalog.py +++ b/backend/app/services/agent/model_catalog.py @@ -79,7 +79,9 @@ class ModelSpec: MODEL_CATALOG: dict[str, ModelSpec] = { "haiku": ModelSpec( provider=PROVIDER_ANTHROPIC, - model_id="claude-haiku-4-5", + # Vertex AI の Anthropic model id は版指定が要る(@日付)。Sonnet 4.6 は版指定 + # 不要だが Haiku 4.5 は @20251001 が必須(ADR-0015 / Vertex Model Garden 正本) + model_id="claude-haiku-4-5@20251001", is_free=True, input_credits_per_mtok=_credits_per_mtok(_HAIKU_INPUT_USD_PER_MTOK), output_credits_per_mtok=_credits_per_mtok(_HAIKU_OUTPUT_USD_PER_MTOK), diff --git a/backend/requirements.txt b/backend/requirements.txt index 4ba54251..23128063 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,9 +14,11 @@ pytest==9.1.1 python-jose[cryptography]==3.5.0 httpx==0.28.1 # マルチプロバイダ LLM(ADR-0013)。Gemini = google-genai、GPT = openai。 +# Gemini / Anthropic は Vertex AI(SA→ADC)経由(ADR-0015)。anthropic[vertex] の +# extra が ADC 用の google-auth を引き込む。google-genai は vertexai=True で Vertex 対応。 # 供給網リスク(レンジ内 yank / 侵害バージョンの混入)を避けるため == で固定する。 # CVE は CI の pip-audit が検出するので、検出時に明示バージョンを引き上げる運用とする。 -anthropic==0.111.0 +anthropic[vertex]==0.111.0 google-genai==2.9.0 openai==2.43.0 # Stripe Checkout / Webhook 署名検証(ADR-0012 Phase 2)。公式ライブラリを使う diff --git a/backend/tests/test_billing.py b/backend/tests/test_billing.py index ad90f9a3..30f5dc44 100644 --- a/backend/tests/test_billing.py +++ b/backend/tests/test_billing.py @@ -84,7 +84,8 @@ def test_model_catalog_matches_schema_alias() -> None: def test_model_catalog_resolves_real_model_ids() -> None: """エイリアスから実モデル ID と無料/有料フラグが解決できる。""" - assert MODEL_CATALOG["haiku"].model_id == "claude-haiku-4-5" + # Vertex AI の Anthropic model id は版指定が要る(Haiku は @20251001 必須 / ADR-0015) + assert MODEL_CATALOG["haiku"].model_id == "claude-haiku-4-5@20251001" assert MODEL_CATALOG["haiku"].is_free is True assert MODEL_CATALOG["sonnet"].model_id == "claude-sonnet-4-6" assert MODEL_CATALOG["sonnet"].is_free is False @@ -177,7 +178,7 @@ def test_chat_haiku_works_with_zero_balance(client: TestClient, monkeypatch) -> resp = client.post("/api/agent/chat", json=_chat_payload(), headers=headers) assert resp.status_code == 200 - assert fake.received_model_id == "claude-haiku-4-5" + assert fake.received_model_id == "claude-haiku-4-5@20251001" user_id = _get_user_id(client, "billing-haiku") repo = BillingRepository(client._db_session, user_id) assert repo.get_balance() == 0 diff --git a/backend/tests/test_llm_clients.py b/backend/tests/test_llm_clients.py index 0daa40eb..934831fc 100644 --- a/backend/tests/test_llm_clients.py +++ b/backend/tests/test_llm_clients.py @@ -81,7 +81,13 @@ def _patch_google(monkeypatch, *, response=None, error=None) -> MagicMock: """google.genai.Client をモックし、aio.models.generate_content を差し替える。""" from app.services.agent.llm import google_client - monkeypatch.setattr(google_client.settings, "get_google_api_key", lambda: "test-key") + # Vertex 化(ADR-0015)後はキーではなく GCP プロジェクト + ロケーションで初期化する + monkeypatch.setattr( + google_client.settings, "get_gcp_project_id", lambda: "test-project" + ) + monkeypatch.setattr( + google_client.settings, "get_vertex_location", lambda: "asia-northeast1" + ) gen = AsyncMock(side_effect=error) if error else AsyncMock(return_value=response) fake_client = MagicMock() fake_client.aio.models.generate_content = gen @@ -112,13 +118,13 @@ def test_google_client_returns_text_and_usage(monkeypatch) -> None: assert "additionalProperties" not in str(config.response_schema) -def test_google_client_missing_key_raises(monkeypatch) -> None: - """GOOGLE_API_KEY 未設定は LLMError。""" +def test_google_client_missing_project_raises(monkeypatch) -> None: + """GCP_PROJECT_ID 未設定は LLMError(Vertex 認証の前提が無い / ADR-0015)。""" from app.services.agent.llm import google_client from app.services.agent.llm.google_client import GoogleClient - monkeypatch.setattr(google_client.settings, "get_google_api_key", lambda: "") - with pytest.raises(LLMError, match="GOOGLE_API_KEY"): + monkeypatch.setattr(google_client.settings, "get_gcp_project_id", lambda: "") + with pytest.raises(LLMError, match="GCP_PROJECT_ID"): GoogleClient() @@ -220,17 +226,23 @@ def test_openai_client_empty_response_raises(monkeypatch) -> None: def _patch_anthropic(monkeypatch, *, response=None, error=None) -> MagicMock: - """anthropic.AsyncAnthropic をモックし、messages.create を差し替える。""" + """anthropic.AsyncAnthropicVertex をモックし、messages.create を差し替える。""" from app.services.agent.llm import anthropic_client + # Vertex 化(ADR-0015)後はキーではなく GCP プロジェクト + ロケーションで初期化する + monkeypatch.setattr( + anthropic_client.settings, "get_gcp_project_id", lambda: "test-project" + ) monkeypatch.setattr( - anthropic_client.settings, "get_anthropic_api_key", lambda: "test-key" + anthropic_client.settings, + "get_vertex_anthropic_location", + lambda: "asia-southeast1", ) create = AsyncMock(side_effect=error) if error else AsyncMock(return_value=response) fake_client = MagicMock() fake_client.messages.create = create monkeypatch.setattr( - anthropic_client.anthropic, "AsyncAnthropic", lambda **kwargs: fake_client + anthropic_client, "AsyncAnthropicVertex", lambda **kwargs: fake_client ) return create @@ -258,15 +270,15 @@ def test_anthropic_client_returns_text_and_usage(monkeypatch) -> None: assert json.loads(result.text) == block.input -def test_anthropic_client_missing_key_raises(monkeypatch) -> None: - """ANTHROPIC_API_KEY 未設定は LLMError。""" +def test_anthropic_client_missing_project_raises(monkeypatch) -> None: + """GCP_PROJECT_ID 未設定は LLMError(Vertex 認証の前提が無い / ADR-0015)。""" from app.services.agent.llm import anthropic_client from app.services.agent.llm.anthropic_client import AnthropicClient monkeypatch.setattr( - anthropic_client.settings, "get_anthropic_api_key", lambda: "" + anthropic_client.settings, "get_gcp_project_id", lambda: "" ) - with pytest.raises(LLMError, match="ANTHROPIC_API_KEY"): + with pytest.raises(LLMError, match="GCP_PROJECT_ID"): AnthropicClient() diff --git a/docker-compose.yml b/docker-compose.yml index d31641ec..2730995a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,10 +35,12 @@ services: CALLBACK_BASE_URL: ${CALLBACK_BASE_URL} TASK_MAX_ATTEMPTS: ${TASK_MAX_ATTEMPTS} # DevForge Agent(ADR-0010)。ローカルは Ollama を既定とする - # ローカルは Ollama 上書きで無料動作(ADR-0013)。実 API で確認したい時は 0 にしてキーを設定 + # ローカルは Ollama 上書きで無料動作(ADR-0013)。Gemini/Anthropic は本番では + # Vertex AI(SA→ADC)経由(ADR-0015)でキー不要。ローカルで実 Vertex を叩く場合のみ + # gcloud auth application-default login で ADC を用意する(GCP_PROJECT_ID は上で設定済み)。 LLM_LOCAL_OLLAMA: ${LLM_LOCAL_OLLAMA:-1} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - GOOGLE_API_KEY: ${GOOGLE_API_KEY} + VERTEX_LOCATION: ${VERTEX_LOCATION} + VERTEX_ANTHROPIC_LOCATION: ${VERTEX_ANTHROPIC_LOCATION} OPENAI_API_KEY: ${OPENAI_API_KEY} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL} OLLAMA_MODEL: ${OLLAMA_MODEL} diff --git a/docs/adr/0015-vertex-ai-for-gemini-anthropic.md b/docs/adr/0015-vertex-ai-for-gemini-anthropic.md new file mode 100644 index 00000000..1da4ed27 --- /dev/null +++ b/docs/adr/0015-vertex-ai-for-gemini-anthropic.md @@ -0,0 +1,61 @@ +# ADR-0015: Gemini / Anthropic を Vertex AI(SA→ADC)経由にする + +## ステータス + +Accepted + +(ADR-0013 の認証部分「Gemini / OpenAI の API キーを Secret Manager で管理し Cloud Run に注入する」を更新する。プロバイダ抽象・モデルエイリアス・課金の仕組みは ADR-0013 を踏襲。) + +## コンテキスト + +DevForge Agent(ADR-0010/0013)は 3 プロバイダ(Anthropic / Gemini / OpenAI)を**すべて API キー方式**で叩いていた。キーは Secret Manager に保管し Cloud Run に注入していた(`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY` / `OPENAI_API_KEY`)。 + +職務経歴書(PII)を LLM に送るプロダクトとして、データガバナンスを見直した: + +- **Gemini Developer API の無料枠は、送信内容がモデル学習・人手レビューに使われ得る。** PII を扱う本番では使えない。 +- 一方 **Anthropic API / OpenAI API / Vertex AI はいずれも送信内容を学習に使わない**(コンシューマ製品の Claude.ai / ChatGPT とは別契約)。 +- Gemini Developer API は有料枠に上げれば学習除外になるが、その場合「API キー(有料)vs Vertex AI」の比較になる。学習リスクが消えると、残る差分(シークレット管理・IAM・監査ログ・GCP 請求統合)は**すべて Vertex AI + SA(ADC) が有利**。 +- **Anthropic も GCP の Vertex AI Model Garden 経由で SA 認証できる**(`AnthropicVertex`)。OpenAI のみ GCP に存在せず(クラウドパートナーは Azure)、API キーが必須。 + +Cloud Run の SA には `roles/aiplatform.user` が既に付与済みで、IAM 追加なしに Vertex を叩ける状態だった。 + +## 決定内容 + +**Gemini と Anthropic を Vertex AI(Cloud Run の SA → ADC)経由に切り替える。OpenAI のみ API キーを継続する。** + +- **認証**: API キーの代わりに ADC(Cloud Run の SA)を使う。プロジェクト ID は Cloud Tasks と共用の `GCP_PROJECT_ID`。`ANTHROPIC_API_KEY` / `GOOGLE_API_KEY` は廃止し、Secret Manager への注入も停止する。 +- **SDK**: 既存のまま。`google-genai` は `genai.Client(vertexai=True, project, location)`、`anthropic` は `AsyncAnthropicVertex(project_id, region)`。`anthropic[vertex]` extra で ADC 用の `google-auth` を引き込む。`generate()` のロジック(構造化出力・tool use・usage 抽出)は無変更。 +- **ロケーション(provider 別)**: + - Gemini: `asia-northeast1`(Tokyo)。データを日本に留める。`VERTEX_LOCATION` で上書き可。 + - Claude: `asia-southeast1`(Singapore)。Claude は Tokyo で提供されないため最寄りの regional endpoint。`VERTEX_ANTHROPIC_LOCATION` で上書き可。データはアジア圏に留まる。 +- **モデル ID**(`model_catalog.py`): Gemini は `gemini-2.5-flash` / `gemini-2.5-pro` で Vertex でも同一。Anthropic は Vertex 形式に合わせ Haiku のみ `claude-haiku-4-5@20251001`(版指定必須)、Sonnet 4.6 は `claude-sonnet-4-6`(版指定不要)。課金レート・`is_free`・エイリアスは無変更。 +- **ローカル/CI**: ローカルは `LLM_LOCAL_OLLAMA=1` で Vertex クライアントを生成しないため ADC 不要。テストは SDK をモックするため CI も ADC・キー不要。実 Vertex をローカルで叩く場合のみ `gcloud auth application-default login`。 +- **`enable_extra_llm_providers`**: OpenAI キー注入のみを gate する意味に狭める(Gemini/Anthropic はキー注入が消えたため)。変数名は据え置き。 + +## 代替案 + +- **Gemini を有料 API キー(Developer API)のまま使う**: コード変更ゼロで学習除外は満たせるが、シークレット管理・IAM・監査ログ・請求が Vertex に劣る。Cloud Run 本番として Vertex を選択。 +- **Anthropic も API キーのまま残す**: リージョン制約(Tokyo 未提供)を回避できるが、シークレットを 2 本残す。SA 認証できるものは寄せる方針で却下。OpenAI のみキーを残す。 +- **Claude を global / multi-region endpoint で叩く**: 可用性最良・課金プレミアム無し。ただしデータ所在地の保証が弱い。PII 重視で regional(Singapore)を選択。 + +## トレードオフ・既知のリスク + +- **regional endpoint の +10% 課金**: Claude の regional/multi-region endpoint は global 比 +10%。`model_catalog` の Anthropic 原価レートは現状 global 想定のままで、必要なら実績に合わせ調整する。 +- **クロスリージョンのレイテンシ**: Claude のみ Singapore のため Cloud Run(Tokyo) からのレイテンシが Gemini より増える。許容範囲の想定。 +- **Anthropic on Vertex のモデル可用性**: `asia-southeast1` での Claude 提供と model id(版)は Model Garden が正本。提供が無ければ region 再検討(global 等)。 +- **無料枠の喪失**: ADR-0013 の「dev 無料確認」動機(Gemini Flash 無料枠)は Vertex では消える。dev 確認は Ollama 無料パスで吸収する。 +- **未使用シークレットの残置**: `anthropic-api-key` / `google-api-key` の Secret Manager コンテナは注入停止のみ行い、削除は別 PR で全環境の設定除外を確認後に実施(破壊的操作の分離)。 + +## 将来の移行条件 + +- Claude が `asia-northeast1` で提供されたら Gemini と同一リージョンに統合する。 +- データ所在地要件が緩和されれば、可用性と課金で有利な global endpoint への切り替えを検討する。 +- OpenAI が GCP で SA 認証可能になれば、キーを完全に廃止する。 + +## 関連リンク + +- ADR-0010(DevForge Agent)/ ADR-0012(モデル切り替えとプリペイド課金)/ ADR-0013(マルチプロバイダ LLM) +- `backend/app/services/agent/llm/{google_client,anthropic_client,base}.py` / `model_catalog.py` +- `backend/app/core/{settings,env_keys}.py` / `infra/modules/cloud_run/main.tf` +- [Claude on Vertex AI(model id 一覧)](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai) +- [Anthropic Claude models | Vertex AI](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude) diff --git a/docs/api.md b/docs/api.md index e2d540d7..7c1b7982 100644 --- a/docs/api.md +++ b/docs/api.md @@ -143,9 +143,9 @@ REST API エンドポイント一覧と、バックエンド/フロントエ | 変数 | 用途 | |---|---| | `LLM_LOCAL_OLLAMA` | ローカル Ollama 上書き(`1`/`true`/`yes` で有効)。選択モデルに関わらず全リクエストを Ollama に通す無料パス。本番は未設定=無効(ADR-0013) | -| `ANTHROPIC_API_KEY` | Anthropic API キー(本番は Secret Manager `anthropic-api-key` から注入) | -| `GOOGLE_API_KEY` | Google Gemini API キー(ADR-0013。`enable_extra_llm_providers=true` の環境で Secret Manager `google-api-key` から注入) | -| `OPENAI_API_KEY` | OpenAI API キー(ADR-0013。`enable_extra_llm_providers=true` の環境で Secret Manager `openai-api-key` から注入) | +| `VERTEX_LOCATION` | Gemini を叩く Vertex AI のロケーション(ADR-0015。既定: `asia-northeast1`)。認証は SA→ADC で `GCP_PROJECT_ID` を共用。API キー不要 | +| `VERTEX_ANTHROPIC_LOCATION` | Claude を叩く Vertex AI のロケーション(ADR-0015。既定: `asia-southeast1`。Tokyo 未提供のため Singapore)。regional endpoint は global 比 +10% 課金 | +| `OPENAI_API_KEY` | OpenAI API キー(ADR-0013/0015。GCP に存在しないため唯一キー継続。`enable_extra_llm_providers=true` の環境で Secret Manager `openai-api-key` から注入) | | `OLLAMA_BASE_URL` | ローカル Ollama のベース URL(既定: `http://localhost:11434`) | | `OLLAMA_MODEL` | ローカル Ollama のモデル名(既定: `llama3.2`) | | `OLLAMA_TIMEOUT_SECONDS` | ローカル Ollama 呼び出しの HTTP タイムアウト秒数(既定: `300`) | diff --git a/infra/modules/cloud_run/main.tf b/infra/modules/cloud_run/main.tf index f8c4fd6d..d2cb2cde 100644 --- a/infra/modules/cloud_run/main.tf +++ b/infra/modules/cloud_run/main.tf @@ -9,11 +9,13 @@ locals { "jwt-public-key", "internal-secret", "turso-auth-token", - # DevForge Agent(ADR-0010)の Anthropic API キー + # 棚卸し TODO(ADR-0015): Gemini / Anthropic を Vertex AI(SA→ADC)へ移行したため + # 以下 2 つのキーは Cloud Run へ注入しなくなった。コンテナ自体の削除は別 PR で + # 全環境の設定から外したことを確認してから実施する(field-encryption-key と同様)。 "anthropic-api-key", - # マルチプロバイダ LLM(ADR-0013)の Gemini / OpenAI API キー。 - # コンテナは常に作成し、注入は enable_extra_llm_providers が true の環境のみ "google-api-key", + # OpenAI のみ GCP に存在せず API キーを継続使用(ADR-0015)。 + # コンテナは常に作成し、注入は enable_extra_llm_providers が true の環境のみ "openai-api-key", # 決済(Stripe Checkout / ADR-0012 Phase 2)の API キーと Webhook 署名シークレット "stripe-secret-key", @@ -29,7 +31,6 @@ locals { JWT_PUBLIC_KEY = "jwt-public-key" INTERNAL_SECRET = "internal-secret" TURSO_AUTH_TOKEN = "turso-auth-token" - ANTHROPIC_API_KEY = "anthropic-api-key" STRIPE_SECRET_KEY = "stripe-secret-key" STRIPE_WEBHOOK_SECRET = "stripe-webhook-secret" } @@ -37,10 +38,10 @@ locals { GITHUB_CLIENT_ID = "github-client-id" GITHUB_CLIENT_SECRET = "github-client-secret" } : {} - # マルチプロバイダ LLM(ADR-0013)。有効時のみ Gemini / OpenAI キーを注入する。 + # マルチプロバイダ LLM。Gemini / Anthropic は Vertex AI(SA→ADC)経由のためキー注入は + # 不要になり(ADR-0015)、本マップは OpenAI キーのみを gate する。有効時のみ注入し、 # 無効なら secret version 未投入でも Cloud Run が起動できる llm_secret_env = var.enable_extra_llm_providers ? { - GOOGLE_API_KEY = "google-api-key" OPENAI_API_KEY = "openai-api-key" } : {} @@ -114,6 +115,16 @@ resource "google_cloud_run_v2_service" "app" { name = "GCP_PROJECT_ID" value = var.project_id } + # Vertex AI(SA→ADC)の provider 別ロケーション(ADR-0015)。 + # Gemini=Tokyo、Claude は Tokyo 未提供のため Singapore。GCP_PROJECT_ID を共用。 + env { + name = "VERTEX_LOCATION" + value = var.vertex_location + } + env { + name = "VERTEX_ANTHROPIC_LOCATION" + value = var.vertex_anthropic_location + } env { name = "CLOUD_TASKS_QUEUE" value = var.cloud_tasks_queue diff --git a/infra/modules/cloud_run/variables.tf b/infra/modules/cloud_run/variables.tf index 19e95386..44955ff7 100644 --- a/infra/modules/cloud_run/variables.tf +++ b/infra/modules/cloud_run/variables.tf @@ -64,6 +64,18 @@ variable "cloud_tasks_location" { default = "asia-northeast1" } +variable "vertex_location" { + description = "Gemini を叩く Vertex AI のロケーション(ADR-0015)。" + type = string + default = "asia-northeast1" +} + +variable "vertex_anthropic_location" { + description = "Claude を叩く Vertex AI のロケーション。Tokyo 未提供のため Singapore(ADR-0015)。" + type = string + default = "asia-southeast1" +} + variable "cloud_tasks_service_account" { description = "Cloud Tasks OIDC 認証用サービスアカウントメール。" type = string From 42b98a13366600d999679e2ba43a5217b117abf6 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Tue, 23 Jun 2026 16:54:19 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix(infra):=20cloudflare=20provider=20?= =?UTF-8?q?=E3=82=92=20v5=20=E3=81=AB=E7=A7=BB=E8=A1=8C=E3=81=97=20provide?= =?UTF-8?q?r=20lock=20=E3=81=AE=E3=83=89=E3=83=AA=E3=83=95=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit versions.tf が cloudflare ~> 5.0 を要求しているのに lock が v4 系(4.52.7)を pin したままで、infra を触る PR の opentofu-validate が provider 解決段階で 失敗していた(main 由来の既存ドリフト。本 PR が初の infra 変更で顕在化)。 - cloudflare provider を v5.21.0 に更新(turso 1.2.0 / google 7.37.0 も追従) - cloudflare_record → cloudflare_dns_record(v5 改名)、value → content、 ttl=1 を明示(v5 で必須)。use_custom_domain は全環境 false で count=0 の ため移行対象インスタンスは無く moved ブロックは不要 - dev/stg/prod の lock を linux_amd64 + darwin_arm64 の両プラットフォームで再生成 make infra-validate(3 環境)/ infra-fmt-check 済み。 Co-Authored-By: Claude Opus 4.8 --- infra/environments/dev/.terraform.lock.hcl | 101 ++++++++++---------- infra/environments/prod/.terraform.lock.hcl | 101 ++++++++++---------- infra/environments/stg/.terraform.lock.hcl | 101 ++++++++++---------- infra/modules/cloudflare/main.tf | 7 +- 4 files changed, 152 insertions(+), 158 deletions(-) diff --git a/infra/environments/dev/.terraform.lock.hcl b/infra/environments/dev/.terraform.lock.hcl index d857ca4a..5bbeb907 100644 --- a/infra/environments/dev/.terraform.lock.hcl +++ b/infra/environments/dev/.terraform.lock.hcl @@ -2,70 +2,67 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.7" - constraints = "~> 4.0" + version = "5.21.0" + constraints = "~> 5.0" hashes = [ - "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", - "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", - "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", - "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", - "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", - "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", - "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", - "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", - "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", - "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", - "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", - "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", - "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", - "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", + "h1:GHS9VFEa0rXjux9Cipqy2QGEmQ0bst6Xqd5WUUJsOwI=", + "h1:TJQJrskWa1FJ8gk6BAhj4X6fa5TFqI255T6Y6ZKrFkU=", + "zh:2d3dc17f27dcf308f52bdede3b7bb00e6cda6c1a9fbdafd1bfe4a915e75fdc44", + "zh:413e3569ad0cc89f8ac425d8a197d9e892ff356ac24dedde386820cf5880a48e", + "zh:59f262b9af9a8845afeb2734a6bd83b260aa2c521e5c318850745823ef62b38d", + "zh:7d42963a47ff6dda8aada0cb73a26eb6807c7ff6bb4419cb91b32ce2412c8362", + "zh:89cad3906c9affa0f2947607d316d70c38541c399d0519ebee1f1ccc8aaf5675", + "zh:d5c7ff6c39d895e5746fed74ecc3f284c91c8c1f502da2e93f5c581f41f87f25", + "zh:dc425b5f40e94165e563dc55bd6e49cedfe879a03e668168610459ef071b1a87", + "zh:f0665907dd24ae93f9511216052d653bba0823c933e888cf02a4abf95f73dfe0", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", ] } provider "registry.opentofu.org/hashicorp/google" { - version = "7.32.0" + version = "7.37.0" constraints = "~> 7.22" hashes = [ - "h1:z0kbKVovrMINaZSXLZW+y5EQ0FpmefU5EMCKL6/OJWQ=", - "zh:011de7394b31d0aeed54d11c9ba0ccd4457addf24e467394088c54be87413f95", - "zh:1b9261f4a7329c4fa73886e3474ebdc66a0fa36aee78233f5a24e335e6062777", - "zh:2b7d9cc6462ff2ecdb0f1b6b6d97cf855f0d8010c7fcfa768f764a524519c796", - "zh:35a872afa39e37f2d266741fde647c10c4d6c0b066f0977de91f849f5cf64649", - "zh:400c2737752690530efcff578060dd24e5d365a038b772c7fc327b4357cb0b70", - "zh:46722e5509ba24cad6ab85cb5dae6f06e97e416660b68b8ff4cbce682f597d59", - "zh:5bdae841e16f327500d3395d626288ea387648ffabdab331786e6102a57df67f", - "zh:6b9421ec63b15f94e6daf46e51ca204cb466e23f8c7f40e18a0dbc4540df6744", - "zh:84cba090b3d9eee432a5121e4484ef592976fc4918cb10eb0182dea00cecd8c1", - "zh:859196e7a7e301cfb0f728b7a92035ede91629462c9d9da6c976d72864c42342", - "zh:a0ef237f70fd6602272f0d6bf82720227cdba205fd72f12c57076523a1e5079c", - "zh:a5df4ac00b3bea99e41672392831ad28fb44b2001bc2c39f3a21a124ff3b09fb", - "zh:d26e0221f7dd7abf5cd9431f5bea227d176ea7e81c50fb0d96c7d7f1fc77ef56", - "zh:e24d8380413b3460b1ac09687a9a78904e6b5d011db0fad952ee76d806ee6e85", - "zh:fb267184f11fa4e803f5559baac1f374cdf1e8f2427ee52c7470622238cf3ac0", + "h1:RvBGaj3aR2sFMDRrHdC+knQqHUuteep69EER781c3X8=", + "h1:SFP7uUJNCfWg2qZGm3P6OS66Q+qGiP9LC/V363ayMYk=", + "zh:3bdd1b931c72ca69c3a4ae3a2d5b7ed35694866382c72df4fe1f7c7333cf67c0", + "zh:4ab8fb029efa086700c59a0dcd891f38fc81c747575bf8f2b1083727ebe3bf75", + "zh:53a29b15a7d696790dcad12cfb0ae9730be3e54bdbdd9dc185f0869f9e2fa716", + "zh:5aa5215fb457bfd466860ed545c0b5820a71ab265065a88f9fc09dbaafef2a45", + "zh:8380b9ae5c893b158798aba7e6d930ad6fcbfd6d0ef22122ec55decc3b73ea55", + "zh:8e92d589fbad49ce14a7faa1d893588786deea2d11b544a77bae50d61d71696d", + "zh:9e853676276699ea2dcf8de07ab361677a16bde470ff48a07db930a7b47d0fc1", + "zh:ad8b47043bfc0e759220f06de35c5c8958abe2cd8fc1d74dde52aaf8d79b0684", + "zh:aff75581d6e0d259d29a3cf62d915d6d7d5a097fd72915db5d576137a11fc62c", + "zh:b7c8b9b9d019d4727e3ba0c29186f4bc8567c0393f14a15f70cf2ec6267e0d0a", + "zh:cfc6869f88141c6a9cb2476e2e2e07410521a4b028e9f9b604f70976158f2c1e", + "zh:d30b04b4290b34cc11fdcf60f8267d4c315bd9f77fd491532d311ff3ac736a6f", + "zh:d7a0231ff91b1c99f9efe77f78260603e23154d38ea43c9a238ebcc77cc5f027", + "zh:dfd606cd787f5b6bb86354eaa3b9139c3d5e3f664912a0caadbcead6709fad50", + "zh:f853da41c3e431a693a3e9435c33a369371063fe1817ae27216a66a4ca20bddb", ] } provider "registry.opentofu.org/jpedroh/turso" { - version = "0.3.0" - constraints = "~> 0.3" + version = "1.2.0" + constraints = "~> 1.0" hashes = [ - "h1:wYBrD/0YvFXdvQ6s4E0nTWLRm4NLgjniics9ldMZ3c8=", - "zh:0bc9b1ddb817a968d5b6905d123f3ea38b147776769acefcc41762dab16290af", - "zh:155847966b07250d653b41c09695e9d8a54833e6e1657dfa12e26fe431dcc7ad", - "zh:1705fb688ab441d714464501a983fd540df50d6915f0350892a14d3ed569d622", - "zh:70135f83c117f970331a5d4ec5b26886f17b2bd88ff5c5bffc0c6bd061b512f0", - "zh:7b1002e83d3e57db79bed9c6715aba45686d66c794a3e9f2658775ac155c5088", - "zh:7c73633bf81680bf122fdae35fed206a095aabbb158939f088856389e46f740b", - "zh:85f34184f785e4d443309dbf88abef5e92029d6fe5e388587e64cd98e1ea4a73", + "h1:neKlwN3dYfsteZKsROmRGevIHn4Vab5H+vAdIo+xlLY=", + "h1:roxxUZ8ox2pGuqptMeekHSc0/j6Er+QU5CHhWzKbFao=", + "zh:224571eed57ff3ea908ae7ee3b9c18aaf7da01d650ab4207e89a75c3cfcc76ac", + "zh:23e1fa68803a009610705a3d8997344604892a745fe96c6c7c643e8eb9077eb3", + "zh:2d8ca4ca0b3573919675e358d699c104aac7bb0fddd778528c540ca45fc7ba57", + "zh:49a71b89d50f62e4593a6dc5d39885ea3cc4617220ff147109fd447b3f072afb", + "zh:57b9a23ade177b64847edea61b8bf50a580e60e428b862a1a83a3bf81eb7bc65", + "zh:820da81c57d1ba0536fe287adc19e70a828fb3122b16ed8858fbc716b9b77c5e", + "zh:87ba90c3e42ea23a241f7175cd43a6fcac3c5395d6269b3c10be4c7f29179bee", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:aac66e67e1b081eb8a9101278e1dbfbfce1007b2f58f854d29367f1c4f7cc1fb", - "zh:b1c3d64c19d21b2b60a45aa1ca62441b799b9d23740bfb71888fd5c104392597", - "zh:ca27cbf3615920e180bb68f3ce10a7630016e6f20904f51aec8dc3aaa4bd1926", - "zh:ca7949802a1ce4906898f3a517dd2f1e2901fbd8f7ed7ffe4daeddfb42895c2c", - "zh:cc6cf5e3ff05ad8d15ac52154eff99fbe06cf7ca613200b528a0903c8f6db118", - "zh:f64072707f2ec6f00eb2bb83c632de59232bbd9da4de779716e52196de69b717", - "zh:ff1fdf852ef6a238090a2f49be35faca3aa124fb621916bd3a2feff4cd6ea6bc", + "zh:a89efa4d96167ff964189bad84543401c545332932c08c889312c0a1a4330b06", + "zh:ae1eda33f89ee6660dd2024b073270b1902bb61438b3b78fcb7f2724bf26c0ff", + "zh:c444992932a615f12951e49192ad71d3ae55b079f4265f01772c4e584b56d774", + "zh:d5038b76377a34a27c1f8eb67457e3914d8adb1c8ec649b83303010a6627065f", + "zh:e3bd2f7fd5419d81113c8aa3589e6b419022d0936cd9abb2278e221d01f3c682", + "zh:ea3fcd041e6f1b79199c886e88821be0eb0b04d7acce24b6520ddab8d80fd1f5", + "zh:f91b137a2628b4e8b755e0da0134ecd2445f95cddfb42ed5f81f19b77dba9820", ] } diff --git a/infra/environments/prod/.terraform.lock.hcl b/infra/environments/prod/.terraform.lock.hcl index 0869dba8..5bbeb907 100644 --- a/infra/environments/prod/.terraform.lock.hcl +++ b/infra/environments/prod/.terraform.lock.hcl @@ -2,70 +2,67 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.7" - constraints = "~> 4.0" + version = "5.21.0" + constraints = "~> 5.0" hashes = [ - "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", - "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", - "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", - "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", - "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", - "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", - "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", - "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", - "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", - "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", - "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", - "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", - "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", - "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", + "h1:GHS9VFEa0rXjux9Cipqy2QGEmQ0bst6Xqd5WUUJsOwI=", + "h1:TJQJrskWa1FJ8gk6BAhj4X6fa5TFqI255T6Y6ZKrFkU=", + "zh:2d3dc17f27dcf308f52bdede3b7bb00e6cda6c1a9fbdafd1bfe4a915e75fdc44", + "zh:413e3569ad0cc89f8ac425d8a197d9e892ff356ac24dedde386820cf5880a48e", + "zh:59f262b9af9a8845afeb2734a6bd83b260aa2c521e5c318850745823ef62b38d", + "zh:7d42963a47ff6dda8aada0cb73a26eb6807c7ff6bb4419cb91b32ce2412c8362", + "zh:89cad3906c9affa0f2947607d316d70c38541c399d0519ebee1f1ccc8aaf5675", + "zh:d5c7ff6c39d895e5746fed74ecc3f284c91c8c1f502da2e93f5c581f41f87f25", + "zh:dc425b5f40e94165e563dc55bd6e49cedfe879a03e668168610459ef071b1a87", + "zh:f0665907dd24ae93f9511216052d653bba0823c933e888cf02a4abf95f73dfe0", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", ] } provider "registry.opentofu.org/hashicorp/google" { - version = "7.23.0" + version = "7.37.0" constraints = "~> 7.22" hashes = [ - "h1:jGXnEOK8/9rwnlWl0TbiSh3G3WrfA/e2+2baIb1LJr4=", - "zh:1a768420a7e3b01a5d19029298c5698710ebacdfe5b864a17d26d25888e5c1db", - "zh:25680f4e393370fcca30346d2b57339c14e47a11290d14c47ce34a799d791086", - "zh:25c8b34a06a41a649c6964d3646361e02959c4dc2366b450193129761d1e34c9", - "zh:2ff7e171acd4bb576977935c976211e34c7899a0e512ec1b1c84ca9a84a92c31", - "zh:333a4102e07bc60ee68b3378e9ca57d3e4fb1b538d01d2405f456e73c3dc33f6", - "zh:46e2f0b0ad65f74e02325528f3d6b066a97270c69826c719b45ac8c61a01782a", - "zh:5807ac3aceb8d18c66945b966e2780fd9af5ea236f8b5ad6baea8c16eefc7254", - "zh:75ed84c8596f037cd32186bcac23e5ef7d77d496a6e455ccb69ccbae920aad00", - "zh:7ff4fa5a5cf6cec2cb0992e2a6a8735b9b9d0784efe48da7e3d70a6f06aacf33", - "zh:84e3124864e14e9e8968783db6c39829d14293450e795e25490090ef31cb9044", - "zh:88001fff66a375a0b0b19b1362475e718455da0b9ae61043974fbe68a4e4cb03", - "zh:bedb354dd8d90e676d4126112788f7742f2e8f1ed8be49cdcc8c7fe5a9439f6b", - "zh:dbf0923ab3f54868752f5a05b0625e530da07436090325d1dcdc8d7b8f2a4695", - "zh:f469b8f3ffea5d54d86fb641708bf51c06fdba77a156c1f9b3d81910cb13f8d0", - "zh:f7d9a990aab859dc7a64f6939e8572035995070e1bdd96440e09649c7cc0011b", + "h1:RvBGaj3aR2sFMDRrHdC+knQqHUuteep69EER781c3X8=", + "h1:SFP7uUJNCfWg2qZGm3P6OS66Q+qGiP9LC/V363ayMYk=", + "zh:3bdd1b931c72ca69c3a4ae3a2d5b7ed35694866382c72df4fe1f7c7333cf67c0", + "zh:4ab8fb029efa086700c59a0dcd891f38fc81c747575bf8f2b1083727ebe3bf75", + "zh:53a29b15a7d696790dcad12cfb0ae9730be3e54bdbdd9dc185f0869f9e2fa716", + "zh:5aa5215fb457bfd466860ed545c0b5820a71ab265065a88f9fc09dbaafef2a45", + "zh:8380b9ae5c893b158798aba7e6d930ad6fcbfd6d0ef22122ec55decc3b73ea55", + "zh:8e92d589fbad49ce14a7faa1d893588786deea2d11b544a77bae50d61d71696d", + "zh:9e853676276699ea2dcf8de07ab361677a16bde470ff48a07db930a7b47d0fc1", + "zh:ad8b47043bfc0e759220f06de35c5c8958abe2cd8fc1d74dde52aaf8d79b0684", + "zh:aff75581d6e0d259d29a3cf62d915d6d7d5a097fd72915db5d576137a11fc62c", + "zh:b7c8b9b9d019d4727e3ba0c29186f4bc8567c0393f14a15f70cf2ec6267e0d0a", + "zh:cfc6869f88141c6a9cb2476e2e2e07410521a4b028e9f9b604f70976158f2c1e", + "zh:d30b04b4290b34cc11fdcf60f8267d4c315bd9f77fd491532d311ff3ac736a6f", + "zh:d7a0231ff91b1c99f9efe77f78260603e23154d38ea43c9a238ebcc77cc5f027", + "zh:dfd606cd787f5b6bb86354eaa3b9139c3d5e3f664912a0caadbcead6709fad50", + "zh:f853da41c3e431a693a3e9435c33a369371063fe1817ae27216a66a4ca20bddb", ] } provider "registry.opentofu.org/jpedroh/turso" { - version = "0.3.0" - constraints = "~> 0.3" + version = "1.2.0" + constraints = "~> 1.0" hashes = [ - "h1:wYBrD/0YvFXdvQ6s4E0nTWLRm4NLgjniics9ldMZ3c8=", - "zh:0bc9b1ddb817a968d5b6905d123f3ea38b147776769acefcc41762dab16290af", - "zh:155847966b07250d653b41c09695e9d8a54833e6e1657dfa12e26fe431dcc7ad", - "zh:1705fb688ab441d714464501a983fd540df50d6915f0350892a14d3ed569d622", - "zh:70135f83c117f970331a5d4ec5b26886f17b2bd88ff5c5bffc0c6bd061b512f0", - "zh:7b1002e83d3e57db79bed9c6715aba45686d66c794a3e9f2658775ac155c5088", - "zh:7c73633bf81680bf122fdae35fed206a095aabbb158939f088856389e46f740b", - "zh:85f34184f785e4d443309dbf88abef5e92029d6fe5e388587e64cd98e1ea4a73", + "h1:neKlwN3dYfsteZKsROmRGevIHn4Vab5H+vAdIo+xlLY=", + "h1:roxxUZ8ox2pGuqptMeekHSc0/j6Er+QU5CHhWzKbFao=", + "zh:224571eed57ff3ea908ae7ee3b9c18aaf7da01d650ab4207e89a75c3cfcc76ac", + "zh:23e1fa68803a009610705a3d8997344604892a745fe96c6c7c643e8eb9077eb3", + "zh:2d8ca4ca0b3573919675e358d699c104aac7bb0fddd778528c540ca45fc7ba57", + "zh:49a71b89d50f62e4593a6dc5d39885ea3cc4617220ff147109fd447b3f072afb", + "zh:57b9a23ade177b64847edea61b8bf50a580e60e428b862a1a83a3bf81eb7bc65", + "zh:820da81c57d1ba0536fe287adc19e70a828fb3122b16ed8858fbc716b9b77c5e", + "zh:87ba90c3e42ea23a241f7175cd43a6fcac3c5395d6269b3c10be4c7f29179bee", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:aac66e67e1b081eb8a9101278e1dbfbfce1007b2f58f854d29367f1c4f7cc1fb", - "zh:b1c3d64c19d21b2b60a45aa1ca62441b799b9d23740bfb71888fd5c104392597", - "zh:ca27cbf3615920e180bb68f3ce10a7630016e6f20904f51aec8dc3aaa4bd1926", - "zh:ca7949802a1ce4906898f3a517dd2f1e2901fbd8f7ed7ffe4daeddfb42895c2c", - "zh:cc6cf5e3ff05ad8d15ac52154eff99fbe06cf7ca613200b528a0903c8f6db118", - "zh:f64072707f2ec6f00eb2bb83c632de59232bbd9da4de779716e52196de69b717", - "zh:ff1fdf852ef6a238090a2f49be35faca3aa124fb621916bd3a2feff4cd6ea6bc", + "zh:a89efa4d96167ff964189bad84543401c545332932c08c889312c0a1a4330b06", + "zh:ae1eda33f89ee6660dd2024b073270b1902bb61438b3b78fcb7f2724bf26c0ff", + "zh:c444992932a615f12951e49192ad71d3ae55b079f4265f01772c4e584b56d774", + "zh:d5038b76377a34a27c1f8eb67457e3914d8adb1c8ec649b83303010a6627065f", + "zh:e3bd2f7fd5419d81113c8aa3589e6b419022d0936cd9abb2278e221d01f3c682", + "zh:ea3fcd041e6f1b79199c886e88821be0eb0b04d7acce24b6520ddab8d80fd1f5", + "zh:f91b137a2628b4e8b755e0da0134ecd2445f95cddfb42ed5f81f19b77dba9820", ] } diff --git a/infra/environments/stg/.terraform.lock.hcl b/infra/environments/stg/.terraform.lock.hcl index 0869dba8..5bbeb907 100644 --- a/infra/environments/stg/.terraform.lock.hcl +++ b/infra/environments/stg/.terraform.lock.hcl @@ -2,70 +2,67 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.7" - constraints = "~> 4.0" + version = "5.21.0" + constraints = "~> 5.0" hashes = [ - "h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=", - "zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b", - "zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e", - "zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10", - "zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285", - "zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13", - "zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d", - "zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f", - "zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d", - "zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe", - "zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455", - "zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2", - "zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b", - "zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe", + "h1:GHS9VFEa0rXjux9Cipqy2QGEmQ0bst6Xqd5WUUJsOwI=", + "h1:TJQJrskWa1FJ8gk6BAhj4X6fa5TFqI255T6Y6ZKrFkU=", + "zh:2d3dc17f27dcf308f52bdede3b7bb00e6cda6c1a9fbdafd1bfe4a915e75fdc44", + "zh:413e3569ad0cc89f8ac425d8a197d9e892ff356ac24dedde386820cf5880a48e", + "zh:59f262b9af9a8845afeb2734a6bd83b260aa2c521e5c318850745823ef62b38d", + "zh:7d42963a47ff6dda8aada0cb73a26eb6807c7ff6bb4419cb91b32ce2412c8362", + "zh:89cad3906c9affa0f2947607d316d70c38541c399d0519ebee1f1ccc8aaf5675", + "zh:d5c7ff6c39d895e5746fed74ecc3f284c91c8c1f502da2e93f5c581f41f87f25", + "zh:dc425b5f40e94165e563dc55bd6e49cedfe879a03e668168610459ef071b1a87", + "zh:f0665907dd24ae93f9511216052d653bba0823c933e888cf02a4abf95f73dfe0", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", ] } provider "registry.opentofu.org/hashicorp/google" { - version = "7.23.0" + version = "7.37.0" constraints = "~> 7.22" hashes = [ - "h1:jGXnEOK8/9rwnlWl0TbiSh3G3WrfA/e2+2baIb1LJr4=", - "zh:1a768420a7e3b01a5d19029298c5698710ebacdfe5b864a17d26d25888e5c1db", - "zh:25680f4e393370fcca30346d2b57339c14e47a11290d14c47ce34a799d791086", - "zh:25c8b34a06a41a649c6964d3646361e02959c4dc2366b450193129761d1e34c9", - "zh:2ff7e171acd4bb576977935c976211e34c7899a0e512ec1b1c84ca9a84a92c31", - "zh:333a4102e07bc60ee68b3378e9ca57d3e4fb1b538d01d2405f456e73c3dc33f6", - "zh:46e2f0b0ad65f74e02325528f3d6b066a97270c69826c719b45ac8c61a01782a", - "zh:5807ac3aceb8d18c66945b966e2780fd9af5ea236f8b5ad6baea8c16eefc7254", - "zh:75ed84c8596f037cd32186bcac23e5ef7d77d496a6e455ccb69ccbae920aad00", - "zh:7ff4fa5a5cf6cec2cb0992e2a6a8735b9b9d0784efe48da7e3d70a6f06aacf33", - "zh:84e3124864e14e9e8968783db6c39829d14293450e795e25490090ef31cb9044", - "zh:88001fff66a375a0b0b19b1362475e718455da0b9ae61043974fbe68a4e4cb03", - "zh:bedb354dd8d90e676d4126112788f7742f2e8f1ed8be49cdcc8c7fe5a9439f6b", - "zh:dbf0923ab3f54868752f5a05b0625e530da07436090325d1dcdc8d7b8f2a4695", - "zh:f469b8f3ffea5d54d86fb641708bf51c06fdba77a156c1f9b3d81910cb13f8d0", - "zh:f7d9a990aab859dc7a64f6939e8572035995070e1bdd96440e09649c7cc0011b", + "h1:RvBGaj3aR2sFMDRrHdC+knQqHUuteep69EER781c3X8=", + "h1:SFP7uUJNCfWg2qZGm3P6OS66Q+qGiP9LC/V363ayMYk=", + "zh:3bdd1b931c72ca69c3a4ae3a2d5b7ed35694866382c72df4fe1f7c7333cf67c0", + "zh:4ab8fb029efa086700c59a0dcd891f38fc81c747575bf8f2b1083727ebe3bf75", + "zh:53a29b15a7d696790dcad12cfb0ae9730be3e54bdbdd9dc185f0869f9e2fa716", + "zh:5aa5215fb457bfd466860ed545c0b5820a71ab265065a88f9fc09dbaafef2a45", + "zh:8380b9ae5c893b158798aba7e6d930ad6fcbfd6d0ef22122ec55decc3b73ea55", + "zh:8e92d589fbad49ce14a7faa1d893588786deea2d11b544a77bae50d61d71696d", + "zh:9e853676276699ea2dcf8de07ab361677a16bde470ff48a07db930a7b47d0fc1", + "zh:ad8b47043bfc0e759220f06de35c5c8958abe2cd8fc1d74dde52aaf8d79b0684", + "zh:aff75581d6e0d259d29a3cf62d915d6d7d5a097fd72915db5d576137a11fc62c", + "zh:b7c8b9b9d019d4727e3ba0c29186f4bc8567c0393f14a15f70cf2ec6267e0d0a", + "zh:cfc6869f88141c6a9cb2476e2e2e07410521a4b028e9f9b604f70976158f2c1e", + "zh:d30b04b4290b34cc11fdcf60f8267d4c315bd9f77fd491532d311ff3ac736a6f", + "zh:d7a0231ff91b1c99f9efe77f78260603e23154d38ea43c9a238ebcc77cc5f027", + "zh:dfd606cd787f5b6bb86354eaa3b9139c3d5e3f664912a0caadbcead6709fad50", + "zh:f853da41c3e431a693a3e9435c33a369371063fe1817ae27216a66a4ca20bddb", ] } provider "registry.opentofu.org/jpedroh/turso" { - version = "0.3.0" - constraints = "~> 0.3" + version = "1.2.0" + constraints = "~> 1.0" hashes = [ - "h1:wYBrD/0YvFXdvQ6s4E0nTWLRm4NLgjniics9ldMZ3c8=", - "zh:0bc9b1ddb817a968d5b6905d123f3ea38b147776769acefcc41762dab16290af", - "zh:155847966b07250d653b41c09695e9d8a54833e6e1657dfa12e26fe431dcc7ad", - "zh:1705fb688ab441d714464501a983fd540df50d6915f0350892a14d3ed569d622", - "zh:70135f83c117f970331a5d4ec5b26886f17b2bd88ff5c5bffc0c6bd061b512f0", - "zh:7b1002e83d3e57db79bed9c6715aba45686d66c794a3e9f2658775ac155c5088", - "zh:7c73633bf81680bf122fdae35fed206a095aabbb158939f088856389e46f740b", - "zh:85f34184f785e4d443309dbf88abef5e92029d6fe5e388587e64cd98e1ea4a73", + "h1:neKlwN3dYfsteZKsROmRGevIHn4Vab5H+vAdIo+xlLY=", + "h1:roxxUZ8ox2pGuqptMeekHSc0/j6Er+QU5CHhWzKbFao=", + "zh:224571eed57ff3ea908ae7ee3b9c18aaf7da01d650ab4207e89a75c3cfcc76ac", + "zh:23e1fa68803a009610705a3d8997344604892a745fe96c6c7c643e8eb9077eb3", + "zh:2d8ca4ca0b3573919675e358d699c104aac7bb0fddd778528c540ca45fc7ba57", + "zh:49a71b89d50f62e4593a6dc5d39885ea3cc4617220ff147109fd447b3f072afb", + "zh:57b9a23ade177b64847edea61b8bf50a580e60e428b862a1a83a3bf81eb7bc65", + "zh:820da81c57d1ba0536fe287adc19e70a828fb3122b16ed8858fbc716b9b77c5e", + "zh:87ba90c3e42ea23a241f7175cd43a6fcac3c5395d6269b3c10be4c7f29179bee", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:aac66e67e1b081eb8a9101278e1dbfbfce1007b2f58f854d29367f1c4f7cc1fb", - "zh:b1c3d64c19d21b2b60a45aa1ca62441b799b9d23740bfb71888fd5c104392597", - "zh:ca27cbf3615920e180bb68f3ce10a7630016e6f20904f51aec8dc3aaa4bd1926", - "zh:ca7949802a1ce4906898f3a517dd2f1e2901fbd8f7ed7ffe4daeddfb42895c2c", - "zh:cc6cf5e3ff05ad8d15ac52154eff99fbe06cf7ca613200b528a0903c8f6db118", - "zh:f64072707f2ec6f00eb2bb83c632de59232bbd9da4de779716e52196de69b717", - "zh:ff1fdf852ef6a238090a2f49be35faca3aa124fb621916bd3a2feff4cd6ea6bc", + "zh:a89efa4d96167ff964189bad84543401c545332932c08c889312c0a1a4330b06", + "zh:ae1eda33f89ee6660dd2024b073270b1902bb61438b3b78fcb7f2724bf26c0ff", + "zh:c444992932a615f12951e49192ad71d3ae55b079f4265f01772c4e584b56d774", + "zh:d5038b76377a34a27c1f8eb67457e3914d8adb1c8ec649b83303010a6627065f", + "zh:e3bd2f7fd5419d81113c8aa3589e6b419022d0936cd9abb2278e221d01f3c682", + "zh:ea3fcd041e6f1b79199c886e88821be0eb0b04d7acce24b6520ddab8d80fd1f5", + "zh:f91b137a2628b4e8b755e0da0134ecd2445f95cddfb42ed5f81f19b77dba9820", ] } diff --git a/infra/modules/cloudflare/main.tf b/infra/modules/cloudflare/main.tf index 0bd3d233..565fd064 100644 --- a/infra/modules/cloudflare/main.tf +++ b/infra/modules/cloudflare/main.tf @@ -16,14 +16,17 @@ resource "cloudflare_pages_project" "app" { # app. へ CNAME レコードを作成(Cloudflare Proxy 経由) # use_custom_domain = false の場合はカスタムドメインを使わず、 # Pages のデフォルト *.pages.dev サブドメインのみで運用する(ゾーン未所有でも可)。 -resource "cloudflare_record" "app" { +# provider v5 で cloudflare_record は cloudflare_dns_record へ改名、value → content、 +# ttl が必須(proxied レコードは 1 = automatic)。 +resource "cloudflare_dns_record" "app" { count = var.use_custom_domain ? 1 : 0 zone_id = var.cloudflare_zone_id name = var.subdomain type = "CNAME" - value = cloudflare_pages_project.app.subdomain + content = cloudflare_pages_project.app.subdomain proxied = true + ttl = 1 lifecycle { # use_custom_domain = true なのに zone_id 未指定だと provider が不明瞭な From c4a4b0b9c7dadaefe6c76f932824ed36915a4517 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Tue, 23 Jun 2026 17:44:39 +0900 Subject: [PATCH 3/5] fix --- AGENTS.md | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 315b84ba..ac55cbdc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,41 +1 @@ -# DevForge - AI エージェント向けガイド(Codex 用エントリポイント) - -このファイルは Codex など `AGENTS.md` を読む AI エージェント向けのエントリポイント。 -Claude Code は `.claude/CLAUDE.md` を自動で読むが、**Codex は CLAUDE.md やパス連動の rule 自動ロードを行わない**ため、ここに読むべきファイルを明示する。 - -## 最初に必ず読む - -- [.claude/CLAUDE.md](./.claude/CLAUDE.md) — 全体ルールの索引(実行方法・コーディング規約・コミット/PR フロー・命名・環境変数・ADR)。**作業前に必読。** - -## 作業領域に応じて読む(該当するものを必ず読む) - -Claude Code では対象パス編集時に自動ロードされる領域別ルール。Codex は自動ロードされないので、触る領域のものを手動で開くこと。 - -### 共通(全領域) - -- [.claude/rules/common/duplication.md](./.claude/rules/common/duplication.md) — DRY / コード重複ポリシー(Rule of Three・抽出先ヒエラルキー) -- [.claude/rules/security.md](./.claude/rules/security.md) — セキュリティ規約(秘密情報・入力検証・認可・IAM 等) - -### backend(`backend/` を触るとき) - -- [.claude/rules/backend/architecture.md](./.claude/rules/backend/architecture.md) — ディレクトリ構成・責務分離 -- [.claude/rules/backend/python.md](./.claude/rules/backend/python.md) — Python コーディング規約 -- [.claude/rules/backend/database.md](./.claude/rules/backend/database.md) — DB / マイグレーション -- [.claude/rules/backend/auth-security.md](./.claude/rules/backend/auth-security.md) — 認証・認可 -- [.claude/rules/backend/test.md](./.claude/rules/backend/test.md) — テスト方針 - -### web(`web/` を触るとき) - -- [.claude/rules/web/architecture.md](./.claude/rules/web/architecture.md) — ディレクトリ構成・責務分離 -- [.claude/rules/web/typescript.md](./.claude/rules/web/typescript.md) — TypeScript コーディング規約 -- [.claude/rules/web/messages.md](./.claude/rules/web/messages.md) — メッセージ管理(リテラル禁止・SSoT) -- [.claude/rules/web/test.md](./.claude/rules/web/test.md) — テスト方針 - -### infra(`infra/` を触るとき) - -- [.claude/rules/infra/opentofu.md](./.claude/rules/infra/opentofu.md) — OpenTofu / modules / environments -- [.claude/rules/infra/test.md](./.claude/rules/infra/test.md) — infra validate / テスト - -## メンテナンス - -`.claude/rules/` にファイルを追加・削除したら、この一覧も更新すること(Claude Code 側はパス連動の自動ロードなので一覧不要だが、Codex 側はこの明示リストが正本)。 +.claude/CLAUDE.md \ No newline at end of file From 10f5fae19956f127f8559d34fe093a4d855c2a9e Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Tue, 23 Jun 2026 17:56:48 +0900 Subject: [PATCH 4/5] =?UTF-8?q?review(infra):=20CodeRabbit=20=E6=8C=87?= =?UTF-8?q?=E6=91=98=E5=AF=BE=E5=BF=9C=EF=BC=88compose=20=E6=97=A2?= =?UTF-8?q?=E5=AE=9A=E5=80=A4=E3=83=BB=E6=9C=AA=E4=BD=BF=E7=94=A8=20secret?= =?UTF-8?q?=20=E5=89=8A=E9=99=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose: VERTEX_LOCATION / VERTEX_ANTHROPIC_LOCATION に :-既定値 を付与。 bare の ${VAR} は未設定時に空文字を注入し backend の os.getenv フォールバックを 潰すため(Vertex client が空ロケーションで初期化される) - cloud_run: Vertex 移行で未使用になった anthropic-api-key / google-api-key を secret_names から削除(コンテナと secretAccessor を destroy / least privilege)。 本 PR で注入廃止と同時に削除する方針に変更(当初の別 PR 分離を取りやめ) make infra-validate(3 環境)/ infra-fmt-check 済み。 Co-Authored-By: Claude Opus 4.8 --- docker-compose.yml | 6 ++++-- infra/modules/cloud_run/main.tf | 11 ++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2730995a..0502fd6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,8 +39,10 @@ services: # Vertex AI(SA→ADC)経由(ADR-0015)でキー不要。ローカルで実 Vertex を叩く場合のみ # gcloud auth application-default login で ADC を用意する(GCP_PROJECT_ID は上で設定済み)。 LLM_LOCAL_OLLAMA: ${LLM_LOCAL_OLLAMA:-1} - VERTEX_LOCATION: ${VERTEX_LOCATION} - VERTEX_ANTHROPIC_LOCATION: ${VERTEX_ANTHROPIC_LOCATION} + # 既定値を付ける(:-)。bare の ${VAR} は未設定時に空文字を注入し、backend 側 + # settings.py の os.getenv フォールバックを潰してしまう(Vertex client が空ロケーションで初期化される) + VERTEX_LOCATION: ${VERTEX_LOCATION:-asia-northeast1} + VERTEX_ANTHROPIC_LOCATION: ${VERTEX_ANTHROPIC_LOCATION:-asia-southeast1} OPENAI_API_KEY: ${OPENAI_API_KEY} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL} OLLAMA_MODEL: ${OLLAMA_MODEL} diff --git a/infra/modules/cloud_run/main.tf b/infra/modules/cloud_run/main.tf index d2cb2cde..6383685b 100644 --- a/infra/modules/cloud_run/main.tf +++ b/infra/modules/cloud_run/main.tf @@ -9,13 +9,10 @@ locals { "jwt-public-key", "internal-secret", "turso-auth-token", - # 棚卸し TODO(ADR-0015): Gemini / Anthropic を Vertex AI(SA→ADC)へ移行したため - # 以下 2 つのキーは Cloud Run へ注入しなくなった。コンテナ自体の削除は別 PR で - # 全環境の設定から外したことを確認してから実施する(field-encryption-key と同様)。 - "anthropic-api-key", - "google-api-key", - # OpenAI のみ GCP に存在せず API キーを継続使用(ADR-0015)。 - # コンテナは常に作成し、注入は enable_extra_llm_providers が true の環境のみ + # Gemini / Anthropic は Vertex AI(SA→ADC)へ移行済み(ADR-0015)。注入廃止に伴い + # anthropic-api-key / google-api-key のシークレットコンテナと secretAccessor も削除した + # (least privilege)。apply で当該シークレットが destroy される点に注意。 + # OpenAI のみ GCP に存在せず API キーを継続使用(注入は enable_extra_llm_providers が true の環境のみ) "openai-api-key", # 決済(Stripe Checkout / ADR-0012 Phase 2)の API キーと Webhook 署名シークレット "stripe-secret-key", From 84e503f257302677553d8318f2914ee39b7521e6 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Wed, 24 Jun 2026 15:10:06 +0900 Subject: [PATCH 5/5] =?UTF-8?q?review(infra):=20CodeRabbit=20=E6=8C=87?= =?UTF-8?q?=E6=91=98=E5=AF=BE=E5=BF=9C=EF=BC=88moved=20block=E3=83=BBlocat?= =?UTF-8?q?ion=20=E6=A4=9C=E8=A8=BC=E3=83=BBADR=20=E6=95=B4=E5=90=88?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cloudflare: cloudflare_record → cloudflare_dns_record の改名に moved block を追加。 全環境 use_custom_domain=false(count=0)で現状インスタンスは無いが、有効化済み state での destroy/recreate(DNS ダウンタイム)を防ぐ防御策 - cloud_run: vertex_location / vertex_anthropic_location に空文字を弾く validation を追加 (空文字注入で backend のデフォルト補完が潰れるのを防ぐ) - ADR-0015: 未使用シークレットを「残置(別PR削除)」→「本PRで削除」に記述を実装と整合 make infra-validate(3 環境)/ infra-fmt-check 済み。 Co-Authored-By: Claude Opus 4.8 --- docs/adr/0015-vertex-ai-for-gemini-anthropic.md | 2 +- infra/modules/cloud_run/variables.tf | 10 ++++++++++ infra/modules/cloudflare/main.tf | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/adr/0015-vertex-ai-for-gemini-anthropic.md b/docs/adr/0015-vertex-ai-for-gemini-anthropic.md index 1da4ed27..ec3c6c15 100644 --- a/docs/adr/0015-vertex-ai-for-gemini-anthropic.md +++ b/docs/adr/0015-vertex-ai-for-gemini-anthropic.md @@ -44,7 +44,7 @@ Cloud Run の SA には `roles/aiplatform.user` が既に付与済みで、IAM - **クロスリージョンのレイテンシ**: Claude のみ Singapore のため Cloud Run(Tokyo) からのレイテンシが Gemini より増える。許容範囲の想定。 - **Anthropic on Vertex のモデル可用性**: `asia-southeast1` での Claude 提供と model id(版)は Model Garden が正本。提供が無ければ region 再検討(global 等)。 - **無料枠の喪失**: ADR-0013 の「dev 無料確認」動機(Gemini Flash 無料枠)は Vertex では消える。dev 確認は Ollama 無料パスで吸収する。 -- **未使用シークレットの残置**: `anthropic-api-key` / `google-api-key` の Secret Manager コンテナは注入停止のみ行い、削除は別 PR で全環境の設定除外を確認後に実施(破壊的操作の分離)。 +- **未使用シークレットの削除**: `anthropic-api-key` / `google-api-key` は注入停止に加え、`secret_names` からも除外して Secret Manager コンテナと secretAccessor を削除する(least privilege)。`tofu apply` で当該シークレットが destroy される不可逆操作のため、pre-migration の Cloud Run revision へのロールバックは不可になる点に注意。 ## 将来の移行条件 diff --git a/infra/modules/cloud_run/variables.tf b/infra/modules/cloud_run/variables.tf index 44955ff7..7307b3e5 100644 --- a/infra/modules/cloud_run/variables.tf +++ b/infra/modules/cloud_run/variables.tf @@ -68,12 +68,22 @@ variable "vertex_location" { description = "Gemini を叩く Vertex AI のロケーション(ADR-0015)。" type = string default = "asia-northeast1" + # 空文字は VERTEX_LOCATION="" を注入し backend のデフォルト補完を潰すため弾く + validation { + condition = trimspace(var.vertex_location) != "" + error_message = "vertex_location must not be empty." + } } variable "vertex_anthropic_location" { description = "Claude を叩く Vertex AI のロケーション。Tokyo 未提供のため Singapore(ADR-0015)。" type = string default = "asia-southeast1" + # 空文字は VERTEX_ANTHROPIC_LOCATION="" を注入し backend のデフォルト補完を潰すため弾く + validation { + condition = trimspace(var.vertex_anthropic_location) != "" + error_message = "vertex_anthropic_location must not be empty." + } } variable "cloud_tasks_service_account" { diff --git a/infra/modules/cloudflare/main.tf b/infra/modules/cloudflare/main.tf index 565fd064..42b1f847 100644 --- a/infra/modules/cloudflare/main.tf +++ b/infra/modules/cloudflare/main.tf @@ -37,3 +37,11 @@ resource "cloudflare_dns_record" "app" { } } } + +# provider v5 のリソース改名(cloudflare_record → cloudflare_dns_record)に伴う state 移設。 +# 全環境 use_custom_domain = false(count = 0)で現状インスタンスは無いが、カスタムドメインを +# 有効化済みの state が存在する場合に destroy/recreate(DNS ダウンタイム)を防ぐ。 +moved { + from = cloudflare_record.app + to = cloudflare_dns_record.app +}