decomposer: generate files for Create and commit a registry-ready single-agent repository that generates technical solution design documents and draw.io architecture diagrams from application requirements.

This commit is contained in:
2026-08-31 14:48:28 +00:00
parent de91ff188d
commit ca18e8db08
9 changed files with 288 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
"""Application landing zone design generator."""
from .generator import generate
__all__ = ["generate"]

View File

@@ -0,0 +1,26 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from .generator import generate
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Generate a TSD and editable draw.io architecture diagram")
parser.add_argument("requirements", help="JSON requirements file or plain-text requirements file")
parser.add_argument("--output-dir", default="artifacts")
args = parser.parse_args(argv)
source = Path(args.requirements).read_text(encoding="utf-8")
try:
requirements = json.loads(source)
except json.JSONDecodeError:
requirements = source
result = generate(requirements, args.output_dir)
print(json.dumps(result, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,116 @@
from __future__ import annotations
import hashlib
import html
import json
from pathlib import Path
from typing import Any
from .model import Requirements, normalize
def _bullets(items: list[str], empty: str = "To be confirmed") -> str:
return "\n".join(f"- {item}" for item in (items or [empty]))
def render_tsd(r: Requirements) -> str:
assumptions = r.extra or {"landing_zone": "A governed network, identity, logging, and security baseline is available."}
assumptions_md = "\n".join(f"- **{k}:** {json.dumps(v, ensure_ascii=False)}" for k, v in assumptions.items())
return f"""# Technical Solution Design: {r.name}
## 1. Purpose and scope
{r.summary or 'Define a secure, observable, and scalable application landing zone for the stated requirements.'}
## 2. Functional requirements
{_bullets(r.requirements)}
## 3. Actors and integrations
### Users and consuming systems
{_bullets(r.users)}
### External integrations
{_bullets(r.integrations)}
## 4. Proposed architecture
The application is deployed into a governed landing zone behind an edge entry point and application boundary. Identity, secrets, encryption, monitoring, audit logging, and policy enforcement are platform capabilities. Data stores remain private and are reached through controlled service paths. See `architecture.drawio` for the editable logical view.
### Logical components
| Component | Responsibility | Trust zone |
|---|---|---|
| Edge / API entry | TLS termination, routing, rate limiting | Public boundary |
| Application services | Business logic and API processing | Application |
| Identity and secrets | Authentication, authorization, key and secret lifecycle | Security |
| Data services | Durable application state and backups | Data |
| Observability | Metrics, logs, traces, alerting, audit evidence | Operations |
## 5. Security and compliance
{_bullets(r.security)}
Baseline controls include least-privilege identities, private data services, encryption in transit and at rest, centralized audit logs, secret rotation, vulnerability management, and separation of deployment roles.
## 6. Data design
{_bullets(r.data)}
Data classification, retention, residency, backup frequency, recovery point objective (RPO), and recovery time objective (RTO) must be confirmed before production approval.
## 7. Availability, resilience, and performance
- **Availability target:** {r.availability}
- **Deployment model:** {r.deployment}
- Use health checks, autoscaling where supported, multi-zone placement for production, tested backups, and defined failure-mode runbooks.
## 8. Network and deployment topology
Ingress is restricted to the edge boundary. Application-to-data traffic uses private routes and explicit security policies. Administrative access uses a controlled management path; direct public access to workloads and data stores is prohibited.
## 9. Operations
Define dashboards for golden signals, actionable alerts, centralized logs, deployment rollback, patching ownership, incident response, and periodic access reviews.
## 10. Risks and decisions
- Confirm workload classification, regulatory obligations, traffic estimates, and non-functional targets.
- Select concrete cloud services and sizing after a platform review; this design intentionally remains provider-neutral.
- Validate integration authentication, data contracts, and failure/retry behavior with each dependency.
## 11. Assumptions and open items
{assumptions_md}
"""
def _cell(cell_id: str, value: str, style: str, x: int, y: int, w: int, h: int, parent: str = "1") -> str:
return f'<mxCell id="{html.escape(cell_id)}" value="{html.escape(value)}" style="{style}" vertex="1" parent="{parent}"><mxGeometry x="{x}" y="{y}" width="{w}" height="{h}" as="geometry"/></mxCell>'
def render_drawio(r: Requirements) -> str:
title = html.escape(f"{r.name} - Logical Architecture")
box = "rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
boundary = "rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#f5f5f5;strokeColor=#666666;verticalAlign=top;align=left;spacingTop=8;"
edge = "rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;"
cells = [
_cell("public", "Public / consumer boundary", boundary, 20, 20, 900, 500),
_cell("application", "Application trust zone", boundary, 260, 100, 420, 330),
_cell("data", "Private data zone", boundary, 710, 100, 180, 330),
_cell("edge", "Edge / API entry", edge, 55, 220, 160, 60),
_cell("identity", "Identity & secrets", box, 300, 135, 170, 60),
_cell("service", "Application services", box, 300, 235, 170, 70),
_cell("observe", "Metrics / logs / audit", box, 300, 345, 170, 60),
_cell("store", "Database / object storage", box, 720, 235, 160, 70),
_cell("admin", "Controlled admin path", box, 55, 345, 160, 60),
'<mxCell id="e1" value="HTTPS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;" edge="1" parent="1" source="edge" target="service"><mxGeometry relative="1" as="geometry"/></mxCell>',
'<mxCell id="e2" value="AuthN/Z" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="service" target="identity"><mxGeometry relative="1" as="geometry"/></mxCell>',
'<mxCell id="e3" value="Private data path" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="service" target="store"><mxGeometry relative="1" as="geometry"/></mxCell>',
'<mxCell id="e4" value="Telemetry" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="service" target="observe"><mxGeometry relative="1" as="geometry"/></mxCell>',
]
return f'''<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2025-01-01T00:00:00.000Z" agent="application_landing_zone_agent" version="24.7.17" type="device"><diagram id="architecture" name="Architecture"><mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827"><root><mxCell id="0"/><mxCell id="1" value="{title}" vertex="0" parent="0"/>''' + ''.join(cells) + '''</root></mxGraphModel></diagram></mxfile>'''
def generate(requirements: dict[str, Any] | str, output_dir: str | Path) -> dict[str, Any]:
r = normalize(requirements)
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
tsd = render_tsd(r)
diagram = render_drawio(r)
(out / "technical-solution-design.md").write_text(tsd, encoding="utf-8")
(out / "architecture.drawio").write_text(diagram, encoding="utf-8")
digest = hashlib.sha256((tsd + diagram).encode()).hexdigest()
manifest = {"application": r.name, "artifacts": ["technical-solution-design.md", "architecture.drawio"], "sha256": digest}
(out / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
return manifest

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
@dataclass
class Requirements:
name: str = "Application"
summary: str = ""
requirements: list[str] = field(default_factory=list)
constraints: list[str] = field(default_factory=list)
users: list[str] = field(default_factory=list)
integrations: list[str] = field(default_factory=list)
security: list[str] = field(default_factory=list)
data: list[str] = field(default_factory=list)
availability: str = "To be confirmed"
deployment: str = "To be confirmed"
extra: dict[str, Any] = field(default_factory=dict)
def _list(value: Any) -> list[str]:
if value is None:
return []
if isinstance(value, list):
return [str(item) for item in value]
return [str(value)]
def normalize(raw: dict[str, Any] | str) -> Requirements:
if isinstance(raw, str):
return Requirements(name="Application", summary=raw, requirements=[raw])
known = {"name", "summary", "requirements", "constraints", "users", "integrations", "security", "data", "availability", "deployment"}
return Requirements(
name=str(raw.get("name", "Application")), summary=str(raw.get("summary", "")),
requirements=_list(raw.get("requirements")), constraints=_list(raw.get("constraints")),
users=_list(raw.get("users")), integrations=_list(raw.get("integrations")),
security=_list(raw.get("security")), data=_list(raw.get("data")),
availability=str(raw.get("availability", "To be confirmed")),
deployment=str(raw.get("deployment", "To be confirmed")),
extra={str(k): v for k, v in raw.items() if k not in known},
)