617 lines
25 KiB
Python
617 lines
25 KiB
Python
"""Cross-product MCP control plane catalog and runtime readback.
|
|
|
|
The service deliberately separates committed desired/source inventory from live
|
|
runtime evidence. Marketplace directories are discovery inputs only; this code
|
|
never installs packages, resolves credentials, calls a tool, writes RAG data, or
|
|
promotes an external candidate.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import structlog
|
|
from sqlalchemy import text
|
|
|
|
from src.db.base import get_db_context
|
|
from src.plugins.mcp.registry import get_provider_registry
|
|
from src.services.mcp_federation_service import collect_mcp_federation_readback
|
|
from src.services.mcp_tool_registry import get_mcp_tool_registry
|
|
from src.services.mcp_version_lifecycle_service import (
|
|
collect_mcp_version_lifecycle_readback,
|
|
)
|
|
from src.services.snapshot_paths import default_operations_dir
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
SCHEMA_VERSION = "mcp_control_plane_overview_v1"
|
|
CATALOG_SCHEMA_VERSION = "mcp_control_plane_catalog_v1"
|
|
TZ_TAIPEI = ZoneInfo("Asia/Taipei")
|
|
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
|
|
_CATALOG_FILE = "mcp-control-plane-catalog.snapshot.json"
|
|
_EXPECTED_PRODUCT_IDS = {
|
|
"awoooi",
|
|
"ewoooc",
|
|
"2026fifa",
|
|
"agent-bounty-protocol",
|
|
"awooogo",
|
|
"stockplatform-v2",
|
|
"vibework",
|
|
"momo-pro-system",
|
|
"tsenyang-website",
|
|
"vtuber",
|
|
"bitan-pharmacy",
|
|
"clawbot-openclaw",
|
|
}
|
|
|
|
|
|
def load_mcp_control_plane_catalog(
|
|
operations_dir: Path | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Load and validate the committed 12-product MCP catalog."""
|
|
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
|
|
path = directory / _CATALOG_FILE
|
|
with path.open(encoding="utf-8") as handle:
|
|
payload = json.load(handle)
|
|
if not isinstance(payload, dict):
|
|
raise ValueError(f"{path}: expected JSON object")
|
|
_validate_catalog(payload, str(path))
|
|
return payload
|
|
|
|
|
|
def build_mcp_control_plane_overview(
|
|
*,
|
|
catalog: dict[str, Any],
|
|
product_matrix: dict[str, Any],
|
|
runtime: dict[str, Any] | None = None,
|
|
generated_at: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Join catalog desired state with source-control and runtime truth."""
|
|
_validate_catalog(catalog, "catalog")
|
|
matrix_rows = {
|
|
str(row.get("product_id")): row
|
|
for row in _dict_rows(product_matrix.get("products"))
|
|
if row.get("product_id")
|
|
}
|
|
if set(matrix_rows) != _EXPECTED_PRODUCT_IDS:
|
|
raise ValueError(
|
|
"product governance matrix must contain the exact 12 governed products: "
|
|
f"missing={sorted(_EXPECTED_PRODUCT_IDS - set(matrix_rows))}, "
|
|
f"unexpected={sorted(set(matrix_rows) - _EXPECTED_PRODUCT_IDS)}"
|
|
)
|
|
|
|
capability_rows = _dict_rows(catalog.get("capabilities"))
|
|
capability_index = {str(row["capability_id"]): row for row in capability_rows}
|
|
products: list[dict[str, Any]] = []
|
|
for desired in _dict_rows(catalog.get("products")):
|
|
product_id = str(desired["product_id"])
|
|
source = matrix_rows[product_id]
|
|
existing_ids = _strings(desired.get("existing_capability_ids"))
|
|
recommended_ids = _strings(desired.get("recommended_capability_ids"))
|
|
rejected_ids = _strings(desired.get("rejected_capability_ids"))
|
|
products.append(
|
|
{
|
|
"product_id": product_id,
|
|
"canonical_repo": str(source.get("canonical_repo") or ""),
|
|
"source_control_status": str(
|
|
source.get("source_control_status") or "unknown"
|
|
),
|
|
"prod_branch": str(source.get("prod_branch") or "main"),
|
|
"prod_sha_short": str(source.get("prod_sha_short") or ""),
|
|
"visibility_readback": str(
|
|
source.get("visibility_readback") or "unknown"
|
|
),
|
|
"inventory_state": str(desired.get("inventory_state") or "unknown"),
|
|
"existing_capability_ids": existing_ids,
|
|
"recommended_capability_ids": recommended_ids,
|
|
"rejected_capability_ids": rejected_ids,
|
|
"existing_capabilities": [
|
|
_capability_summary(capability_index[item]) for item in existing_ids
|
|
],
|
|
"recommended_capabilities": [
|
|
_capability_summary(capability_index[item])
|
|
for item in recommended_ids
|
|
],
|
|
"rejected_capabilities": [
|
|
_capability_summary(capability_index[item]) for item in rejected_ids
|
|
],
|
|
"blockers": _strings(desired.get("blockers")),
|
|
}
|
|
)
|
|
|
|
runtime_payload = runtime or {
|
|
"requested": False,
|
|
"status": "not_requested",
|
|
"provider_registry": {
|
|
"status": "not_requested",
|
|
"provider_count": 0,
|
|
"provider_names": [],
|
|
"tool_count": 0,
|
|
},
|
|
"gateway_audit_24h": {
|
|
"status": "not_requested",
|
|
"call_count": 0,
|
|
"success_count": 0,
|
|
"blocked_count": 0,
|
|
"failed_count": 0,
|
|
},
|
|
"rag": {
|
|
"status": "not_requested",
|
|
"total_chunks": 0,
|
|
"sources": 0,
|
|
},
|
|
"federated_runtime": {
|
|
"status": "not_requested",
|
|
"receipts": [],
|
|
"verified_fresh_receipt_count": 0,
|
|
"expected_receipt_count": 2,
|
|
"blockers": [],
|
|
},
|
|
"federated_product_runtime_receipt_count": 0,
|
|
"federated_product_runtime_receipt_expected": 12,
|
|
}
|
|
federation_runtime = runtime_payload.get("federated_runtime")
|
|
federation_runtime = (
|
|
federation_runtime if isinstance(federation_runtime, dict) else {}
|
|
)
|
|
federation_receipt_index = {
|
|
str(row.get("product_id")): row
|
|
for row in _dict_rows(federation_runtime.get("receipts"))
|
|
if row.get("product_id") and row.get("fresh") is True
|
|
}
|
|
for product in products:
|
|
receipt = federation_receipt_index.get(product["product_id"])
|
|
product["federated_runtime_receipt"] = receipt
|
|
if receipt is None:
|
|
continue
|
|
product["blockers"] = sorted(
|
|
set(product["blockers"])
|
|
.difference({"awoooi_federated_runtime_readback_missing"})
|
|
.union(_strings(receipt.get("blockers")))
|
|
)
|
|
product["inventory_state"] = (
|
|
"federated_runtime_readback_verified"
|
|
if receipt.get("health_status") == "ready"
|
|
else "federated_runtime_readback_partial_degraded"
|
|
)
|
|
lifecycle_runtime = runtime_payload.get("version_lifecycle")
|
|
lifecycle_runtime = lifecycle_runtime if isinstance(lifecycle_runtime, dict) else {}
|
|
version_lifecycle = dict(catalog["version_lifecycle"])
|
|
version_lifecycle["runtime"] = lifecycle_runtime
|
|
lifecycle_status = str(lifecycle_runtime.get("status") or "not_requested")
|
|
if lifecycle_status == "ready" and lifecycle_runtime.get("latest_run"):
|
|
version_lifecycle["runtime_state"] = "running_fail_closed"
|
|
elif lifecycle_status == "pending_first_run":
|
|
version_lifecycle["runtime_state"] = "scheduled_pending_first_receipt"
|
|
elif lifecycle_status == "degraded":
|
|
version_lifecycle["runtime_state"] = "degraded_with_safe_next_action"
|
|
external_rows = [
|
|
row for row in capability_rows if row.get("source_type") == "external"
|
|
]
|
|
promotion_ready = sum(
|
|
1 for row in external_rows if row.get("deployment_allowed") is True
|
|
)
|
|
detected_products = sum(
|
|
1
|
|
for row in products
|
|
if row["inventory_state"]
|
|
not in {
|
|
"no_committed_mcp_runtime_detected",
|
|
"no_federated_mcp_inventory_readback",
|
|
}
|
|
)
|
|
quarantined_products = sum(
|
|
1
|
|
for row in products
|
|
if "quarantine" in row["inventory_state"]
|
|
or "remediation_required" in row["inventory_state"]
|
|
)
|
|
|
|
active_blockers = set(_strings(version_lifecycle.get("active_blockers")))
|
|
active_blockers.update(_strings(lifecycle_runtime.get("blockers")))
|
|
active_blockers.update(_strings(federation_runtime.get("blockers")))
|
|
active_blockers.update(
|
|
blocker
|
|
for product in products
|
|
for blocker in _strings(product.get("blockers"))
|
|
)
|
|
active_blockers.update(
|
|
{
|
|
"external_candidate_immutable_supply_chain_receipts_missing",
|
|
"external_rag_quarantine_promotion_verifier_missing",
|
|
"distributed_gateway_rate_limit_receipt_missing",
|
|
}
|
|
)
|
|
federated_receipt_count = int(
|
|
runtime_payload.get("federated_product_runtime_receipt_count") or 0
|
|
)
|
|
federated_receipt_expected = int(
|
|
runtime_payload.get("federated_product_runtime_receipt_expected") or 12
|
|
)
|
|
if federated_receipt_count < federated_receipt_expected:
|
|
active_blockers.add("cross_product_runtime_federation_receipts_missing")
|
|
rag = (
|
|
runtime_payload.get("rag")
|
|
if isinstance(runtime_payload.get("rag"), dict)
|
|
else {}
|
|
)
|
|
if rag.get("total_chunks") == 0:
|
|
active_blockers.add("internal_rag_index_empty")
|
|
if runtime_payload.get("status") not in {"ready", "not_requested"}:
|
|
active_blockers.add("mcp_runtime_readback_degraded")
|
|
|
|
source_summary = product_matrix.get("summary")
|
|
source_summary = source_summary if isinstance(source_summary, dict) else {}
|
|
return {
|
|
"schema_version": SCHEMA_VERSION,
|
|
"generated_at": generated_at
|
|
or datetime.now(TZ_TAIPEI).isoformat(timespec="seconds"),
|
|
"status": "partial_degraded_with_safe_next_action",
|
|
"summary": {
|
|
"product_count": len(products),
|
|
"expected_product_count": 12,
|
|
"source_control_ready_product_count": int(
|
|
source_summary.get("source_control_ready_product_count") or 0
|
|
),
|
|
"mcp_source_detected_product_count": detected_products,
|
|
"mcp_source_gap_product_count": len(products) - detected_products,
|
|
"quarantined_or_remediation_product_count": quarantined_products,
|
|
"capability_count": len(capability_rows),
|
|
"external_candidate_count": len(external_rows),
|
|
"external_promotion_ready_count": promotion_ready,
|
|
"github_in_scope_count": 0,
|
|
"active_blocker_count": len(active_blockers),
|
|
"runtime_provider_count": int(
|
|
_nested(runtime_payload, "provider_registry", "provider_count") or 0
|
|
),
|
|
"runtime_tool_count": int(
|
|
_nested(runtime_payload, "provider_registry", "tool_count") or 0
|
|
),
|
|
"federated_product_runtime_receipt_count": federated_receipt_count,
|
|
"federated_product_runtime_receipt_expected": federated_receipt_expected,
|
|
},
|
|
"architecture": catalog["architecture"],
|
|
"security_policy": catalog["security_policy"],
|
|
"supply_chain_policy": catalog["supply_chain_policy"],
|
|
"version_lifecycle": version_lifecycle,
|
|
"runtime": runtime_payload,
|
|
"catalog_sources": catalog["source_policy"]["catalog_sources"],
|
|
"capabilities": capability_rows,
|
|
"products": products,
|
|
"active_blockers": sorted(active_blockers),
|
|
"next_actions": [
|
|
(
|
|
"resolve_ewoooc_momo_runtime_blockers_and_expand_next_product_federation_receipts"
|
|
if len(federation_receipt_index) == 2
|
|
else "federate_ewoooc_and_momo_read_only_inventory_health_and_version_receipts"
|
|
),
|
|
"disable_agent_bounty_github_and_prompt_injection_tool_path",
|
|
"remove_bitan_github_external_provider_from_effective_policy",
|
|
"promote_first_internally_mirrored_candidate_through_replay_shadow_and_canary",
|
|
"mirror_and_pin_each_approved_external_candidate_before_shadow",
|
|
"wire_quarantine_scan_citation_verifier_and_governed_rag_promotion",
|
|
],
|
|
"operation_boundaries": {
|
|
"read_only_api_allowed": True,
|
|
"public_response_redacted_aggregate_only": True,
|
|
"mutation_endpoint_exposed": False,
|
|
"operator_auth_required_for_future_mutation": True,
|
|
"normalized_version_receipt_write_allowed": True,
|
|
"external_install_allowed": False,
|
|
"external_tool_call_allowed": False,
|
|
"external_rag_write_allowed": False,
|
|
"github_allowed": False,
|
|
"secret_value_read_allowed": False,
|
|
"request_or_response_body_storage_allowed": False,
|
|
"production_upgrade_allowed": False,
|
|
},
|
|
"source_refs": {
|
|
"catalog": f"docs/operations/{_CATALOG_FILE}",
|
|
"product_matrix": "GET /api/v1/agents/product-governance-matrix-readback",
|
|
"runtime": "live provider registry, MCP tool registry, gateway audit aggregate, RAG stats, durable version lifecycle receipts, and normalized federated product receipts",
|
|
},
|
|
}
|
|
|
|
|
|
async def collect_mcp_control_plane_runtime() -> dict[str, Any]:
|
|
"""Collect aggregate-only runtime evidence without request or response bodies."""
|
|
blockers: list[str] = []
|
|
lifecycle_payload, federation_payload = await asyncio.gather(
|
|
collect_mcp_version_lifecycle_readback(),
|
|
collect_mcp_federation_readback(),
|
|
)
|
|
if lifecycle_payload.get("status") == "degraded":
|
|
blockers.append("version_lifecycle_runtime_readback_degraded")
|
|
if federation_payload.get("status") not in {"ready", "not_requested"}:
|
|
blockers.append("federation_runtime_readback_degraded")
|
|
try:
|
|
provider_registry = get_provider_registry()
|
|
tool_registry = get_mcp_tool_registry()
|
|
provider_payload = {
|
|
"status": "registered",
|
|
"provider_count": len(provider_registry.names()),
|
|
"provider_names": sorted(provider_registry.names()),
|
|
"tool_count": tool_registry.tool_count,
|
|
}
|
|
except Exception as exc:
|
|
logger.warning(
|
|
"mcp_control_plane_provider_registry_readback_failed",
|
|
error_type=type(exc).__name__,
|
|
)
|
|
blockers.append("provider_registry_readback_failed")
|
|
provider_payload = {
|
|
"status": "degraded",
|
|
"provider_count": 0,
|
|
"provider_names": [],
|
|
"tool_count": 0,
|
|
}
|
|
|
|
try:
|
|
# Control-plane readback is deliberately scoped to the canonical AWOOOI
|
|
# tenant. Query the governed pgvector table directly so repository
|
|
# fallback-to-zero cannot disguise a DB/RLS failure as a healthy empty
|
|
# index. Only aggregate counts leave this service.
|
|
async with get_db_context("awoooi") as db:
|
|
result = await db.execute(
|
|
text(
|
|
"""
|
|
SELECT
|
|
COUNT(*)::int AS total_chunks,
|
|
COUNT(DISTINCT source)::int AS sources
|
|
FROM rag_chunks
|
|
"""
|
|
)
|
|
)
|
|
rag_stats = result.mappings().one()
|
|
rag_payload = {
|
|
"status": "ready",
|
|
"total_chunks": int(rag_stats.get("total_chunks") or 0),
|
|
"sources": int(rag_stats.get("sources") or 0),
|
|
"scope": "project:awoooi",
|
|
}
|
|
except Exception as exc:
|
|
logger.warning(
|
|
"mcp_control_plane_rag_readback_failed",
|
|
error_type=type(exc).__name__,
|
|
)
|
|
blockers.append("rag_stats_readback_failed")
|
|
rag_payload = {
|
|
"status": "degraded",
|
|
"total_chunks": 0,
|
|
"sources": 0,
|
|
"scope": "project:awoooi",
|
|
}
|
|
|
|
try:
|
|
async with get_db_context("awoooi") as db:
|
|
result = await db.execute(
|
|
text(
|
|
"""
|
|
SELECT
|
|
COUNT(*)::int AS call_count,
|
|
COUNT(*) FILTER (WHERE result_status = 'success')::int AS success_count,
|
|
COUNT(*) FILTER (WHERE result_status = 'blocked')::int AS blocked_count,
|
|
COUNT(*) FILTER (WHERE result_status IN ('failed', 'timeout'))::int AS failed_count
|
|
FROM awooop_mcp_gateway_audit
|
|
WHERE created_at >= NOW() - INTERVAL '24 hours'
|
|
"""
|
|
)
|
|
)
|
|
row = result.mappings().one()
|
|
gateway_payload = {
|
|
"status": "ready",
|
|
"call_count": int(row["call_count"] or 0),
|
|
"success_count": int(row["success_count"] or 0),
|
|
"blocked_count": int(row["blocked_count"] or 0),
|
|
"failed_count": int(row["failed_count"] or 0),
|
|
"body_storage": "hash_only",
|
|
"scope": "project:awoooi",
|
|
}
|
|
except Exception as exc:
|
|
logger.warning(
|
|
"mcp_control_plane_gateway_audit_readback_failed",
|
|
error_type=type(exc).__name__,
|
|
)
|
|
blockers.append("gateway_audit_readback_failed")
|
|
gateway_payload = {
|
|
"status": "degraded",
|
|
"call_count": 0,
|
|
"success_count": 0,
|
|
"blocked_count": 0,
|
|
"failed_count": 0,
|
|
"body_storage": "hash_only",
|
|
"scope": "project:awoooi",
|
|
}
|
|
|
|
return {
|
|
"requested": True,
|
|
"status": "ready" if not blockers else "degraded",
|
|
"provider_registry": provider_payload,
|
|
"gateway_audit_24h": gateway_payload,
|
|
"rag": rag_payload,
|
|
"version_lifecycle": lifecycle_payload,
|
|
"federated_runtime": federation_payload,
|
|
"federated_product_runtime_receipt_count": int(
|
|
federation_payload.get("verified_fresh_receipt_count") or 0
|
|
),
|
|
"federated_product_runtime_receipt_expected": 12,
|
|
"blockers": sorted(blockers),
|
|
}
|
|
|
|
|
|
def _validate_catalog(payload: dict[str, Any], label: str) -> None:
|
|
if payload.get("schema_version") != CATALOG_SCHEMA_VERSION:
|
|
raise ValueError(f"{label}: invalid schema_version")
|
|
architecture = payload.get("architecture") or {}
|
|
if (
|
|
architecture.get("decision")
|
|
!= "single_control_plane_reuse_existing_gateway_registry_rag"
|
|
):
|
|
raise ValueError(f"{label}: duplicate gateway/RAG architecture is forbidden")
|
|
if architecture.get("separate_gateway_allowed") is not False:
|
|
raise ValueError(f"{label}: separate gateway must remain false")
|
|
if architecture.get("separate_rag_database_allowed") is not False:
|
|
raise ValueError(f"{label}: separate RAG database must remain false")
|
|
|
|
source_policy = payload.get("source_policy") or {}
|
|
for key in (
|
|
"github_allowed",
|
|
"github_actions_allowed",
|
|
"github_artifact_sources_allowed",
|
|
):
|
|
if source_policy.get(key) is not False:
|
|
raise ValueError(f"{label}: {key} must remain false")
|
|
security = payload.get("security_policy") or {}
|
|
if security.get("external_rag_auto_write_allowed") is not False:
|
|
raise ValueError(f"{label}: external RAG auto-write must remain false")
|
|
if security.get("gateway_request_body_storage") != "forbidden_hash_only":
|
|
raise ValueError(f"{label}: gateway request body policy must be hash-only")
|
|
|
|
capabilities = _dict_rows(payload.get("capabilities"))
|
|
capability_ids = [str(row.get("capability_id") or "") for row in capabilities]
|
|
if not all(capability_ids) or len(capability_ids) != len(set(capability_ids)):
|
|
raise ValueError(f"{label}: capability IDs must be non-empty and unique")
|
|
capability_id_set = set(capability_ids)
|
|
github_rows = [
|
|
row for row in capabilities if row.get("capability_id") == "rejected.github"
|
|
]
|
|
if len(github_rows) != 1 or github_rows[0].get("decision") != "rejected":
|
|
raise ValueError(f"{label}: GitHub capability must be explicitly rejected")
|
|
for row in capabilities:
|
|
if row.get("source_type") != "external":
|
|
continue
|
|
if row.get("deployment_allowed") is True and any(
|
|
row.get(field) in {None, "", "missing", "unresolved"}
|
|
for field in (
|
|
"version_state",
|
|
"digest_state",
|
|
"sbom_state",
|
|
"signature_state",
|
|
)
|
|
):
|
|
raise ValueError(
|
|
f"{label}: external capability {row.get('capability_id')} cannot deploy without immutable receipts"
|
|
)
|
|
if row.get("compatibility_state") != "protocol_schema_compatibility_verified":
|
|
continue
|
|
if row.get("replay_status") != "completed_protocol_schema_replay_verified":
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires a completed replay verifier receipt"
|
|
)
|
|
if row.get("replay_independent_verifier") is not True:
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires an independent verifier"
|
|
)
|
|
if not isinstance(row.get("replay_tool_count"), int) or int(
|
|
row["replay_tool_count"]
|
|
) <= 0:
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires a positive replay tool count"
|
|
)
|
|
if row.get("replay_process_exit_code") != 0 or row.get(
|
|
"replay_shutdown_mode"
|
|
) != "stdin_eof_clean_exit":
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires a clean MCP process terminal"
|
|
)
|
|
if row.get("replay_ephemeral_container_removed") is not True:
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires ephemeral container removal"
|
|
)
|
|
no_effect_fields = (
|
|
"replay_browser_started",
|
|
"replay_tool_call_performed",
|
|
"replay_navigation_performed",
|
|
"replay_external_rag_write_performed",
|
|
"replay_production_write_performed",
|
|
"replay_promotion_allowed",
|
|
)
|
|
if any(row.get(field) is not False for field in no_effect_fields):
|
|
raise ValueError(
|
|
f"{label}: compatibility replay must remain no-browser, no-tool, no-write, and no-promotion"
|
|
)
|
|
if row.get("deployment_allowed") is not False:
|
|
raise ValueError(
|
|
f"{label}: compatibility verification alone cannot enable deployment"
|
|
)
|
|
fixed_hex_fields = {
|
|
"replay_audit_commit": 40,
|
|
"replay_artifact_audit_commit": 40,
|
|
"artifact_latest_revalidation_audit_commit": 40,
|
|
"replay_controller_receipt_sha256": 64,
|
|
"replay_verifier_receipt_sha256": 64,
|
|
"replay_tool_name_set_sha256": 64,
|
|
"replay_tool_schema_manifest_sha256": 64,
|
|
}
|
|
if any(
|
|
not _is_lower_hex(row.get(field), length)
|
|
for field, length in fixed_hex_fields.items()
|
|
):
|
|
raise ValueError(
|
|
f"{label}: verified compatibility requires immutable audit and replay hashes"
|
|
)
|
|
if "compatibility_replay_not_executed" in _strings(row.get("blockers")):
|
|
raise ValueError(
|
|
f"{label}: verified compatibility cannot retain replay-not-executed blocker"
|
|
)
|
|
|
|
products = _dict_rows(payload.get("products"))
|
|
product_ids = {str(row.get("product_id") or "") for row in products}
|
|
if product_ids != _EXPECTED_PRODUCT_IDS or len(products) != 12:
|
|
raise ValueError(
|
|
f"{label}: catalog must contain exact 12 products; "
|
|
f"missing={sorted(_EXPECTED_PRODUCT_IDS - product_ids)}, "
|
|
f"unexpected={sorted(product_ids - _EXPECTED_PRODUCT_IDS)}"
|
|
)
|
|
for row in products:
|
|
refs = (
|
|
_strings(row.get("existing_capability_ids"))
|
|
+ _strings(row.get("recommended_capability_ids"))
|
|
+ _strings(row.get("rejected_capability_ids"))
|
|
)
|
|
unknown = sorted(set(refs) - capability_id_set)
|
|
if unknown:
|
|
raise ValueError(
|
|
f"{label}: product {row.get('product_id')} references unknown capabilities {unknown}"
|
|
)
|
|
|
|
|
|
def _capability_summary(row: dict[str, Any]) -> dict[str, Any]:
|
|
return {
|
|
"capability_id": row["capability_id"],
|
|
"title": row["title"],
|
|
"decision": row["decision"],
|
|
"risk_tier": row["risk_tier"],
|
|
"deployment_allowed": row["deployment_allowed"],
|
|
}
|
|
|
|
|
|
def _is_lower_hex(value: Any, length: int) -> bool:
|
|
return (
|
|
isinstance(value, str)
|
|
and len(value) == length
|
|
and all(character in "0123456789abcdef" for character in value)
|
|
)
|
|
|
|
|
|
def _dict_rows(value: Any) -> list[dict[str, Any]]:
|
|
if not isinstance(value, list):
|
|
return []
|
|
return [row for row in value if isinstance(row, dict)]
|
|
|
|
|
|
def _strings(value: Any) -> list[str]:
|
|
if not isinstance(value, list):
|
|
return []
|
|
return [str(item) for item in value if str(item)]
|
|
|
|
|
|
def _nested(payload: dict[str, Any], first: str, second: str) -> Any:
|
|
nested = payload.get(first)
|
|
return nested.get(second) if isinstance(nested, dict) else None
|