Add Secret Management UI Implementation - #3129
Conversation
|
Naduni Pamudika seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change adds revision-aware secret lifecycle events, permanent orphan cleanup, gateway synchronization, secret usage APIs, portal secret management, upstream authentication serialization, and live lifecycle integration coverage. ChangesSecret lifecycle backend
Secret API and portal
Integration coverage
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Portal
participant PlatformAPI
participant GatewayEventsService
participant GatewayController
participant SecretStore
Portal->>PlatformAPI: Rotate or delete secret
PlatformAPI->>GatewayEventsService: Broadcast lifecycle event
GatewayEventsService->>GatewayController: Publish WebSocket event
GatewayController->>PlatformAPI: Fetch rotated value
GatewayController->>SecretStore: Upsert or delete local secret
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@platform-api/internal/model/secret.go`:
- Around line 72-90: Add a revision or generation field to SecretUpdatedEvent
and SecretDeprecatedEvent, then update gateway lifecycle handling to ignore
events older than the locally recorded revision while preserving existing
idempotency and delivery coverage. Ensure deletion followed by reactivation
cannot be undone by a stale event, and add a test covering that sequence.
In `@portals/ai-workspace/src/App.tsx`:
- Around line 591-622: Update portals/ai-workspace/src/App.tsx lines 591-622 to
guard the entire secrets route branch, including nested routes, with
SCOPES.SECRET_READ and replace the hard-coded settings index redirect with a
scope-aware destination. In
portals/ai-workspace/src/pages/appShell/appShellPages/settings/Main.tsx lines
79-84, authorize the matched settings route using its requiredScope rather than
visibleNavItems, preserving access only for users with the route’s required
scope.
In
`@portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsx`:
- Around line 59-76: Bind secret fetch results to the active organization and
handle across SecretOverview.tsx lines 59-76, RotateSecret.tsx lines 68-89, and
SecretsList.tsx lines 77-94. Update the request flows using request IDs or
cancellation, and reject responses that are stale after the organization or
handle changes; ensure SecretOverview cannot replace the current secret
metadata, RotateSecret cannot submit stale metadata, and SecretsList cannot
replace the current organization’s list.
In
`@portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsx`:
- Around line 252-257: Update the row keyboard handler in SecretsList so
keyboard events originating from child controls such as the delete IconButton
are ignored, while Enter and Space pressed directly on the row still prevent
default behavior and navigate to the secret. Use the event target/currentTarget
relationship to distinguish the row from its descendants.
In `@tests/integration-e2e/ingress_helpers_test.go`:
- Around line 68-78: Introduce one shared delay helper that validates a positive
interval and a non-zero interval/2 before adding bounded random jitter, then use
it before every polling fetch. Update
tests/integration-e2e/ingress_helpers_test.go:68-78 in waitIngressWithHeaders,
tests/integration-e2e/secret_helpers_test.go:213-240 before each secret-value
fetch, and tests/integration-e2e/secret_helpers_test.go:246-270 before each
deletion-status fetch; replace the fixed two-second sleeps while preserving the
existing polling behavior.
In `@tests/integration-e2e/README.md`:
- Around line 255-257: Update the “Deletion” scenario in the README to document
the explicit deleteSecret operation: state that DELETE /secrets/:handle triggers
the original secret’s deprecation event, rather than attributing deprecation to
cleanupRotatedSecret. Preserve the existing description of redeployment and
gateway cache eviction.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 600a8b72-f6f9-4d74-b6e9-34bdeb670754
📒 Files selected for processing (31)
gateway/gateway-controller/pkg/controlplane/client.gogateway/gateway-controller/pkg/controlplane/events.gogateway/gateway-controller/pkg/controlplane/sync_secrets_test.goplatform-api/internal/constants/constants.goplatform-api/internal/dto/api.goplatform-api/internal/model/secret.goplatform-api/internal/server/server.goplatform-api/internal/service/gateway_events.goplatform-api/internal/service/secret_service.goplatform-api/internal/service/secret_service_broadcast_test.goplatform-api/internal/service/secret_service_test.goplatform-api/internal/utils/api.goplatform-api/internal/utils/api_test.goportals/ai-workspace/src/App.tsxportals/ai-workspace/src/apis/secretApis.tsportals/ai-workspace/src/clients/choreoApiClient.tsportals/ai-workspace/src/hooks/useIsMounted.tsportals/ai-workspace/src/pages/appShell/appShellPages/secret/CreateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/DeleteSecretDialog.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsxportals/ai-workspace/src/pages/appShell/appShellPages/settings/Main.tsxtests/integration-e2e/README.mdtests/integration-e2e/features/secret_lifecycle.featuretests/integration-e2e/features/secured-api-invocation.featuretests/integration-e2e/ingress_helpers_test.gotests/integration-e2e/secret_helpers_test.gotests/integration-e2e/secret_lifecycle_steps_test.gotests/integration-e2e/steps_secured_test.gotests/integration-e2e/steps_test.go
💤 Files with no reviewable changes (2)
- tests/integration-e2e/features/secured-api-invocation.feature
- tests/integration-e2e/steps_secured_test.go
d00f44d to
b6cbfc3
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/integration-e2e/secret_helpers_test.go`:
- Around line 217-236: Add randomized positive pre-request jitter to both
polling loops around the visible HTTP fetches, using a non-zero half-interval
rather than a fixed two-second retry cadence. Apply the same jittered delay
before every request in the loops containing httpClient.Do, while preserving the
existing deadline and retry behavior.
- Line 162: Update the response-body reads in the relevant secret helper test
paths around the existing io.ReadAll calls to wrap resp.Body with io.LimitReader
before reading. Use the configured response-size limit when available, with a
safe default fallback, and apply the same bounded-reader pattern to all three
occurrences.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3402839c-59e4-48ab-9753-061df24b30f4
📒 Files selected for processing (28)
gateway/gateway-controller/pkg/controlplane/client.gogateway/gateway-controller/pkg/controlplane/events.gogateway/gateway-controller/pkg/controlplane/sync_secrets_test.goplatform-api/internal/constants/constants.goplatform-api/internal/dto/api.goplatform-api/internal/model/secret.goplatform-api/internal/server/server.goplatform-api/internal/service/gateway_events.goplatform-api/internal/service/secret_service.goplatform-api/internal/service/secret_service_broadcast_test.goplatform-api/internal/service/secret_service_test.goplatform-api/internal/utils/api.goplatform-api/internal/utils/api_test.goportals/ai-workspace/src/App.tsxportals/ai-workspace/src/apis/secretApis.tsportals/ai-workspace/src/clients/choreoApiClient.tsportals/ai-workspace/src/hooks/useIsMounted.tsportals/ai-workspace/src/pages/appShell/appShellPages/secret/CreateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/DeleteSecretDialog.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsxportals/ai-workspace/src/pages/appShell/appShellPages/settings/Main.tsxtests/integration-e2e/README.mdtests/integration-e2e/features/secret_lifecycle.featuretests/integration-e2e/secret_helpers_test.gotests/integration-e2e/secret_lifecycle_steps_test.gotests/integration-e2e/steps_test.go
🚧 Files skipped from review as they are similar to previous changes (21)
- portals/ai-workspace/src/hooks/useIsMounted.ts
- portals/ai-workspace/src/apis/secretApis.ts
- platform-api/internal/model/secret.go
- tests/integration-e2e/features/secret_lifecycle.feature
- platform-api/internal/server/server.go
- gateway/gateway-controller/pkg/controlplane/sync_secrets_test.go
- platform-api/internal/service/secret_service_test.go
- platform-api/internal/utils/api_test.go
- platform-api/internal/dto/api.go
- platform-api/internal/utils/api.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/CreateSecret.tsx
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsx
- gateway/gateway-controller/pkg/controlplane/events.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsx
- tests/integration-e2e/secret_lifecycle_steps_test.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/DeleteSecretDialog.tsx
- platform-api/internal/service/secret_service_broadcast_test.go
- platform-api/internal/service/secret_service.go
- portals/ai-workspace/src/App.tsx
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsx
- gateway/gateway-controller/pkg/controlplane/client.go
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@gateway/gateway-controller/pkg/controlplane/client.go`:
- Around line 3810-3827: Update handleMessage and the revision-bearing event
decode path to preserve revision values as full int64 numbers instead of default
float64 values before isStaleSecretEvent compares them. Ensure equal revisions
remain idempotent while adjacent epoch-nanosecond revisions remain
distinguishable, and add a regression test covering adjacent revision values and
reordered events.
In `@platform-api/internal/model/secret.go`:
- Around line 79-85: Replace the time-based revision described in the Secret
lifecycle event flow with a database-backed per-secret version. Add an atomic
data_version/revision increment to both SecretService.Update and
SecretService.Delete, ensure the version is read or returned only after the
corresponding row write commits, and use that committed value in emitted events
so concurrent update and soft-delete operations preserve commit order.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 28faceb6-1ae3-4e64-b38c-13f7f0827ddd
📒 Files selected for processing (13)
gateway/gateway-controller/pkg/controlplane/client.gogateway/gateway-controller/pkg/controlplane/events.gogateway/gateway-controller/pkg/controlplane/sync_secrets_test.goplatform-api/internal/model/secret.goplatform-api/internal/service/secret_service.goportals/ai-workspace/cypress/e2e/001-providers/002-provider-secret-management.cy.jsportals/ai-workspace/cypress/e2e/001-providers/003-llm-proxy-secret-management.cy.jsportals/ai-workspace/src/App.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsxportals/ai-workspace/src/pages/appShell/appShellPages/settings/Main.tsxtests/integration-e2e/secret_helpers_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsx
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsx
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsx
- gateway/gateway-controller/pkg/controlplane/events.go
- platform-api/internal/service/secret_service.go
f2bfd50 to
15a248d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@platform-api/internal/repository/secret.go`:
- Around line 247-251: Update the SQL Server branch in FindRefsAndSoftDelete so
the secret lookup acquires the same blocking lock semantics as the PostgreSQL
path instead of only switching to SELECT TOP (1). Apply a SQL Server row-locking
equivalent on the secrets read, and make sure the later refsQuery and UPDATE
still run under that contract so concurrent artifact_secret_refs writes cannot
slip past the empty-reference check. If needed, align the reference write path
with the same locking behavior, and add a SQL Server concurrency test that
exercises secret deprecation against concurrent reference insertion or update.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7f8c2da6-333e-4a66-9f64-b4f231e49064
📒 Files selected for processing (2)
gateway/gateway-controller/pkg/controlplane/client.goplatform-api/internal/repository/secret.go
🚧 Files skipped from review as they are similar to previous changes (1)
- gateway/gateway-controller/pkg/controlplane/client.go
15a248d to
830bd59
Compare
830bd59 to
9facac3
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
portals/ai-workspace/src/pages/appShell/appShellPages/externalServers/ExternalServersOverview.tsx (1)
1213-1226: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPreserve a selected secret reference during save.
SecretValueFieldsubmits{{ secret "name" }}for an existing secret. The MCP update path stores that placeholder as the new secret value whenhasBackendConnectionChangesis true, then references the new secret. Use the existing handle whenextractSecretHandle(authHeaderValue)returns a value, and skipcreateSecret.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@portals/ai-workspace/src/pages/appShell/appShellPages/externalServers/ExternalServersOverview.tsx` around lines 1213 - 1226, Update the save logic for the MCP auth header associated with SecretValueField to call extractSecretHandle(authHeaderValue) first; when it returns an existing handle, reuse that handle and skip createSecret, including when hasBackendConnectionChanges is true. Only create and reference a new secret when no existing handle is present, preserving selected {{ secret "name" }} references.
🧹 Nitpick comments (2)
gateway/gateway-controller/pkg/controlplane/sync_secrets_test.go (2)
809-847: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider mirroring the precision test for
secret.updated.These tests prove
handleMessagepreserves exact int64 revisions forsecret.deleted.secret.updateddecodes through the same path and uses the same revision cache. A reorderedsecret.updatedpair with adjacent UnixNano revisions would regress silently. Add one test that sends two raw-JSONsecret.updatedmessages with revisions 100ns apart and asserts the stale one is rejected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gateway/gateway-controller/pkg/controlplane/sync_secrets_test.go` around lines 809 - 847, Add a precision regression test for secret.updated alongside the existing secret.deleted tests, sending raw JSON update messages with UnixNano revisions 100ns apart in newer-then-stale order. Use the existing handleMessage, revision cache, and mock syncer symbols to assert the newer update is applied, the reordered stale update is rejected, and the cached revision remains the exact int64 value.
950-959: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBuild the fixtures from
utils.PlatformSecretMetainstead of a parallel struct.
platformSecretJSONduplicates the wire tags ofPlatformSecretMetaby hand. If a production tag changes, for exampleuuidorname, these fixtures still encode the old names. Every secret then decodes with an emptyHandleand an emptyStatus, so the eviction tests still pass, but for the wrong reason. Marshal the production type directly so a tag change breaks the fixture at compile time or fails loudly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gateway/gateway-controller/pkg/controlplane/sync_secrets_test.go` around lines 950 - 959, Remove the parallel platformSecretJSON fixture type and build test server responses directly from utils.PlatformSecretMeta, preserving the fixture values and optional Value behavior. Update all references that construct or marshal platformSecretJSON so production JSON tags are used and tag changes fail loudly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@gateway/gateway-controller/pkg/controlplane/events.go`:
- Around line 424-432: Update SecretService.Create to assign an explicit current
UTC UpdatedAt timestamp when creating a recycled secret handle, ensuring its
revision is strictly newer than the deleted predecessor. Preserve
isStaleSecretEvent’s strict less-than comparison so equal revisions remain
idempotent and older deletion events are ignored.
In `@gateway/gateway-controller/pkg/controlplane/sync_secrets.go`:
- Around line 189-220: Snapshot the keys in secretHashCache before
FetchPlatformSecrets begins, then pass that snapshot into evictSecretsNotIn and
only consider handles present in it for eviction. Update the sync flow and
evictSecretsNotIn signature accordingly, preserving the existing deletion and
logging behavior while preventing handles added during the fetch from being
removed.
- Around line 202-211: Update the stale-secret eviction loop around Delete to
generate a non-deterministic, per-attempt correlation ID instead of calling
GenerateDeterministicUUIDv7 with handle and time.Now(). Keep the existing
correlation_id logging and Delete behavior unchanged.
In `@portals/ai-workspace/src/Components/common/SecretValueField.tsx`:
- Around line 180-185: Restore keyboard access to the visibility control by
removing the tabIndex={-1} override from the IconButton in SecretValueField.
Preserve its existing show/hide behavior, aria-label, and styling while allowing
the button to participate in normal keyboard tab navigation.
In
`@portals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsx`:
- Around line 102-106: Update the RotateSecret submission flow to validate that
the trimmed display name is non-empty, while preserving a trimmed empty
description as ''. In updateSecret, append metadata fields whenever their values
are !== undefined so an explicit empty description is included in the request
rather than omitted.
---
Outside diff comments:
In
`@portals/ai-workspace/src/pages/appShell/appShellPages/externalServers/ExternalServersOverview.tsx`:
- Around line 1213-1226: Update the save logic for the MCP auth header
associated with SecretValueField to call extractSecretHandle(authHeaderValue)
first; when it returns an existing handle, reuse that handle and skip
createSecret, including when hasBackendConnectionChanges is true. Only create
and reference a new secret when no existing handle is present, preserving
selected {{ secret "name" }} references.
---
Nitpick comments:
In `@gateway/gateway-controller/pkg/controlplane/sync_secrets_test.go`:
- Around line 809-847: Add a precision regression test for secret.updated
alongside the existing secret.deleted tests, sending raw JSON update messages
with UnixNano revisions 100ns apart in newer-then-stale order. Use the existing
handleMessage, revision cache, and mock syncer symbols to assert the newer
update is applied, the reordered stale update is rejected, and the cached
revision remains the exact int64 value.
- Around line 950-959: Remove the parallel platformSecretJSON fixture type and
build test server responses directly from utils.PlatformSecretMeta, preserving
the fixture values and optional Value behavior. Update all references that
construct or marshal platformSecretJSON so production JSON tags are used and tag
changes fail loudly.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f5a6aea5-f4d3-44e9-8eeb-2cc141108cf5
📒 Files selected for processing (51)
gateway/gateway-controller/pkg/controlplane/client.gogateway/gateway-controller/pkg/controlplane/events.gogateway/gateway-controller/pkg/controlplane/sync_secrets.gogateway/gateway-controller/pkg/controlplane/sync_secrets_test.goplatform-api/internal/dto/secret.goplatform-api/internal/handler/secret.goplatform-api/internal/handler/secret_integration_test.goplatform-api/internal/model/secret.goplatform-api/internal/repository/api.goplatform-api/internal/repository/api_test.goplatform-api/internal/repository/artifact.goplatform-api/internal/repository/artifact_refs.goplatform-api/internal/repository/interfaces.goplatform-api/internal/repository/llm.goplatform-api/internal/repository/llm_test.goplatform-api/internal/repository/mcp.goplatform-api/internal/repository/secret.goplatform-api/internal/repository/secret_test.goplatform-api/internal/server/scope_route_coverage_test.goplatform-api/internal/service/api.goplatform-api/internal/service/api_secret_integration_test.goplatform-api/internal/service/api_test.goplatform-api/internal/service/gateway_events.goplatform-api/internal/service/llm.goplatform-api/internal/service/llm_test.goplatform-api/internal/service/mcp.goplatform-api/internal/service/mcp_secret_integration_test.goplatform-api/internal/service/mcp_secret_resolution_test.goplatform-api/internal/service/secret_service.goplatform-api/internal/service/secret_service_broadcast_test.goplatform-api/internal/service/secret_service_test.goplatform-api/resources/openapi.yamlportals/ai-workspace/src/App.tsxportals/ai-workspace/src/Components/common/SecretValueField.tsxportals/ai-workspace/src/apis/secretApis.tsportals/ai-workspace/src/pages/appShell/appShellPages/externalServers/ExternalServersNew.tsxportals/ai-workspace/src/pages/appShell/appShellPages/externalServers/ExternalServersOverview.tsxportals/ai-workspace/src/pages/appShell/appShellPages/proxies/LLMProxyNew.tsxportals/ai-workspace/src/pages/appShell/appShellPages/proxies/LLMProxyProviderTab.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/CreateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/DeleteSecretDialog.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretOverview.tsxportals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsxportals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/AddNewProvider/ProviderTemplateFormFields.tsxportals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderConnectionTab.tsxportals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderNew.tsxportals/ai-workspace/src/pages/appShell/appShellPages/settings/Main.tsxtests/integration-e2e/README.mdtests/integration-e2e/secret_helpers_test.gotests/integration-e2e/secret_lifecycle_steps_test.go
💤 Files with no reviewable changes (1)
- portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderNew.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- platform-api/internal/service/secret_service_broadcast_test.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/SecretsList.tsx
- platform-api/internal/model/secret.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/CreateSecret.tsx
- tests/integration-e2e/README.md
- tests/integration-e2e/secret_helpers_test.go
- platform-api/internal/service/secret_service.go
- gateway/gateway-controller/pkg/controlplane/client.go
- portals/ai-workspace/src/pages/appShell/appShellPages/secret/DeleteSecretDialog.tsx
| // SecretDeletedEventPayload represents the payload of a secret.deleted event, | ||
| // fired when a secret is permanently deleted. | ||
| type SecretDeletedEventPayload struct { | ||
| Handle string `json:"handle"` | ||
| // Revision — see SecretUpdatedEventPayload.Revision. Compared against the same | ||
| // cache so a late deletion cannot evict a secret that was recreated under the | ||
| // same handle after it. | ||
| Revision int64 `json:"revision"` | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline platform-api/internal/model/secret.go --items all
rg -n -C 5 'Revision|isStaleSecretEvent|secretRevisionCache|SecretDeletedEventPayload' \
platform-api/internal/model/secret.go \
platform-api/internal/service/secret_service.go \
gateway/gateway-controller/pkg/controlplane/client.go \
gateway/gateway-controller/pkg/controlplane/events.goRepository: wso2/api-platform
Length of output: 19590
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '180,285p' platform-api/internal/service/secret_service.go
sed -n '384,100p' platform-api/internal/store/postgres/secret_repository.go
sed -n '70,220p' platform-api/internal/store/postgres/secret_repository.go
printf '\n--- create/update/delete helpers references ---\n'
rg -n -C 8 'CreatedAt|UpdatedAt|UnixNano|Find[ ]+\\(|Get[ ]+\\(|Create\\(|Update\\(' platform-api/internal/store platform-api/internal/service | sed -n '1,260p'Repository: wso2/api-platform
Length of output: 3706
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate secret repositories ---'
git ls-files | rg 'secret.*repository|secret.*store|postgres.*secret|sql/.*secret|*.sql' | rg -i 'secret' | sed -n '1,120p'
printf '%s\n' '--- Update/GetByHandle/Create definitions'
rg -n -C 8 'func .*GetByHandle|func .*Create|func .*Update|type Secret\s+\{|CreatedAt\s+|UpdatedAt\s+' platform-api/internal platform-api/api | sed -n '1,360p'
printf '%s\n' '--- Create flow around SecretCreate'
rg -n -C 12 'func \(s \*SecretService\) Create|NewSecret|Created_at|CreatedAt|UpdatedAt' platform-api/internal/service/secret_service.go platform-api/internal/model platform-api/api | sed -n '1,280p'Repository: wso2/api-platform
Length of output: 411
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate secret repository files ---'
git ls-files | rg -i 'secret.*(repository|store|reposit)|.*postgres|.*sql' | rg -i 'secret' | sed -n '1,160p'
printf '%s\n' '--- Create/GetByHandle/Update definitions ---'
rg -n -C 12 'func \(s \*SecretService\) Create|func .*GetByHandle|func .*Update|func .*FindRefsAndDelete|NewSecret|CreatedAt\s+|UpdatedAt\s+' platform-api/internal/service/secret_service.go platform-api/internal/model platform-api/internal/store platform-api/internal/dto | sed -n '1,380p'
printf '%s\n' '--- exact Update implementation ---'
sed -n '190,270p' platform-api/internal/service/secret_service.goRepository: wso2/api-platform
Length of output: 36046
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '88,185p' platform-api/internal/service/secret_service.go
rg -n -C 12 'type Secret struct|CreatedAt|UpdatedAt|db:"created_at"|db:"updated_at"|Update\(|FindRefsAndDelete|GetByHandle' platform-api/internal platform-api/api --glob '*.go' --glob '*.sql' | sed -n '1,320p'Repository: wso2/api-platform
Length of output: 31888
🌐 Web query:
GORM callback create UpdatedAt Before Create Now() documentation
💡 Result:
In GORM, you do not need to manually create a callback to set CreatedAt or UpdatedAt to the current time, as this is handled automatically by GORM’s built-in functionality [1]. Automatic Time Tracking GORM tracks creation and update times by convention if your model includes fields named CreatedAt and UpdatedAt [1]. GORM will automatically populate these fields with the current time during create and update operations [1]. You can control this behavior using GORM tags in your model struct [1]: - autoCreateTime: Automatically sets the field to the current time upon creation [1]. - autoUpdateTime: Automatically sets the field to the current time upon creation and update [1]. For example: type User struct { CreatedAt time.Time UpdatedAt time.Time // Automatically set/updated by GORM } Custom Callback Behavior If you need custom logic (such as using a specific time source or formatting), you can implement GORM hooks like BeforeCreate [2][3]. Hooks are methods defined on your model that GORM calls during the operation lifecycle [2]. func (u *User) BeforeCreate(tx *gorm.DB) (err error) { // Custom logic before creation u.UpdatedAt = time.Now return } If you must manipulate callbacks directly (e.g., to replace the default timestamping behavior), you can register custom callbacks or remove existing ones via the GORM callback API [4]. However, this is generally unnecessary for standard timestamp management [1]. GORM's built-in callbacks are executed in a specific order, which you can inspect or modify if required [4]. Top results: [2][1][4]
Citations:
- 1: https://gorm.io/docs/models.html
- 2: https://gorm.io/docs/hooks.html
- 3: https://withcodeexample.com/golang-gorm-hooks-guide/
- 4: https://mintlify.wiki/go-gorm/gorm/extending/callbacks
Make recreated secret handles use strictly newer revisions.
SecretDeletedEventPayload depends on recreated handles being newer than deleted predecessors, but secret creation does not set UpdatedAt in SecretService.Create; a later reuse can receive an older/generated timestamp. Set an explicit UTC update timestamp for the recycled handle, and keep isStaleSecretEvent using strict less-than so equal revisions remain idempotent while older deletions are ignored.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@gateway/gateway-controller/pkg/controlplane/events.go` around lines 424 -
432, Update SecretService.Create to assign an explicit current UTC UpdatedAt
timestamp when creating a recycled secret handle, ensuring its revision is
strictly newer than the deleted predecessor. Preserve isStaleSecretEvent’s
strict less-than comparison so equal revisions remain idempotent and older
deletion events are ignored.
| func (c *Client) evictSecretsNotIn(activeHandles map[string]struct{}) int { | ||
| var stale []string | ||
| c.secretHashCache.Range(func(key, _ any) bool { | ||
| handle, ok := key.(string) | ||
| if !ok { | ||
| return true | ||
| } | ||
| if _, ok := activeHandles[handle]; !ok { | ||
| stale = append(stale, handle) | ||
| } | ||
| return true | ||
| }) | ||
|
|
||
| for _, handle := range stale { | ||
| correlationID := utils.GenerateDeterministicUUIDv7(handle, time.Now()) | ||
| if err := c.secretSyncer.Delete(handle, correlationID); err != nil { | ||
| c.logger.Warn("Failed to evict stale secret from local store", | ||
| slog.String("secret_handle", handle), | ||
| slog.String("correlation_id", correlationID), | ||
| slog.Any("error", err), | ||
| ) | ||
| continue | ||
| } | ||
| c.secretHashCache.Delete(handle) | ||
| c.logger.Info("Evicted stale secret from local store during poll sync", | ||
| slog.String("secret_handle", handle), | ||
| slog.String("correlation_id", correlationID), | ||
| ) | ||
| } | ||
|
|
||
| return len(stale) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Evict only handles that were cached before the poll started.
evictSecretsNotIn compares live secretHashCache state against activeHandles, which was derived from a response fetched earlier. secretHashCache is a shared sync.Map. syncSecretRefsFromYAML writes to it from artifact-deployment goroutines, and handleSecretUpdatedEvent writes to it from the WebSocket read loop. A handle added between the FetchPlatformSecrets call and this eviction is absent from activeHandles, so the poll deletes a secret that was just fetched for a live artifact. Resolution for that artifact then fails until the next poll restores it.
Snapshot the cache keys before the fetch and restrict eviction to that snapshot.
🛠️ Proposed fix
- metas, err := c.apiUtilsService.FetchPlatformSecrets(nil, false)
+ preFetchHandles := c.cachedSecretHandles()
+ metas, err := c.apiUtilsService.FetchPlatformSecrets(nil, false)
if err != nil {
c.logger.Error("Failed to fetch platform secrets metadata", slog.Any("error", err))
return
}- evicted := c.evictSecretsNotIn(activeHandles)
+ evicted := c.evictSecretsNotIn(activeHandles, preFetchHandles)-func (c *Client) evictSecretsNotIn(activeHandles map[string]struct{}) int {
+// cachedSecretHandles snapshots the handles currently in secretHashCache.
+func (c *Client) cachedSecretHandles() map[string]struct{} {
+ snapshot := make(map[string]struct{})
+ c.secretHashCache.Range(func(key, _ any) bool {
+ if handle, ok := key.(string); ok {
+ snapshot[handle] = struct{}{}
+ }
+ return true
+ })
+ return snapshot
+}
+
+// candidates limits eviction to handles cached before the poll response was
+// fetched, so a handle added concurrently is never mistaken for a stale one.
+func (c *Client) evictSecretsNotIn(activeHandles, candidates map[string]struct{}) int {
var stale []string
c.secretHashCache.Range(func(key, _ any) bool {
handle, ok := key.(string)
if !ok {
return true
}
+ if _, eligible := candidates[handle]; !eligible {
+ return true
+ }
if _, ok := activeHandles[handle]; !ok {
stale = append(stale, handle)
}
return true
})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@gateway/gateway-controller/pkg/controlplane/sync_secrets.go` around lines 189
- 220, Snapshot the keys in secretHashCache before FetchPlatformSecrets begins,
then pass that snapshot into evictSecretsNotIn and only consider handles present
in it for eviction. Update the sync flow and evictSecretsNotIn signature
accordingly, preserving the existing deletion and logging behavior while
preventing handles added during the fetch from being removed.
| for _, handle := range stale { | ||
| correlationID := utils.GenerateDeterministicUUIDv7(handle, time.Now()) | ||
| if err := c.secretSyncer.Delete(handle, correlationID); err != nil { | ||
| c.logger.Warn("Failed to evict stale secret from local store", | ||
| slog.String("secret_handle", handle), | ||
| slog.String("correlation_id", correlationID), | ||
| slog.Any("error", err), | ||
| ) | ||
| continue | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect GenerateDeterministicUUIDv7 and its existing call sites.
set -euo pipefail
ast-grep run --pattern 'func GenerateDeterministicUUIDv7($$$) { $$$ }' --lang go gateway/gateway-controller/pkg/utils
rg -nP --type=go -C 3 '\bGenerateDeterministicUUIDv7\s*\('Repository: wso2/api-platform
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
git ls-files | rg 'sync_secrets\.go|utils\.go|uuid|correlation|GenerateDeterministicUUIDv7' || true
echo "== find function by text =="
rg -n --type=go -C 4 'GenerateDeterministicUUIDv7|DeterministicUUIDv7|deterministic.*UUID|UUIDv7|UUID.*v7' . || true
echo "== target file excerpt if present =="
if [ -f gateway/gateway-controller/pkg/controlplane/sync_secrets.go ]; then
nl -ba gateway/gateway-controller/pkg/controlplane/sync_secrets.go | sed -n '170,225p'
fiRepository: wso2/api-platform
Length of output: 31695
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== gateway utils GenerateDeterministicUUIDv7 =="
sed -n '80,115p' gateway/gateway-controller/pkg/utils/commonutils.go
echo "== platform-api GenerateDeterministicUUIDv7 =="
sed -n '526,550p' platform-api/internal/utils/common.go
echo "== relevant deterministic UUID test cases =="
sed -n '137,174p' gateway/gateway-controller/pkg/utils/api_deployment_test.go
sed -n '230,262p' gateway/gateway-controller/pkg/controlplane/sync_test.go
echo "== retry/poll context for secret eviction (if present) =="
rg -n --type=go -C 4 'stale|secretSyncer\.Delete|GenerateDeterministicUUIDv7\(handle' gateway/gateway-controller/pkg/controlplane/sync_secrets.go gateway/gateway-controller/pkg/controlplane || trueRepository: wso2/api-platform
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== sync polling/eviction call context =="
rg -n --type=go -C 4 'evictSecretsNotIn|Start.*poll|poll|Ticker|Ticker\(|time\.Ticker|ticker\.' gateway/gateway-controller/pkg/controlplane || true
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp()*1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for ns in [0, 500_000_000, 999_000_000, 999_999_999]:
t = base + datetime.timedelta(nanoseconds=ns)
print(base.isoformat(), "+", ns, "ns ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PYRepository: wso2/api-platform
Length of output: 18094
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp()*1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry/test around delete error keeps same correlation seed if same time still in same millisecond =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform
Length of output: 393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform
Length of output: 393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return ("{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}").format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7(" handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PYRepository: wso2/api-platform
Length of output: 336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
parts = (
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(*parts)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform
Length of output: 336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return uuid7_bytes(bytes(b))
def uuid7_bytes(b):
parts = ["{:08x}".format(struct.unpack(">I", b[:4])[0]),
"{:04x}".format(struct.unpack(">H", b[4:6])[0]),
"{:04x}".format(b[6] << 8 | b[7]),
"{:02x}{:02x}".format(b[8], b[9]),
"{:02x}{:02x}".format(struct.unpack(">H", b[10:12])[0]),
"{:02x}{:02x}".format(struct.unpack(">H", b[12:14])[0]),
"{:02x}{:02x}".format(struct.unpack(">H", b[14:16])[0])]
return "-".join(parts)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform
Length of output: 378
Use per-attempt entropy for stale-secret eviction correlation IDs.
GenerateDeterministicUUIDv7 truncates the UUIDv7 timestamp to milliseconds and derives the rest of the UUID from the handle hash. A retry within the same millisecond for the same secret handle can reuse the same correlation_id, so separate eviction attempts can appear merged in logs. Use a non-deterministic or per-attempt value for this retry path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@gateway/gateway-controller/pkg/controlplane/sync_secrets.go` around lines 202
- 211, Update the stale-secret eviction loop around Delete to generate a
non-deterministic, per-attempt correlation ID instead of calling
GenerateDeterministicUUIDv7 with handle and time.Now(). Keep the existing
correlation_id logging and Delete behavior unchanged.
| <IconButton | ||
| size="small" | ||
| onClick={() => setShowValue((prev) => !prev)} | ||
| aria-label={showValue ? 'Hide value' : 'Show value'} | ||
| tabIndex={-1} | ||
| > |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore keyboard access to the visibility control.
Line 184 removes the IconButton from the tab order. Keyboard users cannot focus or activate the show/hide control.
Proposed fix
- tabIndex={-1}📝 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.
| <IconButton | |
| size="small" | |
| onClick={() => setShowValue((prev) => !prev)} | |
| aria-label={showValue ? 'Hide value' : 'Show value'} | |
| tabIndex={-1} | |
| > | |
| <IconButton | |
| size="small" | |
| onClick={() => setShowValue((prev) => !prev)} | |
| aria-label={showValue ? 'Hide value' : 'Show value'} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@portals/ai-workspace/src/Components/common/SecretValueField.tsx` around lines
180 - 185, Restore keyboard access to the visibility control by removing the
tabIndex={-1} override from the IconButton in SecretValueField. Preserve its
existing show/hide behavior, aria-label, and styling while allowing the button
to participate in normal keyboard tab navigation.
| await updateSecret(handle, { | ||
| value: value.trim(), | ||
| name: displayName.trim() || undefined, | ||
| description: description.trim() || undefined, | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve explicit metadata edits.
A blank description becomes undefined, and updateSecret then omits it from the request. Users cannot clear an existing description. A blank display name also becomes a silent no-op instead of a validation error.
Validate a non-empty display name. Preserve '' for description and update updateSecret to append fields when they are !== undefined.
Proposed fix
- name: displayName.trim() || undefined,
- description: description.trim() || undefined,
+ name: displayName.trim(),
+ description: description.trim(),- if (request.name) form.append('displayName', request.name);
- if (request.description) form.append('description', request.description);
+ if (request.name !== undefined) form.append('displayName', request.name);
+ if (request.description !== undefined) form.append('description', request.description);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@portals/ai-workspace/src/pages/appShell/appShellPages/secret/RotateSecret.tsx`
around lines 102 - 106, Update the RotateSecret submission flow to validate that
the trimmed display name is non-empty, while preserving a trimmed empty
description as ''. In updateSecret, append metadata fields whenever their values
are !== undefined so an explicit empty description is included in the request
rather than omitted.
…lete, add secret usage rest API
fc1bd91 to
3280af5
Compare
Purpose
Platform API already supports encrypting upstream credentials for LLM providers, LLM proxies, MCP proxies, and REST APIs via {{ secret "handle" }} placeholders (added in #2149, but two gaps made that feature incomplete in practice:
Resolves:
#2149
Goals
Approach
Backend (Go, platform-api):
dto.UpstreamTarget gained an Auth field, and BuildAPIDeploymentYAML now copies it through — the deployment-YAML fix.
Backend (Go, gateway-controller):
Frontend (AI Workspace):
UIs
User Stories
Documentation
https://github.com/wso2/api-platform/blob/main/docs/rest-apis/platform-api/secrets.md
Automation Tests
Related PRs
Refer the PRs mentioned in #2149
Test Environment