Skip to content

governance: quarterly budget windows with configurable fiscal start - #5997

Merged
akshaydeo merged 1 commit into
mainfrom
quarterly-budget-window-math
Aug 10, 2026
Merged

governance: quarterly budget windows with configurable fiscal start#5997
akshaydeo merged 1 commit into
mainfrom
quarterly-budget-window-math

Conversation

@akshaydeo

@akshaydeo akshaydeo commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for configurable fiscal quarter (1Q) budget reset windows. Previously, budgets could only reset on sub-day, daily, weekly, monthly, or yearly boundaries. This change introduces a 1Q duration suffix and an optional reset_config.quarter_start_month field that lets operators define which month opens Q1, enabling non-calendar fiscal years (e.g. April in the UK, October for the US federal government, February for custom fiscal calendars).

Changes

  • GetCalendarPeriodStart and CountCalendarPeriods now accept an explicit quarterStart time.Month parameter. All call sites that carry no fiscal calendar (rate limits, tests using non-quarterly durations) pass the new QuarterStartNotApplicable constant, which is time.January. The parameter is required rather than optional so that adding a budget call site is a compile error until the budget's own definition is threaded through.
  • quarterStartAt implements the boundary arithmetic in absolute months (year*12 + month) so the year boundary needs no special case. A fiscal year opening in November correctly places January in a quarter that began the previous calendar year.
  • IsCalendarAlignableDuration now includes the Q suffix alongside d, w, M, and Y.
  • BudgetResetConfig.QuarterStart() is extracted onto the config struct itself so the reconciler can compare an old definition against a new one through the same normalisation, preventing a spurious re-snap when an unset config and an explicit January are compared.
  • TableBudget.QuarterStartMonth() delegates to ResetConfig.QuarterStart() and remains safe on a nil receiver.
  • newBudgetFromRequest replaces five near-identical struct literals across the reconcilers. A dropped field in a literal is invisible at the API layer; a missing assignment in one constructor is caught in one place.
  • applyResetConfigToExistingBudget moves LastReset onto the new fiscal boundary when the quarter definition actually changes. It compares normalised months so that switching between an unset config and an explicit January does not trigger a re-snap.
  • budgetLastReset now takes the full *TableBudget rather than a bare duration string so a quarterly window snaps to the budget's own fiscal quarter.
  • CreateBudgetRequest and UpdateBudgetRequest gain a ResetConfig field, and validateBudget rejects a quarter definition on a non-quarterly duration and a quarter_start_month outside 1–12.
  • config.schema.json documents 1Q as a valid reset_duration and adds the reset_config object with quarter_start_month.
  • budgetresetconfighash_test.go and budgetresetconfig_test.go are consolidated into budget_test.go and budget_test.go (tables package) respectively, removing the separate files.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/configstore/...
go test ./framework/configstore/tables/...
go test ./plugins/governance/...
go test ./transports/bifrost-http/handlers/...

Key scenarios to verify:

  • A 1Q budget with reset_config.quarter_start_month: 4 resets on April 1, July 1, October 1, and January 1.
  • A 1Q budget with reset_config.quarter_start_month: 2 resets on February 1, May 1, August 1, and November 1.
  • A 1Q budget with no reset_config behaves identically to one with quarter_start_month: 1.
  • Changing quarter_start_month on an existing budget moves LastReset to the new boundary and does not mark the budget as immediately due.
  • A reset_config on a 1M budget is rejected with a validation error.
  • Non-quarterly budgets (existing deployments) produce the same hash before and after the upgrade.

Breaking changes

  • Yes
  • No

GetCalendarPeriodStart and CountCalendarPeriods have a new required quarterStart time.Month parameter. Any code outside this repository calling these functions directly must be updated to pass tables.QuarterStartNotApplicable (or the budget's own QuarterStartMonth()) at each call site.

Related issues

Closes #4851 (perpetually-due budget regression when a quarterly duration falls through GetCalendarPeriodStart to returning now).

Security considerations

No authentication, secrets, or PII changes. The quarter_start_month field is operator-supplied configuration stored in the governance database; it is validated to the range 1–12 before persistence.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@akshaydeo
akshaydeo marked this pull request as ready for review August 9, 2026 18:45

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for quarterly budget reset periods (1Q).
    • Added optional fiscal quarter start-month configuration.
    • Quarterly budgets align to configured fiscal boundaries and resnap when settings change.
    • Budget creation and updates preserve usage while applying reset settings consistently.
  • Bug Fixes

    • Invalid or missing quarter settings default to January.
    • Prevented repeated resets within the same quarter.
    • Added validation to reject reset settings on non-quarterly budgets.
  • Documentation

    • Updated the configuration schema with quarterly reset options and validation rules.

Walkthrough

The change adds quarterly budget durations with configurable fiscal quarter starts. It updates calendar calculations, persistence, API validation, governance reconciliation, reset behavior, rate-limit exclusions, compatibility hashing, and tests.

Changes

Quarterly budget reset flow

Layer / File(s) Summary
Quarterly calendar and persistence contracts
framework/configstore/tables/*, framework/configstore/rdb_test.go
Calendar helpers support fiscal quarters and explicit non-quarterly callers. Budget windows use normalized quarter starts. Persistence and validation tests cover configuration handling.
Reset configuration API and governance integration
transports/config.schema.json, transports/bifrost-http/handlers/governance.go, plugins/governance/*
Budget requests accept ResetConfig. Validation restricts quarter settings to quarterly budgets and valid months. Creation, updates, reconciliation, and reset calculations preserve fiscal-quarter settings.
Compatibility and behavior tests
framework/configstore/budget_test.go, transports/schema_test/config_schema_test.go, transports/bifrost-http/handlers/governance_test.go, plugins/governance/budgetcycle_test.go
Tests cover legacy hash compatibility, schema validation, fiscal-boundary changes, usage preservation, multi-node convergence, and quarterly reset cadence.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant GovernanceHandlers
  participant TableBudget
  participant CalendarHelpers
  participant Storage
  Client->>GovernanceHandlers: Submit budget with ResetConfig
  GovernanceHandlers->>TableBudget: Validate and apply ResetConfig
  GovernanceHandlers->>CalendarHelpers: Calculate fiscal-quarter boundary
  CalendarHelpers-->>GovernanceHandlers: Return LastReset
  GovernanceHandlers->>Storage: Persist or reconcile budget
Loading

Possibly related PRs

  • maximhq/bifrost#6000: Both changes add quarterly budget support and fiscal-quarter configuration.
  • maximhq/bifrost#6003: Both changes modify budget calendar alignment and reset-boundary handling.
  • maximhq/bifrost#6004: Both changes modify budget reset reconciliation, but address different reset behaviors.

Suggested reviewers: pratham-mishra04, danpiths, bearts

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes do not implement issue [#4851]'s required fix for request-time rate-limit resets and expensive reference refreshes. Implement the rate-limit reset hot-path fix from [#4851], or remove the issue link and reference an issue that covers quarterly budget windows.
Out of Scope Changes check ⚠️ Warning Most changes implement quarterly budget functionality and schema updates, which are unrelated to the rate-limit CPU issue [#4851]. Split the quarterly budget feature into a separate pull request or link the appropriate feature issue, and keep this PR focused on [#4851].
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The description covers the feature, design changes, affected areas, tests, breaking API changes, issue link, security, and checklist status.
Title check ✅ Passed The title clearly and concisely describes configurable fiscal-quarter budget windows, which is the main change.
✨ 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 quarterly-budget-window-math

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

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/handlers/governance.go (1)

348-350: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve ResetConfig in legacy budget updates

A reset_config-only legacy budget request currently satisfies isBudgetRemovalRequest, so it deletes the existing budget. Updating that predicate alone is insufficient because coerceLegacyBudget does not copy ResetConfig into CreateBudgetRequest. Exclude ResetConfig from removal detection, propagate it during coercion, and add a regression test.

🤖 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 `@transports/bifrost-http/handlers/governance.go` around lines 348 - 350,
Update isBudgetRemovalRequest to require ResetConfig to be nil, so
reset_config-only requests are not treated as removals; update
coerceLegacyBudget to copy ResetConfig into CreateBudgetRequest, and add a
regression test covering preservation of reset_config in legacy budget updates.
plugins/governance/store.go (1)

2068-2086: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the Q rate-limit compatibility change.

"1Q" is accepted as a positive 90-day duration. For IsCalendarAligned == true, this commit changes it from a rolling reset to a calendar-quarter reset. Confirm deployed rows cannot contain "Q"; otherwise add release notes and upgrade guidance.

🤖 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 `@plugins/governance/store.go` around lines 2068 - 2086, The
rateLimitResetTarget change makes calendar-aligned “1Q” durations use
calendar-quarter resets, so document this compatibility change in the project’s
release notes and upgrade guidance. Explicitly state that “1Q” remains a valid
positive 90-day duration, now resets on calendar quarters when IsCalendarAligned
is true, and confirm deployed rows cannot contain “Q” (or provide migration
guidance if they can).
🧹 Nitpick comments (2)
transports/config.schema.json (1)

482-494: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider enforcing the quarterly-only rule in the schema, not only in the description.

The description says reset_config is valid only when reset_duration is quarterly. The Go validator enforces this and returns "reset_config is only valid on a quarterly reset duration". The schema does not, so config.json validation passes and the failure surfaces later at load time.

The budget item already carries an allOf block for the override_mode rules, so a matching conditional fits the existing structure. A reset_duration pattern check for the Q suffix would move the failure to the earliest point.

This is optional. The runtime already fails closed.

🤖 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 `@transports/config.schema.json` around lines 482 - 494, Update the budget item
schema’s existing allOf validation structure to add a conditional requiring
reset_duration to match the quarterly “1Q” form whenever reset_config is
present. Preserve the current reset_config property definitions and ensure
non-quarterly configurations containing reset_config fail schema validation.

Source: Path instructions

framework/configstore/tables/budget_test.go (1)

241-249: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the SQLite test pool to one connection.

Call db.DB() and SetMaxOpenConns(1) before AutoMigrate; otherwise a second pooled connection sees an empty :memory: database.

🤖 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 `@framework/configstore/tables/budget_test.go` around lines 241 - 249, Update
setupBudgetTestDB to retrieve the underlying SQL database via db.DB() and set
its maximum open connections to 1 before AutoMigrate, handling any returned
error consistently with the existing require checks.

Source: Linters/SAST tools

🤖 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 `@framework/configstore/budget_test.go`:
- Around line 94-110: Extend the GenerateBudgetHash test with a non-nil
ResetConfig whose QuarterStartMonth is 0, representing an omitted
quarter_start_month value. Generate its hash and assert it equals januaryHash,
while preserving the existing nil-config and explicit-January assertions.

In `@transports/bifrost-http/handlers/governance.go`:
- Around line 269-276: Align the framework dependencies used by the governance
modules with a release exposing BudgetResetConfig, QuarterStartNotApplicable,
and the three-argument GetCalendarPeriodStart API: update transports/go.mod and
plugins/governance/go.mod, or add temporary local replace directives if
unreleased. Run go mod tidy in both modules; the affected code is
transports/bifrost-http/handlers/governance.go around UpdateBudgetRequest and
plugins/governance/store.go around its calendar-period usage.

In `@transports/config.schema.json`:
- Around line 486-491: Update the quarter_start_month schema definition to
accept 0 as a valid explicit unset value, changing its minimum constraint while
preserving the 1–12 upper bound. Keep the description’s “omitted or 0 means
January” wording and ensure it remains consistent with validateBudget and
BudgetResetConfig.QuarterStart().

---

Outside diff comments:
In `@plugins/governance/store.go`:
- Around line 2068-2086: The rateLimitResetTarget change makes calendar-aligned
“1Q” durations use calendar-quarter resets, so document this compatibility
change in the project’s release notes and upgrade guidance. Explicitly state
that “1Q” remains a valid positive 90-day duration, now resets on calendar
quarters when IsCalendarAligned is true, and confirm deployed rows cannot
contain “Q” (or provide migration guidance if they can).

In `@transports/bifrost-http/handlers/governance.go`:
- Around line 348-350: Update isBudgetRemovalRequest to require ResetConfig to
be nil, so reset_config-only requests are not treated as removals; update
coerceLegacyBudget to copy ResetConfig into CreateBudgetRequest, and add a
regression test covering preservation of reset_config in legacy budget updates.

---

Nitpick comments:
In `@framework/configstore/tables/budget_test.go`:
- Around line 241-249: Update setupBudgetTestDB to retrieve the underlying SQL
database via db.DB() and set its maximum open connections to 1 before
AutoMigrate, handling any returned error consistently with the existing require
checks.

In `@transports/config.schema.json`:
- Around line 482-494: Update the budget item schema’s existing allOf validation
structure to add a conditional requiring reset_duration to match the quarterly
“1Q” form whenever reset_config is present. Preserve the current reset_config
property definitions and ensure non-quarterly configurations containing
reset_config fail schema validation.
🪄 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: 5cc39f3f-3a45-401a-bf13-6ebc5aeaa22f

📥 Commits

Reviewing files that changed from the base of the PR and between df5aa2b and fc21eb0.

📒 Files selected for processing (14)
  • framework/configstore/budget_test.go
  • framework/configstore/budgetresetconfighash_test.go
  • framework/configstore/rdb_test.go
  • framework/configstore/tables/budget.go
  • framework/configstore/tables/budget_test.go
  • framework/configstore/tables/budgetresetconfig_test.go
  • framework/configstore/tables/budgetwindow_test.go
  • framework/configstore/tables/utils.go
  • plugins/governance/budgetcycle_test.go
  • plugins/governance/ratelimitreset_test.go
  • plugins/governance/store.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/governance_test.go
  • transports/config.schema.json
💤 Files with no reviewable changes (2)
  • framework/configstore/tables/budgetresetconfig_test.go
  • framework/configstore/budgetresetconfighash_test.go

Comment thread framework/configstore/budget_test.go
Comment thread transports/bifrost-http/handlers/governance.go
Comment thread transports/config.schema.json

@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 `@transports/config.schema.json`:
- Around line 482-493: Restrict the reset_config schema definition in
transports/config.schema.json:482-493 to configurations whose reset_duration is
exactly "1Q" by adding the requested allOf conditional. Update
transports/schema_test/config_schema_test.go:1079-1121 to parameterize
budgetConfig by duration and add coverage rejecting reset_config with "1M";
retain acceptance for quarterly budgets.
🪄 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: 9b11d605-3d73-455e-a74a-ce17c13a7d04

📥 Commits

Reviewing files that changed from the base of the PR and between fc21eb0 and eb925a0.

📒 Files selected for processing (3)
  • framework/configstore/budget_test.go
  • transports/config.schema.json
  • transports/schema_test/config_schema_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • framework/configstore/budget_test.go

Comment thread transports/config.schema.json
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 10, 2026
danpiths
danpiths previously approved these changes Aug 10, 2026
@akshaydeo
akshaydeo force-pushed the quarterly-budget-window-math branch from eb925a0 to a2315b7 Compare August 10, 2026 06:57
@akshaydeo
akshaydeo force-pushed the quarterly-budget-storage branch from 2377591 to 0c4b974 Compare August 10, 2026 06:57
@akshaydeo
akshaydeo force-pushed the quarterly-budget-storage branch from 0c4b974 to 436fb8e Compare August 10, 2026 21:38
@akshaydeo
akshaydeo force-pushed the quarterly-budget-window-math branch from a2315b7 to 2bdb827 Compare August 10, 2026 21:38

akshaydeo commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Aug 10, 9:42 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 10, 9:45 PM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 10, 9:46 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from quarterly-budget-storage to graphite-base/5997 August 10, 2026 21:43
@akshaydeo
akshaydeo changed the base branch from graphite-base/5997 to main August 10, 2026 21:43
@akshaydeo
akshaydeo dismissed stale reviews from danpiths and coderabbitai[bot] August 10, 2026 21:43

The base branch was changed.

@akshaydeo
akshaydeo force-pushed the quarterly-budget-window-math branch from 2bdb827 to 1dd593f Compare August 10, 2026 21:45
@akshaydeo
akshaydeo merged commit e394d60 into main Aug 10, 2026
15 checks passed
@akshaydeo
akshaydeo deleted the quarterly-budget-window-math branch August 10, 2026 21:46
atharvamhaske pushed a commit to atharvamhaske/bifrost that referenced this pull request Aug 13, 2026
…aximhq#5997)

## Summary

Adds support for configurable fiscal quarter (`1Q`) budget reset windows. Previously, budgets could only reset on sub-day, daily, weekly, monthly, or yearly boundaries. This change introduces a `1Q` duration suffix and an optional `reset_config.quarter_start_month` field that lets operators define which month opens Q1, enabling non-calendar fiscal years (e.g. April in the UK, October for the US federal government, February for custom fiscal calendars).

## Changes

- `GetCalendarPeriodStart` and `CountCalendarPeriods` now accept an explicit `quarterStart time.Month` parameter. All call sites that carry no fiscal calendar (rate limits, tests using non-quarterly durations) pass the new `QuarterStartNotApplicable` constant, which is `time.January`. The parameter is required rather than optional so that adding a budget call site is a compile error until the budget's own definition is threaded through.
- `quarterStartAt` implements the boundary arithmetic in absolute months (`year*12 + month`) so the year boundary needs no special case. A fiscal year opening in November correctly places January in a quarter that began the previous calendar year.
- `IsCalendarAlignableDuration` now includes the `Q` suffix alongside `d`, `w`, `M`, and `Y`.
- `BudgetResetConfig.QuarterStart()` is extracted onto the config struct itself so the reconciler can compare an old definition against a new one through the same normalisation, preventing a spurious re-snap when an unset config and an explicit January are compared.
- `TableBudget.QuarterStartMonth()` delegates to `ResetConfig.QuarterStart()` and remains safe on a nil receiver.
- `newBudgetFromRequest` replaces five near-identical struct literals across the reconcilers. A dropped field in a literal is invisible at the API layer; a missing assignment in one constructor is caught in one place.
- `applyResetConfigToExistingBudget` moves `LastReset` onto the new fiscal boundary when the quarter definition actually changes. It compares normalised months so that switching between an unset config and an explicit January does not trigger a re-snap.
- `budgetLastReset` now takes the full `*TableBudget` rather than a bare duration string so a quarterly window snaps to the budget's own fiscal quarter.
- `CreateBudgetRequest` and `UpdateBudgetRequest` gain a `ResetConfig` field, and `validateBudget` rejects a quarter definition on a non-quarterly duration and a `quarter_start_month` outside 1–12.
- `config.schema.json` documents `1Q` as a valid `reset_duration` and adds the `reset_config` object with `quarter_start_month`.
- `budgetresetconfighash_test.go` and `budgetresetconfig_test.go` are consolidated into `budget_test.go` and `budget_test.go` (tables package) respectively, removing the separate files.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./framework/configstore/...
go test ./framework/configstore/tables/...
go test ./plugins/governance/...
go test ./transports/bifrost-http/handlers/...
```

Key scenarios to verify:

- A `1Q` budget with `reset_config.quarter_start_month: 4` resets on April 1, July 1, October 1, and January 1.
- A `1Q` budget with `reset_config.quarter_start_month: 2` resets on February 1, May 1, August 1, and November 1.
- A `1Q` budget with no `reset_config` behaves identically to one with `quarter_start_month: 1`.
- Changing `quarter_start_month` on an existing budget moves `LastReset` to the new boundary and does not mark the budget as immediately due.
- A `reset_config` on a `1M` budget is rejected with a validation error.
- Non-quarterly budgets (existing deployments) produce the same hash before and after the upgrade.

## Breaking changes

- [x] Yes
- [ ] No

`GetCalendarPeriodStart` and `CountCalendarPeriods` have a new required `quarterStart time.Month` parameter. Any code outside this repository calling these functions directly must be updated to pass `tables.QuarterStartNotApplicable` (or the budget's own `QuarterStartMonth()`) at each call site.

## Related issues

Closes maximhq#4851 (perpetually-due budget regression when a quarterly duration falls through `GetCalendarPeriodStart` to returning `now`).

## Security considerations

No authentication, secrets, or PII changes. The `quarter_start_month` field is operator-supplied configuration stored in the governance database; it is validated to the range 1–12 before persistence.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
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.

2 participants