Spaces:
Sleeping
Sleeping
| """ | |
| rules/deployment_rules.py | |
| -------------------------- | |
| Decides *which* services are active and how they relate to each other, | |
| based on a user's InfraConfig. This module contains no rendering logic | |
| (no Jinja2, no YAML, no Mermaid syntax) — it only answers questions like | |
| "what does the request path look like?" and "what should the security | |
| report flag?". Generators consume these decisions and render them into | |
| their respective formats. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Literal | |
| from utils.config import InfraConfig | |
| NodeKind = Literal["entry", "network", "compute", "cache", "database", "vector", "monitoring", "cicd"] | |
| class ServiceNode: | |
| """A single box in the architecture graph.""" | |
| id: str | |
| label: str | |
| kind: NodeKind | |
| class ServiceEdge: | |
| """A connection between two ServiceNodes.""" | |
| source: str | |
| target: str | |
| style: Literal["solid", "dashed"] = "solid" | |
| label: str = "" | |
| class ArchitectureGraph: | |
| nodes: list[ServiceNode] = field(default_factory=list) | |
| edges: list[ServiceEdge] = field(default_factory=list) | |
| def build_architecture_graph(config: InfraConfig) -> ArchitectureGraph: | |
| """ | |
| Build the full architecture graph (nodes + edges) for a given config. | |
| Main request path (always present, top to bottom): | |
| Client -> Load Balancer -> Deployment Target -> App Framework | |
| Then, only if the user selected them (i.e. not "None"), each data | |
| service connects DIRECTLY from App Framework — as independent, | |
| parallel dependencies, not a chain: | |
| App Framework -> Cache | |
| App Framework -> Database | |
| App Framework -> Vector Database | |
| (A prior version chained these off each other — App -> Cache -> | |
| Database -> Vector Database — which is topologically wrong: it | |
| implies, for example, that the vector database sits between the app | |
| and the relational database. Since this graph feeds both the Mermaid | |
| diagram and the ArchitectureAdvisor's prompt, that wrong shape was | |
| visible in the diagram and could mislead the AI's reasoning about the | |
| actual request path.) | |
| Side branches (dashed, since they're not on the live request path): | |
| App Framework -> Monitoring | |
| CI/CD -> Deployment Target (deploy pipeline, not user traffic) | |
| """ | |
| nodes: list[ServiceNode] = [ | |
| ServiceNode("client", "Client", "entry"), | |
| ServiceNode("lb", "Load Balancer", "network"), | |
| ServiceNode("deploy_target", config.deployment_target, "compute"), | |
| ServiceNode("app", config.app_framework, "compute"), | |
| ] | |
| edges: list[ServiceEdge] = [ | |
| ServiceEdge("client", "lb"), | |
| ServiceEdge("lb", "deploy_target"), | |
| ServiceEdge("deploy_target", "app"), | |
| ] | |
| # Data-layer dependencies — each is an independent, parallel child of | |
| # "app", only wired in if the user actually chose one. Order in this | |
| # tuple only affects node/edge list ordering (cosmetic), not topology. | |
| for field_value, node_id, kind in ( | |
| (config.cache, "cache", "cache"), | |
| (config.database, "database", "database"), | |
| (config.vector_db, "vector_db", "vector"), | |
| ): | |
| if field_value and field_value != "None": | |
| nodes.append(ServiceNode(node_id, field_value, kind)) | |
| edges.append(ServiceEdge("app", node_id)) | |
| # Side branches | |
| if config.monitoring and config.monitoring != "None": | |
| nodes.append(ServiceNode("monitoring", config.monitoring, "monitoring")) | |
| edges.append(ServiceEdge("app", "monitoring", style="dashed", label="metrics")) | |
| if config.cicd and config.cicd != "None": | |
| nodes.append(ServiceNode("cicd", config.cicd, "cicd")) | |
| edges.append(ServiceEdge("cicd", "deploy_target", style="dashed", label="deploy")) | |
| return ArchitectureGraph(nodes=nodes, edges=edges) | |
| def active_data_services(config: InfraConfig) -> list[str]: | |
| """ | |
| Convenience helper for generators that just need the list of active | |
| data-layer services (used by docker_generator / kubernetes_generator | |
| in later steps to decide which containers/manifests to emit). | |
| """ | |
| services = [] | |
| if config.cache and config.cache != "None": | |
| services.append(config.cache) | |
| if config.database and config.database != "None": | |
| services.append(config.database) | |
| if config.vector_db and config.vector_db != "None": | |
| services.append(config.vector_db) | |
| return services | |
| def high_availability_requested(config: InfraConfig) -> bool: | |
| """Single source of truth for HA branching across generators.""" | |
| return config.high_availability | |