Skip to content

[HYPERSHELL-49] feat: openshell service accounts - #180

Merged
jsell-rh merged 33 commits into
mainfrom
spec/machine-account-client-secret
Aug 24, 2026
Merged

[HYPERSHELL-49] feat: openshell service accounts#180
jsell-rh merged 33 commits into
mainfrom
spec/machine-account-client-secret

Conversation

@jsell-rh

@jsell-rh jsell-rh commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Define the gateway-scoped OpenShellGatewayServiceAccount API and its Keycloak confidential-client lifecycle.
  • Cap the selected OpenShell role by the creator current gateway binding: owners can select user or admin, while viewers can select only user.
  • Add browser-safe role and expiration capabilities, server-side collection behavior, and repeatable non-secret setup metadata.
  • Add Service accounts between the existing Connection and Details tabs, with a management table, create flow, and one-time credential handoff.
  • Provide safe copyable OpenShell CLI and Client Credentials JWT flows without embedding the client secret or replacing interactive gateway login.
  • Specify credential expiration, short-lived access tokens, gateway audience isolation, workspace grants, replacement, revocation, deletion, and audit redaction.

Scope

This specification covers the client-secret flow only. WIF, multi-gateway credentials, configurable scopes, and in-place secret rotation remain deferred.

Validation

  • make check
  • git diff --check origin/main...HEAD

Jira: https://redhat.atlassian.net/browse/HYPERSHELL-49

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 03214a51-25da-43d2-9d9a-0a8e45224b3b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@jsell-rh jsell-rh changed the title [HYPERSHELL-49] spec: define gateway machine accounts [HYPERSHELL-49] spec: define OpenShellGatewayServiceAccount Aug 21, 2026
@jsell-rh jsell-rh changed the title [HYPERSHELL-49] spec: define OpenShellGatewayServiceAccount [HYPERSHELL-49] feat: openshell service accounts Aug 21, 2026

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Security-sensitive details such as one-time secret redaction, exact gateway binding checks, least-privilege Keycloak roles, strict input parsing, and cache avoidance are handled thoughtfully, but the service-account reconciler is not serialized with foreground lifecycle operations. As written it can re-enable a successfully revoked credential, delete an in-flight create, miss a concurrent create during gateway cleanup, and permanently revoke accounts on transient dependency failures; the console and CLI also have one-time-secret loss paths.

Overall assessment: REQUEST_CHANGES

This is a self-review, so GitHub does not permit the PR author to submit a REQUEST_CHANGES event; the formal review is submitted as COMMENT and the amber/changes-requested label records the assessment.

Blocker

  1. components/api-server/plugins/serviceAccounts/service.go:605 — A stale reconciliation snapshot can re-enable a client after Revoke has completed and overwrite Revoked back to Ready. Fix with shared lifecycle serialization plus re-read/CAS before enable and save. Confidence: High (100%).

Major

  1. components/api-server/plugins/serviceAccounts/service.go:503 — The reconciler treats every Provisioning row as abandoned and can clean up a live create. Use a shared lock/lease and a proven-stale threshold. Confidence: High (100%).
  2. components/api-server/plugins/serviceAccounts/service.go:394 — Gateway cleanup is not serialized with Create, so a client can be provisioned after the cleanup scan and while the gateway row is deleted. Lock cleanup plus deletion and re-read readiness inside Create's lock. Confidence: High (99%).
  3. components/api-server/plugins/serviceAccounts/service.go:579 — Internal gateway lookup and temporary OIDC-readiness failures are collapsed into terminal unavailability and permanently revoke accounts. Reserve permanent revocation for confirmed deletion. Confidence: High (100%).
  4. components/api-server/pkg/keycloak/service_accounts.go:185 — Every steady-state sweep disables and rebuilds healthy clients; later failures leave Keycloak disabled while persistence still says Ready. Diff before mutation and persist a truthful reconciliation state. Confidence: High (100%).
  5. components/api-server/plugins/serviceAccounts/dao.go:129 — A sequential all-status scan cannot bound expiration enforcement to the required one minute. Prioritize due/transitional work and separate bounded drift/orphan scheduling. Confidence: High (97%).
  6. packages/gateway-management-ui/src/service-accounts/service-account-create-dialog.tsx:205 — Tab/router/browser navigation bypasses the one-time-secret loss confirmation (UI-INT-04, UI-PERF-04, UI-TRUST-05). Protect navigation and pending completion. Confidence: High (99%).
  7. components/cli/cmd/hypershell/create/serviceAccount/cmd.go:86 — The CLI validates the exclusive output file after creation, so predictable local write errors discard the only secret. Reserve the target before POST. Confidence: High (100%).

Verification

  • Passed repository policy checks (make check).
  • Passed service-account and Keycloak tests under the race detector.
  • Passed control-plane, CLI, Go SDK, SDK/CLI generator, and focused API unit suites.
  • Passed the complete web check pipeline: formatting, architecture checks, lint, type checks, 344 tests, i18n, production build, and Storybook build.
  • The full API integration sweep could not run because the local PostgreSQL service rejected the repository test credentials; unaffected unit packages, including serviceAccounts and keycloak, passed.
  • GitHub image builds are successful; enterprise-contract checks are neutral. The PR is also currently draft and conflicts with the advanced main branch.

— Amber

Findings Summary (ordered by severity, highest first):

  1. [Blocker] Stale reconciliation can re-enable a successfully revoked credential and restore Ready state - Security / Reconciliation (L605)
  2. [Major] Reconciliation can delete a live provisioning request - Concurrency / Reconciliation (L503)
  3. [Major] Gateway cleanup can miss a concurrent service-account creation - Lifecycle Consistency (L394)
  4. [Major] Transient gateway or OIDC failures permanently revoke credentials - Reliability / Reconciliation (L579, L702)
  5. [Major] Routine reconciliation disrupts healthy clients and reports failed clients as Ready - Availability / State Integrity (L185, L436)
  6. [Major] The all-status sequential scan cannot guarantee the one-minute expiration deadline - Security / Scheduling (L129)
  7. [Major] Console navigation can discard an unacknowledged one-time secret without confirmation - UI Safety (L205)
  8. [Major] CLI output-file errors can discard the only secret after account creation - Secret Handoff (L86)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Fail
Typed not-found handling distinguishes 404 from internal failure Fail
No secrets in logs or error messages Pass
Input validated Pass
Reconciliation is convergent and safe to retry Fail
OpenAPI SDK artifacts generated rather than hand-edited Pass
Conventional commit intent present in squash title Pass

Comment thread components/api-server/plugins/serviceAccounts/service.go Outdated
Comment thread components/api-server/plugins/serviceAccounts/service.go
Comment thread components/api-server/plugins/serviceAccounts/service.go Outdated
Comment thread components/api-server/plugins/serviceAccounts/service.go
Comment thread components/control-plane/internal/serviceaccountkeycloak/client.go Outdated
Comment thread components/api-server/plugins/serviceAccounts/dao.go Outdated
Comment thread components/cli/cmd/hypershell/create/serviceAccount/cmd.go
@jsell-rh jsell-rh added amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR. amber/changes-requested Amber requested changes on this PR labels Aug 21, 2026

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The two commits since the previous Amber pass improve the service-account setup UI and move Keycloak administration behind an mTLS control-plane boundary, but all eight prior findings remain unresolved and the moved reconciler can still re-enable a credential after a successful revoke. The new boundary also accepts unscoped Keycloak client UUID mutations and both peers pin cert-manager certificates in memory, so the assessment remains REQUEST_CHANGES.

Overall assessment: REQUEST_CHANGES

This is a self-review, so GitHub does not permit the PR author to submit a REQUEST_CHANGES event; the formal review is submitted as COMMENT and the amber/changes-requested label records the assessment.

Prior-review status

I re-read the formal Amber review at commit 20531fb and all eight inline threads before reviewing commits 55e1170 and faa81fb. All eight threads are unresolved: seven remain current at their existing anchors, while the prior Blocker is outdated only because its call site moved, so I re-anchored that one below and did not duplicate the other active threads.

Blocker

  1. components/control-plane/internal/serviceaccountprovisioner/server.go:91; components/control-plane/internal/serviceaccountkeycloak/client.go:214 — The authenticated RPC accepts a caller-supplied Keycloak UUID and the provider verifies only that some client exists before disabling or deleting it. Unlike reconciliation, these methods do not require the HyperShell managed marker or matching gateway and service-account ownership, so a corrupted row or code path with access to the API client can turn the realm-admin boundary into a delete primitive for gateway, console, or unrelated clients. Fix: accept immutable resource IDs and resolve the managed client server-side, or verify the managed marker and exact ownership metadata before either mutation; reject non-managed clients and add negative tests. Confidence: High (98%).
  2. components/api-server/plugins/serviceAccounts/service.go:638,653 — Reconciliation still acts on an unlocked snapshot. If Revoke completes while this row is paused, this call enables the client and the final update restores the stale Ready state, allowing a credential reported as revoked to mint tokens again. Fix: share lifecycle serialization across reconciliation and foreground mutations, re-read desired state before enabling, and make the final write a status/version CAS; add a barrier-driven regression test. Confidence: High (100%).

Major

  1. components/api-server/plugins/serviceAccounts/service.go:536 — Every Provisioning row is treated as abandoned even though Create persists that state while a live request is still provisioning and delivering its one-time secret. Fix: use the same lifecycle lock or persisted lease in both paths and reclaim only after a proven-stale deadline; test a slow create against reconciliation. Confidence: High (100%).
  2. components/api-server/plugins/serviceAccounts/service.go:427; components/api-server/plugins/serviceAccounts/service.go:185 — Gateway cleanup does not share the create lock, while Create checks gateway readiness before taking that lock. Cleanup can finish its scan and a paused create can then provision a credential for a gateway being deleted. Fix: hold a shared per-gateway lock through cleanup and gateway-row deletion, and re-read gateway existence and readiness after Create obtains it. Confidence: High (99%).
  3. components/api-server/plugins/serviceAccounts/service.go:612,739 — Any gateway problem becomes terminal revocation, but gateway converts every service error to not-found and temporary or malformed OIDC metadata also produces a problem. Fix: preserve confirmed 404 versus retryable internal and readiness failures; only confirmed deletion should permanently revoke. Test all three paths. Confidence: High (100%).
  4. components/control-plane/internal/serviceaccountkeycloak/client.go:185; components/api-server/plugins/serviceAccounts/service.go:473 — Every ordinary sweep disables a healthy client before rebuilding it. A later provider failure leaves Keycloak disabled while the API persists the original Ready state with only LastError changed. Fix: diff actual and desired state before mutation, perform zero writes when converged, and persist a truthful fail-closed reconciliation state when a mutation fails. Confidence: High (100%).
  5. components/api-server/plugins/serviceAccounts/dao.go:129 — The oldest 1,000 rows across all statuses are processed sequentially, so due expirations can be starved beyond the specified one-minute disablement bound. Fix: query due and transitional work first, process safety-critical disables with bounded concurrency, and paginate ordinary drift and orphan work separately. Confidence: High (97%).
  6. packages/gateway-management-ui/src/service-accounts/service-account-create-dialog.tsx:208; packages/gateway-management-ui/src/pages/gateway-pages.tsx:626 — The loss confirmation protects only modal-close events. Tab selection, route navigation, Back, refresh, and unmount can discard a pending or unacknowledged one-time secret, violating UI-INT-04, UI-PERF-04, and UI-TRUST-05. Fix: guard navigation and beforeunload or hoist the ephemeral handoff above the unmounting tab, then test tab, Back, refresh, and pending-completion paths. Confidence: High (99%).
  7. components/cli/cmd/hypershell/create/serviceAccount/cmd.go:86 — The POST happens before the exclusive output target is opened, so an existing or unwritable file discards the only secret after creating a live account. Fix: reserve a mode-0600 target before the request, pass its handle through the write path, and remove the empty reservation if no account is created. Confidence: High (100%).
  8. components/api-server/plugins/serviceAccounts/provisioner_client.go:50; components/control-plane/internal/serviceaccountprovisioner/transport.go:60 — Both processes load their leaf certificate and private key only at startup. cert-manager rotates the mounted Secrets, but no TLS callback, watcher, checksum rollout, or reloader consumes the new files, so reconnects eventually fail after the in-memory certificates expire and service-account create, revoke, delete, and expiration work remains unavailable until restart. Fix: dynamically reload certificates with GetCertificate and GetClientCertificate or trigger deterministic rollouts on Secret renewal; test renewal and reconnect without manual restart. Confidence: High (98%).

UI requirement coverage

Requirement IDs Result Evidence
UI-INT-04, UI-PERF-04, UI-TRUST-05 FAIL One-time-secret loss protection does not cover tab, router, browser, or unmount navigation.
UI-A11Y-01 through UI-A11Y-10 NOT_TESTED No interactive keyboard, screen-reader, zoom/reflow, touch, voice, or disabled-user session was available.
UI-VER-01 through UI-VER-06 PARTIAL Automated source, unit, build, and Storybook evidence passed; no declared browser or representative-user matrix was executed.
UI-PF-01, UI-PF-02, UI-PF-05, UI-PF-09; UI-CONTENT-04; UI-HEX-01, UI-HEX-02 PASS Source inspection plus formatting, lint, type, architecture, localization, and component tests passed.

