Skip to content

fix(reconcile): validate the permission namespace charset at plan time#1797

Merged
rohilsurana merged 2 commits into
mainfrom
fix/reconcile-permission-namespace-charset
Jul 23, 2026
Merged

fix(reconcile): validate the permission namespace charset at plan time#1797
rohilsurana merged 2 commits into
mainfrom
fix/reconcile-permission-namespace-charset

Conversation

@rohilsurana

Copy link
Copy Markdown
Member

What

Closes the Permission kind's Rule 2 and Rule 4 gaps on the rules-v2 audit. Reconcile-side only, one commit.

The gap

A permission's slug is service_resource_verb, built by joining the three parts with _ (schema.FQPermissionNameFromNamespace). The verb was charset-checked but the namespace was not, and _ is legal inside a namespace part. So the map from {namespace, name} to slug was not one-to-one: resource_order/item + get and resource/order_item + get both flatten to resource_order_item_get.

The diff keys on that slug. So a file entry whose namespace differs from a stored one but collides on the slug was treated as already present: the plan showed zero ops while the desired state actually differed (Rule 2), and because nothing ever detected the mismatch it never self-corrected on re-run (Rule 4).

The fix

  • New schema.IsValidPermissionNamespace: the namespace must be service/resource with each part lowercase alphanumeric, no underscore.
  • Called from validatePermissionSpec (server-free), so it runs in both Validate (whole file, before any apply) and the diff. An ambiguous namespace now fails the plan up front with a clear message.

Forbidding underscore-in-a-part makes the slug one-to-one, which closes both cells at once. The rule sits inside SpiceDB's own object-type grammar (lowercase, and although SpiceDB allows _, Frontier's slug scheme cannot represent it unambiguously), so it never rejects a namespace the server could actually store, apart from the ambiguous underscore ones the fix targets.

Scope decisions

  • Reconcile-side only. This fully closes the reconcile Rule 2/4 gap. The server's CreatePermission still has no namespace charset check, so a direct API call could create an ambiguous namespace outside the reconcile flow; adding the same check there is a reasonable follow-up but is out of scope here.
  • Pre-existing malformed rows. If a database already holds a custom permission whose namespace has an underscore in a part (SpiceDB allows it), Export will emit it and the new Validate will reject re-reconciling it. That is intended: the loud failure names the offending namespace so an operator renames it (rename = delete + create, per Rule 1). No silent absorption.
  • Charset is minimal (lowercase alphanumeric, no underscore), consistent with the existing name check, rather than fully mirroring SpiceDB's no-leading-digit rule.

Testing

  • Test first. The headline: a file namespace that flattens to a stored permission's slug now fails instead of planning zero ops. Plus: underscore-in-a-part and uppercase namespaces are rejected, valid custom namespaces (resource/aoi, user/project, org/user, compute/disk) still pass, the check fails in Validate before any server call, and a direct unit test for IsValidPermissionNamespace.
  • go build, go vet, go test ./internal/reconcile/... ./internal/bootstrap/schema/..., gofmt, and golangci-lint all pass.

Status

Closes the last two audit cells (Permission Rules 2 and 4). Targets main.

The permission slug is service_resource_verb joined with "_", but the namespace charset was never checked, only the verb. So a namespace with an underscore in a part (resource_order/item) flattened to the same slug as a differently-spelled one (resource/order_item) and was silently treated as already present: the diff planned zero ops while the desired state differed (rule 2), and nothing ever detected it (rule 4). Add IsValidPermissionNamespace (each of service/resource lowercase alphanumeric, no underscore) and call it from validatePermissionSpec, so an ambiguous namespace fails the plan up front. Reconcile-side only; the rule sits inside SpiceDB's object-type grammar, so it never rejects a namespace the server could store, except the ambiguous underscore ones the fix targets.
@vercel

vercel Bot commented Jul 23, 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 23, 2026 10:03am

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rohilsurana, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ad11a00-bf5d-4a91-87cc-bb9d1b117da4

📥 Commits

Reviewing files that changed from the base of the PR and between c93a3f2 and 319ad0f.

📒 Files selected for processing (2)
  • internal/bootstrap/schema/schema.go
  • internal/reconcile/permission.go
📝 Walkthrough

Walkthrough

Adds strict validation for custom permission namespaces, requiring exactly two lowercase alphanumeric components separated by /. Permission reconciliation now rejects invalid or ambiguous namespaces before planning or API creation, with expanded unit and reconciler coverage.

Changes

Permission namespace validation

Layer / File(s) Summary
Namespace validation contract and coverage
internal/bootstrap/schema/schema.go, internal/bootstrap/schema/schema_test.go
Adds IsValidPermissionNamespace and table-driven tests for valid formats, malformed structures, uppercase characters, underscores, and invalid symbols.
Reconciliation enforcement and collision coverage
internal/reconcile/permission.go, internal/reconcile/permission_reconciler_test.go, internal/reconcile/permission_test.go
Applies namespace validation during spec validation and tests rejection before API calls, collision handling, invalid custom namespaces, and valid add operations.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
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.

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.

Comment thread internal/bootstrap/schema/schema.go Outdated
Comment thread internal/reconcile/permission.go Outdated
@rohilsurana
rohilsurana marked this pull request as ready for review July 23, 2026 10:00
@coveralls

coveralls commented Jul 23, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29997795583

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.008%) to 46.682%

Details

  • Coverage increased (+0.008%) from the base build.
  • Patch coverage: 6 of 6 lines across 2 files are fully covered (100%).
  • 2 coverage regressions across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

2 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
internal/reconcile/permission.go 2 95.77%

Coverage Stats

Coverage Status
Relevant Lines: 38927
Covered Lines: 18172
Line Coverage: 46.68%
Coverage Strength: 14.31 hits per line

💛 - Coveralls

… error

Per review: IsValidPermissionNamespace is now a single regex (^[a-z0-9]+/[a-z0-9]+$) instead of a split plus rune loop, and the plan-time error drops the redundant "no underscore" (lowercase alphanumeric already excludes it).

@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)
internal/reconcile/permission_reconciler_test.go (1)

80-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the orchestration path for the side-effect guarantee.

Calling Validate directly guarantees that no API method is reached, so api.created does not prove the advertised “before any server call” behavior. Exercise Run or the full reconciliation path with fake API call counters and assert that invalid input causes zero server calls.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 60bcd133-1920-413d-af7b-f7ffed07d43f

📥 Commits

Reviewing files that changed from the base of the PR and between 152edd2 and c93a3f2.

📒 Files selected for processing (5)
  • internal/bootstrap/schema/schema.go
  • internal/bootstrap/schema/schema_test.go
  • internal/reconcile/permission.go
  • internal/reconcile/permission_reconciler_test.go
  • internal/reconcile/permission_test.go

@rohilsurana
rohilsurana merged commit a1293a0 into main Jul 23, 2026
8 checks passed
@rohilsurana
rohilsurana deleted the fix/reconcile-permission-namespace-charset branch July 23, 2026 10:05
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