feat(translate,scheduler,trace): new-key-only mode, stale PENDING protection, trace_id propagation

Translations:
- FR-045: new-key-only execution mode — translate only rows with unseen keys
- Compare source row keys against TranslationRecord.source_data from last succeeded run
- Baseline expired (>90 days) fallback to full mode with baseline_expired event
- run_noop early return when zero new keys (skip LLM + SQL)

Scheduler reliability:
- Stale PENDING protection: runs older than 1h auto-marked FAILED, no longer block schedule
- load_schedules() reloads active translation schedules from DB on restart
- add_translation_job/remove_translation_job register/unregister with APScheduler
- execution_mode column on translation_schedules with additive DB migration

Automation view:
- GET /settings/automation/translation-schedules endpoint
- Translation schedules displayed on Automation page below validation policies

Trace propagation:
- seed_trace_id() in all background entry points:
  TaskManager._run_task/_flusher_loop, SchedulerService._trigger_backup,
  websocket_endpoint, IdMappingService, all standalone scripts

Migrations:
- dictionary_entries: origin_run_id, origin_row_key, origin_user_id
- translation_schedules: execution_mode

Tests:
- 3 new test modules (22 tests): core_scheduler, executor_filter, scheduler_execution+guard
- Fix pre-existing translate test isolation (conftest.py)
- Fix test_list_runs_filter_status parameter
This commit is contained in:
2026-05-14 10:13:56 +03:00
parent d1695fe536
commit 8d0bc6fb93
26 changed files with 1525 additions and 19 deletions

View File

@@ -10,6 +10,7 @@ import json
from datetime import date, datetime, timezone
from typing import Any, Dict, List, Optional
from ..core.cot_logger import seed_trace_id
from ..models.clean_release import CandidateArtifact, ReleaseCandidate
from ..services.clean_release.approval_service import (
approve_candidate,
@@ -468,6 +469,7 @@ def run_revoke(args: argparse.Namespace) -> int:
# #region main [TYPE Function]
# @BRIEF CLI entrypoint for clean release commands.
def main(argv: Optional[List[str]] = None) -> int:
seed_trace_id()
parser = build_parser()
args = parser.parse_args(argv)

View File

@@ -19,6 +19,7 @@ BACKEND_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..", ".."))
if BACKEND_ROOT not in sys.path:
sys.path.insert(0, BACKEND_ROOT)
from src.core.cot_logger import seed_trace_id
from src.models.clean_release import (
CandidateArtifact,
CheckFinalStatus,
@@ -662,6 +663,7 @@ def tui_main(stdscr: curses.window):
def main() -> int:
seed_trace_id()
# TUI requires interactive terminal; headless mode must use CLI/API flow.
if not sys.stdout.isatty():
print(

View File

@@ -19,6 +19,7 @@ from src.core.database import AuthSessionLocal, init_db
from src.core.auth.security import get_password_hash
from src.models.auth import User, Role
from src.core.logger import logger, belief_scope
from src.core.cot_logger import seed_trace_id
# #region create_admin [TYPE Function]
@@ -27,6 +28,7 @@ from src.core.logger import logger, belief_scope
# @POST: Admin user exists in auth.db.
#
def create_admin(username, password, email=None):
seed_trace_id()
with belief_scope("create_admin"):
db = AuthSessionLocal()
try:

View File

@@ -17,6 +17,7 @@ sys.path.append(str(Path(__file__).parent.parent.parent))
from src.core.database import init_db
from src.core.encryption_key import ensure_encryption_key
from src.core.logger import logger, belief_scope
from src.core.cot_logger import seed_trace_id
from src.scripts.seed_permissions import seed_permissions
@@ -27,6 +28,7 @@ from src.scripts.seed_permissions import seed_permissions
# @RELATION CALLS -> init_db
# @RELATION CALLS -> seed_permissions
def run_init():
seed_trace_id()
with belief_scope("init_auth_db"):
logger.info("Initializing authentication database...")
try:

View File

@@ -21,6 +21,7 @@ from sqlalchemy import create_engine, text
from sqlalchemy.exc import SQLAlchemyError
from src.core.logger import belief_scope, logger
from src.core.cot_logger import seed_trace_id
# #region Constants [TYPE Section]
@@ -327,6 +328,7 @@ def _migrate_tasks_and_logs(engine, sqlite_conn: sqlite3.Connection) -> Dict[str
def run_migration(
sqlite_path: Path, target_url: str, legacy_config_path: Optional[Path]
) -> Dict[str, int]:
seed_trace_id()
with belief_scope("run_migration"):
logger.reason(
f"sqlite={sqlite_path} target={target_url}",

View File

@@ -19,6 +19,7 @@ from src.core.database import AuthSessionLocal
from src.models.auth import Permission, Role
from src.core.auth.repository import AuthRepository
from src.core.logger import logger, belief_scope
from src.core.cot_logger import seed_trace_id
# #region INITIAL_PERMISSIONS [C:3] [TYPE Constant]
# @BRIEF Canonical bootstrap permission tuples seeded into auth storage.
@@ -66,6 +67,7 @@ INITIAL_PERMISSIONS = [
# @RELATION DEPENDS_ON -> AuthRepository
# @RELATION DEPENDS_ON -> INITIAL_PERMISSIONS
def seed_permissions():
seed_trace_id()
with belief_scope("seed_permissions"):
db = AuthSessionLocal()
try:

View File

@@ -19,6 +19,7 @@ sys.path.append(str(Path(__file__).parent.parent.parent))
from src.core.config_manager import ConfigManager
from src.core.config_models import Environment
from src.core.logger import belief_scope, logger
from src.core.cot_logger import seed_trace_id
from src.core.superset_client import SupersetClient
@@ -255,6 +256,7 @@ def _build_chart_template_pool(
# @POST: Returns execution statistics dictionary.
# @SIDE_EFFECT: Creates objects in Superset environments.
def seed_superset_load_data(args: argparse.Namespace) -> Dict:
seed_trace_id()
rng = random.Random(args.seed)
env_map = _resolve_target_envs(args.envs)
@@ -381,6 +383,7 @@ def seed_superset_load_data(args: argparse.Namespace) -> Dict:
# @PRE: Command line arguments are valid.
# @POST: Prints summary and exits with non-zero status on failure.
def main() -> None:
seed_trace_id()
with belief_scope("seed_superset_load_test.main"):
args = _parse_args()
result = seed_superset_load_data(args)

View File

@@ -18,10 +18,12 @@ sys.path.append(str(Path(__file__).parent.parent.parent))
from src.core.superset_client import SupersetClient
from src.core.config_manager import ConfigManager
from src.core.logger import logger
from src.core.cot_logger import seed_trace_id
def test_dashboard_dataset_relations():
"""Test fetching dataset-to-dashboard relationships."""
seed_trace_id()
# Load environment from existing config
config_manager = ConfigManager()