Skip to content

feat(sdks): Stainless-style SDK fleet — 7 → 11 generated clients + real publishing - #567

Closed
jadenfix wants to merge 9 commits into
mainfrom
claude/sdk-stainless-style-uypmmg
Closed

jadenfix wants to merge 9 commits into
mainfrom
claude/sdk-stainless-style-uypmmg

Conversation

@jadenfix

@jadenfix jadenfix commented Jul 3, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Grows the contract-driven SDK fleet from 7 to 11 fully-generated
"Stainless-style" clients
by adding Ruby, PHP, C#/.NET, and Kotlin, and
wires real publishing for every new registry. Each new language is added
end-to-end — generator config, committed generated client, live conformance
program, and lockstep updates to every drift/parity gate — through the existing
sdks/openapi/beater-api.json → sdks/clients/<lang>/ pipeline. The existing 7
clients and the hand-written Layer-2 ergonomic/instrumentation SDKs are
untouched.

Delivered as 6 focused commits (one per language + publishing + a drift fix):

  • Ruby (8th), PHP (9th), C#/.NET (10th), Kotlin (11th)
  • feat(sdks): wire real publishing for Ruby, PHP, C#/.NET, and Kotlin
  • fix(sdks): stop committing the Kotlin composite-build gradle cache

Type of change

  • Bug fix
  • New feature
  • Docs / examples only
  • Refactor / internal
  • CI / tooling

Contract checklist (required)

  • This PR does not change a /v1 endpoint, request/response type, span
    kind, or attribute — the OpenAPI spec is byte-identical to main; this PR
    only adds new language targets to the generation pipeline.
  • It does, and I regenerated everything in the same change:
    • scripts/regen-sdks.sh — all clients regenerate cleanly (now 11,
      not 7; the count is updated across gates and docs in this PR)
    • cargo xtask regen-spec (n/a — spec unchanged)
    • cargo xtask regen-semconv (n/a — conventions unchanged)
  • scripts/check-contract-sync.sh passes locally (no drift): "No drift:
    API, 11 SDKs, MCP tools, docs, and conventions are all in sync."

Tests

  • cargo build --workspace / cargo test --workspace — not run; no
    Rust source in crates/ changed. The contract gate did compile+run
    cargo test -p beater-api --test openapi_coverage and cargo xtask regen-semconv, both green.
  • cargo clippy --workspace --all-targets — not run (no Rust source changed).
  • Added/updated tests: new sdks/conformance/{ruby,php,csharp,kotlin} live
    conformance programs (wired into e2e-clients-live.sh +
    surface-manifest.json), and the check-contract-sync self-test updated.

Verified locally: scripts/check-contract-sync.sh (11 SDKs, zero drift),
check-sdk-parity-contract.py, check-feature-matrix-contract.py, the
contract-sync self-test, and live conformance against a running beaterd for
Ruby, PHP, and Kotlin (health ok → createDataset → traces.list round-trip).

Notes for reviewers

  • Stainless-style, in-repo. The repo is already Stainless-style in the
    meaningful sense (one spec → one ergonomic, tag-grouped package per language via
    pinned openapi-generator v7.11.0). This PR extends that; it is not a generator
    swap. The 4 new languages are generated-only (like C/C++ today) — no Layer-2
    ergonomic package — so EXPECTED_ERGONOMIC stays at 5 and only
    EXPECTED_GENERATED moves 7 → 11.
  • Dropped a planned EvaluatorKind discriminator change. utoipa 5.5 rejects
    discriminator on an internally-tagged enum with inline variants, and it turned
    out unnecessary — all 4 new generators handle the discriminator-less oneOf,
    and C++ already patches it via the existing reproducible-patch mechanism.
  • BEATER_OPENAPI_GENERATOR_JAR (opt-in) added to regen-sdks.sh. Docker
    stays the default (CI unchanged); setting this env var runs the same pinned
    7.11.0 JAR via a local JVM, producing byte-identical output (verified against
    the existing ts/go clients). It removes the hard Docker-Hub dependency for
    restricted networks. The 4 new clients were generated this way — the first
    sdk-contract CI run (Docker path) is the real cross-check; identical output is
    expected.
  • C#/.NET live conformance is CI-gated, not run locally (no dotnet
    toolchain in the dev env). Generation is verified deterministic (two
    regenerations byte-identical; .sln/.csproj GUID pinned).
  • Publishing (release.yml + publish-sdk.sh) now covers all 11 languages
    and no-ops with a clear SKIP until each token is added as a repo secret. New
    secrets needed to actually publish: RUBYGEMS_API_KEY, NUGET_API_KEY,
    optionally PACKAGIST_USERNAME/PACKAGIST_API_TOKEN (Kotlin reuses OSSRH_*).

