`cloud/openapi.yaml` was a hand-written contract for a product with five served routes and 151 that answer `not found`. It is now a verbatim copy of hanzoai/cloud's own woven document, pulled by `sync.py` from that repo's origin/main (34663c49). Cloud regenerates that document from its router and fails its own build on any diff, so it cannot describe a route the binary does not serve — the property no spec here has — which is why `merge.py` merges it LAST and lets it WIN every route an authored spec also claims (298), explicitly rather than by where `cloud` falls in the alphabet. The document that every SDK, doc site and tool generates from goes 1132 -> 1737 paths, 1519 -> 2478 operations, and 699 -> 1217 operations carrying a description — prose lifted from the handlers' Go doc comments, not written twice. `tags` and `x-tagGroups` now describe the tags OPERATIONS carry (263, of which 213 described, cloud's being the owning package's synopsis) instead of the 55 spec directories, four of which any operation carried. The registry still decides the movements: a tag's group is the domain of the service that introduced it. Three defects that reached generators are fixed where codegen identity already lives. An operation with no `responses` (668, cloud's untyped routes) gets a `default` saying exactly that, rather than a schema nobody knows. An operation with no tag takes its service's name, rather than `DefaultApi`. And `cloud_get_v1_pricing-policy` and `cloud_get_v1_pricing_policy` — two real routes, distinct strings, one Go type once the generator strips punctuation — are separated. openapi-generator 7.14.0: 0 errors, 0 warnings (was 35 errors, 68 warnings); go and typescript-axios both generate; the Go client compiles. Six route collisions surfaced the moment the merge stopped resolving them by alphabet, and every one was duplicate authoring. `/health` was declared by nine specs and answers with the SPA at api.hanzo.ai, while `/healthz` answers JSON: it is gateway's alone now. `observe`, `app` and `product` declared only other services' routes — o11y's, projects', vector's and search-docs' — and are collapsed. Every route they held is still in the document, from cloud. `generated/hanzo.json` is deleted. Measuring hanzo.yaml against a document hanzo.yaml now contains is tautology, and a second, older copy of one emission is the drift this change exists to end. `flows.yaml` is new: hello, chat, money, store, agent, tools — six journeys naming the exact operationIds a client calls and what each step asserts, so every SDK's examples are the SAME journeys. merge.py refuses to write the document if a flow names an operation it does not have, and three flows record a substitution where the obvious route is not the served one. Co-authored-by: Hanzo Dev <[email protected]>
83 lines
3.5 KiB
Python
83 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Pull hanzoai/cloud's woven document into this repo and re-merge.
|
|
|
|
python3 sync.py # resync, then run merge.py
|
|
python3 sync.py --check # fail if cloud/openapi.yaml is stale
|
|
python3 sync.py --cloud ~/work/hanzo/cloud
|
|
|
|
`cloud/openapi.yaml` is the ONE spec here that is not authored here. hanzoai/cloud
|
|
weaves it from its own router — every app projects itself, the weave composes the
|
|
projections, and a drift gate there regenerates from source and fails on any diff
|
|
— so it cannot describe a route the binary does not serve. That is the property
|
|
no hand-written spec has, and it is why `merge.py` lets this file win wherever it
|
|
and an authored spec describe the same route.
|
|
|
|
A copy is only true at the moment it is made, so making it is a command and not a
|
|
procedure: this reads `origin/main:openapi.yaml` out of a cloud checkout (after
|
|
fetching, so a stale worktree cannot be mistaken for the branch) and writes it
|
|
here byte-for-byte. A resync that changes nothing produces no diff; the one that
|
|
does shows exactly what the binary started or stopped serving.
|
|
|
|
Nothing is ever written back to hanzoai/cloud. The traffic is one-way by
|
|
construction: this repo reads a ref, and the SDKs read `hanzo.yaml`.
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
SOURCE = "openapi.yaml" # the path inside hanzoai/cloud
|
|
TARGET = os.path.join(ROOT, "cloud", "openapi.yaml")
|
|
REF = "origin/main"
|
|
CLOUD = os.environ.get("CLOUD_DIR") or os.path.join(os.path.dirname(ROOT), "cloud")
|
|
|
|
|
|
def git(repo, *args):
|
|
r = subprocess.run(["git", "-C", repo, *args], capture_output=True, text=True)
|
|
if r.returncode:
|
|
sys.exit(f"sync: git {' '.join(args)} failed in {repo}\n{r.stderr.strip()}")
|
|
return r.stdout
|
|
|
|
|
|
def repository(path):
|
|
"""A git checkout, worktrees included — where `.git` is a file, not a dir."""
|
|
return subprocess.run(["git", "-C", path, "rev-parse", "--git-dir"],
|
|
capture_output=True).returncode == 0
|
|
|
|
|
|
def woven(repo):
|
|
"""`origin/main:openapi.yaml` from a hanzoai/cloud checkout, and its commit."""
|
|
if not repository(repo):
|
|
sys.exit(f"sync: no hanzoai/cloud checkout at {repo}\n"
|
|
f" git clone [email protected]:hanzoai/cloud {repo}\n"
|
|
f" (or point --cloud / CLOUD_DIR at one)")
|
|
git(repo, "fetch", "--quiet", "origin", "main")
|
|
return git(repo, "show", f"{REF}:{SOURCE}"), git(repo, "rev-parse", "--short", REF).strip()
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--cloud", default=CLOUD, help=f"hanzoai/cloud checkout (default {CLOUD})")
|
|
ap.add_argument("--check", action="store_true",
|
|
help="report drift and exit non-zero; write nothing")
|
|
a = ap.parse_args()
|
|
|
|
spec, commit = woven(a.cloud)
|
|
have = open(TARGET).read() if os.path.exists(TARGET) else ""
|
|
if spec == have:
|
|
print(f"cloud/openapi.yaml is {REF} ({commit}) — nothing to sync")
|
|
return 0
|
|
if a.check:
|
|
print(f"cloud/openapi.yaml differs from {REF} ({commit}) — run `python3 sync.py`")
|
|
return 1
|
|
|
|
open(TARGET, "w").write(spec)
|
|
print(f"cloud/openapi.yaml <- hanzoai/cloud {REF} ({commit})", flush=True)
|
|
return subprocess.call([sys.executable, os.path.join(ROOT, "merge.py")])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|