Skip to content

feat(sdk): Introduce phase-specific policy sub-interfaces and migrate to v2alpha (SDK v0.5.0)#1358

Open
Thushani-Jayasekera wants to merge 9 commits intowso2:mainfrom
Thushani-Jayasekera:sdk-release
Open

feat(sdk): Introduce phase-specific policy sub-interfaces and migrate to v2alpha (SDK v0.5.0)#1358
Thushani-Jayasekera wants to merge 9 commits intowso2:mainfrom
Thushani-Jayasekera:sdk-release

Conversation

@Thushani-Jayasekera
Copy link
Contributor

@Thushani-Jayasekera Thushani-Jayasekera commented Mar 16, 2026

Issue: #1384

Summary

  This PR delivers a significant SDK redesign that introduces a clean, capability-based policy model via a new policyv2alpha package,
  alongside backward-compatible fixes and improvements to the policy engine.

  The key motivation is that the previous v1alpha Policy interface was monolithic — every policy had to implement Mode(), OnRequest(),
   and OnResponse(), even if it only needed one phase. The new v2alpha model lets policies declare capabilities by implementing only
  the phase-specific sub-interfaces they actually need. The kernel discovers these via type assertions at chain-build time with zero
  per-request overhead.

  ---
  What Changed

  1. New sdk/gateway/policy/v2alpha package

  The new package introduces six phase-specific sub-interfaces. Policies implement only the ones they need:

  ┌─────────────────────────┬─────────────────────────────────────────┬──────────────────────────────────────┐
  │        Interface        │                 Method                  │           When it's called           │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ RequestHeaderPolicy     │ OnRequestHeaders(ctx, params)           │ Before the request body is read      │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ ResponseHeaderPolicy    │ OnResponseHeaders(ctx, params)          │ Before the response body is read     │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ RequestPolicy           │ OnRequestBody(ctx, params)              │ After full request body is buffered  │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ ResponsePolicy          │ OnResponseBody(ctx, params)             │ After full response body is buffered │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ StreamingRequestPolicy  │ OnRequestBodyChunk(ctx, chunk, params)  │ Per request chunk (streaming)        │
  ├─────────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────┤
  │ StreamingResponsePolicy │ OnResponseBodyChunk(ctx, chunk, params) │ Per response chunk (streaming)       │
  └─────────────────────────┴─────────────────────────────────────────┴──────────────────────────────────────┘

  The marker Policy interface is empty — capabilities are declared purely through sub-interface implementation.

  2. New action types for header-phase handling

  action.go gains two new sealed action interfaces with their concrete types:

  // Request header phase
  type RequestHeaderAction interface { isRequestHeaderAction() }

  type UpstreamRequestHeaderModifications struct {
      Set    map[string]string
      Remove []string
      Append map[string][]string
      // ...analytics fields
  }

  // Response header phase
  type ResponseHeaderAction interface { isResponseHeaderAction() }

  type DownstreamResponseHeaderModifications struct {
      Set    map[string]string
      Remove []string
      Append map[string][]string
      // ...analytics fields
  }

  ImmediateResponse implements all four action interfaces, so it can be returned from any phase.

  3. New context types

  Four new context types are added for the phase-specific methods:

  - RequestHeaderContext — headers + path/method/authority/scheme/vhost; no body yet
  - ResponseHeaderContext — request info + response headers/status; no body yet
  - RequestStreamContext — same fields as RequestHeaderContext, for streaming chunks
  - ResponseStreamContext — same fields as ResponseHeaderContext, for streaming chunks

  4. Renamed and restructured action types

  ┌───────────────────────────────┬─────────────────────────────────┬───────────────────────────────────┐
  │           Old name            │            New name             │               Notes               │
  ├───────────────────────────────┼─────────────────────────────────┼───────────────────────────────────┤
  │ UpstreamResponseModifications │ DownstreamResponseModifications │ Alias kept for backward compat    │
  ├───────────────────────────────┼─────────────────────────────────┼───────────────────────────────────┤
  │ DropHeadersFromAnalytics      │ AnalyticsHeaderFilter           │ Deprecated field kept as fallback │
  ├───────────────────────────────┼─────────────────────────────────┼───────────────────────────────────┤
  │ AddQueryParameters            │ QueryParametersToAdd            │ Deprecated field kept as fallback │
  ├───────────────────────────────┼─────────────────────────────────┼───────────────────────────────────┤
  │ RemoveQueryParameters         │ QueryParametersToRemove         │ Deprecated field kept as fallback │
  └───────────────────────────────┴─────────────────────────────────┴───────────────────────────────────┘

  UpstreamRequestModifications gains a new Header *UpstreamRequestHeaderModifications field as the preferred way to set request
  headers (instead of flat SetHeaders/RemoveHeaders/AppendHeaders).

  5. Policy engine fixes

  - Response short-circuit: ImmediateResponse returned during the response phase now correctly terminates the chain. Previously this
  was silently ignored.
  - Concurrency safety: Policy params are deep-copied per request using JSON marshal/unmarshal, preventing mutations in one request
  from bleeding into concurrent requests sharing the same PolicySpec.
  - Status code modification: Returning DownstreamResponseModifications.StatusCode now correctly rewrites the :status pseudo-header.
  - QueryParametersToAdd/QueryParametersToRemove are now wired through the translator (they were previously missing).

  6. PII masking default JSONPath update

  The default JSONPath for pii-masking-regex changes from $.messages to $.messages[-1].content, targeting only the latest message's
  content instead of the entire messages array.

  ---
  How to Write a Policy (v2alpha)

  New policies should use policyv2alpha. You only implement the sub-interfaces for the phases you need.

  Example: Header-only policy (auth, set-headers)

  package mypolicy

  import (
      v1 "github.com/wso2/api-platform/sdk/gateway/policy/v1alpha"
      v2 "github.com/wso2/api-platform/sdk/gateway/policy/v2alpha"
  )

  type MyPolicy struct{}

  // GetPolicy is the required factory function.
  func GetPolicy(metadata v1.PolicyMetadata, params map[string]interface{}) (v2.Policy, error) {
      return &MyPolicy{}, nil
  }

  // Implement RequestHeaderPolicy to run at request-header phase (no body buffering needed).
  func (p *MyPolicy) OnRequestHeaders(ctx *v1.RequestHeaderContext, params map[string]interface{}) v1.RequestHeaderAction {
      // Inspect ctx.Headers, ctx.Path, ctx.Method, etc.
      // Return ImmediateResponse to reject:
      // return v1.ImmediateResponse{StatusCode: 401, Body: []byte(`{"error":"unauthorized"}`)}
      return v1.UpstreamRequestHeaderModifications{
          Set: map[string]string{"x-my-header": "value"},
      }
  }

  No Mode() method needed. The kernel infers phases from which interfaces MyPolicy implements.

  Example: Body-inspection policy (guardrail, body rewrite)

  type MyGuardrail struct {
      maxWords int
  }

  func GetPolicy(metadata v1.PolicyMetadata, params map[string]interface{}) (v2.Policy, error) {
      // Parse config once here, store in struct.
      return &MyGuardrail{maxWords: 100}, nil
  }

  // Implement RequestPolicy to buffer and inspect the full request body.
  func (p *MyGuardrail) OnRequestBody(ctx *v1.RequestContext, params map[string]interface{}) v1.RequestAction {
      // ctx.Body.Content is the full buffered body.
      if tooLong(ctx.Body.Content, p.maxWords) {
          return &v1.ImmediateResponse{
              StatusCode: 422,
              Headers:    map[string]string{"content-type": "application/json"},
              Body:       []byte(`{"error":"too many words"}`),
          }
      }
      return &v1.UpstreamRequestModifications{}
  }

  // Implement ResponsePolicy to also inspect the response body.
  func (p *MyGuardrail) OnResponseBody(ctx *v1.ResponseContext, params map[string]interface{}) v1.ResponseAction {
      return &v1.DownstreamResponseModifications{}
  }

  Example: Streaming policy

  type MyStreamingPolicy struct{}

  // Implement StreamingResponsePolicy (which embeds ResponsePolicy as fallback).
  func (p *MyStreamingPolicy) OnResponseBody(ctx *v1.ResponseContext, params map[string]interface{}) v1.ResponseAction {
      // Buffered fallback — called when streaming isn't possible.
      return &v1.DownstreamResponseModifications{}
  }

  func (p *MyStreamingPolicy) OnResponseBodyChunk(ctx *v1.ResponseStreamContext, chunk *v1.StreamBody, params map[string]interface{})
  v1.ResponseChunkAction {
      // Called per chunk. Modify chunk.Body or pass through.
      return v1.ResponseChunkAction{Body: chunk.Body}
  }

  func (p *MyStreamingPolicy) NeedsMoreResponseData(accumulated []byte) bool {
      // Return true to accumulate more chunks before OnResponseBodyChunk is called.
      return false
  }

  ---
  Migrating an Existing v1alpha Policy

  ┌───────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────┐
  │             Step              │                                        What to change                                         │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 1. Change GetPolicy return    │ (policy.Policy, error) → (v2.Policy, error)                                                   │
  │ type                          │                                                                                               │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 2. Remove Mode()              │ Delete the method entirely                                                                    │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 3. Remove unused              │ Delete the empty stubs                                                                        │
  │ OnRequest/OnResponse          │                                                                                               │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 4. Rename phase methods       │ OnRequest → OnRequestBody, OnResponse → OnResponseBody (for body work); use                   │
  │                               │ OnRequestHeaders/OnResponseHeaders for header-only work                                       │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 5. Return pointers            │ policy.UpstreamRequestModifications{} → &v1.UpstreamRequestModifications{}                    │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 6. Use new field names        │ SetHeaders → Header.Set, AnalyticsHeaderFilter instead of DropHeadersFromAnalytics,           │
  │                               │ QueryParametersToAdd/QueryParametersToRemove                                                  │
  ├───────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 7. Rename response type       │ UpstreamResponseModifications → DownstreamResponseModifications (old alias still works)       │
  └───────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────┘

  Note: Existing v1alpha policies continue to work unchanged. The normalization layer in the executor automatically wraps value-type
  action returns in pointers for backward compatibility.