🤖 Generated with Claude Code


Generated by Claude Code

claude added 6 commits July 3, 2026 21:36
Adds a fully-generated Ruby control-plane client to the contract-driven SDK
fleet, taking it from 7 to 8 languages. Generated from the single OpenAPI
spec by the pinned openapi-generator v7.11.0, committed in-repo, and enforced
by the existing zero-drift + parity gates.

- sdks/config/ruby.yaml: faraday library, gem `beater_client`, module
  `BeaterClient`, hideGenerationTimestamp for deterministic output.
- sdks/clients/ruby: committed generated client.
- sdks/conformance/ruby: live conformance program (health + createDataset +
  listTraces round-trip) modeled on the Python harness; wired into
  e2e-clients-live.sh and surface-manifest.json.
- Drift/parity gates moved 7 -> 8: regen-sdks.sh LANGS, check-sdk-parity
  EXPECTED_GENERATED, check-contract-sync + self-test, feature-matrix checker,
  sdk-contract workflow step names, and the canonical docs.

Also adds an opt-in local-JAR backend to regen-sdks.sh
(BEATER_OPENAPI_GENERATOR_JAR): the generator runs via a local JVM instead of
the Docker image, producing byte-identical output (verified against the
existing clients). Docker stays the default so CI is unchanged; this only
removes the hard Docker-Hub dependency for environments where the registry
is unreachable.

Verified: scripts/check-contract-sync.sh (8 SDKs, zero drift),
check-sdk-parity-contract.py, check-feature-matrix-contract.py, and the
contract-sync self-test all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
Adds a fully-generated PHP control-plane client, taking the contract-driven
fleet from 8 to 9 languages. Generated from the single OpenAPI spec by the
pinned openapi-generator v7.11.0, committed in-repo, and enforced by the
existing zero-drift + parity gates.

- sdks/config/php.yaml: invokerPackage Beater\Client, composerPackageName
  beater/client, hideGenerationTimestamp for deterministic output.
- sdks/clients/php: committed generated client (composer package beater/client).
- sdks/conformance/php: live conformance program (health + createDataset +
  listTraces round-trip) via a Composer path repository; wired into
  e2e-clients-live.sh and surface-manifest.json. Test-time vendor/ and
  composer.lock are gitignored.
- Drift/parity gates moved 8 -> 9: regen-sdks.sh LANGS, check-sdk-parity
  EXPECTED_GENERATED, check-contract-sync + self-test, feature-matrix checker,
  sdk-contract workflow step names, and the canonical docs.

Verified: scripts/check-contract-sync.sh (9 SDKs, zero drift), parity +
feature-matrix + self-test gates, and a live PHP conformance round-trip
against beaterd (health ok, createDataset -> Dataset, traces.list ->
PageRunSummary).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
Adds a fully-generated C#/.NET control-plane client, taking the contract-driven
fleet from 9 to 10 languages. Generated from the single OpenAPI spec by the
pinned openapi-generator v7.11.0 (`csharp` client generator, httpclient
library, net8.0), committed in-repo, and enforced by the zero-drift + parity
gates.

- sdks/config/csharp.yaml: packageName Beater.Client, targetFramework net8.0,
  library httpclient. packageGuid is pinned and hideGenerationTimestamp is set
  so the generated .sln/.csproj is deterministic (verified: two regenerations
  are byte-identical).
- sdks/clients/csharp: committed generated client (ships the standard VS
  .gitignore for bin/obj).
- sdks/conformance/csharp: live conformance program (health + createDataset +
  listTraces round-trip) that project-references the generated client; wired
  into e2e-clients-live.sh (SKIPs cleanly where the .NET toolchain is absent)
  and surface-manifest.json.
