Skip to content

feat: add permission condition coverage details#2954

Open
hieu-lee wants to merge 6 commits into
Permify:masterfrom
hieu-lee:bounty-837-condition-components
Open

feat: add permission condition coverage details#2954
hieu-lee wants to merge 6 commits into
Permify:masterfrom
hieu-lee:bounty-837-condition-components

Conversation

@hieu-lee
Copy link
Copy Markdown

@hieu-lee hieu-lee commented May 11, 2026

Summary

/claim #837

Fixes #837.

Adds scenario-level permission condition coverage to the existing coverage command. A scenario no longer gets only permission-name coverage for a complex expression such as:

permission view = system.view or ((is_public or (is_partner and partner) or (viewer or company.maintain or organization.maintain or team.view)) not denied)

The coverage report now walks the compiled permission tree and reports relation, attribute, tuple-to-userset, rule-call, and nested same-entity permission components that do not have matching test data for the asserted scenario.

Changes

  • Extracts leaf components from compiled base.Child permission trees.
  • Tracks component coverage per scenario and permission in EntityCoverageInfo.
  • Uses shape data plus scenario context tuples/attributes when checking whether components are covered.
  • Verifies tuple-to-userset components through both the local tuple-set edge and referenced entity data, so company.maintain is not covered by document#company alone.
  • Expands nested same-entity permission references while avoiding recursion loops.
  • Prints deterministic CLI output for permission condition coverage and uncovered components.
  • Adds --coverage-conditions to fail coverage runs below a minimum permission-condition coverage threshold.
  • Adds a regression test for the partial system.view branch case from the bounty issue.

Demo

Short demo video: https://github.com/hieu-lee/permify/blob/permify-837-demo/demos/permify-837/condition-coverage-demo.mp4

The demo shape and transcript are also on that branch:

Test Plan

  • go test ./pkg/development/coverage ./pkg/cmd
  • go test ./pkg/development/... ./pkg/cmd
  • go run ./cmd/permify coverage /Users/hieu.leduc/Desktop/codex-money-maker/artifacts/permify-837/condition-coverage-demo.yaml
  • go run ./cmd/permify coverage --coverage-conditions 100 /Users/hieu.leduc/Desktop/codex-money-maker/artifacts/permify-837/condition-coverage-demo.yaml exits with permission condition coverage < 100%.

I also started go test ./...; it hit unrelated integration-test failures because this local environment does not provide a permify host, then stayed idle in later environment-dependent packages, so I stopped that run after the relevant package tests had passed.

Summary by CodeRabbit

  • New Features

    • Added permission condition coverage threshold configuration and validation.
    • Extended coverage reporting to display per-permission condition coverage metrics, including covered and uncovered components with color-coded indicators.
  • Tests

    • Added comprehensive test cases for permission condition coverage calculation and tuple-to-userset rewrite semantics.

Review Change Stack

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-permission, per-scenario condition-component coverage: new types and storage, threads compiled schema through coverage computation, extracts and evaluates leaf condition components for coverage, displays sorted/color-coded results in the CLI, and adds tests validating component coverage.

Changes

Permission Condition Coverage Feature

