Files
zeekay 269cde6bd2 Generate dart into the repo that holds the dart client
The row pointed at hanzoai/dart-sdk, which is a 9KB landing page: four licence
files and a README saying the code lives in its language org and that nothing
is published from there. The client itself is hanzo-dart/sdk, 33MB of it, and
it had not been regenerated because the driver was writing next door.

hanzoai/dart-sdk is the shape the other ten should end up in.
2026-09-05 18:42:28 -07:00

1273 lines
77 KiB
YAML

# The SDK matrix — data only. The generator invocation is logic and lives once,
# in generate.py; nothing in this file is executable and nothing in an SDK repo
# duplicates it. `hanzoai/js-sdk` and `hanzoai/java-sdk` are the shape: their
# scripts/generate.sh carries NO flags, it execs this driver. js-sdk removed its
# own copy after the two disagreed about modelPackage and about whether the
# client lands in src/ or src/cloud/, which built an orphan second copy of all
# 2143 files.
#
# Every row is one language projection of THE DOCUMENT — hanzo-inc/cloud's
# `openapi.yaml` at the ref that client's own `.spec-lock` pins. There is no
# `spec:` key and no fallback to this checkout: WHICH document a client is a
# projection of is a fact about the CLIENT, written into its lock by a release,
# so it is not expressible as a row here — generate.py resolves it per repo and
# refuses a client that names none. Why that document and not this repo's
# `hanzo.yaml`, measured: see THE DOCUMENT below.
#
# The validator is not the reason to prefer either: cloud's emission fails
# `openapi-generator validate` with 1012 errors — every route the weave
# publishes with an address and no `responses`, which OpenAPI 3.1 permits and
# the 7.14.0 validator still refuses. `--skip-validate-spec` is the driver's,
# for every language, and `go build` is what actually gates a bad document.
#
# `take` maps a path in the generator's output onto a path in the SDK repo. What
# the generator owns there is the FILES IT WROTE — recorded in that repo's
# `.generated`, removed when the document stops projecting them, and nothing
# else touched. Anything this driver did not write is the repo's own (build
# files, the hand-written seam, examples) whatever directory it sits in.
#
# That rule replaced "the generator owns this DIRECTORY", and replacing it is
# what let go become a row. Under the old rule go was unsayable — `package
# hanzoai` sits at the module root beside go.mod, LICENSE, examples/ and .git,
# so the only take it could name was one that deleted the repository — and so go
# owned a SECOND DRIVER: 196 lines of bash re-deriving the jar, the lock, the
# digest check, the YAML-to-JSON conversion and the drift check, which is four
# copies of this file's logic sitting where it could drift, and did.
#
# WHEN A LANGUAGE LEAVES THIS FILE — the boundary, so it is a rule and not a
# mood: a row exists while the WHOLE invocation is expressible as data. A
# language leaves when its invocation needs something this file cannot say.
#
# A TEMPLATE IS NO LONGER ONE OF THOSE, and the correction is worth stating
# because it moved a boundary. The old rule read: rust needs a
# `reqwest/api.mustache` override, a template is a FILE, it has to sit next to
# the generator call and its flags next to it, so rust owns its whole invocation
# elsewhere. Every clause is true except which call. The invocation is
# `generate.py`'s `emit()`, in THIS repo — an SDK repo holds a one-line call
# site that passes no flags at all. So a template belongs HERE too, under
# `templates/<lang>/`, named by a row's `templates:` key, and the pair that
# would have drifted is one directory apart in one commit.
#
# dart is the row that needed it and the proof it works: the generator's Dart
# `fromJson` calls a static that only a GENERATED CLASS has, on `Object`, `Map`
# and `List` — 52 compile errors no `properties`, `flags` or type mapping
# reaches, and 15 still there on the newest generator. Two overrides, and the
# 2479-operation client analyzes clean.
#
# RUST CAME BACK ON IT, and deliberately — the measured change that sentence
# said someone would have to make, not a side effect of it. Its stated reason
# WAS the template, `templates/rust/` now holds it, and the row below reproduces
# the departed script exactly: regenerated at the ref that client's lock named,
# `--check` reports clean, byte-for-byte. What the departure actually cost is
# recorded in that row — it drifted in the direction it left to avoid.
#
# cpp is NOT hauled back with it. It keeps a second reason a row still cannot
# hold — it needs generator 7.24.0, and the pin below is one version for
# everyone.
#
# A language that still leaves owns its whole invocation in its own
# scripts/generate.sh — same generator, same pin, same document at the same
# locked ref. That is the pull model, the same one interface either way.
# What is NOT allowed is a row here AND flags there: that is two declarations of
# one contract, and the departure had already started drifting into it.
#
# THE DOCUMENT, measured rather than argued, because a projection of it sits in
# this very repo and reads like the better input. Every row reads hanzo-inc/cloud's
# `openapi.yaml` at the ref its own `.spec-lock` names — one document, one
# sha256 per ref, no second authority. The alternative was `hanzo.yaml`,
# publish.py's projection, and it is DERIVED FROM THE SAME BYTES (this repo's
# `.spec-lock` carries the digest every SDK's does), so it settles no question
# of authority; what it changes is shape. Measured at that ref, generating from
# it instead would:
#
# • stamp `info.version: 8.0.0` into every generated file's header — the
# projection's own release counter, which names no cloud release.
# • drop 23 operations (the `compat`-tagged legacy addresses) and move 51 more
# off `DefaultAPI` onto the app that serves them, which is a real
# improvement, against the one above.
#
# A third cost stood here and no longer does, which is worth keeping because it
# is what a projection costs when it outlives its reason: the projection used to
# require a root `security` of `bearerAuth`, a scheme its own
# `components.securitySchemes` did not define, because cloud began declaring
# `bearer` itself and publish.py's `setdefault` stopped firing while the
# requirement it wrote still did. Generated typescript: 2498 bearer call sites
# from cloud's document, 0 from the projection, leaving 191 files importing a
# credential helper none of them called. publish.py no longer touches security,
# and `test_a_client_can_authenticate` refuses a document that requires a scheme
# it does not define.
#
# So: the emission. The 51 untagged and the 23 double-tagged operations are the
# document's defects and belong upstream in cloud's emitter, which is where
# publish.py already says each of its rules is a candidate to go.
generator: 7.14.0 # openapi-generator, pinned for every language
# Files the generator offers that a client must not carry, matched on the whole
# relative path or on the bare name. Two kinds: a SECOND copy of the spec
# (api/openapi.yaml — the exact thing this pipeline exists to kill), and build
# metadata that belongs to the repo, which already has one. Cargo.toml is
# deliberately absent: the Rust client is its own workspace member crate.
# (openapi-generator's own --ignore-file-override honours only some of these,
# so the driver does all of it — one mechanism, not two.)
drop:
- api/openapi.yaml
- .openapi-generator # a VERSION file would be a second home for the pin above
- .openapi-generator-ignore
- .gitignore
- .npmignore
- .travis.yml
- .gitlab-ci.yml
- git_push.sh
- README.md
- package.json
- tsconfig.json
- tsconfig.esm.json
# ng-packagr's manifest, and it is dropped for the reason every manifest here
# is: it states one fact, `lib.entryFile`, and that fact is about a layout the
# ROW chooses. The angular row takes the generator's whole output into src/,
# so the emitted one would land at src/ng-package.json naming `index.ts`
# beside it — and ng-packagr resolves `dest` relative to the manifest, so the
# build would write src/dist, inside the tree it compiles. The repo's own sits
# at the root, names src/index.ts, and builds to dist.
- ng-package.json
# `*.gemspec` is deliberately NOT here, and it is the one build manifest a
# client keeps. The others are dropped because they carry facts the generator
# cannot know; the gemspec carries none — every field in it comes from the
# ruby row's gem* properties, and its runtime dependencies follow from that
# row's `library`. Measured: with all six set to a sentinel, all six appear in
# hanzoai.gemspec and in no file under lib/. Dropping it would make those
# properties inert and move the registry metadata into a hand-written second
# home. See the ruby row.
- Gemfile
- Rakefile
- go.mod
- go.sum
# composer.json is dropped for the reason the gemspec above is kept: the php
# generator has no property for a package DESCRIPTION, so it writes the API
# document's own info.description as the package's tagline — "every operation
# below is a route the subsystem that publishes it registered", which reads
# as prose about a document on a page that is not one. Every other field it
# writes is derivable from the php row; that one is not, so the manifest is
# the repo's. See the php row.
- composer.json
- pom.xml
- build.gradle
- settings.gradle
- setup.py
- setup.cfg
- pyproject.toml
- requirements.txt
- test-requirements.txt
- tox.ini
sdks:
python:
repo: python-sdk
generator: python
# Vendored into the published `hanzoai` distribution as `hanzoai.cloud`, so
# every existing top-level export keeps working untouched.
# A PACKAGE PATH, not a repo. `packageName: hanzoai.cloud` below makes the
# generator emit `hanzoai/cloud/`, and python-sdk tracks it at
# `pkg/hanzoai/cloud`. Both halves read `hanzoai/cloud` and neither is the
# spec repo -- which is why the rename that pointed every DOCUMENT reference
# at hanzo-inc/cloud swept this line up too, and the python projection has
# emitted nothing since: the generator wrote hanzoai/cloud and `take` went
# looking for hanzo-inc/cloud.
take: { hanzoai/cloud: pkg/hanzoai/cloud }
properties:
packageName: hanzoai.cloud
library: urllib3
generateSourceCodeOnly: "true"
# o11y.GettableAgentCheckIn declares `integration_config` AND
# `integrationConfig`, `removed_at` AND `removedAt` — the snake spellings are
# live wire, published so older AWS agents keep working (see the schema's own
# "Older fields for backward compatibility" description). Python snake-cases a
# property to its attribute name, so each pair collapsed onto ONE attribute and
# the second declaration silently replaced the first: eight declared properties,
# six real fields, and an old agent's value read in and dropped with no error.
# Shipped that way as 3.2.0.
#
# Same defect the CLAUDE.md "three o11y schemas" entry records for Go, and the
# same correction the kotlin row makes for distinct_id: rename the ATTRIBUTE,
# never the wire name — the generator then carries the original key as the
# field's alias, so both spellings still round-trip under their own key. The
# value must stay a legal PUBLIC python identifier: a leading underscore would
# make it a pydantic PrivateAttr, which is not a field at all and would drop
# the data exactly as before while looking fixed.
#
# Measured against hanzo.yaml: `removed_at` and `integration_config` each occur
# in exactly ONE schema, so this global mapping reaches nothing else.
flags:
name-mappings: removed_at=removed_at_legacy,integration_config=integration_config_legacy
go:
repo: go-sdk
generator: go
# THE MODULE ROOT. `package hanzoai` sits beside go.mod, LICENSE, examples/
# and .git because that is what `import "github.com/hanzoai/go-sdk"`
# requires, and for as long as `take` meant "the generator owns this
# DIRECTORY" that was unsayable — naming the root would have deleted the
# repository. It is sayable now: the generator owns the FILES it wrote, which
# `.generated` records, so a root take removes stale client files and cannot
# touch a file this driver did not write. See generate.py's `owned()`.
#
# Canonical repo is hanzo-go/sdk; `hanzoai/go-sdk` is a rename redirect and
# stays the MODULE PATH, because that is what the proxy has and what
# consumers require.
take: { .: . }
# The generator's Go output is not gofmt'd — 2655 of 2656 files — and an
# unformatted Go file reformats itself in every contributor's editor, so
# committing it makes the client drift on save. No generator knob reaches
# this; it is a step after the generator, so it is its own key.
format: [gofmt, -w]
# Go is the one client that PUBLISHES its reference: README.md links
# docs/KeysAPI.md and pkg.go.dev has no rendered page for a generated
# method's parameters. The fleet default drops docs because ~4000 files of
# nothing is what they are in a language with a doc site; here they are the
# doc site.
global:
apiDocs: "true"
modelDocs: "true"
properties:
packageName: hanzoai
# The repo owns go.mod: it carries the module path, the go directive and
# two real dependencies, none of which a generator knows.
withGoMod: "false"
# Both prefixes are load-bearing at this surface's size. `structPrefix`
# names a request type after the API class that owns it, which is the only
# reason the two operationId pairs the document folds together —
# deleteSession with DeleteSession, postGitWebhook with PostGitWebhook —
# do not redeclare: they land on different tags, so on different prefixes.
structPrefix: "true"
enumClassPrefix: "true"
flags:
# Inert for what this row keeps (they reach README.md and git_push.sh,
# both dropped) and stated anyway, because they were in the invocation
# this row replaces and a row that regenerates go differently is a
# regression, not a unification.
git-user-id: hanzoai
# go-sdk/v8, not go-sdk: the generator composes the import path in every
# generated doc as github.com/<git-user-id>/<git-repo-id>, and Go's
# semantic import versioning makes a path without a /vN suffix publishable
# only at v0 and v1. The client rides the 8.5.x line, so without this the
# docs would tell a reader to import a path whose 8.x tags the module
# proxy rejects -- which is what they did while it served v1.0.5.
git-repo-id: go-sdk/v8
# Go emits Get<F>, Get<F>Ok, Has<F> and Set<F> beside every field, and
# that convention collides with the document three times.
#
# o11y.PodOnboarding declares eight `has<Label>Name` presence flags
# beside the labels themselves, so the FIELD HasClusterName and the
# METHOD HasClusterName that ClusterName generates are one name. Three
# of the eight collide today; all eight take the `<label>NamePresent`
# spelling, because they are one family with one meaning ("the label is
# present", in the document's own words) and splitting their spelling by
# which sibling happens to exist leaves the next sibling to break the
# build.
#
# o11y.PostableProfile does the same once, has_existing_observability_tool
# beside existing_observability_tool.
#
# o11y.GettableAgentCheckIn carries two spellings of two fields —
# integration_config with integrationConfig, removed_at with removedAt —
# published together so older AWS agents keep working. Both spellings
# Pascal-case to one Go field. The snake one is the legacy wire and takes
# the suffix; the python, java and kotlin rows correct the same pair the
# same way.
#
# Every value is spelled EXPORTED, and that is load-bearing: the Go
# generator takes a mapped name VERBATIM where python and kotlin re-case
# theirs, so a camel value lands a lowercase field — unexported, invisible
# to a caller, and skipped by encoding/json in both directions. It compiles
# and it drops the value it was written to preserve. The wire is untouched
# either way: the generator keeps the document's key as the json tag.
name-mappings: hasClusterName=ClusterNamePresent,hasCronjobName=CronjobNamePresent,hasDaemonsetName=DaemonsetNamePresent,hasDeploymentName=DeploymentNamePresent,hasJobName=JobNamePresent,hasNamespaceName=NamespaceNamePresent,hasNodeName=NodeNamePresent,hasStatefulsetName=StatefulsetNamePresent,has_existing_observability_tool=ExistingObservabilityToolPresent,integration_config=IntegrationConfigLegacy,removed_at=RemovedAtLegacy
# A CLASS NAME, and the collision is with the hand-written seam. The
# document has a schema called `Config`: the MQ stream configuration, POST
# /v1/mq/streams' body and Stream.config. Its constructor is NewConfig,
# which is already hanzo.go's — the seam that builds an authenticated
# Configuration. `StreamConfig` says which config it is, and is the only
# bare Config in a document where every other one is qualified (TLSConfig,
# iam.config, o11y.DiscordConfig).
model-name-mappings: Config=StreamConfig
typescript:
repo: js-sdk
generator: typescript-axios
# `src`, not `src/cloud`: js-sdk IS the client. Python and Go vendor it as a
# subpackage beside a hand-written SDK that predates it, so there the client
# needs its own room; js-sdk has nothing else to be, and its src/index.ts is
# generator output down to the header. Pointing at src/cloud built a second,
# unexported copy of the same 2143 files next to the real one.
take: { .: src }
properties:
# `hanzoai`, the unscoped name, which is what js-sdk publishes and what
# npm hands us: `npm owner ls hanzoai` is zeekay and the registry's
# repository.url on it already points at hanzoai/js-sdk. This row said
# @hanzo/sdk, from a since-reverted belief that `hanzoai` was squatted —
# and @hanzo/sdk is a DIFFERENT package, owned by hanzo-js/sdk (v2.0.0,
# the unified per-service SDK), so that name would publish over someone
# else's line. Inert today only because package.json is on `drop` and the
# generated one never reaches the repo; a landmine the day it isn't.
npmName: hanzoai
withSeparateModelsAndApi: "true"
apiPackage: api
# `models`, plural, because that is what the published artifact ships. The
# singular here would rename every file under src/models and break anyone
# who deep-imports a model path — not something a patch release may do.
# The java/kotlin rows say `model` for the same reason in reverse: it is
# what those artifacts already ship.
modelPackage: models
supportsES6: "true"
useSingleRequestParameter: "true"
# A LANGUAGE CAN NEED MORE THAN ONE ROW, and this is where the rule for that
# lives, because without one `openapi-generator list` reads like a shopping
# list: it offers thirteen TypeScript and JavaScript targets, and shipping
# them is thirteen packages nobody installs and thirteen trees `--check` has
# to keep clean.
#
# A VARIANT EARNS A ROW WHEN A RUNTIME OR A FRAMEWORK CANNOT USE THE DEFAULT
# PROJECTION — not when it would merely read differently. `hanzoai` already
# ships .d.ts beside compiled CJS and ESM, so plain JavaScript, "I would
# rather have promises than callbacks" and every bundler flavour are the same
# audience it already serves, and a second package for them is a second thing
# to keep at the same release for no one.
#
# NAMED BY ONE WORD, and the same word in all four places: the row key, the
# repo (hanzo-js/sdk-<w>), the npm package (hanzoai-<w>) and the generator
# (typescript-<w>). The default carries the suffix nowhere — `typescript`,
# hanzoai/js-sdk, `hanzoai`, typescript-axios. So `hanzoai-fetch` names its
# repo and its row, and neither has to be looked up.
#
# REFUSED, and each for a stated audience rather than a taste:
#
# typescript-node — MEASURED, not judged by age: 7.14.0 emits a package
# whose dependencies are `request@^2.88.2` and `bluebird`, typed against
# `@types/node@^12`. npm serves request with a `deprecated` field and its
# last publish is 2020-02-11. Its audience would be Node without a global
# fetch, and every Node that still ships is past that; the audience that
# is left is served by `hanzoai`, which runs on Node 12 and up. A client
# whose transport has been unmaintained for six years is a supply-chain
# liability shipped to solve a problem nobody has.
# typescript-rxjs — Observables without Angular. Angular is where the
# Observable is the idiom and it has its own row; outside it, `from(p)`
# is one line over a promise, so the package would exist to save a line.
# typescript-nestjs — the closest call. Nest's DI is real, but its
# HttpService is axios underneath, Nest runs on the server where
# `hanzoai` is already idiomatic, and providing a configured client is a
# five-line provider. Angular's case is different in kind: there the
# framework owns the transport (HttpClient) and the return type
# (Observable), so the axios client cannot be made idiomatic at all.
# typescript (experimental) — a client with a swappable http layer, of
# which fetch is one. Same audience as the row below, marked
# experimental, and two ways to say fetch is one too many.
# javascript — superagent, and its audience already installs `hanzoai`:
# the published artifact is compiled JS with types beside it, so a
# JavaScript caller needs nothing from a JavaScript generator.
# typescript-{aurelia,inversify,jquery,redux-query} and
# javascript-{closure-angular,flowtyped} — no audience this fleet can name.
# inversify is a DI container over the same transports rather than a
# transport; jquery and closure-angular address a browser era this API
# has no callers from; flowtyped is a type system TypeScript replaced;
# redux-query is unmaintained. None of them is a runtime that cannot run
# the default, which is the only test that admits a row.
fetch:
repo: ../hanzo-js/sdk-fetch
generator: typescript-fetch
# THE AUDIENCE IS THE HOST, and the measurement is the dependency tree
# rather than a claim about where axios runs. `npm i axios@^1.7.0` — the
# `typescript` row's one dependency — resolves 1.19.0 and installs 27
# packages, 4.1 MB, because axios itself pulls follow-redirects, form-data,
# https-proxy-agent and proxy-from-env, and those pull twenty-two more.
# This client installs ZERO: `fetch` is the host's, so the generated
# package has no dependency to declare and nothing to resolve at all. On a
# Worker, a Deno isolate or a Bun process that is the whole difference
# between a client that bundles and one that drags a proxy-agent tree
# through a bundler with no node core to give it.
#
# It also reaches something the axios projection cannot hand back. Every
# operation has a `…Raw()` twin returning `ApiResponse<T>`, whose `raw` IS
# the `Response` — so `response.body` is a ReadableStream and a streaming
# completion is readable without leaving the client. And `fetchApi` on the
# Configuration takes the host's fetch by value, which is what makes this
# client testable with no network and no interceptor.
#
# The whole client is under src/ — apis/, models/, runtime.ts, index.ts —
# so one take names it. The generator also writes package.json, README.md
# and both tsconfigs at the root; all four are on `drop`, and the repo owns
# them, exactly as hanzoai/js-sdk does.
take: { src: src }
properties:
npmName: hanzoai-fetch
supportsES6: "true"
# THE EXTENSION IS WHAT MAKES THE ESM HALF LOADABLE, and it was measured
# by loading it. The generator writes relative imports bare —
# `from './runtime'` — which a bundler resolves and Node's ESM loader does
# not: importing dist/esm/index.js dies with ERR_MODULE_NOT_FOUND on
# .../dist/esm/runtime. hanzoai/js-sdk has the same hole and cannot close
# it, because typescript-axios has no such property, and its
# tsconfig.esm.json says so — that ESM output feeds the `module` field,
# which only bundlers read.
#
# This client cannot live with that: Deno, Workers and Node ESM are the
# audience it exists for, and all three are the loader that refuses a bare
# specifier. TypeScript emits the specifier verbatim, so one value serves
# both halves — `./runtime.js` resolves to runtime.ts at compile time
# under node16, is what Node ESM wants at run time, and is what
# `require()` wants in the CJS half. Measured after setting it: both
# `import` and `require` of the built client answer GET /v1/models 200.
importFileExtension: .js
# The one naming correction this row needs, and it is the document rather
# than the generator. `deleteSession` (POST /v1/iam/sessions/delete) and
# `DeleteSession` (DELETE /v1/o11y/sessions) differ only in case — the
# collision publish.py's rule 6 renames for hanzo.yaml, which this client
# does not read — and both Pascal-case to the same request interface.
# They land on different tags, so on different API classes, and it is the
# BARREL that breaks: src/apis/index.ts re-exports both files, and TS2308
# says the member is already exported. Prefixing names them
# IamApiDeleteSessionRequest and O11yApiDeleteSessionRequest, which is
# what the typescript-axios generator does by itself — hanzoai/js-sdk
# ships `IamApiDeleteSessionRequest` with no property set — so this puts
# the two clients on ONE spelling rather than inventing a second.
prefixParameterInterfaces: "true"
flags:
# THE COLLISION EVERY OTHER ROW CORRECTS, and the `typescript` row above
# does not — which is the whole reason it is stated here and not once for
# the family. o11y.GettableAgentCheckIn declares `integration_config` AND
# `integrationConfig`, `removed_at` AND `removedAt`; the snake spellings
# are live wire, published so older AWS agents keep working. The AXIOS
# generator declares a property under the document's own key —
# `'integration_config'?: O11yIntegrationConfig` — so the two never meet
# and hanzoai/js-sdk compiles untouched. This generator camel-cases, so
# each pair lands on one property declared twice: TS2300 duplicate
# identifier, plus TS2717, TS1117 and TS2559 — nine errors in that one
# model, and the client does not build.
#
# The two are not even the same shape — `integrationConfig` is
# O11yIntegrationConfig on the snake key and O11yProviderIntegrationConfig
# on the camel one — so collapsing them is a type error and not only a
# name clash, and renaming keeps both fields correctly typed.
#
# CAMEL values, like the kotlin, dart, go and swift rows: the value is an
# identifier in the TARGET language. The wire is untouched — measured, the
# generator keeps the document's key in both FromJSON and ToJSON, so both
# spellings still round-trip under their own key.
name-mappings: removed_at=removedAtLegacy,integration_config=integrationConfigLegacy
angular:
repo: ../hanzo-js/sdk-angular
generator: typescript-angular
# THE AUDIENCE IS THE FRAMEWORK, and it is the one case where the default
# projection cannot be made idiomatic rather than merely inconvenient.
# Angular owns the transport and the return type: a caller expects an
# injected service whose methods hand back `Observable`, so that
# interceptors, `HttpContext`, SSR transfer-cache, retry and the testing
# backend all apply. An axios client provides none of those and cannot be
# made to — wrapping it gives promises inside a framework whose whole
# surface composes Observables.
#
# 192 services, one `@Injectable({providedIn: 'root'})` each, plus both
# entry shapes the generator writes: `provideApi()` for a standalone
# application's providers, and `ApiModule.forRoot()` for an NgModule
# application that has not moved. Nothing here chooses between them.
#
# The whole output is the client, so one take names it; the generator's
# package.json, README.md, tsconfig.json, git_push.sh and ng-package.json
# are all on `drop` and the repo owns each.
take: { .: src }
properties:
npmName: hanzoai-angular
# `providedIn: 'root'` rather than an ApiModule that lists 192 providers:
# a service nobody injects is then unreachable from the graph and the
# bundler drops it. Under the module form every provider is referenced by
# the module, so importing one service pulls all 192 into the bundle.
providedInRoot: "true"
# The same call shape the `typescript` row ships, so the two clients read
# alike and moving between them is not a rewrite of every call site. At
# this operation count it is also the only readable form: the positional
# default puts every parameter of an operation in a row and the caller
# counts commas.
useSingleRequestParameter: "true"
# The identical collision the fetch row corrects, in the barrel this
# generator writes: api/api.ts re-exports iam and o11y, `deleteSession`
# and `DeleteSession` both Pascal-case to DeleteSessionRequestParams, and
# TS2308 names it. Prefixing gives IamApiDeleteSessionRequestParams and
# its o11y twin.
prefixParameterInterfaces: "true"
# `Api`, not the generator's `Service` default, and it is forced rather
# than preferred — 7.14.0 has --model-name-mappings and --name-mappings
# but NO per-tag api mapping, so the only lever that reaches a colliding
# API class name is this one, and it is global.
#
# TWO collisions, both structural, both fixed by the same value:
#
# The document has a product called `base` (managed Hanzo Base). Under
# the default the tag's class is `BaseService`, which is the name of the
# generator's OWN transport parent in api.base.service.ts — so
# base.service.ts imports BaseService and then declares
# `export class BaseService extends BaseService`. TS2440, TS2395,
# TS2506 and five TS2339s: nine errors, and there is nothing on the
# document's side to change, because a tag is what the serving binary
# calls the product.
#
# The document also has a schema `o11y.Service`, which Pascal-cases to
# `O11yService` — the o11y tag's class under the default. src/index.ts
# re-exports ./api/api and then ./model/models, so TS2308 says the
# member is already exported. One more error, same cause: `Service` is
# a word this API uses for its own things.
#
# It also puts the three TypeScript clients on ONE class name per
# product — `KeysApi` in hanzoai, in hanzoai-fetch and here — so moving
# between them renames nothing. The file suffix follows the class rather
# than being left at `.service`, so keys.api.ts declares KeysApi and the
# two halves of a name do not disagree.
serviceSuffix: Api
serviceFileSuffix: .api
# ngVersion is deliberately absent, and it is MEASURED rather than
# assumed — the same probe the cpp row's packageVersion gets. Generated
# at the default 19.0.0 and again at 22.0.0, exactly ONE of 2671 files
# differs and it is package.json, which is on `drop`. So stating a
# version here would reach no file this repo keeps; the peer range is a
# fact about the published package and lives in the package.json the repo
# owns, in one home, where a consumer can read it.
flags:
# The same pair the fetch row above corrects, for the same reason and in
# the same spelling: this generator camel-cases a property too, so
# o11y.GettableAgentCheckIn's `integration_config`/`integrationConfig` and
# `removed_at`/`removedAt` land on one field declared twice. The wire is
# untouched; both spellings still round-trip under their own key.
name-mappings: removed_at=removedAtLegacy,integration_config=integrationConfigLegacy
java:
repo: java-sdk
generator: java
take: { src/main/java/ai/hanzo/cloud: hanzo-java-cloud/src/main/java/ai/hanzo/cloud }
# edge_deployFunction offers both application/octet-stream and
# multipart/form-data. The generator skips a multipart form model by default
# yet still imports it from the API class that used it, so FunctionsApi
# imports a class nobody emitted. Emitting it is the fix; it is set here and
# not in generate.py because it would churn every other client's output.
global:
skipFormModel: "false"
properties:
# `native` (java.net.http, no third-party HTTP dependency) is the library
# this row wants and cannot have: its oneof_model.mustache iterates the
# raw `oneOf` type-name set and emits `{{{.}}}.class`, which is illegal
# Java the moment a variant is an array — `List<CloudEvent>.class`. Three
# schemas in the document have an inline array in a oneOf
# (cloud_post_v1_event's body, gateway_createCompletion's `prompt`,
# search_deleteDocumentsByFilter's `filter`), so `native` produces 532
# javac errors. Every other Java library iterates `composedSchemas.oneOf`
# and guards on `isArray`, so okhttp-gson, jersey3 and apache-httpclient
# all compile; the bug is still present in 7.24.0, so this is not waiting
# on a version bump. okhttp-gson is the generator's default and puts the
# two JVM rows on ONE HTTP+JSON stack — the kotlin row below is already
# okhttp4 + gson.
library: okhttp-gson
groupId: ai.hanzo
artifactId: hanzo-java-cloud
invokerPackage: ai.hanzo.cloud
apiPackage: ai.hanzo.cloud.api
modelPackage: ai.hanzo.cloud.model
hideGenerationTimestamp: "true"
# THE SAME COLLISION THE PYTHON ROW ABOVE CORRECTS, in the language that
# says so at compile time. O11yGettableAgentCheckIn declares
# `integration_config` AND `integrationConfig`, `removed_at` AND
# `removedAt` — the snake spellings are live wire kept for older agents.
# Java camel-cases a property to its field name, so each pair lands on ONE
# field declared twice: `variable integrationConfig is already defined`,
# eight errors, and hanzo-java-cloud does not compile. Python collapsed the
# same pairs SILENTLY and shipped a client that dropped the value.
#
# Rename the FIELD, never the wire name — the generator keeps the original
# key as the field's serialized name, so both spellings still round-trip.
# Same two names as the python row, because it is the same two schemas.
flags:
name-mappings: removed_at=removed_at_legacy,integration_config=integration_config_legacy
# A CLASS NAME, not a field name, and the collision is with the LANGUAGE.
# The document has a schema called `Exception` — the error shape hanging
# off CaptureEvent, five properties and a list of Frame. Every model lands
# in `ai.hanzo.cloud.model`, and a type in your own package beats
# `java.lang.*`, so `catch (Exception e)` in ANY file of that package
# binds to the model: "incompatible types: Exception cannot be converted
# to Throwable", eight times in PostV1EventRequest, whose oneOf adapter is
# the only generated model that catches anything.
#
# `ErrorBody` is the document's own name for the sibling shapes on the
# same schema — CaptureEvent carries `clip: ClipBody`, `log: LogBody`,
# `metric: MetricBody`, `span: SpanBody` and `error: Exception`, so this
# puts `error` in the family the other four are already in, and the name
# is free across all 2260 schemas. A CLASS name is never on the wire, so
# this changes no payload; the schema keeps being called `Exception`
# everywhere the document is read.
#
# Measured against the document: five schema names collide with a builtin
# — Exception, File, Record, Result, Sequence. Only this one reaches
# generated code. `File` the java generator renames to `ModelFile` by
# itself; the other three are emitted as ordinary classes that nothing
# generated refers to by their bare builtin name.
#
# NOT MAPPED HERE, and the reason is a second generator bug: the document
# carries `iam.ListResponse` AND `iam.listResponse`, schemas differing
# only in case — the same defect publish.py's rule 6 corrects for
# operationIds — and both Pascal-case to `IamListResponse`. The second
# write wins, so this client ships ONE class carrying the SCIM shape
# (`Resources`, `schemas`, `startIndex`) under the name GET /v1/iam/keys
# resolves to, and that route deserializes into an all-null model with no
# error. Mapping either one is worse than the disease: the java generator
# applies the new name to the CLASS and not to the FILE, so it writes
# `public class IamKeyList` into `IamListResponse0.java` and nothing
# compiles at all. Measured on 7.14.0, both orders. It belongs upstream,
# where two schemas would stop differing only in case; the kotlin row can
# express it because kotlin's two natural names already differ.
model-name-mappings: Exception=ErrorBody
kotlin:
repo: kotlin-sdk
generator: kotlin
take: { src/main/kotlin/ai/hanzo/cloud: hanzo-kotlin-cloud/src/main/kotlin/ai/hanzo/cloud }
# Same defect the java row hits: the generator skips a multipart form model
# yet still imports it, so FunctionsApi references a class nobody emitted.
global:
skipFormModel: "false"
properties:
library: jvm-okhttp4
groupId: ai.hanzo
artifactId: hanzo-kotlin-cloud
packageName: ai.hanzo.cloud
apiPackage: ai.hanzo.cloud.api
modelPackage: ai.hanzo.cloud.model
serializationLibrary: gson
hideGenerationTimestamp: "true"
# The Kotlin generator emits code that does not compile at this surface's
# size, in ways no `properties` knob reaches. These two are the whole of the
# correction.
flags:
# `sort=name` is an ordinary query value, but the generator names the enum
# entry after it, and `name` is already a property of every Kotlin enum:
# "Conflicting declarations: enum entry name, public final val name".
enum-name-mappings: name=NAME
# POST /v1/event takes a oneOf, and the generator flattens every branch
# into one class. Two branches spell the same field differently —
# cloud_Event.distinctId and cloud_insightsBody.distinct_id — and both
# camel-case to `distinctId`. The rename is Kotlin-side only; the wire
# name is still carried by @SerializedName.
#
# The last two are the collision the python and java rows already carry,
# and Kotlin was simply the row nobody had reached yet:
# O11yGettableAgentCheckIn declares `integration_config` AND
# `integrationConfig`, `removed_at` AND `removedAt` — the snake spellings
# are live wire kept for older agents. Kotlin camel-cases a property to
# its constructor parameter, so each pair lands on ONE name declared
# twice: "Conflicting declarations: value-parameter integrationConfig",
# eight errors, and hanzo-kotlin-cloud does not compile. Kotlin values
# here where python's row spells them snake, for the same reason
# `distinct_id` above maps to a camel one — the value is an identifier in
# the TARGET language.
name-mappings: distinct_id=distinctIdLegacy,removed_at=removedAtLegacy,integration_config=integrationConfigLegacy
# A CLASS NAME, and the collision is with the generator's own type map.
# [email protected] added a schema called `File` — the /v1/ai/files record,
# eleven properties including `store` and `storageProvider`. `file` is a
# mapped type, so the Kotlin generator resolves the model name straight to
# the imported type and writes `data class java.io.File(` into a file
# called `java.io.File.kt`: four syntax errors, and a name that is not an
# identifier in any Kotlin. The Java generator hits the same map and
# renames the model to `ModelFile` by itself, so this row says the name
# Java already chose rather than inventing a second one — one schema, one
# class name, across both JVM clients. A class name is never on the wire.
#
# `iam.ListResponse` decides whether this repo can be CHECKED OUT. The
# document carries it and `iam.listResponse`, two schemas differing only
# in case, and kotlin names them `IamListResponse` and `IamlistResponse` —
# two tracked files differing only in case, which a macOS or Windows
# working copy cannot hold, and which CI would commit from Linux. Renaming
# the one GET /v1/iam/keys returns — it is `{keys: [iam.Key]}`, so
# `IamKeyList` — separates them by more than case and both survive. The
# OTHER one is left alone deliberately: the generator applies a mapping to
# the class and not to the filename, so mapping the second of a colliding
# pair emits a class into a file that does not match it. Java hits that
# and therefore maps neither.
model-name-mappings: File=ModelFile,Call=ModelCall,iam.ListResponse=IamKeyList
ruby:
repo: ruby-sdk
generator: ruby
# THE ONE ROW THAT KEEPS ITS BUILD MANIFEST, and the reason is measured
# rather than preferred. Every other language drops its manifest because it
# carries facts a generator cannot know — go.mod's module path and its two
# real dependencies, package.json's npm name and scripts, pom.xml's parent.
# The gemspec carries none: generating it with every gem* property set to a
# sentinel puts all six ONLY in hanzoai.gemspec and nowhere under lib/, and
# its runtime dependencies (faraday, faraday-multipart, marcel) follow from
# `library` below, which this row also chooses. So the gemspec is wholly
# derivable from this row, and taking it is what keeps the registry metadata
# in ONE home. Dropping it instead would leave the six properties inert here
# and the real ones hand-written there — two declarations of one contract,
# which is the thing this file exists to refuse.
#
# `gem build` on the result succeeds and packages lib/ + README.md + LICENSE
# + examples/; `.spec-lock` and `.generated` are dotfiles, and the template's
# `find *` never sees them.
take: { lib: lib, hanzoai.gemspec: hanzoai.gemspec }
properties:
gemName: hanzoai
moduleName: Hanzoai
# faraday, NOT the generator's typhoeus default. typhoeus binds libcurl
# through ethon, so `bundle install` needs libcurl headers present and the
# client stops being installable wherever they are not. faraday is pure
# Ruby over net/http, so the gem installs anywhere a Ruby does.
library: faraday
# This is the PUBLISHED version: it is the only gem* property that reaches
# lib/ (lib/hanzoai/version.rb), and the gemspec reads it back as
# Hanzoai::VERSION. So a release bumps this line — the same shape as every
# other fact about a client living where the client's projection is
# declared. 0.1.0 because rubygems has never served this name.
gemVersion: 0.1.0
gemSummary: The official Ruby library for the Hanzo API
# NO COMMA and NO APOSTROPHE in any value here, and both were measured on
# this line rather than guessed. `--additional-properties` is ONE
# comma-delimited string, so a comma inside a value ends the value and
# starts a bogus property — the description reached the gemspec cut at the
# comma, its tail parsed as a property the generator silently ignored. The
# apostrophe survives that and then loses to mustache, which HTML-escapes
# an interpolated value: the gemspec read `hanzo-inc/cloud&#39;s`, and
# rubygems renders a gem description verbatim, so that is what the registry
# page would have shown.
gemDescription: The official Ruby library for the Hanzo API — generated from the API OpenAPI document.
gemHomepage: https://github.com/hanzo-ruby/sdk
gemLicense: Apache-2.0
gemAuthor: Hanzo
gemAuthorEmail: [email protected]
# The floor the generated code actually needs, not the generator's `>= 2.4`
# default: the models use the `=>` rightward pattern nothing before 3.0
# parses, and every sibling client targets a live runtime.
gemRequiredRubyVersion: ">= 3.0"
flags:
# THE SAME COLLISION the python, go, java and kotlin rows all correct, in
# the language that neither fails to compile nor stays silent about it.
# o11y.GettableAgentCheckIn declares `integration_config` AND
# `integrationConfig`, `removed_at` AND `removedAt` — the snake spellings
# are live wire, published so older AWS agents keep working. Ruby snake-
# cases a property to its attribute, so each pair lands on ONE hash key
# declared twice, and `attribute_map` keeps only the second: measured, the
# client loaded fine and warned `key :integration_config is duplicated and
# overwritten`, then dropped an old agent's value with no error at runtime
# — python's defect exactly, with a compile-time warning nobody reads.
#
# Snake values, like the python row, because the value is an identifier in
# the TARGET language. The wire is untouched either way: the generator
# keeps the document's key in attribute_map, so both spellings still
# round-trip under their own key.
name-mappings: removed_at=removed_at_legacy,integration_config=integration_config_legacy
# A CLASS NAME, and the collision is with this generator's own api_client:
# `return_type == 'File'` is how it decides a response is a BINARY
# DOWNLOAD and streams it to a Tempfile instead of building a model. The
# document has a schema called `File` — the /v1/ai/files record — so an
# operation returning it at the top level would be silently downloaded
# rather than parsed. Measured at this ref that has not happened yet (0 of
# 2479 operations return it) but 7 responses already carry it one level
# down under `data`, so the top-level one is a cloud release away, and the
# failure it would cause is silent. `ModelFile` is the name the Java
# generator picks for this schema by itself and the one the kotlin row
# states, so all three JVM-and-Ruby clients call it one thing. A class name
# is never on the wire.
#
# NOT MAPPED HERE, for the reason the java row gives and this generator
# confirms: `iam.ListResponse` and `iam.listResponse` differ only in case
# and both land on `IamListResponse`, so the second write wins and GET
# /v1/iam/keys deserializes into the SCIM shape. Mapping either is worse
# than the defect — measured, `iam.ListResponse=IamKeyList` renames the
# class and NOT the require path, so lib/hanzoai.rb asks for
# `hanzoai/models/iam_list_response`, which no longer exists, and the whole
# client fails to load. Kotlin can express this and Ruby cannot, for the
# same reason Java cannot. It belongs upstream, where two schemas would
# stop differing only in case.
model-name-mappings: File=ModelFile
php:
repo: php-sdk
# THREE PHP GENERATORS SHIP IN 7.14.0, and the choice is measured on this
# document rather than preferred. `php-dt` cannot READ it: it reaches
# operation.responses.keySet() unguarded and dies with a NullPointerException
# on the first of the 716 operations this document states the address of and
# not the shape — 0 files, in every configuration. `php-nextgen` reads it
# and emits exactly what `php` does: the same 192 API classes, the same
# five parse failures the flags below correct. Across all 2658 files exactly
# ONE differs in what it declares (its HeaderSelector drops two helpers);
# the rest differ in typed statics and import spelling. So the beta buys
# nothing here, and `php` is the only one of the three the generator does
# not mark beta.
generator: php
# The whole client is under src/ — Api/, Model/ and the five runtime files
# — so one take names it exactly and nothing else in the repo is the
# generator's. composer.json is on `drop` above.
take: { src: src }
properties:
# The PSR-4 root: Hanzo\Cloud\Api\*, Hanzo\Cloud\Model\*. The same
# ai.hanzo.cloud the JVM rows use, spelled as PHP spells a namespace.
invokerPackage: Hanzo\Cloud
# `src`, not the generator's default `lib`: composer autoloads Hanzo\Cloud
# from src/, the way every other client in this fleet lays its sources out.
srcBasePath: src
flags:
# Every one of these is a REDECLARATION — one class declaring one method
# twice, which PHP refuses at parse time, so without them the client does
# not even lint. Four names, five files, two causes.
#
# `modelName` collides with the GENERATOR'S OWN interface. Every model
# implements ModelInterface::getModelName(), which returns the schema's
# name, and a property spelled modelName generates a second one. It
# occurs in exactly three schemas — ModelRoute, o11y.O11yLLMPricingRule,
# o11y.O11yLLMUpdatablePricingRule — and in all three the value is the
# LLM's identifier, which this API spells `model` everywhere it is not
# this field.
#
# `distinct_id`, `integration_config` and `removed_at` are the legacy
# snake spellings the document publishes BESIDE their camel twins so
# older senders keep working. PHP camel-cases a property into its
# accessor, so each pair lands on one method declared twice. The same
# pairs the python, go, java, kotlin and ruby rows correct, snake-spelled
# here because the value is an identifier in the TARGET language.
#
# The wire is untouched in both cases: the generator keeps the document's
# key in attributeMap, so `modelName` still serializes as `modelName` and
# both spellings of the legacy pairs still round-trip under their own key.
name-mappings: modelName=model,distinct_id=distinct_id_legacy,integration_config=integration_config_legacy,removed_at=removed_at_legacy
# NOT MAPPED, for the reason the java and ruby rows give and this generator
# confirms: `iam.ListResponse` and `iam.listResponse` differ only in case,
# both Pascal-case to IamListResponse, and the second write wins — so this
# client ships the SCIM shape under the name GET /v1/iam/keys resolves to.
# Mapping either one is worse than the defect. Measured in all three
# directions: the php generator applies the new name to the CLASS and not
# to the FILE, so one class always lands in IamListResponse0.php, which
# PSR-4 cannot load — `composer dump-autoload --strict-psr` fails there and
# passes on the unmapped tree. It belongs upstream, where two schemas would
# stop differing only in case.
dart:
repo: ../hanzo-dart/sdk
# `dart`, not `dart-dio`, and the two are not close. dart-dio emits models
# annotated for a SECOND code generator — measured on this document, 2460
# files each declaring `part '<name>.g.dart'` and 0 of those parts written,
# so `dart pub get && dart analyze` fails on 2460 missing parts until
# `build_runner` has run. That output is generated by something this driver
# does not invoke, cannot digest and would not own, which is the one shape
# `.generated` has no answer for. `dart` serializes in plain Dart in one
# pass, and its transport is `package:http` — what hanzo-dart/hanzoai and
# hanzo-dart/base already use, so the org has one HTTP stack rather than
# dio beside http.
generator: dart
# PINNED PAST THE FLEET, because 7.14.0 cannot emit this language at all:
# AbstractDartCodegen.fromProperty throws `JsonSchema cannot be cast to
# ComposedSchema` on our polymorphic request bodies, so dart emitted nothing
# and sat four releases behind while the other twelve regenerated fine.
# Measured on 7.19.0: 117 api files, no exception. Drop this line when
# `generator:` above moves past it — carrying two versions is a cost, and it
# is the smaller of the two on offer, the other being regenerating twelve
# working clients to repair one.
generator_version: 7.19.0
# Everything the client is lands under lib/, so nothing else is taken. The
# generator also offers pubspec.yaml, analysis_options.yaml, README.md,
# .travis.yml and git_push.sh at the root; naming lib/ leaves them where
# the fleet `drop` list would have had to grow two Dart-shaped entries.
# The repo owns its manifest, its lint config and its README.
take: { lib: lib }
# TWO mustache overrides, and without them there is no Dart client at all.
#
# The Dart model template decides how to decode a property by asking
# whether it has a `complexType`, and answers YES for `Object`, `Map` and
# `List` — then calls `listFromJson` / `mapFromJson` / `mapListFromJson` on
# them, statics that only a GENERATED CLASS carries. `items: {}` on an array
# is enough to trigger it. Measured on this document: 52 compile errors that
# no property, flag or type mapping reaches, in `serialization/native/
# native_class.mustache`; still 15 on generator 7.24.0, so it is not a
# version away. The override asks the question one level lower, where the
# answer is right, and decodes the container shapes by cast — the same lazy
# semantics `mapCastOfType` already gives every map in this client.
#
# `api_client.mustache` returns `Response.fromStream(response)` from inside
# the try block that turns transport failures into ApiException, unawaited,
# so a failure there is thrown past the handler written to catch it. The
# override awaits it. `dart analyze` calls that a warning and warnings are
# fatal, so the gate names it either way.
templates: templates/dart
properties:
pubName: hanzoai
pubLibrary: hanzoai.cloud
flags:
# `override` is a property of BookRequest — book this bill even though one
# of the same economic identity already posted. Dart spells its own
# override annotation `@override`, and a field of that name shadows it
# inside the class that declares it, so the three `@override` the
# generator writes on ==, hashCode and toString stop resolving:
# "Undefined name 'override' used as an annotation". The wire keeps the
# key — the generator carries baseName into toJson and fromJson.
#
# The last three are the family collision the python, go, java, kotlin and
# ruby rows all correct. o11y.GettableAgentCheckIn declares
# `integration_config` AND `integrationConfig`, `removed_at` AND
# `removedAt` — the snake spellings are live wire, kept so older AWS
# agents keep working — and cloud_insightsBody's `distinct_id` meets
# cloud_Event's `distinctId` in the oneOf that POST /v1/event flattens.
# Dart camel-cases a property to its constructor parameter, so each pair
# lands on ONE name declared twice: "The field 'integrationConfig' can't
# be initialized by multiple parameters in the same constructor". Camel
# values, because the value is an identifier in the TARGET language.
name-mappings: override=overrideDuplicate,distinct_id=distinctIdLegacy,removed_at=removedAtLegacy,integration_config=integrationConfigLegacy
# A PARAMETER, and the collision is with the generator's own local. Every
# generated method opens `final path = r'/v1/...'`, the request's URL. Four
# operations take a query parameter also called `path` — GET /v1/code/file
# and the three git repo reads — and the local shadows the parameter, so
# `_queryParams('', 'path', path)` sends the ROUTE as the value of the
# `path` query and the caller's argument is dropped. Two of the four are
# in an `if (path != null)` the analyzer flags as always-true, which is how
# this surfaced; the shadowing is the defect. Measured against the
# document: exactly 4 parameters are named `path` and all four are broken,
# so this mapping reaches all of them and nothing else. The query key is
# baseName and does not move.
parameter-name-mappings: path=pathParam
# CLASS NAMES. Every model lands in one Dart library, and Dart resolves a
# name from a non-platform import BEFORE `dart:core` — so a model named
# like a core type does not raise an ambiguity, it silently wins.
# Measured by compiling every one of the 2556 generated names against
# dart:core and against the six libraries the client imports: four
# collide.
#
# `Exception` breaks the client itself. `class ApiException implements
# Exception` binds to the MODEL — the error shape hanging off
# CaptureEvent — so ApiException inherits nine abstract members it does
# not implement, and the four `catch` arms in api_client stop being
# assignable to it. `ErrorBody` is the family the document's own
# siblings are in (ClipBody, LogBody, MetricBody, SpanBody) and the name
# the java row already chose.
#
# `File`, `Record`, `Symbol` break the CALLER, and silently, which is
# worse. A consumer importing this client and `dart:io` who writes
# `File('/tmp/x')` gets the /v1/ai/files record: "Too many positional
# arguments: 0 expected". `Record` and `Symbol` need no import at all —
# they are dart:core, so every file in every consumer is exposed.
# `ModelFile` is the name java picks by itself and kotlin and ruby
# state. The other two take the document's own qualifier: `Record` is a
# DNS record under domainView.records, `Symbol` is a code symbol under
# Answer.symbols. `Stream` is the fourth — the MQ stream at
# /v1/mq/streams, whose `config` the go row already calls StreamConfig.
#
# NOT MAPPED HERE, for the reason the java, php and ruby rows each give in
# their own generator's terms, and Dart makes it four. The document
# carries `iam.ListResponse` and `iam.listResponse`, two schemas differing
# only in case; both Pascal-case to IamListResponse, the second write
# wins, and GET /v1/iam/keys — which returns `{keys: [iam.Key]}` —
# deserializes into the SCIM shape with every field null and no error.
#
# Mapping either one is worse than the defect. The Dart generator DOES
# apply a mapping to the file as well as the class, which php's does not,
# and that looked like the language that could finally say it: measured,
# `iam.ListResponse=IamKeyList` writes iam_key_list.dart and leaves the
# other schema in iam_list_response0.dart — but the `part` list in
# api.dart still names iam_list_response.dart, so the file is orphaned and
# nothing in it resolves. 11 errors, and the library does not load.
# Mapping the second one too moves the same orphan onto the other name.
# It belongs upstream, where two schemas would stop differing only in case.
model-name-mappings: Exception=ErrorBody,File=ModelFile,Record=DnsRecord,Stream=MqStream,Symbol=CodeSymbol
# rust WAS the first departure, and the note here said a template is a FILE
# that must sit beside the invocation, so the whole thing lived in
# hanzo-rs/sdk's own scripts/generate.sh. Right about the risk, wrong about
# which call: the invocation is generate.py's emit(), so `templates:` puts the
# override and its flags one directory apart in this repo. See templates/rust/.
#
# WHAT THE DEPARTURE COST, measured rather than predicted — the exact drift it
# left to prevent, in the repo that left. That script defaulted to
# SPEC_REPO=hanzoai/openapi, `hanzo.yaml`, ref `main`, while the `.spec-lock`
# beside it named hanzo-inc/cloud, `openapi.yaml`, at a commit. Two statements of
# which document this client projects, disagreeing, and only one reachable by a
# hand run. It also re-derived the jar fetch, the YAML-to-JSON conversion,
# --skip-validate-spec and the drift check — the same four copies of this
# driver's logic that retired go's.
#
# The row reproduces it exactly: regenerated at the ref that lock named
# (cloud@5126939), `generate.py rust --check` reports clean — byte-for-byte the
# tree the 155-line script produced — `wc -l` on that script at the commit
# whose output was compared, and its maximum across its whole history.
#
# The row that had been deleted BEFORE that would not have compiled, and none
# of it survives here: no type mapping, no template, `useSingleRequestParameter`
# (which changes every call signature), `packageVersion: 8.0.0` (the SPEC's
# version — the crate ships its own), and it named `crates/hanzo-cloud`, which
# this repo does not have.
#
# Canonical repo is hanzo-rs/sdk; `hanzoai/rust-sdk` is a rename redirect to
# it, which is why `repo` reaches out of ~/work/hanzo to a sibling org dir.
rust:
repo: ../hanzo-rs/sdk
generator: rust
# The crate root owns Cargo.toml and README.md — they carry the version this
# repo publishes and a description no generator can know — so the generated
# tree is src/ and nothing else.
take: { src: crates/hanzo-client/src }
templates: templates/rust
properties:
packageName: hanzo-client
library: reqwest
supportAsync: "true"
# One enum per operation per response code, where a plain enum carries the
# same information; at 2479 operations the multi-response form is thousands
# of types nobody names.
supportMultipleResponses: "false"
# The document's integers are plain `integer`; reading them as unsigned
# would make a negative a parse error rather than a value.
preferUnsignedInt: "false"
flags:
# The rust generator maps a binary request body to std::path::PathBuf and
# then emits `.body(that)` — but reqwest::Body has no From<PathBuf>, so
# those operations do not compile. Bytes are what `.body()` takes anyway.
# The lowercase key `file` is the one that works; `binary` and `File` are
# silently ignored.
#
# This is HALF the correction: it fixes the inner type and cannot fix the
# Option around it. 16 operations take an OPTIONAL octet-stream body, so
# the parameter arrives as Option<Vec<u8>> and reqwest::Body has no
# From<Option<_>> either. That is what templates/rust wraps in
# `if let Some(..)`, and no type mapping reaches it because the problem is
# the Option and not the type. Measured on this document: 16 binary request
# bodies, all 16 optional, and the guard appears 16 times in the output.
type-mappings: file=Vec<u8>
# The collision the python, java, kotlin, go and ruby rows all carry,
# spelled for THIS language. ONE schema spells two of its fields twice —
# o11y.GettableAgentCheckIn has `integration_config` AND `integrationConfig`,
# `removed_at` AND `removedAt`, the snake pair being live wire kept for
# older agents. Rust snake_cases a property to get its field name, so each
# pair lands on one identifier declared twice and the struct does not
# compile: 12 errors, E0124 + E0062 + E0308, all in that one model.
#
# Snake-spelled here where the kotlin row spells them camel, for the reason
# that row gives: the value is an identifier in the TARGET language. The
# wire is untouched — serde still carries the document's key, so both
# spellings still round-trip. Delete these the day cloud drops the pair.
name-mappings: integration_config=integration_config_legacy,removed_at=removed_at_legacy
swift:
repo: swift-sdk
# swift6, not swift5, and the difference is the LANGUAGE rather than taste.
# It emits `async throws(ErrorResponse)` — typed throws, which only Swift 6
# parses — and a Package.swift declaring `swiftLanguageModes: [.v6]` to
# compile it. It also carries its own JSONValue for a free-form object, so
# the client has NO external dependency, where swift5 models an untyped
# field as Flight-School AnyCodable and makes every consumer take that
# package. Measured on this document: 192 API classes, 2460 models, 2502
# call sites, 0 dependencies.
generator: swift6
# The client is Sources/Hanzo/{APIs,Models,Infrastructure} plus Package.swift,
# and the manifest is TAKEN for the reason the ruby gemspec is: it states no
# fact this row does not — the module name is `projectName`, the target path
# follows from it, the dependency list is empty — while it does state one
# only the generator knows, the language mode its own output needs. Written
# by hand it would put `swift-tools-version` in a second home, where a
# generator that starts emitting Swift 7 stops compiling and says why in
# neither place.
take: { Sources/Hanzo: Sources/Hanzo, Package.swift: Package.swift }
# ONE FILE, SIX LINES, and without them this client compiles on Apple
# platforms and nowhere else — which on a fleet whose every runner is linux
# means it can never be compiled by anything that gates it. Three
# corrections, all in the generator's own transport, all measured by
# building:
#
# • `#if !os(macOS) / import MobileCoreServices` is TRUE on Linux, where
# that framework does not exist. `canImport` is the question the guard
# was trying to ask.
# • URLSession, URLRequest and URLResponse are named without importing
# FoundationNetworking, which is where corelibs keeps them off Apple
# platforms — 69 error sites in that one file, counted by removing the
# import and building.
# • `mimeType(for:)`'s pre-macOS-11 branch calls four MobileCoreServices C
# functions unguarded. Linux never REACHES that branch — `#available`
# is true for a platform it does not name, so the UniformTypeIdentifiers
# side returns first — but it still has to compile, and it does not: 5
# more errors. Guarding the body leaves the same value returned.
#
# All three ask `canImport`, so on the four platforms Package.swift declares
# they leave the same code standing — macOS additionally imports
# MobileCoreServices, which is present there and whose symbols the pre-11
# branch already calls. The emitted file is not the jar's byte for byte; what
# is preserved is which branch Apple compiles. Everything else comes from
# the jar: the generator reads this directory first and falls back, so the
# override is this file and no more.
templates: templates/swift
properties:
# The module a caller imports. It also names the target, the product, the
# source path and the configuration type (HanzoAPIConfiguration.shared).
projectName: Hanzo
# async/await, not the generator's default completion-handler pair, which
# is what makes an operation read `try await KeysAPI.getKeys()`. The
# callback form is still there underneath — every method has a
# `…WithRequestBuilder` twin — so nothing is given up by choosing it.
responseAs: AsyncAwait
flags:
# THE COLLISION EVERY ROW CORRECTS, in the language where it is a build
# failure rather than a silent drop. o11y.GettableAgentCheckIn declares
# `integration_config` AND `integrationConfig`, `removed_at` AND
# `removedAt` — the snake spellings are live wire, published so older AWS
# agents keep working. Swift camel-cases a property into a stored
# property, so each pair lands on one `public var` declared twice, which
# is "invalid redeclaration" and the module does not build at all.
#
# CAMEL values, where python, ruby and php spell theirs snake: this
# generator takes the mapping VERBATIM — measured, `removed_at_legacy`
# arrives as `public var removed_at_legacy` beside 2459 camel-cased
# siblings — so the value has to be spelled the way the TARGET language
# spells an identifier, as it is for go. The wire is untouched either way:
# CodingKeys still carry `removed_at` and `integration_config`, so both
# spellings still round-trip under their own key.
name-mappings: removed_at=removedAtLegacy,integration_config=integrationConfigLegacy
# FOUR NAMES SWIFT'S OWN MODULES OWN, each measured — a struct of that
# name declared beside the code that uses Swift's, then compiled — rather
# than taken off a list of keywords.
#
# `Result` and `Task` are build failures INSIDE the client: the generated
# transport writes `Result<URLRequest, Error>` and
# `Task.checkCancellation()`, and a module-level struct wins lookup over
# Swift's own — "cannot specialize non-generic type 'Result'", "type
# 'Task' has no member 'checkCancellation'".
#
# `Sequence` and `Stream` build here and break the CONSUMER, and not in
# the same way. The client's `Sequence` wins over the STDLIB's protocol in
# their file, silently, so their own `T: Sequence` stops compiling —
# "constrained to non-protocol, non-class type 'Sequence'". `Stream` ties
# with Foundation's class instead, which is the honest failure:
# "'Stream' is ambiguous for type lookup in this context".
#
# `Model<Name>` is the spelling the java generator picks for itself and
# the kotlin, ruby and php rows state, so one schema keeps one name across
# the fleet; a type name is never on the wire.
#
# `File` is deliberately NOT mapped though ruby and php map it: Swift has
# no File in its prelude or in Foundation, so nothing collides. Those two
# rows correct their own generators, not this schema.
model-name-mappings: Result=ModelResult,Sequence=ModelSequence,Stream=ModelStream,Task=ModelTask
# THE ONE SHAPE SWIFT CANNOT HOLD, and it is the language and not the
# document: a struct may not store a value of its own type, because the
# storage that implies has no size. Two schemas do exactly that —
# `FlowAction.nextAction` and `o11y.FilterAttributeValueResponse.relatedValues`
# — and each is a hard "value type cannot have a stored property that
# recursively contains it", so the module does not build. Two more,
# `TreeFile` and `treeNode`, recurse THROUGH AN ARRAY, which is a heap
# buffer and therefore legal; they need nothing, which is why this maps
# two names and not four.
#
# Mapping them onto the generator's own free-form JSON type retypes the
# five properties that name them and leaves both models generated with
# every other field typed — and `JSONValue` is already what 282 models
# hold for an object whose shape the document does not state, so the
# recursive branch becomes the same thing as every other undescribed one.
#
# The alternative is `useClasses: true`, and it is a GLOBAL switch: all
# 2460 models become final classes — reference semantics for a decoded
# record, and the conformance list loses Sendable, which is what lets a
# @MainActor caller keep a model it awaited (measured: with structs that
# caller type-checks clean). Five untyped properties is the smaller loss.
#
# The real home for this is the box openapi-generator's swift template
# does not emit for a recursive property; until it does, this is the only
# lever that is data.
type-mappings: FlowAction=JSONValue,o11y.FilterAttributeValueResponse=JSONValue
# cpp WAS the last departure, and it gave two reasons. Templates stopped being
# one when `templates:` landed — the override sits at templates/cpp-restsdk/,
# one directory from the flags it goes with, exactly as rust's does. The
# GENERATOR VERSION was the other, and it is gone too, for a reason worth
# stating precisely: it was never a fact about cpp-restsdk.
#
# The floor said 7.24.0, because on 7.14.0 ten translation units would not
# compile through two defects in the generator's type resolution that no
# template reaches. Both defects were real. Both lived in SCHEMAS THE DOCUMENT
# NO LONGER CARRIES — `vector_NamedVectors`/`vector_DenseVector` under
# `additionalProperties` (9 TUs) and `framework_Document.docstatus`, an
# integer-backed enum declared over utility::string_t and defined over int32_t
# (1 TU). Neither schema exists at the ref this fleet reads, so neither defect
# is reachable, and the floor expired with the document that caused it.
#
# Re-measured at that ref rather than argued: 7.14.0 and 7.24.0 emit the SAME
# library. Ignoring blank lines and the generator string each file's header
# carries, exactly one of 5327 files differs, and the difference is a doc
# comment on ConsoleSettings::getOidcConfig. On 7.14.0 the library builds with
# 0 errors (2663 sources) and the six example flows build with 0 errors. So
# this row needs no version of its own and does not raise the pin.
#
# What the departure cost, the same way rust's did: hanzo-cpp/sdk's own
# generate.sh defaulted to SPEC_REPO=hanzoai/openapi, `hanzo.yaml`, ref `main`
# — a document named by something that moves — while the repo's hanzo.yml
# already declared hanzo-inc/cloud's `openapi.yaml`, and there was no .spec-lock
# at all. Two statements of which document this client projects, disagreeing.
# It also re-derived the jar fetch, the YAML-to-JSON conversion,
# --skip-validate-spec and the drift check.
#
# Canonical repo is hanzo-cpp/sdk; `hanzoai/cpp-sdk` is a rename redirect to
# it, which is why `repo` reaches out of ~/work/hanzo to a sibling org dir.
cpp:
repo: ../hanzo-cpp/sdk
generator: cpp-restsdk
# The repo root owns CMakeLists.txt, cmake/, examples/ and templates/ — the
# hanzo::hanzo target, the export set and the examples switch are its
# contract with a consumer — so the generated tree is include/ and src/ and
# nothing else. The generator's own CMakeLists.txt, Config.cmake.in,
# git_push.sh and README.md sit at the output root, under neither take, so
# they are never copied and need no `drop` entry.
take: { include: include, src: src }
# Four overrides, each hunk commented in place. They correct five defect
# classes in the generator's codegen and say nothing about this API: oneOf
# explicit instantiations emitted HTML-escaped; oneOf visitors calling
# members the alternatives do not have; a oneOf class standing outside
# ModelBase and so unusable as a FIELD of another model; a free-form
# `object` held by value with no ModelBase overload; and an API response
# container resolved separately from its element type.
templates: templates/cpp-restsdk
# Same defect the java and kotlin rows correct: the generator skips a
# multipart form model and still imports it from the API class that used it,
# so FunctionsApi would include a header nobody emitted.
global:
skipFormModel: "false"
properties:
# The include root is `hanzo/`, and the namespaces are hanzo::api and
# hanzo::model — the same ai.hanzo.cloud split the JVM rows use, spelled
# as C++ spells a namespace.
packageName: hanzo
apiPackage: hanzo.api
modelPackage: hanzo.model
# packageVersion is deliberately absent, and it is measured rather than
# assumed: set to a sentinel it appears in the generator's own README and
# in NO file under include/ or src/. Stating it here would put the
# library's version in a second home that reaches nothing; CMakeLists.txt
# is its one home, and hanzo.yml's client lane reads it from there.
flags:
# THE SAME COLLISION the python, go, java, kotlin, ruby and php rows all
# correct. o11y.GettableAgentCheckIn declares `integration_config` AND
# `integrationConfig`, `removed_at` AND `removedAt` — the snake spellings
# are live wire, published so older agents keep working. C++ camel-cases a
# property into its accessor, so each pair lands on one method declared
# twice: "cannot be overloaded", 5 errors, and the library does not build.
#
# Snake values, like the python, ruby and php rows, because the generator
# re-cases them into the accessor either way. The wire is untouched:
# measured, all four keys are present in the emitted model, each still
# serialized under the document's own name.
name-mappings: removed_at=removed_at_legacy,integration_config=integration_config_legacy
# ruby WAS absent, and the note here said so because the row that had been
# deleted named a repo to write into and a gem to ship and neither existed.
# Both do now — hanzo-ruby/sdk and `hanzoai` on rubygems — so the row above is
# the claim again. What the note got wrong is worth keeping: it read
# `hanzoai/ruby-sdk` on GITHUB, where it 404s, and concluded there had never
# been a Ruby client. There is one on the FORGE, and a mirror answering 404 is
# not evidence about the thing it mirrors.
#
# That tree is not this one and nothing moved across. It is a projection of a
# different document — a retired vendor's spec URL, 188 endpoints against this
# document's 2479 — carrying its own hand-written transport, and rubygems
# never served it. Keeping any of it would mean a second HTTP stack and a
# second set of names for one API, hand-maintained beside generated code. What
# transferred is the three facts it had already settled and this row now
# states: the gem is `hanzoai`, the module is `Hanzoai`, the floor is Ruby 3.