Skip to content

[codex] Force async advancement for invoice collection#4626

Merged
turip merged 1 commit into
mainfrom
feat/force-async-advance
Jul 1, 2026
Merged

[codex] Force async advancement for invoice collection#4626
turip merged 1 commit into
mainfrom
feat/force-async-advance

Conversation

@turip

@turip turip commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

  • add ForceAsyncAdvance to InvoicePendingLines and route it through standard invoice creation
  • force async advancement from the gathering invoice collector
  • add a billing feature switch for subscription sync pending-line async advancement, defaulting to true in ConfigureBilling

Validation

  • go test ./app/config
  • env POSTGRES_HOST=127.0.0.1 go test -tags=dynamic ./app/common ./openmeter/billing ./openmeter/billing/service ./openmeter/billing/worker/collect ./openmeter/billing/worker/subscriptionsync/service -run '^$'
  • env POSTGRES_HOST=127.0.0.1 go vet -tags=dynamic ./app/config ./app/common ./openmeter/billing ./openmeter/billing/service ./openmeter/billing/worker/collect ./openmeter/billing/worker/subscriptionsync/service
  • go test -c -tags=dynamic ./test/billing

Note: the focused billing integration test was not run because local Postgres/Docker was unavailable in this environment.

Summary by CodeRabbit

  • New Features

    • Added a new billing option to process invoice line advancement asynchronously.
    • Billing and subscription sync flows now support passing this setting through end-to-end.
    • Updated example configuration to show the new billing feature switch.
  • Tests

    • Added coverage for invoice processing when asynchronous advancement is enabled.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2222d5c8-5420-4c8f-928c-668ea83486a2

📥 Commits

Reviewing files that changed from the base of the PR and between 16820a7 and d092e11.

📒 Files selected for processing (12)
  • app/common/billing.go
  • app/config/billing.go
  • app/config/config_test.go
  • config.example.yaml
  • openmeter/billing/invoice.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/stdinvoice.go
  • openmeter/billing/worker/collect/collect.go
  • openmeter/billing/worker/subscriptionsync/service/service.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • test/billing/invoice_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • app/config/billing.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • config.example.yaml
  • test/billing/invoice_test.go
  • openmeter/billing/stdinvoice.go
  • openmeter/billing/worker/collect/collect.go
  • openmeter/billing/invoice.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • app/config/config_test.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/worker/subscriptionsync/service/service.go
  • app/common/billing.go

📝 Walkthrough

Walkthrough

This PR adds a SubscriptionSyncForceAsyncAdvance config switch and threads a new ForceAsyncAdvance boolean through billing's invoice pending-lines input, state advancement logic, and service wiring, letting invoice advancement be forced async in specific call sites like the collect worker and subscription sync.

Changes

ForceAsyncAdvance feature flag propagation

Layer / File(s) Summary
Config flag and defaults
app/config/billing.go, config.example.yaml, app/config/config_test.go
Adds SubscriptionSyncForceAsyncAdvance bool to BillingFeatureSwitchesConfiguration, sets its Viper default to true, activates the example yaml block, and updates test expectations.
Billing input/config data shapes
openmeter/billing/invoice.go, openmeter/billing/stdinvoice.go, openmeter/billing/worker/subscriptionsync/service/service.go
Adds ForceAsyncAdvance to InvoicePendingLinesInput and CreateStandardInvoiceFromGatheringLinesInput, and ForceAsyncInvoicePendingLines to the subscriptionsync Config/Service structs.
Invoice advancement and pending-lines flag propagation
openmeter/billing/service/gatheringinvoicependinglines.go, openmeter/billing/service/invoice.go
Forwards ForceAsyncAdvance from InvoicePendingLines through CreateStandardInvoiceFromGatheringLines into advanceUntilStateStable, which now takes a forceAsync param that triggers the async/queued advancement path; existing callers pass false.
Service construction wiring
app/common/billing.go
NewBillingSubscriptionSyncService gains a billingFsConfig parameter used to set ForceAsyncInvoicePendingLines when building subscriptionsyncservice.New.
Consumer call sites and test
openmeter/billing/worker/collect/collect.go, openmeter/billing/worker/subscriptionsync/service/sync.go, test/billing/invoice_test.go
Collect worker and subscription-sync service set ForceAsyncAdvance: true on their InvoicePendingLinesInput; a new test verifies forced-async InvoicePendingLines returns a draft invoice that can then transition via AdvanceInvoice.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Collector as Collect Worker / SubscriptionSync
    participant BillingService as billing.Service
    participant StateMachine as Invoice State Machine
    participant EventBus as Event Publisher

    Collector->>BillingService: InvoicePendingLines(ForceAsyncAdvance: true)
    BillingService->>BillingService: CreateStandardInvoiceFromGatheringLines(ForceAsyncAdvance)
    BillingService->>StateMachine: advanceUntilStateStable(sm, forceAsync=true)
    alt forceAsync or QueuedAdvancementStrategy
        StateMachine->>EventBus: publish AdvanceStandardInvoiceEvent
    else synchronous strategy
        StateMachine->>StateMachine: activate & validate state machine
    end
    BillingService-->>Collector: draft invoice(s)