Verification

  • Passed repository policy checks with make check and passed git diff --check.
  • Passed API service-account and control-plane service-account packages under the Go race detector.
  • Passed the complete control-plane test and vet suites, API vet, and the complete CLI test suite.
  • Passed the gateway-management UI check pipeline: formatting, lint, type checks, 159 tests, coverage, and build checks.
  • Passed the web-console check pipeline: formatting, architecture, lint, type checks, 67 tests, localization, production build, and Storybook build.
  • Rendered deploy/base, deploy/kind, deploy/openshift, and deploy/ibm successfully.
  • The full API integration sweep remains blocked because local PostgreSQL rejects the repository test credentials; affected unit packages pass. No live Keycloak, certificate-renewal, browser, assistive-technology, or representative-user session was available.
  • GitHub image checks are successful and enterprise-contract checks are neutral. The PR is still draft and currently conflicts with main.

— Amber

Findings Summary (ordered by severity, highest first):

  1. [Blocker] Privileged UUID mutations can disable or delete non-managed Keycloak clients - Security / Authorization Boundary (L91, L214)
  2. [Blocker] Stale reconciliation can re-enable a successfully revoked credential and restore Ready state - Security / Reconciliation (L638, L653)
  3. [Major] Reconciliation can delete a live provisioning request - Concurrency / Reconciliation (L536)
  4. [Major] Gateway cleanup can miss a concurrent service-account creation - Lifecycle Consistency (L427, L185)
  5. [Major] Transient gateway or OIDC failures permanently revoke credentials - Reliability / Reconciliation (L612, L739)
  6. [Major] Routine reconciliation disrupts healthy clients and reports failed clients as Ready - Availability / State Integrity (L185, L473)
  7. [Major] The all-status sequential scan cannot guarantee the one-minute expiration deadline - Security / Scheduling (L129)
  8. [Major] Console navigation can discard an unacknowledged one-time secret without confirmation - UI Safety (L208, L626)
  9. [Major] CLI output-file errors can discard the only secret after account creation - Secret Handoff (L86)
  10. [Major] cert-manager renewals are ignored until both provisioner peers restart - Availability / Certificate Lifecycle (L50, L60)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Fail
Typed not-found handling distinguishes 404 from internal failure Fail
No secrets in logs or error messages Pass
Privileged mutation targets validate managed ownership Fail
mTLS certificates rotate without manual restart Fail
Reconciliation is convergent and safe to retry Fail
SecurityContext present on pod workloads Pass
Image references are registry-qualified Pass
Generated OpenAPI and protobuf artifacts are updated Pass
Conventional commit intent present Pass

Comment thread components/control-plane/internal/serviceaccountprovisioner/server.go Outdated
Comment thread components/api-server/plugins/serviceAccounts/service.go
Comment thread components/api-server/plugins/serviceAccounts/provisioner_client.go Outdated
jsell-rh pushed a commit that referenced this pull request Aug 24, 2026
Address PR #180 review: a paused reconciliation could observe a stale scan
snapshot and re-enable a credential a concurrent Revoke/Delete had already
retired, and a mid-reconcile provider failure left a record asserting Ready.

- Serialize every lifecycle mutation (Create, Revoke, Delete, gateway
  cleanup) and per-account reconciliation through the shared per-gateway
  advisory lock; Revoke/Delete/reconcile re-read committed state under the
  lock so decisions never act on a pre-lock snapshot.
- Make the reconciler's terminal write a compare-and-set on the observed
  status (ConditionalUpdate) so a superseded reconciliation yields instead
  of restoring stale state.
- Split reconciliation scans into due/transitional and drift lists taken
  from one snapshot so the two are disjoint and nothing is double-processed.
- Reclaim abandoned Provisioning rows only after a stale deadline so an
  in-flight one-time-secret delivery is never destroyed.
- Revoke on a confirmed gateway 404 only; treat transient lookup failures
  and not-yet-ready OIDC as retryable without winding down the credential.
- Add StatusDegraded so a previously-ready account whose reconcile mutation
  failed reports a truthful non-Ready state (never triggering credential
  removal) and re-converges on the next sweep; extend the OpenAPI enum and
  regenerate the client.
- Cover the above with regression tests: stale-snapshot no-reenable,
  provisioning reclaim deadline, confirmed-vs-transient gateway failure,
  degrade-then-recover, and lock acquisition across every path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jsell-rh pushed a commit that referenced this pull request Aug 24, 2026
…t secret

Address PR #180 review: the client secret is shown exactly once, but an
in-app tab switch or a browser refresh/close could unmount the create dialog
and discard an unacknowledged secret (or a create still in flight) with no
warning.

- Expose a leave guard from the create dialog and register it with the
  gateway detail host so a tab switch is deferred, the existing loss
  confirmation is shown, and navigation resumes only on confirmation.
- Arm the browser's native beforeunload prompt while a create is pending or
  a secret is unacknowledged, covering refresh, tab close, and hard nav.
- Cover both the in-app and browser-level guards with tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
user and others added 20 commits August 24, 2026 10:35
Blocker: destructive Keycloak operations (disable/delete) now verify the
HyperShell managed marker before mutating, returning ErrNotManaged for any
client the platform does not own. Map that to gRPC PermissionDenied.

Major: ReconcileServiceAccount diffs desired vs. observed state and performs
zero writes when already converged, only disabling/repairing/enabling on
detected drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both provisioner mTLS peers now load their key pair through a mtime-cached
reloader wired via GetCertificate/GetClientCertificate, so cert-manager
in-place rotation is picked up per handshake without a process restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI now exclusively reserves the output file before issuing the create
request and releases it on failure, so a one-time client secret is never
lost to an unwritable or pre-existing target after the account exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address PR #180 review: a paused reconciliation could observe a stale scan
snapshot and re-enable a credential a concurrent Revoke/Delete had already
retired, and a mid-reconcile provider failure left a record asserting Ready.

- Serialize every lifecycle mutation (Create, Revoke, Delete, gateway
  cleanup) and per-account reconciliation through the shared per-gateway
  advisory lock; Revoke/Delete/reconcile re-read committed state under the
  lock so decisions never act on a pre-lock snapshot.
- Make the reconciler's terminal write a compare-and-set on the observed
  status (ConditionalUpdate) so a superseded reconciliation yields instead
  of restoring stale state.
- Split reconciliation scans into due/transitional and drift lists taken
  from one snapshot so the two are disjoint and nothing is double-processed.
