Skip to content

feat(authz): Namespaced policy in decisioning#3226

Open
elizabethhealy wants to merge 16 commits intomainfrom
dspx-2753-namespaced-policy-decisioning
Open

feat(authz): Namespaced policy in decisioning#3226
elizabethhealy wants to merge 16 commits intomainfrom
dspx-2753-namespaced-policy-decisioning

Conversation

@elizabethhealy
Copy link
Copy Markdown
Member

@elizabethhealy elizabethhealy commented Mar 31, 2026

Proposed Changes

This pull request introduces a namespaced policy evaluation model to the Policy Decision Point (PDP). The primary goal is to resolve ambiguities in access decisioning where action names might overlap across different namespaces. By threading a new NamespacedPolicy configuration through the PDP, the system can now enforce strict namespace equality for actions and subject mappings, ensuring a fail-closed and deterministic authorization process suitable for staged migration.

Highlights

  • Namespaced Policy Enforcement: Introduced a NamespacedPolicy feature flag to enforce strict namespace ownership in access decisioning, preventing cross-namespace action matches.
  • Deterministic Action Resolution: Implemented namespace-aware action matching logic that resolves actions within specific evaluation contexts, with explicit identity precedence (ID > Name+Namespace > Name).
  • Subject Mapping Filtering: Added logic to filter out unnamespaced subject mappings when the NamespacedPolicy mode is enabled.
  • Conflict Resolution: Added defensive logic to EvaluateSubjectMappingsWithActions to handle and log action name collisions deterministically.

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

Summary by CodeRabbit

  • New Features
    • Optional strict, namespace-aware authorization mode (NamespacedPolicy) that evaluates actions and subject mappings per-namespace; request action identity prefers explicit IDs and enforces namespace compatibility when enabled.
  • Bug Fixes
    • Deterministic handling of conflicting actions with the same name to avoid silent overwrites.
  • Documentation
    • Added architecture decision record and deployment template for namespaced decisioning.
  • Tests
    • Extensive unit and BDD coverage added for namespaced decisioning behaviors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eee66f20-cd2d-4a09-9a8f-54d574ed4e8e

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef6a6e and eb7dd89.

📒 Files selected for processing (2)
  • service/internal/access/v2/pdp.go
  • service/internal/access/v2/pdp_test.go

📝 Walkthrough

Walkthrough

Added a namespaced-policy ADR and implemented a feature-flag-controlled namespaced subject-mapping decisioning flow: config flag, PDP plumbing, namespaced indexing/filtering, namespace-aware action matching and entitlements merging, tests, BDD scenarios, and auxiliary deduplication/logging for conflicting actions.

Changes

Cohort / File(s) Summary
Architecture ADR
adr/decisions/2026-03-30-namespaced-subject-mappings-decisioning.md
New ADR describing namespaced policy decisioning semantics, identity precedence, fail-closed rules, rollout, and implementation notes.
Service config & wiring
service/authorization/v2/config.go, service/authorization/v2/authorization.go
Added NamespacedPolicy config flag and threaded it into PDP construction calls across authorization handlers. LogValue() updated to include the flag.
PDP core & indexing
service/internal/access/v2/pdp.go, service/internal/access/v2/just_in_time_pdp.go
Added namespacedPolicy flag on PDP and constructors; subject-mapping filtering at construction, namespaced and legacy FQN indexing for registered-resource values, and merging of direct entitlements by FQN with namespace assignment logic.
Evaluation logic
service/internal/access/v2/evaluate.go, service/internal/access/v2/helpers_test.go
Refactored resource evaluation to accept namespacedPolicy, added isRequestedActionMatch with namespace-aware identity precedence, introduced RuleScoped variants for ALL_OF/ANY_OF/HIERARCHY, and adjusted helper test call signatures.
PDP & evaluation tests
service/internal/access/v2/evaluate_test.go, service/internal/access/v2/pdp_test.go
Expanded and added tests for namespaced policy behavior (strict vs legacy), updated PDP construction in tests to pass the flag, and added many strict-mode-specific assertions.
Subject mapping dedupe & tests
service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions.go, service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions_test.go
Added deterministic handling and warning logging for conflicting action-name deduplication via actionsConflict and preferAction; added a test validating deterministic retention.
BDD test scenarios & steps
tests-bdd/features/namespaced-decisioning.feature, tests-bdd/cukes/steps_subjectmappings.go, tests-bdd/cukes/resources/platform.namespaced_policy.template
Added BDD feature testing strict namespaced decisioning; extended step helpers to support namespace fields; added a platform template that enables authorization.namespaced_policy.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant AuthService as Authorization Service (handler)
participant JIT_PDP as JustInTimePDP
participant PDP
participant Registry as RegisteredResourceIndex
participant SubjectMapping as SubjectMappingsStore

Client->>AuthService: Decision request (action name/id, resource attr)
AuthService->>JIT_PDP: NewJustInTimePDP(..., allowDirectEntitlements, namespacedPolicy)
AuthService->>JIT_PDP: GetDecision(request)
JIT_PDP->>PDP: Evaluate(resource, request, namespacedPolicy)
PDP->>Registry: Lookup registered-resource values (namespaced & legacy keys)
PDP->>SubjectMapping: Filter subject mappings (skip unnamespaced if strict)
PDP->>PDP: isRequestedActionMatch(action, requiredNamespace)
PDP-->>JIT_PDP: Decision (PERMIT/DENY)
JIT_PDP-->>AuthService: Statusified Decision
AuthService-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

comp:policy, docs

Suggested reviewers

  • c-r33d
  • alkalescent
  • jakedoublev

Poem

🐰
I hopped through code with tiny paws,
Namespaces stitched into rules and laws.
Actions matched with care and grace,
Policies now know their proper place.
Hooray — a tidy, bounded space!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: introducing namespaced policy decisioning in the authorization service, which is the primary objective across all modified files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dspx-2753-namespaced-policy-decisioning

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a namespaced policy evaluation model to the Policy Decision Point (PDP). The primary goal is to resolve ambiguities in access decisioning where action names might overlap across different namespaces. By threading a new NamespacedPolicy configuration through the PDP, the system can now enforce strict namespace equality for actions and subject mappings, ensuring a fail-closed and deterministic authorization process suitable for staged migration.

Highlights

  • Namespaced Policy Enforcement: Introduced a NamespacedPolicy feature flag to enforce strict namespace ownership in access decisioning, preventing cross-namespace action matches.
  • Deterministic Action Resolution: Implemented namespace-aware action matching logic that resolves actions within specific evaluation contexts, with explicit identity precedence (ID > Name+Namespace > Name).
  • Subject Mapping Filtering: Added logic to filter out unnamespaced subject mappings when the NamespacedPolicy mode is enabled.
  • Conflict Resolution: Added defensive logic to EvaluateSubjectMappingsWithActions to handle and log action name collisions deterministically.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


Namespaces strict and clear, Policy doubts disappear. Actions matched with care, Security beyond compare.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 201.554433ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 108.047735ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 395.392321ms
Throughput 252.91 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.837164037s
Average Latency 407.036478ms
Throughput 122.44 requests/second

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements namespaced subject mapping decisioning in the PDP, as outlined in the newly added ADR. It introduces a NamespacedPolicy feature flag to control the enforcement of strict namespace ownership during access evaluation. The core logic includes a centralized action-matching helper and updated evaluation paths for attribute rules. Feedback focuses on the transition to uniform case-insensitive action name matching and suggests more robust logging or error handling when resolving action name collisions during subject mapping evaluation.

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 196.102058ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 97.046499ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 386.928477ms
Throughput 258.45 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.111521964s
Average Latency 399.3757ms
Throughput 124.65 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

X-Test Failure Report

opentdfplatformB2MWHF.dockerbuild

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 195.114094ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.941112ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 399.245747ms
Throughput 250.47 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.721397139s
Average Latency 405.954345ms
Throughput 122.79 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 196.070008ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.915101ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 393.140224ms
Throughput 254.36 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.796006383s
Average Latency 416.225723ms
Throughput 119.63 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 206.206272ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.347417ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 380.864314ms
Throughput 262.56 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.471782036s
Average Latency 412.725819ms
Throughput 120.56 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 203.173319ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 102.323047ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 383.684246ms
Throughput 260.63 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.367541483s
Average Latency 411.964684ms
Throughput 120.87 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
service/internal/access/v2/pdp.go (2)

297-326: ⚠️ Potential issue | 🟠 Major

Keep registered-resource action candidates ID/namespace-aware here too.

This loop still filters on GetName() and deduplicates by name, but the common matcher now supports Action.Id and namespace-aware comparisons. Requests that identify the action by ID, or registered-resource values that carry same-name actions in multiple namespaces, can be denied based on iteration order before getResourceDecision ever runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/internal/access/v2/pdp.go` around lines 297 - 326, The loop that
builds entitledFQNsToActions deduplicates and matches actions only by name
(using aav.GetAction().GetName() and slices.ContainsFunc comparing GetName()),
which ignores Action.Id and namespaces and can produce wrong ordering/denials;
update the matching and deduplication to be ID+namespace-aware: when reading
aav.GetAction() (aavAction) and comparing to the Decision Request action
(action), compare both Id and namespace (or use a composite key like
action.GetId()+":"+action.GetNamespace()) instead of GetName(); likewise replace
the slices.ContainsFunc check with a comparison on Id+namespace (or maintain
actionsList as a map keyed by that composite key and convert back to
[]*policy.Action) so entitledFQNsToActions stores unique actions by ID+namespace
before calling getResourceDecision (preserve use of entitledFQNsToActions,
getResourceDecision, entityRegisteredResourceValue.GetActionAttributeValues,
aav.GetAction, action, and p.namespacedPolicy in your changes).

102-157: ⚠️ Potential issue | 🟠 Major

Don't mutate caller-owned Value.SubjectMappings while building the PDP index.

The builder stores references to policy objects returned from the store/cache and then mutates their SubjectMappings fields in place. When using EntitlementPolicyCache, the cache returns the same slice and object references across builds; mutations persist in the cached data. A prior non-strict build can leave legacy mappings attached to cached values, and a later strict build will evaluate stale mappings that should have been skipped. Clone the value or clear SubjectMappings before populating the lookup to ensure each PDP build operates on independent data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/internal/access/v2/pdp.go` around lines 102 - 157, The code mutates
caller-owned policy.Value objects (specifically Value.SubjectMappings) while
building the PDP index (see allEntitleableAttributesByValueFQN, mappedValue, and
the block that appends to SubjectMappings), which can leak into cached data; fix
by cloning the attribute value (or creating a fresh value instance) before
assigning or clearing SubjectMappings and before appending mappings so you never
modify objects returned from the store/cache; update the branch that handles
existing entries and the branch that calls getDefinition to use the cloned value
(or reset SubjectMappings) and attach the clone to the new
attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue entry to ensure each
PDP build has independent SubjectMappings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@adr/decisions/2026-03-30-namespaced-subject-mappings-decisioning.md`:
- Around line 30-36: The ADR currently contradicts itself about whether the
request action is unnamespaced: update the decision text so there is a single
normative request-action contract and remove the conflicting statement; choose
one model (either keep "request action remains unnamespaced" and restrict the
precedence list to contextual/derived matching, or adopt the explicit request
shapes `action.id`, `action.name + action.namespace`, `action.name` as the
normative request contract) and rewrite the paragraph around "Request-action
identity precedence" to match that single choice, ensuring all references to
action.id, action.name, and action.namespace in the document are consistent with
the chosen contract.

In `@service/internal/access/v2/evaluate.go`:
- Around line 82-91: The loop that computes requiredNamespaceID for each
regResValue AAV uses accessibleAttributeValues (a filtered map) and leaves
requiredNamespaceID empty when an AAV isn’t present, allowing
isRequestedActionMatch(...) to silently skip instead of denying in strict mode;
fix by resolving the namespace from the unfiltered value→attribute lookup used
elsewhere (the full value->attribute map) when computing requiredNamespaceID for
regResValue.GetActionAttributeValues(), and if that lookup still fails while the
AAV is otherwise a candidate for the requested action and
namespacedPolicy/strict mode is enabled, return a deny (fail-closed) rather than
continuing; update the logic around requiredNamespaceID,
isRequestedActionMatch(action, requiredNamespaceID, aav.GetAction(),
namespacedPolicy), and the strict-mode path accordingly and add a mixed-AAV
strict-mode regression test that ensures a resource with multiple AAVs is denied
when any AAV’s namespace cannot be resolved.

In `@service/internal/access/v2/helpers_test.go`:
- Around line 396-399: The fixture `valueRestricted` is inconsistent because its
SubjectMapping uses `exampleSecretFQN` instead of the intended
`exampleRestrictedFQN`; update the call to `createSimpleSubjectMapping` inside
the `valueRestricted` declaration so the subject FQN argument is
`exampleRestrictedFQN` (not `exampleSecretFQN`) to correctly represent the
restricted fixture and ensure `populateHigherValuesIfHierarchy` tests exercise
the intended hierarchy.

In `@service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions.go`:
- Around line 147-159: The current selection only checks presence of a Namespace
but doesn't prefer the one with an ID (Namespace.Id) when both have namespaces;
update the logic around existingNS/candidateNS so that if both existingHasNS and
candidateHasNS are true you choose the action whose Namespace.GetId() is
non-empty (prefer candidate if it has an ID, otherwise prefer existing if it has
an ID, otherwise fall back to existing); adjust the branch that returns
existing/candidate to inspect Namespace.GetId() before returning to ensure the
action with an actual namespace ID is chosen.

---

Outside diff comments:
In `@service/internal/access/v2/pdp.go`:
- Around line 297-326: The loop that builds entitledFQNsToActions deduplicates
and matches actions only by name (using aav.GetAction().GetName() and
slices.ContainsFunc comparing GetName()), which ignores Action.Id and namespaces
and can produce wrong ordering/denials; update the matching and deduplication to
be ID+namespace-aware: when reading aav.GetAction() (aavAction) and comparing to
the Decision Request action (action), compare both Id and namespace (or use a
composite key like action.GetId()+":"+action.GetNamespace()) instead of
GetName(); likewise replace the slices.ContainsFunc check with a comparison on
Id+namespace (or maintain actionsList as a map keyed by that composite key and
convert back to []*policy.Action) so entitledFQNsToActions stores unique actions
by ID+namespace before calling getResourceDecision (preserve use of
entitledFQNsToActions, getResourceDecision,
entityRegisteredResourceValue.GetActionAttributeValues, aav.GetAction, action,
and p.namespacedPolicy in your changes).
- Around line 102-157: The code mutates caller-owned policy.Value objects
(specifically Value.SubjectMappings) while building the PDP index (see
allEntitleableAttributesByValueFQN, mappedValue, and the block that appends to
SubjectMappings), which can leak into cached data; fix by cloning the attribute
value (or creating a fresh value instance) before assigning or clearing
SubjectMappings and before appending mappings so you never modify objects
returned from the store/cache; update the branch that handles existing entries
and the branch that calls getDefinition to use the cloned value (or reset
SubjectMappings) and attach the clone to the new
attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue entry to ensure each
PDP build has independent SubjectMappings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 40e86ab6-c264-4bb2-9f3a-45f77facf7bb

📥 Commits

Reviewing files that changed from the base of the PR and between 99a8147 and 26740fd.

📒 Files selected for processing (15)
  • adr/decisions/2026-03-30-namespaced-subject-mappings-decisioning.md
  • docs/namespaced-subject-mappings-decisioning-plan.md
  • service/authorization/v2/authorization.go
  • service/authorization/v2/config.go
  • service/internal/access/v2/evaluate.go
  • service/internal/access/v2/evaluate_test.go
  • service/internal/access/v2/helpers_test.go
  • service/internal/access/v2/just_in_time_pdp.go
  • service/internal/access/v2/pdp.go
  • service/internal/access/v2/pdp_test.go
  • service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions.go
  • service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions_test.go
  • tests-bdd/cukes/resources/platform.namespaced_policy.template
  • tests-bdd/cukes/steps_subjectmappings.go
  • tests-bdd/features/namespaced-decisioning.feature

@github-actions
Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 192.176089ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 97.047772ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 425.711707ms
Throughput 234.90 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.165398514s
Average Latency 419.919515ms
Throughput 118.58 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 194.763155ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.847303ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 386.503988ms
Throughput 258.73 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.769552371s
Average Latency 396.022769ms
Throughput 125.72 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 190.726445ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 98.427274ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 387.842308ms
Throughput 257.84 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.291971027s
Average Latency 400.747326ms
Throughput 124.09 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 191.899224ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 95.333591ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 385.002013ms
Throughput 259.74 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.09543811s
Average Latency 399.328579ms
Throughput 124.70 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 198.576507ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.65821ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 385.932604ms
Throughput 259.11 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.227174417s
Average Latency 400.798061ms
Throughput 124.29 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 196.574734ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 106.813424ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 385.594587ms
Throughput 259.34 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.323416698s
Average Latency 401.869539ms
Throughput 124.00 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 195.842298ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.48287ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 384.236258ms
Throughput 260.26 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.069128469s
Average Latency 408.554842ms
Throughput 121.75 requests/second

@elizabethhealy
Copy link
Copy Markdown
Member Author

@coderabbitai review

@elizabethhealy
Copy link
Copy Markdown
Member Author

/gemini summary

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

This pull request introduces a namespaced policy evaluation model to the Policy Decision Point (PDP). The primary goal is to resolve ambiguities in access decisioning where action names might overlap across different namespaces. By threading a new NamespacedPolicy configuration through the PDP, the system can now enforce strict namespace equality for actions and subject mappings, ensuring a fail-closed and deterministic authorization process suitable for staged migration.

Highlights

  • Namespaced Policy Enforcement: Introduced a NamespacedPolicy feature flag to enforce strict namespace ownership in access decisioning, preventing cross-namespace action matches.
  • Deterministic Action Resolution: Implemented namespace-aware action matching logic that resolves actions within specific evaluation contexts, with explicit identity precedence (ID > Name+Namespace > Name).
  • Subject Mapping Filtering: Added logic to filter out unnamespaced subject mappings when the NamespacedPolicy mode is enabled.
  • Conflict Resolution: Added defensive logic to EvaluateSubjectMappingsWithActions to handle and log action name collisions deterministically.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Activity
  • Initial PR creation and draft status detected.
  • Automated review and benchmark reports generated.
  • Reviewer feedback provided regarding case-sensitivity and conflict resolution logic.
  • Author clarified design decisions regarding action preference logic.
  • Additional regression tests and BDD features added to validate strict mode behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

@elizabethhealy elizabethhealy marked this pull request as ready for review April 1, 2026 17:53
@elizabethhealy elizabethhealy requested a review from a team as a code owner April 1, 2026 17:53
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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@adr/decisions/2026-03-30-namespaced-subject-mappings-decisioning.md`:
- Line 86: Add debug logging to action-match and rule-evaluation paths: inside
isRequestedActionMatch() and wherever rules are evaluated, emit a debug log that
includes the requested action, the required namespace, the candidate namespace
being tested, and the current rule/value context (e.g., rule id, rule
conditions, evaluated value). Ensure these logs run at debug level and are added
both for successful matches and for mismatches/early exits so decision traces
show why a candidate was accepted or rejected.

In `@service/internal/access/v2/evaluate.go`:
- Around line 500-515: The namespace matching on requestedAction.Namespace is
being applied unconditionally and can deny requests even when namespacedPolicy
is false; to fix, ensure the namespace filter is only applied when
namespacedPolicy is true by moving the entire requestedAction.GetNamespace() /
entitledAction.GetNamespace() comparison block behind the namespacedPolicy guard
or wrapping that block in if namespacedPolicy { ... }; reference the
requestedAction.GetNamespace(), entitledAction.GetNamespace(), and
namespacedPolicy symbols to locate the code to change.

In `@service/internal/access/v2/pdp_test.go`:
- Around line 2731-2734: The test case name is misleading: the case uses action
Id idNamespaceBRead which is not present in the subject mapping (so the denial
is due to a non-existent action id, not a namespace mismatch). Update the test
entry (the "name" field in the table where action: &policy.Action{Id:
idNamespaceBRead, Name: actions.ActionNameRead}) to a clearer label such as
"non-existent action id denies" (or similar) so the test name matches the actual
cause of denial.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e47ec27-d04c-444b-89ce-3d4db19b9ed0

📥 Commits

Reviewing files that changed from the base of the PR and between 26740fd and e03b0e5.

📒 Files selected for processing (7)
  • adr/decisions/2026-03-30-namespaced-subject-mappings-decisioning.md
  • service/internal/access/v2/evaluate.go
  • service/internal/access/v2/evaluate_test.go
  • service/internal/access/v2/helpers_test.go
  • service/internal/access/v2/pdp.go
  • service/internal/access/v2/pdp_test.go
  • tests-bdd/features/namespaced-decisioning.feature

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 185.518519ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 112.033782ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 388.890733ms
Throughput 257.14 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.323767225s
Average Latency 399.57637ms
Throughput 124.00 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026


// When authorization service can consume cached policy, switch to the other PDP (process based on policy passed in)
pdp, err := access.NewJustInTimePDP(ctx, as.logger, as.sdk, as.cache, as.config.AllowDirectEntitlements)
pdp, err := access.NewJustInTimePDP(ctx, as.logger, as.sdk, as.cache, as.config.AllowDirectEntitlements, as.config.NamespacedPolicy)
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: could we rename this to a boolean like shouldEnforceNamespacedPolicy or something a tiny bit more readable?

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
service/internal/access/v2/pdp.go (2)

103-116: ⚠️ Potential issue | 🟠 Major

Avoid mutating caller-owned Value.SubjectMappings.

allEntitleableAttributesByValueFQN stores the original attr.GetValues() pointers, and Line 146 appends straight into those shared slices. If the same attribute objects are reused for another PDP build — plausible on the cached NewJustInTimePDP path — a legacy build can leave unnamespaced mappings attached and a later strict build will still evaluate them even though Lines 130-140 skip them. Repeated constructors also accumulate duplicates. Please clone/reset the policy.Values before wiring mappings and add a regression that builds legacy then strict PDPs from the same inputs.

Suggested fix
+import "google.golang.org/protobuf/proto"
...
 		for _, value := range attr.GetValues() {
+			valueCopy := proto.Clone(value).(*policy.Value)
+			valueCopy.SubjectMappings = nil
 			mapped := &attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue{
-				Value:     value,
+				Value:     valueCopy,
 				Attribute: attr,
 			}
-			allEntitleableAttributesByValueFQN[value.GetFqn()] = mapped
+			allEntitleableAttributesByValueFQN[valueCopy.GetFqn()] = mapped
 		}
...
-		mappedValue.SubjectMappings = []*policy.SubjectMapping{sm}
+		mappedValue = proto.Clone(mappedValue).(*policy.Value)
+		mappedValue.SubjectMappings = []*policy.SubjectMapping{sm}

Also applies to: 130-146, 154-159

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/internal/access/v2/pdp.go` around lines 103 - 116, The code mutates
caller-owned Value objects (attr.GetValues()) by reusing their pointers in
allEntitleableAttributesByValueFQN and later appending to Value.SubjectMappings;
to fix, create deep copies of each policy.Value (or at minimum copy the Value
struct and reset/clone its SubjectMappings slice) before storing/inspecting them
in allEntitleableAttributesByValueFQN and before any code that appends mappings
(refer to allEntitleableAttributesByValueFQN, attr.GetValues(), and
Value.SubjectMappings), so the original attribute definitions are never
modified; also add a regression test that constructs a legacy PDP then a strict
PDP from the same input (exercise NewJustInTimePDP reuse path) to ensure
mappings are not accumulated across builds.

314-345: ⚠️ Potential issue | 🟠 Major

Preserve ID/namespace action variants for registered-resource subjects.

By the time Line 344 calls getResourceDecision, this map has already filtered and deduped entityRegisteredResourceValue.GetActionAttributeValues() on Action.Name alone. That drops same-name actions that differ only by namespace or ID, so strict mode — or any request with an explicit namespace/ID — can incorrectly deny when the kept variant is not the matching one. Use isRequestedActionMatch(..., false) for the prefilter and keep distinct ID -> Name+Namespace -> Name variants until evaluation. Please add a regression with two same-name RR actions on the same value FQN.

Suggested fix
 		aavAction := aav.GetAction()
-		if action.GetName() != aavAction.GetName() {
+		if !isRequestedActionMatch(ctx, l, action, "", aavAction, false) {
 			l.DebugContext(ctx, "skipping action not matching Decision Request action", slog.String("action_name", aavAction.GetName()))
 			continue
 		}
...
-		if !slices.ContainsFunc(actionsList, func(a *policy.Action) bool {
-			return a.GetName() == aavAction.GetName()
-		}) {
-			actionsList = append(actionsList, aavAction)
-		}
+		// Preserve distinct ID / namespace variants so downstream evaluation can
+		// apply the full action-identity precedence.
+		actionsList = append(actionsList, aavAction)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/internal/access/v2/pdp.go` around lines 314 - 345, The current
prefilter over entityRegisteredResourceValue.GetActionAttributeValues collapses
actions by Action.Name into entitledFQNsToActions, losing distinct ID/namespace
variants; change the prefilter to call isRequestedActionMatch(..., false) when
deciding which ActionAttributeValues to keep, and stop deduping solely by name —
preserve distinct variants (e.g., keep actions keyed by their ID or
Name+Namespace as well as Name) so getResourceDecision can pick the exact match;
update the construction of entitledFQNsToActions to store multiple variants per
attrValFQN (not just unique names) and ensure getResourceDecision still consumes
the new structure, then add a regression test that creates two
registered-resource actions with the same Name but different ID/namespace on the
same attribute FQN to verify strict/namespace-specific requests choose the
correct variant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@service/internal/access/v2/pdp.go`:
- Around line 103-116: The code mutates caller-owned Value objects
(attr.GetValues()) by reusing their pointers in
allEntitleableAttributesByValueFQN and later appending to Value.SubjectMappings;
to fix, create deep copies of each policy.Value (or at minimum copy the Value
struct and reset/clone its SubjectMappings slice) before storing/inspecting them
in allEntitleableAttributesByValueFQN and before any code that appends mappings
(refer to allEntitleableAttributesByValueFQN, attr.GetValues(), and
Value.SubjectMappings), so the original attribute definitions are never
modified; also add a regression test that constructs a legacy PDP then a strict
PDP from the same input (exercise NewJustInTimePDP reuse path) to ensure
mappings are not accumulated across builds.
- Around line 314-345: The current prefilter over
entityRegisteredResourceValue.GetActionAttributeValues collapses actions by
Action.Name into entitledFQNsToActions, losing distinct ID/namespace variants;
change the prefilter to call isRequestedActionMatch(..., false) when deciding
which ActionAttributeValues to keep, and stop deduping solely by name — preserve
distinct variants (e.g., keep actions keyed by their ID or Name+Namespace as
well as Name) so getResourceDecision can pick the exact match; update the
construction of entitledFQNsToActions to store multiple variants per attrValFQN
(not just unique names) and ensure getResourceDecision still consumes the new
structure, then add a regression test that creates two registered-resource
actions with the same Name but different ID/namespace on the same attribute FQN
to verify strict/namespace-specific requests choose the correct variant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1045d389-d60a-4d33-94dc-8c2d747a43ac

📥 Commits

Reviewing files that changed from the base of the PR and between e03b0e5 and 7ef6a6e.

📒 Files selected for processing (4)
  • service/internal/access/v2/evaluate.go
  • service/internal/access/v2/evaluate_test.go
  • service/internal/access/v2/pdp.go
  • service/internal/access/v2/pdp_test.go

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 195.41972ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 95.984601ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 381.328778ms
Throughput 262.24 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 39.852719192s
Average Latency 396.516987ms
Throughput 125.46 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

AllowDirectEntitlements bool `mapstructure:"allow_direct_entitlements" json:"allow_direct_entitlements" default:"false"`

// enforce strict namespaced policy evaluation behavior in access decisioning
NamespacedPolicy bool `mapstructure:"namespaced_policy" json:"namespaced_policy" default:"false"`
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.

this is currently a seperate feature flag under service.authorization.namespaced_policy, should it be seperate? or should we figure out a way to rely on the service.policy.namespaced_policy flag?

maybe namespaced_policy and namespaced_decisioning are seperate features?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants