fix(stockplatform): classify ai recommendations stale after core recovery
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 58s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-02 21:20:30 +08:00
parent b9349159fb
commit 0beb3e4f10
7 changed files with 528 additions and 1 deletions

View File

@@ -2577,6 +2577,11 @@ def _stockplatform_recovery_next_state(
"controlled_recovery_preflight_waiting_stockplatform_production_"
"migration_control_channel_readback_and_public_api_verifier"
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"controlled_recovery_preflight_waiting_stockplatform_ai_"
"recommendation_pipeline_readback_and_public_api_verifier"
)
if api_layer == "api_live_data_dependency_not_ready":
return (
"controlled_recovery_preflight_waiting_stockplatform_data_dependency_"
@@ -2610,6 +2615,15 @@ def _stockplatform_recovery_reason(
"KM/RAG/MCP/PlayBook writeback receipts. This is not a generic API "
"container recovery task and not a manual end state."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"Live StockPlatform API health is reachable and official core data "
"readbacks have recovered, but AI recommendations remain stale. "
"The P0-006 lane now points at the AI recommendation pipeline "
"readback, public API verifier, and KM/RAG/MCP/PlayBook writeback "
"receipts. This is not a generic DB/source repair task and not a "
"manual end state."
)
if api_layer == "api_live_data_dependency_not_ready":
return (
"Live StockPlatform API health is reachable, but freshness and/or "
@@ -2661,6 +2675,18 @@ def _stockplatform_recovery_action(
"hosts, reload Nginx, manually write DB rows, fake freshness, "
"trigger unrelated workflows, or read secrets from this lane."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"Run the StockPlatform AI recommendation pipeline controlled "
"readback in order: verify core freshness and ingestion are still "
"green, inspect the AI recommendation queue/pipeline receipt in "
"read-only mode, use only the existing bounded pipeline path after "
"dry-run if apply is needed, rerun public freshness/selection "
"brief verifiers, and write metadata-only KM/RAG/MCP/PlayBook "
"receipts. Do not restart Docker daemon, reboot hosts, reload "
"Nginx, write DB rows, fake freshness, trigger unrelated "
"workflows, or read secrets from this lane."
)
if api_layer == "api_live_data_dependency_not_ready":
return (
"Run the StockPlatform data dependency controlled preflight in order: "
@@ -2697,6 +2723,14 @@ def _stockplatform_recovery_first_order(
"production migration/control-channel path with rollback and public "
"API verifier; do not manually DB-write or fake freshness."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-CONTROLLED-RECOVERY-PREFLIGHT: "
"StockPlatform API and official core data are recovered, but AI "
"recommendations are stale; run AI recommendation pipeline "
"readback, public API verifier, and metadata writeback before "
"claiming the lane is only reboot-window gated."
)
if api_layer == "api_live_data_dependency_not_ready":
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-CONTROLLED-RECOVERY-PREFLIGHT: "
@@ -2724,6 +2758,11 @@ def _stockplatform_runtime_next_state(
"blocked_live_stockplatform_public_api_requires_production_"
"migration_control_channel_readback"
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"blocked_live_stockplatform_public_api_requires_ai_recommendation_"
"pipeline_readback"
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"blocked_live_stockplatform_public_api_requires_data_dependency_"
@@ -2747,6 +2786,14 @@ def _stockplatform_runtime_drift_reason(
"the production migration/control-channel path before claiming the "
"reboot SLO lane is only waiting on a fresh boot window."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"StockPlatform public API health is now HTTP 200 and official core "
"data readbacks are recovered, but AI recommendations are stale. "
"P0-006 must stay on the AI recommendation pipeline readback and "
"public verifier path before claiming the reboot SLO lane is only "
"waiting on a fresh boot window."
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"StockPlatform public API health is now HTTP 200, but freshness and "
@@ -2776,6 +2823,15 @@ def _stockplatform_runtime_drift_action(
"reload Nginx, manually write DB rows, fake freshness, trigger "
"unrelated workflows, or read secrets from this lane."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"Run the StockPlatform AI recommendation pipeline readback with "
"target selector, source-of-truth diff, dry-run/check-mode, "
"rollback, and public API verifier, then rerun the public "
"readback. Do not restart Docker daemon, reboot hosts, reload "
"Nginx, manually write DB rows, fake freshness, trigger unrelated "
"workflows, or read secrets from this lane."
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"Run the StockPlatform data dependency/source contract readback "
@@ -2805,6 +2861,14 @@ def _stockplatform_runtime_first_order(
"postgres_not_ready; run the production migration/control-channel "
"path before claiming the lane is only reboot-window gated."
)
if _stockplatform_ai_recommendations_wait(data_dependency_classification):
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-RUNTIME-READBACK: live public "
"StockPlatform API health is 200 and official core data are "
"recovered, but AI recommendations are stale; run AI recommendation "
"pipeline readback and public verifier before claiming the lane is "
"only reboot-window gated."
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-RUNTIME-READBACK: live public "
@@ -3519,7 +3583,11 @@ def _stockplatform_external_data_wait_active(state: dict[str, Any]) -> bool:
return (
state.get("stockplatform_source_contract_readback_present") is True
and state.get("stockplatform_source_contract_ready") is not True
and data_dependency == "freshness_or_ingestion_not_ready"
and data_dependency
in {
"freshness_or_ingestion_not_ready",
"ai_recommendations_stale_after_core_recovered",
}
and any(
blocker
in {
@@ -3532,6 +3600,15 @@ def _stockplatform_external_data_wait_active(state: dict[str, Any]) -> bool:
)
def _stockplatform_ai_recommendations_wait(
data_dependency_classification: str,
) -> bool:
return (
data_dependency_classification
== "ai_recommendations_stale_after_core_recovered"
)
def _apply_parallel_mainline_execution_overlay(payload: dict[str, Any]) -> None:
state = _dict(payload.setdefault("mainline_execution_state", {}))
items = _list(payload.get("commander_inserted_requirement_work_items"))

View File

@@ -27,6 +27,9 @@ _DEFAULT_ROUTE_SOURCE = (
/ "templates"
/ "188-all-sites.conf.j2"
)
_AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED = (
"ai_recommendations_stale_after_core_recovered"
)
def load_latest_stockplatform_public_api_controlled_recovery_preflight(
@@ -383,6 +386,14 @@ def _current_apply_blocker(
"postgres_readiness_and_production_migration_control_path_readback_"
"required_before_apply"
)
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return (
"ai_recommendation_pipeline_readback_required_before_public_api_"
"verifier"
)
if api_layer_classification == "api_live_data_dependency_not_ready":
return (
"data_dependency_control_path_readback_required_before_source_"
@@ -401,6 +412,15 @@ def _allowed_scope(
"km_rag_mcp_playbook_metadata_writeback",
]
if api_layer_classification == "api_live_data_dependency_not_ready":
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return [
"read_only_ai_recommendation_queue_status",
"read_only_ai_recommendation_pipeline_receipt",
*common,
]
scope = [
"read_only_data_dependency_status_inspection",
"read_only_source_freshness_contract_diff",
@@ -459,6 +479,14 @@ def _safe_next_step(
candidate_packaged
and api_layer_classification == "api_live_data_dependency_not_ready"
):
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return (
"run_stockplatform_ai_recommendation_pipeline_readback_then_"
"public_api_verifier"
)
if data_dependency_classification != "postgres_not_ready":
return (
"run_stockplatform_data_dependency_source_contract_readback_"
@@ -489,6 +517,47 @@ def _controlled_work_items(
return []
base_status = "queued_waiting_control_path_readback" if candidate_packaged else "blocked_waiting_route_evidence"
if api_layer_classification == "api_live_data_dependency_not_ready":
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return [
{
"id": "stockplatform-ai-recommendation-control-path-readback",
"title": "StockPlatform AI recommendation control path readback",
"owner_agent": "DevOps Agent",
"status": base_status,
"required_output": "non_secret_pipeline_liveness_and_runtime_contract_match",
},
{
"id": "stockplatform-ai-recommendation-queue-inspection",
"title": "Read-only AI recommendation queue inspection",
"owner_agent": "SRE Agent",
"status": "waiting_control_path_readback",
"required_output": "ai_recommendations_latest_date_and_queue_receipt",
},
{
"id": "stockplatform-ai-recommendation-pipeline-preflight",
"title": "AI recommendation pipeline verifier preflight",
"owner_agent": "Data Reliability Agent",
"status": "waiting_read_only_inspection",
"required_output": "dry_run_pipeline_receipt_no_manual_db_write",
},
{
"id": "stockplatform-api-post-apply-verifier",
"title": "Public API post-apply verifier",
"owner_agent": "Verifier Agent",
"status": "ready",
"required_output": "public_health_freshness_ingestion_green",
},
{
"id": "stockplatform-ai-learning-writeback",
"title": "KM / RAG / MCP / PlayBook trust writeback",
"owner_agent": "Learning Agent",
"status": "ready",
"required_output": "metadata_only_ai_pipeline_receipt_and_trust_delta",
},
]
contract_title = "Source freshness and Postgres contract preflight"
contract_output = "dry_run_contract_diff_no_db_write"
if data_dependency_classification == "postgres_not_ready":
@@ -582,6 +651,46 @@ def _controlled_action_candidates(
if runtime_ready:
return []
if api_layer_classification == "api_live_data_dependency_not_ready":
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return [
{
"id": "read_only_ai_recommendation_pipeline_readback",
"execution_route": "stockplatform_ai_recommendation_executor",
"current_status": (
"queued_waiting_control_path_readback"
if candidate_packaged
else "blocked_waiting_route_evidence"
),
"check_mode": "read_only_ai_recommendation_queue_and_pipeline_receipt",
"apply_allowed_when": [
"host_control_path_liveness_readback_ok",
"stockplatform_api_health_ok",
"core_freshness_and_ingestion_ok",
"post_apply_public_verifier_ready",
],
"forbidden_actions": [
"secret_read",
"docker_daemon_restart",
"host_reboot",
"database_write",
"fake_freshness_marker",
],
},
{
"id": "ai_recommendation_pipeline_verifier_preflight",
"execution_route": "stockplatform_ai_recommendation_executor",
"current_status": "waiting_read_only_pipeline_readback",
"check_mode": "dry_run_ai_recommendation_pipeline_contract",
"bounded_apply": (
"use the existing AI recommendation pipeline path only "
"after control-channel readback and dry-run; no manual "
"DB rows, no fake freshness marker, no destructive restore"
),
},
]
second_candidate = {
"id": "source_freshness_contract_preflight",
"execution_route": "stockplatform_data_contract_executor",

View File

@@ -27,6 +27,9 @@ _DATA_READINESS_RECEIPT_KEY = (
)
_SOURCE_CONTRACT_SCHEMA_VERSION = "stockplatform_source_contract_control_readback_v1"
_SOURCE_CONTRACT_RECEIPT_KEY = "stockplatform_source_contract_readback"
_AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED = (
"ai_recommendations_stale_after_core_recovered"
)
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
_RECOVERY_RECEIPT_FILE = (
"stockplatform-public-api-runtime-recovery-control-receipt.snapshot.json"
@@ -224,6 +227,10 @@ def _build_payload(
freshness_sources
),
"data_dependency_classification": data_dependency_classification,
"ai_recommendations_stale_after_core_recovered": (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
),
"postgres_not_ready": postgres_not_ready,
"committed_freshness_status": str(
committed_stockplatform.get("freshness_status") or "unknown"
@@ -287,6 +294,10 @@ def _build_payload(
recovery_control_path_blockers
),
"data_dependency_classification": data_dependency_classification,
"ai_recommendations_stale_after_core_recovered": (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
),
"postgres_not_ready": postgres_not_ready,
"postgres_readiness_or_data_source_readback_present": (
data_readiness_control_readback.get("readback_present") is True
@@ -454,6 +465,15 @@ def _data_readiness_control_readback(
if data_ready:
status = "stockplatform_data_readiness_ready"
safe_next_step = "run_post_apply_public_api_verifier_and_clear_p0_006_blocker"
elif (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
status = "blocked_stockplatform_ai_recommendations_stale_after_core_recovered"
safe_next_step = (
"run_stockplatform_ai_recommendation_pipeline_readback_then_rerun_"
"public_api_verifier"
)
elif readback_present and postgres_not_ready:
status = "blocked_stockplatform_postgres_not_ready"
safe_next_step = (
@@ -490,6 +510,10 @@ def _data_readiness_control_readback(
"postgres_ready": data_ready and not postgres_not_ready,
"postgres_not_ready": postgres_not_ready,
"data_dependency_classification": data_dependency_classification,
"ai_recommendations_stale_after_core_recovered": (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
),
"freshness_http_status": freshness_http_status,
"ingestion_http_status": ingestion_http_status,
"freshness_status": str(freshness_json.get("status") or "unknown"),
@@ -556,6 +580,16 @@ def _source_contract_control_readback(
if data_ready:
status = "stockplatform_source_contract_ready"
safe_next_step = "run_post_apply_public_api_verifier_and_clear_p0_006_blocker"
elif (
readback_present
and data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
status = "stockplatform_source_contract_core_ready_ai_recommendations_stale"
safe_next_step = (
"run_stockplatform_ai_recommendation_pipeline_readback_then_rerun_"
"public_api_verifier"
)
elif readback_present and data_dependency_classification == (
"freshness_or_ingestion_not_ready"
):
@@ -604,6 +638,10 @@ def _source_contract_control_readback(
"safe_next_step": safe_next_step,
"source_contract_ready": data_ready,
"data_dependency_classification": data_dependency_classification,
"ai_recommendations_stale_after_core_recovered": (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
),
"postgres_not_ready": postgres_not_ready,
"freshness_status": str(freshness_json.get("status") or "unknown"),
"ingestion_status": str(ingestion_json.get("status") or "unknown"),
@@ -715,6 +753,14 @@ def _blocked_safe_next_step(*, data_dependency_classification: str) -> str:
"run_stockplatform_production_migration_path_after_control_channel_"
"readback_then_rerun_public_api_readback"
)
if (
data_dependency_classification
== _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
):
return (
"run_stockplatform_ai_recommendation_pipeline_readback_then_rerun_"
"public_api_verifier"
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"run_stockplatform_data_dependency_source_contract_readback_then_"
@@ -735,6 +781,14 @@ def _data_dependency_classification(
return "none"
if any("postgres_not_ready" in blocker for blocker in active_blockers):
return "postgres_not_ready"
data_blockers = [
blocker
for blocker in active_blockers
if blocker.startswith("stockplatform_freshness_")
or blocker.startswith("stockplatform_ingestion_")
]
if data_blockers == ["stockplatform_freshness_ai_recommendations_stale"]:
return _AI_RECOMMENDATIONS_STALE_AFTER_CORE_RECOVERED
if any(
blocker.startswith("stockplatform_freshness_")
or blocker.startswith("stockplatform_ingestion_")