Skip to content

quarterly budget - #5996

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

quarterly budget#5996
akshaydeo merged 1 commit into
mainfrom
quarterly-budget-storage

Conversation

@akshaydeo

@akshaydeo akshaydeo commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Budgets with a quarterly reset duration (1Q) previously had no way to configure which month begins Q1. This meant all quarterly budgets defaulted to a January fiscal year, making it impossible to model an April or October fiscal year start. This PR introduces a BudgetResetConfig struct persisted as a JSON blob (reset_config_json) on the governance_budgets table, allowing operators to specify a quarter_start_month per budget.

Changes

  • Added BudgetResetConfig struct with a QuarterStartMonth field (1–12), persisted as reset_config_json via GORM BeforeSave/AfterFind hooks on TableBudget.
  • Added QuarterStartMonth() accessor on TableBudget that defaults to time.January on nil receivers, nil configs, and zero values, so all call sites can read it unconditionally.
  • Added IsQuarterlyDuration() helper and "Q" suffix support to ParseDuration, approximating one quarter as 90 days (matching how "M" approximates 30 days).
  • Updated GenerateBudgetHash to include the effective quarter start month in the digest for quarterly budgets, so editing the quarter definition in config.json is detected as a change. Non-quarterly budgets produce byte-identical digests to avoid spurious resyncs on upgrade.
  • The hash reads QuarterStartMonth() rather than the raw ResetConfigJSON blob, ensuring a budget parsed from config.json (no blob yet) and its persisted counterpart hash identically.
  • Added migrationAddBudgetResetConfigColumn to add the nullable reset_config_json column to existing governance_budgets tables with no backfill, preserving the January-default cadence of all pre-existing budgets.
  • BeforeSave validates that ResetConfig is only set on quarterly durations and that QuarterStartMonth is in range before serializing, so a rejected config never reaches the column.

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/... ./framework/configstore/tables/...

Key test scenarios covered:

  • TestGenerateBudgetHashUnchangedForNonQuarterlyBudgets — upgrading does not trigger spurious config resyncs for non-quarterly budgets.
  • TestGenerateBudgetHashAgreesAcrossConfigAndDatabaseSources — a config.json budget and its persisted counterpart hash identically.
  • TestGenerateBudgetHashTracksQuarterStartMonth — editing the quarter start is detected as a change.
  • TestMigrationAddsBudgetResetConfigColumnToExistingTable — migration is idempotent on an already-provisioned table.
  • TestExistingBudgetsKeepTheirCadenceAfterMigration — pre-existing budgets read back with nil ResetConfig and a January quarter start.
  • TestBudgetResetConfigSurvivesNestedPreloadAfterFind fires on preloaded associations, ensuring cluster peers receive the correct quarter definition.

Breaking changes

  • Yes
  • No

The migration is additive and nullable with no backfill. All existing budgets continue to operate with a January quarter start, which is identical to their previous behavior.

Related issues

Security considerations

No auth, secrets, or PII implications. The reset_config_json column stores only a fiscal month integer.

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

@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 schedules using the Q duration.
    • Quarterly budgets can specify a fiscal quarter start month, defaulting to January.
    • Reset settings are saved and restored with budget data.
    • Added safe migration support for storing quarterly reset configuration.
  • Bug Fixes

    • Preserved existing budget behavior and hash compatibility for non-quarterly budgets.
    • Ensured quarterly budget hashes reflect changes to the quarter start month.

Walkthrough

The change adds quarterly budget reset configuration with a configurable quarter-start month. It adds Q duration parsing, validation, database persistence, schema migration, hash compatibility rules, and tests for migration, round-tripping, defaults, and hash behavior.

Changes

Quarterly budget reset configuration

