守住 runtime 環境變數文件契約
This commit is contained in:
24
.env.example
24
.env.example
@@ -272,11 +272,15 @@ MOMO_AI_AUTOMATION_SMOKE_HISTORY_LIMIT=200
|
||||
|
||||
# [選填] OpenClaw Telegram bot
|
||||
OPENCLAW_BOT_TOKEN=your_openclaw_bot_token_here
|
||||
TELEGRAM_BOT_USERNAME=@OpenClawAwoooI_Bot
|
||||
OPENCLAW_BOT_USERNAME=@OpenClawAwoooI_Bot
|
||||
OPENCLAW_GROUP_ID=-1003940688311
|
||||
OPENCLAW_ALLOWED_USERS=
|
||||
OPENCLAW_ADMIN_USER_IDS=
|
||||
# [預設 1] 舊行為:空白名單仍允許私訊;正式環境建議設 0 並填 OPENCLAW_ALLOWED_USERS
|
||||
OPENCLAW_ALLOW_PRIVATE_WITHOUT_WHITELIST=1
|
||||
# [預設 24] PPT 報表快取保留時間(小時)
|
||||
OPENCLAW_PPT_CACHE_TTL_HOURS=24
|
||||
# [預設 OFF] ADR-019 agent dispatch;啟用後只讓白名單 cmd 轉 NL agent 處理
|
||||
OPENCLAW_AGENT_DISPATCH=0
|
||||
OPENCLAW_AGENT_DISPATCH_CMDS=sales,top,vendor
|
||||
@@ -287,15 +291,30 @@ YOUTUBE_API_KEY=
|
||||
GEMINI_TIMEOUT=60
|
||||
|
||||
# [預設 OFF] AI runtime feature flags;未完成部署驗收前不要在正式環境打開
|
||||
AI_CALL_LOGGING_ENABLED=true
|
||||
MODEL_ROUTER_ENABLED=false
|
||||
COST_THROTTLE_ENABLED=false
|
||||
COST_THROTTLE_PROJECT_RATIO=1.10
|
||||
COST_UNTHROTTLE_PROJECT_RATIO=0.95
|
||||
RAG_ENABLED=false
|
||||
RAG_DEFAULT_THRESHOLD=0.85
|
||||
RAG_DEFAULT_TOP_K=5
|
||||
RAG_EMBED_MODEL=bge-m3:latest
|
||||
RAG_EMBED_DIM=1024
|
||||
RAG_EMBED_NORMALIZE=true
|
||||
PPT_VISION_ENABLED=false
|
||||
PPT_VISION_MODEL=minicpm-v:latest
|
||||
PPT_VISION_TIMEOUT=60
|
||||
DEEPSEEK_DIRECT_ENABLED=false
|
||||
DEEPSEEK_API_KEY=
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
|
||||
DEEPSEEK_MODEL=deepseek-chat
|
||||
DEEPSEEK_TIMEOUT=60
|
||||
OPENCLAW_DAILY_HERMES_TEMPLATE=true
|
||||
OPENCLAW_OLLAMA_MODEL=qwen2.5-coder:7b
|
||||
PROMOTION_PENDING_BATCH_SIZE=50
|
||||
AWAITING_REVIEW_PUSH_BATCH=5
|
||||
TELEGRAM_ADMIN_CHAT_ID=
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Ollama / MCP / 密碼政策
|
||||
@@ -315,6 +334,8 @@ OPENCLAW_QA_OLLAMA_FIRST=true
|
||||
OPENCLAW_QA_OLLAMA_MODEL=qwen3:14b
|
||||
OPENCLAW_QA_OLLAMA_HOST=http://34.143.170.20:11434
|
||||
OPENCLAW_QA_OLLAMA_TIMEOUT=60
|
||||
NEMOTRON_OLLAMA_FIRST=true
|
||||
NEMOTRON_OLLAMA_MODEL=qwen3:14b
|
||||
NEMOTRON_OLLAMA_TIMEOUT=180
|
||||
|
||||
# [預設 OFF] MCP Router;需先部署 docker-compose.mcp.yml 並完成健康檢查再開
|
||||
@@ -323,6 +344,9 @@ MCP_POSTGRES_URL=http://127.0.0.1:3001
|
||||
MCP_FIRECRAWL_URL=http://127.0.0.1:3002
|
||||
MCP_OMNISEARCH_URL=http://127.0.0.1:3003
|
||||
MCP_FILESYSTEM_URL=http://127.0.0.1:3004
|
||||
MCP_TIMEOUT_SEC=30
|
||||
MCP_CACHE_TTL_SEC=3600
|
||||
MCP_MAX_RESULT_BYTES=65536
|
||||
MCP_CACHE_TTL_HOURS=24
|
||||
MCP_GEMINI_MODEL=gemini-2.0-flash
|
||||
|
||||
|
||||
@@ -16,6 +16,29 @@ def _env_example_keys() -> set[str]:
|
||||
return keys
|
||||
|
||||
|
||||
def _runtime_env_keys_from_code() -> set[str]:
|
||||
pattern = re.compile(r"""os\.(?:getenv|environ\.get)\(\s*['"]([A-Z][A-Z0-9_]+)['"]""")
|
||||
scan_paths = [
|
||||
ROOT / "app.py",
|
||||
ROOT / "config.py",
|
||||
ROOT / "scheduler.py",
|
||||
ROOT / "run_scheduler.py",
|
||||
ROOT / "routes",
|
||||
ROOT / "services",
|
||||
ROOT / "utils",
|
||||
]
|
||||
|
||||
keys = set()
|
||||
for scan_path in scan_paths:
|
||||
paths = scan_path.rglob("*.py") if scan_path.is_dir() else [scan_path]
|
||||
for path in paths:
|
||||
if not path.exists() or "__pycache__" in path.parts:
|
||||
continue
|
||||
content = path.read_text(encoding="utf-8", errors="ignore")
|
||||
keys.update(match.group(1) for match in pattern.finditer(content))
|
||||
return keys
|
||||
|
||||
|
||||
def test_phase3f_orphan_ai_services_stay_removed():
|
||||
orphan_services = [
|
||||
"services/elephant_alpha_decision_router.py",
|
||||
@@ -44,9 +67,13 @@ def test_active_guides_do_not_point_to_removed_ai_services():
|
||||
|
||||
def test_env_example_documents_runtime_and_ai_automation_variables():
|
||||
expected_keys = {
|
||||
"AI_CALL_LOGGING_ENABLED",
|
||||
"AUTO_FIX_ENABLED",
|
||||
"AWAITING_REVIEW_PUSH_BATCH",
|
||||
"CODE_REVIEW_AUTO_FIX_ENABLED",
|
||||
"COST_THROTTLE_ENABLED",
|
||||
"COST_THROTTLE_PROJECT_RATIO",
|
||||
"COST_UNTHROTTLE_PROJECT_RATIO",
|
||||
"DEEPSEEK_API_KEY",
|
||||
"DEEPSEEK_BASE_URL",
|
||||
"DEEPSEEK_DIRECT_ENABLED",
|
||||
@@ -69,26 +96,45 @@ def test_env_example_documents_runtime_and_ai_automation_variables():
|
||||
"MOMO_EVENT_ROUTER_REPLAY_ON_SUCCESS",
|
||||
"MCP_FILESYSTEM_URL",
|
||||
"MCP_FIRECRAWL_URL",
|
||||
"MCP_CACHE_TTL_SEC",
|
||||
"MCP_MAX_RESULT_BYTES",
|
||||
"MCP_OMNISEARCH_URL",
|
||||
"MCP_POSTGRES_URL",
|
||||
"MCP_ROUTER_ENABLED",
|
||||
"MCP_TIMEOUT_SEC",
|
||||
"MODEL_ROUTER_ENABLED",
|
||||
"N8N_HOST",
|
||||
"N8N_PASSWORD",
|
||||
"N8N_PROTOCOL",
|
||||
"N8N_USER",
|
||||
"N8N_WEBHOOK_BASE_URL",
|
||||
"NEMOTRON_OLLAMA_FIRST",
|
||||
"NEMOTRON_OLLAMA_MODEL",
|
||||
"NEMOTRON_OLLAMA_TIMEOUT",
|
||||
"OLLAMA_EMBED_TIMEOUT",
|
||||
"OPENCLAW_ADMIN_USER_IDS",
|
||||
"OPENCLAW_AGENT_DISPATCH",
|
||||
"OPENCLAW_AGENT_DISPATCH_CMDS",
|
||||
"OPENCLAW_ALLOW_PRIVATE_WITHOUT_WHITELIST",
|
||||
"OPENCLAW_DAILY_HERMES_TEMPLATE",
|
||||
"OPENCLAW_OLLAMA_MODEL",
|
||||
"OPENCLAW_PPT_CACHE_TTL_HOURS",
|
||||
"OPENCLAW_QA_OLLAMA_FIRST",
|
||||
"OPENCLAW_QA_OLLAMA_HOST",
|
||||
"OPENCLAW_QA_OLLAMA_MODEL",
|
||||
"OPENCLAW_QA_OLLAMA_TIMEOUT",
|
||||
"PPT_VISION_ENABLED",
|
||||
"PPT_VISION_MODEL",
|
||||
"PPT_VISION_TIMEOUT",
|
||||
"PROMOTION_PENDING_BATCH_SIZE",
|
||||
"RAG_ENABLED",
|
||||
"RAG_DEFAULT_THRESHOLD",
|
||||
"RAG_DEFAULT_TOP_K",
|
||||
"RAG_EMBED_DIM",
|
||||
"RAG_EMBED_MODEL",
|
||||
"RAG_EMBED_NORMALIZE",
|
||||
"TELEGRAM_ADMIN_CHAT_ID",
|
||||
"TELEGRAM_BOT_USERNAME",
|
||||
"TELEGRAM_CHAT_ID",
|
||||
"WEB_CONCURRENCY",
|
||||
}
|
||||
@@ -96,6 +142,15 @@ def test_env_example_documents_runtime_and_ai_automation_variables():
|
||||
assert expected_keys <= _env_example_keys()
|
||||
|
||||
|
||||
def test_env_example_documents_runtime_os_env_keys():
|
||||
internal_runtime_keys = {
|
||||
"MOMO_ALLOW_INSECURE_CONFIG_FOR_TESTS",
|
||||
"PYTEST_CURRENT_TEST",
|
||||
}
|
||||
|
||||
assert _runtime_env_keys_from_code() - internal_runtime_keys <= _env_example_keys()
|
||||
|
||||
|
||||
def test_scheduler_does_not_silently_swallow_exceptions():
|
||||
scheduler_source = (ROOT / "scheduler.py").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user