[HYPERSHELL-49] feat: openshell service accounts - #180
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
jsell-rh
left a comment
There was a problem hiding this comment.
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
- 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
- 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%).
- 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%).
- 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%).
- 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%).
- 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%).
- 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%).
- 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):
- [Blocker] Stale reconciliation can re-enable a successfully revoked credential and restore Ready state - Security / Reconciliation (L605)
- [Major] Reconciliation can delete a live provisioning request - Concurrency / Reconciliation (L503)
- [Major] Gateway cleanup can miss a concurrent service-account creation - Lifecycle Consistency (L394)
- [Major] Transient gateway or OIDC failures permanently revoke credentials - Reliability / Reconciliation (L579, L702)
- [Major] Routine reconciliation disrupts healthy clients and reports failed clients as Ready - Availability / State Integrity (L185, L436)
- [Major] The all-status sequential scan cannot guarantee the one-minute expiration deadline - Security / Scheduling (L129)
- [Major] Console navigation can discard an unacknowledged one-time secret without confirmation - UI Safety (L205)
- [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 |
jsell-rh
left a comment
There was a problem hiding this comment.
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
- 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%).
- 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
- 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%).
- 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%).
- 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%).
- 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%).
- 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%).
- 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%).
- 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%).
- 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):
- [Blocker] Privileged UUID mutations can disable or delete non-managed Keycloak clients - Security / Authorization Boundary (L91, L214)
- [Blocker] Stale reconciliation can re-enable a successfully revoked credential and restore Ready state - Security / Reconciliation (L638, L653)
- [Major] Reconciliation can delete a live provisioning request - Concurrency / Reconciliation (L536)
- [Major] Gateway cleanup can miss a concurrent service-account creation - Lifecycle Consistency (L427, L185)
- [Major] Transient gateway or OIDC failures permanently revoke credentials - Reliability / Reconciliation (L612, L739)
- [Major] Routine reconciliation disrupts healthy clients and reports failed clients as Ready - Availability / State Integrity (L185, L473)
- [Major] The all-status sequential scan cannot guarantee the one-minute expiration deadline - Security / Scheduling (L129)
- [Major] Console navigation can discard an unacknowledged one-time secret without confirmation - UI Safety (L208, L626)
- [Major] CLI output-file errors can discard the only secret after account creation - Secret Handoff (L86)
- [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 |
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>
…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>
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>
c1bf83b to
6fc5b8c
Compare
jsell-rh
left a comment
There was a problem hiding this comment.
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
- components/control-plane/internal/serviceaccountkeycloak/client.go:238,328 —
reconcileConvergedcannot seefullScopeAllowed, public-client/service-account/interactive-grant flags, redirect origins, or default scopes becausekcClientdoes not deserialize them. Its mapper comparison also accepts an extraincluded.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
- components/api-server/plugins/serviceAccounts/service.go:478; components/api-server/plugins/gateways/service.go:213 —
CleanupGatewayreleases the shared lock beforegateways.Deleteremoves 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%). - 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%).
- 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 controlledactiveTabdirectly, and navigation to another SPA route unmounts the handoff without firingbeforeunload, 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%). - components/api-server/plugins/serviceAccounts/provisioner_client.go:103; components/control-plane/internal/serviceaccountprovisioner/transport.go:114 — leaf certificates reload, but
RootCAsandClientCAsare read only at startup. The deployed self-signed CA usesrotationPolicy: 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%). - components/api-server/openapi/openapi.serviceAccounts.yaml:237 — the public
degradedstatus 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):
- [Blocker] The zero-write convergence check preserves security-broadening Keycloak client and mapper drift - Security / Reconciliation (L238, L328)
- [Major] Gateway cleanup releases its barrier before the gateway row is deleted - Lifecycle Consistency (L478, L213)
- [Major] The capped sequential due scan still cannot guarantee one-minute expiration enforcement - Security / Scheduling (L165, L539)
- [Major] SPA route and history navigation can still discard the one-time secret - UI Safety (L545, L84)
- [Major] CA trust-anchor rotation still requires both provisioner peers to restart - Availability / Certificate Lifecycle (L103, L114)
- [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 |
…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
left a comment
There was a problem hiding this comment.
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
- 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:
kcClientdoes not compare the desired protocol/authenticator, and the role mapper can setaccess.token.claim=false,multivalued=false, or a wrong JSON type while passing convergence. A client can therefore remain reportedreadywhile 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%). - components/api-server/plugins/serviceAccounts/service.go:583,595 — Re-querying the same ordered
LIMIT 1000page 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 --checkand repository policy checks withmake 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):
- [Major] Zero-write convergence accepts behavior-breaking client and role-mapper drift - Identity / Reconciliation (L109, L393)
- [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 |
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
left a comment
There was a problem hiding this comment.
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
-
Preserve mapper protocol during zero-write convergence —
components/control-plane/internal/serviceaccountkeycloak/client.go:417parsedProtocolMapperdoes not deserializeprotocol, andprotocolMappersConvergedconsequently compares only name, mapper provider, and required config. A live audience or role mapper changed away fromopenid-connectcan therefore be accepted as converged even though repair pins that field, leaving the resourcereadywhile its required access-token claims may no longer be emitted. Addprotocolto the shared desired/live model, compare it toopenid-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— passgo test -race -count=1 ./internal/serviceaccountkeycloak— passgo vet ./plugins/serviceAccounts— passgo 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.mddrift. 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):
- [Major] Mapper
protocoldrift 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 |
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
left a comment
There was a problem hiding this comment.
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
-
Commit the regenerated OpenAPI documentation —
components/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— passgo 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):
- [Minor] Generated
DefaultAPI.mdremains 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>
|
Addressed the Minor from the latest self-review (generated OpenAPI SDK drift) in 5e8f59a. Ran |
jsell-rh
left a comment
There was a problem hiding this comment.
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 ./...incomponents/sdk-go— passgo vet ./...incomponents/sdk-go— passgit 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 --checkreports the pinned generator's trailing spaces inDefaultAPI.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):
- 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 |
Summary
OpenShellGatewayServiceAccountAPI and its Keycloak confidential-client lifecycle.Service accountsbetween the existingConnectionandDetailstabs, with a management table, create flow, and one-time credential handoff.Scope
This specification covers the client-secret flow only. WIF, multi-gateway credentials, configurable scopes, and in-place secret rotation remain deferred.
Validation
make checkgit diff --check origin/main...HEADJira: https://redhat.atlassian.net/browse/HYPERSHELL-49