feat(platform): 顯示全域產品資產納管
Some checks failed
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m33s
CD Pipeline / build-and-deploy (push) Successful in 4m17s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-14 15:18:54 +08:00
parent 30f2f490c7
commit fb5c6fbadd
7 changed files with 1146 additions and 8 deletions

View File

@@ -9,6 +9,7 @@ ADR-106AwoooP Agent Platform
from __future__ import annotations
import asyncio
import json
import os
import re
import time
@@ -16,6 +17,7 @@ import uuid
from collections import defaultdict
from collections.abc import Mapping
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any, get_args
from urllib.parse import urlencode
from uuid import UUID
@@ -65,6 +67,7 @@ from src.services.operator_summary_cache import (
store_operator_summary_async,
)
from src.services.run_state_machine import transition
from src.services.snapshot_paths import resolve_repo_root
logger = structlog.get_logger(__name__)
@@ -130,6 +133,454 @@ _CALLBACK_REPLY_AUDIT_SUMMARY_SCHEMA_VERSION = (
# Tenants
# =============================================================================
_PUBLIC_GATEWAY_INVENTORY_SNAPSHOT = (
"docs/security/public-gateway-preflight-inventory.snapshot.json"
)
_SOURCE_CONTROL_READINESS_SNAPSHOT = (
"docs/security/source-control-primary-readiness-gate.snapshot.json"
)
_DOMAIN_PRODUCT_OVERRIDES: dict[str, dict[str, str]] = {
"aiops.wooo.work": {
"product_id": "awoooi",
"product_name": "AWOOOI / AwoooP / IwoooS",
"category": "core_platform",
},
"awoooi.wooo.work": {
"product_id": "awoooi",
"product_name": "AWOOOI / AwoooP / IwoooS",
"category": "core_platform",
},
"mo.wooo.work": {
"product_id": "ewoooc",
"product_name": "EwoooC / Mo",
"category": "business_product",
},
"vibework.wooo.work": {
"product_id": "vibework",
"product_name": "VibeWork",
"category": "business_product",
},
"agent.wooo.work": {
"product_id": "agent-bounty-protocol",
"product_name": "Agent Bounty Protocol",
"category": "business_product",
},
"stock.wooo.work": {
"product_id": "stockplatform",
"product_name": "StockPlatform",
"category": "business_product",
},
"bitan.wooo.work": {
"product_id": "bitan-pharmacy",
"product_name": "Bitan Pharmacy",
"category": "business_product",
},
"tsenyang.com": {
"product_id": "tsenyang-website",
"product_name": "TsenYang Website",
"category": "public_site",
},
"www.tsenyang.com": {
"product_id": "tsenyang-website",
"product_name": "TsenYang Website",
"category": "public_site",
},
"vtuber.wooo.work": {
"product_id": "vtuber",
"product_name": "VTuber",
"category": "public_site",
},
"gitea.wooo.work": {
"product_id": "source-control",
"product_name": "Source Control / DevOps",
"category": "platform_tool",
},
"gitlab.wooo.work": {
"product_id": "source-control",
"product_name": "Source Control / DevOps",
"category": "platform_tool",
},
"harbor.wooo.work": {
"product_id": "source-control",
"product_name": "Source Control / DevOps",
"category": "platform_tool",
},
"registry.wooo.work": {
"product_id": "source-control",
"product_name": "Source Control / DevOps",
"category": "platform_tool",
},
"sentry.wooo.work": {
"product_id": "observability-tooling",
"product_name": "Observability / LLMOps",
"category": "platform_tool",
},
"signoz.wooo.work": {
"product_id": "observability-tooling",
"product_name": "Observability / LLMOps",
"category": "platform_tool",
},
"langfuse.wooo.work": {
"product_id": "observability-tooling",
"product_name": "Observability / LLMOps",
"category": "platform_tool",
},
}
_CANONICAL_PRODUCT_SURFACES: tuple[dict[str, Any], ...] = (
{
"product_id": "awoooi",
"product_name": "AWOOOI / AwoooP / IwoooS",
"project_id": "awoooi",
"category": "core_platform",
"surface_kind": "platform_product",
"owner_lane": "S4.9-S4.13",
"coverage_status": "read_only_visible",
"public_routes": ["awoooi.wooo.work", "aiops.wooo.work"],
"source_keys": ["wooo/awoooi"],
},
{
"product_id": "ewoooc",
"product_name": "EwoooC / Mo",
"project_id": "ewoooc",
"category": "business_product",
"surface_kind": "product_console",
"owner_lane": "S4.10",
"coverage_status": "owner_response_required",
"public_routes": ["mo.wooo.work"],
"source_keys": ["wooo/ewoooc / root/momo-pro-system / momo working trees"],
},
{
"product_id": "vibework",
"product_name": "VibeWork",
"project_id": "vibework",
"category": "business_product",
"surface_kind": "product_site",
"owner_lane": "S4.10",
"coverage_status": "owner_response_required",
"public_routes": ["vibework.wooo.work"],
"source_keys": ["vibework"],
},
{
"product_id": "agent-bounty-protocol",
"product_name": "Agent Bounty Protocol",
"project_id": "agent-bounty-protocol",
"category": "business_product",
"surface_kind": "agent_protocol",
"owner_lane": "S4.10",
"coverage_status": "owner_response_required",
"public_routes": ["agent.wooo.work"],
"source_keys": ["agent-bounty-protocol"],
},
{
"product_id": "stockplatform",
"product_name": "StockPlatform",
"project_id": "stockplatform",
"category": "business_product",
"surface_kind": "product_site",
"owner_lane": "P0-CRON-001",
"coverage_status": "read_only_visible",
"public_routes": ["stock.wooo.work"],
"source_keys": ["stockplatform"],
},
{
"product_id": "bitan-pharmacy",
"product_name": "Bitan Pharmacy",
"project_id": "bitan-pharmacy",
"category": "business_product",
"surface_kind": "product_site",
"owner_lane": "P0-CRON-002",
"coverage_status": "read_only_visible",
"public_routes": ["bitan.wooo.work"],
"source_keys": ["bitan-pharmacy"],
},
{
"product_id": "tsenyang-website",
"product_name": "TsenYang Website",
"project_id": "tsenyang-website",
"category": "public_site",
"surface_kind": "public_site",
"owner_lane": "S4.10",
"coverage_status": "owner_response_required",
"public_routes": ["tsenyang.com", "www.tsenyang.com"],
"source_keys": ["tsenyang-website"],
},
{
"product_id": "vtuber",
"product_name": "VTuber",
"project_id": "vtuber",
"category": "public_site",
"surface_kind": "public_site",
"owner_lane": "public_gateway",
"coverage_status": "read_only_visible",
"public_routes": ["vtuber.wooo.work"],
"source_keys": [],
},
{
"product_id": "source-control",
"product_name": "Source Control / DevOps",
"project_id": "__platform__",
"category": "platform_tool",
"surface_kind": "tooling",
"owner_lane": "S4.9-S4.13",
"coverage_status": "read_only_visible",
"public_routes": [
"gitea.wooo.work",
"gitlab.wooo.work",
"harbor.wooo.work",
"registry.wooo.work",
],
"source_keys": ["wooo/wooo-infra-config"],
},
{
"product_id": "observability-tooling",
"product_name": "Observability / LLMOps",
"project_id": "__platform__",
"category": "platform_tool",
"surface_kind": "tooling",
"owner_lane": "P1-003",
"coverage_status": "read_only_visible",
"public_routes": ["sentry.wooo.work", "signoz.wooo.work", "langfuse.wooo.work"],
"source_keys": [],
},
)
_ADDITIONAL_PUBLIC_ROUTES: tuple[dict[str, Any], ...] = (
{
"domain": "awoooi.wooo.work",
"coverage_status": "read_only_visible",
"source": "awoooi_canonical_route",
},
{
"domain": "vibework.wooo.work",
"coverage_status": "owner_response_required",
"source": "vibework_onboarding_handoff",
},
{
"domain": "agent.wooo.work",
"coverage_status": "owner_response_required",
"source": "agent_bounty_onboarding_handoff",
},
)
def _load_committed_snapshot(relative_path: str) -> dict[str, Any]:
repo_root = resolve_repo_root(Path(__file__))
snapshot_path = repo_root / relative_path
try:
return json.loads(snapshot_path.read_text(encoding="utf-8"))
except FileNotFoundError:
logger.warning("tenant_asset_snapshot_missing", path=relative_path)
except json.JSONDecodeError as exc:
logger.warning(
"tenant_asset_snapshot_invalid_json",
path=relative_path,
error=str(exc),
)
return {}
def _domain_product(domain: str) -> dict[str, str]:
return _DOMAIN_PRODUCT_OVERRIDES.get(
domain,
{
"product_id": domain.replace(".", "-"),
"product_name": domain,
"category": "public_route",
},
)
def _route_asset_from_gateway_row(row: Mapping[str, Any]) -> dict[str, Any]:
domain = str(row.get("domain") or "")
product = _domain_product(domain)
route_smoke_accepted = bool(row.get("route_smoke_accepted"))
owner_response_accepted = bool(row.get("owner_response_accepted"))
coverage_status = (
"verified"
if route_smoke_accepted and owner_response_accepted
else "read_only_pending_smoke"
)
return {
"domain": domain,
"product_id": product["product_id"],
"product_name": product["product_name"],
"category": product["category"],
"coverage_status": coverage_status,
"control_tier": row.get("control_tier") or "unknown",
"upstream_count": int(row.get("upstream_count") or 0),
"admin_route_count": int(row.get("admin_route_count") or 0),
"websocket_route_count": int(row.get("websocket_route_count") or 0),
"public_route_smoke_required": bool(row.get("public_route_smoke_required")),
"route_smoke_accepted": route_smoke_accepted,
"owner_response_accepted": owner_response_accepted,
"runtime_gate_count": 0,
"action_button_count": 0,
"source": "public_gateway_preflight_inventory",
}
def _build_route_assets(public_gateway_snapshot: Mapping[str, Any]) -> list[dict[str, Any]]:
seen_domains: set[str] = set()
route_assets: list[dict[str, Any]] = []
for row in public_gateway_snapshot.get("route_impacts") or []:
if not isinstance(row, Mapping):
continue
asset = _route_asset_from_gateway_row(row)
if not asset["domain"] or asset["domain"] in seen_domains:
continue
seen_domains.add(asset["domain"])
route_assets.append(asset)
for row in _ADDITIONAL_PUBLIC_ROUTES:
domain = str(row["domain"])
if domain in seen_domains:
continue
product = _domain_product(domain)
route_assets.append(
{
"domain": domain,
"product_id": product["product_id"],
"product_name": product["product_name"],
"category": product["category"],
"coverage_status": row["coverage_status"],
"control_tier": "candidate",
"upstream_count": 0,
"admin_route_count": 0,
"websocket_route_count": 0,
"public_route_smoke_required": True,
"route_smoke_accepted": False,
"owner_response_accepted": False,
"runtime_gate_count": 0,
"action_button_count": 0,
"source": row["source"],
}
)
return route_assets
def _build_source_repo_assets(
source_control_snapshot: Mapping[str, Any],
) -> list[dict[str, Any]]:
source_assets: list[dict[str, Any]] = []
for row in source_control_snapshot.get("repo_readiness") or []:
if not isinstance(row, Mapping):
continue
source_key = str(row.get("source_key") or "")
github_repo = str(row.get("github_repo") or "")
product = {
"product_id": (source_key or github_repo).replace("/", "-") or "unknown-source",
"product_name": source_key or github_repo or "unknown-source",
"category": "source_repo",
}
for surface in _CANONICAL_PRODUCT_SURFACES:
if source_key in surface.get("source_keys", []) or github_repo in surface.get(
"source_keys", []
):
product = {
"product_id": str(surface["product_id"]),
"product_name": str(surface["product_name"]),
"category": str(surface["category"]),
}
break
source_assets.append(
{
"github_repo": github_repo,
"source_key": source_key,
"product_id": product["product_id"],
"product_name": product["product_name"],
"category": product["category"],
"scope_status": row.get("scope_status") or "unknown",
"readiness_state": row.get("readiness_state") or "unknown",
"risk": row.get("risk") or "UNKNOWN",
"primary_ready": bool(row.get("primary_ready")),
"blocker_count": len(row.get("blockers") or []),
"runtime_gate_count": 0,
"action_button_count": 0,
}
)
return source_assets
def build_tenant_asset_inventory(tenants: list[Mapping[str, Any]]) -> dict[str, Any]:
"""Build a read-only product / route / source-control asset view for tenants."""
public_gateway = _load_committed_snapshot(_PUBLIC_GATEWAY_INVENTORY_SNAPSHOT)
source_control = _load_committed_snapshot(_SOURCE_CONTROL_READINESS_SNAPSHOT)
public_routes = _build_route_assets(public_gateway)
source_repos = _build_source_repo_assets(source_control)
public_route_domains = {asset["domain"] for asset in public_routes}
source_keys = {asset["source_key"] for asset in source_repos}
products: list[dict[str, Any]] = []
for surface in _CANONICAL_PRODUCT_SURFACES:
public_route_count = len(
[domain for domain in surface["public_routes"] if domain in public_route_domains]
)
source_repo_count = len(
[source for source in surface["source_keys"] if source in source_keys]
)
missing_public_routes = [
domain for domain in surface["public_routes"] if domain not in public_route_domains
]
products.append(
{
**surface,
"public_route_count": public_route_count,
"source_repo_count": source_repo_count,
"missing_public_routes": missing_public_routes,
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"runtime_gate_count": 0,
"action_button_count": 0,
}
)
source_summary = source_control.get("summary") or {}
gateway_summary = public_gateway.get("summary") or {}
return {
"schema_version": "awooop_tenant_asset_inventory_v1",
"mode": "read_only_global_asset_inventory",
"evidence_refs": [
_PUBLIC_GATEWAY_INVENTORY_SNAPSHOT,
_SOURCE_CONTROL_READINESS_SNAPSHOT,
"docs/security/SECURITY-MIRROR-STATUS-ROLLUP.md",
],
"summary": {
"tenant_table_count": len(tenants),
"product_surface_count": len(products),
"public_route_count": len(public_routes),
"public_gateway_snapshot_route_count": int(
gateway_summary.get("route_impact_count") or 0
),
"source_candidate_repo_count": int(
source_summary.get("candidate_repo_count") or len(source_repos)
),
"source_in_scope_repo_count": int(source_summary.get("in_scope_repo_count") or 0),
"source_primary_ready_count": int(source_summary.get("primary_ready_count") or 0),
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"runtime_gate_count": 0,
"action_button_count": 0,
},
"products": products,
"public_routes": public_routes,
"source_repos": source_repos,
"boundaries": [
"read_only_inventory_only=true",
"owner_response_received_count=0",
"owner_response_accepted_count=0",
"runtime_execution_authorized=false",
"active_runtime_gate_count=0",
"action_buttons_allowed=false",
"repo_creation_authorized=false",
"refs_sync_authorized=false",
"workflow_modification_authorized=false",
"public_route_change_authorized=false",
],
}
async def list_tenants() -> dict[str, Any]:
"""列出所有 AwoooP 租戶Operator Console不依 RLS 過濾)。"""
async with get_db_context("awoooi") as db:
@@ -158,7 +609,11 @@ async def list_tenants() -> dict[str, Any]:
}
for r in rows
]
return {"tenants": tenants, "total": len(tenants)}
return {
"tenants": tenants,
"total": len(tenants),
"asset_inventory": build_tenant_asset_inventory(tenants),
}
# =============================================================================