Loading

Possibly related PRs

  • openmeterio/openmeter#4059: Both PRs touch the subscription-sync wiring in app/common/billing.go for NewBillingSubscriptionSyncService/subscriptionsyncservice.New, one adding ForceAsyncInvoicePendingLines, the other adding InvoicePendingLinesService.
  • openmeterio/openmeter#4136: Both PRs modify NewBillingSubscriptionSyncService in app/common/billing.go to adjust how the subscription-sync service is configured/wired.

Suggested labels: release-note/feature, area/billing

Suggested reviewers: tothandras

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: forcing async advancement during invoice collection.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/force-async-advance

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.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds forced async advancement for invoice pending-line collection. The main changes are:

  • Adds ForceAsyncAdvance to pending-line invoice creation.
  • Routes forced async advancement through gathering-line invoice creation.
  • Forces async advancement from customer invoice collection.
  • Adds a subscription-sync feature switch with a default-on configuration.
  • Adds focused coverage for forced async pending-line advancement.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
openmeter/billing/service/gatheringinvoicependinglines.go Passes forced async advancement from pending-line collection into standard invoice creation.
openmeter/billing/service/invoice.go Allows invoice advancement to enqueue async work when the caller explicitly forces it.
openmeter/billing/worker/collect/collect.go Forces async advancement for customer invoice collection while keeping the collection timestamp on pending-line selection.
openmeter/billing/worker/subscriptionsync/service/sync.go Applies the configured forced async behavior to subscription-sync pending-line invoicing.
app/config/billing.go Adds and defaults the subscription-sync forced async feature switch.
app/common/billing.go Wires the new billing feature switch into the subscription-sync service.

Reviews (2): Last reviewed commit: "Force async advancement for invoice coll..." | Re-trigger Greptile

Comment thread openmeter/billing/service/gatheringinvoicependinglines.go
Comment thread openmeter/billing/worker/collect/collect.go
@turip turip marked this pull request as ready for review July 1, 2026 17:07
@turip turip requested a review from a team as a code owner July 1, 2026 17:07
@turip turip force-pushed the feat/force-async-advance branch from 16820a7 to d092e11 Compare July 1, 2026 17:11

@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.

🧹 Nitpick comments (3)
app/config/billing.go (1)

54-54: 🩺 Stability & Availability | 🔵 Trivial

Heads up: shipping this default as true needs solid confidence.

This flips subscription-sync's invoice pending-lines advancement to async by default for everyone, not just an opt-in cohort. The PR description mentions the test/billing integration test wasn't actually run locally (no Postgres/Docker), so this default is going out without the full end-to-end validation that would normally cover the state-machine transitions it affects.

Worth double-checking CI actually ran the test/billing suite (with -tags=dynamic) before merge, given this touches the invoicing critical path by default.

🤖 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 `@app/config/billing.go` at line 54, The billing config default for
subscription-sync is being switched to async for all users, so verify the change
is backed by full end-to-end coverage before keeping it on by default. Recheck
that the `test/billing` integration suite with `-tags=dynamic` actually ran in
CI, and if it did not, gate the new default behind a safer rollout mechanism or
revert the `billing.featureSwitches.subscriptionSyncForceAsyncAdvance` default
in `config/billing.go` until the state-machine transitions are validated.
openmeter/billing/invoice.go (1)

457-463: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a short doc comment on ForceAsyncAdvance.

The field itself is fine, but its effect isn't obvious from the name alone: setting it true causes the invoice to be left in draft.created and advancement to be deferred to an async published event (see advanceUntilStateStable in openmeter/billing/service/invoice.go) rather than happening synchronously in this call. A one-liner here (and on the mirrored field in stdinvoice.go) would help future readers avoid surprises.

As per coding guidelines: "Add docstrings to domain helpers when the name compresses important business semantics, and describe the observable business contract rather than implementation mechanics."

✏️ Suggested doc comment
 	IncludePendingLines mo.Option[[]string]
 	AsOf                *time.Time