- Reclaim abandoned Provisioning rows only after a stale deadline so an
  in-flight one-time-secret delivery is never destroyed.
- Revoke on a confirmed gateway 404 only; treat transient lookup failures
  and not-yet-ready OIDC as retryable without winding down the credential.
- Add StatusDegraded so a previously-ready account whose reconcile mutation
  failed reports a truthful non-Ready state (never triggering credential
  removal) and re-converges on the next sweep; extend the OpenAPI enum and
  regenerate the client.
- Cover the above with regression tests: stale-snapshot no-reenable,
  provisioning reclaim deadline, confirmed-vs-transient gateway failure,
  degrade-then-recover, and lock acquisition across every path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the new server-side StatusDegraded so a previously-ready account
whose reconciliation failed part-way is shown truthfully instead of as a
clean Ready credential.

- Add "degraded" to the service-account status union and its status label.
- Reuse the existing warning appearance already mapped for degraded.
- Poll degraded rows like other transitional states so the list reflects
  re-convergence without a manual refresh.
- Extract the new locale message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t secret

Address PR #180 review: the client secret is shown exactly once, but an
in-app tab switch or a browser refresh/close could unmount the create dialog
and discard an unacknowledged secret (or a create still in flight) with no
warning.

- Expose a leave guard from the create dialog and register it with the
  gateway detail host so a tab switch is deferred, the existing loss
  confirmation is shown, and navigation resumes only on confirmation.
- Arm the browser's native beforeunload prompt while a create is pending or
  a secret is unacknowledged, covering refresh, tab close, and hard nav.
- Cover both the in-app and browser-level guards with tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…disable/delete

The UUID-targeted Disable and Delete provisioner paths only confirmed the
HyperShell managed marker before mutating a Keycloak client. A stale or
mismatched stored UUID could therefore disable or delete a managed client
belonging to a different gateway or service account.

Thread the owning gateway_id and service_account_id through the Disable and
Delete RPCs and enforce, in the control-plane Keycloak client, that the target
client carries both the managed marker and the exact ownership attributes
before either mutation. Every api-server call site already has the owner IDs in
scope, so the guard is exact for foreground revoke/delete, reconciliation, and
orphan cleanup alike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jsell-rh
jsell-rh force-pushed the spec/machine-account-client-secret branch from c1bf83b to 6fc5b8c Compare August 24, 2026 14:41

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The seven follow-up commits fix the unscoped Keycloak mutation, stale-reconcile, live-provisioning, transient-dependency, steady-state outage, CLI handoff, and leaf-certificate defects from the last Amber round. The assessment remains REQUEST_CHANGES because security-sensitive Keycloak drift can now be accepted as converged, gateway deletion and expiry enforcement still have lifecycle gaps, and the UI, CA rotation, and public-status contract are only partially propagated.

Overall assessment: REQUEST_CHANGES

This is a self-review, so GitHub does not permit the PR author to submit a REQUEST_CHANGES event; the formal review is submitted as COMMENT and the amber/changes-requested label records the assessment.

Prior-review status

I re-read both prior formal Amber reviews and all 11 review threads before evaluating commits b8d6ff1..6fc5b8c.

Previous finding Current status
Privileged UUID mutation can target non-managed clients Fixed — destructive operations now require the managed marker and exact stored ownership.
Stale reconcile can re-enable a revoked credential Fixed — shared locking, a locked re-read, and conditional persistence prevent the stale write.
Reconcile can delete a live Provisioning request Fixed — Create and reconciliation share the lock, with a 15-minute reclaim threshold.
Gateway cleanup can miss a concurrent Create PARTIAL — cleanup shares the lock, but gateway-row deletion happens after it is released.
Transient gateway/OIDC failures cause terminal revocation Fixed — confirmed 404 is now separated from retryable dependency/readiness failures.
Routine reconciliation disables healthy clients and reports failure as Ready Fixed for the original defect — no-write convergence and degraded state address the outage/state lie; the new convergence predicate has a separate Blocker below.
Expiration scan cannot prove the one-minute bound PARTIAL — due work is prioritized, but remains capped and sequential.
Navigation can discard the one-time secret PARTIAL — tab clicks and hard unload are guarded; SPA route/history navigation is not.
CLI output-file failure loses the only secret Fixed — the exclusive mode-0600 target is reserved before POST.
cert-manager renewal requires restart PARTIAL — leaf key pairs reload, but CA trust anchors remain static.

Blocker

  1. components/control-plane/internal/serviceaccountkeycloak/client.go:238,328reconcileConverged cannot see fullScopeAllowed, public-client/service-account/interactive-grant flags, redirect origins, or default scopes because kcClient does not deserialize them. Its mapper comparison also accepts an extra included.custom.audience; the zero-write return therefore preserves security-broadening drift that the product specification explicitly requires reconciliation to remove. Fix: compare an exact normalized security representation and exact mapper configs, including absent custom audiences and disabled device/CIBA grants, and add full-scope/custom-audience drift tests. Confidence: High (100%).

Major

  1. components/api-server/plugins/serviceAccounts/service.go:478; components/api-server/plugins/gateways/service.go:213CleanupGateway releases the shared lock before gateways.Delete removes the gateway row. A blocked Create can resume in that gap, see the still-healthy gateway, provision a live identity, and then have the gateway deleted. Fix: hold the same lifecycle barrier through final gateway deletion, or persist a deleting state under the barrier that Create rejects; add the exact blocked-Create interleaving test. Confidence: High (100%).
  2. components/api-server/plugins/serviceAccounts/dao.go:165; components/api-server/plugins/serviceAccounts/service.go:539 — due rows are now prioritized, but only 1,000 are selected and processed sequentially under a 30-second cycle context. More than one page of due accounts or ordinary provider latency still defeats the specified one-minute disablement guarantee. Fix: drain/page due work independently with bounded concurrency or a deadline-aware worker queue, and prove the bound with a high-latency, multi-gateway test. Confidence: High (98%).
  3. packages/gateway-management-ui/src/pages/gateway-pages.tsx:545; components/web-console/app/routes/gateway.tsx:84 — the new leave guard runs only through changeTab. Browser Back/Forward changes the externally controlled activeTab directly, and navigation to another SPA route unmounts the handoff without firing beforeunload, so a pending or unacknowledged secret can still be lost. Fix: integrate a router-level blocker or hoist the handoff above the route boundary, then test Back and another client-side route (UI-INT-04, UI-INT-07, UI-PERF-04, UI-TRUST-05). Confidence: High (100%).
  4. components/api-server/plugins/serviceAccounts/provisioner_client.go:103; components/control-plane/internal/serviceaccountprovisioner/transport.go:114 — leaf certificates reload, but RootCAs and ClientCAs are read only at startup. The deployed self-signed CA uses rotationPolicy: Always; after CA rollover and leaf reissuance, the peers reject one another until restart. Fix: reload trust bundles for new handshakes or trigger deterministic rollouts on CA/leaf Secret changes, and add a full CA-rollover handshake test. Confidence: High (99%).
  5. components/api-server/openapi/openapi.serviceAccounts.yaml:237 — the public degraded status is incomplete across contract surfaces: the product spec and its polling rule omit it, the web route allowlist omits it, and the checked-in TypeScript SDK omits it. The latter currently breaks the web-console typecheck and generated-SDK drift gate. Fix: define the state semantics in the product spec, update the route parser with a round-trip test, and regenerate every SDK artifact. Confidence: High (100%).

