fix(api): expose kb_ready and gate E2E/nightly on shared KB
Health reports kb_ready; PI E2E uses layered Load + require-online; nightly preflights SELECT 1 before product suite. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,8 @@ SYSTEM_DB_NAME=agent_system_dev
|
||||
SYSTEM_DB_PUBLISH_PORT=5433
|
||||
|
||||
# 4. 共享知识库(容器 app 经 shuimu-kb-shared 访问)
|
||||
# KB_DB_PASSWORD 必须在仓库根 .env 提供;config.Load("") 会分层合并根 .env + 本文件。
|
||||
# 勿依赖代码内默认口令(已清空);宿主机 go test / 本地进程同样走分层 Load。
|
||||
KB_DB_USER=regkb
|
||||
KB_DB_NAME=regulations
|
||||
KB_DB_HOST=shuimu-kb-shared
|
||||
|
||||
@@ -22,10 +22,12 @@ SYSTEM_DB_NAME=agent_system_main
|
||||
SYSTEM_DB_PUBLISH_PORT=5435
|
||||
|
||||
# 4. 共享知识库连接 (只读检索共享数据)
|
||||
# KB_DB_PASSWORD 必须在仓库根 .env;Load("") 分层合并;生产禁止弱口令/空口令。
|
||||
KB_DB_USER=regkb
|
||||
KB_DB_NAME=regulations
|
||||
KB_DB_HOST=shuimu-kb-shared
|
||||
KB_DB_PORT=5432
|
||||
KB_DB_PASSWORD=
|
||||
|
||||
# 5. Cursor / LLM Agent Runner 服务
|
||||
CURSOR_RUNNER_PORT=18767
|
||||
|
||||
@@ -20,6 +20,7 @@ SYSTEM_DB_NAME=agent_system_test
|
||||
SYSTEM_DB_PUBLISH_PORT=5434
|
||||
|
||||
# 4. 共享知识库连接 (只读检索共享数据)
|
||||
# KB_DB_PASSWORD 必须在仓库根 .env;config.Load("") 分层合并根 .env + 本文件。勿依赖代码默认口令。
|
||||
KB_DB_USER=regkb
|
||||
KB_DB_NAME=regulations
|
||||
KB_DB_HOST=shuimu-kb-shared
|
||||
|
||||
@@ -30,6 +30,7 @@ type HealthResponse struct {
|
||||
AgentBackend string `json:"agent_backend"`
|
||||
Version string `json:"version"`
|
||||
SharedKBPort int `json:"shared_kb_port"`
|
||||
KBReady bool `json:"kb_ready"`
|
||||
DatabaseReady bool `json:"database_ready"`
|
||||
SchemaVersion int64 `json:"schema_version,omitempty"`
|
||||
SchemaReady bool `json:"schema_ready,omitempty"`
|
||||
@@ -117,12 +118,15 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
|
||||
}
|
||||
}
|
||||
|
||||
kbReady := s.kb != nil && s.kb.Online()
|
||||
|
||||
return HealthResponse{
|
||||
Status: status,
|
||||
Env: s.cfg.Env,
|
||||
AgentBackend: s.cfg.AgentBackend,
|
||||
Version: "v1.0",
|
||||
SharedKBPort: s.cfg.KbDbPort,
|
||||
KBReady: kbReady,
|
||||
DatabaseReady: dbReady,
|
||||
SchemaVersion: schemaVersion,
|
||||
SchemaReady: schemaReady,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -22,8 +21,13 @@ import (
|
||||
"chat-agent-system/internal/squad"
|
||||
)
|
||||
|
||||
// mergePiAgentE2EKbConfig 从 .env / 环境变量注入共享知识库与向量检索配置(E2E 必须走真实 KB,不可 stub)。
|
||||
// mergePiAgentE2EKbConfig 从分层 Load(根 .env + configs/.env.{ENV})注入共享知识库配置。
|
||||
// E2E 默认 fail-closed:须在线 KB;仅 KB_ALLOW_OFFLINE=1 时允许降级并打印 NOTE。
|
||||
func mergePiAgentE2EKbConfig(cfg *config.Config) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
_ = os.Setenv("KB_REQUIRE_ONLINE", "1")
|
||||
envCfg, err := config.Load("")
|
||||
if err == nil && envCfg != nil {
|
||||
cfg.KbDbHost = envCfg.KbDbHost
|
||||
@@ -38,39 +42,16 @@ func mergePiAgentE2EKbConfig(cfg *config.Config) {
|
||||
cfg.RerankerAPIKey = envCfg.RerankerAPIKey
|
||||
cfg.RerankerModel = envCfg.RerankerModel
|
||||
}
|
||||
config.NormalizeKBHostForRuntime(cfg)
|
||||
if cfg.KbDbHost == "" {
|
||||
cfg.KbDbHost = "127.0.0.1"
|
||||
}
|
||||
// Host-side go test cannot use compose DNS; shuimu-kb-shared often resolves to a
|
||||
// poisoned/VPN address (e.g. 198.18.0.0/15). Prefer the published localhost port.
|
||||
if cfg.KbDbHost == "shuimu-kb-shared" || cfg.KbDbHost == "shuimu-kb" {
|
||||
cfg.KbDbHost = "127.0.0.1"
|
||||
}
|
||||
if cfg.KbDbPort == 0 {
|
||||
cfg.KbDbPort = 5432
|
||||
}
|
||||
if cfg.KbDbUser == "" {
|
||||
cfg.KbDbUser = "regkb"
|
||||
}
|
||||
// configs/.env.dev often omits KB_DB_PASSWORD; Load then keeps the insecure default.
|
||||
// Prefer process env, then root .env, so host E2E actually authenticates to :5432.
|
||||
if v := strings.TrimSpace(os.Getenv("KB_DB_PASSWORD")); v != "" {
|
||||
cfg.KbDbPassword = v
|
||||
} else if cfg.KbDbPassword == "" || cfg.KbDbPassword == "shared_kb_secret_pass" {
|
||||
if b, err := os.ReadFile(".env"); err == nil {
|
||||
for _, line := range strings.Split(string(b), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "KB_DB_PASSWORD=") {
|
||||
continue
|
||||
}
|
||||
cfg.KbDbPassword = strings.Trim(strings.TrimPrefix(line, "KB_DB_PASSWORD="), "\"'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg.KbDbPassword == "" {
|
||||
cfg.KbDbPassword = envOr("KB_DB_PASSWORD", "kb_secret_pass")
|
||||
}
|
||||
if cfg.KbDbName == "" {
|
||||
cfg.KbDbName = "regulations"
|
||||
}
|
||||
@@ -131,12 +112,20 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) {
|
||||
var healthData server.HealthResponse
|
||||
_ = json.NewDecoder(respHealth.Body).Decode(&healthData)
|
||||
respHealth.Body.Close()
|
||||
t.Logf("✓ [0. 环境校验] Health检查正常: Env=%s, AgentBackend=%s, SharedKBPort=%d",
|
||||
healthData.Env, healthData.AgentBackend, healthData.SharedKBPort)
|
||||
t.Logf("✓ [0. 环境校验] Health检查正常: Env=%s, AgentBackend=%s, SharedKBPort=%d kb_ready=%v",
|
||||
healthData.Env, healthData.AgentBackend, healthData.SharedKBPort, healthData.KBReady)
|
||||
if !kb.RequireOnline() && !healthData.KBReady {
|
||||
t.Logf("NOTE: KB offline degradation test (KB_ALLOW_OFFLINE=1)")
|
||||
}
|
||||
kbClient := kb.NewClient(cfg)
|
||||
defer kbClient.Close()
|
||||
if stats, err := kbClient.Stats(ctx); err != nil {
|
||||
t.Logf("⚠ [0. 知识库] 未连接共享 KB(%s:%d/%s): %v", cfg.KbDbHost, cfg.KbDbPort, cfg.KbDbName, err)
|
||||
if !kbClient.Online() {
|
||||
if kb.RequireOnline() {
|
||||
t.Fatalf("KB required online but offline (host=%s port=%d); set KB_ALLOW_OFFLINE=1 only for degradation tests", cfg.KbDbHost, cfg.KbDbPort)
|
||||
}
|
||||
t.Logf("NOTE: KB offline degradation test — continuing without shared KB")
|
||||
} else if stats, err := kbClient.Stats(ctx); err != nil {
|
||||
t.Fatalf("KB online but Stats failed: %v", err)
|
||||
} else if reg, ok := stats[kb.DomainRegulatory]; ok {
|
||||
t.Logf("✓ [0. 知识库] 已连接共享 KB: documents=%d chunks=%d", reg.DocumentCount, reg.ChunkCount)
|
||||
} else {
|
||||
|
||||
@@ -126,6 +126,44 @@ export PI_AGENT_RUNNER_AUTH_TOKEN="${TOKEN}"
|
||||
export RUNNER_AUTH_TOKEN="${TOKEN}"
|
||||
export PI_AGENT_MODEL="${MODEL_ID}"
|
||||
export RUN_PI_E2E=1
|
||||
export KB_REQUIRE_ONLINE="${KB_REQUIRE_ONLINE:-1}"
|
||||
|
||||
# Shared KB preflight (host :5432). Fail closed unless KB_ALLOW_OFFLINE=1.
|
||||
preflight_shared_kb() {
|
||||
if [[ "${KB_ALLOW_OFFLINE:-0}" == "1" ]]; then
|
||||
echo "NOTE: KB offline degradation test (KB_ALLOW_OFFLINE=1)" | tee "${EVIDENCE_DIR}/kb-allow-offline.txt"
|
||||
return 0
|
||||
fi
|
||||
local kb_host="${KB_DB_HOST:-127.0.0.1}"
|
||||
local kb_port="${KB_DB_PORT:-5432}"
|
||||
local kb_user="${KB_DB_USER:-regkb}"
|
||||
local kb_name="${KB_DB_NAME:-regulations}"
|
||||
local kb_pass="${KB_DB_PASSWORD:-}"
|
||||
if [[ -z "${kb_pass}" && -f "${ROOT}/.env" ]]; then
|
||||
kb_pass="$(grep -E '^KB_DB_PASSWORD=' "${ROOT}/.env" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||
fi
|
||||
if [[ -z "${kb_pass}" ]]; then
|
||||
echo "KB_DB_PASSWORD missing (root .env or env); cannot preflight shared KB" | tee "${EVIDENCE_DIR}/infra-kb-missing-password.txt"
|
||||
write_receipt infra_fail infra "${EVIDENCE_DIR}/infra-kb-missing-password.txt"
|
||||
echo "infrastructure failure: shared KB password missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v psql >/dev/null 2>&1; then
|
||||
echo "psql not found; skip SQL preflight, rely on go test KB_REQUIRE_ONLINE" | tee "${EVIDENCE_DIR}/kb-preflight-skipped-no-psql.txt"
|
||||
return 0
|
||||
fi
|
||||
if ! PGPASSWORD="${kb_pass}" psql -h "${kb_host}" -p "${kb_port}" -U "${kb_user}" -d "${kb_name}" -c 'SELECT 1' >/dev/null 2>"${EVIDENCE_DIR}/kb-preflight.err"; then
|
||||
{
|
||||
echo "shared KB preflight failed host=${kb_host} port=${kb_port} db=${kb_name} user=${kb_user}"
|
||||
cat "${EVIDENCE_DIR}/kb-preflight.err" 2>/dev/null || true
|
||||
} | tee "${EVIDENCE_DIR}/infra-kb-down.txt"
|
||||
write_receipt infra_fail infra "${EVIDENCE_DIR}/infra-kb-down.txt"
|
||||
echo "infrastructure failure: shared KB unreachable" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "shared KB preflight OK ${kb_host}:${kb_port}/${kb_name}" | tee "${EVIDENCE_DIR}/kb-preflight-ok.txt"
|
||||
}
|
||||
preflight_shared_kb
|
||||
|
||||
# Clear env pollution that flakes short/config tests elsewhere.
|
||||
unset AGENT_TIMEOUT KB_DATABASE_URL KB_EMBEDDING_DIM 2>/dev/null || true
|
||||
|
||||
Reference in New Issue
Block a user