Skip to content

feat(reconcile): add BillingFeature kind - #2046

Open
rohilsurana wants to merge 6 commits into
mainfrom
feat/pricing-feature-hover-info
Open

rohilsurana wants to merge 6 commits into
mainfrom
feat/pricing-feature-hover-info

Conversation

@rohilsurana

@rohilsurana rohilsurana commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

What

This adds a BillingFeature kind to frontier reconcile and frontier export. Gitops can now manage each billing feature's title and metadata, for example a markdown tooltip.

apiVersion: v1
kind: BillingFeature
spec:
  - name: aoi_size_for_tasking
    title: "Max size for each Tasking AOI: 2,500 km²"
    metadata:
      info: "Imagery is charged separately. Contact [support](mailto:support@example.com)."

Why

Today the BillingProduct kind lists a product's features by name only. A feature's title and metadata could only be set by hand in the DB. The server also ignores title and metadata for features that already exist when a product is updated.

How it works

It follows RFC 0001.

  • Identity: the feature name, matched exactly as the server stores it, including case and spaces.
  • Field rule: a title left out becomes "". Metadata left out becomes {}.
  • Features are objects: a server feature missing from the file fails the plan. delete: true is rejected, because there is no API to delete a feature.
  • Name checks: a name that matches a server feature only by case fails the plan. So do exact duplicate names and blank names. Two server features whose names differ only by case are both allowed in the file.
  • Short names: server features with names shorter than 3 characters are out of scope, the same as BillingProduct.
  • Product links stay with BillingProduct:
    • An update sends back the feature's current product links, so a feature is never unlinked.
    • A create sends no links.
  • Order: in one file, BillingFeature must come before BillingProduct.
  • Validation: every planned request is checked against the API's own rules before anything is applied.
  • Export: writes name, title and metadata, sorted by name. Reconciling an export plans no changes.

Commits

  1. Spec and diff
  2. Reconciler
  3. Export
  4. Kind order rule
  5. CLI registration
  6. Docs, covering the reconcile guide, the CLI reference and an addendum to RFC 0001. The published RFC itself is not changed.

Tests

  • Unit tests:
    • the diff cases
    • product links sent back on update
    • a dry run makes no writes
    • validation
    • the export round trip, including nested metadata
    • short names out of scope
    • kind order, checking that no product is written before the order error
  • A local sandbox run:
    • apply, then a rerun with no changes
    • ListFeatures and ListPlans show the new title and metadata
    • product links unchanged
    • a reset, a missing feature, and the wrong kind order all behave as expected

Related

This needs the UpdateFeature change in #2045. Without it, the server ignores an empty title or empty metadata, so a reset would plan but not apply. The two PRs can be reviewed and merged in either order. The CLI and the server are expected to run the same version.

The product link bug found during review is tracked in #2047.

@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Nov 7, 2026 6:22pm UTC

@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Summary

Summary by CodeRabbit

  • New Features
    • Added support for exporting and reconciling billing features, including creating missing features and updating titles and metadata.
    • Reconciliation validates feature names and metadata, supports dry runs, and preserves existing product links when updating features.
    • Billing feature documents must appear before billing product documents. Features that cannot be managed or deleted are reported as errors.
  • Documentation
    • Updated CLI and reconciliation guides with billing feature support, ordering rules, and deletion limitations.

Adds billing-feature reconciliation, including validation, change detection, API operations, and export. Registers the reconciler and requires billing-feature documents to precede billing-product documents. Updates the CLI reference, reconciliation guide, and RFC addendum.

Changes: Billing feature reconciliation

Layer / File(s) Summary
Feature specification and diff
internal/reconcile/billingfeature.go, internal/reconcile/billingfeature_test.go
Adds billing-feature spec validation and change detection for names, titles, and metadata. The diff reports unlisted server features and sorts additions and updates by name. Tests cover validation, change detection, and operation ordering.
Reconciler API and operations
internal/reconcile/billingfeature_reconciler.go, internal/reconcile/billingfeature_reconciler_test.go
Adds API-backed listing, scope filtering, reconciliation, dry runs, create and update requests, and export. Tests cover API errors, partial reports, request fields, and export behavior.
Registry and document ordering
cmd/reconcile.go, internal/reconcile/reconcile.go, internal/reconcile/reconcile_test.go
Registers the billing-feature reconciler and requires billing-feature documents to precede billing-product documents. A test checks ordering behavior.
Billing feature documentation
cmd/reconcile.go, docs/content/docs/reference/cli.mdx, docs/content/docs/reconcile.mdx, docs/rfcs/*
Documents billing-feature support, desired-state rules, export behavior, deletion limits, and document ordering. The RFC addendum describes feature fields and update behavior.

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

Change: Feature

Possibly related PRs

  • raystack/frontier#2045: Changes UpdateFeature field replacement behavior that the billing-feature reconciliation uses.

Suggested reviewers: amangit07

Merge Risk: Moderate · up to 662c0

Applying a reset of a feature title or metadata only works when the server includes the matching server change. Otherwise the tool reports success while the old values remain, and the next run plans the same update again. Release the CLI and server together or confirm the dependent server change is included. A documentation line about listing short-name features should also be corrected.

Pre-merge checks: Passed 2

Check name Status Explanation
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.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

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

@coveralls

coveralls commented Nov 6, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 42400912187

Coverage increased (+0.2%) to 56.244%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 24 uncovered changes across 3 files (221 of 245 lines covered, 90.2%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
internal/reconcile/billingfeature_reconciler.go 136 123 90.44%
internal/reconcile/billingfeature.go 101 91 90.1%
cmd/reconcile.go 8 7 87.5%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 42428
Covered Lines: 23863
Line Coverage: 56.24%
Coverage Strength: 19.77 hits per line

💛 - Coveralls

@rohilsurana
rohilsurana force-pushed the feat/pricing-feature-hover-info branch from a43267c to 22fa519 Compare November 6, 2026 09:21
@rohilsurana
rohilsurana marked this pull request as ready for review November 6, 2026 09:21

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

Nitpick comments: 2

internal/reconcile/reconcile_test.go: 1

169-172: Trivial · Maintainability & Code Quality · Low value

Assert that no product write occurs before the ordering error.

Use a valid, nonempty product spec and assert that productAPI.created remains empty. The empty spec produces no product writes, so the test can pass if product reconciliation runs before the ordering check.

internal/reconcile/billingfeature_reconciler_test.go: 1

83-85: Trivial · Data Integrity & Integration · Heavy lift

Test reset persistence through the server.

This test checks only the request sent to a fake. The current server retains the old title and metadata when the update sends empty values, so the test cannot detect the failed reset. Add an integration test that reloads both fields after applying the reset and verifies that a second reconciliation plans no changes.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between f38ac11 and 22fa519.

Files selected for processing: 10

  • cmd/reconcile.go
  • docs/content/docs/reconcile.mdx
  • docs/content/docs/reference/cli.mdx
  • docs/rfcs/0001-declarative-reconcile.md
  • internal/reconcile/billingfeature.go
  • internal/reconcile/billingfeature_reconciler.go
  • internal/reconcile/billingfeature_reconciler_test.go
  • internal/reconcile/billingfeature_test.go
  • internal/reconcile/reconcile.go
  • internal/reconcile/reconcile_test.go

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

Comment thread docs/rfcs/0001-declarative-reconcile.md Resolved
Comment on lines +180 to +185
case opUpdate:
req = &frontierv1beta1.UpdateFeatureRequest{Id: op.current.ID, Body: &frontierv1beta1.FeatureRequestBody{
Name: op.current.Name,
Title: op.spec.Title,
Metadata: metadata,
ProductIds: op.current.ProductIDs,

@coderabbitai coderabbitai Bot Nov 6, 2026 •

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.

Major · Data Integrity & Integration · Heavy lift

Supported by static analysis

Script executed:

sed -n '145,200p' internal/reconcile/billingfeature_reconciler.go
sed -n '225,260p' internal/api/v1beta1connect/billing_product.go
sed -n '570,605p' billing/product/service.go
rg -n 'UpsertFeature\(' billing internal/api
sed -n '260,285p' docs/content/docs/reconcile.mdx

Repository: raystack/frontier

Length of output: 8321


Script executed:

for spec in 'billing/product/service.go:105:135' 'billing/product/service.go:600:645' 'billing/plan/service.go:145:172' 'billing/plan/service.go:235:285' 'internal/reconcile/billingfeature_reconciler.go:1:110' 'internal/reconcile/billingfeature.go:1:160' 'cmd/reconcile.go:1:110'; do IFS=: read -r f start end <<< "$spec"; echo "--- $f:$start-$end"; nl -ba "$f" | sed -n "${start},${end}p"; done
rg -n -F -e 'NewBillingFeatureReconciler' -e 'NewFrontierServiceClient' -e 'frontierURL' -e 'frontier_url' -- cmd internal/reconcile

Repository: raystack/frontier

Length of output: 23756


Script executed:

for spec in 'internal/reconcile/billingfeature_reconciler.go:109:164' 'internal/reconcile/billingfeature_reconciler_test.go:14:98' 'internal/store/postgres/billing_feature_repository.go:178:220' 'pkg/metadata/metadata.go:15:35' 'cmd/client.go:1:75'; do IFS=: read -r f start end <<< "$spec"; echo "--- $f:$start-$end"; nl -ba "$f" | sed -n "${start},${end}p"; done
rg -n -F -e 'Host ' -e '"host"' -e 'FRONTIER_HOST' -- cmd/root.go cmd/config.go cmd/client.go

Repository: raystack/frontier

Length of output: 11758


Support and check empty field replacement.

When a title or metadata is omitted, the current server retains the existing value. Reconcile reports success, but later runs plan the same update.

Add replacement support to UpdateFeature without changing partial updates for other UpsertFeature callers. Check the returned feature against the desired values and report an error if it did not converge. Document that resets require a server with replacement support, and test both successful resets and nonconverging responses.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this. The CLI and the server are expected to run the same version. The server side of the reset is in #2045.

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.

Analysis chain

Script executed:

#!/bin/bash
set -o pipefail
echo '--- PR metadata ---'
gh pr view 2045 --repo raystack/frontier --json title,state,mergedAt,baseRefName,headRefName,body,files
echo '--- Relevant diff excerpts ---'
gh pr diff 2045 --repo raystack/frontier | python3 -c '
import sys
s = sys.stdin.read()
for part in s.split("diff --git "):
    if not part.strip():
        continue
    lines = part.splitlines()
    if any(k in part for k in ("UpdateFeature", "UpsertFeature", "ReplaceFeature", "metadata", "Metadata")):
        print("diff --git " + lines[0])
        for i, line in enumerate(lines):
            if any(k in line for k in ("UpdateFeature", "UpsertFeature", "ReplaceFeature", "metadata", "Metadata", "ProductIds", "ProductIDs")):
                print("\n".join(lines[max(1, i-3):min(len(lines), i+5)]))
                print("---")
' | head -c 30000

Length of output: 33942


@rohilsurana Understood. I won’t pursue a CLI response check for a version mismatch.

#2045 is still open and contains the server-side replacement change. Before release, please verify that it is included and that a reset followed by a rerun plans no changes.

You are interacting with an AI system.

Comment thread internal/reconcile/billingfeature.go Resolved
Comment thread internal/reconcile/billingfeature.go Resolved
var kindDependencies = map[string][]string{
KindRole: {KindPermission},
KindRole: {KindPermission},
KindBillingProduct: {KindBillingFeature},

@coderabbitai coderabbitai Bot Nov 6, 2026 •

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.

Major · Data Integrity & Integration · Heavy lift

Supported by static analysis

Script executed:

git diff f38ac119be14d4fda11f874ecfad9b2c4d9b65e8 22fa519476ecf9d61ffe6d9539b00e4bc72d5b20 -- internal/reconcile/reconcile.go
nl -ba internal/reconcile/reconcile.go | sed -n '1,220p'
rg -n -F -e 'CreateProduct(' -e 'UpsertFeature(' -e 'ProductIDs' -- internal cmd | head -250

Repository: raystack/frontier

Length of output: 13787


Script executed:

rg -n -F -e 'func (s *Service) Create(' -e 'func (s *Service) UpsertFeature(' -- billing/product
nl -ba internal/reconcile/billingproduct_reconciler.go | sed -n '1,310p'
nl -ba internal/api/v1beta1connect/billing_product.go | sed -n '1,105p'
nl -ba internal/store/postgres/billing_feature_repository.go | sed -n '50,115p;160,235p'
nl -ba internal/reconcile/billingfeature_reconciler.go | sed -n '65,194p'

Repository: raystack/frontier

Length of output: 29606


Script executed:

nl -ba billing/product/service.go | sed -n '1,220p;530,670p'
nl -ba internal/store/postgres/billing_feature_repository.go | sed -n '99,160p'
nl -ba internal/reconcile/billingproduct.go | sed -n '1,250p'
rg -n -F -e 'UpsertFeature' -e 'ProductIDs' -- billing/product
rg -n -F -e 'features:' -e 'BillingFeature' -- docs/content/docs/reconcile.mdx docs/rfcs/0001-declarative-reconcile.md

Repository: raystack/frontier

Length of output: 32966


Script executed:

nl -ba internal/reconcile/billingproduct.go | sed -n '198,345p'
nl -ba billing/product/service.go | sed -n '230,290p;670,710p'
nl -ba billing/entitlement/service.go | sed -n '1,180p'
nl -ba docs/content/docs/reconcile.mdx | sed -n '245,330p'
nl -ba internal/reconcile/billingproduct_reconciler_test.go | sed -n '275,375p'
git diff f38ac119be14d4fda11f874ecfad9b2c4d9b65e8 22fa519476ecf9d61ffe6d9539b00e4bc72d5b20 -- internal/reconcile/billingproduct.go internal/reconcile/billingproduct_reconciler.go billing/product/service.go internal/store/postgres/billing_feature_repository.go

Repository: raystack/frontier

Length of output: 24592


Preserve existing feature links when creating a product.

If a new product references a feature already linked to another product, creation replaces the feature’s product IDs with the new ID. The earlier product can lose the feature and its subscribers can lose entitlement. Merge the new product ID with the existing links on the create path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. This is an older server bug in product.Service.Create, and this PR does not change it. Tracked in #2047.

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.

@rohilsurana Thanks for tracking the existing server bug in #2047. The feature-link risk remains until that bug is fixed.

Would you like a separate follow-up issue, or should #2047 remain the tracker?

You are interacting with an AI system.

@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

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 22fa519 and 662c0b3.

Files selected for processing: 6

  • docs/content/docs/reconcile.mdx
  • docs/rfcs/0001-declarative-reconcile-addendum-billingfeature.md
  • internal/reconcile/billingfeature.go
  • internal/reconcile/billingfeature_reconciler.go
  • internal/reconcile/billingfeature_test.go
  • internal/reconcile/reconcile_test.go
Files skipped from review as they are similar to previous changes: 1

  • docs/content/docs/reconcile.mdx

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Comment on lines +26 to +27
BillingFeature is an object, like BillingProduct and BillingPlan. Every feature on the server
must be in the file, and a missing one fails the plan.

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.

Minor · Functional Correctness · Quick win

Exclude out-of-scope features from the full-list requirement.

Features with names shorter than three characters are omitted from export. If a user follows this requirement and adds one to the file, reconciliation fails. State that only in-scope features must be listed, and that short-name entries are rejected.

This branch was successfully deployed

1 active deployment
Preview — 662c0b35 Deployed Nov 7, 2026 by vercel[bot]
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.

4 participants