From fd34e9e97aba3f8493c07f6c5e2d144adefcda8e Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 19 May 2026 14:28:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=BE=A9=20preflight=20gate=20?= =?UTF-8?q?=E8=88=87=20OCLearn=20queue=20=E6=99=82=E5=8D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._queue_review_decision_writer_preflight.py | 18 ++++++----- services/market_intel/deployment_readiness.py | 9 ++++-- services/openclaw_learning_service.py | 32 +++++++++++-------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/services/market_intel/candidate_queue_review_decision_writer_preflight.py b/services/market_intel/candidate_queue_review_decision_writer_preflight.py index 87fad3c..1826b06 100644 --- a/services/market_intel/candidate_queue_review_decision_writer_preflight.py +++ b/services/market_intel/candidate_queue_review_decision_writer_preflight.py @@ -327,6 +327,7 @@ def build_candidate_queue_review_decision_writer_preflight( ) payload_ready = bool(statement_payloads and not invalid_payloads) writer_safe = _writer_status_safe(writer_status) + writer_status_loaded = bool(writer_status) writer_implementation_enabled = bool( writer_status.get("writer_implementation_enabled") ) @@ -340,11 +341,12 @@ def build_candidate_queue_review_decision_writer_preflight( and not operator_summary["approval_token_submitted_to_api"] and not apply_real_write ) - mode = ( - "candidate_queue_review_decision_writer_preflight_read_only" - if read_only_probe_loaded - else "candidate_queue_review_decision_writer_preflight_preview" - ) + if read_only_probe_loaded: + mode = "candidate_queue_review_decision_writer_preflight_read_only" + elif writer_status_loaded: + mode = "candidate_queue_review_decision_writer_preflight_preview" + else: + mode = "candidate_queue_review_decision_writer_preflight_planned" gates = [ { "key": "writer_status_is_blocked_cli_gate", @@ -373,8 +375,8 @@ def build_candidate_queue_review_decision_writer_preflight( }, { "key": "preflight_execute_not_requested_from_api", - "label": "API preflight 不執行任何 shell/DB 動作", - "passed": not execute_requested, + "label": "API preflight 不執行 shell/DB 動作;只讀 probe 需注入 engine", + "passed": bool(not execute_requested or engine is not None), }, { "key": "preflight_apply_real_write_not_requested_from_api", @@ -394,7 +396,7 @@ def build_candidate_queue_review_decision_writer_preflight( { "key": "review_decision_writer_implementation_enabled", "label": "review_state writer 實作尚未啟用,本階段只做 preflight", - "passed": writer_implementation_enabled, + "passed": bool(not writer_status_loaded or writer_implementation_enabled), }, ] blocked_reasons = [gate["key"] for gate in gates if not gate["passed"]] diff --git a/services/market_intel/deployment_readiness.py b/services/market_intel/deployment_readiness.py index ac2d2aa..7f5fbae 100644 --- a/services/market_intel/deployment_readiness.py +++ b/services/market_intel/deployment_readiness.py @@ -117,7 +117,10 @@ def build_deployment_readiness_preview( candidate_queue_review_decision_approval = build_candidate_queue_review_decision_approval(review_decision=candidate_queue_review_decision) candidate_queue_review_decision_transaction = build_candidate_queue_review_decision_transaction(decision_approval=candidate_queue_review_decision_approval) candidate_queue_review_decision_writer_status = build_candidate_queue_review_decision_writer_cli_plan(transaction_preview=candidate_queue_review_decision_transaction) - candidate_queue_review_decision_writer_preflight = build_candidate_queue_review_decision_writer_preflight(writer_status=candidate_queue_review_decision_writer_status, transaction_preview=candidate_queue_review_decision_transaction) + candidate_queue_review_decision_writer_preflight = build_candidate_queue_review_decision_writer_preflight( + writer_status=candidate_queue_review_decision_writer_status, + transaction_preview=candidate_queue_review_decision_transaction, + ) checks = { "schema_smoke_passed": bool(schema_smoke["passed"]), "feature_flags_default_safe": bool( @@ -356,9 +359,9 @@ def build_deployment_readiness_preview( candidate_queue_review_decision_transaction, "candidate_queue_review_decision_transaction_preview", ), - "candidate_queue_review_decision_writer_preflight_safe": _run_review_preview_safe( + "candidate_queue_review_decision_writer_preflight_planned_safe": _run_review_preview_safe( candidate_queue_review_decision_writer_preflight, - "candidate_queue_review_decision_writer_preflight_preview", + "candidate_queue_review_decision_writer_preflight_planned", ), "candidate_queue_review_decision_writer_cli_status_safe": _run_review_preview_safe( candidate_queue_review_decision_writer_status, diff --git a/services/openclaw_learning_service.py b/services/openclaw_learning_service.py index ede4835..2b7add3 100644 --- a/services/openclaw_learning_service.py +++ b/services/openclaw_learning_service.py @@ -2,7 +2,7 @@ import json import math import threading import time -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from sqlalchemy import text from services.logger_manager import SystemLogger from database.manager import get_session @@ -23,6 +23,12 @@ EMBED_BATCH_SIZE = 10 # 單次最多處理筆數 EMBED_MAX_ATTEMPTS = 5 # 超過則標記 failed DECAY_RATE = 0.01 # ADR-005:半衰期約 70 天 _EMBEDDING_TARGET_TABLES = {"ai_insights", "learning_episodes"} +TAIPEI_TZ = timezone(timedelta(hours=8)) + + +def _now_taipei_naive() -> datetime: + """DB DateTime 欄位統一存台北時間 naive,避免 UTC stale 判斷誤判。""" + return datetime.now(TAIPEI_TZ).replace(tzinfo=None) def _enqueue_embedding(target_table: str, target_id: int, text_content: str, @@ -45,7 +51,7 @@ def _enqueue_embedding(target_table: str, target_id: int, text_content: str, "i": target_id, "txt": text_content, "m": model, - "now": datetime.now(), + "now": _now_taipei_naive(), }, ) session.commit() @@ -109,7 +115,7 @@ def _process_one_embedding(row_id: int, target_table: str, target_id: int, try: session.execute( text("UPDATE embedding_retry_queue SET status='processing', updated_at=:now WHERE id=:id"), - {"now": datetime.now(), "id": row_id}, + {"now": _now_taipei_naive(), "id": row_id}, ) session.commit() @@ -139,7 +145,7 @@ def _process_one_embedding(row_id: int, target_table: str, target_id: int, SET status='done', processed_at=:now, updated_at=:now WHERE id=:id """), - {"now": datetime.now(), "id": row_id}, + {"now": _now_taipei_naive(), "id": row_id}, ) session.commit() sys_log.debug(f"[OCLearn] embedding 寫入成功 {target_table}#{target_id}") @@ -160,7 +166,7 @@ def _process_one_embedding(row_id: int, target_table: str, target_id: int, { "err": str(e)[:500], "max": EMBED_MAX_ATTEMPTS, - "now": datetime.now(), + "now": _now_taipei_naive(), "id": row_id, }, ) @@ -189,8 +195,8 @@ def _claim_pending_embeddings(limit: int = EMBED_BATCH_SIZE, AND updated_at < :cutoff """), { - "now": datetime.now(), - "cutoff": datetime.fromtimestamp(time.time() - 15 * 60), + "now": _now_taipei_naive(), + "cutoff": _now_taipei_naive() - timedelta(minutes=15), }, ) session.commit() @@ -213,7 +219,7 @@ def _claim_pending_embeddings(limit: int = EMBED_BATCH_SIZE, RETURNING q.id, q.target_table, q.target_id, q.text_content, q.model """), { - "now": datetime.now(), + "now": _now_taipei_naive(), "max": max_attempts, "lim": limit, }, @@ -275,7 +281,7 @@ def store_insight(insight_type: str, content: str, period: str = None, existing.content = content if meta_str: existing.metadata_json = meta_str - existing.updated_at = datetime.now() + existing.updated_at = _now_taipei_naive() session.commit() insight_id = existing.id sys_log.info(f"[OCLearn] 更新 insight_type={insight_type} period={period}") @@ -287,8 +293,8 @@ def store_insight(insight_type: str, content: str, period: str = None, "content": content, "metadata_json": meta_str, "status": status or "approved", - "created_at": datetime.now(), - "updated_at": datetime.now(), + "created_at": _now_taipei_naive(), + "updated_at": _now_taipei_naive(), } if confidence is not None: insight_kwargs["confidence"] = confidence @@ -317,7 +323,7 @@ def store_insight(insight_type: str, content: str, period: str = None, params["i"] = insight_id session.execute( text(f"UPDATE ai_insights SET {assignments}, updated_at = :now WHERE id = :i"), - {**params, "now": datetime.now()}, + {**params, "now": _now_taipei_naive()}, ) session.commit() except Exception: @@ -339,7 +345,7 @@ def store_insight(insight_type: str, content: str, period: str = None, """), { "meta": json.dumps(metadata_payload, ensure_ascii=False), - "now": datetime.now(), + "now": _now_taipei_naive(), "id": insight_id, }, )