+	// ForceAsyncAdvance defers invoice advancement to the async AdvanceStandardInvoiceEvent
+	// flow instead of advancing synchronously in this call; the returned invoice(s) will
+	// remain in draft.created until the async advancement is processed.
 	ForceAsyncAdvance   bool
🤖 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 `@openmeter/billing/invoice.go` around lines 457 - 463, Add a short doc comment
for ForceAsyncAdvance in InvoicePendingLinesInput, and mirror the same comment
on the equivalent field in stdinvoice.go. Clarify the observable business
behavior: when true, the invoice stays in draft.created and advancement is
deferred to an async published event instead of completing synchronously in the
current call. Use the field names ForceAsyncAdvance and InvoicePendingLinesInput
(and the mirrored stdinvoice struct) to locate the spots.

Source: Coding guidelines

openmeter/billing/worker/collect/collect.go (1)

105-114: 🩺 Stability & Availability | 🔵 Trivial

Operational note: collection now always relies on the async advancement consumer.

Forcing ForceAsyncAdvance: true unconditionally here (unlike the subscription-sync path, which is gated by SubscriptionSyncForceAsyncAdvance) means every collected invoice now depends on the AdvanceStandardInvoiceEvent consumer actually running to leave draft.created. Worth confirming that consumer's throughput/alerting is solid, since a stuck or lagging consumer would now silently stall all collection-created invoices with no config-level fallback to synchronous advancement.

🤖 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 `@openmeter/billing/worker/collect/collect.go` around lines 105 - 114, The
collect flow in collect.go is now hard-wired to async advancement via
InvoicePendingLines with ForceAsyncAdvance set to true, so collected invoices
fully depend on the AdvanceStandardInvoiceEvent consumer. Update this path to
follow the same guarded behavior as the subscription-sync flow, using a
configurable or fallback mechanism instead of always forcing async advancement,
and keep the existing billing.WithPartialInvoiceLinesDisabled usage intact while
adjusting the collect logic around InvoicePendingLines and params handling.
🤖 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.

Nitpick comments:
In `@app/config/billing.go`:
- Line 54: The billing config default for subscription-sync is being switched to
async for all users, so verify the change is backed by full end-to-end coverage
before keeping it on by default. Recheck that the `test/billing` integration
suite with `-tags=dynamic` actually ran in CI, and if it did not, gate the new
default behind a safer rollout mechanism or revert the
`billing.featureSwitches.subscriptionSyncForceAsyncAdvance` default in
`config/billing.go` until the state-machine transitions are validated.

In `@openmeter/billing/invoice.go`:
- Around line 457-463: Add a short doc comment for ForceAsyncAdvance in
InvoicePendingLinesInput, and mirror the same comment on the equivalent field in
stdinvoice.go. Clarify the observable business behavior: when true, the invoice
stays in draft.created and advancement is deferred to an async published event
instead of completing synchronously in the current call. Use the field names
ForceAsyncAdvance and InvoicePendingLinesInput (and the mirrored stdinvoice
struct) to locate the spots.

In `@openmeter/billing/worker/collect/collect.go`:
- Around line 105-114: The collect flow in collect.go is now hard-wired to async
advancement via InvoicePendingLines with ForceAsyncAdvance set to true, so
collected invoices fully depend on the AdvanceStandardInvoiceEvent consumer.
Update this path to follow the same guarded behavior as the subscription-sync
flow, using a configurable or fallback mechanism instead of always forcing async
advancement, and keep the existing billing.WithPartialInvoiceLinesDisabled usage
intact while adjusting the collect logic around InvoicePendingLines and params
handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 784de61f-883a-4803-b9dd-edc2b56d765e

📥 Commits

Reviewing files that changed from the base of the PR and between 10cdf05 and 16820a7.

📒 Files selected for processing (14)
  • app/common/billing.go
  • app/config/billing.go
  • app/config/config_test.go
  • cmd/billing-worker/wire_gen.go
  • cmd/jobs/internal/wire_gen.go
  • config.example.yaml
  • openmeter/billing/invoice.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/stdinvoice.go
  • openmeter/billing/worker/collect/collect.go
  • openmeter/billing/worker/subscriptionsync/service/service.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • test/billing/invoice_test.go

@turip turip added the release-note/misc Miscellaneous changes label Jul 1, 2026
@turip turip enabled auto-merge (squash) July 1, 2026 17:24
@turip turip merged commit fc2ec36 into main Jul 1, 2026
27 of 28 checks passed
@turip turip deleted the feat/force-async-advance branch July 1, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants