Skip to content

build: bump openapi-generation to license token validation; elect the commercial license - #2124

Open
ThomasRooney wants to merge 13 commits into
mainfrom
build/bump-generator-license-election
Open

build: bump openapi-generation to license token validation; elect the commercial license#2124
ThomasRooney wants to merge 13 commits into
mainfrom
build/bump-generator-license-election

Conversation

@ThomasRooney

@ThomasRooney ThomasRooney commented Aug 27, 2026

Copy link
Copy Markdown
Member

Why

  • The platform issues target-scoped license tokens (speakeasy-registry#4708, live) and the generator validates them offline (openapi-generation#55); the CLI has to attach them to every generation.
  • The CLI generates only under the customer's commercial license. The AGPL election is a source-build fallback in the upstream generator and is not used by the CLI.
  • On main, the paid-tier "not in the approved SDK target list" warn path has produced AGPL-stamped output since the generation-context change.

What Changed

Dependencies: openapi-generation/v2 v2.933.1 → v2.934.0 (offline license validation with target-scoped tokens), generation-context v1.0.0 → v1.1.0 (WithDirect no longer implies AGPL; the election is explicit). Uses speakeasy-core v0.24.0 (access.CheckGenerationAccess exposing AccessDetails.license_jwt) and speakeasy-client-sdk-go/v3 v3.28.1, already on main.

  • Every generation elects commercial (internal/sdkgen.withGenerationContext) and attaches the authenticated context's license token; the generator validates signature, expiry, and target coverage offline. /v1/auth/validate issues the token for every tier (speakeasy-api/speakeasy-registry#4709: commercial ["*"] at 30d, free ["*"] at 24h), so the context token is authoritative and the access check's token is not consulted.
  • No token: the election is still commercial and the generator rejects the run as an unproven commercial election.
  • Blocked access (Level == blocked) aborts before generation, unchanged.
  • Result vs main: the paid-tier warn path produces commercial output again. Free workspaces get commercial output for their one language; a second language is blocked by the access check.
  • lint --dry-run elects commercial with the context's token. A dry run that fails before producing any warnings (unauthenticated, or a rejected election) skips that target, and the summary names the skipped targets instead of reporting "no warnings found".
  • Standalone generate codeSamples and generate usage — dead on main (ErrMissingGenerationAccess, no generation state ever established) — now elect through the same helper: they work when authenticated and fail with a clear "unauthenticated" error otherwise.
  • internal/run performs no election of its own (the workflow-level AGPL/direct election is removed); election happens in internal/sdkgen. Validation elects authenticated commercial for authenticated callers (keeping SDK access, e.g. link shortening) and direct OSS mode otherwise; it attaches no license token, since it produces no licensed output and an invalid token must not fail read-only diagnostics.

Rollout prerequisite: the deployed platform must issue license_jwt from /v1/auth/validate for every allowed tier before this ships — a platform that authenticates without issuing a token now produces a hard generator rejection (unproven commercial election) where main fell back to AGPL. Hosted prod satisfies this as of speakeasy-api/speakeasy-registry#4709 (deployed); older self-hosted/staging registries are the population at risk.

Follow-up: #2127 adds offline license authentication on top of this.

Testing

  • go build ./...; unit suite (excluding integration) green; golangci-lint clean on touched packages.
  • Unit coverage: election (internal/sdkgen/generation_context_test.go).
  • Prod smoke in a sandboxed $HOME during development: speakeasy-self run → commercial output, no warning. Free workspace run -t go → commercial output; run -t typescript → "Upgrade Required … exceeded the limit of one free generated SDK", blocked.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Running ultrareview automatically — High-risk licensing change: wiring the CLI to the new license-token validation changes AGPL vs commercial election across all generation paths; a missed bug could mislicense outputs or break authenticated runs.. I'll post findings when complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview completed in 7m 55s

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread internal/sdkgen/generation_context_test.go Outdated
Comment thread go.mod Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread internal/sdkgen/sdkgen.go
ThomasRooney added a commit that referenced this pull request Aug 28, 2026
…2126)

Dependency bump only, split out of #2124 so the SDK/core change ships
and is verified on its own before the generator change.

- `speakeasy-client-sdk-go/v3` v3.28.0 → **v3.28.1** (regenerated from
the registry spec after speakeasy-registry#4708: `AccessDetails` gains
`license_jwt` and `status_code`; no signature changes).
- `speakeasy-core` v0.23.0 → **v0.24.0** (`access.CheckGenerationAccess`
returns the full access decision including the platform-issued license
token; `HasGenerationAccess` unchanged).

No CLI behaviour change: nothing reads the token yet. #2124 (generator
pin + commercial-on-token wiring) rebases on top once
openapi-generation#55 is released.

Guards from #2121 still apply: `internal/sdk/sdk_test.go` asserts
`x-api-key` on `Artifacts.PostTags`/`GetRevisions`; `TestRegistryFlow`
ends with `tag promote`.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Bumps `speakeasy-core` to v0.24.0 and `speakeasy-client-sdk-go/v3` to
v3.28.1. These upgrades expose a platform-issued license token and new
`AccessDetails` fields (`license_jwt`, `status_code`), but nothing reads
them yet, so CLI behavior is unchanged.

<sup>Written for commit 943cae8.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/speakeasy-api/speakeasy/pull/2126?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 5dbba26 to 07519df Compare August 28, 2026 11:56

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/sdkgen/sdkgen.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 13 files (changes from recent commits).

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread internal/model/command.go Outdated
Comment thread internal/run/run.go Outdated
Comment thread internal/config/config.go Outdated
Comment thread internal/config/config.go Outdated
Comment thread cmd/generate/sdk.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 17 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread cmd/run.go
Comment thread internal/model/command.go Outdated
Comment thread internal/config/config.go Outdated
Comment thread internal/config/offline_license_test.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/run/target.go Outdated
Comment thread cmd/configure.go Outdated
Comment thread internal/sdkgen/sdkgen.go Outdated
Comment thread internal/run/run.go Outdated
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 588a056 to 532bfa2 Compare August 28, 2026 23:18

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 15 files (changes from recent commits).

Confidence score: 2/5

  • internal/model/command.go conflates --force for run and generate sdk with an authentication refresh, causing valid offline licenses to be bypassed; keep generation forcing separate from authentication refresh handling.
  • internal/auth/auth.go returns an SDK-less context for valid offline-token users on every RequiresAuth command, so status, billing activate, and configure can fail despite valid credentials; ensure offline authentication supplies the required SDK context or narrowly scopes the restriction.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/auth/auth.go">

<violation number="1" location="internal/auth/auth.go:74">
P2: When a valid offline token is used without an API key, `CommandContext` returns an SDK-less context for every `RequiresAuth` command. Commands such as `status`, `billing activate`, and configure then fail with a missing-API-key error instead of authenticating online; restrict this path to offline-capable commands or ensure platform authentication before online commands.

(Based on your team's feedback about SDK-less offline command contexts.)</violation>
</file>

<file name="internal/model/command.go">

<violation number="1" location="internal/model/command.go:140">
P1: When `--force` is used on `run` or `generate sdk`, this treats the generation-control flag as an authentication refresh request and bypasses valid offline licenses. Keep generation force separate from authentication refresh, using a dedicated auth-refresh signal instead.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/model/command.go Outdated
Comment thread internal/auth/auth.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread cmd/lint/lint.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/sdkgen/sdkgen.go Outdated
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 8c57c76 to ba6ef5c Compare August 28, 2026 23:56

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 17 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/run/run.go Outdated
Comment thread internal/sdkgen/sdkgen.go Outdated
Comment thread cmd/lint/lint.go Outdated
Comment thread internal/auth/auth.go Outdated
@ThomasRooney ThomasRooney changed the title build: bump openapi-generation to license token validation and wire the license election build: bump openapi-generation to license token validation; commercial-only election and offline licenses Sep 1, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/run/run.go Outdated
… commercial license

Bumps openapi-generation/v2 to v2.934.0 (offline license validation with
target-scoped tokens) and generation-context to v1.1.0 (WithDirect no
longer implies AGPL; the election is explicit).

Every generation elects the commercial license and attaches the license
token from the platform access check (AccessDetails.license_jwt),
falling back to the token in the authenticated context. The generator
validates the token offline; an absent token is rejected as an unproven
commercial election and the CLI warns that the platform may need
updating. There is no AGPL election in the CLI.

The workflow-level and lint dry-run AGPL/direct elections are removed;
lint's dry-run elects commercial and skips a target's diagnostics when
no generation context can be built (unauthenticated lint).
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 2c8c154 to 66c9824 Compare September 1, 2026 09:46
@ThomasRooney ThomasRooney changed the title build: bump openapi-generation to license token validation; commercial-only election and offline licenses build: bump openapi-generation to license token validation; elect the commercial license Sep 1, 2026
…y-run diagnostics

Removing the workflow-level election left run-path validation stamping a
direct generation state onto authenticated contexts, which made
GetSDKFromContext report unauthenticated and degraded link shortening.
Validation now elects authenticated commercial when the caller is
authenticated and falls back to direct OSS mode otherwise.

Lint logs a debug line when a target's dry-run diagnostics are skipped
instead of dropping them silently.
@AshGodfrey

AshGodfrey commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Possible regression: the workflow code-samples fallback loses its generation context.

With the workflow-level election removed from run.Run, the context that reaches runCodeSamples (internal/run/target.go) no longer carries any generation-access state — the new election in sdkgen.Generate is local to that function and doesn't propagate back to the workflow context.

The pre-rendered branch (GenerateOverlayFromRawSnippets) is fine, but the fallback branch calls codesamples.GenerateOverlayusagegen.Generateg.Generate, which requires generation-access state (and, on v2.934.0, a validated commercial token). That fallback is reachable in practice: snippet pre-rendering is best-effort in the generator — renderStandaloneSnippets failures only log a warning and leave RenderedUsageSnippets nil, and RawOutput is empty whenever no rendered file contains the "Usage snippet provided" marker. On main the workflow-level context covered this path; after this change it fails with ErrMissingGenerationAccess, and since code samples are blocking by default, the whole target fails.

A hung CLI child consumed the whole suite's 30m budget and the suite
timeout discarded the child's output. Each quickstart and run invocation
now gets its own 10m deadline and reports the captured output when it
expires.
…t fallback

Removing the workflow-level election left runCodeSamples' fallback branch
(GenerateOverlay -> usagegen -> Generate) without generation-access state:
sdkgen.Generate's election is local to that call. The exported
WithCommercialGenerationContext establishes the same commercial election
with the context's license token, and lint's dry-run reuses it.
@ThomasRooney

Copy link
Copy Markdown
Member Author

Confirmed and fixed in 5655b00 — the fallback branch of runCodeSamples now goes through the exported sdkgen.WithCommercialGenerationContext, which establishes the same commercial election (with the context's license token) that sdkgen.Generate makes locally, so GenerateOverlay → usagegen → g.Generate gets valid generation-access state again. The pre-rendered branch is untouched, and lint's dry-run reuses the same helper. Covered by TestWithCommercialGenerationContext in internal/sdkgen/generation_context_test.go.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread internal/sdkgen/sdkgen.go
The token resolved during generation may come from the access check rather
than the caller's context; runTarget now places it on the context so the
code-samples fallback validates with the same token the generation did.
With recent generations in the workspace event stream, useRemoteSource
defaulted to true and selectRecentGeneration ran its interactive picker
even under --skip-interactive, blocking forever on a piped stdin. This is
what intermittently hung the Windows quickstart integration tests: the
shared CI workspace's telemetry from other runs made recent generations
appear. Non-interactive quickstarts now always take the sample/local
spec flow.
The validate call and the access check issue the same license token, so
the resolved token no longer travels through GenerationAccess and the
workflow context, and the no-token warning is dropped — an absent token
surfaces as the generator's unproven-commercial rejection.
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 3aefe55 to df17f46 Compare September 1, 2026 13:33
@AshGodfrey

Copy link
Copy Markdown
Contributor
  1. Standalone generate codeSamples and generate usage commands are left broken. cmd/generate/codesamples.go:73 and cmd/generate/usage.go:81 call codesamples.GenerateOverlay/usagegen.Generate → g.Generate → establishLicense with no generation state and no token, so they fail with ErrMissingGenerationAccess. This was already broken on main (v2.933.1's requireGenerationAccess at generator.go:637 has the same requirement), so it's not a regression — but the PR fixes the workflow codesamples fallback while leaving these two siblings of the exact same code path dead. We should decide if we want to fix.

The PR body's lint claim is wrong: the unproven-license error is swallowed, not surfaced. In runDryRunGeneration, g.Generate's errors are explicitly ignored (_ = errs, cmd/lint/lint.go ~line 625) and the unproven-commercial error is returned as an error before any warnings are produced, so g.GetWarnings() is empty. Authenticated-with-no-token lint silently yields zero dry-run diagnostics (it does not put the error "in the target's warnings" as the body states). Worth either surfacing that specific error or correcting the PR description.

A bad license_jwt now breaks validation/lint too, not just generation. --> Intentional??

The platform now mints license tokens from /v1/auth/validate for free
tiers as well (speakeasy-registry#4709), so the authenticated context's
token is authoritative and the access check's token no longer needs to
be preferred.
@ThomasRooney
ThomasRooney force-pushed the build/bump-generator-license-election branch from 93d1529 to 2cd9b57 Compare September 1, 2026 14:35
…dry runs; keep validation token-free

generate codeSamples and generate usage called the generator with no
generation-access state and failed with ErrMissingGenerationAccess (also
broken on main); both now elect through WithCommercialGenerationContext.
Lint's dry run reports a generation that failed before producing any
warnings instead of presenting the target as clean. Validation no longer
attaches the license token: it produces no licensed output, and an
invalid token must not fail read-only diagnostics.
An unauthenticated --dry-run printed "No SDK generation warnings found"
after skipping every target; the summary now names the skipped targets
instead of presenting them as clean.
@ThomasRooney

Copy link
Copy Markdown
Member Author

All three points verified and addressed in 9fa4bc7 (e2e-tested locally against prod with a built binary, sandboxed $HOME):

  1. Standalone generate codeSamples / generate usage — confirmed dead on main (no generation state → ErrMissingGenerationAccess). Both now elect through sdkgen.WithCommercialGenerationContext. E2E: authenticated generate codeSamples produced a real 591-line overlay (18 x-codeSamples); generate usage runs the full generation pipeline; unauthenticated invocations fail with a clear unauthenticated error instead of the generator's internal one.

  2. Lint claim — correct, the error was swallowed (_ = errs) and the body was wrong. Fixed rather than reworded: a dry run that fails before producing any warnings now returns its error, callers skip the target, and the non-interactive summary prints "SDK generation dry-run skipped for (authentication required)" instead of "No SDK generation warnings found ✓". E2E: truly-unauthenticated --dry-run shows the skip line; authenticated --dry-run runs the generation under the commercial election with the validate-issued token and reports honestly.

  3. Bad license_jwt breaking validation/lint — confirmed and not intentional: ValidateWithOptsresolveGenerationAccess(ctx, "", false) doesn't require proof but still errors on a present-but-invalid token. Validation no longer attaches the token at all — it produces no licensed output, so an invalid token can't fail read-only diagnostics. The lint dry run still attaches it (it's a real generation), where a bad token now surfaces as a skipped target rather than a hang or fake-clean.

PR body updated to match. One e2e side-note: the full flow validated speakeasy-registry#4709 end to end — online auth persisted the validate-issued token as offline_license_token and the commercial election was accepted by the generator.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread cmd/lint/lint.go Outdated
The skip summary hardcoded "authentication required", mislabeling
schema-load and generation failures; it now carries each target's error.
@AshGodfrey

Copy link
Copy Markdown
Contributor

Two issues in the latest commits:

1. 2cd9b57 drops the access check's target-scoped token; the validate-time token can't always stand in for it.

sdkgen.Generate now reads the license token only from the auth context (minted at /v1/auth/validate time) and ignores accessResult.LicenseToken, which GetAccess mints for the exact TargetType being generated. The two diverge:

  • Free-tier tokens must name exactly one target — licensetoken.validatePayload rejects wildcard and multi-target free tokens. /v1/auth/validate runs before any target is known, so for a free workspace's first generation (quickstart) or a switch to a different target, the context token can't be guaranteed to cover the target being generated. Generation then fails with ErrLicenseTargetNotCovered (or ErrUnprovenCommercialLicense if no token was minted) even when GetAccess returned Allowed: true together with a covering token that is now discarded.
  • The studio is a long-lived process: its context token is minted once at startup and never refreshed, while the generator allows only 1 minute of clock skew past exp. The per-generation GetAccess call used to supply a fresh token; now a studio run that outlives the token's lifetime fails mid-session.

Preferring accessResult.LicenseToken when present and falling back to the context token (the 5ea1f1c shape) covers both. An enterprise workspace with a wildcard token can't reproduce either case, so e2e passing doesn't rule them out.

2. cmd/sdk/go/.speakeasy/gen.yaml gained idiomaticMethodCollisionNames: true, which will rename fields on the next regen.

That option defaults to true only for newly-bootstrapped SDKs (its DefaultValue is newSDK in the Go target config), so true here means a fresh bootstrap wrote this file — consistent with the new .speakeasy/.gitignore beside it and no regenerated SDK source in the PR. The committed SDK was generated with the compat value (false, trailing-underscore collision names); pinning true makes the next regeneration of the embedded SDK silently rename those fields, a breaking change for whatever consumes them. optionalMethodArguments: pointers and unionGenerics: false match the compat defaults and are inert; idiomaticMethodCollisionNames should be false (or the SDK regenerated deliberately) unless the rename is intended.

A test bootstrap rewrote the fixture with newSDK defaults
(idiomaticMethodCollisionNames: true would rename collision fields on
the next generation from this config); no generated source is committed
here, so the config returns to main's state.
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