Summary by CodeRabbit

Release Notes

  • New Features
    • Phase-specific policy interfaces for granular request/response header and body processing
    • Streaming policy capabilities with chunk-by-chunk body handling and buffering control
    • ImmediateResponse action type enabling immediate policy termination with custom status and headers
    • New v2alpha policy API with enhanced parameter support for policy implementations

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 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

This pull request introduces a sealed, capability-based action and policy interface system for the gateway. It replaces monolithic policy patterns with phase-specific interfaces and adds header-level, full-request/response, and streaming action types. A new v2alpha policy interface is introduced alongside updated v1alpha types and implementations.

Changes

Cohort / File(s) Summary
Configuration
.gitignore
Added ignore entry for dev-policies/ directory in platform API and gateway sections.
V1Alpha Type System
sdk/gateway/policy/v1alpha/action.go, sdk/gateway/policy/v1alpha/context.go
Introduced sealed action hierarchy with ImmediateResponse, DropHeaderAction, and phase-specific action interfaces (RequestHeaderAction, ResponseHeaderAction, RequestAction, ResponseAction). Added streaming action types (RequestChunkAction, ResponseChunkAction). Introduced new context types for headers and streaming phases (RequestHeaderContext, RequestStreamContext, ResponseHeaderContext, ResponseStreamContext, StreamBody).
V1Alpha Policy Interface
sdk/gateway/policy/v1alpha/interface.go
Replaced monolithic Policy interface with capability-based model. Added phase-specific interfaces (RequestHeaderPolicy, ResponseHeaderPolicy, RequestPolicy, ResponsePolicy). Introduced streaming variants (StreamingRequestPolicy, StreamingResponsePolicy). Added ProcessingMode, HeaderProcessingMode, BodyProcessingMode, Level type definitions and PolicyFactory function type.
V2Alpha Policy Interface
sdk/gateway/policy/v2alpha/interface.go
New file introducing v2alpha policy model with phase-specific interfaces mirroring v1alpha. All methods accept params map parameter for configuration. Reuses v1alpha context and action types.
Policy Executor Chain
gateway/gateway-runtime/policy-engine/internal/executor/chain.go
Added parameter cloning to prevent mutation of shared spec across concurrent requests. Introduced normalizeRequestAction and normalizeResponseAction functions to convert legacy value-type actions to pointer form. Updated type assertions to use pointer semantics and DownstreamResponseModifications. Added short-circuit handling for response path.
Executor Test Updates
gateway/gateway-runtime/policy-engine/internal/executor/chain_bench_test.go, gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go
Updated mock policy implementations to return pointer-typed actions and set ProcessingMode with Buffer modes. Changed OnRequest/OnResponse return types from value structs to pointers.
Test Utilities
gateway/gateway-runtime/policy-engine/internal/testutils/policies.go
Updated mock policy implementations (NoopPolicy, HeaderModifyingPolicy, ShortCircuitingPolicy, SimpleMockPolicy) to return pointer-typed actions and proper ProcessingMode configurations with buffer modes.
Policy Engine Kernel
gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go, gateway/gateway-runtime/policy-engine/internal/kernel/translator.go, gateway/gateway-runtime/policy-engine/internal/kernel/xds.go, gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
Updated type assertions to use pointer dereferencing for ImmediateResponse and modifications structs. Removed inline comments. Note: xds.go removes mode variable initialization; subsequent code references mode without definition in current scope.
Kernel Tests
gateway/gateway-runtime/policy-engine/internal/kernel/translator_test.go
Updated RequestPolicyResult.Action and RequestExecutionResult.FinalAction field types from value to pointer forms in test assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Sealed types hop and spring,
Actions dance through every phase,
Streaming chunks, headers gleam,
Pointers now guide the way,
V1 and V2 align—
Policies refactored fine! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Title check ✅ Passed The title accurately describes the main objective: introducing phase-specific policy sub-interfaces and migrating to v2alpha SDK version.
Description check ✅ Passed The PR description comprehensively covers purpose, goals, approach, changes, and migration guidance with detailed tables and code examples, closely aligning with template requirements.

✏️ 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.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

@Thushani-Jayasekera Thushani-Jayasekera changed the title Update policy interfaces with backward compatibility feat(sdk): Introduce phase-specific policy sub-interfaces and backward-compatibility shims (v0.5.0) Mar 16, 2026
Copy link
Contributor

@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

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

Inline comments:
In `@sdk/gateway/policy/v1alpha/action.go`:
- Around line 158-176: The response header mutations in
DownstreamResponseModifications.Header are not being applied because executors
in gateway-runtime/policy-engine/internal/executor/chain.go and the translator
in gateway-runtime/policy-engine/internal/kernel/translator.go still read only
the deprecated SetHeaders/RemoveHeaders/AppendHeaders; update both the
translator and the response executor to read and apply header changes from
Header (DownstreamResponseHeaderModifications) in addition to or instead of the
deprecated maps, mapping Header.Set -> SetHeaders, Header.Remove ->
RemoveHeaders, Header.Append -> AppendHeaders (or directly applying
Header.Set/Remove/Append semantics) so policies that populate Header take
effect. Ensure you reference and update the translator code path that builds
response modifications and the executor path that applies them to the downstream
response.

In `@sdk/gateway/policy/v1alpha/context.go`:
- Around line 78-80: Update the exported doc comments that reference removed
symbols to use the package's current exported names: replace mentions of
RequestBodyContext with RequestContext, ResponseBodyContext with
ResponseContext, and update
RequestBodyPolicy/ResponseBodyPolicy/StreamingRequestBodyPolicy/StreamingResponseBodyPolicy
to the corresponding current policy interface names used in this package; ensure
the comment above the RequestContext type (and the other comment blocks at the
other noted locations) accurately states which current type and which current
interface method (e.g., the RequestContext type and the appropriate policy
interface/method names) those contexts are passed to so generated docs point to
existing symbols.

In `@sdk/gateway/policy/v1alpha/legacy.go`:
- Around line 22-25: The shared spec.Parameters.Raw map is reused across
requests and must be cloned before invoking legacy policies to avoid concurrent
mutation; update the executor code that calls LegacyPolicy.OnRequest and
LegacyPolicy.OnResponse (the places where the executor passes
spec.Parameters.Raw into legacy policy invocations) to create a shallow copy of
the params map (copy keys/values into a new map) and pass that clone instead,
ensuring BuildPolicyChain, GetPolicy, LegacyPolicy, and executor call sites use
the cloned map so concurrent requests cannot mutate the original shared map.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7fe36676-3fac-4bc6-af9d-10603feb889a

📥 Commits

Reviewing files that changed from the base of the PR and between c621181 and 07d5879.

⛔ Files ignored due to path filters (1)
  • sdk/go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • .gitignore
  • sdk/gateway/policy/v1alpha/action.go
  • sdk/gateway/policy/v1alpha/backward_compat.go
  • sdk/gateway/policy/v1alpha/context.go
  • sdk/gateway/policy/v1alpha/interface.go
  • sdk/gateway/policy/v1alpha/legacy.go

Copy link
Contributor

@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: 2

♻️ Duplicate comments (2)
sdk/gateway/policy/v1alpha/context.go (1)

153-170: ⚠️ Potential issue | 🟡 Minor

Update streaming context docs to current interface names.

These comments still reference removed symbols (StreamingRequestBodyPolicy, StreamingResponseBodyPolicy). Please point them to StreamingRequestPolicy and StreamingResponsePolicy so generated docs resolve correctly.

✏️ Suggested doc update
-// RequestStreamContext is the per-chunk context passed to StreamingRequestBodyPolicy.
+// RequestStreamContext is the per-chunk context passed to StreamingRequestPolicy.
...
-// ResponseStreamContext is the per-chunk context passed to StreamingResponseBodyPolicy.
+// ResponseStreamContext is the per-chunk context passed to StreamingResponsePolicy.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/gateway/policy/v1alpha/context.go` around lines 153 - 170, Update the doc
comments for RequestStreamContext and ResponseStreamContext to reference the
current interface names RequestStreamingPolicy? No—use exact names: replace
references to the removed StreamingRequestBodyPolicy and
StreamingResponseBodyPolicy with the new names RequestStreamingPolicy? Wait, the
review says use RequestStreamingPolicy and RequestStreamingPolicy? The review
says use RequestStreamingPolicy and RequestStreamingPolicy? This is confusing.
Please update the comments to point to RequestStreamingPolicy and
StreamingResponsePolicy? I'm going to correct: Replace references to the removed
StreamingRequestBodyPolicy with RequestStreamingPolicy and
StreamingResponseBodyPolicy with ResponseStreamingPolicy; update wording to
mention StreamingRequestPolicy and StreamingResponsePolicy so generated docs
link to the existing interfaces, and ensure the comments around
RequestStreamContext and ResponseStreamContext mention those exact symbol names.
sdk/gateway/policy/v1alpha/action.go (1)

158-176: ⚠️ Potential issue | 🔴 Critical

Response Header wiring concern still applies.

DownstreamResponseModifications.Header is declared as the new contract, but runtime handling has already been flagged as relying on deprecated flat fields; this remains a functional risk for policies using only Header.

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

In `@sdk/gateway/policy/v1alpha/action.go` around lines 158 - 176, The runtime
still reads the deprecated flat response header fields instead of honoring the
new DownstreamResponseModifications.Header contract; update the wiring so any
code that mutates or reads response header actions prefers Header
(DownstreamResponseModifications.Header) and only falls back to the deprecated
fields (SetHeaders, RemoveHeaders, AppendHeaders, DropHeadersFromAnalytics) when
Header is nil/empty. Locate the response handling logic that inspects
SetHeaders/RemoveHeaders/AppendHeaders/DropHeadersFromAnalytics or
AnalyticsHeaderFilter and change it to: 1) if Header != nil apply
Header.Set/Remove/Append and use AnalyticsHeaderFilter for analytics; 2)
otherwise translate the deprecated maps/slices into the equivalent Header
structure so both old and new policies behave identically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sdk/gateway/policy/v1alpha/action.go`:
- Around line 11-13: Update the ImmediateResponse comment to reference the
current public action type names: replace "HeaderAction" with
"RequestHeaderAction" or "ResponseHeaderAction" as appropriate, and replace
"RequestBodyAction/ResponseBodyAction" with "RequestAction" and "ResponseAction"
respectively; ensure the sentence now reads that a non-nil *ImmediateResponse
embedded in RequestHeaderAction, ResponseHeaderAction, RequestAction, or
ResponseAction short-circuits the policy chain and returns a response
immediately.
- Around line 103-105: The runtime translator is ignoring the new
UpstreamRequestModifications.Header field and only reads the deprecated
SetHeaders/AppendHeaders/RemoveHeaders, so update the translator
(gateway/gateway-runtime/policy-engine/internal/kernel/translator.go) to honor
mods.Header (type *UpstreamRequestHeaderModifications) when present: read
Header.Set, Header.Append, Header.Remove and merge or fall back to the
deprecated SetHeaders/AppendHeaders/RemoveHeaders as needed so policies using
only Header are applied; ensure the same mapping/normalization logic used for
the deprecated fields is applied to Header to produce the same runtime header
mutations.

---

Duplicate comments:
In `@sdk/gateway/policy/v1alpha/action.go`:
- Around line 158-176: The runtime still reads the deprecated flat response
header fields instead of honoring the new DownstreamResponseModifications.Header
contract; update the wiring so any code that mutates or reads response header
actions prefers Header (DownstreamResponseModifications.Header) and only falls
back to the deprecated fields (SetHeaders, RemoveHeaders, AppendHeaders,
DropHeadersFromAnalytics) when Header is nil/empty. Locate the response handling
logic that inspects
SetHeaders/RemoveHeaders/AppendHeaders/DropHeadersFromAnalytics or
AnalyticsHeaderFilter and change it to: 1) if Header != nil apply
Header.Set/Remove/Append and use AnalyticsHeaderFilter for analytics; 2)
otherwise translate the deprecated maps/slices into the equivalent Header
structure so both old and new policies behave identically.

In `@sdk/gateway/policy/v1alpha/context.go`:
- Around line 153-170: Update the doc comments for RequestStreamContext and
ResponseStreamContext to reference the current interface names
RequestStreamingPolicy? No—use exact names: replace references to the removed
StreamingRequestBodyPolicy and StreamingResponseBodyPolicy with the new names
RequestStreamingPolicy? Wait, the review says use RequestStreamingPolicy and
RequestStreamingPolicy? The review says use RequestStreamingPolicy and
RequestStreamingPolicy? This is confusing. Please update the comments to point
to RequestStreamingPolicy and StreamingResponsePolicy? I'm going to correct:
Replace references to the removed StreamingRequestBodyPolicy with
RequestStreamingPolicy and StreamingResponseBodyPolicy with
ResponseStreamingPolicy; update wording to mention StreamingRequestPolicy and
StreamingResponsePolicy so generated docs link to the existing interfaces, and
ensure the comments around RequestStreamContext and ResponseStreamContext
mention those exact symbol names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e9b0cbe-4c0a-46da-9514-d13cb0982e74

📥 Commits

Reviewing files that changed from the base of the PR and between 07d5879 and 35afd7b.

📒 Files selected for processing (3)
  • sdk/gateway/policy/v1alpha/action.go
  • sdk/gateway/policy/v1alpha/backward_compat.go
  • sdk/gateway/policy/v1alpha/context.go

Copy link
Contributor

@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 (1)
gateway/gateway-runtime/policy-engine/internal/kernel/translator.go (1)

397-461: ⚠️ Potential issue | 🔴 Critical

Same pointer-only assertion issue for response modifications.

Line 398 asserts *policy.DownstreamResponseModifications but analytics.go (lines 400-413 in context snippet) returns a value type. Response analytics metadata will be silently dropped.

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

In `@gateway/gateway-runtime/policy-engine/internal/kernel/translator.go` around
lines 397 - 461, The code in the translator.go uses a type assertion on
policyResult.Action to *policy.DownstreamResponseModifications, but analytics.go
returns a value type for this, causing the assertion to fail and response
analytics metadata to be dropped. To fix this, change the type assertion to the
value type policy.DownstreamResponseModifications instead of the pointer type in
the block handling response modifications in the relevant function. This ensures
the correct type is asserted, preserving response analytics metadata processing.
🧹 Nitpick comments (2)
gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go (1)

240-314: Consider extracting shared body requirement logic to reduce duplication.

The buildPolicyChain method here duplicates substantial logic from kernel/body_mode.go:BuildPolicyChain, including the new LegacyPolicy mode handling. Consider extracting the common body requirement computation into a shared helper.

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

In `@gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go` around
lines 240 - 314, The buildPolicyChain function duplicates body-requirement and
execution-condition logic present in kernel/body_mode.go:BuildPolicyChain;
extract that logic into a shared helper (e.g.,
policyutil.DetermineBodyRequirements or registry.DetermineBodyRequirements) that
accepts the created policies and/or PolicySpecs and returns requiresRequestBody,
requiresResponseBody, hasExecutionConditions. Move the checks that use
policy.LegacyPolicy and the ExecutionCondition inspection (policy.ModeRequest,
policy.ModeResponse, policy.ModeRequestResponse, and non-empty
ExecutionCondition) into that helper, and replace the duplicated code in
ResourceHandler.buildPolicyChain (and kernel.BuildPolicyChain) to call the new
helper and assign the three booleans before constructing the
registry.PolicyChain.
gateway/gateway-runtime/policy-engine/internal/kernel/xds.go (1)

167-175: Third instance of duplicated LegacyPolicy body requirement logic.

This is the file-based config loader's version of the same pattern found in body_mode.go and xdsclient/handler.go. The body requirement computation is now duplicated in three places.

♻️ Suggested consolidation

Extract the body requirement logic into a shared function in the policy package or a kernel utility:

// In a shared location (e.g., policy package or kernel/utils.go)
func ComputeBodyRequirements(impl policy.Policy) (requiresRequest, requiresResponse bool) {
    if lp, ok := impl.(policy.LegacyPolicy); ok {
        mode := lp.Mode()
        requiresRequest = mode == policy.ModeRequest || mode == policy.ModeRequestResponse
        requiresResponse = mode == policy.ModeResponse || mode == policy.ModeRequestResponse
    }
    // TODO: Add detection for new capability-based interfaces
    return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-runtime/policy-engine/internal/kernel/xds.go` around lines
167 - 175, The duplicated logic that inspects LegacyPolicy.Mode() to set
requiresRequestBody/requiresResponseBody should be consolidated: add a shared
helper (e.g., policy.ComputeBodyRequirements or kernel.ComputeBodyRequirements)
that accepts policy.Policy (or policy.LegacyPolicy) and returns
(requiresRequest, requiresResponse), then replace the three inline blocks
(including the block using impl.(policy.LegacyPolicy) in xds.go) to call that
helper; ensure the helper uses the same mode checks (policy.ModeRequest,
policy.ModeResponse, policy.ModeRequestResponse) and update callers to use the
returned booleans.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go`:
- Around line 152-157: The current cloning of spec.Parameters.Raw into params is
only a shallow copy (params := make(...); for k,v := range spec.Parameters.Raw {
params[k]=v }), so nested maps/slices remain shared and can be mutated across
requests; replace these shallow copies (the params cloning near
spec.Parameters.Raw in chain.go and the similar block later) with a deep-copy
routine that recursively duplicates maps, slices, and scalar values (or use a
safe serialization round-trip like JSON/YAML marshal->unmarshal) to ensure no
shared references remain; apply the same deep-copy change to both occurrences
(the initial params clone and the later clone around lines 292-297) and keep the
semantics of copying all keys from spec.Parameters.Raw into the new params map.
- Around line 197-199: The code currently only type-asserts
*policy.UpstreamRequestModifications and thus drops value-form returns and
entirely ignores UpstreamResponseModifications; update the action handling so it
accepts both pointer and value forms for request mods
(policy.UpstreamRequestModifications and *policy.UpstreamRequestModifications)
and invokes applyRequestModifications, and add analogous handling in the
response execution path for policy.UpstreamResponseModifications and
*policy.UpstreamResponseModifications calling the existing
applyResponseModifications (or appropriate response modifier function); ensure
both switch/type-assert branches are added where actions are processed
(referencing the action type assertions around the lines with
applyRequestModifications and the response execution path near the later
execution block).

In `@gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go`:
- Around line 81-89: The code only inspects LegacyPolicy.Mode() and misses
policies that implement the new capability interfaces, so update the detection
around the existing impl handling to also type-assert impl for
policy.RequestPolicy, policy.ResponsePolicy, policy.StreamingRequestPolicy and
policy.StreamingResponsePolicy and set chain.RequiresRequestBody and
chain.RequiresResponseBody accordingly (e.g., if impl.(policy.RequestPolicy) is
true -> chain.RequiresRequestBody = true; if impl.(policy.ResponsePolicy) is
true -> chain.RequiresResponseBody = true); keep this logic alongside the
existing LegacyPolicy.Mode() branch that currently reads lp.Mode().

In `@gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go`:
- Around line 295-303: The code only checks for policy.LegacyPolicy when setting
requiresRequestBody/requiresResponseBody, missing newer policies that expose a
Mode() method; change the type assertion to accept any implementation that
exposes Mode() by using an inline interface assertion (e.g. if m, ok :=
impl.(interface{ Mode() policy.ModeType }); ok { mode := m.Mode(); if
mode==policy.ModeRequest||mode==policy.ModeRequestResponse { requiresRequestBody
= true } if mode==policy.ModeResponse||mode==policy.ModeRequestResponse {
requiresResponseBody = true } }) so impl (and not only LegacyPolicy) that
provide Mode() will trigger buffering—update the block referencing impl,
policy.LegacyPolicy, and the requiresRequestBody/requiresResponseBody flags
accordingly.

---

Outside diff comments:
In `@gateway/gateway-runtime/policy-engine/internal/kernel/translator.go`:
- Around line 397-461: The code in the translator.go uses a type assertion on
policyResult.Action to *policy.DownstreamResponseModifications, but analytics.go
returns a value type for this, causing the assertion to fail and response
analytics metadata to be dropped. To fix this, change the type assertion to the
value type policy.DownstreamResponseModifications instead of the pointer type in
the block handling response modifications in the relevant function. This ensures
the correct type is asserted, preserving response analytics metadata processing.

---

Nitpick comments:
In `@gateway/gateway-runtime/policy-engine/internal/kernel/xds.go`:
- Around line 167-175: The duplicated logic that inspects LegacyPolicy.Mode() to
set requiresRequestBody/requiresResponseBody should be consolidated: add a
shared helper (e.g., policy.ComputeBodyRequirements or
kernel.ComputeBodyRequirements) that accepts policy.Policy (or
policy.LegacyPolicy) and returns (requiresRequest, requiresResponse), then
replace the three inline blocks (including the block using
impl.(policy.LegacyPolicy) in xds.go) to call that helper; ensure the helper
uses the same mode checks (policy.ModeRequest, policy.ModeResponse,
policy.ModeRequestResponse) and update callers to use the returned booleans.

In `@gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go`:
- Around line 240-314: The buildPolicyChain function duplicates body-requirement
and execution-condition logic present in kernel/body_mode.go:BuildPolicyChain;
extract that logic into a shared helper (e.g.,
policyutil.DetermineBodyRequirements or registry.DetermineBodyRequirements) that
accepts the created policies and/or PolicySpecs and returns requiresRequestBody,
requiresResponseBody, hasExecutionConditions. Move the checks that use
policy.LegacyPolicy and the ExecutionCondition inspection (policy.ModeRequest,
policy.ModeResponse, policy.ModeRequestResponse, and non-empty
ExecutionCondition) into that helper, and replace the duplicated code in
ResourceHandler.buildPolicyChain (and kernel.BuildPolicyChain) to call the new
helper and assign the three booleans before constructing the
registry.PolicyChain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb50e180-68c6-4b10-aa88-b57b903c5bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 35afd7b and d9a702f.

📒 Files selected for processing (10)
  • gateway/gateway-runtime/policy-engine/internal/executor/chain.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_bench_test.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/execution_context.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/translator.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/xds.go
  • gateway/gateway-runtime/policy-engine/internal/testutils/policies.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • sdk/gateway/policy/v1alpha/action.go

Comment on lines 81 to 89
if lp, ok := impl.(policy.LegacyPolicy); ok {
mode := lp.Mode()
if mode == policy.ModeRequest || mode == policy.ModeRequestResponse {
chain.RequiresRequestBody = true
}
if mode == policy.ModeResponse || mode == policy.ModeRequestResponse {
chain.RequiresResponseBody = true
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any policies implement the new interfaces without LegacyPolicy
echo "=== Searching for RequestPolicy/ResponsePolicy implementations ==="
rg -n 'func \([^)]+\) OnRequestBody\(' --type=go gateway sdk

echo ""
echo "=== Searching for LegacyPolicy implementations ==="
rg -n 'func \([^)]+\) Mode\(\) policy\.ProcessingMode' --type=go gateway sdk

Repository: wso2/api-platform

Length of output: 2156


🏁 Script executed:

# Find policy interface definitions
find . -name "interface.go" -o -name "*policy*.go" | grep -E "(interface|policy)" | head -20

Repository: wso2/api-platform

Length of output: 1196


🏁 Script executed:

# Check the complete body_mode.go file to understand full context
cat -n gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go

Repository: wso2/api-platform

Length of output: 5295


🏁 Script executed:

# Search for the policy interface definitions
rg -n "type.*Policy interface" --type=go gateway/gateway-runtime/policy-engine

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

# Find policy interface definitions in the SDK package
rg -n "type.*Policy interface" --type=go -A 3 sdk/

Repository: wso2/api-platform

Length of output: 2534


🏁 Script executed:

# Check what's in the policy package
find sdk -name "*.go" -type f | head -20

Repository: wso2/api-platform

Length of output: 956


🏁 Script executed:

# Look for RequestPolicy or ResponsePolicy mentions
rg -n "RequestPolicy\|ResponsePolicy" --type=go gateway sdk

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

# Check the policy package structure
cat -n sdk/gateway/policy/v1alpha/policy.go 2>/dev/null | head -100

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

# Check if there are any implementations of the new interfaces
rg -n "impl.*RequestPolicy|impl.*ResponsePolicy|impl.*StreamingRequestPolicy|impl.*StreamingResponsePolicy" --type=go

Repository: wso2/api-platform

Length of output: 746


🏁 Script executed:

# Check if any policies implement both LegacyPolicy and new interfaces
rg -l "type.*Policy struct" --type=go gateway/ | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && cat -n {} | grep -A 10 "type.*Policy struct"'

Repository: wso2/api-platform

Length of output: 9570


🏁 Script executed:

# Check what the AnalyticsPolicy (which implements LegacyPolicy) looks like
cat -n gateway/system-policies/analytics/analytics.go | head -150

Repository: wso2/api-platform

Length of output: 6747


🏁 Script executed:

# Check if there's any other place where RequiresRequestBody/RequiresResponseBody are set
rg -n "RequiresRequestBody\|RequiresResponseBody" --type=go gateway/

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

# Check the policy.go file to understand ProcessingMode structure
cat -n sdk/gateway/policy/v1alpha/policy.go

Repository: wso2/api-platform

Length of output: 129


🏁 Script executed:

# Look at the complete interface definitions with their documentation
cat -n sdk/gateway/policy/v1alpha/interface.go | head -120

Repository: wso2/api-platform

Length of output: 5893


Policies implementing only the new capability interfaces will not trigger body buffering.

The body requirement computation in lines 81–89 only checks LegacyPolicy.Mode(). According to the interface documentation at lines 8–14, the kernel is supposed to infer body buffering requirements from implementations of RequestPolicy, ResponsePolicy, StreamingRequestPolicy, and StreamingResponsePolicy. However, the current code does not detect these interfaces.

If a policy implements only these new capability-based interfaces without also implementing LegacyPolicy, it will not set RequiresRequestBody or RequiresResponseBody, causing the kernel to skip body buffering entirely.

Add capability detection for the new interfaces:

Example implementation
// Detect RequestPolicy/ResponsePolicy capabilities
if _, ok := impl.(policy.RequestPolicy); ok {
    chain.RequiresRequestBody = true
}
if _, ok := impl.(policy.ResponsePolicy); ok {
    chain.RequiresResponseBody = true
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go` around
lines 81 - 89, The code only inspects LegacyPolicy.Mode() and misses policies
that implement the new capability interfaces, so update the detection around the
existing impl handling to also type-assert impl for policy.RequestPolicy,
policy.ResponsePolicy, policy.StreamingRequestPolicy and
policy.StreamingResponsePolicy and set chain.RequiresRequestBody and
chain.RequiresResponseBody accordingly (e.g., if impl.(policy.RequestPolicy) is
true -> chain.RequiresRequestBody = true; if impl.(policy.ResponsePolicy) is
true -> chain.RequiresResponseBody = true); keep this logic alongside the
existing LegacyPolicy.Mode() branch that currently reads lp.Mode().

Copy link
Contributor

@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

♻️ Duplicate comments (2)
gateway/gateway-runtime/policy-engine/internal/executor/chain.go (2)

146-151: ⚠️ Potential issue | 🟠 Major

The params clone is still shallow.

spec.Parameters.Raw is a map[string]interface{}; nested maps, slices, and pointers are still shared after this copy. A policy mutating nested params can still leak state across requests or race with another request. Please deep-clone the values once and reuse that helper in both request and response paths.

Also applies to: 280-285

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

In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go` around
lines 146 - 151, The shallow copy of spec.Parameters.Raw into params still
shares nested structures; implement a deep-clone helper (e.g.,
DeepCloneParamValue or cloneParamsMap) that recursively copies maps, slices, and
pointers (falling back to encoding/json marshal/unmarshal if easier) and replace
the current shallow loop that creates params := make(...)/for k,v := range
spec.Parameters.Raw { params[k]=v } with params[k]=DeepCloneParamValue(v); then
reuse the same helper for the response-path clone (the other clone at the
response handling site) to ensure no nested state is shared across requests.

324-344: ⚠️ Potential issue | 🔴 Critical

Canonicalize structured mutations here before downstream code sees them.

These helpers only normalize pointer vs value. The new API and newV2AlphaAdapter populate Header and QueryParametersToAdd / QueryParametersToRemove, while the executor/translator paths still consume the legacy flat fields. As written, adapted header-phase mutations are stored in policyResult.Action but never applied. Flatten the structured fields here until every consumer reads the new shape.

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

In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go` around
lines 324 - 344, normalizeRequestAction and normalizeResponseAction currently
only convert value vs pointer but do not flatten the new structured mutation
fields, so adapted header/query mutations end up in policyResult.Action but are
never applied; update both functions (handling
policy.UpstreamRequestModifications and policy.DownstreamResponseModifications /
policy.ImmediateResponse) to, after converting to pointer form, copy/flatten the
structured fields (e.g., Header map/array, QueryParametersToAdd,
QueryParametersToRemove, etc.) into the legacy flat mutation fields the
executor/translator expects (the old HeaderToSet/HeaderToRemove,
QueryParametersToAdd/Remove equivalents, and any body/status fields), preserving
existing values and avoiding duplication so downstream consumers see the
flattened mutations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go`:
- Around line 310-314: The response-phase loop currently only handles
*policy.DownstreamResponseModifications and ignores *policy.ImmediateResponse,
so short-circuiting never happens; update the loop that calls
normalizeResponseAction to also detect when action is a
*policy.ImmediateResponse, apply the immediate response handling (invoke the
existing immediate response helper or create one analogous to
applyResponseModifications), set the chain's FinalAction to that
ImmediateResponse value, call StopExecution() to halt further policy execution,
and ensure you still call applyResponseModifications when action is a
DownstreamResponseModifications so both cases are covered; reference
normalizeResponseAction, policy.ImmediateResponse,
policy.DownstreamResponseModifications, applyResponseModifications,
StopExecution(), and FinalAction when making the change.

In `@gateway/gateway-runtime/policy-engine/internal/registry/v2alpha_adapter.go`:
- Around line 73-84: The body-phase callback is currently skipped when
ctx.Body.Present is false, causing v2 RequestPolicy/ResponsePolicy.OnRequestBody
and OnResponseBody to never run for empty bodies; update the checks in the
v2alpha_adapter.go adapter so you call rp.OnRequestBody(ctx) and the response
equivalent regardless of ctx.Body.Present (i.e., remove the ctx.Body.Present
gating around the type switch), while keeping existing nil-safety (ensure
handlers tolerate ctx.Body == nil) and still handling the same return types
(v1.ImmediateResponse, *v1.ImmediateResponse, v1.UpstreamRequestModifications,
*v1.UpstreamRequestModifications) via the same switch/mergeRequestMods logic;
apply the same change to the analogous block at the other location (the
OnResponseBody mapping at lines 108-119).
- Around line 157-159: The current assignment dst.Header = src.Header overwrites
earlier header mutations (breaking composition for
OnRequestHeaders/OnResponseHeaders); instead, update the code in
v2alpha_adapter.go to merge src.Header into dst.Header: ensure dst.Header is
non-nil, then copy/overwrite individual map entries from src.Header.Set into
dst.Header.Set, append entries from src.Header.Append into dst.Header.Append
(preserving existing slices), and union/remove keys from src.Header.Remove into
dst.Header.Remove (preserving prior removes), for both places where dst.Header
is currently replaced (the src.Header handling at the earlier block and the
similar one at lines ~191-193) so later phases only override conflicting keys
rather than discarding prior mutations.

---

Duplicate comments:
In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go`:
- Around line 146-151: The shallow copy of spec.Parameters.Raw into params still
shares nested structures; implement a deep-clone helper (e.g.,
DeepCloneParamValue or cloneParamsMap) that recursively copies maps, slices, and
pointers (falling back to encoding/json marshal/unmarshal if easier) and replace
the current shallow loop that creates params := make(...)/for k,v := range
spec.Parameters.Raw { params[k]=v } with params[k]=DeepCloneParamValue(v); then
reuse the same helper for the response-path clone (the other clone at the
response handling site) to ensure no nested state is shared across requests.
- Around line 324-344: normalizeRequestAction and normalizeResponseAction
currently only convert value vs pointer but do not flatten the new structured
mutation fields, so adapted header/query mutations end up in policyResult.Action
but are never applied; update both functions (handling
policy.UpstreamRequestModifications and policy.DownstreamResponseModifications /
policy.ImmediateResponse) to, after converting to pointer form, copy/flatten the
structured fields (e.g., Header map/array, QueryParametersToAdd,
QueryParametersToRemove, etc.) into the legacy flat mutation fields the
executor/translator expects (the old HeaderToSet/HeaderToRemove,
QueryParametersToAdd/Remove equivalents, and any body/status fields), preserving
existing values and avoiding duplication so downstream consumers see the
flattened mutations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 164ce868-9568-430e-b12c-747cdd352878

📥 Commits

Reviewing files that changed from the base of the PR and between d9a702f and b3c1da1.

📒 Files selected for processing (13)
  • gateway/gateway-runtime/policy-engine/internal/executor/chain.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_bench_test.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/translator_test.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/xds.go
  • gateway/gateway-runtime/policy-engine/internal/registry/registry.go
  • gateway/gateway-runtime/policy-engine/internal/registry/v2alpha_adapter.go
  • gateway/gateway-runtime/policy-engine/internal/testutils/policies.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • sdk/gateway/policy/v1alpha/action.go
  • sdk/gateway/policy/v1alpha/interface.go
  • sdk/gateway/policy/v2alpha/interface.go
💤 Files with no reviewable changes (3)
  • gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/xds.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_bench_test.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go

Copy link
Contributor