UI requirement coverage

Requirement IDs Result Evidence
UI-INT-04, UI-INT-07, UI-PERF-04, UI-TRUST-05 FAIL SPA history and route navigation can bypass the secret-loss guard.
UI-FND-06 PARTIAL degraded has inconsistent state semantics across the product spec, OpenAPI clients, and route parser.
UI-A11Y-01 through UI-A11Y-10; UI-FND-01 through UI-FND-02; UI-VER-02, UI-VER-05 through UI-VER-06; UI-PERF-01, UI-PERF-05 NOT_TESTED No browser/assistive-technology matrix, representative-user evidence, or field performance/monitoring evidence was available.
UI-PF-01, UI-PF-02, UI-PF-05, UI-PF-09; UI-CONTENT-04; UI-I18N-01; UI-HEX-01, UI-HEX-02, UI-HEX-05, UI-HEX-06 PASS Source inspection and the package formatting, lint, type, localization, architecture, and component-test evidence support these requirements.

Verification

  • Passed git diff --check.
  • Passed API service-account, control-plane Keycloak/provisioner, and CLI service-account tests under the Go race detector; full go vet ./... passed in all three Go modules.
  • Passed the gateway-management UI check pipeline: formatting, lint, type checks, and 163 tests.
  • Current GitHub E2E Kind, repository policy, Go lint, gateway-management UI, image builds, and CodeRabbit checks pass.
  • The committed head fails the web-console typecheck because the generated TypeScript status union lacks degraded; the OpenAPI SDK drift check fails for the same ungenerated contract change.
  • No live Keycloak drift repair, CA rollover, browser Back/route navigation, assistive-technology, or representative-user session was available.

— Amber

Findings Summary (ordered by severity, highest first):

  1. [Blocker] The zero-write convergence check preserves security-broadening Keycloak client and mapper drift - Security / Reconciliation (L238, L328)
  2. [Major] Gateway cleanup releases its barrier before the gateway row is deleted - Lifecycle Consistency (L478, L213)
  3. [Major] The capped sequential due scan still cannot guarantee one-minute expiration enforcement - Security / Scheduling (L165, L539)
  4. [Major] SPA route and history navigation can still discard the one-time secret - UI Safety (L545, L84)
  5. [Major] CA trust-anchor rotation still requires both provisioner peers to restart - Availability / Certificate Lifecycle (L103, L114)
  6. [Major] The new degraded status is incomplete across the specification, SDK, and route contract - Spec / Generated Artifacts (L237)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Fail
Typed not-found handling distinguishes 404 from internal failure Pass
No secrets in logs or error messages Pass
Privileged mutation targets validate managed ownership Pass
Reconciliation removes broader roles, grants, and audiences Fail
One-minute expiration enforcement is bounded Fail
mTLS leaf and trust-anchor rotation works without manual restart Fail
Generated OpenAPI SDK artifacts are current Fail
One-time UI handoff survives or blocks all navigation paths Fail
Conventional commit intent is present Pass

Comment thread components/control-plane/internal/serviceaccountkeycloak/client.go
Comment thread components/api-server/plugins/serviceAccounts/dao.go
Comment thread components/api-server/plugins/serviceAccounts/provisioner_client.go Outdated
Comment thread components/api-server/openapi/openapi.serviceAccounts.yaml
user and others added 7 commits August 24, 2026 10:54
…tatus

The degraded status was added to the OpenAPI spec and the hand-authored
gateway-management-ui types, but the generated TypeScript and Go SDKs were
never regenerated. This caused the OpenAPI SDK drift check to fail.

Regenerated both SDKs via `make generate-sdk`; the service-account files
gain the degraded enum value and every generated file picks up the updated
spec SHA256 header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o writes

The reconcile zero-write predicate only compared the fields kcClient
deserialized (client ID, name, enabled, attributes), so security-broadening
Keycloak drift passed as converged and skipped repair: a flipped
fullScopeAllowed leaked every realm role into the token, an enabled
interactive/device grant or public-client downgrade widened the authorized
flows, a rogue redirect origin or injected client scope broadened token
contents, and the audience mapper accepted an extra included.custom.audience
that added an unrelated gateway.

Deserialize every security-relevant client field and compare it, add the
device/CIBA grant attributes to the converged attribute set, and reject a
free-form custom audience on the gateway-audience mapper. Any divergence now
fails closed toward the disable-and-repair path. Adds drift regression tests
for each broadening field and for the injected token audience.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etion

CleanupGateway held the per-gateway lifecycle lock only across the
service-account scan-and-delete, releasing it before the gateway row was
deleted. A Create blocked on that lock could then wake, re-read the
still-present gateway as healthy, and provision an orphaned live
credential for a gateway that was being torn down.

Thread the gateway-row deletion into the cleanup barrier as a finalize
callback that CleanupGateway invokes while it still holds the lock, so a
blocked Create only proceeds after the row is gone and then rejects with
not-found. Add a race-enabled interleaving regression test proving a
concurrent Create blocks until deletion completes and provisions nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reconcile pass processed due expirations sequentially, one Keycloak
round-trip at a time, and capped each scan at a single page. Under many
gateways with high per-account latency a single pass could exceed the
one-minute disablement bound the spec requires, and a backlog larger than
one page dribbled out one page per interval instead of draining.

Drain all due and transitional accounts, re-querying until no new rows
remain, and reconcile each page with bounded concurrency
(reconcileDueConcurrency) so a slow round-trip no longer serializes the
whole backlog. Accounts already attempted this cycle are skipped so a
persistently transitional row cannot spin the drain loop; same-gateway
work still serializes on the per-gateway lock. The drift list is
snapshotted up front, preserving its disjointness from the due scan so a
freshly expired account is never disabled twice in one cycle.

Make the in-memory test DAO mutex-guarded (mirroring the production DAO's
pool safety) and add a race-enabled high-latency multi-gateway test
proving the due backlog is drained in parallel and fully cleared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aved

The one-time service-account client secret lives only in the mounted
create-dialog view. Switching detail tabs already consulted a leave guard,
but a SPA route change or browser Back/Forward would silently unmount the
dialog and discard the secret.

Refactor the leave guard from a `(proceed) => boolean` callback into a
`{ shouldBlock, confirmLeave }` object with a `ServiceAccountLeaveDecision`
so both the in-page tab switcher and a router-level blocker can share it:
`shouldBlock` is a pure predicate a router blocker may poll repeatedly, and
`confirmLeave` surfaces the loss confirmation and resolves the deferred
navigation via onConfirm/onCancel. GatewayPage forwards the guard to the
host through onLeaveGuardChange; the web-console gateway route installs a
useBlocker that intercepts SPA nav and Back/Forward, confirming or resetting
through the guard. The browser beforeunload prompt still covers reload/close.

Adds a data-router test covering link nav and POP navigation for cancel and
confirm paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provisioner mTLS transports reloaded rotated leaf key pairs per
handshake but baked the CA trust bundle into a static tls.Config at
startup. A rolled-over issuing CA (as cert-manager performs) would then be
distrusted until the process restarted, breaking the synchronous
provisioning path.

Add a caPoolReloader (mtime-cached, mirroring certificateReloader) on both
sides:

- API-server client: tls.Config has no per-handshake RootCAs hook, so verify
  the server chain manually in VerifyConnection against a freshly reloaded
  pool (InsecureSkipVerify disables only the built-in check; the manual check
  still enforces the chain and server name).
- Control-plane server: install GetConfigForClient so ClientCAs is reloaded
  per handshake while the base config keeps the reloading leaf and identity
  check.

Adds CA-rollover handshake tests on both sides (untrusted before rotation,
trusted after in-place bundle rewrite) and a spec requirement stating both
endpoints pick up rotated key pairs and CA bundles without a restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The degraded service-account status was added to the OpenAPI schema, SDK,
and UI type, but the web console route's sa-status allowlist omitted it, so a
?sa-status=degraded deep link was silently dropped and the filter could not
round-trip. The product spec also never defined what degraded means.

- Add "degraded" to the gateway route's sa-status parser allowlist.
- Add a "Degraded Health State" requirement to the spec: degraded is a health
  signal (Keycloak verification temporarily unavailable), never disables,
  revokes, or deletes the client, is re-polled on the same at-least-once-per-
  minute cadence, restores to the persisted lifecycle intent on recovery, and
  never defers expiration enforcement. Add degraded to the status enum.
- Add data-router tests: parse a degraded deep link and round-trip a degraded
  filter through the URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The seven follow-up commits close four of the six findings from the previous Amber round and remove the original security-broadening Keycloak cases. The assessment remains REQUEST_CHANGES because zero-write convergence still accepts behavior-breaking client/mapper drift, and the due-work drain can stop behind a retained first page and leave later expirations unprocessed.

Overall assessment: REQUEST_CHANGES

This is a self-review, so GitHub does not permit the PR author to submit a REQUEST_CHANGES event; the formal review is submitted as COMMENT and the amber/changes-requested label records the assessment.

Prior-review status

I re-read all prior Amber reviews and all 15 review threads before evaluating commits dd8b904..795ec35.

Previous finding Current status
Zero-write convergence preserves security-broadening Keycloak drift PARTIAL — full-scope, interactive-flow, injected-scope, and custom-audience drift now trigger repair; behavior-bearing authenticator and role-mapper settings remain unchecked.
Gateway cleanup releases its barrier before gateway-row deletion Fixed — the final gateway delete now runs inside the shared lifecycle barrier, with an interleaving regression test.
Capped sequential due scan cannot guarantee one-minute expiry PARTIAL — work is concurrent and successful pages drain, but retained rows can pin the first page and hide later expirations.
SPA route/history navigation can discard the one-time secret Fixed — a route-level blocker covers links and POP navigation, while the existing guard handles tabs and beforeunload covers hard navigation.
CA trust-anchor rotation requires restart Fixed — both peers reload trust pools per new handshake and exercise CA-rollover handshakes.
degraded is incomplete across specification, SDK, and route contract Fixed — the product semantics, generated SDKs, route allowlist, and deep-link round trip are aligned.

Major

  1. components/control-plane/internal/serviceaccountkeycloak/client.go:109,393 — The new predicate detects the prior security-broadening fields, but it still accepts behavior-breaking drift: kcClient does not compare the desired protocol/authenticator, and the role mapper can set access.token.claim=false, multivalued=false, or a wrong JSON type while passing convergence. A client can therefore remain reported ready while its stored secret or access-token role claim no longer works. Fix: compare every behavior-bearing client field written by repair and every required mapper config value; add authenticator and role-claim drift regressions. Confidence: High (100%).
  2. components/api-server/plugins/serviceAccounts/service.go:583,595 — Re-querying the same ordered LIMIT 1000 page does not advance when attempted rows remain eligible. If the first 1,000 rows fail or intentionally stay transitional, they are returned again, all skipped, and the empty batch exits before later due rows are ever loaded; those later credentials can mint tokens beyond the specified one-minute bound. Fix: use stable keyset pagination or exclusion of attempted IDs, preferably separating due-ready expiration work from other transitions, and test more than one page with a retained first page. Confidence: High (100%).

UI requirement coverage

Requirement IDs Result Evidence
UI-INT-04, UI-INT-07, UI-PERF-04, UI-TRUST-05 PASS Source inspection plus tab, SPA-link, Back/Forward cancel/confirm, and beforeunload tests cover the one-time handoff paths.
UI-FND-06 PASS degraded now round-trips through the product spec, OpenAPI/SDK types, status presentation, and URL state.
UI-A11Y-01 through UI-A11Y-10; UI-PERF-01, UI-PERF-05 NOT_TESTED No interactive assistive-technology, representative viewport/input, or field-performance session was available.
UI-VER-01 through UI-VER-06 PARTIAL Deterministic source, type, component, route, build, and Storybook evidence passed; no declared browser/representative-user matrix was executed.

Verification

  • Passed git diff --check and repository policy checks with make check.
  • Passed focused API service-account and control-plane Keycloak/provisioner tests under the Go race detector; full go vet ./... passed in both Go modules.
  • Passed generated Go SDK tests and the TypeScript SDK check.
  • Passed the gateway-management UI check pipeline with 163 tests.
  • Passed the complete web-console check pipeline with 73 tests, localization, production build, and Storybook build.
  • The API gateway integration package remains locally blocked because PostgreSQL rejects the repository test credentials; the affected service-account package passes independently.
  • Current GitHub Konflux checks are pending. The PR remains draft and conflicts with main.
  • No live Keycloak drift session, deployed CA rollover, browser assistive-technology session, or representative-user evaluation was available.

— Amber

Findings Summary (ordered by severity, highest first):

  1. [Major] Zero-write convergence accepts behavior-breaking client and role-mapper drift - Identity / Reconciliation (L109, L393)
  2. [Major] The due-work drain stops behind a retained first page - Security / Scheduling (L583, L595)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Typed not-found handling distinguishes deletion from transient failure Pass
No secrets in logs or error messages Pass
Privileged mutations validate exact managed ownership Pass
Reconciliation compares every behavior-bearing client and mapper setting Fail
One-minute expiration work advances across every scan page Fail
mTLS leaf and trust-anchor rotation works without restart Pass
Generated OpenAPI SDK artifacts are current Pass
One-time UI handoff blocks tab, route, history, and hard navigation Pass
Conventional commit intent is present Pass

Comment thread components/control-plane/internal/serviceaccountkeycloak/client.go Outdated
Comment thread components/api-server/plugins/serviceAccounts/service.go Outdated
user and others added 3 commits August 24, 2026 12:20
The due/transitional drain re-queried the same ordered LIMIT page each
round and skipped rows it had already attempted. When the first page
stayed eligible (for example a persistently transitional row), every row
on it was skipped, the batch went empty, and the drain stopped before it
reached later due rows. A backlog larger than one page could therefore
miss expirations and breach the one-minute disablement bound.

Replace the attempted-set re-query with keyset pagination over the
immutable (expires_at, id) pair. expires_at is NOT NULL and neither key
changes for the life of a row, so the cursor always advances strictly
forward: a retained page is never re-served and every later page is
reached. ListDueAndTransitional now takes a ReconcileCursor and orders by
(expires_at, id); reconcileDue advances the cursor from the last row of
each page and stops on a short page.

Add a scanLimit seam and a regression test that pins a fully eligible
first page (no-op provisioning rows) ahead of later due rows and asserts
the later expirations are still disabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r field

Zero-write convergence accepted drift on fields that repair pins but the
predicate never inspected, so a broadened or broken client could read as
"converged" and skip repair.

Client representation: add protocol and clientAuthenticatorType to the
parsed client and compare both. A switch away from openid-connect changes
token issuance; a changed authenticator type changes how the client
authenticates and can strand the issued secret.

Protocol mappers: derive both repair and convergence from one shared
definition (managedProtocolMappers) and compare every required config
value. The gateway-client-roles mapper now checks access.token.claim,
multivalued, jsonType.label, and id.token.claim, so drift that drops the
roles claim, collapses roles to a single value, or corrupts the JSON type
no longer survives as converged. A missing required value reads as empty
and fails closed to repair.

Add authenticator- and protocol-drift cases to the client drift table and
a role-claim drift table covering each corrupted mapper value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…client-secret

# Conflicts:
#	components/sdk-typescript/src/base.ts
#	components/sdk-typescript/src/client.ts
#	components/sdk-typescript/src/fleet.ts
#	components/sdk-typescript/src/fleet_api.ts
#	components/sdk-typescript/src/gateway.ts
#	components/sdk-typescript/src/gateway_api.ts
#	components/sdk-typescript/src/gateway_network.ts
#	components/sdk-typescript/src/gateway_network_api.ts
#	components/sdk-typescript/src/gateway_release.ts
#	components/sdk-typescript/src/gateway_release_api.ts
#	components/sdk-typescript/src/index.ts
#	components/sdk-typescript/src/managed_cluster.ts
#	components/sdk-typescript/src/managed_cluster_api.ts
#	components/sdk-typescript/src/managed_database.ts
#	components/sdk-typescript/src/managed_database_api.ts
#	components/sdk-typescript/src/role.ts
#	components/sdk-typescript/src/role_api.ts
#	components/sdk-typescript/src/role_binding.ts
#	components/sdk-typescript/src/role_binding_api.ts
#	components/web-console/app/adapters/api/gateway-operations.ts

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The retained-first-page starvation is fixed by the new immutable keyset scan, and the previously cited client-representation and mapper-config fields now converge correctly. I am still requesting changes because mapper-level protocol is written during repair but omitted from the zero-write convergence model; the generated OpenAPI SDK check is also red for DefaultAPI.md drift.

Major

  1. Preserve mapper protocol during zero-write convergencecomponents/control-plane/internal/serviceaccountkeycloak/client.go:417

    parsedProtocolMapper does not deserialize protocol, and protocolMappersConverged consequently compares only name, mapper provider, and required config. A live audience or role mapper changed away from openid-connect can therefore be accepted as converged even though repair pins that field, leaving the resource ready while its required access-token claims may no longer be emitted. Add protocol to the shared desired/live model, compare it to openid-connect, and cover both managed mappers with drift tests. Confidence: High (98%).

Overall assessment

REQUEST_CHANGES. This self-review is submitted as a GitHub comment because GitHub does not allow an author to request changes on their own PR.

UI standards coverage

Requirement Status Evidence
UI-HEX-05 PASS The merge resolution kept generated-SDK normalization inside the API adapter; the latest web-console and gateway-management UI quality gates pass.
UI-VER-09 PASS Per-change UI checks completed successfully on 3ad4802.
UI-A11Y-01–10; UI-INT-01–10 NOT_TESTED No behavior-bearing rendered UI changed in the PR-only delta since 795ec35; browser, keyboard, and assistive-technology journeys were not rerun.

Checks and limitations

  • go test -race -count=1 ./plugins/serviceAccounts — pass
  • go test -race -count=1 ./internal/serviceaccountkeycloak — pass
  • go vet ./plugins/serviceAccounts — pass
  • go vet ./internal/serviceaccountkeycloak — pass
  • Latest GitHub checks — all code, E2E, lint, UI, repository, and build gates pass except Check generated OpenAPI SDK, which reports only components/api-server/pkg/api/openapi/docs/DefaultAPI.md drift. I did not duplicate that already-actionable CI output as an inline finding.
  • Scope: re-reviewed 795ec35..3ad4802, the current-base merge resolution, and both prior open Amber findings. No live Keycloak or manual browser/assistive-technology environment was exercised.

Findings Summary (ordered by severity, highest first):

  1. [Major] Mapper protocol drift is still accepted as converged - Reconciliation / Spec Consistency (L417-L450)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
No secrets in logs or responses Pass
Reconcile every repair-controlled behavior field Fail
Generated OpenAPI artifacts synchronized Fail
UI API-adapter boundary preserved Pass