Layer / File(s) Summary
Quarterly duration and budget model
framework/configstore/tables/budget.go, framework/configstore/tables/utils.go, framework/configstore/tables/budgetresetconfig_test.go
Q durations now parse as approximately 90-day multiples. BudgetResetConfig stores the quarter-start month. TableBudget validates, serializes, and reconstructs reset configuration.
Schema migration and configstore persistence
framework/configstore/migrations.go, framework/configstore/migrations_test.go, framework/configstore/budgetresetconfigstore_test.go
The migration adds the nullable reset_config_json column and rejects rollback. Budget persistence retains legacy values, stores quarterly settings, and clears reset configuration when the duration becomes non-quarterly.
Budget hash compatibility
framework/configstore/clientconfig.go, framework/configstore/budgetresetconfighash_test.go
Quarterly hashes include the effective quarter-start month. Non-quarterly hashes retain the legacy digest. Equivalent January representations produce matching hashes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Configstore
  participant TableBudget
  participant Database
  participant GenerateBudgetHash
  Configstore->>TableBudget: Update quarterly reset configuration
  TableBudget->>Database: Save ResetConfigJSON
  Database-->>TableBudget: Load ResetConfigJSON
  TableBudget-->>Configstore: Expose QuarterStartMonth
  Configstore->>GenerateBudgetHash: Hash budget fields and quarter start
  GenerateBudgetHash-->>Configstore: Return budget hash
Loading

Suggested reviewers: pratham-mishra04, bearts

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the quarterly budget feature, but it does not specify configurable quarter start months.
Description check ✅ Passed The description covers the purpose, implementation, testing, affected areas, breaking changes, security, and checklist items.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 quarterly-budget-storage

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

@akshaydeo
akshaydeo marked this pull request as ready for review August 9, 2026 18:01
@coderabbitai
coderabbitai Bot requested a review from Pratham-Mishra04 August 9, 2026 18:02

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

🤖 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/migrations.go`:
- Around line 11033-11040: Update the migration containing the Rollback function
for TableBudget.reset_config_json to mark it explicitly non-rollbackable
according to the migration framework’s supported semantics, rather than dropping
the column silently. If the framework requires a rollback callback, return a
clear non-rollbackable error and document that dropping the column permanently
deletes fiscal-quarter settings.

In `@framework/configstore/tables/budget.go`:
- Around line 140-147: Update TableBudget.WindowStart and its quarterly
calendar-boundary helper to pass b.QuarterStartMonth() alongside ResetDuration
and now, ensuring configured fiscal starts such as April are honored. Add a test
covering both January- and April-start quarterly budgets with distinct
boundaries.
🪄 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: 2dfd9618-edb7-444c-8062-69f55c4d1fe9

📥 Commits

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

📒 Files selected for processing (7)
  • framework/configstore/budgetresetconfighash_test.go
  • framework/configstore/budgetresetconfigstore_test.go
  • framework/configstore/clientconfig.go
  • framework/configstore/migrations.go
  • framework/configstore/tables/budget.go
  • framework/configstore/tables/budgetresetconfig_test.go
  • framework/configstore/tables/utils.go

Comment thread framework/configstore/migrations.go Outdated
Comment thread framework/configstore/tables/budget.go

@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 (1)
framework/configstore/migrations_test.go (1)

2951-2958: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover existing rows and column nullability.

This test inserts seed only after the migration. It can pass if the migration backfills existing rows or adds reset_config_json as non-null with a default. Insert a legacy budget after dropping the column and before running the migration. Then verify that the row survives with a NULL reset_config_json, and inspect column metadata to confirm nullability.

As per coding guidelines, migration tests should cover data-preservation edge paths. Based on learnings from the provided PR objective, this migration must add a nullable column without backfilling existing budgets.

🤖 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/migrations_test.go` around lines 2951 - 2958, Extend
the migration test around migrationAddBudgetResetConfigColumn by inserting a
legacy TableBudget row after dropping reset_config_json and before running the
migration. After migration, fetch that row and verify it remains present with a
NULL reset_config_json value, then inspect the column metadata to confirm
reset_config_json is nullable rather than required or defaulted.

Source: Coding guidelines

🤖 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 `@framework/configstore/migrations_test.go`:
- Around line 2951-2958: Extend the migration test around
migrationAddBudgetResetConfigColumn by inserting a legacy TableBudget row after
dropping reset_config_json and before running the migration. After migration,
fetch that row and verify it remains present with a NULL reset_config_json
value, then inspect the column metadata to confirm reset_config_json is nullable
rather than required or defaulted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 09201b63-d194-4583-998d-c648d13e5367

📥 Commits

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

📒 Files selected for processing (2)
  • framework/configstore/migrations.go
  • framework/configstore/migrations_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • framework/configstore/migrations.go

@akshaydeo
akshaydeo force-pushed the quarterly-budget-storage branch from 0c4b974 to 436fb8e 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:43 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit 44f38a0 into main Aug 10, 2026
14 checks passed
@akshaydeo
akshaydeo deleted the quarterly-budget-storage branch August 10, 2026 21:43
atharvamhaske pushed a commit to atharvamhaske/bifrost that referenced this pull request Aug 13, 2026
## Summary

Budgets with a quarterly reset duration (`1Q`) previously had no way to configure which month begins Q1. This meant all quarterly budgets defaulted to a January fiscal year, making it impossible to model an April or October fiscal year start. This PR introduces a `BudgetResetConfig` struct persisted as a JSON blob (`reset_config_json`) on the `governance_budgets` table, allowing operators to specify a `quarter_start_month` per budget.

## Changes

- Added `BudgetResetConfig` struct with a `QuarterStartMonth` field (1–12), persisted as `reset_config_json` via GORM `BeforeSave`/`AfterFind` hooks on `TableBudget`.
- Added `QuarterStartMonth()` accessor on `TableBudget` that defaults to `time.January` on nil receivers, nil configs, and zero values, so all call sites can read it unconditionally.
- Added `IsQuarterlyDuration()` helper and `"Q"` suffix support to `ParseDuration`, approximating one quarter as 90 days (matching how `"M"` approximates 30 days).
- Updated `GenerateBudgetHash` to include the effective quarter start month in the digest for quarterly budgets, so editing the quarter definition in `config.json` is detected as a change. Non-quarterly budgets produce byte-identical digests to avoid spurious resyncs on upgrade.
- The hash reads `QuarterStartMonth()` rather than the raw `ResetConfigJSON` blob, ensuring a budget parsed from `config.json` (no blob yet) and its persisted counterpart hash identically.
- Added `migrationAddBudgetResetConfigColumn` to add the nullable `reset_config_json` column to existing `governance_budgets` tables with no backfill, preserving the January-default cadence of all pre-existing budgets.
- `BeforeSave` validates that `ResetConfig` is only set on quarterly durations and that `QuarterStartMonth` is in range before serializing, so a rejected config never reaches the column.

## Type of change

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

## Affected areas

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

## How to test

```sh
go test ./framework/configstore/... ./framework/configstore/tables/...
```

Key test scenarios covered:

- `TestGenerateBudgetHashUnchangedForNonQuarterlyBudgets` — upgrading does not trigger spurious config resyncs for non-quarterly budgets.
- `TestGenerateBudgetHashAgreesAcrossConfigAndDatabaseSources` — a `config.json` budget and its persisted counterpart hash identically.
- `TestGenerateBudgetHashTracksQuarterStartMonth` — editing the quarter start is detected as a change.
- `TestMigrationAddsBudgetResetConfigColumnToExistingTable` — migration is idempotent on an already-provisioned table.
- `TestExistingBudgetsKeepTheirCadenceAfterMigration` — pre-existing budgets read back with nil `ResetConfig` and a January quarter start.
- `TestBudgetResetConfigSurvivesNestedPreload` — `AfterFind` fires on preloaded associations, ensuring cluster peers receive the correct quarter definition.

## Breaking changes

- [ ] Yes
- [x] No

The migration is additive and nullable with no backfill. All existing budgets continue to operate with a January quarter start, which is identical to their previous behavior.

## Related issues

## Security considerations

No auth, secrets, or PII implications. The `reset_config_json` column stores only a fiscal month integer.

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

1 participant