Skip to content

Gateway Manifest Retrieval Support#1369

Merged
slahirucd7 merged 12 commits intowso2:mainfrom
slahirucd7:main
Mar 20, 2026
Merged

Gateway Manifest Retrieval Support#1369
slahirucd7 merged 12 commits intowso2:mainfrom
slahirucd7:main

Conversation

@slahirucd7
Copy link
Copy Markdown
Contributor

@slahirucd7 slahirucd7 commented Mar 16, 2026

Purpose

Overview

Implements an async mechanism for the Platform API to request and retrieve the list of installed policies (gateway manifest) from a connected gateway controller. This enables Choreo product APIM to discover which policies are available on a given gateway.

How It Works

  1. When connecting the gateway to the platform API, gateway controller sends the gateway manifest to the platform API via a HTTP POST request.
  2. After receiving the manifest platform API processes the request payload. In the gateways table, manifest column is populated as a JSON object.
  3. The arranged json object includes, policy details.
    • For the policy hub managed policies name, version, description will be maintained
    • Custom policies will maintain the policy definition additional to the above fields.
  4. When platform API's gateways/{gwID}/manifest endpoint is called the stored JSON will be returned to the client.

Goals

Describe what solutions this feature or fix introduces to address the problems outlined above.

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI. Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

User stories

Summary of user stories addressed by this change>

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Summary by CodeRabbit

  • New Features

    • Gateway controllers can push and the platform can store/retrieve gateway policy manifests.
    • New APIs to submit manifests (internal) and to fetch a gateway's installed policies (public).
    • Policies now indicate whether they are built-in or customer-provided.
  • Chores

    • Improved detection of locally developed/custom policies and build-lock handling.

@coderabbitai
Copy link
Copy Markdown
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

Adds gateway manifest sync: the gateway controller classifies policies as wso2 or customer, pushes a manifest to the control plane on connect, and the platform API stores/retrieves manifests via new internal POST and public GET endpoints backed by a new manifest column in the gateways table.

Changes

Cohort / File(s) Summary
API Schema & Generated Types
gateway/gateway-controller/api/openapi-management.yaml, gateway/gateway-controller/pkg/api/management/generated.go
Added managedBy (enum wso2,customer) to PolicyDefinition schema and regenerated Go types (PolicyDefinition.ManagedBy).
Controller: push manifest on connect & HTTP client
gateway/gateway-controller/pkg/controlplane/client.go, gateway/gateway-controller/pkg/utils/api_utils.go
On Connect, controller spawns goroutine to build manifest and POST to control plane with retries/backoff. Added ManifestPolicyEntry and APIUtilsService.PushGatewayManifest.
Controller: classify local policies & build-lock
gateway/gateway-controller/cmd/controller/main.go, gateway/gateway-controller/pkg/config/config.go, gateway/gateway-controller/pkg/utils/policy_loader.go, gateway/gateway-builder/templates/Dockerfile.gateway-controller.tmpl
Added BuildLockPath config, PolicyLoader.LocalPolicyNames() to detect local policies from build-lock.yaml, mark definitions customer or wso2; Dockerfile copies build-lock.yaml.
Platform DB schema
platform-api/src/internal/database/schema.postgres.sql, platform-api/src/internal/database/schema.sql, platform-api/src/internal/database/schema.sqlite.sql
Added nullable manifest column (JSONB/TEXT) to gateways table across DB variants.
Platform API: internal ingestion & public retrieval endpoints
platform-api/src/internal/handler/gateway_internal.go, platform-api/src/internal/handler/gateway.go, platform-api/src/resources/gateway-internal-api.yaml, platform-api/src/resources/openapi.yaml
Added internal POST /api/internal/v1/gateways/{gatewayId}/manifest (API-key auth) and public GET /api/v1/gateways/{gatewayId}/manifest; new OpenAPI schemas GatewayPolicyDefinition and ManifestSyncResponse.
Platform API: persistence, service, wiring
platform-api/src/internal/repository/gateway.go, platform-api/src/internal/repository/interfaces.go, platform-api/src/internal/service/gateway.go, platform-api/src/internal/server/server.go
Added UpdateGatewayManifest / GetGatewayManifest repo methods and interface entries; GatewayService gains manifest methods (ReceiveGatewayManifest, GetStoredManifest), new Manifest types, and dependency injection updated to include gatewayEventsService.
Platform config
platform-api/src/config/config.go
Appended /api/internal/v1/gateways to JWT.SkipPaths default.
Minor generated/regen changes
gateway/gateway-controller/pkg/api/management/generated.go (already included above)
Swagger/OpenAPI payload updated to include new managedBy property in embedded spec.

Sequence Diagram

sequenceDiagram
    participant GC as Gateway Controller
    participant CP as Control Plane API
    participant DB as Platform Database
    participant Client as External Client

    GC->>CP: Establish connection
    CP-->>GC: Connected

    rect rgba(100, 150, 200, 0.5)
        Note over GC,CP: Manifest push on connect
        GC->>GC: Build manifest (policies with ManagedBy)
        GC->>CP: POST /api/internal/v1/gateways/{gatewayId}/manifest
        CP->>CP: ReceiveGatewayManifest (auth, validate)
        CP->>DB: UpdateGatewayManifest (store JSON)
        DB-->>CP: 200/OK
        CP-->>GC: 204 No Content
    end

    rect rgba(150, 200, 100, 0.5)
        Note over Client,CP: Manifest retrieval
        Client->>CP: GET /api/v1/gateways/{gatewayId}/manifest
        CP->>DB: GetGatewayManifest
        DB-->>CP: manifest bytes
        CP-->>Client: 200 OK + ManifestSyncResponse
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

"A rabbit hopped through code so spry,
Marking policies low and high,
Pushed manifests on connect's chime,
Stored them safe, in DB and time,
WSO2 and custom—now they vie 🐇✨"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides a clear 'Purpose' section with implementation overview, but most template sections (Goals, Approach, User stories, Documentation, Automation tests, Security checks, Samples, Related PRs, Test environment) lack substantive content. Complete remaining template sections: Goals, Approach, User stories, Documentation, Automation tests, and Security checks. At minimum, explain how the solution addresses the stated purpose and confirm security compliance.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Gateway Manifest Retrieval Support' is concise and directly reflects the primary feature: implementing manifest retrieval capability for gateways.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

Copy link
Copy Markdown
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: 12

🧹 Nitpick comments (3)
platform-api/src/internal/handler/gateway_internal.go (1)

652-655: Consider moving gatewayGroup out of the mcpProxyGroup block for readability.

This works, but the current nesting makes route ownership harder to scan.

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

In `@platform-api/src/internal/handler/gateway_internal.go` around lines 652 -
655, The gateway route registration is currently declared inside the
mcpProxyGroup block which makes ownership harder to scan; move the declaration
of gatewayGroup := r.Group("/api/internal/v1/gateways") and its POST
registration gatewayGroup.POST("/:gatewayId/manifest", h.ReceiveGatewayManifest)
out of the mcpProxyGroup closure so it is defined at the same top-level router
scope as other API groups, keeping the same group path and handler
(ReceiveGatewayManifest) but not nested inside mcpProxyGroup.
gateway/gateway-controller/pkg/utils/api_utils.go (1)

602-609: Reuse the shared HTTP client for manifest POSTs.

NewAPIUtilsService already builds s.client with the service timeout, pool tuning, and TLS settings. Spinning up a fresh client/transport here drops those settings and defeats connection reuse on the retrying manifest-sync path.

Proposed refactor
-	client := &http.Client{
-		Timeout: s.config.Timeout,
-		Transport: &http.Transport{
-			TLSClientConfig: &tls.Config{
-				InsecureSkipVerify: s.config.InsecureSkipVerify,
-			},
-		},
-	}
-
 	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
 	if err != nil {
 		return fmt.Errorf("failed to create manifest request: %w", err)
 	}
@@
-	resp, err := client.Do(req)
+	resp, err := s.client.Do(req)

Also applies to: 624-624

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

In `@gateway/gateway-controller/pkg/utils/api_utils.go` around lines 602 - 609,
The code creates a new local http.Client (client := &http.Client{...}) for
manifest POSTs which bypasses the shared s.client configured in
NewAPIUtilsService and prevents connection reuse; replace the local client
creation with using the existing s.client (and ensure any TLS/insecure and
Timeout behavior uses s.config via the shared s.client), and remove duplicate
client instantiation at the other occurrence (the second instance noted around
the other diff) so manifest POSTs reuse s.client and its transport/pool tuning.
platform-api/src/resources/openapi.yaml (1)

4665-4684: Model ready state explicitly so policies is guaranteed.

ManifestSyncResponse currently permits status: ready without policies. That weakens the contract for clients polling this endpoint.

♻️ Proposed schema refinement
     ManifestSyncResponse:
-      type: object
-      required:
-        - status
-      properties:
-        status:
-          type: string
-          enum: [pending, ready, failed]
-          description: |
-            Current state of the manifest sync job.
-            - `pending`: Request sent to gateway controller, waiting for response.
-            - `ready`: Manifest received and policies are available.
-            - `failed`: Request could not be delivered (gateway offline or error).
-          example: "ready"
-        policies:
-          type: array
-          description: Custom policies installed on the gateway. Only present when status is `ready`.
-          items:
-            $ref: '#/components/schemas/GatewayPolicyDefinition'
+      oneOf:
+        - type: object
+          required: [status]
+          properties:
+            status:
+              type: string
+              enum: [pending, failed]
+        - type: object
+          required: [status, policies]
+          properties:
+            status:
+              type: string
+              enum: [ready]
+            policies:
+              type: array
+              items:
+                $ref: '#/components/schemas/GatewayPolicyDefinition'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/resources/openapi.yaml` around lines 4665 - 4684,
ManifestSyncResponse currently allows status: "ready" without guaranteeing
policies; change the schema to use a oneOf with two explicit variants to enforce
the contract: one variant where status is enum ["ready"] and policies is
required (items $ref to GatewayPolicyDefinition), and a second variant where
status is enum ["pending","failed"] and policies is omitted (or optional).
Update the ManifestSyncResponse definition (referencing ManifestSyncResponse,
status, policies, and GatewayPolicyDefinition) so clients seeing status: "ready"
can rely on policies being present.
🤖 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-controller/pkg/controlplane/client.go`:
- Around line 2717-2719: The retry backoff uses time.Sleep(attempt*2s) which
blocks cancellation; change it to a context-aware wait by replacing the direct
sleep with a select that waits on time.After(backoff) and on ctx.Done(), so the
loop (where attempt and maxRetries are used) returns promptly when the provided
context is cancelled. Ensure the function uses the surrounding context variable
(ctx) — or add one if missing — and on ctx.Done() break/return with the
appropriate error instead of sleeping.
- Around line 2674-2708: The manifest push does not propagate the incoming
correlation ID from the event to the downstream service; update the gateway
manifest handling so the correlation ID (event["correlationId"]) is captured and
passed into c.apiUtilsService.PushGatewayManifest (modify PushGatewayManifest
signature and all callers to accept a correlationId parameter), and update the
internal manifest POST contract/service handler to accept and validate the
correlation ID header/body so the request/response can be strictly correlated
(refer to PushGatewayManifest and the gateway manifest handling block that reads
event["correlationId"]).

In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 579-587: ManifestPolicyEntry.IsCustomPolicy is a nullable *bool
which is being marshalled as null when omitted; ensure callers normalize it to a
real boolean before sending by setting ManifestPolicyEntry.IsCustomPolicy to a
non-nil pointer to false when it's nil (or change the struct to use a
non-pointer bool and update usages accordingly), and apply the same
normalization where ManifestPolicyEntry instances are prepared for POST (also
cover the other occurrences referenced around the same type).

In `@platform-api/src/config/config.go`:
- Around line 132-134: The ManifestSyncTimeoutSecs and ManifestReadyTTLSecs
fields on the config struct must be validated to be positive to avoid immediate
expiry/retrigger loops; update the config initialization/validation (where you
parse/env-load the config) to check ManifestSyncTimeoutSecs > 0 and
ManifestReadyTTLSecs > 0 (referencing the ManifestSyncTimeoutSecs and
ManifestReadyTTLSecs struct fields) and either return a descriptive error or
replace non-positive values with safe defaults (e.g., 30 and 60) before using
them; ensure the validation happens immediately after env parsing so callers
never observe non-positive values.

In `@platform-api/src/internal/handler/gateway_internal.go`:
- Around line 586-591: When handling h.gatewayService.VerifyToken(apiKey),
distinguish auth failures from backend/internal errors: if errors.Is(err,
auth.ErrInvalidCredentials) or your existing helper (e.g.
auth.IsUnauthorized(err) or an auth helper used elsewhere) indicates an
authentication error, keep the 401 response using h.slogger.Warn and
utils.NewErrorResponse; otherwise treat it as a server error—log with
h.slogger.Error including the error details and return
http.StatusInternalServerError with an appropriate utils.NewErrorResponse
message; update the VerifyToken error branch to use errors.Is or the existing
auth helper to decide between 401 and 500.

In `@platform-api/src/internal/handler/gateway.go`:
- Around line 501-510: The error handling after calling
gatewayService.SyncManifest should distinguish client-side invalid gateway IDs
from server errors: update the error branch around SyncManifest(gatewayId,
orgId) to check for an "invalid gateway id" or UUID parse error (e.g.,
err.Error() contains "invalid" or "invalid UUID" or a specific validation error
returned by SyncManifest) and return HTTP 400 with a NewErrorResponse(400, "Bad
Request", "Invalid gateway ID") for that case; keep the existing check for
"gateway not found" returning 404 and fall back to 500 for all other errors.
Ensure you modify the error-handling block in gateway.go where
gatewayService.SyncManifest is called so the correct status codes are returned.

In `@platform-api/src/internal/repository/gateway.go`:
- Around line 427-447: UpdateManifestJob currently returns nil even when no rows
are affected; modify GatewayRepo.UpdateManifestJob to inspect the sql.Result
returned by r.db.Exec(r.db.Rebind(query), args...), check res.RowsAffected(),
and if it is 0 return a not-found error (e.g., a repository-level ErrNotFound or
sql.ErrNoRows) instead of nil; also preserve and return any Exec error
encountered before checking RowsAffected.
- Around line 459-461: The code in gateway.go incorrectly converts sql.ErrNoRows
into (nil, nil, nil), hiding a real not-found condition; change the branch that
tests errors.Is(err, sql.ErrNoRows) so it returns an explicit not-found error
instead of nils (for example return nil, nil, err or wrap err with context like
fmt.Errorf("gateway not found: %w", err) or use your repository sentinel
ErrNotFound), keeping the rest of the error handling unchanged; locate the
conditional that checks errors.Is(err, sql.ErrNoRows) in gateway.go and replace
the nil return with a proper error return so callers can distinguish “not found”
from success.

In `@platform-api/src/internal/service/gateway_events.go`:
- Around line 1195-1202: Validate that the correlationID is non-empty (and
ideally non-whitespace) at the start of BroadcastGatewayManifestRequestEvent; if
invalid, return an error immediately instead of broadcasting. In practice, add a
simple check in GatewayEventsService.BroadcastGatewayManifestRequestEvent that
trims/validates correlationID and returns a descriptive error (e.g.,
fmt.Errorf("missing correlationID")) before constructing dto.GatewayEventDTO and
publishing the event so the method never emits events without a valid
CorrelationID.

In `@platform-api/src/internal/service/gateway.go`:
- Around line 203-206: The loop over policies currently filters out non-custom
policies (for _, p := range policies { if !p.IsCustomPolicy { continue } }) and
does not preserve or return the IsCustomPolicy flag, preventing callers from
filtering; update the iteration and the policy-mapping logic (the code that
converts each p into the returned policy object/DTO) to include a boolean field
IsCustomPolicy (or propagate p.IsCustomPolicy) into the returned policy
representation instead of dropping non-custom entries or stripping the flag, and
ensure the alternative branch preserves and returns policies with
IsCustomPolicy=false so callers can perform caller-side filtering (apply same
change for the other similar loop handling policies at the other location).
- Around line 222-228: The code currently mutates s.manifestStore[gatewayID]
under s.manifestMu before calling s.gatewayRepo.UpdateManifestJob(gatewayID,
"ready"), which can leave the in-memory cache inconsistent if the DB update
fails; change the sequence so you either (a) perform UpdateManifestJob first and
only set s.manifestStore[gatewayID]=custom on success, or (b) if you must set
the cache first, capture the previous value (and whether it existed) before
assigning, then call UpdateManifestJob and if it returns an error revert
s.manifestStore to the previous state inside the same s.manifestMu lock to avoid
races; ensure you reference manifestStore, manifestMu,
gatewayRepo.UpdateManifestJob, gatewayID and custom when locating the change.
- Around line 170-175: The code currently returns a ManifestJob with Status
"ready" when DB status is "ready" even if s.manifestStore[gatewayID] is missing;
update the block that checks status == "ready" (around ManifestJob,
manifestStore, manifestMu) to verify the manifestStore contains a non-nil entry
for gatewayID before returning ready; if the entry is absent or nil, do not
return Status "ready"—either trigger/queue a manifest sync/load for that gateway
(call the existing sync/load routine) and return a non-ready job (or an error)
so the caller re-triggers sync, and ensure you still use s.manifestMu
RLock/RUnlock when reading s.manifestStore[gatewayID].

---

Nitpick comments:
In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 602-609: The code creates a new local http.Client (client :=
&http.Client{...}) for manifest POSTs which bypasses the shared s.client
configured in NewAPIUtilsService and prevents connection reuse; replace the
local client creation with using the existing s.client (and ensure any
TLS/insecure and Timeout behavior uses s.config via the shared s.client), and
remove duplicate client instantiation at the other occurrence (the second
instance noted around the other diff) so manifest POSTs reuse s.client and its
transport/pool tuning.

In `@platform-api/src/internal/handler/gateway_internal.go`:
- Around line 652-655: The gateway route registration is currently declared
inside the mcpProxyGroup block which makes ownership harder to scan; move the
declaration of gatewayGroup := r.Group("/api/internal/v1/gateways") and its POST
registration gatewayGroup.POST("/:gatewayId/manifest", h.ReceiveGatewayManifest)
out of the mcpProxyGroup closure so it is defined at the same top-level router
scope as other API groups, keeping the same group path and handler
(ReceiveGatewayManifest) but not nested inside mcpProxyGroup.

In `@platform-api/src/resources/openapi.yaml`:
- Around line 4665-4684: ManifestSyncResponse currently allows status: "ready"
without guaranteeing policies; change the schema to use a oneOf with two
explicit variants to enforce the contract: one variant where status is enum
["ready"] and policies is required (items $ref to GatewayPolicyDefinition), and
a second variant where status is enum ["pending","failed"] and policies is
omitted (or optional). Update the ManifestSyncResponse definition (referencing
ManifestSyncResponse, status, policies, and GatewayPolicyDefinition) so clients
seeing status: "ready" can rely on policies being present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8afddb92-c798-44d2-b2d1-79a8f458ab82

📥 Commits

Reviewing files that changed from the base of the PR and between 4b41401 and 2cd49f4.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • gateway/gateway-controller/api/openapi-management.yaml
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/controlplane/client.go
  • gateway/gateway-controller/pkg/controlplane/events.go
  • gateway/gateway-controller/pkg/utils/api_utils.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/database/schema.sqlite.sql
  • platform-api/src/internal/handler/gateway.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/gateway.go
  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/resources/openapi.yaml

Comment thread gateway/gateway-controller/pkg/controlplane/client.go Outdated
Comment thread gateway/gateway-controller/pkg/controlplane/client.go
Comment thread gateway/gateway-controller/pkg/utils/api_utils.go Outdated
Comment thread platform-api/src/config/config.go Outdated
Comment thread platform-api/src/internal/handler/gateway_internal.go Outdated
Comment thread platform-api/src/internal/repository/gateway.go
Comment thread platform-api/src/internal/service/gateway_events.go Outdated
Comment thread platform-api/src/internal/service/gateway.go Outdated
Comment thread platform-api/src/internal/service/gateway.go
Comment thread platform-api/src/internal/service/gateway.go Outdated
Copy link
Copy Markdown
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: 5

♻️ Duplicate comments (3)
gateway/gateway-controller/pkg/controlplane/client.go (1)

2702-2715: ⚠️ Potential issue | 🟠 Major

Manifest push retry path is not shutdown-aware and can delay stop/reconnect cleanup.

The goroutine does not observe cancellation before retries, and backoff wait is blocking. Since Stop() waits on the waitgroup, this path can keep shutdown waiting unnecessarily.

💡 Suggested fix
 func (c *Client) pushGatewayManifestOnConnect(gatewayID string) {
@@
 	for attempt := 1; attempt <= maxRetries; attempt++ {
+		select {
+		case <-c.ctx.Done():
+			c.logger.Info("Aborting gateway manifest push: client context cancelled",
+				slog.String("gateway_id", gatewayID),
+			)
+			return
+		case <-c.stopChan:
+			c.logger.Info("Aborting gateway manifest push: stop signal received",
+				slog.String("gateway_id", gatewayID),
+			)
+			return
+		default:
+		}
+
 		pushErr = c.apiUtilsService.PushGatewayManifest(gatewayID, policies)
 		if pushErr == nil {
 			break
 		}
@@
 		if attempt < maxRetries {
-			time.Sleep(time.Duration(attempt) * 2 * time.Second)
+			select {
+			case <-time.After(time.Duration(attempt) * 2 * time.Second):
+			case <-c.ctx.Done():
+				return
+			case <-c.stopChan:
+				return
+			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-controller/pkg/controlplane/client.go` around lines 2702 -
2715, The retry loop around c.apiUtilsService.PushGatewayManifest is not
observing cancellation and uses blocking time.Sleep, which can delay shutdown
via Stop()/waitgroup; update the retry logic in the function containing this
loop to accept/propagate a context (or use the existing ctx), check ctx.Done()
before each retry and when waiting, replace time.Sleep with a select that waits
on time.After(backoff) and ctx.Done(), and if ctx is canceled break/return
immediately; also prefer passing ctx into PushGatewayManifest if that API
supports it (or wrap the call in a select to abort if ctx is canceled) so
shutdown/reconnect can proceed promptly.
platform-api/src/internal/repository/gateway.go (2)

425-429: ⚠️ Potential issue | 🟠 Major

Detect “gateway not found” when manifest update affects zero rows.

UpdateGatewayManifest can return success even when the gateway row does not exist. Please check RowsAffected() and return a not-found error when it is zero.

Suggested fix
 func (r *GatewayRepo) UpdateGatewayManifest(gatewayID string, manifest []byte) error {
 	query := `UPDATE gateways SET manifest = ? WHERE uuid = ?`
-	_, err := r.db.Exec(r.db.Rebind(query), string(manifest), gatewayID)
-	return err
+	result, err := r.db.Exec(r.db.Rebind(query), string(manifest), gatewayID)
+	if err != nil {
+		return err
+	}
+	rows, err := result.RowsAffected()
+	if err != nil {
+		return err
+	}
+	if rows == 0 {
+		return errors.New("gateway not found")
+	}
+	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/repository/gateway.go` around lines 425 - 429, The
UpdateGatewayManifest function currently ignores whether the UPDATE actually
modified a row; change it to capture the sql.Result from r.db.Exec in
UpdateGatewayManifest, call RowsAffected() on that result, and if it returns 0
return a not-found error (use your repository's existing NotFound or
gatewayNotFound error type/message) instead of nil; otherwise return the
original err (or nil) as appropriate.

437-440: ⚠️ Potential issue | 🟠 Major

Do not treat missing gateway rows as “no manifest yet.”

Mapping sql.ErrNoRows to (nil, nil) hides a real missing-gateway condition. Keep raw == nil as “no manifest yet,” but return a not-found error for missing rows.

Suggested fix
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
-			return nil, nil
+			return nil, errors.New("gateway not found")
 		}
 		return nil, err
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/repository/gateway.go` around lines 437 - 440, The
code currently maps sql.ErrNoRows to (nil, nil), which hides a missing-gateway
condition; change the error handling in the function that queries the gateway
(the block checking "if err != nil" and "errors.Is(err, sql.ErrNoRows)") to
return a not-found error instead of (nil, nil) when errors.Is(err,
sql.ErrNoRows) is true (e.g., return repository.ErrNotFound or the package's
standard NotFound error), while preserving the existing semantics where raw ==
nil still denotes “no manifest yet.” Ensure you update the error return path in
that same function (the code handling "err" and "raw") so callers can
distinguish missing DB rows from a nil manifest.
🧹 Nitpick comments (1)
platform-api/src/internal/service/gateway.go (1)

41-43: Update stale comment about IsCustomPolicy behavior.

The comment says IsCustomPolicy is not stored/returned, but the current implementation does both. Please align the comment with actual behavior.

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

In `@platform-api/src/internal/service/gateway.go` around lines 41 - 43, The
struct comment for GatewayPolicyInput is stale about IsCustomPolicy; update the
comment above the GatewayPolicyInput type to reflect that IsCustomPolicy is
currently persisted and returned (not just used for filtering). Edit the
documentation text to remove the "is not stored or returned" clause and instead
state that IsCustomPolicy is included in storage/response, keeping the rest of
the comment about raw policy data and any filtering behavior accurate.
🤖 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-controller/pkg/controlplane/client.go`:
- Around line 347-352: The current code only calls
c.pushGatewayManifestOnConnect(gatewayID) on connect, but the manifest-sync
protocol requires responding to "gateway.manifest.request" (including timeout
re-triggers); update the control-plane client to subscribe/handle the
"gateway.manifest.request" message and invoke the same push logic from that
handler instead of relying solely on connect-time goroutine. Specifically, wire
the handler for "gateway.manifest.request" to call
c.pushGatewayManifestOnConnect(gatewayID) (or extract the push logic into a
shared method used by both the connect path and the request handler) and
implement the timeout/re-trigger behavior in that handler so pending polls are
satisfied without waiting for a reconnect.

In `@platform-api/src/internal/service/gateway.go`:
- Around line 112-116: The mapping of the input policy to the stored type drops
the Description field; update the construction of GatewayPolicyDefinition inside
the manifest entry creation (the block that builds entry :=
GatewayPolicyDefinition{ ... }) to set Description: p.Description (or the
appropriate source field) so the stored GatewayPolicyDefinition retains the
policy description along with Name, Version, and IsCustomPolicy.
- Around line 91-97: The current handler in gateway.go collapses repository
errors from s.gatewayRepo.GetByUUID into a generic "gateway not found"; change
it to first handle and return the repository error (or wrap it) when err != nil
so real failures surface, and only treat gateway == nil or
gateway.OrganizationID != orgID as not-found cases; specifically update the
gatewayRepo.GetByUUID error branch to return the repo error (e.g., wrap with
context) and keep the subsequent nil/ownership checks returning the not-found
error.

In `@platform-api/src/resources/gateway-internal-api.yaml`:
- Around line 829-833: The OpenAPI schema's policies array in
gateway-internal-api.yaml is unbounded which risks oversized payloads; update
the `policies` array schema to include a `maxItems` (e.g., 50 or another
team-agreed limit) and add limits on nested objects (e.g., `maxProperties` or
`additionalProperties: false`) for the policy item object to constrain request
size and memory usage; apply the same changes to the other occurrence referenced
(the second `policies` schema around lines 853-860) so both array definitions
enforce the new limits.

In `@platform-api/src/resources/openapi.yaml`:
- Around line 1743-1767: Update the GET /gateways/{gatewayId}/manifest
(operationId GetGatewayManifest) OpenAPI contract so clients can implement
trigger-and-poll: add a required "status" property to the ManifestSyncResponse
schema with an enum of at least "pending" and "ready", and add a "manifestSync"
(or "manifest_sync") object to ManifestSyncResponse that contains sync metadata
(e.g., lastAttemptTime, lastSuccessTime, maybe a syncVersion or error message)
and, when status == "ready", the manifest payload (or a reference to it). Ensure
ManifestSyncResponse's required list includes "status" (and "manifestSync" if
you choose), and document the semantics of the enum values so consumers can
implement the pending→ready poll loop.

---

Duplicate comments:
In `@gateway/gateway-controller/pkg/controlplane/client.go`:
- Around line 2702-2715: The retry loop around
c.apiUtilsService.PushGatewayManifest is not observing cancellation and uses
blocking time.Sleep, which can delay shutdown via Stop()/waitgroup; update the
retry logic in the function containing this loop to accept/propagate a context
(or use the existing ctx), check ctx.Done() before each retry and when waiting,
replace time.Sleep with a select that waits on time.After(backoff) and
ctx.Done(), and if ctx is canceled break/return immediately; also prefer passing
ctx into PushGatewayManifest if that API supports it (or wrap the call in a
select to abort if ctx is canceled) so shutdown/reconnect can proceed promptly.

In `@platform-api/src/internal/repository/gateway.go`:
- Around line 425-429: The UpdateGatewayManifest function currently ignores
whether the UPDATE actually modified a row; change it to capture the sql.Result
from r.db.Exec in UpdateGatewayManifest, call RowsAffected() on that result, and
if it returns 0 return a not-found error (use your repository's existing
NotFound or gatewayNotFound error type/message) instead of nil; otherwise return
the original err (or nil) as appropriate.
- Around line 437-440: The code currently maps sql.ErrNoRows to (nil, nil),
which hides a missing-gateway condition; change the error handling in the
function that queries the gateway (the block checking "if err != nil" and
"errors.Is(err, sql.ErrNoRows)") to return a not-found error instead of (nil,
nil) when errors.Is(err, sql.ErrNoRows) is true (e.g., return
repository.ErrNotFound or the package's standard NotFound error), while
preserving the existing semantics where raw == nil still denotes “no manifest
yet.” Ensure you update the error return path in that same function (the code
handling "err" and "raw") so callers can distinguish missing DB rows from a nil
manifest.

---

Nitpick comments:
In `@platform-api/src/internal/service/gateway.go`:
- Around line 41-43: The struct comment for GatewayPolicyInput is stale about
IsCustomPolicy; update the comment above the GatewayPolicyInput type to reflect
that IsCustomPolicy is currently persisted and returned (not just used for
filtering). Edit the documentation text to remove the "is not stored or
returned" clause and instead state that IsCustomPolicy is included in
storage/response, keeping the rest of the comment about raw policy data and any
filtering behavior accurate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4bbbac0f-dea5-4559-adfb-d1e0fd23fc0b

📥 Commits

Reviewing files that changed from the base of the PR and between 2cd49f4 and a22b427.

📒 Files selected for processing (11)
  • gateway/gateway-controller/pkg/controlplane/client.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/database/schema.sqlite.sql
  • platform-api/src/internal/handler/gateway.go
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/service/gateway.go
  • platform-api/src/resources/gateway-internal-api.yaml
  • platform-api/src/resources/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • platform-api/src/config/config.go
  • platform-api/src/internal/database/schema.postgres.sql

Comment thread platform-api/src/internal/service/gateway.go
Comment thread platform-api/src/internal/service/gateway.go
Comment thread platform-api/src/resources/gateway-internal-api.yaml
Comment thread platform-api/src/resources/openapi.yaml
Copy link
Copy Markdown
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 (5)
platform-api/src/resources/openapi.yaml (1)

1970-1994: ⚠️ Potential issue | 🟠 Major

Manifest sync contract is still underspecified for trigger-and-poll clients.

The path is exposed as /manifest, and ManifestSyncResponse does not require a sync status (pending/ready). That prevents clients from implementing the documented polling flow deterministically.

Suggested contract patch
-  /gateways/{gatewayId}/manifest:
+  /gateways/{gatewayId}/manifest-sync:
     get:
-      summary: Get gateway policy manifest
+      summary: Trigger/poll gateway policy manifest sync
       ...
       responses:
         '200':
-          description: Gateway policy manifest
+          description: Manifest sync state and payload (when ready)
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ManifestSyncResponse'

    ManifestSyncResponse:
      type: object
+      required:
+        - status
      properties:
+        status:
+          type: string
+          enum: [pending, ready]
+          description: Current manifest synchronization state.
        policies:
          type: array
+          description: Present when status is `ready`.
          items:
            $ref: '#/components/schemas/GatewayPolicyDefinition'

Also applies to: 5109-5119

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

In `@platform-api/src/resources/openapi.yaml` around lines 1970 - 1994, The GET
operation GetGatewayManifest at path /gateways/{gatewayId}/manifest currently
returns ManifestSyncResponse but lacks a deterministic sync status for
trigger-and-poll clients; update the ManifestSyncResponse schema to include a
required "status" property (enum: "pending" | "ready" | "error") and ensure any
controller code that produces this response sets the status accordingly so
clients can poll until status == "ready"; also update the OpenAPI responses/docs
to indicate when "pending" is returned and document any retry/polling semantics
for GetGatewayManifest.
platform-api/src/internal/service/gateway.go (2)

112-116: ⚠️ Potential issue | 🟡 Minor

Preserve Description when mapping incoming manifest policies.

GatewayPolicyInput.Description is dropped during conversion, so stored manifest entries lose metadata.

Proposed fix
 		entry := GatewayPolicyDefinition{
 			Name:           p.Name,
 			Version:        p.Version,
+			Description:    p.Description,
 			IsCustomPolicy: p.IsCustomPolicy,
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 112 - 116, The
mapping from incoming policies is dropping the Description field: when
constructing a GatewayPolicyDefinition from the input (currently building entry
with Name, Version, IsCustomPolicy), include the Description by adding
Description: p.Description so GatewayPolicyDefinition preserves
GatewayPolicyInput.Description during conversion and stored manifest entries
retain their metadata.

91-94: ⚠️ Potential issue | 🟠 Major

Do not collapse repository failures into “gateway not found.”

This branch hides real repository errors and makes diagnostics/HTTP mapping inaccurate.

Proposed fix
 	gateway, err := s.gatewayRepo.GetByUUID(gatewayID)
-	if err != nil || gateway == nil {
+	if err != nil {
+		return nil, fmt.Errorf("failed to get gateway: %w", err)
+	}
+	if gateway == nil {
 		return nil, errors.New("gateway not found")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 91 - 94, The code
currently collapses repository errors into a generic "gateway not found"; change
the logic in the GetByUUID handling so that if s.gatewayRepo.GetByUUID returns a
non-nil err you return or wrap that err (preserving the original repository
error for accurate diagnostics/HTTP mapping), and only return the "gateway not
found" error when err == nil && gateway == nil; update the return path around
the call to s.gatewayRepo.GetByUUID to preserve repository errors (or annotate
them with context) rather than replacing them with the not-found message.
gateway/gateway-controller/pkg/utils/api_utils.go (1)

579-587: ⚠️ Potential issue | 🟠 Major

Avoid emitting "isCustomPolicy": null in manifest payloads.

IsCustomPolicy is nullable but serialized as a required key, so nil values become null in JSON. That can break the boolean contract and downstream filtering semantics.

Proposed fix
 type ManifestPolicyEntry struct {
 	Name             string                 `json:"name"`
 	Version          string                 `json:"version"`
 	Description      *string                `json:"description,omitempty"`
 	Parameters       map[string]interface{} `json:"parameters,omitempty"`
 	SystemParameters map[string]interface{} `json:"systemParameters,omitempty"`
-	IsCustomPolicy   *bool                   `json:"isCustomPolicy"`
+	IsCustomPolicy   bool                   `json:"isCustomPolicy"`
 }

Also applies to: 593-597

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

In `@gateway/gateway-controller/pkg/utils/api_utils.go` around lines 579 - 587,
ManifestPolicyEntry currently serializes IsCustomPolicy as a required key,
emitting "isCustomPolicy": null when the pointer is nil; update the struct tag
on ManifestPolicyEntry.IsCustomPolicy to `json:"isCustomPolicy,omitempty"` so
nil values are omitted, and apply the same fix to the other struct(s) that
declare an IsCustomPolicy pointer (the similar definition referenced around
lines 593-597) to prevent null booleans in manifest payloads.
platform-api/src/resources/gateway-internal-api.yaml (1)

829-860: ⚠️ Potential issue | 🟠 Major

Constrain manifest payload size in schema.

policies and nested policy schema objects are currently unbounded, which leaves this endpoint open to oversized payload pressure.

Suggested schema hardening
                 policies:
                   type: array
                   description: All policies installed on the gateway.
+                  maxItems: 1000
                   items:
                     type: object
@@
                       parameters:
                         type: object
                         description: JSON Schema for the policy's user-configurable parameters. Present for custom policies.
+                        maxProperties: 200
                         additionalProperties: true
                       systemParameters:
                         type: object
                         description: JSON Schema for the policy's system-level parameters. Present for custom policies.
+                        maxProperties: 200
                         additionalProperties: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/resources/gateway-internal-api.yaml` around lines 829 - 860,
The policy schema is unbounded and needs size limits: add a sensible maxItems
(and optionally minItems) to the policies array and add maxLength for string
fields like name and version; for the nested objects parameters and
systemParameters add maxProperties and set additionalProperties to true only if
needed (or switch to additionalProperties: false and use patternProperties) to
prevent arbitrarily large objects; ensure you update the schema entries named
policies, parameters, and systemParameters (and the properties name/version) to
include these constraints so the manifest payload is bounded.
🧹 Nitpick comments (2)
platform-api/src/internal/server/server.go (1)

170-170: Minor: Extra space in argument list.

There's a double space between gatewayEventsService, and slogger. This is a trivial formatting nit.

-	gatewayService := service.NewGatewayService(gatewayRepo, orgRepo, apiRepo, gatewayEventsService,  slogger)
+	gatewayService := service.NewGatewayService(gatewayRepo, orgRepo, apiRepo, gatewayEventsService, slogger)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/server/server.go` at line 170, Remove the extra
space in the argument list when calling NewGatewayService: locate the call to
service.NewGatewayService using the identifiers gatewayRepo, orgRepo, apiRepo,
gatewayEventsService and slogger and ensure there is only a single space (or no
extra spaces) between the comma after gatewayEventsService and slogger so the
arguments are consistently formatted.
gateway/gateway-controller/pkg/utils/api_utils.go (1)

602-609: Reuse s.client instead of creating a per-call client; this ensures consistent transport configuration across all operations.

The per-call http.Client created here omits the MinVersion: tls.VersionTLS12 requirement and connection pool tuning (MaxIdleConns, MaxIdleConnsPerHost, MaxConnsPerHost, IdleConnTimeout) already configured in NewAPIUtilsService. This same pattern appears in other methods in this file—apply the same fix wherever per-call clients are created.

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

In `@gateway/gateway-controller/pkg/utils/api_utils.go` around lines 602 - 609,
The per-call http.Client created in this method overwrites shared transport
settings and omits TLS/connection-pool options; replace the local client
creation with reuse of the service-level s.client (constructed in
NewAPIUtilsService) so the TLSConfig.MinVersion (tls.VersionTLS12) and
connection pool tuning (MaxIdleConns, MaxIdleConnsPerHost, MaxConnsPerHost,
IdleConnTimeout) are preserved; update this method (and other methods in the
file using per-call clients) to use s.client and ensure any per-request timeouts
are applied via context with deadline rather than a new http.Client.Timeout.
🤖 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-controller/pkg/controlplane/client.go`:
- Around line 348-353: handleMessage currently lacks a case for the
"gateway.manifest.request" event so re-sync requests fall through to the default
unknown-event path; add a switch case for "gateway.manifest.request" in
handleMessage that calls c.pushGatewayManifest() (mirror the existing logic that
pushes the manifest on connect such as pushGatewayManifestOnConnect) so the
control plane-triggered re-transmit is handled properly.

In `@platform-api/src/internal/handler/gateway.go`:
- Around line 498-508: The current error handling around
h.gatewayService.GetStoredManifest(gatewayId, orgId) maps "gateway not found" to
404 but treats invalid UUID formats as 500; update the handler to detect an
invalid-UUID error (e.g., check err.Error() for "invalid UUID" or the same
pattern used in GetGateway) and return HTTP 400 with a descriptive message, keep
the 404 mapping for "gateway not found", and change the internal error response
message from "Failed to sync gateway manifest" to "Failed to retrieve gateway
manifest" so the error text matches the retrieval context; locate this logic in
the gateway manifest retrieval block that calls GetStoredManifest and apply the
checks and message changes there.

In `@platform-api/src/internal/service/gateway.go`:
- Around line 41-43: The comment for GatewayPolicyInput is outdated: it
currently states IsCustomPolicy "is used only for filtering and is not stored or
returned," but IsCustomPolicy is persisted in GatewayPolicyDefinition and
included in the stored manifest JSON; update the doc comment for
GatewayPolicyInput to reflect that IsCustomPolicy is persisted and included in
stored manifest data (reference GatewayPolicyInput, IsCustomPolicy, and
GatewayPolicyDefinition), ensuring the comment no longer contradicts the
implementation.

In `@platform-api/src/resources/gateway-internal-api.yaml`:
- Around line 834-852: The policy item schema currently requires only "name" and
"version" but allows "isCustomPolicy" to be omitted; update the OpenAPI schema
for the policy object by adding "isCustomPolicy" to the required list (alongside
"name" and "version") so every policy entry must include the boolean
isCustomPolicy property referenced in the properties block (name, version,
description, isCustomPolicy).

---

Duplicate comments:
In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 579-587: ManifestPolicyEntry currently serializes IsCustomPolicy
as a required key, emitting "isCustomPolicy": null when the pointer is nil;
update the struct tag on ManifestPolicyEntry.IsCustomPolicy to
`json:"isCustomPolicy,omitempty"` so nil values are omitted, and apply the same
fix to the other struct(s) that declare an IsCustomPolicy pointer (the similar
definition referenced around lines 593-597) to prevent null booleans in manifest
payloads.

In `@platform-api/src/internal/service/gateway.go`:
- Around line 112-116: The mapping from incoming policies is dropping the
Description field: when constructing a GatewayPolicyDefinition from the input
(currently building entry with Name, Version, IsCustomPolicy), include the
Description by adding Description: p.Description so GatewayPolicyDefinition
preserves GatewayPolicyInput.Description during conversion and stored manifest
entries retain their metadata.
- Around line 91-94: The code currently collapses repository errors into a
generic "gateway not found"; change the logic in the GetByUUID handling so that
if s.gatewayRepo.GetByUUID returns a non-nil err you return or wrap that err
(preserving the original repository error for accurate diagnostics/HTTP
mapping), and only return the "gateway not found" error when err == nil &&
gateway == nil; update the return path around the call to
s.gatewayRepo.GetByUUID to preserve repository errors (or annotate them with
context) rather than replacing them with the not-found message.

In `@platform-api/src/resources/gateway-internal-api.yaml`:
- Around line 829-860: The policy schema is unbounded and needs size limits: add
a sensible maxItems (and optionally minItems) to the policies array and add
maxLength for string fields like name and version; for the nested objects
parameters and systemParameters add maxProperties and set additionalProperties
to true only if needed (or switch to additionalProperties: false and use
patternProperties) to prevent arbitrarily large objects; ensure you update the
schema entries named policies, parameters, and systemParameters (and the
properties name/version) to include these constraints so the manifest payload is
bounded.

In `@platform-api/src/resources/openapi.yaml`:
- Around line 1970-1994: The GET operation GetGatewayManifest at path
/gateways/{gatewayId}/manifest currently returns ManifestSyncResponse but lacks
a deterministic sync status for trigger-and-poll clients; update the
ManifestSyncResponse schema to include a required "status" property (enum:
"pending" | "ready" | "error") and ensure any controller code that produces this
response sets the status accordingly so clients can poll until status ==
"ready"; also update the OpenAPI responses/docs to indicate when "pending" is
returned and document any retry/polling semantics for GetGatewayManifest.

---

Nitpick comments:
In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 602-609: The per-call http.Client created in this method
overwrites shared transport settings and omits TLS/connection-pool options;
replace the local client creation with reuse of the service-level s.client
(constructed in NewAPIUtilsService) so the TLSConfig.MinVersion
(tls.VersionTLS12) and connection pool tuning (MaxIdleConns,
MaxIdleConnsPerHost, MaxConnsPerHost, IdleConnTimeout) are preserved; update
this method (and other methods in the file using per-call clients) to use
s.client and ensure any per-request timeouts are applied via context with
deadline rather than a new http.Client.Timeout.

In `@platform-api/src/internal/server/server.go`:
- Line 170: Remove the extra space in the argument list when calling
NewGatewayService: locate the call to service.NewGatewayService using the
identifiers gatewayRepo, orgRepo, apiRepo, gatewayEventsService and slogger and
ensure there is only a single space (or no extra spaces) between the comma after
gatewayEventsService and slogger so the arguments are consistently formatted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2751d91-e0ab-4cdd-885f-594927d78481

📥 Commits

Reviewing files that changed from the base of the PR and between a22b427 and 09069f1.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • gateway/gateway-controller/api/openapi-management.yaml
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/controlplane/client.go
  • gateway/gateway-controller/pkg/controlplane/events.go
  • gateway/gateway-controller/pkg/utils/api_utils.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/database/schema.sqlite.sql
  • platform-api/src/internal/handler/gateway.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/gateway.go
  • platform-api/src/resources/gateway-internal-api.yaml
  • platform-api/src/resources/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/database/schema.sqlite.sql
  • gateway/gateway-controller/api/openapi-management.yaml
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/config/config.go

Comment thread gateway/gateway-controller/pkg/controlplane/client.go
Comment thread platform-api/src/internal/handler/gateway.go
Comment thread platform-api/src/internal/service/gateway.go
Comment on lines +834 to +852
required:
- name
- version
properties:
name:
type: string
description: Unique policy name
example: "set-wso2-headers"
version:
type: string
description: Semantic version of the policy
example: "v0.8.0"
description:
type: string
description: Human-readable description of the policy
isCustomPolicy:
type: boolean
description: Whether this is a user-installed custom policy
example: true
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.

⚠️ Potential issue | 🟠 Major

Require isCustomPolicy in each policy item.

The endpoint contract describes isCustomPolicy as part of every policy entry, but the schema currently allows it to be omitted.

Proposed fix
                     required:
                       - name
                       - version
+                      - isCustomPolicy
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/resources/gateway-internal-api.yaml` around lines 834 - 852,
The policy item schema currently requires only "name" and "version" but allows
"isCustomPolicy" to be omitted; update the OpenAPI schema for the policy object
by adding "isCustomPolicy" to the required list (alongside "name" and "version")
so every policy entry must include the boolean isCustomPolicy property
referenced in the properties block (name, version, description, isCustomPolicy).

)

policies := make([]utils.ManifestPolicyEntry, 0, len(c.policyDefinitions))
for _, def := range c.policyDefinitions {
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.

Currently, we are using policyDefinitions DTO (generated.go). I guess we have to introduce a model for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO opinion it can be handled as a separate task. This PR need to be merged soon. This is being opened for more ~1 week now.

Comment thread gateway/gateway-controller/api/openapi-management.yaml Outdated
Comment thread gateway/gateway-controller/pkg/controlplane/client.go Outdated
Comment thread platform-api/src/internal/handler/gateway_internal.go Outdated
Comment thread platform-api/src/resources/gateway-internal-api.yaml Outdated
Copy link
Copy Markdown
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

♻️ Duplicate comments (2)
platform-api/src/internal/service/gateway.go (2)

41-43: ⚠️ Potential issue | 🟡 Minor

Update stale GatewayPolicyInput comment to match behavior.

Line 42 says IsCustomPolicy is not stored/returned, but it is persisted in GatewayPolicyDefinition and used in returned manifest data.

Suggested fix
-// IsCustomPolicy is used only for filtering and is not stored or returned.
+// IsCustomPolicy indicates whether the policy is custom and is persisted in stored/returned manifest data.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 41 - 43, The
comment on GatewayPolicyInput is stale: it incorrectly states IsCustomPolicy "is
not stored or returned" while the field is persisted in GatewayPolicyDefinition
and included in returned manifest data; update the doc comment for
GatewayPolicyInput to state that IsCustomPolicy is stored (persisted to
GatewayPolicyDefinition) and propagated in the returned manifest/response so it
accurately reflects current behavior.

91-97: ⚠️ Potential issue | 🟠 Major

Do not collapse repository failures into “gateway not found.”

Line 92 currently maps GetByUUID errors to not-found, which hides real backend failures and breaks error classification.

Suggested fix
 	gateway, err := s.gatewayRepo.GetByUUID(gatewayID)
-	if err != nil || gateway == nil {
+	if err != nil {
+		return nil, fmt.Errorf("failed to get gateway: %w", err)
+	}
+	if gateway == nil {
 		return nil, errors.New("gateway not found")
 	}
 	if gateway.OrganizationID != orgID {
 		return nil, errors.New("gateway not found")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 91 - 97, The
current code collapses repository errors from s.gatewayRepo.GetByUUID into a
generic "gateway not found"; change the logic to propagate actual repository
errors (return the err) when GetByUUID returns a non-nil error, and only return
a "gateway not found" error when err is nil but gateway == nil or when
gateway.OrganizationID != orgID; update the gateway retrieval block around
s.gatewayRepo.GetByUUID and the subsequent OrganizationID check so backend
failures are not masked and only true not-found/unauthorized cases produce the
not-found error.
🧹 Nitpick comments (1)
gateway/gateway-controller/pkg/utils/api_utils.go (1)

602-625: Reuse s.client instead of spinning up a second HTTP transport here.

This method creates a duplicate client, bypassing the shared configuration from NewAPIUtilsService. The new client lacks MinVersion: tls.VersionTLS12 and connection pool tuning (MaxIdleConns, MaxIdleConnsPerHost, MaxConnsPerHost), causing manifest POSTs to drift from the shared timeout/TLS/pooling settings. Using the existing client keeps all control-plane calls consistent and avoids transport duplication.

Suggested cleanup
-	client := &http.Client{
-		Timeout: s.config.Timeout,
-		Transport: &http.Transport{
-			TLSClientConfig: &tls.Config{
-				InsecureSkipVerify: s.config.InsecureSkipVerify,
-			},
-		},
-	}
-
 	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
 	if err != nil {
 		return fmt.Errorf("failed to create manifest request: %w", err)
 	}
@@
-	resp, err := client.Do(req)
+	resp, err := s.client.Do(req)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-controller/pkg/utils/api_utils.go` around lines 602 - 625,
The code creates a new http.Client/Transport instead of reusing the shared
client on the service; replace the local client creation with the service's
existing HTTP client (s.client) so all calls inherit TLS settings (e.g.
MinVersion: tls.VersionTLS12) and connection pool tuning (MaxIdleConns,
MaxIdleConnsPerHost, MaxConnsPerHost) established in NewAPIUtilsService; update
the function that builds and sends the manifest request (the block creating
http.NewRequest and calling client.Do) to call s.client.Do(req) and remove the
local Transport/HTTP client construction to avoid duplicating transports and
diverging settings.
🤖 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-controller/pkg/utils/policy_loader.go`:
- Around line 201-209: The custom policy map is keyed only by entry.Name causing
different versions to collide; change the keying to include version (e.g., key
:= entry.Name + "|" + entry.Version) wherever the map customPolicies is
populated in the lock.Policies loop and ensure LocalPolicyNames/any lookup logic
(which currently checks customPolicies[entry.Name]) uses the same "name|version"
composite key so ownership is resolved per name+version rather than just name.

---

Duplicate comments:
In `@platform-api/src/internal/service/gateway.go`:
- Around line 41-43: The comment on GatewayPolicyInput is stale: it incorrectly
states IsCustomPolicy "is not stored or returned" while the field is persisted
in GatewayPolicyDefinition and included in returned manifest data; update the
doc comment for GatewayPolicyInput to state that IsCustomPolicy is stored
(persisted to GatewayPolicyDefinition) and propagated in the returned
manifest/response so it accurately reflects current behavior.
- Around line 91-97: The current code collapses repository errors from
s.gatewayRepo.GetByUUID into a generic "gateway not found"; change the logic to
propagate actual repository errors (return the err) when GetByUUID returns a
non-nil error, and only return a "gateway not found" error when err is nil but
gateway == nil or when gateway.OrganizationID != orgID; update the gateway
retrieval block around s.gatewayRepo.GetByUUID and the subsequent OrganizationID
check so backend failures are not masked and only true not-found/unauthorized
cases produce the not-found error.

---

Nitpick comments:
In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 602-625: The code creates a new http.Client/Transport instead of
reusing the shared client on the service; replace the local client creation with
the service's existing HTTP client (s.client) so all calls inherit TLS settings
(e.g. MinVersion: tls.VersionTLS12) and connection pool tuning (MaxIdleConns,
MaxIdleConnsPerHost, MaxConnsPerHost) established in NewAPIUtilsService; update
the function that builds and sends the manifest request (the block creating
http.NewRequest and calling client.Do) to call s.client.Do(req) and remove the
local Transport/HTTP client construction to avoid duplicating transports and
diverging settings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f51980-a374-4fe0-81d0-356992eb7dda

📥 Commits

Reviewing files that changed from the base of the PR and between 09069f1 and d448864.

📒 Files selected for processing (10)
  • gateway/gateway-builder/templates/Dockerfile.gateway-controller.tmpl
  • gateway/gateway-controller/api/openapi-management.yaml
  • gateway/gateway-controller/cmd/controller/main.go
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/config/config.go
  • gateway/gateway-controller/pkg/controlplane/client.go
  • gateway/gateway-controller/pkg/utils/api_utils.go
  • gateway/gateway-controller/pkg/utils/policy_loader.go
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/service/gateway.go
✅ Files skipped from review due to trivial changes (2)
  • gateway/gateway-builder/templates/Dockerfile.gateway-controller.tmpl
  • gateway/gateway-controller/api/openapi-management.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • platform-api/src/internal/repository/gateway.go
  • gateway/gateway-controller/pkg/controlplane/client.go

Comment thread gateway/gateway-controller/pkg/utils/policy_loader.go Outdated
Copy link
Copy Markdown
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 (7)
gateway/gateway-controller/pkg/controlplane/client.go (2)

371-376: ⚠️ Potential issue | 🟠 Major

Add a re-sync path, not just the connect-time POST.

Line 371 only schedules a one-shot upload during Connect(). If that POST fails after retries or the control plane later asks for a resend, there is still no event path that invokes this helper again, so manifest sync cannot recover until the next reconnect. Wire the same helper into the manifest-request branch in handleMessage.

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

In `@gateway/gateway-controller/pkg/controlplane/client.go` around lines 371 -
376, The current Connect() only schedules a one-shot manifest upload via
pushGatewayManifestOnConnect(gatewayID), so failed POSTs or later
manifest-request messages won't trigger a resend; update handleMessage to call
the same helper in the manifest-request branch so requests from the control
plane re-trigger pushGatewayManifestOnConnect, and ensure the call follows the
same goroutine/waitgroup pattern used in Connect() (c.wg.Add(1); go func(gwID
string){ defer c.wg.Done(); c.pushGatewayManifestOnConnect(gwID) }(gatewayID))
so retries and concurrency semantics are consistent.

3074-3076: ⚠️ Potential issue | 🟡 Minor

Make the retry backoff cancellation-aware.

Line 3075 blocks on time.Sleep, so Stop() can sit in wg.Wait() until the current backoff finishes even after c.ctx or stopChan has been closed.

Suggested fix
 		if attempt < maxRetries {
-			time.Sleep(time.Duration(attempt) * 2 * time.Second)
+			select {
+			case <-time.After(time.Duration(attempt) * 2 * time.Second):
+			case <-c.ctx.Done():
+				return
+			case <-c.stopChan:
+				return
+			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-controller/pkg/controlplane/client.go` around lines 3074 -
3076, The retry backoff currently blocks on time.Sleep(attempt*2s) which
prevents Stop() from returning while wg.Wait() waits; replace the blocking sleep
in the retry loop (the block using attempt and maxRetries) with a
cancellation-aware wait: create a timer via time.After or time.NewTimer for
time.Duration(attempt)*2*time.Second and use a select that listens on the timer
channel AND on c.ctx.Done() (and stopChan if present) so you break out
immediately when cancellation occurs; ensure you stop the timer when canceled
and return or break the retry loop promptly to allow Stop()/wg.Wait() to
complete.
gateway/gateway-controller/pkg/utils/policy_loader.go (1)

201-209: ⚠️ Potential issue | 🟠 Major

Key the local-policy set by name|version.

Line 203 and Line 209 collapse every build-lock entry for a policy name into one boolean. If only one version of a policy is local, the downstream ownership lookup will still mark the other versions as customer, so the manifest reports the wrong managedBy. The lookup that consumes LocalPolicyNames needs to use the same composite key.

Suggested fix
 	for _, entry := range lock.Policies {
+		key := entry.Name + "|" + entry.Version
 		if entry.FilePath != "" {
-			customPolicies[entry.Name] = true
+			customPolicies[key] = true
 			pl.logger.Debug("Detected locally developed policy via filePath",
 				slog.String("name", entry.Name),
 				slog.String("version", entry.Version),
 				slog.String("filePath", entry.FilePath))
 		} else if entry.Gomodule != "" && !strings.HasPrefix(entry.Gomodule, "github.com/wso2") {
-			customPolicies[entry.Name] = true
+			customPolicies[key] = true
 			pl.logger.Debug("Detected third-party custom policy via gomodule",
 				slog.String("name", entry.Name),
 				slog.String("version", entry.Version),
 				slog.String("gomodule", entry.Gomodule))
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-controller/pkg/utils/policy_loader.go` around lines 201 -
209, The map customPolicies currently keys only by entry.Name causing different
versions to collapse; change the key to the composite "name|version" for all
places that mark local policies (i.e., when iterating lock.Policies in the block
using entry.FilePath and entry.Gomodule) so you set customPolicies[entry.Name +
"|" + entry.Version] = true (and ensure any consumer expecting LocalPolicyNames
uses the same composite key). Update any other assignments/readers of
LocalPolicyNames/ customPolicies to use this composite key to preserve
per-version ownership.
platform-api/src/internal/handler/gateway_internal.go (1)

541-555: 🛠️ Refactor suggestion | 🟠 Major

Use the existing authenticateRequest helper to avoid duplicating auth logic.

This inline authentication duplicates the logic in authenticateRequest (lines 60-82). The existing helper already distinguishes between missing API key, invalid token, and internal errors, returning appropriate status codes (401 vs 500).

♻️ Suggested refactor
 func (h *GatewayInternalAPIHandler) ReceiveGatewayManifest(c *gin.Context) {
-	clientIP := c.ClientIP()
-
-	apiKey := c.GetHeader("api-key")
-	if apiKey == "" {
-		h.slogger.Warn("Unauthorized access attempt - Missing API key", "clientIP", clientIP)
-		c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
-			"API key is required. Provide 'api-key' header."))
-		return
-	}
-
-	gateway, err := h.gatewayService.VerifyToken(apiKey)
-	if err != nil {
-		h.slogger.Warn("Authentication failed", "clientIP", clientIP, "error", err)
-		c.JSON(http.StatusUnauthorized, utils.NewErrorResponse(401, "Unauthorized",
-			"Invalid or expired API key"))
-		return
-	}
+	_, authenticatedGatewayID, ok := h.authenticateRequest(c)
+	if !ok {
+		return
+	}

 	gatewayID := c.Param("gatewayId")
-	if gatewayID == "" || gatewayID != gateway.ID {
+	if gatewayID == "" || gatewayID != authenticatedGatewayID {
 		c.JSON(http.StatusForbidden, utils.NewErrorResponse(403, "Forbidden",
 			"Gateway ID mismatch"))
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/handler/gateway_internal.go` around lines 541 -
555, The inline auth block duplicates authenticateRequest; replace the manual
header read and VerifyToken call in gateway_internal.go with a call to
authenticateRequest(c) and use its returned values (e.g., token/gateway and
error/status) instead of reimplementing logic; ensure you preserve clientIP
logging (h.slogger) and respond using the status/error returned by
authenticateRequest (so missing key, invalid token, and internal errors map to
the helper's 401/500 behavior) and remove the duplicated
apiKey/gatewayService.VerifyToken code.
platform-api/src/internal/service/gateway.go (2)

41-43: ⚠️ Potential issue | 🟡 Minor

Comment contradicts implementation.

The comment states IsCustomPolicy "is used only for filtering and is not stored or returned," but IsCustomPolicy is persisted in GatewayPolicyDefinition (line 58) and included in the stored manifest JSON.

📝 Suggested fix
-// GatewayPolicyInput is the raw policy data received from the gateway controller.
-// IsCustomPolicy is used only for filtering and is not stored or returned.
+// GatewayPolicyInput is the raw policy data received from the gateway controller.
+// IsCustomPolicy indicates whether this is a user-installed custom policy.
 type GatewayPolicyInput struct {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 41 - 43, The
comment on GatewayPolicyInput is inaccurate: the IsCustomPolicy field is not
only used for filtering but is persisted in GatewayPolicyDefinition and included
in the stored manifest JSON; update the comment for GatewayPolicyInput to state
that IsCustomPolicy is persisted and stored (referencing GatewayPolicyDefinition
and the manifest storage) so the documentation matches the implementation.

91-97: ⚠️ Potential issue | 🟠 Major

Separate repository errors from "gateway not found" responses.

Collapsing err != nil and gateway == nil into the same "gateway not found" error hides actual repository failures (e.g., database connectivity issues) and returns misleading responses. Repository errors should surface as internal server errors.

🐛 Suggested fix
 func (s *GatewayService) GetStoredManifest(gatewayID, orgID string) (*Manifest, error) {
 	gateway, err := s.gatewayRepo.GetByUUID(gatewayID)
-	if err != nil || gateway == nil {
+	if err != nil {
+		return nil, fmt.Errorf("failed to get gateway: %w", err)
+	}
+	if gateway == nil {
 		return nil, errors.New("gateway not found")
 	}
 	if gateway.OrganizationID != orgID {
 		return nil, errors.New("gateway not found")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/internal/service/gateway.go` around lines 91 - 97, The code
currently treats any repository error the same as a missing gateway; update the
GetByUUID handling so that if s.gatewayRepo.GetByUUID(gatewayID) returns a
non-nil err you return/surface that error (or wrap it as an internal repository
error) while only returning a "gateway not found" response when gateway == nil
or when gateway.OrganizationID != orgID; specifically change the block around
s.gatewayRepo.GetByUUID to first check err and return it (or an internal error)
then check gateway == nil and finally verify gateway.OrganizationID against
orgID.
platform-api/src/resources/gateway-internal-api.yaml (1)

750-757: ⚠️ Potential issue | 🟠 Major

Add maxItems constraint and require isCustomPolicy.

Per static analysis (CKV_OPENAPI_21), the policies array lacks bounds, which poses a DoS risk from oversized payloads. Additionally, isCustomPolicy should be required per the endpoint contract described in the PR objectives.

♻️ Suggested fix
                 policies:
                   type: array
                   description: All policies installed on the gateway.
+                  maxItems: 1000
                   items:
                     type: object
                     required:
                       - name
                       - version
+                      - isCustomPolicy
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-api/src/resources/gateway-internal-api.yaml` around lines 750 - 757,
The policies array definition is missing an upper bound and doesn't require
isCustomPolicy; update the OpenAPI fragment for the "policies" schema so the
array includes a sensible maxItems (e.g., maxItems: 100) and inside items ensure
the object required list includes "isCustomPolicy" and that items.properties
contains an "isCustomPolicy" boolean; modify the schema referenced by policies
to add maxItems: 100 and add "isCustomPolicy" to the required array for the item
object (and define its type as boolean) so the endpoint contract and
CKV_OPENAPI_21 constraint are satisfied.
🧹 Nitpick comments (1)
gateway/gateway-controller/pkg/utils/api_utils.go (1)

600-607: Reuse the shared API client for manifest POSTs.

Line 600 spins up a second http.Client, so this path bypasses the transport hardening and pool settings configured in NewAPIUtilsService and gives manifest sync a different TLS profile than the rest of APIUtilsService. Send this request through s.client instead of rebuilding the transport here.

Suggested fix
-	client := &http.Client{
-		Timeout: s.config.Timeout,
-		Transport: &http.Transport{
-			TLSClientConfig: &tls.Config{
-				InsecureSkipVerify: s.config.InsecureSkipVerify,
-			},
-		},
-	}
-
 	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
 	if err != nil {
 		return fmt.Errorf("failed to create manifest request: %w", err)
 	}
@@
-	resp, err := client.Do(req)
+	resp, err := s.client.Do(req)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/gateway-controller/pkg/utils/api_utils.go` around lines 600 - 607,
The manifest POST currently creates a new http.Client with its own TLS/transport
settings (the block that constructs client := &http.Client{ Timeout:
s.config.Timeout, Transport: &http.Transport{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: s.config.InsecureSkipVerify, }, }, }), which bypasses the
shared, hardened transport created in NewAPIUtilsService; change the manifest
sync code to use the existing s.client (the shared http.Client on
APIUtilsService) for the request so it inherits the configured transport, pool,
and TLS profile instead of constructing a new client locally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@platform-api/src/internal/handler/gateway_internal.go`:
- Around line 614-617: The gatewayGroup route block is declared inside the
mcpProxyGroup block which is misleading; move the gatewayGroup creation and its
POST registration (gatewayGroup := r.Group("/api/internal/v1/gateways") and
gatewayGroup.POST("/:gatewayId/manifest", h.ReceiveGatewayManifest)) out of the
mcpProxyGroup { ... } scope so it sits alongside the other top-level route
groups (using r as the router) to match the file's routing structure and avoid
incorrect nesting.

In `@platform-api/src/resources/gateway-internal-api.yaml`:
- Line 719: The OpenAPI path key
"/api/internal/v1/gateways/{gatewayId}/manifest" redundantly repeats the base
path defined in servers[].url; change the path key to remove the duplicated
prefix so it matches other paths (e.g., use "/gateways/{gatewayId}/manifest"
instead of "/api/internal/v1/gateways/{gatewayId}/manifest") so the effective
route becomes servers[].url + "/gateways/{gatewayId}/manifest"; update the path
entry associated with that string in the document (the path key itself) and
verify no other endpoints repeat the base prefix.

---

Duplicate comments:
In `@gateway/gateway-controller/pkg/controlplane/client.go`:
- Around line 371-376: The current Connect() only schedules a one-shot manifest
upload via pushGatewayManifestOnConnect(gatewayID), so failed POSTs or later
manifest-request messages won't trigger a resend; update handleMessage to call
the same helper in the manifest-request branch so requests from the control
plane re-trigger pushGatewayManifestOnConnect, and ensure the call follows the
same goroutine/waitgroup pattern used in Connect() (c.wg.Add(1); go func(gwID
string){ defer c.wg.Done(); c.pushGatewayManifestOnConnect(gwID) }(gatewayID))
so retries and concurrency semantics are consistent.
- Around line 3074-3076: The retry backoff currently blocks on
time.Sleep(attempt*2s) which prevents Stop() from returning while wg.Wait()
waits; replace the blocking sleep in the retry loop (the block using attempt and
maxRetries) with a cancellation-aware wait: create a timer via time.After or
time.NewTimer for time.Duration(attempt)*2*time.Second and use a select that
listens on the timer channel AND on c.ctx.Done() (and stopChan if present) so
you break out immediately when cancellation occurs; ensure you stop the timer
when canceled and return or break the retry loop promptly to allow
Stop()/wg.Wait() to complete.

In `@gateway/gateway-controller/pkg/utils/policy_loader.go`:
- Around line 201-209: The map customPolicies currently keys only by entry.Name
causing different versions to collapse; change the key to the composite
"name|version" for all places that mark local policies (i.e., when iterating
lock.Policies in the block using entry.FilePath and entry.Gomodule) so you set
customPolicies[entry.Name + "|" + entry.Version] = true (and ensure any consumer
expecting LocalPolicyNames uses the same composite key). Update any other
assignments/readers of LocalPolicyNames/ customPolicies to use this composite
key to preserve per-version ownership.

In `@platform-api/src/internal/handler/gateway_internal.go`:
- Around line 541-555: The inline auth block duplicates authenticateRequest;
replace the manual header read and VerifyToken call in gateway_internal.go with
a call to authenticateRequest(c) and use its returned values (e.g.,
token/gateway and error/status) instead of reimplementing logic; ensure you
preserve clientIP logging (h.slogger) and respond using the status/error
returned by authenticateRequest (so missing key, invalid token, and internal
errors map to the helper's 401/500 behavior) and remove the duplicated
apiKey/gatewayService.VerifyToken code.

In `@platform-api/src/internal/service/gateway.go`:
- Around line 41-43: The comment on GatewayPolicyInput is inaccurate: the
IsCustomPolicy field is not only used for filtering but is persisted in
GatewayPolicyDefinition and included in the stored manifest JSON; update the
comment for GatewayPolicyInput to state that IsCustomPolicy is persisted and
stored (referencing GatewayPolicyDefinition and the manifest storage) so the
documentation matches the implementation.
- Around line 91-97: The code currently treats any repository error the same as
a missing gateway; update the GetByUUID handling so that if
s.gatewayRepo.GetByUUID(gatewayID) returns a non-nil err you return/surface that
error (or wrap it as an internal repository error) while only returning a
"gateway not found" response when gateway == nil or when gateway.OrganizationID
!= orgID; specifically change the block around s.gatewayRepo.GetByUUID to first
check err and return it (or an internal error) then check gateway == nil and
finally verify gateway.OrganizationID against orgID.

In `@platform-api/src/resources/gateway-internal-api.yaml`:
- Around line 750-757: The policies array definition is missing an upper bound
and doesn't require isCustomPolicy; update the OpenAPI fragment for the
"policies" schema so the array includes a sensible maxItems (e.g., maxItems:
100) and inside items ensure the object required list includes "isCustomPolicy"
and that items.properties contains an "isCustomPolicy" boolean; modify the
schema referenced by policies to add maxItems: 100 and add "isCustomPolicy" to
the required array for the item object (and define its type as boolean) so the
endpoint contract and CKV_OPENAPI_21 constraint are satisfied.

---

Nitpick comments:
In `@gateway/gateway-controller/pkg/utils/api_utils.go`:
- Around line 600-607: The manifest POST currently creates a new http.Client
with its own TLS/transport settings (the block that constructs client :=
&http.Client{ Timeout: s.config.Timeout, Transport: &http.Transport{
TLSClientConfig: &tls.Config{ InsecureSkipVerify: s.config.InsecureSkipVerify,
}, }, }), which bypasses the shared, hardened transport created in
NewAPIUtilsService; change the manifest sync code to use the existing s.client
(the shared http.Client on APIUtilsService) for the request so it inherits the
configured transport, pool, and TLS profile instead of constructing a new client
locally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1a3de76-cd6c-49e4-a6bf-937b014c9e8a

📥 Commits

Reviewing files that changed from the base of the PR and between d448864 and f99626a.

📒 Files selected for processing (20)
  • gateway/gateway-builder/templates/Dockerfile.gateway-controller.tmpl
  • gateway/gateway-controller/api/openapi-management.yaml
  • gateway/gateway-controller/cmd/controller/main.go
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/config/config.go
  • gateway/gateway-controller/pkg/controlplane/client.go
  • gateway/gateway-controller/pkg/utils/api_utils.go
  • gateway/gateway-controller/pkg/utils/policy_loader.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/database/schema.sqlite.sql
  • platform-api/src/internal/handler/gateway.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/gateway.go
  • platform-api/src/resources/gateway-internal-api.yaml
  • platform-api/src/resources/openapi.yaml
✅ Files skipped from review due to trivial changes (5)
  • gateway/gateway-builder/templates/Dockerfile.gateway-controller.tmpl
  • platform-api/src/internal/database/schema.sql
  • gateway/gateway-controller/pkg/config/config.go
  • gateway/gateway-controller/api/openapi-management.yaml
  • platform-api/src/resources/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • platform-api/src/internal/database/schema.sqlite.sql
  • gateway/gateway-controller/cmd/controller/main.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/database/schema.postgres.sql
  • platform-api/src/internal/repository/gateway.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/handler/gateway.go

Comment thread platform-api/src/internal/handler/gateway_internal.go Outdated
Comment thread platform-api/src/resources/gateway-internal-api.yaml Outdated
changes to resolve conflicts

generated go file update
Comment thread gateway/gateway-controller/cmd/controller/main.go
Comment thread gateway/gateway-controller/pkg/utils/api_utils.go Outdated
Comment thread gateway/gateway-controller/pkg/utils/api_utils.go Outdated
Comment thread gateway/gateway-controller/pkg/utils/policy_loader.go Outdated
Comment thread gateway/gateway-controller/pkg/controlplane/client.go Outdated
Comment thread gateway/gateway-controller/pkg/utils/api_utils.go Outdated
Comment thread gateway/gateway-controller/pkg/utils/api_utils.go Outdated
Comment thread gateway/gateway-controller/pkg/utils/policy_loader.go
Comment thread gateway/gateway-controller/pkg/utils/policy_loader.go Outdated
@slahirucd7 slahirucd7 merged commit 87e5c41 into wso2:main Mar 20, 2026
6 checks passed
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.

3 participants