Skip to content

feat(reconcile): unify custom and predefined role handling under one field model#1779

Draft
rohilsurana wants to merge 2 commits into
mainfrom
feat/reconcile-role-unified-field-model
Draft

feat(reconcile): unify custom and predefined role handling under one field model#1779
rohilsurana wants to merge 2 commits into
mainfrom
feat/reconcile-role-unified-field-model

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

What

Collapses the Role kind's two code paths (custom roles and predefined roles) into one field
model, part of adopting rules v2. A role's managed fields now follow a single rule: a field
written in the file is the whole desired value; an omitted field takes the role's default;
an explicitly empty field means empty. Custom and predefined roles differ only in their
default (empty for custom, the shipped definition for predefined) and in that a predefined
role is never created or deleted by the flow.

Stacked on #1776 (the Validate change), which this branches from. Retarget to main
once that merges.

Why

Role was the most complex kind. The old code had a separate diffPredefinedRole with four
carve-out conditions, keep-if-omitted guards on custom roles, and three empty-list
rejections, all to protect the export round trip. Measured effect of the unification on
role.go:

  • branch keywords 42 -> 15, compound conditions (&&/||) 13 -> 1
  • diffPredefinedRole (53 lines) deleted, folded into one diffRoles
  • validateRoleSpec 24 lines / 4 rules -> 8 lines / 2 rules
  • production code (role.go + reconciler) down ~55 lines

It also closes Role's two gaps on the rules-v2 compatibility audit (Rule 2, the field model,
and Rule 5, the export round trip).

The model

  • roleDefault(name) returns the default fields: the shipped definition for a predefined
    role, empty for a custom role.
  • resolve lays the spec's present fields over the default; diffFields reports what
    changed. One path for both role types.
  • Presence tracking uses pointers (*string, *[]string) so an omitted field and an
    explicitly empty field are distinguishable, and so export can write scopes: [] for a
    field that is empty on the server. This removes the carve-outs entirely; the round trip
    was verified over the tricky reachable states (a predefined field emptied on the server, a
    custom role whose permissions were emptied).

Behavior changes to know

  • Omitting a field on a role now means its default, not "keep the server value." For a custom
    role an omitted title clears it; for a predefined role an omitted field converges to the
    definition (unchanged from before). Files generated by frontier export write every
    managed field, so the normal edit-the-export workflow is unaffected; only a hand-trimmed
    file behaves differently.
  • An unlisted predefined role with an empty legacy field now converges to its definition
    instead of staying frozen. More predictable, and export keeps such roles listed so the
    round trip never triggers a surprise.
  • The "a custom role must list at least one permission" rule and the empty-list rejections
    are gone: an empty set is a legal, explicit value now.

Testing

  • go build, go test ./internal/reconcile/..., go vet, gofmt, golangci-lint all
    pass. Test updates were mostly mechanical (pointer literals), plus three new cases: a
    zero-permission custom role round-trips, an omitted custom title clears, and a predefined
    field emptied on the server round-trips.

Follow-up

RFC 0001 should be updated to describe the one field model and drop the predefined-role
"two edges" paragraph, since those cases no longer exist. That is a separate change.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jul 20, 2026 12:07pm

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2752d099-8f93-4986-9303-06aec9982351

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 29741017461

Coverage decreased (-0.07%) to 46.072%

Details

  • Coverage decreased (-0.07%) from the base build.
  • Patch coverage: 105 of 105 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 38331
Covered Lines: 17660
Line Coverage: 46.07%
Coverage Strength: 13.38 hits per line

💛 - Coveralls

Base automatically changed from feat/reconcile-validate-before-apply to main July 20, 2026 15:03
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