From 4ecbb3ba1210c564f9048cfa5aaad9471e305ccb Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 17 Jul 2026 10:00:32 +0800 Subject: [PATCH 1/3] fix(sre): carry executor correlation into Ansible claims --- .../services/awooop_ansible_audit_service.py | 70 +++++++++++++++++-- ...claw_callback_forwarder_ansible_catalog.py | 51 +++++++++++++- 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/apps/api/src/services/awooop_ansible_audit_service.py b/apps/api/src/services/awooop_ansible_audit_service.py index 6f4efcd46..95942a7ec 100644 --- a/apps/api/src/services/awooop_ansible_audit_service.py +++ b/apps/api/src/services/awooop_ansible_audit_service.py @@ -34,6 +34,60 @@ _RECURRENCE_REQUIRED_PROPOSAL_SOURCES = frozenset( "iwooos_wazuh_alert_ingress_scheduler", } ) +_CONTROLLED_CORRELATION_RE = re.compile( + r"^[A-Za-z0-9](?:[A-Za-z0-9_.:-]{0,158}[A-Za-z0-9])?$" +) + + +def _controlled_correlation( + incident: dict[str, Any], + proposal: dict[str, Any], + *, + project_id: str, + incident_id: str, +) -> dict[str, str]: + """Preserve safe source correlation or generate one bounded run identity.""" + + signal_labels = [ + signal.get("labels") + for signal in incident.get("signals") or [] + if isinstance(signal, dict) and isinstance(signal.get("labels"), dict) + ] + + def first_safe(key: str) -> str: + for source in (proposal, incident, *signal_labels): + value = str(source.get(key) or "").strip() + if ( + value + and value not in {".", ".."} + and _CONTROLLED_CORRELATION_RE.fullmatch(value) + ): + return value + return "" + + safe_project = re.sub(r"[^A-Za-z0-9_.:-]", "-", project_id).strip("-.") + safe_incident = re.sub(r"[^A-Za-z0-9_.:-]", "-", incident_id).strip("-.") + generated_run = f"ansible:{safe_project or 'awoooi'}:{safe_incident or 'incident'}" + generated_run = generated_run[:160].rstrip("_.:-") + trace_id = first_safe("trace_id") + run_id = first_safe("run_id") + if not trace_id and not run_id: + trace_id = run_id = generated_run + elif not trace_id: + trace_id = run_id + elif not run_id: + run_id = trace_id + work_item_id = first_safe("work_item_id") or ( + f"ansible-controlled:{safe_project or 'awoooi'}:" + f"{safe_incident or 'incident'}" + )[:160].rstrip("_.:-") + return { + "trace_id": trace_id, + "run_id": run_id, + "work_item_id": work_item_id, + "run_namespace": first_safe("run_namespace") or safe_project or "awoooi", + "source_receipt_ref": first_safe("source_receipt_ref"), + } def _source_fingerprint(incident: dict[str, Any], proposal: dict[str, Any]) -> str: @@ -1684,6 +1738,12 @@ def build_ansible_decision_audit_payload( incident_id = str(incident_payload.get("incident_id") or "") project_id = str(incident_payload.get("project_id") or "awoooi") + correlation = _controlled_correlation( + incident_payload, + proposal_data, + project_id=project_id, + incident_id=incident_id, + ) affected_services = [ str(value) for value in incident_payload.get("affected_services") or [] @@ -1746,11 +1806,11 @@ def build_ansible_decision_audit_payload( input_payload = { "incident_id": incident_id, "project_id": project_id, - "trace_id": str(incident_payload.get("trace_id") or ""), - "run_id": str(incident_payload.get("run_id") or ""), - "work_item_id": str(incident_payload.get("work_item_id") or ""), - "run_namespace": str(incident_payload.get("run_namespace") or ""), - "source_receipt_ref": str(incident_payload.get("source_receipt_ref") or ""), + "trace_id": correlation["trace_id"], + "run_id": correlation["run_id"], + "work_item_id": correlation["work_item_id"], + "run_namespace": correlation["run_namespace"], + "source_receipt_ref": correlation["source_receipt_ref"], "asset_scope_aliases": [ str(alias) for alias in incident_payload.get("asset_scope_aliases") or [] diff --git a/apps/api/tests/test_host188_openclaw_callback_forwarder_ansible_catalog.py b/apps/api/tests/test_host188_openclaw_callback_forwarder_ansible_catalog.py index 8aac8605b..c36150730 100644 --- a/apps/api/tests/test_host188_openclaw_callback_forwarder_ansible_catalog.py +++ b/apps/api/tests/test_host188_openclaw_callback_forwarder_ansible_catalog.py @@ -8,6 +8,7 @@ import yaml from src.services.awooop_ansible_audit_service import ( _catalog_hints, + build_ansible_decision_audit_payload, get_ansible_catalog_item, ) from src.services.awooop_ansible_check_mode_service import ( @@ -72,7 +73,13 @@ def _incident() -> dict: "signals": [ { "alert_name": "TelegramCallbackIngressUnverified", - "labels": {"component": "openclaw", "host": "188"}, + "labels": { + "component": "openclaw", + "host": "188", + "trace_id": "aia-sre-017-callback-002", + "run_id": "aia-sre-017-callback-002", + "work_item_id": "AIA-SRE-017", + }, } ], } @@ -134,6 +141,48 @@ def test_openclaw_incident_selects_one_exact_bounded_catalog() -> None: assert hints["cross_domain_fallback_allowed"] is False +def test_openclaw_candidate_preserves_same_run_correlation() -> None: + payload = build_ansible_decision_audit_payload( + incident=_incident(), + proposal_data={ + "source": "alert_webhook_controlled_router", + "risk_level": "medium", + "source_fingerprint": "callback-fingerprint", + "source_occurrence_id": "alert-callback-002", + "source_recurrence_verified": True, + }, + decision_path="repair_candidate_controlled_queue", + not_used_reason="test", + ) + + assert payload is not None + assert payload["input"]["trace_id"] == "aia-sre-017-callback-002" + assert payload["input"]["run_id"] == "aia-sre-017-callback-002" + assert payload["input"]["work_item_id"] == "AIA-SRE-017" + assert payload["input"]["run_namespace"] == "awoooi" + + +def test_openclaw_candidate_generates_safe_correlation_when_source_omits_it() -> None: + incident = _incident() + incident["signals"][0]["labels"] = { + "component": "openclaw", + "host": "188", + } + payload = build_ansible_decision_audit_payload( + incident=incident, + proposal_data={"source": "test", "risk_level": "medium"}, + decision_path="repair_candidate_controlled_queue", + not_used_reason="test", + ) + + assert payload is not None + assert payload["input"]["trace_id"] == payload["input"]["run_id"] + assert payload["input"]["trace_id"].startswith("ansible:awoooi:") + assert payload["input"]["work_item_id"].startswith( + "ansible-controlled:awoooi:" + ) + + def test_openclaw_catalog_and_independent_verifier_are_exact() -> None: catalog = get_ansible_catalog_item(CATALOG_ID) conditions = postconditions_for_catalog(CATALOG_ID) From 52d788294881abebcc7c436a12954550745e72ba Mon Sep 17 00:00:00 2001 From: AWOOOI CD Date: Fri, 17 Jul 2026 10:05:14 +0800 Subject: [PATCH 2/3] chore(cd): deploy 9b8ac95 [skip ci] --- k8s/awoooi-prod/06-deployment-api.yaml | 4 ++-- k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml | 2 +- k8s/awoooi-prod/08-deployment-worker.yaml | 2 +- k8s/awoooi-prod/kustomization.yaml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/k8s/awoooi-prod/06-deployment-api.yaml b/k8s/awoooi-prod/06-deployment-api.yaml index ce0c5d4ac..9eed3f1be 100644 --- a/k8s/awoooi-prod/06-deployment-api.yaml +++ b/k8s/awoooi-prod/06-deployment-api.yaml @@ -157,12 +157,12 @@ spec: - name: AWOOOI_BUILD_COMMIT_SHA # 2026-06-29 Codex: CD rewrites this to the deployed image tag so # production deploy readback does not rely on a stale static snapshot. - value: "29e09afcc36b8c44d434356e93e8e43d69a945e9" + value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" - name: AWOOOI_DESIRED_API_IMAGE_TAG # 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA. # Production readback compares runtime image truth against this # GitOps desired tag instead of doing a slow Gitea raw fetch. - value: "29e09afcc36b8c44d434356e93e8e43d69a945e9" + value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" - name: DATABASE_POOL_SIZE # 2026-07-01 Codex: production role `awoooi` currently has a low # connection limit. Keep API pool conservative until DB role diff --git a/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml b/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml index 261d0d6ef..6433daf2d 100644 --- a/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml +++ b/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml @@ -66,7 +66,7 @@ spec: - name: DATABASE_NULL_POOL value: "true" - name: AWOOOI_BUILD_COMMIT_SHA - value: "29e09afcc36b8c44d434356e93e8e43d69a945e9" + value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" - name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER value: "false" - name: ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER diff --git a/k8s/awoooi-prod/08-deployment-worker.yaml b/k8s/awoooi-prod/08-deployment-worker.yaml index 8ba02f777..0b63a048a 100644 --- a/k8s/awoooi-prod/08-deployment-worker.yaml +++ b/k8s/awoooi-prod/08-deployment-worker.yaml @@ -178,7 +178,7 @@ spec: - name: DATABASE_BOOTSTRAP_DDL_ENABLED value: "false" - name: AWOOOI_BUILD_COMMIT_SHA - value: "29e09afcc36b8c44d434356e93e8e43d69a945e9" + value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" - name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER value: "true" - name: ENABLE_SECURITY_CONTROL_PLANE_MAINTENANCE_WORKER diff --git a/k8s/awoooi-prod/kustomization.yaml b/k8s/awoooi-prod/kustomization.yaml index dca0d3429..b3aad57f4 100644 --- a/k8s/awoooi-prod/kustomization.yaml +++ b/k8s/awoooi-prod/kustomization.yaml @@ -40,9 +40,9 @@ resources: # ⚠️ 重要: name 必須與 deployment YAML 中的 image 完全匹配 (含 tag) # newName + newTag 由 CI 透過 kustomize edit set image 注入 images: -- digest: sha256:b4bac7a281c61b1b51448a6189c9c5b806948e16f21879c90d113c678e299779 +- digest: sha256:12beb2b1c7f75e611ff37b549540416d2d8ac0795aedc26d9b279e59c6d35e57 name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/api -- digest: sha256:db8807dc230db1130b87fab86e8a016ec6b9f11bfc746282afa44e236bbaa159 +- digest: sha256:84bc2308e18e595863b6a5c1e68b51f84b652f4ff913a756d77f410f5c5c6bd6 name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web From 165a256fb4c813bb56ee14cf639d2811e31e34b8 Mon Sep 17 00:00:00 2001 From: AWOOOI CD Date: Fri, 17 Jul 2026 10:26:13 +0800 Subject: [PATCH 3/3] chore(cd): deploy a789c5c [skip ci] --- k8s/awoooi-prod/06-deployment-api.yaml | 4 ++-- k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml | 2 +- k8s/awoooi-prod/08-deployment-worker.yaml | 2 +- k8s/awoooi-prod/kustomization.yaml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/k8s/awoooi-prod/06-deployment-api.yaml b/k8s/awoooi-prod/06-deployment-api.yaml index 9eed3f1be..b16aadd28 100644 --- a/k8s/awoooi-prod/06-deployment-api.yaml +++ b/k8s/awoooi-prod/06-deployment-api.yaml @@ -157,12 +157,12 @@ spec: - name: AWOOOI_BUILD_COMMIT_SHA # 2026-06-29 Codex: CD rewrites this to the deployed image tag so # production deploy readback does not rely on a stale static snapshot. - value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" + value: "a789c5c99b7c05a745f709852fc19196d237b98d" - name: AWOOOI_DESIRED_API_IMAGE_TAG # 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA. # Production readback compares runtime image truth against this # GitOps desired tag instead of doing a slow Gitea raw fetch. - value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" + value: "a789c5c99b7c05a745f709852fc19196d237b98d" - name: DATABASE_POOL_SIZE # 2026-07-01 Codex: production role `awoooi` currently has a low # connection limit. Keep API pool conservative until DB role diff --git a/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml b/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml index 6433daf2d..0b25a8e17 100644 --- a/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml +++ b/k8s/awoooi-prod/08-deployment-ansible-executor-broker.yaml @@ -66,7 +66,7 @@ spec: - name: DATABASE_NULL_POOL value: "true" - name: AWOOOI_BUILD_COMMIT_SHA - value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" + value: "a789c5c99b7c05a745f709852fc19196d237b98d" - name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER value: "false" - name: ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER diff --git a/k8s/awoooi-prod/08-deployment-worker.yaml b/k8s/awoooi-prod/08-deployment-worker.yaml index 0b63a048a..84a394bbf 100644 --- a/k8s/awoooi-prod/08-deployment-worker.yaml +++ b/k8s/awoooi-prod/08-deployment-worker.yaml @@ -178,7 +178,7 @@ spec: - name: DATABASE_BOOTSTRAP_DDL_ENABLED value: "false" - name: AWOOOI_BUILD_COMMIT_SHA - value: "9b8ac95f94a20c6878343d31a3cc33c079086e35" + value: "a789c5c99b7c05a745f709852fc19196d237b98d" - name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER value: "true" - name: ENABLE_SECURITY_CONTROL_PLANE_MAINTENANCE_WORKER diff --git a/k8s/awoooi-prod/kustomization.yaml b/k8s/awoooi-prod/kustomization.yaml index b3aad57f4..d997cdd89 100644 --- a/k8s/awoooi-prod/kustomization.yaml +++ b/k8s/awoooi-prod/kustomization.yaml @@ -40,9 +40,9 @@ resources: # ⚠️ 重要: name 必須與 deployment YAML 中的 image 完全匹配 (含 tag) # newName + newTag 由 CI 透過 kustomize edit set image 注入 images: -- digest: sha256:12beb2b1c7f75e611ff37b549540416d2d8ac0795aedc26d9b279e59c6d35e57 +- digest: sha256:c6d591f673cb36fbcb52b8a84f89e10fb7358d3e37c40b31451aff7e4d324fcf name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/api -- digest: sha256:84bc2308e18e595863b6a5c1e68b51f84b652f4ff913a756d77f410f5c5c6bd6 +- digest: sha256:e61e593ea860383775a6bf89f51bb91985dd627fe8b14b806d0efffdf6b0c518 name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web