feat(stockplatform): surface data readiness receipt
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 53s
CD Pipeline / build-and-deploy (push) Successful in 6m56s
CD Pipeline / post-deploy-checks (push) Successful in 1m59s

This commit is contained in:
Your Name
2026-07-02 18:39:40 +08:00
parent 12cb07bdeb
commit ae5844733d
10 changed files with 835 additions and 24 deletions

View File

@@ -994,6 +994,9 @@ def apply_stockplatform_public_api_runtime_readback(
runtime_blockers = _strings(runtime_readback.get("active_blockers"))
runtime_rollups = _dict(runtime_readback.get("rollups"))
runtime_readback_body = _dict(runtime_readback.get("readback"))
data_readiness_readback = _dict(
runtime_readback.get("data_readiness_control_readback")
)
data_dependency_classification = str(
runtime_rollups.get("data_dependency_classification")
or runtime_readback_body.get("data_dependency_classification")
@@ -1033,6 +1036,18 @@ def apply_stockplatform_public_api_runtime_readback(
data_dependency_classification
)
state["stockplatform_public_api_postgres_not_ready"] = postgres_not_ready
state["stockplatform_postgres_readiness_or_data_source_readback_status"] = str(
data_readiness_readback.get("status") or "unknown"
)
state["stockplatform_postgres_readiness_or_data_source_readback_present"] = bool(
data_readiness_readback.get("readback_present") is True
)
state["stockplatform_postgres_readiness_or_data_source_receipt_provided"] = bool(
data_readiness_readback.get("receipt_provided") is True
)
state["stockplatform_postgres_readiness_or_data_source_postgres_ready"] = bool(
data_readiness_readback.get("postgres_ready") is True
)
for item in _list(payload.get("in_progress_or_blocked_in_priority_order")):
workplan = _dict(item)
@@ -1063,6 +1078,18 @@ def apply_stockplatform_public_api_runtime_readback(
data_dependency_classification
)
evidence["stockplatform_postgres_not_ready"] = postgres_not_ready
evidence[
"stockplatform_postgres_readiness_or_data_source_readback_status"
] = state["stockplatform_postgres_readiness_or_data_source_readback_status"]
evidence[
"stockplatform_postgres_readiness_or_data_source_readback_present"
] = state["stockplatform_postgres_readiness_or_data_source_readback_present"]
evidence[
"stockplatform_postgres_readiness_or_data_source_receipt_provided"
] = state["stockplatform_postgres_readiness_or_data_source_receipt_provided"]
evidence[
"stockplatform_postgres_readiness_or_data_source_postgres_ready"
] = state["stockplatform_postgres_readiness_or_data_source_postgres_ready"]
evidence["stockplatform_recovery_control_path_status"] = (
recovery_control_path_status
)
@@ -1079,12 +1106,14 @@ def apply_stockplatform_public_api_runtime_readback(
)
workplan["reason"] = (
_stockplatform_runtime_drift_reason(
data_dependency_classification=data_dependency_classification,
postgres_not_ready=postgres_not_ready,
)
)
professional_fix = _dict(workplan.setdefault("professional_fix", {}))
professional_fix["action"] = (
_stockplatform_runtime_drift_action(
data_dependency_classification=data_dependency_classification,
postgres_not_ready=postgres_not_ready,
)
)
@@ -1108,11 +1137,15 @@ def apply_stockplatform_public_api_runtime_readback(
"P0-006-STOCKPLATFORM-PUBLIC-API-RUNTIME-READBACK"
)
state["next_executable_mainline_state"] = (
_stockplatform_runtime_next_state(postgres_not_ready=postgres_not_ready)
_stockplatform_runtime_next_state(
data_dependency_classification=data_dependency_classification,
postgres_not_ready=postgres_not_ready,
)
)
payload["status"] = "p0_006_blocked_stockplatform_public_api_runtime_drift"
payload["next_execution_order"] = [
_stockplatform_runtime_first_order(
data_dependency_classification=data_dependency_classification,
postgres_not_ready=postgres_not_ready,
),
(
@@ -1152,6 +1185,9 @@ def apply_stockplatform_public_api_controlled_recovery_preflight(
source_diff = _dict(recovery_preflight.get("source_of_truth_diff"))
target_selector = _dict(recovery_preflight.get("target_selector"))
controlled_policy = _dict(recovery_preflight.get("controlled_apply_policy"))
data_readiness_readback = _dict(
recovery_preflight.get("data_readiness_control_readback")
)
candidate_packaged = bool(
rollups.get("controlled_recovery_candidate_packaged") is True
)
@@ -1167,6 +1203,18 @@ def apply_stockplatform_public_api_controlled_recovery_preflight(
state["stockplatform_public_api_controlled_recovery_data_dependency"] = (
data_dependency_classification
)
state["stockplatform_postgres_readiness_or_data_source_readback_status"] = str(
data_readiness_readback.get("status") or "unknown"
)
state["stockplatform_postgres_readiness_or_data_source_readback_present"] = bool(
data_readiness_readback.get("readback_present") is True
)
state["stockplatform_postgres_readiness_or_data_source_receipt_provided"] = bool(
data_readiness_readback.get("receipt_provided") is True
)
state["stockplatform_postgres_readiness_or_data_source_postgres_ready"] = bool(
data_readiness_readback.get("postgres_ready") is True
)
state["stockplatform_public_api_controlled_recovery_active_blockers"] = _strings(
recovery_preflight.get("active_blockers")
)
@@ -1208,6 +1256,18 @@ def apply_stockplatform_public_api_controlled_recovery_preflight(
evidence["stockplatform_source_of_truth_diff_status"] = str(
source_diff.get("status") or ""
)
evidence[
"stockplatform_postgres_readiness_or_data_source_readback_status"
] = state["stockplatform_postgres_readiness_or_data_source_readback_status"]
evidence[
"stockplatform_postgres_readiness_or_data_source_readback_present"
] = state["stockplatform_postgres_readiness_or_data_source_readback_present"]
evidence[
"stockplatform_postgres_readiness_or_data_source_receipt_provided"
] = state["stockplatform_postgres_readiness_or_data_source_receipt_provided"]
evidence[
"stockplatform_postgres_readiness_or_data_source_postgres_ready"
] = state["stockplatform_postgres_readiness_or_data_source_postgres_ready"]
evidence["stockplatform_controlled_recovery_candidate_packaged"] = (
candidate_packaged
)
@@ -1291,6 +1351,14 @@ def apply_stockplatform_public_api_controlled_recovery_preflight(
summary["stockplatform_public_api_host_control_path_readback_required"] = bool(
rollups.get("host_control_path_readback_required") is True
)
summary["stockplatform_postgres_readiness_or_data_source_readback_status"] = str(
state.get("stockplatform_postgres_readiness_or_data_source_readback_status")
or "unknown"
)
summary["stockplatform_postgres_readiness_or_data_source_receipt_provided"] = bool(
state.get("stockplatform_postgres_readiness_or_data_source_receipt_provided")
is True
)
def apply_harbor_registry_controlled_recovery_preflight(
@@ -2493,19 +2561,32 @@ def _stockplatform_recovery_first_order(
)
def _stockplatform_runtime_next_state(*, postgres_not_ready: bool) -> str:
def _stockplatform_runtime_next_state(
*,
data_dependency_classification: str,
postgres_not_ready: bool,
) -> str:
if postgres_not_ready:
return (
"blocked_live_stockplatform_public_api_requires_production_"
"migration_control_channel_readback"
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"blocked_live_stockplatform_public_api_requires_data_dependency_"
"source_contract_readback"
)
return (
"blocked_live_stockplatform_public_api_requires_separate_runtime_"
"control_path_recovery_without_daemon_restart"
)
def _stockplatform_runtime_drift_reason(*, postgres_not_ready: bool) -> str:
def _stockplatform_runtime_drift_reason(
*,
data_dependency_classification: str,
postgres_not_ready: bool,
) -> str:
if postgres_not_ready:
return (
"StockPlatform public API health is now HTTP 200, but freshness and "
@@ -2513,6 +2594,13 @@ def _stockplatform_runtime_drift_reason(*, postgres_not_ready: bool) -> str:
"the production migration/control-channel 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 "
"ingestion readbacks are not green. P0-006 must stay on the data "
"dependency/source contract readback path before claiming the reboot "
"SLO lane is only waiting on a fresh boot window."
)
return (
"Committed P0-006 scorecard says StockPlatform freshness and ingestion "
"are ok, but live public API readback is blocked. This must remain "
@@ -2521,7 +2609,11 @@ def _stockplatform_runtime_drift_reason(*, postgres_not_ready: bool) -> str:
)
def _stockplatform_runtime_drift_action(*, postgres_not_ready: bool) -> str:
def _stockplatform_runtime_drift_action(
*,
data_dependency_classification: str,
postgres_not_ready: bool,
) -> str:
if postgres_not_ready:
return (
"Recover the StockPlatform production migration/control channel "
@@ -2531,6 +2623,15 @@ def _stockplatform_runtime_drift_action(*, postgres_not_ready: bool) -> str:
"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 "
"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."
)
return (
"Recover the StockPlatform API container/runtime control path with a "
"separate bounded plan, then rerun public API readback. Do not restart "
@@ -2539,7 +2640,11 @@ def _stockplatform_runtime_drift_action(*, postgres_not_ready: bool) -> str:
)
def _stockplatform_runtime_first_order(*, postgres_not_ready: bool) -> str:
def _stockplatform_runtime_first_order(
*,
data_dependency_classification: str,
postgres_not_ready: bool,
) -> str:
if postgres_not_ready:
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-RUNTIME-READBACK: live public "
@@ -2547,6 +2652,13 @@ def _stockplatform_runtime_first_order(*, postgres_not_ready: bool) -> str:
"postgres_not_ready; run the production migration/control-channel "
"path 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 "
"StockPlatform API health is 200 but freshness/ingestion are not "
"green; run the data dependency/source contract readback before "
"claiming the lane is only reboot-window gated."
)
return (
"P0-006-STOCKPLATFORM-PUBLIC-API-RUNTIME-READBACK: live public "
"StockPlatform API is blocked while committed scorecard still says "
@@ -3094,6 +3206,18 @@ def _refresh_rollups_after_stockplatform_overlay(
rollups["stockplatform_public_api_recovery_control_path_blocker_count"] = len(
recovery_control_path_blockers
)
rollups["stockplatform_postgres_readiness_or_data_source_readback_present"] = bool(
state.get("stockplatform_postgres_readiness_or_data_source_readback_present")
is True
)
rollups["stockplatform_postgres_readiness_or_data_source_receipt_provided"] = bool(
state.get("stockplatform_postgres_readiness_or_data_source_receipt_provided")
is True
)
rollups["stockplatform_postgres_readiness_or_data_source_postgres_ready"] = bool(
state.get("stockplatform_postgres_readiness_or_data_source_postgres_ready")
is True
)
rollups["active_p0_live_active_blocker_count"] = len(active_blockers)
rollups["active_p0_event_gated_by_fresh_reboot_window_only"] = (
rollups.get("active_p0_event_gated_by_fresh_reboot_window_only") is True
@@ -3118,6 +3242,14 @@ def _refresh_rollups_after_stockplatform_overlay(
summary["stockplatform_public_api_recovery_control_path_blockers"] = (
recovery_control_path_blockers
)
summary["stockplatform_postgres_readiness_or_data_source_readback_status"] = str(
state.get("stockplatform_postgres_readiness_or_data_source_readback_status")
or "unknown"
)
summary["stockplatform_postgres_readiness_or_data_source_receipt_provided"] = bool(
state.get("stockplatform_postgres_readiness_or_data_source_receipt_provided")
is True
)
summary["next_executable_mainline_workplan_id"] = str(
state.get("next_executable_mainline_workplan_id") or ""
)

View File

@@ -48,6 +48,9 @@ def _build_payload(
runtime_ready = runtime.get("status") == "stockplatform_public_api_runtime_ready"
readback = _dict(runtime.get("readback"))
rollups = _dict(runtime.get("rollups"))
data_readiness_control_readback = _dict(
runtime.get("data_readiness_control_readback")
)
api_layer_classification = _classify_api_layer(readback, rollups, runtime_ready)
data_dependency_classification = _data_dependency_classification(runtime)
route_target_ready = route_contract.get("route_target_ready") is True
@@ -84,6 +87,7 @@ def _build_payload(
candidate_packaged=candidate_packaged,
route_target_ready=route_target_ready,
api_layer_classification=api_layer_classification,
data_dependency_classification=data_dependency_classification,
),
"active_blockers": active_blockers,
"active_blocker_count": len(active_blockers),
@@ -118,9 +122,16 @@ def _build_payload(
"ingestion_http_status": readback.get("ingestion_http_status"),
"http_502_count": _int(rollups.get("http_502_count")),
"data_dependency_classification": data_dependency_classification,
"data_readiness_control_readback_status": (
data_readiness_control_readback.get("status")
),
"data_readiness_control_receipt_provided": (
data_readiness_control_readback.get("receipt_provided") is True
),
"route_source_file": str(route_contract.get("route_source_file") or ""),
"route_target": str(route_contract.get("route_target") or ""),
},
"data_readiness_control_readback": data_readiness_control_readback,
"controlled_apply_policy": {
"risk_level": "high",
"break_glass_required": False,
@@ -184,6 +195,12 @@ def _build_payload(
_post_apply_verifiers(runtime_ready=runtime_ready)
),
"learning_writeback_contract_count": len(_learning_writeback_contracts()),
"postgres_readiness_or_data_source_readback_present": (
data_readiness_control_readback.get("readback_present") is True
),
"postgres_readiness_or_data_source_receipt_provided": (
data_readiness_control_readback.get("receipt_provided") is True
),
},
"operation_boundaries": {
"read_only_public_https_probe": True,
@@ -258,12 +275,20 @@ def _classify_api_layer(
def _data_dependency_classification(runtime: dict[str, Any]) -> str:
runtime_rollups = _dict(runtime.get("rollups"))
runtime_readback = _dict(runtime.get("readback"))
live_classification = str(
runtime_rollups.get("data_dependency_classification")
or runtime_readback.get("data_dependency_classification")
or ""
)
if live_classification and live_classification != "none":
return live_classification
blockers = _strings(runtime.get("active_blockers"))
readback = _dict(runtime.get("readback"))
if readback.get("api_health_http_status") != 200:
if runtime_readback.get("api_health_http_status") != 200:
return "none"
if (
readback.get("postgres_not_ready") is True
runtime_readback.get("postgres_not_ready") is True
or any("postgres_not_ready" in blocker for blocker in blockers)
):
return "postgres_not_ready"
@@ -395,6 +420,7 @@ def _safe_next_step(
candidate_packaged: bool,
route_target_ready: bool,
api_layer_classification: str,
data_dependency_classification: str,
) -> str:
if runtime_ready:
return "stockplatform_public_api_ready_keep_p0_006_reboot_window_gate"
@@ -402,6 +428,11 @@ def _safe_next_step(
candidate_packaged
and api_layer_classification == "api_live_data_dependency_not_ready"
):
if data_dependency_classification != "postgres_not_ready":
return (
"run_stockplatform_data_dependency_source_contract_readback_"
"then_public_api_verifier"
)
return (
"run_stockplatform_production_migration_path_after_control_channel_"
"readback_then_public_api_verifier"

View File

@@ -19,6 +19,12 @@ from src.services.reboot_auto_recovery_slo_scorecard import (
from src.services.snapshot_paths import default_operations_dir
_API_SCHEMA_VERSION = "stockplatform_public_api_runtime_readback_v1"
_DATA_READINESS_SCHEMA_VERSION = (
"stockplatform_postgres_readiness_or_data_source_readback_v1"
)
_DATA_READINESS_RECEIPT_KEY = (
"stockplatform_postgres_readiness_or_data_source_readback"
)
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
_RECOVERY_RECEIPT_FILE = (
"stockplatform-public-api-runtime-recovery-control-receipt.snapshot.json"
@@ -125,19 +131,30 @@ def _build_payload(
ingestion_json=ingestion_json,
)
ready = all(checks.values())
recovery_control_path = _recovery_control_path_readback(
runtime_ready=ready,
receipt=recovery_control_receipt,
)
recovery_control_path_blockers = _strings(
recovery_control_path.get("active_blockers")
)
active_blockers = _unique_strings(active_blockers + recovery_control_path_blockers)
data_dependency_classification = _data_dependency_classification(
active_blockers,
api_health_ok=checks["public_api_health_ok"],
)
postgres_not_ready = data_dependency_classification == "postgres_not_ready"
data_readiness_control_readback = _data_readiness_control_readback(
checks=checks,
freshness=freshness,
ingestion=ingestion,
freshness_json=freshness_json,
ingestion_json=ingestion_json,
data_dependency_classification=data_dependency_classification,
postgres_not_ready=postgres_not_ready,
data_dependency_blockers=active_blockers,
)
recovery_control_path = _recovery_control_path_readback(
runtime_ready=ready,
receipt=recovery_control_receipt,
data_readiness_control_readback=data_readiness_control_readback,
)
recovery_control_path_blockers = _strings(
recovery_control_path.get("active_blockers")
)
active_blockers = _unique_strings(active_blockers + recovery_control_path_blockers)
committed_freshness_ok = committed_stockplatform.get("freshness_status") == "ok"
committed_ingestion_ok = committed_stockplatform.get("ingestion_status") == "ok"
committed_ok = committed_freshness_ok and committed_ingestion_ok
@@ -151,7 +168,7 @@ def _build_payload(
"stockplatform_public_api_ready_keep_p0_006_reboot_window_gate"
if ready
else _blocked_safe_next_step(
postgres_not_ready=postgres_not_ready,
data_dependency_classification=data_dependency_classification,
)
)
http_statuses = {
@@ -194,6 +211,12 @@ def _build_payload(
),
"recovery_control_path_status": recovery_control_path.get("status"),
"recovery_control_path_active_blockers": recovery_control_path_blockers,
"data_readiness_control_readback_status": (
data_readiness_control_readback.get("status")
),
"data_readiness_control_readback_present": (
data_readiness_control_readback.get("readback_present") is True
),
},
"rollups": {
"runtime_ready": ready,
@@ -224,7 +247,14 @@ def _build_payload(
),
"data_dependency_classification": data_dependency_classification,
"postgres_not_ready": postgres_not_ready,
"postgres_readiness_or_data_source_readback_present": (
data_readiness_control_readback.get("readback_present") is True
),
"postgres_readiness_or_data_source_receipt_provided": (
data_readiness_control_readback.get("receipt_provided") is True
),
},
"data_readiness_control_readback": data_readiness_control_readback,
"recovery_control_path": recovery_control_path,
"probes": probes,
"operation_boundaries": {
@@ -249,20 +279,38 @@ def _recovery_control_path_readback(
*,
runtime_ready: bool,
receipt: dict[str, Any],
data_readiness_control_readback: dict[str, Any],
) -> dict[str, Any]:
if runtime_ready:
return {
"status": "not_required_stockplatform_runtime_ready",
"active_blockers": [],
"receipt": receipt,
"provided_receipts": _provided_receipts_with_live_readback(
_strings(receipt.get("provided_receipts")),
data_readiness_control_readback,
),
"missing_receipts": _missing_receipts_after_live_readback(
_strings(receipt.get("missing_receipts")),
data_readiness_control_readback,
),
"safe_recovery_channels": [
"keep_monitoring_public_api_runtime_readback",
],
}
if receipt:
active_blockers = _unique_strings(
_strings(receipt.get("active_blockers"))
or _strings(receipt.get("missing_receipts"))
provided_receipts = _provided_receipts_with_live_readback(
_strings(receipt.get("provided_receipts")),
data_readiness_control_readback,
)
missing_receipts = _missing_receipts_after_live_readback(
_strings(receipt.get("missing_receipts")),
data_readiness_control_readback,
)
active_blockers = _recovery_control_active_blockers(
receipt_blockers=_strings(receipt.get("active_blockers")),
missing_receipts=missing_receipts,
data_readiness_control_readback=data_readiness_control_readback,
)
return {
"status": str(
@@ -271,8 +319,13 @@ def _recovery_control_path_readback(
),
"active_blockers": active_blockers,
"receipt": receipt,
"provided_receipts": _strings(receipt.get("provided_receipts")),
"missing_receipts": _strings(receipt.get("missing_receipts")),
"provided_receipts": provided_receipts,
"missing_receipts": missing_receipts,
"live_data_readiness_receipt_key": (
_DATA_READINESS_RECEIPT_KEY
if data_readiness_control_readback.get("receipt_provided") is True
else ""
),
"safe_recovery_channels": _strings(
receipt.get("safe_recovery_channels")
),
@@ -310,6 +363,130 @@ def _recovery_control_path_readback(
}
def _data_readiness_control_readback(
*,
checks: dict[str, bool],
freshness: dict[str, Any],
ingestion: dict[str, Any],
freshness_json: dict[str, Any],
ingestion_json: dict[str, Any],
data_dependency_classification: str,
postgres_not_ready: bool,
data_dependency_blockers: list[str],
) -> dict[str, Any]:
freshness_http_status = freshness.get("http_status")
ingestion_http_status = ingestion.get("http_status")
readback_present = freshness_http_status == 200 and ingestion_http_status == 200
data_ready = (
readback_present
and freshness_json.get("status") == "ok"
and ingestion_json.get("status") == "ok"
)
if data_ready:
status = "stockplatform_data_readiness_ready"
safe_next_step = "run_post_apply_public_api_verifier_and_clear_p0_006_blocker"
elif readback_present and postgres_not_ready:
status = "blocked_stockplatform_postgres_not_ready"
safe_next_step = (
"run_stockplatform_production_migration_path_after_control_channel_"
"readback_no_manual_db_write"
)
elif readback_present:
status = "blocked_stockplatform_data_dependency_not_ready"
safe_next_step = (
"diff_stockplatform_freshness_ingestion_source_contract_then_rerun_"
"public_api_verifier"
)
else:
status = "blocked_stockplatform_data_readiness_readback_unavailable"
safe_next_step = (
"restore_public_api_data_endpoints_or_control_channel_readback_then_"
"rerun_data_readiness_readback"
)
missing_evidence = []
if freshness_http_status != 200:
missing_evidence.append("freshness_endpoint_http_200")
if ingestion_http_status != 200:
missing_evidence.append("ingestion_endpoint_http_200")
return {
"schema_version": _DATA_READINESS_SCHEMA_VERSION,
"receipt_key": _DATA_READINESS_RECEIPT_KEY,
"status": status,
"source": "stockplatform_public_api_freshness_ingestion_probe",
"readback_present": readback_present,
"receipt_provided": readback_present,
"safe_next_step": safe_next_step,
"data_ready": data_ready,
"postgres_ready": data_ready and not postgres_not_ready,
"postgres_not_ready": postgres_not_ready,
"data_dependency_classification": data_dependency_classification,
"freshness_http_status": freshness_http_status,
"ingestion_http_status": ingestion_http_status,
"freshness_status": str(freshness_json.get("status") or "unknown"),
"ingestion_status": str(ingestion_json.get("status") or "unknown"),
"freshness_blockers": _strings(freshness_json.get("blockers")),
"ingestion_blockers": _strings(ingestion_json.get("blockers")),
"active_blockers": [
blocker
for blocker in data_dependency_blockers
if blocker.startswith("stockplatform_freshness_")
or blocker.startswith("stockplatform_ingestion_")
],
"missing_evidence": missing_evidence,
"operation_boundaries": {
"public_https_probe_only": True,
"ssh_used": False,
"docker_command_performed": False,
"database_write_or_restore_performed": False,
"stockplatform_manual_data_write_performed": False,
"secret_value_collection_allowed": False,
"runtime_action_performed": False,
},
}
def _provided_receipts_with_live_readback(
provided_receipts: list[str],
data_readiness_control_readback: dict[str, Any],
) -> list[str]:
if data_readiness_control_readback.get("receipt_provided") is not True:
return provided_receipts
return _unique_strings([*provided_receipts, _DATA_READINESS_RECEIPT_KEY])
def _missing_receipts_after_live_readback(
missing_receipts: list[str],
data_readiness_control_readback: dict[str, Any],
) -> list[str]:
if data_readiness_control_readback.get("receipt_provided") is not True:
return missing_receipts
return [
receipt
for receipt in missing_receipts
if receipt != _DATA_READINESS_RECEIPT_KEY
]
def _recovery_control_active_blockers(
*,
receipt_blockers: list[str],
missing_receipts: list[str],
data_readiness_control_readback: dict[str, Any],
) -> list[str]:
blockers = receipt_blockers or missing_receipts
if data_readiness_control_readback.get("receipt_provided") is not True:
return _unique_strings(blockers)
if data_readiness_control_readback.get("postgres_not_ready") is True:
return _unique_strings(blockers)
return _unique_strings(
[
blocker
for blocker in blockers
if blocker != "stockplatform_postgres_not_ready"
]
)
def _load_recovery_control_receipt(operations_dir: Path) -> dict[str, Any]:
path = operations_dir / _RECOVERY_RECEIPT_FILE
if not path.exists():
@@ -327,12 +504,17 @@ def _load_recovery_control_receipt(operations_dir: Path) -> dict[str, Any]:
return payload
def _blocked_safe_next_step(*, postgres_not_ready: bool) -> str:
if postgres_not_ready:
def _blocked_safe_next_step(*, data_dependency_classification: str) -> str:
if data_dependency_classification == "postgres_not_ready":
return (
"run_stockplatform_production_migration_path_after_control_channel_"
"readback_then_rerun_public_api_readback"
)
if data_dependency_classification == "freshness_or_ingestion_not_ready":
return (
"run_stockplatform_data_dependency_source_contract_readback_then_"
"rerun_public_api_verifier"
)
return (
"recover_stockplatform_api_container_runtime_or_docker_control_path_"
"without_daemon_restart_then_rerun_public_api_readback"

View File

@@ -1255,6 +1255,27 @@ def test_awoooi_priority_work_order_readback_routes_stockplatform_data_dependenc
assert evidence["stockplatform_controlled_recovery_data_dependency"] == (
"postgres_not_ready"
)
assert evidence[
"stockplatform_postgres_readiness_or_data_source_readback_status"
] == "blocked_stockplatform_postgres_not_ready"
assert evidence[
"stockplatform_postgres_readiness_or_data_source_readback_present"
] is True
assert evidence[
"stockplatform_postgres_readiness_or_data_source_receipt_provided"
] is True
assert evidence[
"stockplatform_postgres_readiness_or_data_source_postgres_ready"
] is False
assert payload["rollups"][
"stockplatform_postgres_readiness_or_data_source_readback_present"
] is True
assert payload["rollups"][
"stockplatform_postgres_readiness_or_data_source_receipt_provided"
] is True
assert payload["summary"][
"stockplatform_postgres_readiness_or_data_source_receipt_provided"
] is True
assert "production migration" in in_progress["professional_fix"]["action"]
assert "Do not restart Docker daemon" in in_progress["professional_fix"]["action"]
assert "manually write DB rows" in in_progress["professional_fix"]["action"]
@@ -1581,6 +1602,17 @@ def _stockplatform_runtime_data_dependency_blocked() -> dict:
"stockplatform_controlled_deploy_or_ssh_readback_required",
],
},
"data_readiness_control_readback": {
"schema_version": (
"stockplatform_postgres_readiness_or_data_source_readback_v1"
),
"receipt_key": "stockplatform_postgres_readiness_or_data_source_readback",
"status": "blocked_stockplatform_postgres_not_ready",
"readback_present": True,
"receipt_provided": True,
"postgres_ready": False,
"postgres_not_ready": True,
},
}

View File

@@ -102,6 +102,21 @@ def test_stockplatform_controlled_recovery_preflight_packages_data_dependency(
assert payload["source_of_truth_diff"]["data_dependency_classification"] == (
"postgres_not_ready"
)
assert payload["source_of_truth_diff"][
"data_readiness_control_readback_status"
] == "blocked_stockplatform_postgres_not_ready"
assert payload["source_of_truth_diff"][
"data_readiness_control_receipt_provided"
] is True
assert payload["data_readiness_control_readback"]["schema_version"] == (
"stockplatform_postgres_readiness_or_data_source_readback_v1"
)
assert payload["rollups"][
"postgres_readiness_or_data_source_readback_present"
] is True
assert payload["rollups"][
"postgres_readiness_or_data_source_receipt_provided"
] is True
assert payload["controlled_apply_policy"]["current_apply_blocker"] == (
"postgres_readiness_and_production_migration_control_path_readback_"
"required_before_apply"
@@ -131,6 +146,34 @@ def test_stockplatform_controlled_recovery_preflight_packages_data_dependency(
assert payload["operation_boundaries"]["database_write_or_restore_performed"] is False
def test_stockplatform_controlled_recovery_preflight_prefers_live_data_classification(
tmp_path: Path,
):
payload = load_latest_stockplatform_public_api_controlled_recovery_preflight(
runtime_readback=_runtime_data_dependency_non_postgres_blocked(),
route_source_path=_route_source(tmp_path),
)
assert payload["status"] == (
"controlled_data_dependency_preflight_packaged_waiting_control_path_readback"
)
assert payload["api_layer_classification"] == "api_live_data_dependency_not_ready"
assert payload["data_dependency_classification"] == (
"freshness_or_ingestion_not_ready"
)
assert payload["safe_next_step"] == (
"run_stockplatform_data_dependency_source_contract_readback_"
"then_public_api_verifier"
)
assert payload["controlled_apply_policy"]["current_apply_blocker"] == (
"data_dependency_control_path_readback_required_before_source_"
"freshness_or_postgres_contract_apply"
)
assert payload["controlled_action_candidates"][1]["id"] == (
"source_freshness_contract_preflight"
)
def test_stockplatform_controlled_recovery_preflight_endpoint_returns_payload(
monkeypatch,
tmp_path: Path,
@@ -251,4 +294,48 @@ def _runtime_data_dependency_blocked() -> dict:
"rollups": {
"http_502_count": 0,
},
"data_readiness_control_readback": {
"schema_version": (
"stockplatform_postgres_readiness_or_data_source_readback_v1"
),
"receipt_key": "stockplatform_postgres_readiness_or_data_source_readback",
"status": "blocked_stockplatform_postgres_not_ready",
"readback_present": True,
"receipt_provided": True,
"postgres_ready": False,
"postgres_not_ready": True,
},
}
def _runtime_data_dependency_non_postgres_blocked() -> dict:
payload = _runtime_data_dependency_blocked()
payload["active_blockers"] = [
"stockplatform_freshness_core_margin_short_daily_missing",
"stockplatform_freshness_ai_recommendations_stale",
"stockplatform_ingestion_status_not_ok",
"stockplatform_postgres_not_ready",
]
payload["readback"] = {
"web_health_http_status": 200,
"api_health_http_status": 200,
"freshness_http_status": 200,
"ingestion_http_status": 200,
"data_dependency_classification": "freshness_or_ingestion_not_ready",
"postgres_not_ready": False,
}
payload["rollups"] = {
"http_502_count": 0,
"data_dependency_classification": "freshness_or_ingestion_not_ready",
"postgres_not_ready": False,
}
payload["data_readiness_control_readback"] = {
"schema_version": "stockplatform_postgres_readiness_or_data_source_readback_v1",
"receipt_key": "stockplatform_postgres_readiness_or_data_source_readback",
"status": "blocked_stockplatform_data_dependency_not_ready",
"readback_present": True,
"receipt_provided": True,
"postgres_ready": False,
"postgres_not_ready": False,
}
return payload

View File

@@ -127,6 +127,42 @@ def test_stockplatform_public_api_runtime_readback_routes_postgres_not_ready():
assert payload["readback"]["postgres_not_ready"] is True
assert payload["rollups"]["http_502_count"] == 0
assert payload["rollups"]["postgres_not_ready"] is True
assert (
payload["rollups"][
"postgres_readiness_or_data_source_readback_present"
]
is True
)
assert (
payload["rollups"][
"postgres_readiness_or_data_source_receipt_provided"
]
is True
)
data_readiness = payload["data_readiness_control_readback"]
assert data_readiness["schema_version"] == (
"stockplatform_postgres_readiness_or_data_source_readback_v1"
)
assert data_readiness["receipt_key"] == (
"stockplatform_postgres_readiness_or_data_source_readback"
)
assert data_readiness["status"] == "blocked_stockplatform_postgres_not_ready"
assert data_readiness["readback_present"] is True
assert data_readiness["receipt_provided"] is True
assert data_readiness["postgres_ready"] is False
assert data_readiness["postgres_not_ready"] is True
assert data_readiness["operation_boundaries"]["public_https_probe_only"] is True
assert data_readiness["operation_boundaries"]["database_write_or_restore_performed"] is False
assert data_readiness["operation_boundaries"]["secret_value_collection_allowed"] is False
assert "stockplatform_postgres_readiness_or_data_source_readback" in payload[
"recovery_control_path"
]["provided_receipts"]
assert "stockplatform_postgres_readiness_or_data_source_readback" not in payload[
"recovery_control_path"
]["missing_receipts"]
assert payload["recovery_control_path"]["missing_receipts"] == [
"post_apply_public_api_verifier_green"
]
assert payload["safe_next_step"] == (
"run_stockplatform_production_migration_path_after_control_channel_"
"readback_then_rerun_public_api_readback"
@@ -139,6 +175,36 @@ def test_stockplatform_public_api_runtime_readback_routes_postgres_not_ready():
]
def test_stockplatform_public_api_runtime_readback_routes_data_dependency_without_postgres():
payload = load_latest_stockplatform_public_api_runtime_readback(
probe=_probe_public_api_ok_data_dependency_blocked
)
assert payload["status"] == "blocked_stockplatform_public_api_runtime_drift"
assert payload["readback"]["api_health_http_status"] == 200
assert payload["readback"]["freshness_status"] == "blocked"
assert payload["readback"]["ingestion_status"] == "unknown"
assert payload["readback"]["data_dependency_classification"] == (
"freshness_or_ingestion_not_ready"
)
assert payload["readback"]["postgres_not_ready"] is False
assert payload["safe_next_step"] == (
"run_stockplatform_data_dependency_source_contract_readback_then_"
"rerun_public_api_verifier"
)
assert payload["data_readiness_control_readback"]["status"] == (
"blocked_stockplatform_data_dependency_not_ready"
)
assert payload["data_readiness_control_readback"]["receipt_provided"] is True
assert "stockplatform_postgres_not_ready" not in payload["active_blockers"]
assert payload["recovery_control_path"]["active_blockers"] == [
"stockplatform_post_apply_public_api_verifier_not_green"
]
assert payload["recovery_control_path"]["missing_receipts"] == [
"post_apply_public_api_verifier_green"
]
def test_stockplatform_public_api_runtime_endpoint_returns_readback(monkeypatch):
monkeypatch.setattr(
agents,
@@ -201,3 +267,31 @@ def _probe_public_api_ok_postgres_not_ready(
"error": "",
}
return {"http_status": 200, "body": "ok", "error": ""}
def _probe_public_api_ok_data_dependency_blocked(
url: str,
timeout_seconds: float,
) -> dict:
del timeout_seconds
if url.endswith("/api/v1/system/freshness"):
return {
"http_status": 200,
"body": json.dumps(
{
"status": "blocked",
"blockers": [
"core_margin_short_daily_missing",
"ai_recommendations_stale",
],
}
),
"error": "",
}
if url.endswith("/api/v1/system/ingestion"):
return {
"http_status": 200,
"body": json.dumps({"status": "unknown", "blockers": []}),
"error": "",
}
return {"http_status": 200, "body": "ok", "error": ""}

View File

@@ -9180,6 +9180,31 @@
"evidenceBoundary": "證據邊界"
}
},
"stockplatformP0Receipt": {
"eyebrow": "P0-006 Runtime receipt",
"title": "StockPlatform data dependency readback",
"subtitle": "Shows the public API data readiness receipt, dependency classification, and next controlled step.",
"preflight": "Controlled preflight",
"next": "Current next step",
"empty": "Waiting for priority readback",
"metrics": {
"runtime": "Runtime",
"receipt": "Receipt",
"dataDependency": "Data dependency",
"postgres": "Postgres"
},
"values": {
"received": "received",
"missing": "missing",
"ready": "ready",
"notReady": "not ready"
},
"boundaries": {
"publicHttps": "public HTTPS only",
"noDbWrite": "no DB write",
"noSecret": "no secret read"
}
},
"commanderInsertedRequirements": {
"eyebrow": "主線優先序",
"title": "統帥插入需求工作項",

View File

@@ -9180,6 +9180,31 @@
"evidenceBoundary": "證據邊界"
}
},
"stockplatformP0Receipt": {
"eyebrow": "P0-006 Runtime receipt",
"title": "StockPlatform 資料依賴回讀",
"subtitle": "顯示公開 API 回讀出的 data readiness receipt、資料依賴分類與下一個受控步驟。",
"preflight": "Controlled preflight",
"next": "目前下一步",
"empty": "等待 priority readback",
"metrics": {
"runtime": "Runtime",
"receipt": "Receipt",
"dataDependency": "Data dependency",
"postgres": "Postgres"
},
"values": {
"received": "已收件",
"missing": "缺收件",
"ready": "ready",
"notReady": "not ready"
},
"boundaries": {
"publicHttps": "public HTTPS only",
"noDbWrite": "no DB write",
"noSecret": "no secret read"
}
},
"commanderInsertedRequirements": {
"eyebrow": "主線優先序",
"title": "統帥插入需求工作項",

View File

@@ -1127,6 +1127,12 @@ type PriorityWorkOrderResponse = {
ai_automation_node_receipt_metadata_only?: boolean | null;
ai_automation_node_receipt_lanes?: string[] | null;
ai_automation_node_receipt_work_items?: string[] | null;
stockplatform_public_api_runtime_status?: string | null;
stockplatform_public_api_controlled_recovery_preflight_status?: string | null;
stockplatform_public_api_controlled_recovery_data_dependency?: string | null;
stockplatform_public_api_recovery_control_path_status?: string | null;
stockplatform_postgres_readiness_or_data_source_readback_status?: string | null;
stockplatform_postgres_readiness_or_data_source_receipt_provided?: boolean | null;
} | null;
in_progress_or_blocked_in_priority_order?: Array<{
evidence?: {
@@ -1161,8 +1167,21 @@ type PriorityWorkOrderResponse = {
controlled_cd_lane_live_gitea_actions_active_process_count?: number | null;
controlled_cd_lane_live_metric_blocker_count?: number | null;
controlled_cd_lane_live_metric_blockers?: string[] | null;
stockplatform_public_api_runtime_status?: string | null;
stockplatform_public_api_runtime_ready?: boolean | null;
stockplatform_public_api_health_http_status?: number | null;
stockplatform_freshness_http_status?: number | null;
stockplatform_ingestion_http_status?: number | null;
stockplatform_controlled_recovery_preflight_status?: string | null;
stockplatform_controlled_recovery_data_dependency?: string | null;
stockplatform_recovery_control_path_status?: string | null;
stockplatform_postgres_readiness_or_data_source_readback_status?: string | null;
stockplatform_postgres_readiness_or_data_source_readback_present?: boolean | null;
stockplatform_postgres_readiness_or_data_source_receipt_provided?: boolean | null;
stockplatform_postgres_readiness_or_data_source_postgres_ready?: boolean | null;
} | null;
}>;
next_execution_order?: string[] | null;
commander_inserted_requirement_work_items?: CommanderInsertedRequirementWorkItem[];
ai_automation_node_receipts?: AiAutomationNodeReceipt[];
ai_automation_node_receipt_schema?: {
@@ -8691,6 +8710,166 @@ function AiLoopLogSourceTagsPanel({
);
}
function stockplatformReceiptTone(value: string | boolean | null | undefined) {
if (value === true || value === "ready" || String(value).includes("_ready")) {
return "border-[#b9d9c2] bg-[#f2fbf3] text-[#236332]";
}
if (String(value).includes("blocked") || value === false) {
return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]";
}
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
}
function StockPlatformP0ReceiptPanel({
priority,
loading,
}: {
priority: PriorityWorkOrderResponse | null;
loading: boolean;
}) {
const t = useTranslations("awooop.workItems.stockplatformP0Receipt");
const summary = priority?.summary;
const evidence = priority?.in_progress_or_blocked_in_priority_order?.find(
(item) =>
item.evidence?.stockplatform_postgres_readiness_or_data_source_readback_status ||
item.evidence?.stockplatform_controlled_recovery_preflight_status ||
item.evidence?.stockplatform_public_api_runtime_status
)?.evidence;
const readbackStatus =
summary?.stockplatform_postgres_readiness_or_data_source_readback_status ??
evidence?.stockplatform_postgres_readiness_or_data_source_readback_status ??
"--";
const receiptProvided =
summary?.stockplatform_postgres_readiness_or_data_source_receipt_provided ??
evidence?.stockplatform_postgres_readiness_or_data_source_receipt_provided ??
false;
const dataDependency =
summary?.stockplatform_public_api_controlled_recovery_data_dependency ??
evidence?.stockplatform_controlled_recovery_data_dependency ??
"--";
const preflightStatus =
summary?.stockplatform_public_api_controlled_recovery_preflight_status ??
evidence?.stockplatform_controlled_recovery_preflight_status ??
"--";
const runtimeStatus =
summary?.stockplatform_public_api_runtime_status ??
evidence?.stockplatform_public_api_runtime_status ??
"--";
const recoveryStatus =
summary?.stockplatform_public_api_recovery_control_path_status ??
evidence?.stockplatform_recovery_control_path_status ??
"--";
const postgresReady =
evidence?.stockplatform_postgres_readiness_or_data_source_postgres_ready ??
false;
const nextOrder = priority?.next_execution_order?.[0] ?? "";
const metrics = [
{
key: "runtime",
label: t("metrics.runtime"),
value: runtimeStatus,
tone: stockplatformReceiptTone(runtimeStatus),
},
{
key: "receipt",
label: t("metrics.receipt"),
value: receiptProvided ? t("values.received") : t("values.missing"),
tone: stockplatformReceiptTone(receiptProvided),
},
{
key: "data",
label: t("metrics.dataDependency"),
value: dataDependency,
tone: stockplatformReceiptTone(readbackStatus),
},
{
key: "postgres",
label: t("metrics.postgres"),
value: postgresReady ? t("values.ready") : t("values.notReady"),
tone: stockplatformReceiptTone(postgresReady),
},
];
return (
<section
className="border border-[#d8d3c7] bg-white"
data-testid="stockplatform-p0-runtime-receipt"
>
<div className="p-4">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex min-w-0 items-center gap-3">
<Database className="h-5 w-5 shrink-0 text-[#1f5b9b]" aria-hidden="true" />
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
{t("eyebrow")}
</p>
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
{t("title")}
</h3>
<p className="mt-2 max-w-3xl text-xs leading-5 text-[#77736a]">
{t("subtitle")}
</p>
</div>
</div>
<span
className={cn(
"border px-3 py-2 font-mono text-[11px] font-semibold",
stockplatformReceiptTone(readbackStatus)
)}
>
{loading ? "--" : readbackStatus}
</span>
</div>
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
{metrics.map((metric) => (
<div key={metric.key} className={cn("border px-3 py-2", metric.tone)}>
<div className="text-[11px] font-semibold">{metric.label}</div>
<div className="mt-1 break-words font-mono text-xs font-semibold text-[#141413]">
{loading ? "--" : metric.value}
</div>
</div>
))}
</div>
<div className="mt-3 grid gap-2 border border-[#c9d8ea] bg-[#eef5ff] p-3 lg:grid-cols-[minmax(0,0.75fr)_minmax(0,1.8fr)]">
<div className="min-w-0">
<div className="text-[11px] font-semibold text-[#1f5b9b]">
{t("preflight")}
</div>
<div className="mt-1 break-words font-mono text-xs font-semibold text-[#141413]">
{loading ? "--" : preflightStatus}
</div>
<div className="mt-2 break-words font-mono text-[10px] leading-4 text-[#5f5b52]">
{loading ? "--" : recoveryStatus}
</div>
</div>
<div className="min-w-0">
<div className="text-[11px] font-semibold text-[#1f5b9b]">
{t("next")}
</div>
<div className="mt-1 break-words text-xs leading-5 text-[#141413]">
{loading ? "--" : nextOrder || t("empty")}
</div>
</div>
</div>
<div className="mt-3 flex flex-wrap gap-2 text-[11px] font-semibold">
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-[#5f5b52]">
{t("boundaries.publicHttps")}
</span>
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-[#5f5b52]">
{t("boundaries.noDbWrite")}
</span>
<span className="border border-[#d8d3c7] bg-[#faf9f3] px-2 py-1 text-[#5f5b52]">
{t("boundaries.noSecret")}
</span>
</div>
</div>
</section>
);
}
function commanderPriorityTone(priority: string) {
switch (priority) {
case "P0":
@@ -9318,6 +9497,11 @@ export default function AwoooPWorkItemsPage() {
loading={priorityWorkOrderLoading && !priorityWorkOrder}
/>
<StockPlatformP0ReceiptPanel
priority={priorityWorkOrder}
loading={priorityWorkOrderLoading && !priorityWorkOrder}
/>
<CommanderInsertedRequirementsPanel
priority={commanderInsertedRequirementWorkOrder}
loading={false}