---
status: accepted
date: 2026-04-07
---

# Edge Architecture — Two Named Edge Modes with Vendor-Independent Internal Contract

**ID**: `cpt-cf-adr-edge-architecture`

## Table of Contents

<!-- generated by `cypilot toc` -->

## Context and Problem Statement

The platform needs to serve external traffic in two very different environments. On-premise deployments need a
self-contained stack — gateway, login, auth — with no external dependencies. Enterprise and k8s deployments often
already have an API gateway (Tyk, Kong, Envoy) and an identity provider (Keycloak, Auth0, Okta) that the platform must
integrate with rather than replace.

This creates a tension: should the built-in `api-gateway` gear grow to match Tyk/Kong features, or should it stay
minimal? Should `authn-resolver` learn about social providers and OIDC user-login flows, or should that live elsewhere?
And most critically: how do we ensure that internal gears see a stable `SecurityContext` regardless of which edge
stack is in front?

## Decision Drivers

* **Self-contained on-prem**: a fresh on-premise installation must work out of the box with no external gateway or IdP.
  The built-in gears must cover the baseline.
* **Enterprise integration**: customers with existing Tyk/Kong/Keycloak deployments must be able to put their stack in
  front of the platform without forking or patching internal gears.
* **No vendor lock-in**: the internal auth model (`SecurityContext`, `authn-resolver`, `authz-resolver`) must not depend
  on Tyk-specific claims, Kong-specific headers, or any provider-specific token format.
* **Minimal api-gateway scope**: the built-in gateway is maintained by a small team. Turning it into a full-featured API
  management platform is neither feasible nor desirable.
* **Separation of login and routing**: login orchestration (OAuth2 flows, social login, account linking, session
  management) is a fundamentally different concern from HTTP routing and auth delegation. Mixing them creates a
  monolith.

## Considered Options

* **Option A**: Two named edge modes (Embedded / External) with a vendor-independent internal contract and a separate
  identity broker subsystem.
* **Option B**: Make api-gateway full-featured — add OAuth2/OIDC login flows, social providers, token issuance, rate
  limiting policies, API analytics directly into the built-in gateway.
* **Option C**: Make authn-resolver directly aware of external providers — add Tyk/Kong/Keycloak-specific token parsing
  into authn-resolver plugins.

## Decision Outcome

Chosen option: "Two named edge modes with a vendor-independent internal contract", because it keeps the built-in gateway
minimal, isolates login orchestration in a dedicated subsystem, and ensures the internal runtime contract (
`SecurityContext`) remains stable regardless of which edge stack is deployed.

### Edge Modes

**Mode A — Embedded Edge** (on-prem baseline):

```
External Client
      │
      ▼
┌──────────────┐
│  api-gateway  │  ← routing, CORS, rate limit, auth delegation
└──────┬───────┘
       │ Bearer token
       ▼
┌──────────────┐
│authn-resolver │  ← validates platform token → SecurityContext
└──────┬───────┘
       │ SecurityContext
       ▼
┌──────────────┐
│  Gears        │  ← see only SecurityContext, never raw tokens
└──────────────┘

Identity Broker / Login Service  ← separate gear for login flows
  ├─ built-in social login (GitHub, Google)
  ├─ local username/password
  └─ issues platform tokens
```

**Mode B — External Edge** (enterprise / k8s):

```
External Client
      │
      ▼
┌──────────────────┐
│  Tyk / Kong /    │  ← external gateway handles TLS, rate limiting,
│  Envoy / Ingress │    possibly login via its own IdP/broker
└──────┬───────────┘
       │
       ├─── B1: pass-through platform token (already issued by platform)
       │
       └─── B2: exchange external credential → platform token
                 (calls platform token-exchange endpoint)
       │
       ▼
┌──────────────┐
│  api-gateway │  ← still handles auth delegation (or bypassed entirely)
└──────┬───────┘
       │
       ▼
┌──────────────┐
│authn-resolver│  ← validates platform token (same as Mode A)
└──────┬───────┘     optionally: validates trusted-issuer token (configurable)
       │
       ▼
┌──────────────┐
│  Gears       │  ← see only SecurityContext (identical to Mode A)
└──────────────┘
```

### The Invariant

**Downstream gears always see a normalized `SecurityContext`, never provider-specific credentials.** Whether the user
logged in via the built-in identity broker, Tyk Identity Broker, Keycloak, or Auth0 — by the time the request reaches
an application gear, `SecurityContext.subject_id`, `SecurityContext.tenant_id`, and scopes carry the same normalized
claim values. Mode B1 may preserve the trusted-issuer token format on the wire (no exchange), but `authn-resolver`
always produces an identical `SecurityContext` regardless of which issuer minted the bearer token.

This is enforced by the **authn-resolver contract**: it validates only platform tokens and explicitly configured
trusted-issuer tokens. It does not parse Tyk-specific claims, Kong JWT plugin headers, or provider-specific ID tokens.

### Consequences

* The built-in `api-gateway` must NOT grow beyond HTTP edge basics: routing, CORS, rate limiting, auth middleware
  delegation, SecurityContext propagation. This is an explicit architectural constraint.
* A separate **Identity Broker / Login Service** subsystem must be defined (future gear, P2). It handles login
  orchestration, social providers, identity binding, tenant resolution, and platform token issuance. It is NOT part of
  api-gateway or authn-resolver.
* A **token exchange endpoint** must be defined (P2) for Mode B2: external gateways/IdPs call this endpoint to convert
  their authenticated result into a platform token that authn-resolver can validate.
* `authn-resolver` remains a token validation service. It gains an optional "trusted issuer" configuration for Mode B1 (
  accept tokens from a configured external issuer without exchange), but does NOT learn about social providers or login
  flows.
* The `GatewayProvider` abstraction (ADR-0003) handles route registration in both modes: `ToolKitGatewayProvider` for
  Mode A, `KongGatewayProvider` / `TykGatewayProvider` for Mode B.
* In Mode B, the built-in api-gateway may be bypassed entirely (external gateway routes directly to gears) or retained
  as an internal routing layer. This is a deployment choice, not an architectural one.

### Confirmation

* Architecture review: verify that api-gateway contains no login orchestration, social provider, or token issuance code.
* Architecture review: verify that authn-resolver does not import or reference any external gateway/IdP-specific
  libraries.
* Integration test (Mode A): end-to-end request through built-in gateway → authn-resolver → gear produces correct
  SecurityContext.
* Design review (Mode B): token exchange endpoint contract is defined; external gateway integration documentation
  exists.

## Pros and Cons of the Options

### Option A: Two Edge Modes with Stable Internal Contract (chosen)

Embedded Edge for on-prem self-contained; External Edge for enterprise integration. Internal contract unchanged.

* Good, because api-gateway stays small and maintainable — no feature creep into API management territory.
* Good, because enterprise customers use their existing gateway/IdP stack without platform modifications.
* Good, because internal gears are completely decoupled from the edge stack — same code in all deployments.
* Good, because identity broker as a separate subsystem follows the single-responsibility principle.
* Good, because aligns with industry patterns: Kubernetes platforms (Istio, Dapr) separate ingress from application
  auth.
* Neutral, because Mode B2 (token exchange) requires defining and implementing an exchange endpoint — additional work
  but clear scope.
* Bad, because two modes means two integration paths to document and test.

### Option B: Make api-gateway Full-Featured

Add OAuth2/OIDC flows, social login, token issuance, API analytics, advanced rate limiting to the built-in gateway.

* Good, because single component handles everything — simple mental model.
* Bad, because enormous engineering scope — competing with Tyk/Kong/Envoy which have hundreds of person-years of
  investment.
* Bad, because customers with existing gateways now have two full gateways — conflicts, redundancy, confusion.
* Bad, because login orchestration mixed with HTTP routing creates a monolithic gear that's hard to maintain and test.
* Bad, because every social provider addition requires a platform release — slow iteration.

### Option C: Make authn-resolver Aware of External Providers

Add Tyk/Kong/Keycloak-specific token parsing and claims mapping directly into authn-resolver plugins.

* Good, because no separate identity broker needed — authn-resolver handles everything.
* Bad, because couples the core auth pipeline to vendor-specific formats — Tyk claims structure changes require platform
  update.
* Bad, because violates the vendor-independence principle — authn-resolver becomes a Tyk/Kong compatibility layer.
* Bad, because social login flows (redirects, callbacks, state management) do not belong in a stateless token validation
  service.
* Bad, because every new external provider requires a new authn-resolver plugin, bloating the core auth surface.

## More Information

### Component Boundary Summary

| Concern                                                          | Responsible component    | NOT responsible                 |
|------------------------------------------------------------------|--------------------------|---------------------------------|
| HTTP routing, CORS, rate limiting                                | api-gateway              | identity-broker, authn-resolver |
| Auth middleware (Bearer extraction, SecurityContext propagation) | api-gateway              | identity-broker                 |
| Token validation (platform tokens, trusted issuers)              | authn-resolver (plugins) | api-gateway, identity-broker    |
| Authorization (ABAC/RBAC policy evaluation)                      | authz-resolver (plugins) | api-gateway, authn-resolver     |
| Login orchestration (OAuth2 flows, social login, callbacks)      | identity-broker (future) | api-gateway, authn-resolver     |
| Identity binding (external ID → subject_id)                      | identity-broker (future) | authn-resolver                  |
| Tenant resolution (subject → tenant membership)                  | identity-broker (future) | api-gateway                     |
| Platform token issuance                                          | identity-broker (future) | api-gateway, authn-resolver     |
| Token exchange (external credential → platform token)            | identity-broker (future) | api-gateway                     |
| External gateway route registration                              | GatewayProvider adapters | api-gateway internals           |

### Relationship to Other ADRs

- **ADR-0008 (Two-Plane Auth)**: complements this ADR — per-hop JWT re-validation applies in both modes; the "edge" is
  either the built-in gateway (Mode A) or the external gateway (Mode B).
- **ADR-0003 (Gateway Abstraction)**: the `GatewayProvider` trait enables Mode B route registration with external
  gateways.
- **ADR-0006 (Platform-Plane Authentication)**: platform-plane auth (SA tokens first, mTLS+SPIFFE next) is orthogonal
  to edge mode — it secures system calls regardless of which edge stack is deployed.

## Traceability

- **PRD**: [PRD.md](../PRD.md)
- **DESIGN**: [DESIGN.md](../DESIGN.md)

This decision directly addresses the following requirements or design elements:

* `cpt-cf-fr-edge-modes` — Defines the two edge modes and their characteristics.
* `cpt-cf-fr-gateway-minimal` — Constrains api-gateway to HTTP edge basics only.
* `cpt-cf-fr-identity-broker` — Motivates the identity broker as a separate subsystem.
* `cpt-cf-fr-token-exchange` — Defines the Mode B2 token exchange pattern.
* `cpt-cf-component-gateway-provider` — GatewayProvider enables both edge modes.
* `cpt-cf-adr-two-plane-auth` — Per-hop JWT re-validation applies regardless of which edge is in use.
