Skip to content

feat(gateway-access): gateway access management (owner/admin/viewer tiers) - #447

Merged
bsquizz merged 27 commits into
mainfrom
user_management
Oct 9, 2026
Merged

bsquizz merged 27 commits into
mainfrom
user_management

Conversation

@bsquizz

@bsquizz bsquizz commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Gateway access management

Adds, and implements, gateway access management: how a gateway's administrators grant, change, and revoke other users' access to a single gateway, projected into per-gateway Keycloak client roles so granted users get the same access through the openshell CLI as in the management plane.

Roles form a strict hierarchy Owner > Admin > Viewer. Owners may delete the gateway and assign other owners; admins manage the Admin and User tiers only; a gateway always keeps at least one owner.

Specs: specs/platform/gateway-access-management.spec.md (GAM-01..14) and specs/web-console/gateway-access-management.spec.md (GAM-UI-01..11), plus aligned edits to data-model, security/rbac-enforcement, openshell-gateway-keycloak, web-console/architecture, and web-console/user_flows.md.

What changed

API server - a new gatewayAccess facade over the existing role_bindings store:

  • Enriched, server-joined access list with case-insensitive literal search and a role filter (no per-row user lookups).
  • Grant resolves the target against the Keycloak realm directory first (404 if absent), pre-provisions a User record when needed, then converges the binding.
  • Role change is an in-place role_id update that emits a single RoleBinding UPDATED event (no zero-access window); revoke removes the user's bindings.
  • Last-owner protection (409 with a descriptive message), owner/admin-tier authorization with owner-tier gating, and idempotent convergence to one effective role, all serialized per gateway.
  • New gateway:admin built-in role seeded idempotently; RBAC middleware extended for the access resource (read/update, never delete) and admin service-account role capping.

Control plane - OIDC Role Bridge maps gateway:admin to openshell-admin + openshell-user and reconciles each user's client roles to the union of their surviving bindings on create/update/delete (demotion strips openshell-admin, keeps openshell-user). New Keycloak realm directory projection (ListRealmUsers + periodic refresh loop + a directory gRPC service that rides the existing provisioner channel the API server already dials).

SDK - Go and TypeScript clients for the scoped access resource; the generator was extended for optional item-GET, PATCH, and a directory sub-collection, leaving the service-account client byte-identical.

CLI - hsctl create|list|update|delete gatewayAccess (new update verb). 403 and 409 surface as non-zero exits with the server message.

Web console - a Manage access tab next to Connection: access table with inline role control and remove action, search + role filter, an Add users directory typeahead, a role-assignment radio modal, viewer read-only presentation, and last-owner / owner-row disabling. Expressed through application-owned ports with domain probes; the production BFF proxy needs no change.

e2e (local dev) - scripts/kind/openshell-container.sh now relabels its config bind mount (:z) so the containerized openshell CLI can read its config on SELinux-enforcing hosts (Fedora/RHEL). Without it the local kind e2e gateway-connectivity area failed with "No gateway configured". CI is unaffected: IPv4-only hosts use the native CLI, not the container wrapper.

Verification

Build + test + lint gates pass for every touched component: api-server (facade integration tests, role/rbac/service-account/gateway tests, golangci-lint), control-plane (role-bridge + directory tests, lint), CLI (pkg tests, vet), Go + TS SDKs (build), web-console (full check including typecheck, tests, i18n, build, storybook), and the gateway-management-ui package (check, 221 tests). No em-dashes.

Full kind e2e (bash tests/e2e/e2e-openshell.sh, long mode) passes locally on an SELinux-enforcing host: 105 passed, 0 failed, exercising the new gateway-access-management area (9b) end to end alongside gateway provisioning, sandbox lifecycle, RBAC, managed-cluster registration, and control-plane disconnect/reconnect convergence.

Notes

  • One deliberate omission: GAM-UI-11's Storybook fixture story is not added (no access mockup exists to extend); its scenarios are covered by component tests.
  • Two ponytail:-marked shortcuts with the server as the authoritative backstop: the directory projection is an in-memory O(n) scan, and the UI counts owners from the current page.
  • The directory resolver activates via the HYPERSHELL_SERVICE_ACCOUNT_PROVISIONER_ADDR the API server already sets; no manifest change required.

🤖 Generated with Claude Code

Specify a Manage access capability so gateway admins can grant, change, and revoke other users' access to a gateway, projected into per-gateway Keycloak client roles.

- New specs: platform/gateway-access-management.spec.md (access API, roles, Keycloak directory search, creator protection, hsctl parity) and web-console/gateway-access-management.spec.md (the Manage access tab).
- Add gateway:admin role (granted administrator); reserve gateway:owner for the immutable creator. Both map to openshell-admin + openshell-user; gateway:viewer maps to openshell-user.
- Align the Keycloak OIDC Role Bridge to the implemented union/reconcile-to-union behavior (rbac-enforcement, openshell-gateway-keycloak).
- Update data-model (endpoints + hsctl commands), user_flows, and gateway visibility references to include gateway:admin.

The directory search endpoint serves the console Add users picker only; it is a documented exception to the hsctl 1-for-1 CLI mirror.

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

coderabbitai Bot commented Oct 5, 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: Enterprise
  • Run ID: 0496f93d-1007-451c-a8f8-736af913450a

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

Comment @coderabbitai help to get the list of available commands.

@hypershell-delivery

hypershell-delivery Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Amber review: approve

Amber review

Status: Complete

View the submitted review.

hypershell-delivery[bot]

This comment was marked as outdated.

…tion

Rework the access role model from 'owner = immutable creator' to a three-tier hierarchy Owner > Admin > Viewer.

- gateway:owner is now grantable (multiple owners allowed), can delete the gateway, and can grant/change/revoke any role including owner.
- gateway:admin manages the Admin and User tiers only (no delete, cannot touch the Owner tier).
- Replace creator-immutability with last-owner protection: a gateway must always keep at least one owner; the sole/last owner cannot be demoted or revoked (409). The creator remains an informational is_creator marker only.
- Owner-tier operations (assign/promote/demote/revoke owner) require the caller to be an owner (403 otherwise).
- Propagate three tiers through the access list/filter, grant/change APIs and hsctl --role, the radio role modal (Owner shown to owners only), UI row gating, visibility, and verification.

Updates platform/gateway-access-management, web-console/gateway-access-management, security/rbac-enforcement, platform/openshell-gateway-keycloak, platform/data-model, and web-console/user_flows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.


- GIVEN user C has no binding on gw-1 and is not `platform:admin`
- WHEN user C calls `GET /api/hypershell/v1/gateways/gw-1/access`
- THEN the response SHALL be `404`

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.

I think response should be '403 Forbidden'

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.

🤖 Automated reply posted by a robot.

Not changing this one, and here is why. The 404 for a non-member read is deliberate: it follows the repo's opaque-denial convention in security/rbac-enforcement.spec.md (RBAC-11 / "No binding returns 404 -- existence not disclosed"). GET /gateways/{id} already returns 404 for non-members, so returning 403 on the nested /access endpoint would disclose that the gateway exists to someone who cannot see it -- inconsistent with the parent resource and a small information leak.

Happy to switch to 403 if you would rather override that convention for this endpoint -- just confirm and I will update GAM-03 (and we would likely want to revisit the parent 404 for consistency).

- THEN a `gateway:admin` binding SHALL be created for B on gw-1
- AND the Role Bridge SHALL assign `openshell-admin` and `openshell-user` to B on gw-1

#### Scenario: Grant access to a user who has never signed in

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.

We need to also consider the case where a user does not exist in the realm. What happens if a user tries to grant access to a non-existant keycloak user?

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.

🤖 Automated reply posted by a robot.

Good catch -- addressed in 6f153bc. GAM-04 now resolves the target against the Keycloak realm directory first; if the identity does not exist in the realm, the grant is rejected with 404 and a clear error, with no User pre-provisioned and no binding created. Added a scenario and a verification requirement (GAM-14).


The API server SHALL expose `DELETE /api/hypershell/v1/gateways/{gateway_id}/access/{user_id}` to revoke a user's access to the gateway.

Revocation SHALL remove the user's `gateway:owner`, `gateway:admin`, and/or `gateway:viewer` bindings on the gateway and SHALL cause the Role Bridge to remove the corresponding Keycloak client roles for that user on that gateway's client. Revoking a user who holds `gateway:owner` SHALL require the caller to be a `gateway:owner` (GAM-08), and SHALL be rejected if that user is the last remaining owner (GAM-07).

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.

If the user is the last remaining owner, we should report a detailed error message in the API response indicating the problem

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.

🤖 Automated reply posted by a robot.

Addressed in 6f153bc. GAM-07 now requires each last-owner 409 response to carry a detailed, human-readable message explaining that a gateway must retain at least one owner and that another owner must be assigned before this one can be demoted or removed, so the CLI/console can surface actionable guidance. (Note the model also moved from creator-immutability to last-owner protection earlier on this branch.)


Each candidate SHALL expose `username`, `name`, and `email`, and SHALL be usable as the target identity of a grant (GAM-04). Candidates SHALL include realm users who have never signed in to HyperShell (the search is against the Keycloak directory, not the HyperShell users inventory).

Because the API server SHALL NOT read the `hypershell-keycloak-admin` Secret (`openshell-gateway-keycloak.spec.md`), the API server SHALL obtain directory results from the control plane over the existing in-cluster gRPC path used for Keycloak-backed provisioning. The control plane SHALL query the Keycloak Admin REST API realm user search and return candidates. Results SHALL be bounded (paginated/capped) and the search SHALL require a non-trivial query term to avoid enumerating the entire realm in one call.

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.

Should the control plane periodically sync Users into its DB based on what users exist in the keycloak realm... instead of requiring a query out to keycloak each time we want to conduct this search?

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.

🤖 Automated reply posted by a robot.

Good idea -- adopted in 6f153bc. GAM-09 now serves the directory from a control-plane-maintained projection of realm users, refreshed periodically (the control plane holds the Keycloak admin secret and already runs reconcile loops), instead of a live Keycloak query per search. The tradeoff is bounded staleness (= the refresh interval, which is configuration, not code); to stay correct, grant (GAM-04) re-validates the chosen identity against the realm at grant time. Added projection-refresh verification.

- GAM-05: state the role-change mechanism explicitly (in-place role_id update emitting a single UPDATED event, not delete+create); note the generic role_bindings REST stays create/delete-only (addresses role-change/UPDATED clarification).
- GAM-03: role_binding_id is informational only; change/revoke are keyed by user_id (resolve field-vs-endpoint inconsistency).
- GAM-04: resolve the target against the realm first and reject grants to users absent from the realm with 404 (no pre-provision/binding).
- GAM-07: last-owner 409 responses SHALL carry a detailed, actionable error message.
- GAM-09: serve the directory from a periodically-refreshed control-plane projection of realm users instead of a live Keycloak query per search; grant re-validates against the realm.
- data-model + GAM-14: reflect the in-place update path and the above in notes and verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

…iers)

Implement the gateway access management specs introduced in this PR:
platform GAM-01..14 and web-console GAM-UI-01..11.

- API server: new gatewayAccess facade over the existing role_bindings store
  (enriched server-joined list with search/role filter, grant with Keycloak
  realm resolution + user pre-provisioning, in-place role change emitting a
  single UPDATED event, revoke, last-owner 409 protection, owner/admin-tier
  authorization, idempotent convergence); new gateway:admin built-in role
  seeded idempotently; RBAC middleware extended for the access resource.
- Control plane: OIDC Role Bridge maps gateway:admin to openshell-admin +
  openshell-user and reconciles to the union of a user's surviving bindings on
  create/update/delete; Keycloak realm directory projection (ListRealmUsers +
  refresh loop + a directory gRPC service riding the provisioner channel).
- SDK: Go and TypeScript clients for the scoped access resource (generator
  extended for optional item-GET, PATCH, and a directory sub-collection;
  service-account output unchanged).
- CLI: hsctl create/list/update/delete gatewayAccess (new update verb); 403
  and 409 surface as non-zero exits.
- Web console: Manage access tab (ports, domain probes, resource table,
  directory picker, role modal, inline role control, last-owner UI) plus the
  production API adapter and contract tests.

Verified: api-server, control-plane, cli, both SDKs, web-console, and the
gateway-management-ui package all build and pass their test and lint gates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bsquizz bsquizz changed the title docs(gateway-access): spec gateway access management feat(gateway-access): gateway access management (owner/admin/viewer tiers) Oct 6, 2026
hypershell-delivery[bot]

This comment was marked as outdated.

@hypershell-delivery hypershell-delivery Bot added the amber/changes-requested Amber requested changes on this PR label Oct 6, 2026
bsquizz and others added 2 commits October 6, 2026 16:33
The generic POST/DELETE /role_bindings path validated caller ownership
only for gateway:owner and gateway:viewer, omitting the new
gateway:admin tier. Any caller holding a single RoleBinding could
self-grant gateway:admin (-> openshell-admin + openshell-user via the
Role Bridge) on any gateway, bypassing the facade's owner-tier gating.

Route gateway:admin through validateCallerOwnsGateway in Create/Delete
and require scope=gateway in validateScopeMatchesRole, matching the
facade semantics. Add a regression test for the self-grant exploit path.

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

The gateway-access feature added 5 operations (GET/POST /access, GET
/access/directory, PATCH/DELETE /access/{user_id}), so the embedded
OpenAPI contract now has 40 operations, not 35. Update the expected
count in openapi_embed_test.go.

Also gofmt control-plane cmd/hypershell-controller/main.go (import
ordering) to pass the formatting check.

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

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

HyperShell environment destroyed

This ephemeral OpenShift environment has been destroyed. Comment /pr-extend to redeploy it.

hypershell-delivery[bot]

This comment was marked as outdated.

Resolve conflicts between the gateway-access-management feature and main's
ADLC domain-model change (which removed GatewayRelease/GatewayNetwork and
added AgentRuntime/SandboxTemplate/ProviderSpec/ProviderBinding/
InferenceRoute/SecretSource).

Hand-merged sources (union of both sides):
- openapi/openapi.yaml: kept gateway-access paths/schemas + main's new resources
- cmd/hypershell/main.go: gatewayAccess + agentRuntimes plugins; dropped
  gatewayNetworks/gatewayReleases
- cli create/delete/list aggregators: main's curated set + gatewayAccess
- pkg/api/openapi_embed_test.go: operation count 59
- skills/RECONCILE.md: both gap-table sections

Regenerated all generated artifacts from the merged spec (make generate,
generate-sdk-ts, generate-sdk-go); left generate-cli untouched since its
template lags the committed CLI (connection.go oauth refresh).

Verified: go build/vet across api-server, control-plane, cli, sdk-go;
embed test; TS SDK + web-console + gateway/operational/fleet UI typechecks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

bsquizz and others added 2 commits October 7, 2026 12:05
Main's ADLC change added plugins/gateways migrationDropReleaseFK with ID
2026100600000001; the gateway-access role seed used the same ID, so after the
merge gormigrate failed at startup ("Duplicated migration ID"), breaking every
api-server package that runs migrations (seen in CI: Unit / Go - API server).

Bump the roles gateway:admin seed to 2026100600000004 (a free slot on the same
date). The migration is idempotent, so re-running under the new ID is a no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er (GAM-08)

Grant called authorizeManage with a hard-coded empty currentTier, so the
owner-tier gate never saw when the grant target was already an owner. A
gateway:admin could therefore demote or alter an existing gateway:owner via
grant, bypassing GAM-08 owner-tier gating.

Once the target user is resolved and the gateway lock is held, look up the
target's actual current tier and re-run authorizeManage with it (mirroring
ChangeRole/Revoke). The early pre-check stays so unauthorized callers are
rejected before the directory lookup and user pre-provisioning side effects;
fresh grants still pass since userTier returns "" for a user with no binding.

Add TestGrant_AdminCannotDemoteOwner, which fails without the fix and passes
with it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The last remaining owner (and owner rows a non-owner caller cannot manage)
already disabled the row's role control and Remove access action, but the
reason was only an HTML title attribute. Surface it as a PatternFly Tooltip on
hover/focus, matching the disabled "Delete gateway" affordance.

Both controls now render isAriaDisabled (grayed but focusable, so the reason is
announced and the tooltip fires) wrapped in a Tooltip carrying the reason. The
role control, which cannot be changed in this state, renders as a disabled plain
button showing the current role instead of an interactive Select.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

Changing your own access is easy to do by accident (e.g. demoting yourself), so
the inline role control now opens a confirmation modal when the row being edited
is the signed-in user. Changing another user's role is unchanged (applies
immediately).

