fix(worker): delegate production ddl to cd
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 59s
CD Pipeline / build-and-deploy (push) Successful in 7m9s
CD Pipeline / post-deploy-checks (push) Successful in 1m53s

This commit is contained in:
ogt
2026-07-11 20:12:02 +08:00
parent 99e69001e9
commit 8424af5343
4 changed files with 50 additions and 9 deletions

View File

@@ -298,6 +298,13 @@ class Settings(BaseSettings):
"processes that share a constrained production role." "processes that share a constrained production role."
), ),
) )
DATABASE_BOOTSTRAP_DDL_ENABLED: bool = Field(
default=True,
description=(
"Run idempotent schema bootstrap DDL during process startup. "
"Production workers disable this after CD migration preflight."
),
)
# ========================================================================== # ==========================================================================
# Redis (192.168.0.188:6380, DB 0 - 與 OpenClaw 共用) # Redis (192.168.0.188:6380, DB 0 - 與 OpenClaw 共用)

View File

@@ -582,15 +582,26 @@ async def _main() -> None:
await init_redis_pool() await init_redis_pool()
await init_worker_redis_pool() # Worker 專屬,無超時限制 await init_worker_redis_pool() # Worker 專屬,無超時限制
# Initialize PostgreSQL (Episodic Memory) - 確保 incidents 表存在 # Production schema ownership belongs to CD migration/preflight. Local and
try: # development workers retain the idempotent bootstrap fallback by default.
await init_db() if settings.DATABASE_BOOTSTRAP_DDL_ENABLED:
logger.info("postgresql_initialized", status="ok") try:
except Exception as e: await init_db()
logger.error( logger.info("postgresql_initialized", status="ok")
"postgresql_init_failed", except Exception as e:
error=str(e), logger.error(
message="Episodic Memory (DB) will be unavailable - incidents won't persist", "postgresql_init_failed",
error=str(e),
message=(
"Episodic Memory (DB) will be unavailable - incidents "
"won't persist"
),
)
else:
logger.info(
"database_bootstrap_ddl_skipped",
owner="cd_migration_preflight",
workload="signal_worker",
) )
try: try:

View File

@@ -8,10 +8,13 @@ Runtime bootstrap guard tests.
from __future__ import annotations from __future__ import annotations
import inspect
from collections.abc import Awaitable from collections.abc import Awaitable
from pathlib import Path
from typing import Any from typing import Any
import pytest import pytest
import yaml
from sqlalchemy.exc import DBAPIError from sqlalchemy.exc import DBAPIError
@@ -63,6 +66,24 @@ class _ConnectionBudgetEngine:
raise RuntimeError('too many connections for role "awoooi"') raise RuntimeError('too many connections for role "awoooi"')
def test_production_worker_delegates_bootstrap_ddl_to_cd() -> None:
from src.workers import signal_worker
root = Path(__file__).resolve().parents[3]
manifest = yaml.safe_load(
(root / "k8s/awoooi-prod/08-deployment-worker.yaml").read_text(
encoding="utf-8"
)
)
container = manifest["spec"]["template"]["spec"]["containers"][0]
env = {item["name"]: item.get("value") for item in container["env"]}
source = inspect.getsource(signal_worker._main)
assert env["DATABASE_BOOTSTRAP_DDL_ENABLED"] == "false"
assert "if settings.DATABASE_BOOTSTRAP_DDL_ENABLED" in source
assert 'owner="cd_migration_preflight"' in source
def test_get_engine_uses_database_pool_budget(monkeypatch): def test_get_engine_uses_database_pool_budget(monkeypatch):
from src.db import base as db_base from src.db import base as db_base

View File

@@ -167,6 +167,8 @@ spec:
value: "0" value: "0"
- name: DATABASE_NULL_POOL - name: DATABASE_NULL_POOL
value: "true" value: "true"
- name: DATABASE_BOOTSTRAP_DDL_ENABLED
value: "false"
- name: AWOOOI_BUILD_COMMIT_SHA - name: AWOOOI_BUILD_COMMIT_SHA
value: "ea857399517abed2824e536a2dda7e77463512c3" value: "ea857399517abed2824e536a2dda7e77463512c3"
- name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER - name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER