feat(openapi): unified master over all 36 services (regenerable via merge.py)

hanzo.yaml was only aggregating ~5 services. merge.py now regenerates it from
EVERY per-service spec: 1581 paths, 729 schemas, 0 dangling $refs. Component
names namespaced per service (<svc>_<Name>) so specs never clobber. Edit a
per-service <svc>/openapi.yaml and re-run merge.py — one unified /v1/ surface.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-01 12:46:08 -07:00
co-authored by Hanzo Dev
parent 1d11643f2a
commit d75abbdf27
2 changed files with 71306 additions and 90 deletions
+71206 -90
View File
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Regenerate the unified master `hanzo.yaml` from every per-service spec.
The master is the ONE unified OpenAPI surface for all Hanzo services. It is a
pure aggregation — edit a per-service `<svc>/openapi.yaml`, then run this:
python3 merge.py
Every service is exposed at `https://api.hanzo.ai/v1/<service>/*`, so paths are
already namespaced and never collide. Component schemas/responses/parameters ARE
namespaced here (`<svc>_<Name>`) with their `$ref`s rewritten to match, so two
services can both define `Error`/`User`/etc. without clobbering each other.
"""
import os
import sys
import yaml
ROOT = os.path.dirname(os.path.abspath(__file__))
def prefix(node, svc):
"""Rewrite local component $refs to the namespaced `<svc>_` form."""
if isinstance(node, dict):
out = {}
for k, v in node.items():
if k == "$ref" and isinstance(v, str) and v.startswith("#/components/"):
kind = v.split("/")[2] # schemas | responses | parameters | ...
if kind in ("schemas", "responses", "parameters"):
name = "/".join(v.split("/")[3:])
out[k] = f"#/components/{kind}/{svc}_{name}"
continue
out[k] = prefix(v, svc)
return out
if isinstance(node, list):
return [prefix(x, svc) for x in node]
return node
def main():
services = sorted(
d for d in os.listdir(ROOT)
if os.path.isdir(os.path.join(ROOT, d))
and os.path.isfile(os.path.join(ROOT, d, "openapi.yaml"))
and d != "shared"
)
paths, schemas, responses, params, secschemes, tags = {}, {}, {}, {}, {}, []
for svc in services:
spec = yaml.safe_load(open(os.path.join(ROOT, svc, "openapi.yaml")))
comps = spec.get("components", {}) or {}
for p, item in (spec.get("paths", {}) or {}).items():
paths[p] = prefix(item, svc)
for n, x in (comps.get("schemas", {}) or {}).items():
schemas[f"{svc}_{n}"] = prefix(x, svc)
for n, x in (comps.get("responses", {}) or {}).items():
responses[f"{svc}_{n}"] = prefix(x, svc)
for n, x in (comps.get("parameters", {}) or {}).items():
params[f"{svc}_{n}"] = prefix(x, svc)
for n, x in (comps.get("securitySchemes", {}) or {}).items():
secschemes.setdefault(n, x)
tags.append({"name": svc, "description": spec.get("info", {}).get("title", svc)})
components = {
"securitySchemes": secschemes or {"bearerAuth": {"type": "http", "scheme": "bearer"}},
"schemas": dict(sorted(schemas.items())),
}
if responses:
components["responses"] = dict(sorted(responses.items()))
if params:
components["parameters"] = dict(sorted(params.items()))
out = {
"openapi": "3.1.0",
"info": {
"title": "Hanzo Cloud — Unified API",
"description": (
"The single unified OpenAPI surface for ALL Hanzo services, "
"aggregated from the per-service specs. Every route is "
"https://api.hanzo.ai/v1/<service>/*. Regenerated by merge.py — "
"edit the per-service <svc>/openapi.yaml, not this file."
),
"version": "1.0.0",
"contact": {"name": "Hanzo AI", "url": "https://hanzo.ai", "email": "[email protected]"},
"license": {"name": "Proprietary"},
},
"servers": [{"url": "https://api.hanzo.ai", "description": "Hanzo Gateway"}],
"tags": tags,
"security": [{"bearerAuth": []}],
"paths": dict(sorted(paths.items())),
"components": components,
}
yaml.dump(out, open(os.path.join(ROOT, "hanzo.yaml"), "w"),
default_flow_style=False, sort_keys=False, width=120)
print(f"merged {len(services)} services → {len(paths)} paths, "
f"{len(schemas)} schemas, {len(responses)} responses, {len(params)} params")
return 0
if __name__ == "__main__":
sys.exit(main())