- Drift/parity gates moved 9 -> 10: regen-sdks.sh LANGS, check-sdk-parity
  EXPECTED_GENERATED, check-contract-sync + self-test, feature-matrix checker,
  sdk-contract workflow step names, and the canonical docs.

Verified: scripts/check-contract-sync.sh (10 SDKs, zero drift), parity +
feature-matrix + self-test gates. Live conformance runs in CI where the .NET
SDK is present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
Completes the common-language fleet at 11 generated clients. Generated from the
single OpenAPI spec by the pinned openapi-generator v7.11.0 (`kotlin` generator,
jvm-okhttp4 + moshi), committed in-repo, and enforced by the zero-drift +
parity gates.

- sdks/config/kotlin.yaml: groupId ai.beater, artifactId beater-client-kotlin,
  package ai.beater.client.kotlin, hideGenerationTimestamp for determinism.
- sdks/clients/kotlin: committed generated client.
- sdks/conformance/kotlin: live conformance program via a Gradle composite
  build that includeBuilds the generated client; adds okhttp to the consumer
  classpath (the client's API constructors expose okhttp3.Call.Factory in a
  default parameter). Wired into e2e-clients-live.sh and surface-manifest.json.
- Drift/parity gates moved 10 -> 11: regen-sdks.sh LANGS, check-sdk-parity
  EXPECTED_GENERATED, check-contract-sync + self-test, feature-matrix checker,
  sdk-contract workflow step names, and the docs (ARCHITECTURE.md reconciled to
  11 throughout).

Verified: scripts/check-contract-sync.sh (11 SDKs, zero drift), parity +
feature-matrix + self-test gates, and a live Kotlin conformance round-trip
against beaterd (health ok, createDataset -> Dataset, traces.list ->
PageRunSummary).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
Extends the tag-triggered release pipeline to publish all 11 generated clients.
The four new languages plug into the existing "wired now, secret-gated" pattern:
each target no-ops with a clear SKIP message until its registry token is added
as a repo secret.

- scripts/publish-sdk.sh: new ruby (gem build + gem push), php (Packagist is
  git-tag driven, with an optional update-hook ping), csharp (dotnet pack +
  nuget push), and kotlin (gradle publish to OSSRH, same creds as Java) arms.
  Generated-only clients are versioned at generation time, so no extra stamping
  is needed.
- scripts/regen-sdks.sh: add gemVersion to the release version properties so a
  tagged release actually stamps the Ruby gem version (verified: gemVersion=0.2.0
  -> lib/beater_client/version.rb VERSION = '0.2.0').
- .github/workflows/release.yml: extend the publish matrix to ruby/php/csharp/
  kotlin and pass the new secrets (RUBYGEMS_API_KEY, NUGET_API_KEY,
  PACKAGIST_USERNAME, PACKAGIST_API_TOKEN; Kotlin reuses OSSRH_*).
- sdks/README.md: document the language -> registry -> secret mapping.

Verified: bash -n on both scripts, release.yml parses as YAML, publish-sdk.sh
--validate-version, and each new arm prints its SKIP/tag note when run without
secrets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
The Kotlin conformance runs sdks/clients/kotlin as a Gradle composite included
build, which writes a .gradle/ cache into that directory. Six cache files were
accidentally committed with the Kotlin client; because regen-sdks.sh wipes and
regenerates sdks/clients/kotlin (which never contains a .gradle cache), the
committed cache made `regen-sdks.sh --check` report drift.

Remove the tracked cache and add `sdks/**/.gradle/` to .gitignore (alongside the
existing `sdks/**/build/`) so it cannot be re-committed.

Verified: scripts/regen-sdks.sh --check now reports zero drift across all 11
clients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
@vercel

vercel Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beater Error Error Jul 6, 2026 8:54pm
beater-noll Error Error Jul 6, 2026 8:54pm

Request Review

…sion guard, consumer docs

Applies the findings from the multi-agent review of the 7→11 SDK fleet, in one pass.

Publishing (blocker + hardening):
- Kotlin `gradle publish` was a silent no-op: the generated build.gradle only
  applies `maven-publish` with no publication/repository/signing, so it uploaded
  nothing yet reported success. Add `sdks/config/maven-central.init.gradle` (kept
  out of the regenerated client tree so it can't drift) that wires a real
  MavenPublication + OSSRH repository + in-memory GPG signing at publish time.
  Verified: `gradle --init-script … tasks` now registers
  `publishMavenJavaPublicationToOSSRHRepository`. The kotlin arm invokes the
  client's pinned wrapper and gates on OSSRH_USERNAME + OSSRH_PASSWORD +
  SIGNING_KEY, skipping cleanly (no false success) when any is absent.
- Gate the java and kotlin arms on OSSRH_PASSWORD (not just OSSRH_USERNAME).
- release.yml passes SIGNING_KEY, SIGNING_PASSWORD, OSSRH_URL.

Generation:
- regen-sdks.sh now asserts the local generator JAR is exactly v7.11.0
  (BEATER_OPENAPI_GENERATOR_JAR) — a mismatched generator would silently emit
  divergent clients and break the zero-drift guarantee.

Repo hygiene:
- .gitignore: ignore sdks/**/bin/ and sdks/**/obj/ (the C# conformance leaves
  .NET build output under sdks/clients/csharp when it builds via ProjectReference).

Docs — client-friendly + accuracy:
- sdks/README.md: add a "Quickstart (for consumers)" section — per-language
  install command, import/namespace, and a link to each generated client's own
  README, plus a host+auth snippet and a note that clients default to
  http://localhost (spec is pre-1.0, no servers block).
- Fix stale "7 SDKs" counts the review caught: ARCHITECTURE.md,
  docs/sdk-platform-architecture.md, docs/architecture-status.md, GOVERNANCE.md,
  SECURITY.md, and .github/PULL_REQUEST_TEMPLATE.md → 11.

Verified: check-contract-sync.sh (11 SDKs, zero drift), parity, feature-matrix,
contract-sync self-test, docs-walkthrough, and the Kotlin publish-task
registration all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
…taging, .NET 8 pin

Addresses the follow-up (round 2) review of commit 076e40c.

Docs correctness:
- sdks/README.md quickstart: the Python snippet set cfg.api_key["ApiKeyAuth"],
  but the spec declares no securitySchemes, so the generated clients never send
  an auth header — the line was misleading. Remove it and add an explicit note
  that auth is not modeled in the contract yet (adding servers + securitySchemes
  in crates/beater-api is the follow-up that makes it work across all 11 clients).

Publishing honesty (Maven Central path):
- Drop the baked-in default OSSRH_URL (the legacy s01.oss.sonatype.org host is
  sunset); OSSRH_URL is now required and the kotlin arm skips cleanly without it.
- Document in the init script + README that `publish` uploads SIGNED artifacts to
  a STAGING repo — a release still needs a close+release / Central Portal
  promotion, so a green release means "staged", not necessarily "live on Central".
- release.yml: pin .NET 8 via actions/setup-dotnet@v4 (first-party, pins-gate-safe)
  so the csharp `dotnet pack -c Release` (net8.0) doesn't break when the runner's
  default SDK moves.

Left as intentional follow-ups (user infra / contract decisions, shared with the
pre-existing java arm): automated Central close+release promotion, and adding
servers/securitySchemes to the OpenAPI for real SDK auth.

Verified: bash -n, action-pins gate, release.yml YAML, feature-matrix,
docs-walkthrough, and the Kotlin init script still registers
publishMavenJavaPublicationToOSSRHRepository with OSSRH_URL set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsWPsZsDkwGnhGTCZhMkep
@jadenfix

jadenfix commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

Non-author review (automated): release-safe & MERGE-READY, but push back on the 4 new languages.

Release safety (the critical question) — SAFE, no auto-publish on merge: release.yml triggers only on push: tags: v* + workflow_dispatch (no push: branches: main). Merging cannot publish. Double-gated: publish needs: [conformance, verify-contract]; every target no-ops with SKIP when its registry secret is absent; all creds via ${{ secrets.* }} (none hardcoded), written with umask 077 + trap cleanup; SemVer-validated. Committing generated clients is the established design (the sdk-contract zero-drift gate requires it); .gitignore correctly excludes build artifacts and none leaked (verified). Docs stay honest ("registry publish planned", "readiness ≈33%"), counts consistent across all docs/scripts.

Blocking concern (minimalism): the 4 new languages (Ruby, PHP, C#, Kotlin) are added with no stated demand — this is the one thing that violates the no-speculative-code discipline. They're generated-only (low marginal maintenance, symmetric with C/C++), but they add 4 registry-publish paths + 4 CI toolchains for languages with no known users. Please either (a) provide the demand signal, or (b) drop them — the diff is cleanly split into 4 per-language commits, so landing the 7-client hardening now and deferring the 4 languages behind real demand is trivial and preferred under "more code != better."

Nits: local generation used the JAR path (not Docker) — confirm the first sdk-contract CI (Docker regen) is byte-green before merge; C#/.NET live conformance is CI-gated only (no dotnet locally).

@jadenfix

jadenfix commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

Non-author review — APPROVE, pending rebase 🔁 (reviewer subagent did not author this PR)

  • Clean, contract-respecting: 4 new clients (ruby/php/csharp/kotlin) are generated (.openapi-generator/VERSION 7.11.0+FILES manifest), spec byte-identical to base, lockstep updates to check-contract-sync.sh (7→11), sdk-contract.yml, CLAUDE.md, check-sdk-parity-contract.py (EXPECTED_GENERATED=11, EXPECTED_ERGONOMIC=5). Publish arms all skip() cleanly without tokens.
  • Minor: PHP Packagist token as query param (prefer header); C# conformance CI-gated only.
  • ⚠️ Merge-order: once feat(api): expose RSI gate as /v1 gateOptimizationCandidate operation #549 (new gateOptimizationCandidate /v1 op) lands, rebase onto main + re-run scripts/regen-sdks.sh so the RSI op is generated into all 11 clients, else check-contract-sync fails on main. Approved to merge after rebase+regen.

@jadenfix

jadenfix commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Coordination note for the next agent turns: the ecosystem root now has a compatibility/uniformity control plane (ecosystem.toml, AGENTS.md, docs/ecosystem-pipeline.md, docs/architecture-uniformity.md, and scripts/ecosystem-pipeline.sh).

Please make the next sequence of work compatible with that pipeline before expanding feature scope. Focus on the repo-local migration queue: latest stable Rust target, Rust 2024/MSRV alignment, package/license/repository metadata, storage policy, thin-binary architecture, generated-contract discipline, package sync, and E2E capability hooks.

Backward compatibility is not required pre-adoption; prefer the clean uniform breaking migration over shims/aliases. From the ecosystem root, use scripts/ecosystem-pipeline.sh report as the drift list and only delete migration tasks once the relevant checks pass.

@jadenfix

jadenfix commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Migration priority escalation: this PR still does not appear to be the Tempera uniformity migration we need.

Next agent sequence should treat ecosystem migration as first-priority work before continuing feature scope. If this PR cannot be cleanly converted, open a new PR from this repo with a title like:

[codex] Tempera ecosystem migration: <product>

Required first slice:

  • update repo-local AGENTS.md / CLAUDE.md with the canonical Tempera naming and top-repo direction;
  • align GitHub repo metadata/remotes with jadenfix/ecosystem;
  • start resolving scripts/ecosystem-pipeline.sh report findings for this repo;
  • keep Rust-first, best-language-wins, SOTA engineering standards from docs/ecosystem-pipeline.md;
  • prefer breaking cleanup over compatibility shims because these packages are pre-adoption.

Do not delete migration queue items until this repo passes the relevant ecosystem checks. The source of truth is https://github.com/jadenfix/ecosystem on main.

jadenfix commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Closing this to reduce conflicting SDK/API surface work while the ecosystem API contract gate lands in jadenfix/ecosystem#2.

This PR is currently DIRTY and changes a broad generated SDK fleet on top of the old contract shape. The shared target is now the data-engine-style contract: project-scoped /v1 resources, dotted projects.<collection>[.<subcollection>].<verb> operationIds, shared error envelope, cursor pagination, Operation, Idempotency-Key, and patch If-Match/update_mask.

The SDK fleet work should be recut after Palette's OpenAPI is migrated to that target shape, so generated clients are not immediately invalidated by the contract migration. This is a coordination close, not a rejection of the SDK direction.

@jadenfix jadenfix closed this Jul 9, 2026

This branch had an error being deployed

2 failed deployments
Preview – beater-noll — 33be32df Deployed Jul 6, 2026 by vercel[bot]
Preview – beater — 33be32df Deployed Jul 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants