feat(sre): add 24h alert learning corpus
This commit is contained in:
@@ -1256,8 +1256,9 @@ async def get_sre_k3s_controlled_automation_work_items() -> dict[str, Any]:
|
||||
description=(
|
||||
"唯讀彙總 PostgreSQL incident、run 與 provider budget receipts,計算 "
|
||||
"MTTA、MTTR、誤判、復發、人工介入、verifier、rollback、freshness、"
|
||||
"asset coverage 與模型成本。缺 evidence 時保持 partial,不執行修復、"
|
||||
"不呼叫模型、不讀 secret。"
|
||||
"asset coverage 與模型成本,並輸出去識別、可重算分母的 24h learning "
|
||||
"corpus。缺 evidence 時保持 partial;shadow/no-write 不計 production "
|
||||
"execution,不執行修復、不呼叫模型、不讀 secret。"
|
||||
),
|
||||
)
|
||||
async def get_sre_automation_slo_scorecard_24h(
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import UTC, datetime, timedelta
|
||||
@@ -25,6 +27,8 @@ _INCIDENT_SAMPLES_SQL = text(
|
||||
COALESCE(operation.incident_id, operation.id::text) AS sample_id,
|
||||
operation.event_type::text AS event_type,
|
||||
operation.created_at,
|
||||
operation.action_detail,
|
||||
operation.success,
|
||||
operation.context,
|
||||
incident.resolved_at AS incident_resolved_at,
|
||||
COALESCE(
|
||||
@@ -43,6 +47,66 @@ _INCIDENT_SAMPLES_SQL = text(
|
||||
NULLIF(operation.context ->> 'project_id', ''),
|
||||
'awoooi'
|
||||
) = :project_id
|
||||
), execution_evidence AS (
|
||||
SELECT
|
||||
*,
|
||||
(
|
||||
event_type IN (
|
||||
'EXECUTION_STARTED', 'EXECUTION_COMPLETED',
|
||||
'CHANGE_APPLIED'
|
||||
)
|
||||
) AS execution_like_event,
|
||||
(
|
||||
event_type = 'CHANGE_APPLIED'
|
||||
OR lower(COALESCE(context ->> 'execution_mode', ''))
|
||||
IN ('production', 'controlled_apply')
|
||||
OR lower(COALESCE(context ->> 'execution_kind', ''))
|
||||
= 'executable'
|
||||
OR lower(COALESCE(context ->> 'side_effect_performed', ''))
|
||||
IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(context ->> 'repair_executed', ''))
|
||||
IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(
|
||||
context ->> 'controlled_apply_executed', ''
|
||||
)) IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(context ->> 'runtime_apply_executed', ''))
|
||||
IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(action_detail, '')) LIKE 'controlled_apply_%'
|
||||
) AS production_execution_evidence,
|
||||
(
|
||||
lower(COALESCE(context ->> 'is_shadow', 'false'))
|
||||
IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(context ->> 'execution_mode', '')) IN (
|
||||
'shadow', 'canary', 'check_mode', 'dry_run',
|
||||
'read_only', 'no_write', 'no_write_replay',
|
||||
'controlled_retry_check_mode_replay',
|
||||
'stdin_boundary_check_mode_replay'
|
||||
)
|
||||
OR lower(COALESCE(context ->> 'execution_kind', ''))
|
||||
IN ('shadow', 'no_action', 'no_write', 'no_write_replay')
|
||||
OR lower(COALESCE(context ->> 'side_effect_performed', ''))
|
||||
IN ('false', '0', 'no')
|
||||
OR lower(COALESCE(action_detail, '')) LIKE ANY (ARRAY[
|
||||
'%shadow%', '%no_write%', '%check_mode%', '%dry_run%'
|
||||
])
|
||||
) AS execution_exclusion_evidence
|
||||
FROM scoped_events
|
||||
), classified_events AS (
|
||||
SELECT
|
||||
*,
|
||||
(
|
||||
execution_like_event
|
||||
AND production_execution_evidence
|
||||
AND NOT execution_exclusion_evidence
|
||||
) AS production_execution_event,
|
||||
(
|
||||
execution_like_event
|
||||
AND NOT (
|
||||
production_execution_evidence
|
||||
AND NOT execution_exclusion_evidence
|
||||
)
|
||||
) AS excluded_execution_event
|
||||
FROM execution_evidence
|
||||
), per_incident AS (
|
||||
SELECT
|
||||
sample_id,
|
||||
@@ -79,17 +143,24 @@ _INCIDENT_SAMPLES_SQL = text(
|
||||
) AS recurrence,
|
||||
bool_or(event_type IN ('USER_ACTION', 'MANUAL_FIX_RECORDED'))
|
||||
AS human_intervention,
|
||||
bool_or(event_type IN (
|
||||
'AUTO_REPAIR_TRIGGERED', 'EXECUTION_STARTED',
|
||||
'EXECUTION_COMPLETED', 'CHANGE_APPLIED'
|
||||
)) AS execution_observed,
|
||||
bool_or(event_type = 'AUTO_REPAIR_TRIGGERED') AS repair_observed,
|
||||
bool_or(
|
||||
event_type = 'AUTO_REPAIR_TRIGGERED' AND success IS TRUE
|
||||
) AS repair_accepted,
|
||||
bool_or(production_execution_event) AS execution_observed,
|
||||
bool_or(excluded_execution_event)
|
||||
AS shadow_or_no_write_execution_observed,
|
||||
bool_or(
|
||||
context ? 'verifier_passed'
|
||||
OR context ? 'post_verifier_passed'
|
||||
OR context ? 'verification_result'
|
||||
) AS verifier_observed,
|
||||
bool_or(
|
||||
lower(COALESCE(context ->> 'verifier_passed', 'false'))
|
||||
IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(
|
||||
context ->> 'post_verifier_passed', 'false'
|
||||
)) IN ('true', '1', 'yes')
|
||||
OR lower(COALESCE(context ->> 'verification_result', ''))
|
||||
IN ('success', 'passed', 'verified')
|
||||
) AS verifier_passed,
|
||||
@@ -109,7 +180,7 @@ _INCIDENT_SAMPLES_SQL = text(
|
||||
AND COALESCE(context ->> 'asset_identity_status', 'resolved')
|
||||
<> 'asset_identity_unresolved'
|
||||
) AS asset_identity_resolved
|
||||
FROM scoped_events
|
||||
FROM classified_events
|
||||
GROUP BY sample_id
|
||||
)
|
||||
SELECT *
|
||||
@@ -196,6 +267,21 @@ def _average(values: Sequence[float]) -> float | None:
|
||||
return round(sum(values) / len(values), 2) if values else None
|
||||
|
||||
|
||||
def _iso_timestamp(value: object) -> str | None:
|
||||
parsed = _as_datetime(value)
|
||||
return parsed.isoformat() if parsed else None
|
||||
|
||||
|
||||
def _sha256_json(value: object) -> str:
|
||||
canonical = json.dumps(
|
||||
value,
|
||||
ensure_ascii=True,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
)
|
||||
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _metrics_for(
|
||||
samples: Sequence[Mapping[str, Any]],
|
||||
) -> tuple[dict[str, Any], dict[str, int]]:
|
||||
@@ -213,8 +299,11 @@ def _metrics_for(
|
||||
total = len(samples)
|
||||
false_observed = sum(_flag(row.get("false_positive_observed")) for row in samples)
|
||||
recurrence_observed = sum(_flag(row.get("recurrence_observed")) for row in samples)
|
||||
execution_observed = sum(_flag(row.get("execution_observed")) for row in samples)
|
||||
verifier_observed = sum(_flag(row.get("verifier_observed")) for row in samples)
|
||||
execution_samples = [row for row in samples if _flag(row.get("execution_observed"))]
|
||||
execution_observed = len(execution_samples)
|
||||
verifier_observed = sum(
|
||||
_flag(row.get("verifier_observed")) for row in execution_samples
|
||||
)
|
||||
freshness_observed = sum(_flag(row.get("freshness_observed")) for row in samples)
|
||||
denominators = {
|
||||
"alert_count": total,
|
||||
@@ -222,8 +311,17 @@ def _metrics_for(
|
||||
"mttr_sample_count": len(mttr),
|
||||
"false_positive_classified_count": false_observed,
|
||||
"recurrence_classified_count": recurrence_observed,
|
||||
"repair_observed_count": sum(
|
||||
_flag(row.get("repair_observed")) for row in samples
|
||||
),
|
||||
"repair_accepted_count": sum(
|
||||
_flag(row.get("repair_accepted")) for row in samples
|
||||
),
|
||||
"execution_observed_count": execution_observed,
|
||||
"verifier_observed_count": verifier_observed,
|
||||
"shadow_or_no_write_execution_excluded_count": sum(
|
||||
_flag(row.get("shadow_or_no_write_execution_observed")) for row in samples
|
||||
),
|
||||
"freshness_observed_count": freshness_observed,
|
||||
}
|
||||
metrics = {
|
||||
@@ -242,11 +340,15 @@ def _metrics_for(
|
||||
total,
|
||||
),
|
||||
"verifier_pass_rate": _rate(
|
||||
sum(_flag(row.get("verifier_passed")) for row in samples),
|
||||
sum(
|
||||
_flag(row.get("verifier_observed"))
|
||||
and _flag(row.get("verifier_passed"))
|
||||
for row in execution_samples
|
||||
),
|
||||
verifier_observed,
|
||||
),
|
||||
"rollback_rate": _rate(
|
||||
sum(_flag(row.get("rollback_performed")) for row in samples),
|
||||
sum(_flag(row.get("rollback_performed")) for row in execution_samples),
|
||||
execution_observed,
|
||||
),
|
||||
"signal_freshness_slo_pass_rate": _rate(
|
||||
@@ -267,6 +369,198 @@ def _metrics_for(
|
||||
return metrics, denominators
|
||||
|
||||
|
||||
def _build_learning_corpus(
|
||||
*,
|
||||
project_id: str,
|
||||
incident_samples: Sequence[Mapping[str, Any]],
|
||||
safe_runs: Mapping[str, int],
|
||||
provider_usage: Mapping[str, Any],
|
||||
window_start: datetime,
|
||||
window_end: datetime,
|
||||
) -> dict[str, Any]:
|
||||
records: list[dict[str, Any]] = []
|
||||
missing_source_identity_count = 0
|
||||
for sample in incident_samples:
|
||||
source_identity = str(sample.get("sample_id") or "").strip()
|
||||
if not source_identity:
|
||||
missing_source_identity_count += 1
|
||||
identity_material = f"{project_id}\0{source_identity}".encode()
|
||||
record_id = (
|
||||
f"sha256:{hashlib.sha256(identity_material).hexdigest()}"
|
||||
if source_identity
|
||||
else ""
|
||||
)
|
||||
lane = str(sample.get("lane") or "unclassified")[:64]
|
||||
execution_observed = _flag(sample.get("execution_observed"))
|
||||
verifier_observed = execution_observed and _flag(
|
||||
sample.get("verifier_observed")
|
||||
)
|
||||
recurrence_observed = _flag(sample.get("recurrence_observed"))
|
||||
false_positive_observed = _flag(sample.get("false_positive_observed"))
|
||||
freshness_observed = _flag(sample.get("freshness_observed"))
|
||||
asset_identity_resolved = _flag(sample.get("asset_identity_resolved"))
|
||||
received_at = _iso_timestamp(sample.get("received_at"))
|
||||
resolved_at = _iso_timestamp(sample.get("resolved_at"))
|
||||
learning_eligible = bool(
|
||||
record_id
|
||||
and received_at
|
||||
and resolved_at
|
||||
and lane != "unclassified"
|
||||
and recurrence_observed
|
||||
and false_positive_observed
|
||||
and freshness_observed
|
||||
and asset_identity_resolved
|
||||
and (not execution_observed or verifier_observed)
|
||||
)
|
||||
records.append(
|
||||
{
|
||||
"record_id": record_id,
|
||||
"lane": lane,
|
||||
"timestamps": {
|
||||
"alert_received_at": received_at,
|
||||
"first_action_at": _iso_timestamp(sample.get("first_action_at")),
|
||||
"resolved_at": resolved_at,
|
||||
},
|
||||
"outcomes": {
|
||||
"repair_observed": _flag(sample.get("repair_observed")),
|
||||
"repair_accepted": _flag(sample.get("repair_accepted")),
|
||||
"production_execution_observed": execution_observed,
|
||||
"shadow_or_no_write_execution_excluded": _flag(
|
||||
sample.get("shadow_or_no_write_execution_observed")
|
||||
),
|
||||
"verifier_observed": verifier_observed,
|
||||
"verifier_passed": (
|
||||
_flag(sample.get("verifier_passed"))
|
||||
if verifier_observed
|
||||
else None
|
||||
),
|
||||
"recurrence_observed": recurrence_observed,
|
||||
"recurrence": (
|
||||
_flag(sample.get("recurrence")) if recurrence_observed else None
|
||||
),
|
||||
"false_positive_observed": false_positive_observed,
|
||||
"false_positive": (
|
||||
_flag(sample.get("false_positive"))
|
||||
if false_positive_observed
|
||||
else None
|
||||
),
|
||||
"human_intervention": _flag(sample.get("human_intervention")),
|
||||
"rollback_performed": (
|
||||
_flag(sample.get("rollback_performed"))
|
||||
if execution_observed
|
||||
else None
|
||||
),
|
||||
},
|
||||
"quality": {
|
||||
"signal_freshness_observed": freshness_observed,
|
||||
"signal_freshness_slo_passed": (
|
||||
_flag(sample.get("freshness_passed"))
|
||||
if freshness_observed
|
||||
else None
|
||||
),
|
||||
"asset_identity_resolved": asset_identity_resolved,
|
||||
},
|
||||
"learning_eligible": learning_eligible,
|
||||
}
|
||||
)
|
||||
|
||||
records.sort(
|
||||
key=lambda row: (
|
||||
str(row["timestamps"].get("alert_received_at") or ""),
|
||||
str(row.get("record_id") or ""),
|
||||
)
|
||||
)
|
||||
denominators = {
|
||||
"alert_count": len(records),
|
||||
"repair_observed_count": sum(
|
||||
row["outcomes"]["repair_observed"] for row in records
|
||||
),
|
||||
"repair_accepted_count": sum(
|
||||
row["outcomes"]["repair_accepted"] for row in records
|
||||
),
|
||||
"production_execution_observed_count": sum(
|
||||
row["outcomes"]["production_execution_observed"] for row in records
|
||||
),
|
||||
"verifier_observed_execution_count": sum(
|
||||
row["outcomes"]["verifier_observed"] for row in records
|
||||
),
|
||||
"recurrence_classified_count": sum(
|
||||
row["outcomes"]["recurrence_observed"] for row in records
|
||||
),
|
||||
"controlled_non_shadow_run_count": int(
|
||||
safe_runs.get("controlled_runs_with_steps") or 0
|
||||
),
|
||||
"provider_call_count": int(provider_usage.get("call_count") or 0),
|
||||
}
|
||||
excluded_from_execution = {
|
||||
"shadow_run_count": max(
|
||||
int(safe_runs.get("total_runs") or 0)
|
||||
- int(safe_runs.get("non_shadow_runs") or 0),
|
||||
0,
|
||||
),
|
||||
"non_shadow_no_step_run_count": max(
|
||||
int(safe_runs.get("non_shadow_runs") or 0)
|
||||
- int(safe_runs.get("controlled_runs_with_steps") or 0),
|
||||
0,
|
||||
),
|
||||
"alert_count_with_shadow_or_no_write_execution_evidence": sum(
|
||||
row["outcomes"]["shadow_or_no_write_execution_excluded"] for row in records
|
||||
),
|
||||
}
|
||||
eligible_record_count = sum(row["learning_eligible"] for row in records)
|
||||
status = (
|
||||
"no_runtime_sample_fail_closed"
|
||||
if not records
|
||||
else "partial_missing_receipt_coverage"
|
||||
if eligible_record_count < len(records) or missing_source_identity_count
|
||||
else "learning_corpus_ready_same_run_verification_pending"
|
||||
)
|
||||
fingerprint_payload = {
|
||||
"window": {
|
||||
"start": window_start.astimezone(UTC).isoformat(),
|
||||
"end": window_end.astimezone(UTC).isoformat(),
|
||||
},
|
||||
"records": records,
|
||||
"denominators": denominators,
|
||||
"excluded_from_execution": excluded_from_execution,
|
||||
"provider_usage": provider_usage,
|
||||
}
|
||||
return {
|
||||
"schema_version": "sre_alert_learning_corpus_v1",
|
||||
"window": fingerprint_payload["window"],
|
||||
"status": status,
|
||||
"runtime_closed": False,
|
||||
"read_only": True,
|
||||
"raw_context_included": False,
|
||||
"record_count": len(records),
|
||||
"learning_eligible_record_count": eligible_record_count,
|
||||
"missing_source_identity_count": missing_source_identity_count,
|
||||
"records": records,
|
||||
"denominators": denominators,
|
||||
"provider_usage": dict(provider_usage),
|
||||
"provider_cost_usd": str(provider_usage.get("cost_usd") or "0.0000"),
|
||||
"excluded_from_execution": excluded_from_execution,
|
||||
"recomputation_contract": {
|
||||
"alert": "count(records)",
|
||||
"repair": "count(records where outcomes.repair_observed=true)",
|
||||
"execution": (
|
||||
"count(records where outcomes.production_execution_observed=true); "
|
||||
"shadow, canary, check-mode, dry-run and no-write are excluded"
|
||||
),
|
||||
"verifier": (
|
||||
"count(executed records where outcomes.verifier_observed=true)"
|
||||
),
|
||||
"recurrence": ("count(records where outcomes.recurrence_observed=true)"),
|
||||
"cost": (
|
||||
"sum(provider_usage.rows.call_count) and "
|
||||
"sum(provider_usage.rows.cost_usd)"
|
||||
),
|
||||
},
|
||||
"corpus_sha256": _sha256_json(fingerprint_payload),
|
||||
"same_run_receipt_verification": "pending",
|
||||
}
|
||||
|
||||
|
||||
def build_automation_slo_scorecard(
|
||||
*,
|
||||
project_id: str,
|
||||
@@ -349,6 +643,21 @@ def build_automation_slo_scorecard(
|
||||
"controlled_failed_runs",
|
||||
)
|
||||
}
|
||||
provider_usage = {
|
||||
"rows": costs,
|
||||
"call_count": sum(row["call_count"] for row in costs),
|
||||
"prompt_tokens": sum(row["prompt_tokens"] for row in costs),
|
||||
"completion_tokens": sum(row["completion_tokens"] for row in costs),
|
||||
"cost_usd": f"{total_cost:.4f}",
|
||||
}
|
||||
learning_corpus = _build_learning_corpus(
|
||||
project_id=project_id,
|
||||
incident_samples=incident_samples,
|
||||
safe_runs=safe_runs,
|
||||
provider_usage=provider_usage,
|
||||
window_start=window_start,
|
||||
window_end=window_end,
|
||||
)
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"project_id": project_id,
|
||||
@@ -363,13 +672,8 @@ def build_automation_slo_scorecard(
|
||||
"denominators": denominators,
|
||||
"lane_scorecards": lane_scorecards,
|
||||
"runs": safe_runs,
|
||||
"provider_usage": {
|
||||
"rows": costs,
|
||||
"call_count": sum(row["call_count"] for row in costs),
|
||||
"prompt_tokens": sum(row["prompt_tokens"] for row in costs),
|
||||
"completion_tokens": sum(row["completion_tokens"] for row in costs),
|
||||
"cost_usd": f"{total_cost:.4f}",
|
||||
},
|
||||
"provider_usage": provider_usage,
|
||||
"learning_corpus": learning_corpus,
|
||||
"missing_metrics": missing_metrics,
|
||||
"coverage_blockers": coverage_blockers,
|
||||
"evidence": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
@@ -20,8 +21,10 @@ def _sample(
|
||||
action_seconds: int,
|
||||
resolve_seconds: int,
|
||||
positive: bool,
|
||||
sample_id: str | None = None,
|
||||
) -> dict:
|
||||
return {
|
||||
"sample_id": sample_id or f"{lane}:{int(start.timestamp())}",
|
||||
"lane": lane,
|
||||
"received_at": start,
|
||||
"first_action_at": start + timedelta(seconds=action_seconds),
|
||||
@@ -31,7 +34,10 @@ def _sample(
|
||||
"recurrence_observed": True,
|
||||
"recurrence": positive,
|
||||
"human_intervention": positive,
|
||||
"repair_observed": True,
|
||||
"repair_accepted": True,
|
||||
"execution_observed": True,
|
||||
"shadow_or_no_write_execution_observed": False,
|
||||
"verifier_observed": True,
|
||||
"verifier_passed": not positive,
|
||||
"rollback_performed": positive,
|
||||
@@ -53,6 +59,7 @@ def test_scorecard_computes_all_required_metrics_and_provider_cost() -> None:
|
||||
action_seconds=10,
|
||||
resolve_seconds=100,
|
||||
positive=False,
|
||||
sample_id="INC-REAL-1",
|
||||
),
|
||||
_sample(
|
||||
start + timedelta(hours=2),
|
||||
@@ -60,6 +67,7 @@ def test_scorecard_computes_all_required_metrics_and_provider_cost() -> None:
|
||||
action_seconds=20,
|
||||
resolve_seconds=200,
|
||||
positive=True,
|
||||
sample_id="INC-REAL-2",
|
||||
),
|
||||
],
|
||||
run_summary={
|
||||
@@ -115,6 +123,11 @@ def test_scorecard_computes_all_required_metrics_and_provider_cost() -> None:
|
||||
assert scorecard["runs"]["completed_runs"] == 4
|
||||
assert scorecard["runs"]["controlled_completed_runs"] == 1
|
||||
assert scorecard["runs"]["controlled_failed_runs"] == 1
|
||||
assert scorecard["denominators"]["repair_observed_count"] == 2
|
||||
assert scorecard["denominators"]["repair_accepted_count"] == 2
|
||||
assert scorecard["denominators"]["execution_observed_count"] == 2
|
||||
assert scorecard["denominators"]["verifier_observed_count"] == 2
|
||||
assert scorecard["denominators"]["shadow_or_no_write_execution_excluded_count"] == 0
|
||||
assert scorecard["provider_usage"] == {
|
||||
"rows": [
|
||||
{
|
||||
@@ -139,6 +152,40 @@ def test_scorecard_computes_all_required_metrics_and_provider_cost() -> None:
|
||||
"completion_tokens": 350,
|
||||
"cost_usd": "0.0123",
|
||||
}
|
||||
corpus = scorecard["learning_corpus"]
|
||||
assert corpus["schema_version"] == "sre_alert_learning_corpus_v1"
|
||||
assert corpus["window"] == {
|
||||
"start": start.astimezone(UTC).isoformat(),
|
||||
"end": end.astimezone(UTC).isoformat(),
|
||||
}
|
||||
assert corpus["runtime_closed"] is False
|
||||
assert corpus["read_only"] is True
|
||||
assert corpus["raw_context_included"] is False
|
||||
assert corpus["record_count"] == 2
|
||||
assert corpus["learning_eligible_record_count"] == 1
|
||||
assert corpus["status"] == "partial_missing_receipt_coverage"
|
||||
assert corpus["denominators"] == {
|
||||
"alert_count": 2,
|
||||
"repair_observed_count": 2,
|
||||
"repair_accepted_count": 2,
|
||||
"production_execution_observed_count": 2,
|
||||
"verifier_observed_execution_count": 2,
|
||||
"recurrence_classified_count": 2,
|
||||
"controlled_non_shadow_run_count": 2,
|
||||
"provider_call_count": 4,
|
||||
}
|
||||
assert corpus["provider_usage"] == scorecard["provider_usage"]
|
||||
assert corpus["provider_cost_usd"] == "0.0123"
|
||||
assert corpus["excluded_from_execution"] == {
|
||||
"shadow_run_count": 2,
|
||||
"non_shadow_no_step_run_count": 1,
|
||||
"alert_count_with_shadow_or_no_write_execution_evidence": 0,
|
||||
}
|
||||
assert all(row["record_id"].startswith("sha256:") for row in corpus["records"])
|
||||
assert len(corpus["corpus_sha256"]) == 64
|
||||
serialized_corpus = json.dumps(corpus)
|
||||
assert "INC-REAL-1" not in serialized_corpus
|
||||
assert "INC-REAL-2" not in serialized_corpus
|
||||
assert scorecard["evidence"] == {
|
||||
"source": "postgresql_read_only_aggregate",
|
||||
"same_run_receipt_verification": "pending",
|
||||
@@ -171,6 +218,90 @@ def test_scorecard_does_not_false_green_missing_runtime_samples() -> None:
|
||||
"verifier_pass_rate",
|
||||
}
|
||||
assert scorecard["provider_usage"]["cost_usd"] == "0.0000"
|
||||
assert scorecard["learning_corpus"]["status"] == ("no_runtime_sample_fail_closed")
|
||||
assert scorecard["learning_corpus"]["record_count"] == 0
|
||||
|
||||
|
||||
def test_learning_corpus_excludes_shadow_and_no_write_from_execution() -> None:
|
||||
end = datetime(2026, 7, 19, 4, 0, tzinfo=UTC)
|
||||
real = _sample(
|
||||
end - timedelta(hours=2),
|
||||
lane="kubernetes_workload",
|
||||
action_seconds=10,
|
||||
resolve_seconds=100,
|
||||
positive=False,
|
||||
sample_id="INC-PRODUCTION",
|
||||
)
|
||||
excluded = _sample(
|
||||
end - timedelta(hours=1),
|
||||
lane="host_systemd",
|
||||
action_seconds=20,
|
||||
resolve_seconds=200,
|
||||
positive=False,
|
||||
sample_id="INC-SHADOW",
|
||||
)
|
||||
excluded["execution_observed"] = False
|
||||
excluded["shadow_or_no_write_execution_observed"] = True
|
||||
|
||||
scorecard = build_automation_slo_scorecard(
|
||||
project_id="awoooi",
|
||||
incident_samples=[real, excluded],
|
||||
run_summary={
|
||||
"total_runs": 3,
|
||||
"non_shadow_runs": 2,
|
||||
"controlled_runs_with_steps": 1,
|
||||
"completed_runs": 3,
|
||||
"failed_runs": 0,
|
||||
"controlled_completed_runs": 1,
|
||||
"controlled_failed_runs": 0,
|
||||
},
|
||||
provider_cost_rows=[],
|
||||
window_start=end - timedelta(hours=24),
|
||||
window_end=end,
|
||||
)
|
||||
|
||||
assert scorecard["denominators"]["execution_observed_count"] == 1
|
||||
assert scorecard["denominators"]["verifier_observed_count"] == 1
|
||||
assert scorecard["denominators"]["shadow_or_no_write_execution_excluded_count"] == 1
|
||||
corpus = scorecard["learning_corpus"]
|
||||
assert corpus["status"] == ("learning_corpus_ready_same_run_verification_pending")
|
||||
assert corpus["denominators"]["production_execution_observed_count"] == 1
|
||||
assert corpus["denominators"]["verifier_observed_execution_count"] == 1
|
||||
assert corpus["excluded_from_execution"] == {
|
||||
"shadow_run_count": 1,
|
||||
"non_shadow_no_step_run_count": 1,
|
||||
"alert_count_with_shadow_or_no_write_execution_evidence": 1,
|
||||
}
|
||||
excluded_record = next(
|
||||
row
|
||||
for row in corpus["records"]
|
||||
if row["outcomes"]["shadow_or_no_write_execution_excluded"]
|
||||
)
|
||||
assert excluded_record["outcomes"]["production_execution_observed"] is False
|
||||
assert excluded_record["outcomes"]["verifier_observed"] is False
|
||||
|
||||
|
||||
def test_incident_query_classifies_only_non_shadow_runtime_execution() -> None:
|
||||
statement = " ".join(str(scorecard_module._INCIDENT_SAMPLES_SQL).split())
|
||||
execution_events = statement.split("), execution_evidence AS (", 1)[1].split(
|
||||
"AS execution_like_event", 1
|
||||
)[0]
|
||||
|
||||
assert "AUTO_REPAIR_TRIGGERED" not in execution_events
|
||||
assert (
|
||||
"event_type IN ( 'EXECUTION_STARTED', 'EXECUTION_COMPLETED', "
|
||||
"'CHANGE_APPLIED' )" in execution_events
|
||||
)
|
||||
assert "AS production_execution_evidence" in statement
|
||||
assert "AS execution_exclusion_evidence" in statement
|
||||
assert "context ->> 'is_shadow'" in statement
|
||||
assert "context ->> 'execution_mode'" in statement
|
||||
assert "context ->> 'side_effect_performed'" in statement
|
||||
assert "'%no_write%'" in statement
|
||||
assert "context ->> 'execution_mode', 'production'" not in statement
|
||||
assert "context ->> 'execution_kind', 'executable'" not in statement
|
||||
assert "bool_or(production_execution_event) AS execution_observed" in statement
|
||||
assert "context ? 'post_verifier_passed'" in statement
|
||||
|
||||
|
||||
def test_run_query_excludes_shadow_and_no_step_runs_from_controlled_outcomes() -> None:
|
||||
@@ -182,8 +313,7 @@ def test_run_query_excludes_shadow_and_no_step_runs_from_controlled_outcomes() -
|
||||
)
|
||||
assert (
|
||||
"WHERE is_shadow IS FALSE AND step_count > 0 "
|
||||
"AND state IN ('failed', 'cancelled', 'timeout')"
|
||||
in statement
|
||||
"AND state IN ('failed', 'cancelled', 'timeout')" in statement
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -155,8 +155,8 @@ def test_loader_returns_fixed_architecture_provider_order_and_agent99_bridge() -
|
||||
"active_or_completed_commitments": 68,
|
||||
"by_status": {
|
||||
"analysis_or_governance_complete": 6,
|
||||
"source_implemented_runtime_pending": 35,
|
||||
"in_progress": 26,
|
||||
"source_implemented_runtime_pending": 36,
|
||||
"in_progress": 25,
|
||||
"not_started_or_no_current_evidence": 1,
|
||||
"superseded": 2,
|
||||
},
|
||||
@@ -206,6 +206,12 @@ def test_loader_returns_fixed_architecture_provider_order_and_agent99_bridge() -
|
||||
assert "independent exit-code and freshness receipts" in " ".join(
|
||||
commitments["AIA-CONV-041"]["source_evidence"]
|
||||
)
|
||||
assert commitments["AIA-CONV-045"]["status"] == (
|
||||
"source_implemented_runtime_pending"
|
||||
)
|
||||
assert "deterministically recomputable" in " ".join(
|
||||
commitments["AIA-CONV-045"]["source_evidence"]
|
||||
)
|
||||
assert "Host112" in commitments["AIA-CONV-049"]["title"]
|
||||
assert commitments["AIA-CONV-049"]["status"] == (
|
||||
"source_implemented_runtime_pending"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "sre_ai_agent_conversation_commitments_v1",
|
||||
"program_id": "AIA-SRE-P0-20260715",
|
||||
"generated_at": "2026-07-22T18:49:36+08:00",
|
||||
"generated_at": "2026-07-22T21:45:56+08:00",
|
||||
"source_scope": "This Codex AI Agent task only; cross-task delegation envelopes are excluded.",
|
||||
"authoritative_for_execution_order": false,
|
||||
"raw_conversation_embedded": false,
|
||||
@@ -63,7 +63,7 @@
|
||||
{"id":"AIA-CONV-042","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"所有告警推理前整合 MCP 與 RAG context","linked_work_items":["AIA-SRE-012","AIA-SRE-015"],"terminal_condition":"每次推理記錄 context source IDs、freshness 與 retrieval receipt。","source_evidence":["all DecisionManager reasoning and early-return paths reserve one durable pre-inference MCP/RAG receipt before provider or Agent debate","receipt records exact MCP evidence or execution IDs, Knowledge entry IDs, observed timestamps and deterministic freshness without persisting raw context","Phase2, primary LLM and background PlayBook draft receive the same sanitized untrusted receipt-bound context","receipt persistence or context-source contract failure returns a deterministic no-provider no-executor no-runtime-mutation terminal"]},
|
||||
{"id":"AIA-CONV-043","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"診斷、候選、執行、驗證沉澱至 KM/RAG/MCP/PlayBook","linked_work_items":["AIA-SRE-015"],"terminal_condition":"同 run 六個 durable write receipts 唯一、可讀回且與 target/source 一致。"},
|
||||
{"id":"AIA-CONV-044","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"每項資產必須有 ID、owner、status、next action、來源與 receipt","linked_work_items":["AIA-SRE-001","AIA-SRE-002","AIA-SRE-015"],"terminal_condition":"Runs、Work Items 與 KB 只讀同一 canonical identity 與 evidence chain。"},
|
||||
{"id":"AIA-CONV-045","category":"knowledge_closure","status":"in_progress","title":"24 小時 production 告警形成可學習 corpus","linked_work_items":["AIA-SRE-016"],"terminal_condition":"alert、repair、execution、verifier、recurrence 與 cost 分母可重算,shadow 不冒充 execution。"},
|
||||
{"id":"AIA-CONV-045","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"24 小時 production 告警形成可學習 corpus","linked_work_items":["AIA-SRE-016"],"terminal_condition":"alert、repair、execution、verifier、recurrence 與 cost 分母可重算,shadow 不冒充 execution。","source_evidence":["project-scoped read-only 24h corpus projects only hashed alert identities and sanitized lifecycle labels","alert, repair, production execution, verifier, recurrence, controlled non-shadow run and provider-call cost denominators are deterministically recomputable","execution classifier and run projection exclude shadow, canary, check-mode, dry-run, no-write and non-step outcomes from production execution","missing identity, freshness, classification or verifier coverage remains partial and same-run production verification pending"]},
|
||||
{"id":"AIA-CONV-046","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"Runs、Work Items、Knowledge Base 顯示完整關聯證據","linked_work_items":["AIA-SRE-001","AIA-SRE-015","AIA-SRE-017"],"terminal_condition":"API 與 desktop/mobile cockpit 對相同 run 顯示一致的資產與 receipt truth。"},
|
||||
{"id":"AIA-CONV-047","category":"knowledge_closure","status":"in_progress","title":"Executor 不得是唯一 verifier","linked_work_items":["AIA-SRE-014"],"terminal_condition":"每個 mutating domain 都有不同 owner/identity 的 independent verifier。"},
|
||||
{"id":"AIA-CONV-048","category":"knowledge_closure","status":"source_implemented_runtime_pending","title":"Telegram、callback、learning receipt 使用 durable production readback","linked_work_items":["AIA-SRE-015","AIA-SRE-017"],"terminal_condition":"source flag、HTTP 200 或 configured state 不得取代 production durable acknowledgement。"},
|
||||
@@ -98,8 +98,8 @@
|
||||
"active_or_completed_commitments": 68,
|
||||
"by_status": {
|
||||
"analysis_or_governance_complete": 6,
|
||||
"source_implemented_runtime_pending": 35,
|
||||
"in_progress": 26,
|
||||
"source_implemented_runtime_pending": 36,
|
||||
"in_progress": 25,
|
||||
"not_started_or_no_current_evidence": 1,
|
||||
"superseded": 2
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user