Comment thread components/control-plane/internal/serviceaccountkeycloak/client.go
The mapper protocol is behavior-bearing: repair writes protocol
openid-connect on every managed protocol mapper, but convergence did
not deserialize or compare it. A mapper that kept its name, provider,
and config but switched protocol therefore read as converged, so
reconciliation performed zero writes while the required audience or
role claim was no longer emitted on the token.

Add protocol to the shared managedProtocolMapper definition so repair
and convergence use one source of truth, deserialize protocol in
parsedProtocolMapper, and fail closed to repair when it drifts. Add a
regression case per managed mapper that switches only protocol and
asserts a repair write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The prior mapper-protocol finding is fixed on d90e5e7: live and desired mapper protocols share one definition, both managed mappers have drift regressions, and the focused race/vet checks pass. I cannot approve this head while the required generated OpenAPI SDK check remains failing on DefaultAPI.md drift, so the overall assessment stays REQUEST_CHANGES.

Prior finding disposition

The Major at components/control-plane/internal/serviceaccountkeycloak/client.go:417 is fixed. managedProtocolMapper.protocol is now the single source used by repair and convergence, the live representation deserializes it, and each managed mapper has a protocol-only drift regression.

Minor

  1. Commit the regenerated OpenAPI documentationcomponents/api-server/pkg/api/openapi/docs/DefaultAPI.md:93 (and repeated generated table/example rows)

    The required Check generated OpenAPI SDK job still regenerates this file and finds drift. The current difference is mechanical trailing whitespace in generated Markdown, but the checked-in artifact does not match the repository generator and the required gate remains red. Run cd components/api-server && make generate generate-sdk, commit the output, and rerun the check. I did not add a duplicate inline comment because the CI job already identifies the exact file and command. Confidence: High (100%).

Overall assessment

REQUEST_CHANGES. This self-review is submitted as a GitHub comment because GitHub does not allow an author to request changes on their own PR.

UI standards coverage

Requirement Status Evidence
UI-VER-09 PASS The latest web-console and gateway-management UI quality gates pass on d90e5e7.
UI-A11Y-01–10; UI-INT-01–10 N/A The new commit changes only control-plane mapper reconciliation and tests; it introduces no rendered UI behavior.

Checks and limitations

  • go test -race -count=1 ./internal/serviceaccountkeycloak — pass
  • go vet ./internal/serviceaccountkeycloak — pass
  • Latest GitHub checks — code, E2E, lint, UI, repository, and build checks pass; only Check generated OpenAPI SDK fails, solely on DefaultAPI.md.
  • Scope: reviewed 3ad4802..d90e5e7, reconciled the previous open thread, and inspected the current CI result. No live Keycloak environment was exercised.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Generated DefaultAPI.md remains out of sync with the repository generator - Generated Artifacts (L93 and repeated generated rows)

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
No secrets in logs or responses Pass
Reconcile every repair-controlled behavior field Pass
Generated OpenAPI artifacts synchronized Fail

Run make generate generate-sdk and commit the canonical generator
output. DefaultAPI.md now matches the generator (trailing whitespace in
generated Markdown table rows), and the Go SDK picks up the Gateway
created_by field and the refreshed spec checksum that the main merge
introduced but did not regenerate. This clears the required Check
generated OpenAPI SDK gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jsell-rh

Copy link
Copy Markdown
Collaborator Author

Addressed the Minor from the latest self-review (generated OpenAPI SDK drift) in 5e8f59a. Ran cd components/api-server && make generate generate-sdk and committed the canonical output: DefaultAPI.md now matches the generator (trailing whitespace in generated Markdown table rows), and sdk-go picked up the Gateway created_by field plus the refreshed spec checksum that the main merge introduced but had not regenerated. The Check generated OpenAPI SDK gate is now green; re-running the generator produces no drift.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The generated OpenAPI artifacts are synchronized on 5e8f59a: the prior DefaultAPI.md drift is closed, and the committed Go SDK matches an independent regeneration exactly. No new correctness, security, or UI issue was found in the latest delta and all applicable head checks are green, so the overall assessment is APPROVE.

Prior finding disposition

The Minor generated-artifact finding from the previous review is fixed: DefaultAPI.md is now canonical, the Go SDK contains the generated Gateway.CreatedBy field and refreshed source checksum, and regeneration produces no SDK diff. The earlier mapper-protocol Major remains fixed on d90e5e7; the latest commit does not touch that control-plane code, and all 18 prior review threads remain resolved.

Overall assessment

APPROVE. This self-review is submitted as a GitHub COMMENT because GitHub does not allow an author to approve their own PR.

UI standards coverage

Requirement Status Evidence
UI-VER-09 PASS The web-console and gateway-management UI quality gates pass on 5e8f59a.
UI-A11Y-01–10; UI-INT-01–10 N/A (latest delta) The latest commit changes generated documentation and the Go SDK only; it introduces no rendered behavior.

Checks and limitations

  • Independent Go SDK regeneration into a temporary directory — exact match
  • go test -count=1 ./... in components/sdk-go — pass
  • go vet ./... in components/sdk-go — pass
  • git diff --ignore-all-space d90e5e7..5e8f59a -- DefaultAPI.md — no semantic documentation change
  • GitHub head checks — 17 pass, four expected enterprise-contract results are neutral, one unrelated validation job is skipped, and none are failing or pending
  • The generic git diff --check reports the pinned generator's trailing spaces in DefaultAPI.md; the authoritative OpenAPI drift and repository-policy gates pass on that exact canonical output.
  • Scope: re-reviewed d90e5e7..5e8f59a, the latest discussion, every prior thread, and current head CI. No live Keycloak or manual browser/assistive-technology journey was rerun for this generated-artifact-only delta.

Findings Summary (ordered by severity, highest first):

  1. No findings.

Convention Checklist (omit conventions not applicable to the diff):

Convention Result
OpenAPI clients generated rather than manually edited Pass
Generated OpenAPI artifacts synchronized Pass
Generated SDK preserves the source contract Pass
No secrets in generated output Pass
Conventional commit message Pass

@jsell-rh jsell-rh added amber/approved The Amber review agent has approved this PR. and removed amber/changes-requested Amber requested changes on this PR labels Aug 24, 2026
@jsell-rh
jsell-rh marked this pull request as ready for review August 24, 2026 19:31
@jsell-rh
jsell-rh added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit 8511557 Aug 24, 2026
22 checks passed
@jsell-rh
jsell-rh deleted the spec/machine-account-client-secret branch August 24, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amber/approved The Amber review agent has approved this PR. amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant