fix: OpenAPI guardian sweep 2026-08-17 (slice 2 remainder + docs-guardian leads) - #566
fix: OpenAPI guardian sweep 2026-08-17 (slice 2 remainder + docs-guardian leads)#566sarkissianraffi wants to merge 2 commits into
Conversation
- 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).
📣 Slack announcement (relay needed)Slack is unreachable from this runner — no Slack tooling is registered and no 🔧 OpenAPI Guardian — sweep of 2026-08-17 (slice 2: customers, billing_entities, organizations) |
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 buildandnpm run testpass on this branch (0 errors, same 6 warnings asmain; 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
Field-level evidence
Typos and definitions
src/schemas/CustomerObjectExtended.yamlerror_details: "processing backgroud actions for the customers" → "processing background actions for the customer".src/schemas/CustomerObjectExtended.yamlapplicable_invoice_custom_sections: curly apostrophe → straight (repo convention), and "custom section" → "custom sections" — the field is an array.src/schemas/CustomerBaseObject.yamlsequential_id: stray double backtick (`sequential_id``) closing the inline code span.src/schemas/BillingEntityObject.yamlis_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:110has_one :default_billing_entity, -> { active.order(created_at: :asc) }, andbilling_entity_serializer.rbcomputesis_defaultby comparing against it.src/schemas/AlertThresholdInput.yaml+AlertThresholdObject.yamlvalue: the unit was undocumented. Added the per-alert-type unit — evidence: thefind_valueimplementations inapp/models/usage_monitoring/*_alert.rb:billable_metric_current_usage_units,billable_metric_lifetime_usage_units→fee.units(units)wallet_credits_balance,wallet_credits_ongoing_balance→wallet.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.yamlaccount_type:["string","null"]withnullin the enum →string, enumcustomer/partner. Evidence:customer.rb:55enum :account_type, ACCOUNT_TYPES, suffix: :account, validate: true— noallow_nil, in deliberate contrast tocustomer_typeon line 53 which usesvalidate: {allow_nil: true}; anddb/structure.sqlhasaccount_type ... NOT NULL DEFAULT 'customer'. See the [BREAKING-DOC] note below.src/schemas/CustomerCreateInput.yamlaccount_typedescription: added that the field is only applied when revenue share is enabled — evidence:customers/upsert_from_api_service.rb:80assigns it onlyif customer.organization.revenue_share_enabled? && customer.editable?; otherwise the value is silently ignored.src/schemas/InvoiceOneOffCreateInput.yamlfees[].units:["string","null"]→["string","number","null"]. Evidence:fees/one_off_service.rb:26units = 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_typeno longer documentsnull. This tightens an input type, so a consumer generating a client from the spec loses the ability to sendaccount_type: null. The code provesnullis 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.
BillingEntityUpdateInputmissing itsbilling_entityrequest wrapper (billing_entities_controller.rb:105doesparams.require(:billing_entity))InvoiceOneOffCreateInput.fees[].unitstyped string-only while docs pass integersvaluehas no documented unitSDK drift (spec is right — needs an
sdk-clients-updaterun)Cross-diffed slice 2 against all five clients. Nothing here indicates a spec error; in every case
lago-apiconfirms the spec.account_typeandexternal_salesforce_id; both are permitted incustomers_controller.rb:125,147integration_customers[]whitelist omitstargeted_object(permitted on line 159)CustomerResponseomitssequential_id,slug,account_typeandexternal_salesforce_id, all always emitted byCustomerSerializerCustomerInputalso omitslogo_url;CustomerListInputomits theaccount_type[],billing_entity_codes[]andexternal_idfiltersCustomerPaymentProviderenum has onlystripe/adyen/gocardless; the API also acceptscashfree,flutterwave,moneyhash(Go has all six)IntegrationTypeincludesokta, which is not anintegration_customerstype, and omitsavalaraeinvoicing(permitted inbilling_entities_controller.rb:73,106, serialized byBillingEntitySerializer)logo_url, but the API permitslogo(base64). Ruby and Go correctly sendlogoBillingEntityresponse struct omitsphonewebhook_urls, whichorganizations_controller.rb:51-76does not permit — silently droppedslugmissing from both request and response modelsOrganizationResponseomitsdefault_currencyopenapi/client.tsis generated from the published spec at build time and is not committedNeeds human confirmation (not changed)
GET /organizationsnever returnstaxes.organizations_controller.rb:11passesinclude: %i[taxes]whileModelSerializer#include?readsoptions[:includes];updateon line 23 correctly passesincludes:. 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.taxesshould be documented as update-only.GET /organizations/grpc_tokenexists inconfig/routes.rb:201and 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.CustomersPaginatedover-promises. It points atCustomerObjectExtended, which carriesapplicable_invoice_custom_sectionsanderror_details, butcustomers_controller.rb:76serializes the index withincludes: %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.CustomerBaseObjectunder-declaresrequired.billing_entity_code,account_type,finalize_zero_amount_invoice,skip_invoice_custom_sectionsandupdated_atare emitted unconditionally byCustomerSerializerand areNOT NULLindb/structure.sql, but are not in therequiredlist. 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, theintegration_typeandpayment_providerenums, organizationslug/email_settings, theGET /organizationsoperation, the customersexternal_idfilter, and theCustomerIntegratrion/CustomerPaymentProvidernschema-key typos. #563 should merge first; the only file both touch issrc/schemas/CustomerCreateInput.yaml, in non-adjacent hunks.Also note: #564 adds
avalaratoIntegrationCustomer.type, which is why that enum is untouched here.Deferred to next run
CustomerCreateInput.finalize_zero_amount_invoiceshould also acceptnull—upsert_from_api_service.rbdoesparams[:finalize_zero_amount_invoice] || "inherit"andvalid_finalize_zero_amount_invoice?returns true fornil. 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.CustomerObjectExtendedredefinesmetadata, whichCustomerBaseObjectalready declares. Harmless duplication; a cleanup, not a correctness fix.Process feedback for the retro
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