Compare commits

...
12 changed files with 523 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
"""API adapter for visual AIOS operations dashboard."""
from __future__ import annotations
try:
from fastapi import APIRouter
except Exception:
APIRouter = None
from .aios_visual_ops_flow import AIOSVisualOpsFlow
visual = AIOSVisualOpsFlow()
def get_visual_operations_dashboard():
return {
"flow": visual.build_flow(),
"timeline": visual.build_timeline(),
"trends": visual.build_trends(),
}
def build_router():
if APIRouter is None:
return None
router = APIRouter(prefix="/admin/ai-evolution/visual", tags=["visual-ops"])
@router.get("/overview", operation_id="getAIOSVisualOperationsDashboard")
def overview():
return get_visual_operations_dashboard()
return router
router = build_router()
__all__ = ["router", "get_visual_operations_dashboard"]
+134
View File
@@ -0,0 +1,134 @@
"""Visual operations flow model for AIOS Web UI.
Provides graph/timeline/trend data so the admin UI can show system behavior as
flows and operational diagrams instead of only numeric tables.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Any
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
@dataclass(frozen=True)
class FlowNode:
id: str
label: str
node_type: str
status: str = "ok"
group: str = "default"
detail: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class FlowEdge:
source: str
target: str
label: str
status: str = "ok"
count: int = 0
latency_ms: int | None = None
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class TimelineEvent:
id: str
title: str
event_type: str
status: str
occurred_at: str
summary: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class TrendPoint:
timestamp: str
values: dict[str, Any]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
class AIOSVisualOpsFlow:
"""Builds visual workflow data for `/admin/ai-evolution` dashboards."""
def build_flow(self, runtime: dict[str, Any] | None = None, counters: dict[str, int] | None = None) -> dict[str, Any]:
runtime = runtime or {}
counters = counters or {}
nodes = [
FlowNode("event", "事件/命令输入", "input", group="ingest"),
FlowNode("watcher", "Command Watcher", "service", runtime.get("watcher_status", "ok"), "control"),
FlowNode("controller", "Autonomous Controller", "service", runtime.get("controller_status", "ok"), "control"),
FlowNode("diagnosis", "诊断/策略判断", "process", group="analysis"),
FlowNode("patch", "Patch 生成", "process", group="gitops"),
FlowNode("owner_gate", "Owner Gate", "gate", runtime.get("owner_gate_status", "ok"), "governance"),
FlowNode("git", "Git Archive", "store", runtime.get("git_status", "ok"), "gitops"),
FlowNode("audit", "Audit Plane", "store", runtime.get("audit_status", "ok"), "audit"),
FlowNode("ui", "Web UI 可视化", "output", group="visibility"),
FlowNode("notify", "通知/日报", "output", runtime.get("notification_status", "ok"), "visibility"),
]
edges = [
FlowEdge("event", "watcher", "发现命令", count=counters.get("commands_seen", 0)),
FlowEdge("watcher", "controller", "触发自治循环", count=counters.get("controller_cycles", 0)),
FlowEdge("controller", "diagnosis", "健康检查/差异检测", count=counters.get("health_checks", 0)),
FlowEdge("diagnosis", "patch", "生成修复计划", count=counters.get("patch_candidates", 0)),
FlowEdge("patch", "owner_gate", "提交审批", count=counters.get("pending_owner_gate", 0)),
FlowEdge("owner_gate", "git", "批准后归档", count=counters.get("archived_changes", 0)),
FlowEdge("git", "audit", "写入审计", count=counters.get("audit_events", 0)),
FlowEdge("audit", "ui", "展示证据", count=counters.get("ui_refresh", 0)),
FlowEdge("audit", "notify", "汇报状态", count=counters.get("notifications", 0)),
]
return {
"type": "flow_graph",
"layout": "left_to_right",
"generated_at": utc_now(),
"nodes": [n.to_dict() for n in nodes],
"edges": [e.to_dict() for e in edges],
}
def build_timeline(self, events: list[dict[str, Any]] | None = None) -> dict[str, Any]:
if not events:
events = [
{"id": "baseline", "title": "Baseline V1R3C1 生效", "event_type": "release", "status": "ok", "summary": "自治系统 baseline 已晋升"},
{"id": "watcher", "title": "Command Watcher 心跳", "event_type": "heartbeat", "status": "ok", "summary": "最近周期正常"},
{"id": "audit", "title": "GitOps 差异巡检", "event_type": "audit", "status": "ok", "summary": "无未归档变更"},
]
timeline = [
TimelineEvent(
id=e.get("id", f"event-{idx}"),
title=e.get("title", "event"),
event_type=e.get("event_type", "generic"),
status=e.get("status", "ok"),
occurred_at=e.get("occurred_at", utc_now()),
summary=e.get("summary", ""),
).to_dict()
for idx, e in enumerate(events)
]
return {"type": "timeline", "events": timeline, "generated_at": utc_now()}
def build_trends(self, points: list[dict[str, Any]] | None = None) -> dict[str, Any]:
points = points or [
{"timestamp": utc_now(), "values": {"health_score": 100, "drift_count": 0, "pending_owner_gate": 0, "failed_commands": 0}}
]
return {
"type": "trend_series",
"series": [TrendPoint(p["timestamp"], p.get("values", {})).to_dict() for p in points],
"recommended_charts": ["health_score_line", "drift_bar", "owner_gate_queue", "failure_rate"],
}
__all__ = ["FlowNode", "FlowEdge", "TimelineEvent", "TrendPoint", "AIOSVisualOpsFlow"]
+42
View File
@@ -0,0 +1,42 @@
"""GitOps change archive and drift audit engine."""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Any
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
@dataclass(frozen=True)
class ChangeAuditReport:
status: str
workspace_dirty: bool
missing_metadata: list[str]
drift_items: list[str]
checked_at: str = field(default_factory=utc_now)
def to_dict(self) -> dict[str, Any]:
return asdict(self)
class GitOpsChangeAuditor:
def audit(
self,
workspace_dirty: bool,
metadata: dict[str, Any],
baseline_diff: list[str],
) -> ChangeAuditReport:
required = ["actor", "timestamp", "reason", "branch", "commit"]
missing = [key for key in required if not metadata.get(key)]
drift = list(baseline_diff)
status = "healthy"
if workspace_dirty or missing or drift:
status = "attention_required"
return ChangeAuditReport(status, workspace_dirty, missing, drift)
__all__ = ["ChangeAuditReport", "GitOpsChangeAuditor"]
@@ -0,0 +1,40 @@
"""API adapter for GitOps operations dashboard."""
from __future__ import annotations
from typing import Any
try:
from fastapi import APIRouter
except Exception:
APIRouter = None
from .gitops_ops_operating_audit_dashboard import GitOpsOpsOperatingAuditDashboard
dashboard = GitOpsOpsOperatingAuditDashboard()
def get_gitops_ops_dashboard(
baseline: dict[str, Any] | None = None,
runtime: dict[str, Any] | None = None,
audit_report: dict[str, Any] | None = None,
) -> dict[str, Any]:
return dashboard.overview(baseline, runtime, audit_report)
def build_router():
if APIRouter is None:
return None
router = APIRouter(prefix="/admin/ai-evolution/gitops", tags=["gitops-dashboard"])
@router.get("/overview", operation_id="getGitOpsOpsOperatingAuditDashboard")
def overview():
return get_gitops_ops_dashboard()
return router
router = build_router()
__all__ = ["router", "get_gitops_ops_dashboard"]
@@ -0,0 +1,121 @@
"""Web UI dashboard model for GitOps 运维 / 运营 / 审计 visibility.
This module produces framework-neutral data for `/admin/ai-evolution/gitops`.
It is intentionally read-only: no secrets, no direct Git writes, no main push.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Any
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
@dataclass(frozen=True)
class PanelMetric:
key: str
title: str
value: Any
status: str = "ok"
detail: str = ""
def to_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True)
class DashboardPanel:
key: str
title: str
description: str
metrics: list[PanelMetric] = field(default_factory=list)
def to_dict(self) -> dict[str, Any]:
return {
"key": self.key,
"title": self.title,
"description": self.description,
"metrics": [m.to_dict() for m in self.metrics],
}
class GitOpsOpsOperatingAuditDashboard:
"""Aggregates GitOps archive/audit state into three UI sections."""
def overview(
self,
baseline: dict[str, Any] | None = None,
runtime: dict[str, Any] | None = None,
audit_report: dict[str, Any] | None = None,
) -> dict[str, Any]:
baseline = baseline or {}
runtime = runtime or {}
audit_report = audit_report or {}
panels = [
self.ops_panel(baseline, runtime, audit_report),
self.operating_panel(baseline, runtime, audit_report),
self.audit_panel(baseline, runtime, audit_report),
]
return {
"module": "gitops-ops-operating-audit",
"route": "/admin/ai-evolution/gitops",
"title": "GitOps 运维 / 运营 / 审计",
"status": self._overall_status(panels),
"generated_at": utc_now(),
"panels": [p.to_dict() for p in panels],
}
def ops_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
return DashboardPanel(
key="ops",
title="运维视图",
description="Runtime、Baseline、Git 同步和差异巡检状态。",
metrics=[
PanelMetric("runtime_status", "Runtime 状态", runtime.get("status", "unknown"), "ok" if runtime.get("status") == "active" else "warn"),
PanelMetric("baseline", "当前 Baseline", baseline.get("current_baseline") or baseline.get("active_version") or "unknown"),
PanelMetric("git_rev", "Git Revision", baseline.get("git", "unknown")),
PanelMetric("workspace_dirty", "未归档工作区变化", bool(audit_report.get("workspace_dirty", False)), "warn" if audit_report.get("workspace_dirty") else "ok"),
],
)
def operating_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
counters = audit_report.get("counters", {})
return DashboardPanel(
key="operating",
title="运营视图",
description="自治运行效果、变更吞吐、待审批事项和巡检结果。",
metrics=[
PanelMetric("change_events", "今日变更事件", counters.get("change_events", 0)),
PanelMetric("diff_audits", "差异巡检次数", counters.get("diff_audits", 0)),
PanelMetric("pending_owner_gate", "待 Owner 审批", counters.get("pending_owner_gate", 0), "warn" if counters.get("pending_owner_gate", 0) else "ok"),
PanelMetric("auto_repair_candidates", "自动修复候选", counters.get("auto_repair_candidates", 0)),
],
)
def audit_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
missing = audit_report.get("missing_metadata", [])
drift = audit_report.get("drift_items", [])
return DashboardPanel(
key="audit",
title="审计视图",
description="Git 归档完整性、元数据合规、runtime-only 变更和差异证据。",
metrics=[
PanelMetric("archive_policy", "所有配置/代码变更必须入 Git", "enabled"),
PanelMetric("missing_metadata", "缺失变更元数据", len(missing), "warn" if missing else "ok", ",".join(missing)),
PanelMetric("drift_items", "发现差异项", len(drift), "warn" if drift else "ok", ",".join(drift[:10])),
PanelMetric("secrets_in_git", "Secrets in Git", False, "ok"),
],
)
def _overall_status(self, panels: list[DashboardPanel]) -> str:
for panel in panels:
if any(metric.status == "warn" for metric in panel.metrics):
return "attention_required"
return "healthy"
__all__ = ["PanelMetric", "DashboardPanel", "GitOpsOpsOperatingAuditDashboard"]
+26
View File
@@ -0,0 +1,26 @@
gitops_change_archive:
enabled: true
rule: every_code_and_config_change_must_be_git_archived
enforcement:
uncommitted_workspace_changes: alert
direct_runtime_mutation: alert
missing_commit_metadata: alert
required_metadata:
- actor
- timestamp
- reason
- branch
- commit
diff_audit:
enabled: true
interval: hourly
compare:
- workspace_vs_head
- head_vs_baseline
- runtime_config_vs_git
reports:
path: audit/change-diff/
governance:
direct_main_push: false
secrets_in_git: false
owner_gate_required: true
+8
View File
@@ -0,0 +1,8 @@
openapi: 3.0.0
info:
title: GitOps Ops Operating Audit Dashboard API
version: V1R3C1+
paths:
/admin/ai-evolution/gitops/overview:
get:
operationId: getGitOpsOpsOperatingAuditDashboard
@@ -0,0 +1,34 @@
# CMD-20260709-enable-gitops-change-archive-audit
## Goal
Enforce that all AIOS code and configuration changes are archived in Git and
periodically checked for drift.
## Execute
1. Enable GitOps change archive policy.
2. Run hourly diff audits:
- workspace vs HEAD
- HEAD vs baseline
- runtime config vs Git
3. Write reports:
- audit/change-diff/
4. Alert on:
- uncommitted changes
- runtime-only mutation
- missing actor/reason/branch/commit metadata
## Validation
Expected signal:
- AIOS_GITOPS_CHANGE_ARCHIVE_READY
- AIOS_DIFF_AUDITOR_READY
## Governance
- No direct main push.
- No secrets in Git.
- All changes require commit metadata.
- Owner gate remains enabled.
@@ -0,0 +1,30 @@
# CMD-20260709-enable-gitops-ops-operating-audit-ui
## Goal
Deploy GitOps 运维 / 运营 / 审计 Web UI visibility.
## Deploy
1. Register dashboard API:
- getGitOpsOpsOperatingAuditDashboard
2. Mount Web UI route:
- /admin/ai-evolution/gitops
3. Display panels:
- 运维: runtime, baseline, git revision, drift
- 运营: changes, audits, approvals, repairs
- 审计: metadata, diff, archive compliance
4. Enable hourly diff audit data feed.
## Validation signals
- AIOS_GITOPS_UI_OPS_READY
- AIOS_GITOPS_UI_OPERATING_READY
- AIOS_GITOPS_UI_AUDIT_READY
## Governance
- All changes archived in Git.
- No direct main push.
- No secrets in Git.
- Owner gate remains enabled.
@@ -0,0 +1,29 @@
# CMD-20260709-enable-visual-aios-ops-dashboard
## Goal
Upgrade AIOS Web UI from numeric dashboards to visual operational flow.
## Deploy
1. Register visual operations API:
- getAIOSVisualOperationsDashboard
2. Add UI panels:
- autonomous control flow graph
- command lifecycle timeline
- health trend charts
- drift/failure visualization
- owner gate flow
3. Integrate with GitOps audit data.
## Validation signals
- AIOS_VISUAL_OPS_FLOW_READY
- AIOS_TIMELINE_DASHBOARD_READY
- AIOS_HEALTH_TREND_READY
## Governance
- All visual data comes from audited Git/runtime sources.
- No direct runtime mutation.
- No secrets in Git.
@@ -0,0 +1,9 @@
{
"type": "branch",
"branch": "patch/gitops-change-archive-audit",
"base": "main",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-08T12:43:11Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -0,0 +1,13 @@
from active_runtime.app.gitops_ops_operating_audit_dashboard import GitOpsOpsOperatingAuditDashboard
def test_dashboard_contains_ops_operating_audit():
result = GitOpsOpsOperatingAuditDashboard().overview(
baseline={"current_baseline": "V1R3C1", "git": "test"},
runtime={"status": "active"},
audit_report={"counters": {"change_events": 3}}
)
assert result["module"] == "gitops-ops-operating-audit"
assert len(result["panels"]) == 3
assert result["panels"][2]["key"] == "audit"