Self-recognition is a client-side convenience: the host supplies the caller's
identity through the gateway UI provider (the web console wires it from the BFF
session), and AccessRoleControl compares it to the row's username. The server
remains authoritative for the change. GAM-UI-05 updated with the behavior and
scenarios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

Mirror the can_delete affordance for rename. The Gateway REST resource now also
advertises a read-only, per-caller can_edit boolean, computed with the same
check that authorizes PATCH (gateway:owner or gateway:admin; a gateway:viewer -
a plain user - and a platform:admin without a per-gateway binding cannot edit).

The console disables (not hides) the Rename action in both the row kebab and the
detail header Actions dropdown with a tooltip explaining why, when can_edit is
false, matching the disabled Delete affordance. The server stays authoritative
on PATCH.

Regenerated the Go/TS/Go-SDK OpenAPI artifacts; added rbac CanEditGateway unit
test; updated the platform and web-console specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

The Keycloak realm directory projection did one eager refresh at
control-plane startup and then only refreshed every 5 minutes. On a
fresh deploy Keycloak has not finished importing realm users yet, so
that first refresh caches an empty snapshot and the console "Add users"
picker returns no users for a full refresh interval.

Fast-poll at a shorter interval while the snapshot is empty, then settle
into the normal cadence once populated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

Drop the "Creator" concept end to end (API is_creator field, creatorUserID
DAO lookup, UI label). After granting the "user" role, keep the Add users
dialog open to show the copyable `openshell workspace member add` command,
and surface a per-row icon to re-open it, since user-role grants need
workspace membership before the gateway is usable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

@hypershell-delivery hypershell-delivery Bot added the amber/changes-requested Amber requested changes on this PR label Oct 8, 2026
Resolve conflicts between the user_management (gateway access management)
work and main's placement-availability, CLI-generator overhaul (#477), and
SDK relative-path changes:

- sdk-generator (parser.go): keep HEAD's scoped-resource capability flags +
  TS import ordering and adopt main's relativeAPIPath for collection/item
  paths; make the directory-search path relative too, for consistency.
- gateways plugin (handler.go/plugin.go): union of enforceRBAC + caller
  capability checks (HEAD) and placement/availability resolver (main).
- hsctl: take main's generated-command architecture (ui -> tui, generated
  update/version/whoami via addGeneratedCommands); re-wire the hand-written
  gatewayAccess scoped commands like service accounts and declare them in
  generate-cli.sh HAND_MAINTAINED_COMMANDS.
- openapi_embed_test.go: merged operation count is 60.
- Regenerated sdk-go, sdk-typescript, and hsctl from the merged OpenAPI spec.
- RECONCILE.md / gateway-create.test.tsx: union both sides.

Verified: all Go components build+vet; api-server embed test; sdk-generator
tests; gateway-management-ui 220 tests; sdk-typescript tsc; make check
(no CLI drift, no forbidden terms).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

Commit 0908480 removed the "Creator" concept end to end (is_creator API
field, creatorUserID DAO lookup, OpenAPI schema, regenerated SDKs, UI label)
but left the specs mandating it, so the is_creator SHALL statements were
unsatisfiable against the code and a later /reconcile would read them as a
gap and try to re-add the field.

Update the desired-state specs to match:
- platform: drop the is_creator data-model row, the is_creator assertions in
  the "Owner lists access" scenario, and the is_creator display/audit prose
  (L38/L310). The RBAC "creator is the auto-provisioned first owner" concept
  stays; it is simply no longer surfaced as a distinct field.
- web-console: drop the "Creator" marker requirement, its scenario assertion,
  and the creator-marker references in the i18n, test, and fixtures lists.

Addresses PR #447 review finding (is_creator spec/code drift).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

The scoped-client generator built paths via relativeAPIPath, stripping
the /api/hypershell prefix, while the base HTTP clients concatenate
baseURL + path with no prefix. Every generated GatewayAccess and
ServiceAccount SDK call therefore targeted /v1/... and 404'd. This was
reintroduced by the origin/main merge, which re-adopted the relative
model for scoped resources.

Build scoped paths from the absolute collection/item/directory paths,
matching the non-scoped builder and the server routes, and regenerate
the Go and TypeScript SDKs. Tighten the scoped-path test to require the
full /api/hypershell prefix so a relative-path regression fails CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hypershell-delivery[bot]

This comment was marked as outdated.

@hypershell-delivery hypershell-delivery Bot added amber/approved The Amber review agent has approved this PR. and removed amber/changes-requested Amber requested changes on this PR labels Oct 9, 2026
The access-management area built two gateway create bodies inline with the
old cluster_id shape and no placement, so the API rejected them with
HTTP 400 (required property placement). Reuse e2e_gateway_create_body, which
already emits the deployment's placement intent, at both call sites.

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

@hypershell-delivery hypershell-delivery Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verdict

This revision adds a single e2e commit on top of the previously approved tree: both access-management gateway creates now reuse e2e_gateway_create_body, which emits the required placement intent instead of the removed cluster_id shape, fixing the HTTP 400. All six prior findings remain resolved and no new code defect surfaced; the three cross-PR coordination items below still need a maintainer decision on the shared role model, the data-model.spec.md table shape, and the e2e harness landing order.

Detail

The new commit (4d2043d) replaces two inline create bodies in tests/e2e/e2e-openshell.sh (L2318, L2364) with e2e_gateway_create_body "$NAME". That helper (tests/e2e/lib.sh L800) builds placement as {'mode':'local-kind'} for the Kind driver and {'network':'public','provider':'aws'} otherwise, matching the five other call sites, so the create bodies now satisfy the required placement property. The change is net subtractive (drops the dead cluster_id/E2E_CLUSTER_ID path) and touches only the e2e script.

Cross-PR coordination

  • #185 respecifies the same Gateway OIDC Role Bridge this PR edits (specs/platform/openshell-gateway-keycloak.spec.md). Its periodic RoleBinding projection reconciler derives each user's effective Keycloak role set as the union of only gateway:owner (-> openshell-admin + openshell-user) and gateway:viewer (-> openshell-user), and declares the control plane the sole writer that removes any bridge-owned role not backed by a surviving binding. This PR adds a third tier, gateway:admin -> openshell-admin + openshell-user, seeded as a new built-in role (components/api-server/plugins/roles/migration.go). If #185 lands as written, its recovery cycle has no gateway:admin case and would strip openshell-admin from admin-only grantees on the next projection. Maintainers must agree on one effective-role model that includes gateway:admin and a landing order so the second PR rebases onto the agreed union rather than overwriting it.

  • #445 restructures the specs/platform/data-model.spec.md client-reference tables into a five-column shape (adding Go SDK and TypeScript SDK columns) and enumerates the seeded built-in roles as gateway:creator, platform:admin, gateway:owner, gateway:viewer. This PR edits the same data-model.spec.md and seeds a new built-in role, gateway:admin, via migration. A merge-order decision is needed: whichever lands second must re-apply its rows in the five-column SDK shape and extend the seeded-role enumeration to include gateway:admin, or the table lands in conflicting shapes with a role list that omits the new role.

  • #489 deletes the Bash e2e suite wholesale (tests/e2e/e2e-openshell.sh) and replaces it with a Go/testify harness. This PR instead adds gateway-access e2e coverage into that same Bash file, and this revision's new commit edits it further. The two approaches cannot both land as written: once #489 lands, this PR's gateway-access e2e coverage is removed with the Bash suite. Maintainers must decide the landing order and which PR ports the gateway-access scenario into the surviving Go harness.

Previous concerns

The committer addressed the previous concerns.

Findings Summary (ordered by severity, highest first)

No new code findings. All prior findings are addressed; three cross-PR coordination items (above) require maintainer decisions on the effective-role model including gateway:admin, the data-model.spec.md table landing order, and the e2e harness landing order/coverage port.

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
OpenAPI/SDK clients regenerated, not hand-edited Pass
Generated SDK paths consistent with server routes Pass
Spec matches implementation (desired state) Pass
RBAC owner/admin authorization enforced on all write paths Pass
E2E gateway create bodies send required placement intent Pass
Conventional commit message Pass

@bsquizz
bsquizz added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 0f62e8c Oct 9, 2026
36 checks passed
@bsquizz
bsquizz deleted the user_management branch October 9, 2026 14:52
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant