fix(all): report server-sent event operations and field deprecations in SDK diffs

MF
2d ago
+487-25

[skip changelog]

Why

speakeasy diff and the release notes that speakeasy run writes to gen.lock both build SDK ASTs through pkg/changes. Two gaps made those diffs incomplete.

  1. Operations with a text/event-stream response disappeared from the diff. resolveAST ran the generator with a context that carried no generation access state. When the generator reaches an event-stream response, it calls licensing.ValidateAccountHasFeatureAccess. Without access state, that check returns ErrMissingGenerationAccess, which is wrapped as an UnsupportedError, and g.skip silently drops the whole operation. Because the operation was missing from both the old and new AST, it was never reported as added or removed, and changes to its request or response (for example a new enum value) were never reported either. This hits both an operation whose 200 response offers application/json and text/event-stream, and an operation that only streams. The same gate covers other feature-gated constructs, such as JSONL responses and webhooks.
  2. Field deprecation was ignored. diffFieldDefs compares nullability, optionality, defaults, consts and annotations, but not Comments.Deprecated. So marking a request or response field deprecated: true produced no change.

What changed

  • resolveAST attaches direct generation access state when the caller supplies none. This matches internal/generate.Generate. Callers that already provide access state are unaffected.
  • A field that becomes deprecated is now reported with a new field_deprecated diff reason, rendered as **Deprecated**. It is non-breaking, and only the transition into deprecation is reported, consistent with method-level deprecation. If the field also changed in another way, it is still reported as **Changed**.
  • The duplicated reason-to-action switches in markdown.go are now one formatDiffAction helper.

Before (old spec has generateReport with a json + event-stream 200 response; new spec adds the html enum value to its request, adds createWidget (json + event-stream) and tailEvents (event-stream only)):

No changes detected.

After:

## Python SDK Changes:
* `sdk.create_widget()`: **Added**
* `sdk.tail_events()`: **Added**
* `sdk.generate_report()`: 
  *  `request.format.enum(html)` **Added**

Before (request field color and response field nickname marked deprecated: true):

No changes detected.

After:

## Typescript SDK Changes:
* `sdk.updateWidget()`: 
  *  `request.color` **Deprecated**
  *  `response.nickname` **Deprecated**

No generated SDK output changes, so this PR skips the changelog.

Testing

  • New snapshot tests TestChanges_MethodChanges_ServerSentEvents and TestChanges_FieldChanges_FieldDeprecated. Both fail with CI=true against the previous pkg/changes sources and pass with this change.
  • go test ./pkg/changes/... ./cmd/changes/... passes, with no changes to existing snapshots.
  • golangci-lint run ./pkg/changes/... reports 0 issues. make lint also reports a gofmt issue in templates/perms.go, which is unchanged from main.
  • Ran go run ./cmd/changes on a pair of specs combining json + event-stream operations, an enum addition and a deprecated response field. All expected changes are now reported.
  • git diff --check flags only trailing spaces in the new snapshots. Those are literal markdown output (: before a nested list), the same as in 43 existing snapshots.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (.claude/skills/public-repo-communication/SKILL.md).
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.

Summary by cubic

Fixes two gaps that made speakeasy diff and release notes written to gen.lock miss real SDK changes.

  • Operations with text/event-stream responses were dropped because resolveAST ran the generator without generation access state; it now attaches direct access state when none is provided, matching internal/generate.Generate. This also covers other feature-gated constructs such as JSONL responses and webhooks.
  • Marking a request or response field deprecated: true produced no change; fields that become deprecated are now reported with a new field_deprecated diff reason, rendered as **Deprecated**. Only the transition into deprecation is reported, consistent with method-level deprecation, and fields that also changed otherwise are still reported as **Changed**.
  • The duplicated reason-to-action switches in markdown.go are now one formatDiffAction helper.

No generated SDK output changes, so the changelog is skipped.