@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

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

Inline comments:
In `@sdk/gateway/policy/v1alpha/interface.go`:
- Around line 118-119: Update the BodyModeBuffer documentation to mention the
phase-specific handlers by stating that it buffers the complete body before
invoking both the request/response lifecycle handlers and their body-specific
counterparts; specifically reference BodyModeBuffer (type BodyProcessingMode)
and note it applies to OnRequest, OnRequestBody, OnResponse, and OnResponseBody
so implementers know all relevant hooks will receive the buffered body.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 200348c5-af42-41c7-97ec-278593a0ece3

📥 Commits

Reviewing files that changed from the base of the PR and between b3c1da1 and 750c248.

📒 Files selected for processing (1)
  • sdk/gateway/policy/v1alpha/interface.go

Comment on lines 118 to 119
// BodyModeBuffer — buffer the complete body before invoking OnRequest/OnResponse.
BodyModeBuffer BodyProcessingMode = "BUFFER"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update BodyModeBuffer doc to include phase-specific handlers.

The comment currently mentions only OnRequest/OnResponse; with the new sub-interfaces, it should also mention OnRequestBody/OnResponseBody to avoid implementer confusion.

✏️ Suggested doc update
-	// BodyModeBuffer — buffer the complete body before invoking OnRequest/OnResponse.
+	// BodyModeBuffer — buffer the complete body before invoking body-phase handlers
+	// (OnRequest/OnResponse for monolithic policies, or
+	// OnRequestBody/OnResponseBody for phase-specific policies).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// BodyModeBuffer — buffer the complete body before invoking OnRequest/OnResponse.
BodyModeBuffer BodyProcessingMode = "BUFFER"
// BodyModeBuffer — buffer the complete body before invoking body-phase handlers
// (OnRequest/OnResponse for monolithic policies, or
// OnRequestBody/OnResponseBody for phase-specific policies).
BodyModeBuffer BodyProcessingMode = "BUFFER"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/gateway/policy/v1alpha/interface.go` around lines 118 - 119, Update the
BodyModeBuffer documentation to mention the phase-specific handlers by stating
that it buffers the complete body before invoking both the request/response
lifecycle handlers and their body-specific counterparts; specifically reference
BodyModeBuffer (type BodyProcessingMode) and note it applies to OnRequest,
OnRequestBody, OnResponse, and OnResponseBody so implementers know all relevant
hooks will receive the buffered body.

@Thushani-Jayasekera
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
gateway/gateway-runtime/policy-engine/internal/kernel/translator.go (1)

367-398: ⚠️ Potential issue | 🔴 Critical

Handle response-phase *ImmediateResponse before folding mutations.

ResponseExecutionResult already carries FinalAction, but this code never mirrors the request-side short-circuit branch. If a response policy returns *policy.ImmediateResponse, the translator drops the replacement response and continues forwarding the upstream one.

♻️ Duplicate comments (4)
gateway/gateway-runtime/policy-engine/internal/kernel/translator.go (2)

105-142: ⚠️ Potential issue | 🔴 Critical

Normalize the new request mutation fields before translating.

This loop still reads only the deprecated flat fields (SetHeaders, AppendHeaders, RemoveHeaders, AddQueryParameters, RemoveQueryParameters, DropHeadersFromAnalytics). Policies using Header, QueryParametersToAdd, QueryParametersToRemove, or AnalyticsHeaderFilter silently become no-ops here.

Suggested normalization shape
-				// Collect SetHeader operations
-				for key, value := range mods.SetHeaders {
+				headerMods := mods.Header
+				if headerMods == nil {
+					headerMods = &policy.UpstreamRequestHeaderModifications{
+						Set:    mods.SetHeaders,
+						Remove: mods.RemoveHeaders,
+						Append: mods.AppendHeaders,
+					}
+				}
+				for key, value := range headerMods.Set {
 					headerOps[strings.ToLower(key)] = append(headerOps[strings.ToLower(key)], &headerOp{opType: "set", value: value})
 				}
 
-				// Collect AppendHeader operations
-				for key, values := range mods.AppendHeaders {
+				for key, values := range headerMods.Append {
 					for _, value := range values {
 						headerOps[strings.ToLower(key)] = append(headerOps[strings.ToLower(key)], &headerOp{opType: "append", value: value})
 					}
 				}
 
-				// Collect RemoveHeader operations
-				for _, key := range mods.RemoveHeaders {
+				for _, key := range headerMods.Remove {
 					headerOps[strings.ToLower(key)] = append(headerOps[strings.ToLower(key)], &headerOp{opType: "remove", value: ""})
 				}
 
-				if mods.AddQueryParameters != nil {
-					path = utils.AddQueryParametersToPath(path, mods.AddQueryParameters)
+				addQuery := mods.QueryParametersToAdd
+				if addQuery == nil {
+					addQuery = mods.AddQueryParameters
+				}
+				if addQuery != nil {
+					path = utils.AddQueryParametersToPath(path, addQuery)
 					pathMutation = &path
 				}

Also applies to: 167-179

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

In `@gateway/gateway-runtime/policy-engine/internal/kernel/translator.go` around
lines 105 - 142, The loop that translates policyResult.Action into
headerOps/path/bodyMutation must first normalize the newer mutation fields into
the legacy flat shape so both old and new fields are honored: detect when
policyResult.Action is a *policy.UpstreamRequestModifications and if mods.Header
(new header map), mods.QueryParametersToAdd, mods.QueryParametersToRemove, or
mods.AnalyticsHeaderFilter are present, merge them into mods.SetHeaders,
mods.AppendHeaders, mods.RemoveHeaders, mods.AddQueryParameters,
mods.RemoveQueryParameters, and DropHeadersFromAnalytics respectively before the
existing translation logic runs; update the normalization in the translator.go
block handling UpstreamRequestModifications (the code that currently reads
mods.SetHeaders/AppendHeaders/RemoveHeaders/AddQueryParameters/RemoveQueryParameters/DropHeadersFromAnalytics)
so policies using Header, QueryParametersToAdd/Remove, or AnalyticsHeaderFilter
are not no-ops.

398-458: ⚠️ Potential issue | 🔴 Critical

Honor the new response mutation fields in this translator.

Like the request path, this still aggregates only SetHeaders, AppendHeaders, RemoveHeaders, and DropHeadersFromAnalytics. DownstreamResponseModifications.Header and AnalyticsHeaderFilter are ignored, so policies migrated to the new surface never mutate the downstream response.

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

In `@gateway/gateway-runtime/policy-engine/internal/kernel/translator.go` around
lines 398 - 458, The translator currently ignores
DownstreamResponseModifications.Header and AnalyticsHeaderFilter so migrated
policies don't actually mutate downstream responses; update the block that
handles policy.DownstreamResponseModifications (the mods variable) to: 1)
collect/modulate header mutations from mods.Header into headerOps (like
SetHeaders/AppendHeaders/RemoveHeaders) so downstream headers are mutated, 2)
honor mods.AnalyticsHeaderFilter when computing finalizedHeaders (use it
together with dropAction when calling finalizeAnalyticsHeaders or a new helper)
and add the resulting filtered set into analyticsData["response_headers"], and
3) ensure any reference to execCtx.responseContext.ResponseHeaders/GetAll
remains the source of originalHeaders; touch symbols headerOps, headerOp,
mods.Header, mods.AnalyticsHeaderFilter, finalizeAnalyticsHeaders,
analyticsData, and execCtx.responseContext.ResponseHeaders.
sdk/gateway/policy/v1alpha/context.go (1)

153-158: ⚠️ Potential issue | 🟡 Minor

Fix stale streaming interface names in the exported docs.

RequestStreamContext and ResponseStreamContext still point readers to StreamingRequestBodyPolicy / StreamingResponseBodyPolicy, which are not part of the current public surface. Generated docs will send policy authors to dead symbols unless these comments reference the current streaming callbacks.

Also applies to: 170-172

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

In `@sdk/gateway/policy/v1alpha/context.go` around lines 153 - 158, The GoDoc for
RequestStreamContext (and likewise for ResponseStreamContext) still references
removed symbols StreamingRequestBodyPolicy/StreamingResponseBodyPolicy; update
the comment text on RequestStreamContext and ResponseStreamContext to remove
those stale names and instead reference the current exported streaming callback
types/functions used by the SDK (replace the dead symbol names with the present
streaming callback identifiers), keeping the same explanation about read-only
headers and body arrival via the stream argument so generated docs point to
valid public symbols.
gateway/gateway-runtime/policy-engine/internal/executor/chain.go (1)

147-152: ⚠️ Potential issue | 🟠 Major

Parameter cloning is still shallow; nested values can leak mutations across requests.

Top-level map copying here does not clone nested maps/slices, so policy mutations can still race through shared nested references.

Suggested fix (use deep clone helper in both paths)
-		params := make(map[string]interface{}, len(spec.Parameters.Raw))
-		for k, v := range spec.Parameters.Raw {
-			params[k] = v
-		}
+		params := cloneParams(spec.Parameters.Raw)
-		params := make(map[string]interface{}, len(spec.Parameters.Raw))
-		for k, v := range spec.Parameters.Raw {
-			params[k] = v
-		}
+		params := cloneParams(spec.Parameters.Raw)
+func cloneParams(raw map[string]interface{}) map[string]interface{} {
+	cloned := make(map[string]interface{}, len(raw))
+	for k, v := range raw {
+		cloned[k] = deepCloneValue(v)
+	}
+	return cloned
+}
+
+func deepCloneValue(v interface{}) interface{} {
+	switch t := v.(type) {
+	case map[string]interface{}:
+		m := make(map[string]interface{}, len(t))
+		for k, vv := range t {
+			m[k] = deepCloneValue(vv)
+		}
+		return m
+	case []interface{}:
+		s := make([]interface{}, len(t))
+		for i, vv := range t {
+			s[i] = deepCloneValue(vv)
+		}
+		return s
+	default:
+		return v
+	}
+}

Also applies to: 281-286

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

In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go` around
lines 147 - 152, The current shallow copy of spec.Parameters.Raw into params
allows nested maps/slices to be shared and mutated; replace the manual top-level
copy with a deep-clone of spec.Parameters.Raw using the project's deep clone
helper (e.g., deepClone or DeepClone) so nested structures are duplicated;
update both occurrences (the block that creates params from spec.Parameters.Raw
and the other instance at the later block around the 281-286 region) to call the
helper and assign the result to params to prevent cross-request mutation.
🧹 Nitpick comments (1)
gateway/gateway-runtime/policy-engine/internal/testutils/policies.go (1)

69-80: Use the new nested header actions in this shared test fixture.

HeaderModifyingPolicy still returns SetHeaders, so every test that relies on this helper keeps validating only the deprecated API. Switching this fixture to UpstreamRequestModifications.Header / DownstreamResponseModifications.Header would make the suite exercise the migration path by default.

Proposed fixture update
 func (p *HeaderModifyingPolicy) OnRequest(*policy.RequestContext, map[string]interface{}) policy.RequestAction {
 	return &policy.UpstreamRequestModifications{
-		SetHeaders: map[string]string{p.Key: p.Value},
+		Header: &policy.UpstreamRequestHeaderModifications{
+			Set: map[string]string{p.Key: p.Value},
+		},
 	}
 }
 
 // OnResponse returns modifications to set the configured header.
 func (p *HeaderModifyingPolicy) OnResponse(*policy.ResponseContext, map[string]interface{}) policy.ResponseAction {
 	return &policy.DownstreamResponseModifications{
-		SetHeaders: map[string]string{p.Key: p.Value},
+		Header: &policy.DownstreamResponseHeaderModifications{
+			Set: map[string]string{p.Key: p.Value},
+		},
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-runtime/policy-engine/internal/testutils/policies.go` around
lines 69 - 80, The shared test fixture HeaderModifyingPolicy still populates the
deprecated SetHeaders fields, so update OnRequest and OnResponse to use the new
nested Header structs: return an &policy.UpstreamRequestModifications{Header:
&policy.HeaderActions{Set: map[string]string{p.Key: p.Value}}} in OnRequest and
an &policy.DownstreamResponseModifications{Header: &policy.HeaderActions{Set:
map[string]string{p.Key: p.Value}}} in OnResponse so tests exercise the
migration path; adjust to reference UpstreamRequestModifications.Header,
DownstreamResponseModifications.Header and policy.HeaderActions.Set instead of
SetHeaders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sdk/gateway/policy/v1alpha/interface.go`:
- Around line 3-9: The comment in the Policy interface block incorrectly
instructs authors to write new policies against policyv2alpha and to prefer the
v2 API (and deprecate BodyModeStream) even though the engine will not invoke v2
methods yet; update the comment for Policy (and the lines referencing Mode(),
OnRequest, OnResponse, policyv2alpha, and BodyModeStream) to instead state that
new policies should continue to target the existing Policy interface
(Mode()/OnRequest()/OnResponse()) for now, and mention policyv2alpha as a future
migration target without recommending it as the current implementation path or
deprecating BodyModeStream until the engine actually switches.

---

Duplicate comments:
In `@gateway/gateway-runtime/policy-engine/internal/executor/chain.go`:
- Around line 147-152: The current shallow copy of spec.Parameters.Raw into
params allows nested maps/slices to be shared and mutated; replace the manual
top-level copy with a deep-clone of spec.Parameters.Raw using the project's deep
clone helper (e.g., deepClone or DeepClone) so nested structures are duplicated;
update both occurrences (the block that creates params from spec.Parameters.Raw
and the other instance at the later block around the 281-286 region) to call the
helper and assign the result to params to prevent cross-request mutation.

In `@gateway/gateway-runtime/policy-engine/internal/kernel/translator.go`:
- Around line 105-142: The loop that translates policyResult.Action into
headerOps/path/bodyMutation must first normalize the newer mutation fields into
the legacy flat shape so both old and new fields are honored: detect when
policyResult.Action is a *policy.UpstreamRequestModifications and if mods.Header
(new header map), mods.QueryParametersToAdd, mods.QueryParametersToRemove, or
mods.AnalyticsHeaderFilter are present, merge them into mods.SetHeaders,
mods.AppendHeaders, mods.RemoveHeaders, mods.AddQueryParameters,
mods.RemoveQueryParameters, and DropHeadersFromAnalytics respectively before the
existing translation logic runs; update the normalization in the translator.go
block handling UpstreamRequestModifications (the code that currently reads
mods.SetHeaders/AppendHeaders/RemoveHeaders/AddQueryParameters/RemoveQueryParameters/DropHeadersFromAnalytics)
so policies using Header, QueryParametersToAdd/Remove, or AnalyticsHeaderFilter
are not no-ops.
- Around line 398-458: The translator currently ignores
DownstreamResponseModifications.Header and AnalyticsHeaderFilter so migrated
policies don't actually mutate downstream responses; update the block that
handles policy.DownstreamResponseModifications (the mods variable) to: 1)
collect/modulate header mutations from mods.Header into headerOps (like
SetHeaders/AppendHeaders/RemoveHeaders) so downstream headers are mutated, 2)
honor mods.AnalyticsHeaderFilter when computing finalizedHeaders (use it
together with dropAction when calling finalizeAnalyticsHeaders or a new helper)
and add the resulting filtered set into analyticsData["response_headers"], and
3) ensure any reference to execCtx.responseContext.ResponseHeaders/GetAll
remains the source of originalHeaders; touch symbols headerOps, headerOp,
mods.Header, mods.AnalyticsHeaderFilter, finalizeAnalyticsHeaders,
analyticsData, and execCtx.responseContext.ResponseHeaders.

In `@sdk/gateway/policy/v1alpha/context.go`:
- Around line 153-158: The GoDoc for RequestStreamContext (and likewise for
ResponseStreamContext) still references removed symbols
StreamingRequestBodyPolicy/StreamingResponseBodyPolicy; update the comment text
on RequestStreamContext and ResponseStreamContext to remove those stale names
and instead reference the current exported streaming callback types/functions
used by the SDK (replace the dead symbol names with the present streaming
callback identifiers), keeping the same explanation about read-only headers and
body arrival via the stream argument so generated docs point to valid public
symbols.

---

Nitpick comments:
In `@gateway/gateway-runtime/policy-engine/internal/testutils/policies.go`:
- Around line 69-80: The shared test fixture HeaderModifyingPolicy still
populates the deprecated SetHeaders fields, so update OnRequest and OnResponse
to use the new nested Header structs: return an
&policy.UpstreamRequestModifications{Header: &policy.HeaderActions{Set:
map[string]string{p.Key: p.Value}}} in OnRequest and an
&policy.DownstreamResponseModifications{Header: &policy.HeaderActions{Set:
map[string]string{p.Key: p.Value}}} in OnResponse so tests exercise the
migration path; adjust to reference UpstreamRequestModifications.Header,
DownstreamResponseModifications.Header and policy.HeaderActions.Set instead of
SetHeaders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 134579fc-a533-47a2-9903-4d4cd20379df

📥 Commits

Reviewing files that changed from the base of the PR and between c621181 and fb17118.

⛔ Files ignored due to path filters (1)
  • sdk/go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • .gitignore
  • gateway/gateway-runtime/policy-engine/internal/executor/chain.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_bench_test.go
  • gateway/gateway-runtime/policy-engine/internal/executor/chain_test.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/translator.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/translator_test.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/xds.go
  • gateway/gateway-runtime/policy-engine/internal/testutils/policies.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • sdk/gateway/policy/v1alpha/action.go
  • sdk/gateway/policy/v1alpha/context.go
  • sdk/gateway/policy/v1alpha/interface.go
  • sdk/gateway/policy/v2alpha/interface.go
💤 Files with no reviewable changes (3)
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/xds.go
  • gateway/gateway-runtime/policy-engine/internal/kernel/body_mode.go

Comment on lines +3 to +9
// Policy is the interface all policies must implement.
// It uses a monolithic style: Mode() declares processing requirements, and
// OnRequest/OnResponse are called by the kernel for the respective phases.
//
// New policies should be written against policyv2alpha, which uses a clean
// sub-interface model where capabilities are declared by implementing
// RequestHeaderPolicy, RequestPolicy, ResponsePolicy, etc.
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t steer policy authors to policyv2alpha as the runtime path yet.

The migration plan for this PR says v2alpha methods compile for dual-implementation, but are not invoked until the engine is updated. These comments currently tell authors to write new policies against policyv2alpha and deprecate BodyModeStream in its favor, which would push users to an API the current engine ignores.

Also applies to: 121-123

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

In `@sdk/gateway/policy/v1alpha/interface.go` around lines 3 - 9, The comment in
the Policy interface block incorrectly instructs authors to write new policies
against policyv2alpha and to prefer the v2 API (and deprecate BodyModeStream)
even though the engine will not invoke v2 methods yet; update the comment for
Policy (and the lines referencing Mode(), OnRequest, OnResponse, policyv2alpha,
and BodyModeStream) to instead state that new policies should continue to target
the existing Policy interface (Mode()/OnRequest()/OnResponse()) for now, and
mention policyv2alpha as a future migration target without recommending it as
the current implementation path or deprecating BodyModeStream until the engine
actually switches.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes in interface.go

  1. Policy interface moved to the top
  2. BodyModeStream deprecation notice
  3. Level and its constants moved up
  4. ProcessingMode fields lost their inline field comments

Copy link
Contributor

Choose a reason for hiding this comment

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

@Thushani-Jayasekera, thanks for the summary. A few notes on each point:

  1. Policy interface moved to the top — No issue there structurally, but the comment block still contains:

    New policies should be written against policyv2alpha…

    My concern stands: since the engine does not yet invoke v2alpha methods, steering authors there now can cause silent no-ops. It would be safer to soften the wording until the engine is updated — e.g., "A capability-based v2alpha interface is available for future migration; the engine currently dispatches only through this interface."

  2. BodyModeStream deprecation notice — Same concern. The deprecation comment // Deprecated: Use policyv2alpha.StreamingRequestPolicy / StreamingResponsePolicy instead. directs users to an API the runtime doesn't call yet. Consider replacing it with something like // Note: streaming chunk support is coming in a future engine release. until v2alpha dispatch is live.

  3. Level and its constants moved up — Purely structural, no concerns.

  4. ProcessingMode fields lost their inline field comments — Good catch on your end. The previous version had per-field documentation (e.g., what RequestHeaderMode vs RequestBodyMode controls). Those are useful for authors scanning the struct. Worth restoring them, for example:

type ProcessingMode struct {
    // RequestHeaderMode controls whether OnRequest is called at the header phase.
    RequestHeaderMode  HeaderProcessingMode
    // RequestBodyMode controls whether/how the request body is buffered before OnRequest.
    RequestBodyMode    BodyProcessingMode
    // ResponseHeaderMode controls whether OnResponse is called at the response header phase.
    ResponseHeaderMode HeaderProcessingMode
    // ResponseBodyMode controls whether/how the response body is buffered before OnResponse.
    ResponseBodyMode   BodyProcessingMode
}

// ResponseExecutionResult represents the result of executing all response policies in a chain
type ResponseExecutionResult struct {
Results []ResponsePolicyResult
ShortCircuited bool // true if chain stopped early due to ImmediateResponse
Copy link
Contributor Author

Choose a reason for hiding this comment

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

separates "chain completed normally" from "chain bailed early to block the request"

// OperationPath is the operation path pattern from the API definition
// (e.g., "/pets/{id}" for a parameterized path)
// This differs from RequestContext.Path which contains the actual request path
// This differs from RequestHeaderContext.Path which contains the actual request path
Copy link
Contributor Author

@Thushani-Jayasekera Thushani-Jayasekera Mar 17, 2026

Choose a reason for hiding this comment

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

Changes in context.go - Four new types added:

  1. RequestHeaderContext — header-phase context for the new RequestHeaderPolicy sub-interface
  2. ResponseHeaderContext — header-phase context for the new ResponseHeaderPolicy sub-interface
  3. StreamBody — a single streaming chunk (raw bytes + end-of-stream signal)
  4. RequestStreamContext — per-chunk context for StreamingRequestPolicy
  5. ResponseStreamContext — per-chunk context for StreamingResponsePolicy

No existing types were modified. Zero breaking changes in this file.

@@ -1,72 +1,237 @@
package policyv1alpha

// RequestAction marker interface (oneof pattern)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

 ---                                                                                                                                 
  Renamed types (breaking for existing policies)                                                                                      
                                                
  ┌────────────────────────────────────────┬──────────────────────────────────────────────┐                                           
  │                Old name                │                   New name                   │                                           
  ├────────────────────────────────────────┼──────────────────────────────────────────────┤                                           
  │ UpstreamResponseModifications (struct) │ DownstreamResponseModifications (new struct) │                                           
  └────────────────────────────────────────┴──────────────────────────────────────────────┘                                           
                                                            
  UpstreamResponseModifications now exists as a type alias pointing to DownstreamResponseModifications:                               
  type UpstreamResponseModifications = DownstreamResponseModifications
  Existing policies returning UpstreamResponseModifications{} continue to compile because it's an alias, not a new type.              
                                                                                                                                      
  ---                                                                                                                                 
  Fields renamed inside UpstreamRequestModifications (breaking for existing policies)                                                 
                                                                                     
  ┌──────────────────────────┬─────────────────────────┐                                                                              
  │        Old field         │        New field        │
  ├──────────────────────────┼─────────────────────────┤                                                                              
  │ AddQueryParameters       │ QueryParametersToAdd    │    
  ├──────────────────────────┼─────────────────────────┤                                                                              
  │ RemoveQueryParameters    │ QueryParametersToRemove │
  ├──────────────────────────┼─────────────────────────┤                                                                              
  │ DropHeadersFromAnalytics │ AnalyticsHeaderFilter   │    
  └──────────────────────────┴─────────────────────────┘
                                          
  The old field names are kept as deprecated fields alongside the new ones, so existing policies still compile. But any policy setting
   the old names will get deprecation notices.                                                                                        
  
  ---                                                                                                                                 
  New fields added to UpstreamRequestModifications          
                                                                                                                                      
  - Header *UpstreamRequestHeaderModifications — sub-struct for header mutations
  - AnalyticsHeaderFilter DropHeaderAction — renamed version of DropHeadersFromAnalytics
                                                                                                                                      
  New fields added to DownstreamResponseModifications (was UpstreamResponseModifications)
                                                                                                                                      
  - Header *DownstreamResponseHeaderModifications — sub-struct for header mutations
  - AnalyticsHeaderFilter DropHeaderAction — renamed version of DropHeadersFromAnalytics                                              
                                                            
  New field added to ImmediateResponse                                                                                                
                                                            
  - AnalyticsHeaderFilter DropHeaderAction — renamed version of DropHeadersFromAnalytics                                              
                                          
  ---                                                                                                                                 
  Entirely new types added                                                                                                            
                          
  - RequestHeaderAction — sealed interface for header-phase request actions                                                           
  - ResponseHeaderAction — sealed interface for header-phase response actions
  - UpstreamRequestHeaderModifications — concrete type implementing RequestHeaderAction
  - DownstreamResponseHeaderModifications — concrete type implementing ResponseHeaderAction
  - RequestChunkAction — returned by streaming request chunk handlers                                                                 
  - ResponseChunkAction — returned by streaming response chunk handlers                                                               
                                                                                                                                      
  ---                                                                                                                                 
  New compile-time interface checks added                                                                                             
                                                            
  var (                                                                                                                               
      _ RequestHeaderAction  = UpstreamRequestHeaderModifications{}
      _ RequestHeaderAction  = ImmediateResponse{}                                                                                    
      _ ResponseHeaderAction = DownstreamResponseHeaderModifications{}
      _ ResponseHeaderAction = ImmediateResponse{}                                                                                    
      _ RequestAction        = UpstreamRequestModifications{}
      _ RequestAction        = ImmediateResponse{}
      _ ResponseAction       = DownstreamResponseModifications{}                                                                      
      _ ResponseAction       = ImmediateResponse{}                                                                                    
  )                                               

@Thushani-Jayasekera Thushani-Jayasekera changed the title feat(sdk): Introduce phase-specific policy sub-interfaces and backward-compatibility shims (v0.5.0) feat(sdk): Introduce phase-specific policy sub-interfaces and migrate to v2alpha (SDK v0.5.0) Mar 18, 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.

1 participant