Skip to content

feat(reconcile): add Permission and Role kinds, bootstrap only creates missing roles#1737

Open
rohilsurana wants to merge 3 commits into
feat/reconcile-exportfrom
feat/reconcile-roles-permissions
Open

feat(reconcile): add Permission and Role kinds, bootstrap only creates missing roles#1737
rohilsurana wants to merge 3 commits into
feat/reconcile-exportfrom
feat/reconcile-roles-permissions

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

Stacked on #1731; will rebase onto main once that squash-merges.

What

Two new desired-state kinds for frontier reconcile and frontier export, plus a bootstrap behavior change that makes role management through the API stick.

Permission kind

A permission is identity only (namespace + name). It is added or deleted, never updated.

kind: Permission
spec:
  - namespace: compute/order
    name: get
  - namespace: compute/order
    name: legacy
    delete: true
  • Base-schema permissions (app namespaces) are server-managed: the diff and export ignore them, and spec entries targeting them are rejected.
  • A custom permission on the server that the file does not mention fails the plan. Nothing is deleted by omission; deletion needs the explicit flag.
  • Validation mirrors the server: alphanumeric name, two-part service/resource namespace.

Role kind

Platform-level roles only. The role name is the identity and never changes. Title, permissions, and scopes are the managed fields: a field that is present in an entry is managed, a field that is omitted keeps its server value.

kind: Role
spec:
  - name: compute_order_manager
    title: Order Manager
    permissions: [compute_order_get, compute_order_update]
  - name: old_role
    delete: true
  - name: app_organization_owner   # predefined: title/permissions can be overridden
    title: Workspace Owner
  • Custom roles must appear in the file — kept, or marked delete: true — so the plan fails loudly on unaccounted roles instead of removing anything by omission.
  • Predefined roles are managed only when listed; unlisted ones are left alone. Deleting a predefined role is rejected (bootstrap would recreate it at boot).
  • Permission references accept any server-known form (slug, service/resource:verb, service.resource.verb); slugs are canonical.
  • Deleting a role that still has policy bindings fails with the server's foreign-key error, so a bound role cannot be dropped by accident.
  • Roles the reconciler creates or updates carry managed_by: frontier-reconcile in metadata for provenance.

Bootstrap change: existing roles are left alone

MigrateRoles used to write a role's definition back whenever its permission set drifted. That meant any change made through the API — including this reconciler's — was reverted on the next boot. Now boot creates a role only when it is missing and never updates an existing one. Operators own existing roles; the reconcile flow is the way to change them.

Trade-off, stated on purpose: when a new frontier version changes a predefined role's permission set, existing deployments no longer pick that up automatically — only fresh installs do. The desired-state file becomes the way to roll such changes out.

Why no role renames

An earlier draft allowed renaming predefined roles. Verifying it showed the server resolves predefined roles by name at runtime in 13+ places (org owner constraint checks, group creation, invitation acceptance, service-user creation, admin listings, notifications). Renaming a predefined role would break all of them, so the name is fixed as the identity and only the display title is renameable.

Tests

  • Diff-level: adds/updates/deletes ordering, merge-over-current semantics, unaccounted-role and unaccounted-permission failures, predefined-role guards, permission-format normalization, duplicate and conflict detection.
  • Reconciler-level with fake APIs: apply paths incl. the metadata marker, dry-run, base-namespace invisibility.
  • Round-trips: exporting each kind and reconciling the output plans no changes, including a retitled predefined role.
  • Bootstrap: existing role (even drifted) is not touched; missing role is created; transient Get errors do not fall through to create.

@vercel

vercel Bot commented Jul 3, 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 3, 2026 2:44pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: 03d42a5f-1bae-41ef-9692-990743c72ff3

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

coveralls commented Jul 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28667601403

Coverage increased (+0.3%) to 45.29%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 63 uncovered changes across 6 files (333 of 396 lines covered, 84.09%).
  • 71 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
internal/reconcile/role_reconciler.go 108 85 78.7%
internal/reconcile/permission_reconciler.go 77 61 79.22%
cmd/reconcile.go 20 5 25.0%
internal/reconcile/role.go 122 115 94.26%
cmd/export.go 1 0 0.0%
internal/reconcile/permission.go 65 64 98.46%
Total (7 files) 396 333 84.09%

Coverage Regressions

71 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
billing/invoice/service.go 71 5.54%

Coverage Stats

Coverage Status
Relevant Lines: 38059
Covered Lines: 17237
Line Coverage: 45.29%
Coverage Strength: 12.69 hits per line

💛 - Coveralls

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