Layer / File(s) Summary
Data Types & Schema
pkg/development/coverage/coverage.go
Introduces ConditionComponent and ConditionCoverageInfo; adds PermissionConditionCoverage to EntityCoverageInfo and TotalConditionsCoverage to SchemaCoverageInfo; imports sort and strings.
Computation Pipeline Setup
pkg/development/coverage/coverage.go
Updates Run and calculateEntityCoverages to use compiled definitions, builds entity-definition lookup, initializes per-entity maps, and aggregates condition totals into schema coverage.
Condition Coverage Computation
pkg/development/coverage/coverage.go
Extracts asserted permissions (including entity-ID scoping), traverses compiled permission trees to collect/deduplicate leaf condition components (resolving referenced permissions), builds scenario-local indices, and evaluates component coverage producing ConditionCoverageInfo.
Display & Formatting
pkg/cmd/coverage.go, pkg/cmd/flags/coverage.go
Adds coverage-conditions flag binding; reads threshold; extends DisplayCoverageInfo with displayConditionCoverage; sorts scenarios/permissions deterministically, color-codes percentages, and lists uncovered components per permission.
Tests / Validation
pkg/development/coverage/coverage_test.go
Adds "Case 3: Permission Condition Components" and "Case 4: Tuple-to-userset Permission Rewrite Semantics" tests asserting condition coverage percent and exact covered/uncovered component identifiers; renumbers an existing test and adds findEntityCoverage helper.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as coverage CLI
  participant Runner as calculateEntityCoverage
  participant Compiler as schema compiler
  participant Extractor as condition extractor
  participant Indexer as scenario indexer
  participant Evaluator as coverage evaluator

  CLI->>Runner: request coverage display
  Runner->>Compiler: use compiled entity definitions
  Runner->>Extractor: extract leaf condition components per permission
  Extractor->>Indexer: build scenario-local relationship/attribute index
  Indexer->>Evaluator: evaluate each component against asserted targets
  Evaluator->>Runner: return ConditionCoverageInfo
  Runner->>CLI: render sorted/color-coded results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Leaves inspected, each condition found,
Components checked, then neatly bound,
Percentages glow in tidy rows,
A rabbit hops where coverage grows. 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add permission condition coverage details' directly and clearly describes the main change: adding detailed permission condition coverage reporting to the existing coverage command.
Linked Issues check ✅ Passed The PR fully implements the core requirements from issue #837: detailed component-level coverage analysis for permission conditions, quality checks via threshold flags, and comprehensive test coverage for the new functionality.
Out of Scope Changes check ✅ Passed All code changes directly support the stated objectives of adding permission condition coverage: core coverage logic, CLI display, threshold validation, and corresponding tests are all within scope.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/development/coverage/coverage.go (1)

789-805: 💤 Low value

Consider documenting the always-covered/always-uncovered branches.

componentCall always returns covered and componentPermission always returns uncovered. The former is a reasonable simplification (rule logic is opaque to coverage), and the latter is a fallback for recursive same-entity permission references (caught by visitedPermissions upstream). A short comment here would help future readers understand the design intent and avoid mistaking these for bugs.

📝 Proposed comment
 func (data componentCoverageData) isComponentCovered(entityName string, component ConditionComponent, targets []assertionTarget) bool {
 	switch component.Type {
 	case componentRelation:
 		return data.hasRelationship(entityName, component.Name, targets)
 	case componentTupleToUserset:
 		tupleSetRelation, _, _ := strings.Cut(component.Name, ".")
 		return data.hasRelationship(entityName, tupleSetRelation, targets)
 	case componentAttribute:
 		return data.hasAttribute(entityName, component.Name, targets)
 	case componentCall:
+		// Rule bodies are opaque to coverage; the call itself is considered covered.
+		// Any attribute arguments are tracked separately as componentAttribute leaves.
 		return true
 	case componentPermission:
+		// Fallback for recursive same-entity permission references that cannot be
+		// expanded further; conservatively reported as uncovered.
 		return false
 	default:
 		return false
 	}
 }
🤖 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 `@pkg/development/coverage/coverage.go` around lines 789 - 805, Add concise
comments inside the isComponentCovered function explaining why componentCall
always returns true (rule logic is opaque so we treat calls as covered) and why
componentPermission always returns false (this is a deliberate fallback for
recursive/same-entity permission references handled by visitedPermissions
upstream). Locate the isComponentCovered function and add short inline comments
next to the case labels for componentCall and componentPermission mentioning
these design intentions and referencing visitedPermissions as the upstream guard
so future readers do not treat these branches as bugs.
🤖 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 `@pkg/development/coverage/coverage.go`:
- Around line 789-805: Add concise comments inside the isComponentCovered
function explaining why componentCall always returns true (rule logic is opaque
so we treat calls as covered) and why componentPermission always returns false
(this is a deliberate fallback for recursive/same-entity permission references
handled by visitedPermissions upstream). Locate the isComponentCovered function
and add short inline comments next to the case labels for componentCall and
componentPermission mentioning these design intentions and referencing
visitedPermissions as the upstream guard so future readers do not treat these
branches as bugs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f0aa0b4-3e72-4221-9119-47007cfe691b

📥 Commits

Reviewing files that changed from the base of the PR and between ba21b2e and d82f09e.

📒 Files selected for processing (3)
  • pkg/cmd/coverage.go
  • pkg/development/coverage/coverage.go
  • pkg/development/coverage/coverage_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 594a9038-845c-48d9-ad8d-599dcb54e0aa

📥 Commits

Reviewing files that changed from the base of the PR and between 202ccc1 and 305574b.

📒 Files selected for processing (4)
  • pkg/cmd/coverage.go
  • pkg/cmd/flags/coverage.go
  • pkg/development/coverage/coverage.go
  • pkg/development/coverage/coverage_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/development/coverage/coverage_test.go

Comment thread pkg/development/coverage/coverage.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/development/coverage/coverage_test.go (1)

678-685: ⚡ Quick win

Fail fast when entity coverage is missing.

Returning an empty EntityCoverageInfo defers the failure and makes diagnostics less clear. In tests, it’s better to fail at lookup time with a precise message.

Proposed refactor
-func findEntityCoverage(sci SchemaCoverageInfo, entityName string) EntityCoverageInfo {
+func findEntityCoverage(sci SchemaCoverageInfo, entityName string) (EntityCoverageInfo, bool) {
 	for _, entityCoverage := range sci.EntityCoverageInfo {
 		if entityCoverage.EntityName == entityName {
-			return entityCoverage
+			return entityCoverage, true
 		}
 	}
-	return EntityCoverageInfo{}
+	return EntityCoverageInfo{}, false
 }

And at call sites:

documentCoverage, ok := findEntityCoverage(sci, "document")
Expect(ok).Should(BeTrue(), "expected coverage info for entity 'document'")
🤖 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 `@pkg/development/coverage/coverage_test.go` around lines 678 - 685, Change
findEntityCoverage to fail fast by returning an explicit presence flag instead
of silently returning an empty EntityCoverageInfo: update the signature of
findEntityCoverage(sci SchemaCoverageInfo, entityName string) to return
(EntityCoverageInfo, bool), iterate as before and return (entityCoverage, true)
when found and (EntityCoverageInfo{}, false) when not; then update all call
sites to check the bool (e.g., documentCoverage, ok := findEntityCoverage(...);
Expect(ok).Should(BeTrue(), "expected coverage info for entity 'document'")) so
tests fail immediately with a clear assertion when coverage is missing.
🤖 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 `@pkg/development/coverage/coverage_test.go`:
- Around line 678-685: Change findEntityCoverage to fail fast by returning an
explicit presence flag instead of silently returning an empty
EntityCoverageInfo: update the signature of findEntityCoverage(sci
SchemaCoverageInfo, entityName string) to return (EntityCoverageInfo, bool),
iterate as before and return (entityCoverage, true) when found and
(EntityCoverageInfo{}, false) when not; then update all call sites to check the
bool (e.g., documentCoverage, ok := findEntityCoverage(...);
Expect(ok).Should(BeTrue(), "expected coverage info for entity 'document'")) so
tests fail immediately with a clear assertion when coverage is missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d120abdd-a15f-4b67-b4f2-98b8262cb067

📥 Commits

Reviewing files that changed from the base of the PR and between 4238198 and cef8fb6.

📒 Files selected for processing (2)
  • pkg/development/coverage/coverage.go
  • pkg/development/coverage/coverage_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/development/coverage/coverage.go

@hieu-lee
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request May 11, 2026
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.

Enhancing the 'Coverage' Command for Detailed Action/Permission Conditions

1 participant