Skip to content

fix: review fixes for the spend API contract - #4879

Open
tothandras wants to merge 8 commits into
feat/v3-charges-spend-apifrom
feat/v3-charges-spend-api-review
Open

fix: review fixes for the spend API contract#4879
tothandras wants to merge 8 commits into
feat/v3-charges-spend-apifrom
feat/v3-charges-spend-api-review

Conversation

@tothandras

@tothandras tothandras commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added distinct flat-fee and usage-based charge creation request types.
    • Charge realizations can now include optional identifiers for persisted runs.
    • Charge feature references are handled consistently in charge creation and responses.
  • Improvements

    • Updated service-period filters to find charges fully within a specified window.
    • Clarified billing, realization, credit, and detailed-line documentation.
    • Improved generated union variant naming and invoice reference APIs.
  • Bug Fixes

    • Charges without features now convert correctly.
    • Required realization collections are consistently serialized as non-null values.

Greptile Summary

The PR aligns the spend API contract and generated clients around charge creation, realization metadata, and nested-union handling.

  • Uses feature references rather than expandable feature resources in charge creation requests.
  • Adds optional persisted realization identifiers while preserving non-null realization collections.
  • Strengthens TypeScript wire mapping and Go union generation for nested unions.
  • Updates service-period filter documentation and generated SDK artifacts.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
api/spec/packages/aip/src/customers/charges/charges.tsp Separates create-time feature references from expandable response resources and adds optional realization IDs.
api/spec/packages/typespec-go/src/components/GoUnion.tsx Extends non-discriminated union compatibility checks to model variants nested inside unions.
api/spec/packages/typespec-typescript/src/runtime/wire.ts Makes zero-coverage object-union selection deterministic and preserves established object-walker semantics.
api/v3/handlers/customers/charges/convert.go Keeps charge request conversion aligned with the generated feature-reference request types.
api/v3/openapi.yaml Regenerates the public v3 contract for charge requests, realization identifiers, and filter documentation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  TSP[TypeSpec charge contract] --> OA[OpenAPI v3 schema]
  TSP --> TS[TypeScript SDK models]
  TSP --> GO[Go client models]
  OA --> H[Charge HTTP handler]
  H --> C[Domain/API conversion]
  C --> R[Charge response]
  R --> W[SDK wire mapping]
Loading

Reviews (3): Last reviewed commit: "docs(emitter): record the deliberate ULI..." | Re-trigger Greptile

Context used:

@tothandras
tothandras requested a review from a team as a code owner August 7, 2026 12:21
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates charge creation contracts, feature-reference handling, realization identifiers, service-period filter documentation, generated clients, conversion tests, wire mapping tests, and nested union validation. It also changes Go union accessor naming to use mapped model and nested union names.

Changes

Charge API contracts and generated models

Layer / File(s) Summary
Charge source contracts and filter semantics
api/spec/packages/aip/src/customers/charges/*, api/v3/client/customers_charges.go
Charge creation models now declare feature references explicitly. Charge responses use read-only feature references. Realization IDs are optional. Service-period filters describe contained windows.
Generated charge models and schemas
api/spec/packages/aip-client-javascript/src/*, api/v3/api.gen.go, api/v3/client/models_customers.go
Generated clients expose separate flat-fee and usage-based request types and unions. Feature inputs use FeatureReference. Realization and filter documentation is updated.
Charge conversion and validation
api/v3/handlers/customers/charges/*, e2e/billinginvoice_override_test.go
Charge conversion maps feature IDs directly, supports absent features, emits empty realization slices, and passes feature IDs into create inputs. Tests validate references, JSON serialization, and invoice feature IDs.
Charge wire mapping coverage
api/spec/packages/aip-client-javascript/src/lib/wire.ts, api/spec/packages/aip-client-javascript/tests/*
Comments and tests cover nested union mapping, casing conversion, round trips, zero-key variant selection, defaults, key dropping, and non-object variants.

Nested union validation and naming

Layer / File(s) Summary
TypeScript nested union compatibility
api/spec/packages/typespec-typescript/src/casing-gate.ts, api/spec/packages/typespec-typescript/src/runtime/wire.ts, api/spec/packages/typespec-typescript/test/nested-union.test.ts
Nested object variants are checked recursively against outer variants. Cycle protection prevents repeated traversal. Tests cover conflicts and compatible nested variants.
Mapped Go union accessors
api/spec/packages/typespec-go/src/components/GoUnion.tsx, api/spec/packages/typespec-go/test/union-gate.test.ts, api/spec/packages/typespec-go/src/go-types.tsx
Go union accessors and constructors use mapped model and nested union names. Tests cover nested conflicts, mixed variants, emitted names, and unsupported envelopes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: area/billing

Suggested reviewers: borosr, chrisgacsal, turip

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies fixes to the spend API contract, which is the main focus of the pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v3-charges-spend-api-review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tothandras tothandras added the release-note/bug-fix Release note: Bug Fixes label Aug 7, 2026
- nil-safe flat-fee feature mapping; serialize required realizations as []
- create requests take a feature reference instead of the expandable union
- expose optional realization run id; window filter recipe uses gte/lte
- name Go union accessors after nested unions (AsInvoice, not AsVariantName)
- tsp doc cleanups and regenerated OpenAPI/Go/TS artifacts
@borosr
borosr force-pushed the feat/v3-charges-spend-api-review branch from a8378e1 to 30d1707 Compare August 7, 2026 13:37
borosr and others added 6 commits August 7, 2026 16:27
Neither codegen gate reached into a union-typed variant, so the models a
nested union contributes were never verified against the outer variants
they compete with in the mapper's key-coverage pick. Both gates now check
them.

Nested models are checked against the map built from the outer union's
direct variants, never merged into it: a nested discriminated union's own
variants legitimately disagree on non-discriminator keys because that
union selects on its discriminator rather than by shape, and merging them
would fail the build on unions that decode correctly.

Nested-vs-nested stays unchecked and is now stated as such, in the gates
and in the wire runtime comment that previously claimed nested variants
were not verified at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
selectVariant starts its search below zero matches, so a non-discriminated
union with any object variant always resolves one — data matching no
variant key selects the narrowest, and the walk then drops the undeclared
keys. The pass-through comment claimed the opposite; it describes only a
discriminated miss or a union with no object variant, and now says so.

Keeping the behavior rather than requiring a match: passing unknown keys
through would put snake_case names into a value fromWire's return type
declares to be camelCase, and dropping them is what the object branch
already does for a non-union field.

Tests pin the pick (narrowest of three, identified by its default), the
key dropping, the defaults materialized onto zero-coverage data, and the
pass-through path that survives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gate tests exercised conflictingVariantProperty and variantGoName, but
none of the three throws GoUnion itself raises: conflicting variants in a
non-discriminated union, a discriminated union mixing model and non-model
variants, and an unsupported discriminator envelope. All three fire before
any JSX is produced, so calling GoUnion as a plain function drives them.

Also covers the nested-variant conflict end to end, not just through the
predicate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-operation sweep samples a union's first option only, so the
reference side of every expandable reference goes unexercised and nothing
asserts that the expanded side keeps its fields instead of collapsing onto
the reference. Charge realizations are where both shapes land: `invoice`
is a union whose expanded arm is itself a discriminated union, and
`detailed_lines` is an array of another.

Verified as a regression guard: three of the four cases fail when the
nested-union resolution in selectVariant is removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review asked why ChargeFilter.FeatureID is *StringExactFilter when the
spec declares ULIDFieldFilter. It is the emitter's mapping, applied SDK-
wide, not a charges-specific slip. Say so where the mapping lives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/spec/packages/typespec-typescript/src/casing-gate.ts`:
- Around line 231-238: Update
api/spec/packages/typespec-typescript/src/casing-gate.ts lines 231-238 to retain
each nested model’s immediate outer-variant provenance and compare shared fields
across models from different outer branches, while preserving the exemption for
siblings within the same discriminated union. Apply the same provenance-aware
conflict detection in api/spec/packages/typespec-go/src/components/GoUnion.tsx
lines 324-359, and add a regression fixture covering nested union branches with
incompatible field types; ensure unsafe non-discriminated unions with multiple
object variants in request or success-response bodies are rejected.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6afa1dbd-9477-4047-9aad-fe3f7f6c04a1

📥 Commits

Reviewing files that changed from the base of the PR and between 30d1707 and 10b8c0e.

📒 Files selected for processing (10)
  • api/spec/packages/aip-client-javascript/src/lib/wire.ts
  • api/spec/packages/aip-client-javascript/tests/charges.wire.spec.ts
  • api/spec/packages/aip-client-javascript/tests/wire.spec.ts
  • api/spec/packages/typespec-go/src/components/GoUnion.tsx
  • api/spec/packages/typespec-go/src/go-types.tsx
  • api/spec/packages/typespec-go/test/union-gate.test.ts
  • api/spec/packages/typespec-typescript/src/casing-gate.ts
  • api/spec/packages/typespec-typescript/src/runtime/wire.ts
  • api/spec/packages/typespec-typescript/test/nested-union.test.ts
  • api/v3/handlers/customers/charges/convert.go

Comment on lines +231 to 238
for (const { model, name } of nestedObjectVariants(program, union)) {
for (const [key, prop] of effectiveShape(program, model)) {
const seen = byKey.get(key)
if (seen && seen.type !== prop.type) {
return `'${key}' has a different type in ${seen.variant} and ${name}`
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Check conflicts between separate nested-union branches.

The current code compares nested models only with direct object variants. For an outer non-discriminated union such as NestedA | NestedB, incompatible shared fields in NestedA and NestedB are never compared. The mapper and generated As* accessors can then select or decode incompatible payloads.

  • api/spec/packages/typespec-typescript/src/casing-gate.ts#L231-L238: retain the immediate outer-variant origin for each nested model, then compare models from different outer branches while preserving the exemption for siblings of the same discriminated union.
  • api/spec/packages/typespec-go/src/components/GoUnion.tsx#L324-L359: retain the same branch provenance before conflict detection, and add a regression fixture for two nested union branches with incompatible field types.

As per coding guidelines, reject unsafe non-discriminated unions with multiple object variants in request or success-response bodies.

📍 Affects 2 files
  • api/spec/packages/typespec-typescript/src/casing-gate.ts#L231-L238 (this comment)
  • api/spec/packages/typespec-go/src/components/GoUnion.tsx#L324-L359
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/spec/packages/typespec-typescript/src/casing-gate.ts` around lines 231 -
238, Update api/spec/packages/typespec-typescript/src/casing-gate.ts lines
231-238 to retain each nested model’s immediate outer-variant provenance and
compare shared fields across models from different outer branches, while
preserving the exemption for siblings within the same discriminated union. Apply
the same provenance-aware conflict detection in
api/spec/packages/typespec-go/src/components/GoUnion.tsx lines 324-359, and add
a regression fixture covering nested union branches with incompatible field
types; ensure unsafe non-discriminated unions with multiple object variants in
request or success-response bodies are rejected.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants