Skip to content

fix: OpenAPI guardian sweep 2026-08-17 (slice 2 remainder + docs-guardian leads) - #566

Open
sarkissianraffi wants to merge 2 commits into
mainfrom
openapi-guardian/2026-08-17
Open

fix: OpenAPI guardian sweep 2026-08-17 (slice 2 remainder + docs-guardian leads)#566
sarkissianraffi wants to merge 2 commits into
mainfrom
openapi-guardian/2026-08-17

Conversation

@sarkissianraffi

Copy link
Copy Markdown
Contributor

OpenAPI Guardian sweep — 2026-08-17 (slice 2: customers, billing_entities, organizations)

Automated spec sweep vs lago-api and the SDK clients.
A human must review and merge — this agent never merges.
npm run build and npm run test pass on this branch (0 errors, same 6 warnings as main; no new ones).

Weekly cadence this run, so the rotation slice was picked as ISO week % 8 (week 34 → slice 2) rather than the skill's twice-weekly formula. That lands on the same slice as the still-open #563, so this sweep deliberately covers only what #563 does not — see "Overlap with #563" below.

Fixed in this PR

Type Count
Typos / definitions 5
Required vs optional / nullability 3
Filters / query params / paths 0 (slice-2 filters match the controller)

Field-level evidence

Typos and definitions

  • src/schemas/CustomerObjectExtended.yaml error_details: "processing backgroud actions for the customers" → "processing background actions for the customer".
  • src/schemas/CustomerObjectExtended.yaml applicable_invoice_custom_sections: curly apostrophe → straight (repo convention), and "custom section" → "custom sections" — the field is an array.
  • src/schemas/CustomerBaseObject.yaml sequential_id: stray double backtick ( `sequential_id ``) closing the inline code span.
  • src/schemas/BillingEntityObject.yaml is_default: the description repeated itself and ended in a broken sentence ("…when invoice is created. is the oldest active billing entity…"). Rewritten to one accurate sentence — evidence: organization.rb:110 has_one :default_billing_entity, -> { active.order(created_at: :asc) }, and billing_entity_serializer.rb computes is_default by comparing against it.
  • src/schemas/AlertThresholdInput.yaml + AlertThresholdObject.yaml value: the unit was undocumented. Added the per-alert-type unit — evidence: the find_value implementations in app/models/usage_monitoring/*_alert.rb:
    • billable_metric_current_usage_units, billable_metric_lifetime_usage_unitsfee.units (units)
    • wallet_credits_balance, wallet_credits_ongoing_balancewallet.credits_balance / credits_ongoing_balance (credits)
    • current_usage_amount, billable_metric_current_usage_amount, lifetime_usage_amount, wallet_balance_amount, wallet_ongoing_balance_amount*_cents (cents)

Required vs optional / nullability

  • src/schemas/CustomerCreateInput.yaml account_type: ["string","null"] with null in the enum → string, enum customer/partner. Evidence: customer.rb:55 enum :account_type, ACCOUNT_TYPES, suffix: :account, validate: true — no allow_nil, in deliberate contrast to customer_type on line 53 which uses validate: {allow_nil: true}; and db/structure.sql has account_type ... NOT NULL DEFAULT 'customer'. See the [BREAKING-DOC] note below.
  • src/schemas/CustomerCreateInput.yaml account_type description: added that the field is only applied when revenue share is enabled — evidence: customers/upsert_from_api_service.rb:80 assigns it only if customer.organization.revenue_share_enabled? && customer.editable?; otherwise the value is silently ignored.
  • src/schemas/InvoiceOneOffCreateInput.yaml fees[].units: ["string","null"]["string","number","null"]. Evidence: fees/one_off_service.rb:26 units = fee[:units]&.to_f || 1, so a JSON number is accepted. Widening only. (Lead raised by the docs guardian in Docs Guardian sweep — 2026-08-17 (slice 4: invoicing, one-off invoices, emails) lago-doc#642.)

[BREAKING-DOC] flags

  • CustomerCreateInput.account_type no longer documents null. This tightens an input type, so a consumer generating a client from the spec loses the ability to send account_type: null. The code proves null is never a storable value, and sending it is either silently ignored (revenue share off) or rejected by the inclusion validation (revenue share on) — but flagging it so reviewers weigh it consciously.

Docs-guardian leads triaged

Leads came from the "Suspected spec issues" sections of getlago/lago-doc#642 (today) and #636.

Lead Verdict
BillingEntityUpdateInput missing its billing_entity request wrapper (billing_entities_controller.rb:105 does params.require(:billing_entity)) Confirmed — already fixed in #563, not duplicated here
InvoiceOneOffCreateInput.fees[].units typed string-only while docs pass integers Confirmed — fixed in this PR
Wallet alert threshold value has no documented unit Confirmed — fixed in this PR, and extended to every alert type, not just the wallet ones

SDK drift (spec is right — needs an sdk-clients-update run)

Cross-diffed slice 2 against all five clients. Nothing here indicates a spec error; in every case lago-api confirms the spec.

Resource Client(s) Divergence
customers Ruby, Python, Go Create params omit account_type and external_salesforce_id; both are permitted in customers_controller.rb:125,147
customers Ruby, Python integration_customers[] whitelist omits targeted_object (permitted on line 159)
customers Python CustomerResponse omits sequential_id, slug, account_type and external_salesforce_id, all always emitted by CustomerSerializer
customers Go CustomerInput also omits logo_url; CustomerListInput omits the account_type[], billing_entity_codes[] and external_id filters
customers Rust CustomerPaymentProvider enum has only stripe/adyen/gocardless; the API also accepts cashfree, flutterwave, moneyhash (Go has all six)
customers Go IntegrationType includes okta, which is not an integration_customers type, and omits avalara
billing_entities Ruby, Python, Go Create/update params and response models omit einvoicing (permitted in billing_entities_controller.rb:73,106, serialized by BillingEntitySerializer)
billing_entities Python Request models expose logo_url, but the API permits logo (base64). Ruby and Go correctly send logo
billing_entities Go BillingEntity response struct omits phone
organizations Ruby, Python Update params send webhook_urls, which organizations_controller.rb:51-76 does not permit — silently dropped
organizations Ruby, Python, Go slug missing from both request and response models
organizations Python OrganizationResponse omits default_currency
billing_entities, organizations Rust Not modelled at all — known partial-coverage gap by design, not drift
all JavaScript No drift possible: openapi/client.ts is generated from the published spec at build time and is not committed

Needs human confirmation (not changed)

  • GET /organizations never returns taxes. organizations_controller.rb:11 passes include: %i[taxes] while ModelSerializer#include? reads options[:includes]; update on line 23 correctly passes includes:. This looks like a one-character bug in lago-api, not in the spec, so nothing was changed here. If it is a bug, the spec is already correct; if the omission is intentional, OrganizationObject.taxes should be documented as update-only.
  • GET /organizations/grpc_token exists in config/routes.rb:201 and returns {organization: {grpc_token}}, but is absent from the spec. It looks internal (a JWT for the gRPC gateway), so it was not added. Confirm whether it should be public.
  • CustomersPaginated over-promises. It points at CustomerObjectExtended, which carries applicable_invoice_custom_sections and error_details, but customers_controller.rb:76 serializes the index with includes: %i[taxes integration_customers] only, so those two keys never appear in a list response. Fixing this properly needs a new intermediate schema (base + integration_customers + taxes + metadata); left alone rather than invent one unilaterally.
  • CustomerBaseObject under-declares required. billing_entity_code, account_type, finalize_zero_amount_invoice, skip_invoice_custom_sections and updated_at are emitted unconditionally by CustomerSerializer and are NOT NULL in db/structure.sql, but are not in the required list. Adding them is correct but tightens the response contract for generated clients, so it is left for a human call.

Overlap with #563

#563 (approved 2026-08-14, still unmerged) covers the same slice. Everything it already fixes was deliberately left untouched here: the billing-entity request/response wrappers, the {code} path param, external_salesforce_id, targeted_object, the integration_type and payment_provider enums, organization slug / email_settings, the GET /organizations operation, the customers external_id filter, and the CustomerIntegratrion/CustomerPaymentProvidern schema-key typos. #563 should merge first; the only file both touch is src/schemas/CustomerCreateInput.yaml, in non-adjacent hunks.

Also note: #564 adds avalara to IntegrationCustomer.type, which is why that enum is untouched here.

Deferred to next run

  • CustomerCreateInput.finalize_zero_amount_invoice should also accept nullupsert_from_api_service.rb does params[:finalize_zero_amount_invoice] || "inherit" and valid_finalize_zero_amount_invoice? returns true for nil. Deferred only because the edit lands in the same hunk fix: OpenAPI guardian sweep 2026-08-11 (customers, billing entities, organizations) #563 modifies and would conflict.
  • CustomerObjectExtended redefines metadata, which CustomerBaseObject already declares. Harmless duplication; a cleanup, not a correctness fix.

Process feedback for the retro

  • The slice formula collided with an unmerged PR covering the same slice. The skill has no rule for "an open guardian PR already covers this slice"; the run's value came almost entirely from schemas outside the previous sweep's diff, plus the docs-guardian leads. A cheap rule would be: when the previous guardian PR is open and covers the same resources, treat its diff as already-fixed and spend the run on leads plus the untouched remainder (what this run did).
  • Slack is unreachable from this runner (no Slack tooling and no SLACK_* webhook), so the announcement is posted as a comment on this PR instead. This is the second consecutive run to hit it.

🤖 Generated with Claude Code

lago-guardians[bot] added 2 commits August 17, 2026 07:54
- CustomerObjectExtended: 'backgroud' -> 'background'; singular customer;
  straight apostrophe and plural 'custom sections'
- CustomerBaseObject: stray double backtick in sequential_id description
- BillingEntityObject: rewrite the broken/duplicated is_default sentence
  (organization.rb:110 defines default_billing_entity as the oldest active one)
- AlertThreshold input/object: document the unit of 'value' per alert type
  (units / credits / cents), from the find_value implementations in
  app/models/usage_monitoring/*_alert.rb
- CustomerCreateInput.account_type: null is never a valid value. customer.rb:55
  declares 'enum :account_type, ACCOUNT_TYPES, suffix: :account, validate: true'
  (no allow_nil, unlike customer_type on line 53) and customers.account_type is
  NOT NULL DEFAULT 'customer' in db/structure.sql. Also documents that the field
  is only applied when revenue share is enabled
  (customers/upsert_from_api_service.rb:80).
- InvoiceOneOffCreateInput.fees[].units: also accepts a JSON number.
  fees/one_off_service.rb:26 does 'fee[:units]&.to_f || 1', so an integer or
  float body value is valid. Reported by the docs guardian (lago-doc#642).
@sarkissianraffi

sarkissianraffi commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

📣 Slack announcement (relay needed)

Slack is unreachable from this runner — no Slack tooling is registered and no SLACK_* webhook is configured — so the #documentation post is reproduced here. Could someone relay it to #documentation? (Second consecutive run to hit this; flagged for the retro.)


🔧 OpenAPI Guardian — sweep of 2026-08-17 (slice 2: customers, billing_entities, organizations)
PR ready for human review: #566 — build & lint green, npm run build bundled before commit.
Fixed: 5 typos/definitions · 3 required/optional & nullability flags · 0 filter/path fixes
🔀 SDK drift found: Ruby (customer account_type/external_salesforce_id/targeted_object, billing-entity einvoicing, org webhook_urls sent but not permitted) · Python (same, plus logo_url instead of logo on billing entities and missing response fields) · Go (missing customer list filters, billing-entity phone/einvoicing, stray okta integration type) · Rust (payment-provider enum missing cashfree/flutterwave/moneyhash) · JavaScript (none — generated from the spec) → consider an sdk-clients-update run
⚠️ Needs a human decision: GET /organizations never returns taxes (looks like an include: vs includes: bug in lago-api, not the spec) · undocumented GET /organizations/grpc_token — public or internal? · CustomersPaginated promises two fields the index never returns · CustomerBaseObject under-declares required
⏳ Still awaiting review: #563 (approved 2026-08-14, same slice — please merge it before this one) and #564
I never merge — please review and merge when ready.

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.

1 participant