Skip to content

Commit 99ce979

Browse files
mlehotskylfclaude
andcommitted
docs(easycla-ss): EasyCLA → Self Serve migration architecture docs
Adds the architecture-review context for the EasyCLA → LFX Self Serve migration under docs/easycla-ss-migration/: the milestone proposal (M1–M6), the EasyCLA↔SS role-mapping feasibility memo, the spike runbook, and a README (which links the architecture-review Google Slides deck). Scoped to the architecture docs only. The M1 spec-kit set (specs/…/m1-my-cla/) is intentionally not carried here — M1 is shipped and its spec docs already live on dev via the My CLAs API PRs (#5125/#5128). Docs only — no code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
1 parent 22867ae commit 99ce979

4 files changed

Lines changed: 441 additions & 0 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!-- Copyright The Linux Foundation and each contributor to CommunityBridge.
2+
SPDX-License-Identifier: CC-BY-4.0 -->
3+
4+
# EasyCLA → LFX Self Serve Migration — Architecture Review
5+
6+
Self-contained materials for the architecture review of the EasyCLA-to-Self-Serve migration. Reading order:
7+
8+
1. **[architecture-proposal.md](architecture-proposal.md)** — start here. Current state, milestones, what leadership already settled, the proposed architecture (P1–P8), top risks, and what the review should challenge.
9+
2. **[role-mapping-feasibility.md](role-mapping-feasibility.md)** — the supporting deep analysis for the roles/permissions bridge (P2/P3): how EasyCLA v4 authorization actually works, token paths, read paths, options assessment, and the spike list. All claims cite `file:line`.
10+
3. **[Slide deck (Google Slides)](https://docs.google.com/presentation/d/1FQJOpiETIO_H10c6_eP2Zu-LM7qlvG_t7blhRm--2KA/edit)** — presentation for the review session.
11+
12+
Implementation-level specifications (milestone scopes, acceptance criteria, per-milestone plans — used by the Spec Kit workflow) live separately in [specs/001-easycla-ss-integration-fable/](../../specs/001-easycla-ss-integration-fable/spec.md). This folder is for evaluating the architecture; that folder is for building it.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<!-- Copyright The Linux Foundation and each contributor to CommunityBridge.
2+
SPDX-License-Identifier: CC-BY-4.0 -->
3+
4+
# EasyCLA → LFX Self Serve: Architecture Proposal
5+
6+
**Status**: Reviewed by architecture (Eric Searcy, 2026-07-20) — feedback incorporated below (P2/P3 adjusted, P9 and one risk added)
7+
**Owner**: Michal (engineering) | **Date**: 2026-07-20
8+
9+
This document is self-contained for review purposes. Implementation-level specs (milestone scopes, acceptance criteria, M1 plan) live in [specs/001-easycla-ss-integration-fable/](../../specs/001-easycla-ss-integration-fable/spec.md) — linked below only for deep dives.
10+
11+
## 1. What the program is
12+
13+
Migrate all EasyCLA user-facing functionality — Contributor Console, Corporate CLA Console (plus its Node/Apollo GraphQL BFF), the PCC EasyCLA module, and the landing page — into **LFX Self Serve** under its Me / Organization / Project lenses. The backend re-platform (M6: Lambda → Kubernetes V2 service, optional DynamoDB → Postgres) is a **separately gated decision**, not part of this proposal.
14+
15+
```mermaid
16+
flowchart LR
17+
subgraph TODAY["Today: four UIs being absorbed"]
18+
CTC["Contributor Console"]
19+
CCC["Corporate CLA Console<br/>+ GraphQL BFF"]
20+
PCC["PCC EasyCLA module"]
21+
LP["Landing page"]
22+
end
23+
subgraph TARGET["Target: LFX Self Serve"]
24+
ME["Me lens (M1-M3)"]
25+
ORG["Organization lens (M4)"]
26+
PRJ["Project lens (M5)"]
27+
end
28+
subgraph BACKEND["EasyCLA backend (unchanged until M6)"]
29+
GW["lfx-gateway"]
30+
V4["cla-backend-go<br/>/v3 + /v4 Lambdas"]
31+
LEG["cla-backend-legacy<br/>/v1 + /v2"]
32+
DDB[("DynamoDB<br/>19 tables + S3 PDFs")]
33+
DS["DocuSign"]
34+
ACS2["ACS roles<br/>(LFX v1 service, own DB)"]
35+
end
36+
CTC --> GW
37+
CCC --> GW
38+
PCC --> GW
39+
ME --> GW
40+
ORG --> GW
41+
PRJ --> GW
42+
CTC --> LEG
43+
ME --> LEG
44+
GW --> V4
45+
V4 --> DDB
46+
V4 --> DS
47+
V4 --> ACS2
48+
LP -.->|"retired at M3"| TARGET
49+
```
50+
51+
Current-state facts the proposal relies on (verified in code; details in the [feasibility memo](role-mapping-feasibility.md)):
52+
53+
- EasyCLA already routes through **lfx-gateway** (`/cla-service/v3|v4` → Lambda). It is behind the platform gateway today; nothing needs onboarding.
54+
- **Two Go API surfaces**: `cla-backend-go` (`/v3`, `/v4`) and `cla-backend-legacy` (`/v1`, `/v2` — contributor flows still call it; the old Python backend is gone).
55+
- **DocuSign integration lives server-side** (`v2/sign`): consoles only fetch a `sign_url` and redirect; webhooks/PDFs/state never touch the UIs.
56+
- **CLA roles** (`cla-manager`, `cla-signatory`, `cla-manager-designee`) are ACS role+scope tuples (ACS is an LFX v1 component with its own database), assigned asynchronously. LFX V2 authorization is OpenFGA relations; **no CLA object types exist in the FGA model**.
57+
- The PR status-check redirect URL is an **SSM parameter per environment** — contributor cutover is a config flip.
58+
59+
## 2. Milestones (context)
60+
61+
| # | Milestone | Retires | Effort |
62+
|---|-----------|---------|--------|
63+
| [M1](../../specs/001-easycla-ss-integration-fable/01-milestone-read-only-me-lens-fable.md) | Read-only "My CLAs" (Me lens) || S |
64+
| [M2](../../specs/001-easycla-ss-integration-fable/02-milestone-sign-icla-fable.md) | Sign ICLA in SS (a: GitHub, b: GitLab, c: Gerrit) || M |
65+
| [M3](../../specs/001-easycla-ss-integration-fable/03-milestone-sign-ecla-fable.md) | Sign ECLA in SS (a–c per platform) | Contributor Console + landing page | L |
66+
| [M4](../../specs/001-easycla-ss-integration-fable/04-milestone-ccla-org-lens-fable.md) | CCLA management (Organization lens) | Corporate Console + its BFF | XL |
67+
| [M5](../../specs/001-easycla-ss-integration-fable/05-milestone-project-lens-pcc-fable.md) | EasyCLA admin (Project lens) — *decision-gated* | PCC EasyCLA module | L |
68+
| [M6](../../specs/001-easycla-ss-integration-fable/06-milestone-k8s-v2-api-fable.md) | API → Kubernetes V2 service (± Postgres) — *separately gated* | Lambda/API GW stack | XL–XXL |
69+
70+
## 3. Already settled at the leadership review (2026-07-15) — context, not up for review
71+
72+
| # | Decision |
73+
|---|---|
74+
| L1 | **UI-first sequencing approved**: M1–M5 build on the existing EasyCLA APIs; M6 gated separately |
75+
| L2 | Target timeline: **Q3 / early Q4 2026** |
76+
| L3 | **M5 is decision-gated**: PCC EasyCLA admin moves to SS *or stays in PCC* — open product decision (Kieran/Manish/Heather) |
77+
| L4 | EasyCLA **landing page retired** (added to the M3 decommission package) |
78+
| L5 | LFID account is a prerequisite for signing (already the status quo in the consoles) |
79+
| L6 | Post-signing redirect to the SS profile (collect GitHub linking *after* signing, no friction before) |
80+
81+
## 4. Proposed architecture (for review)
82+
83+
| # | Proposal | Rationale (short) |
84+
|---|---|---|
85+
| P1 | **Strangler pattern: SS is a new client of the existing EasyCLA v4/v3 APIs.** One SS server-side `cla` module; no business logic reimplemented; enforcement (roles, approval lists, sanctions) stays in EasyCLA until M6 | One source of truth; follows the proven crowdfunding integration shape in SS |
86+
| P2 | **Roles: bridge, don't migrate.** No CLA object types in OpenFGA before M6. SS gates UI via the user's **self permission check** (`POST user-service/v1/me/permissions/checks` — the same ACS decision the gateway enforces), handling v4 403s gracefully; the public manager-list endpoint supplies display data and post-assignment pending-state UX. Every write is still enforced by the gateway/ACS + v4 | Self-check guarantees UI gating and API enforcement agree by construction (review guidance); an FGA copy would be non-enforcing, with sync lag on an already-async pipeline; evidence in the [feasibility memo](role-mapping-feasibility.md) |
87+
| P3 | **Tokens: user-scoped access tokens (never ID tokens), via SS's existing api-gw-audience refresh-token exchange** — the same access/refresh authentication the me/project lenses use. No M2M by default, no token infrastructure work | The whole v4 chain keys on user identity, not client/audience — verified in code ([feasibility §4](role-mapping-feasibility.md)); two curl spikes remain. ID-token usage stays legacy-console-only; interim gateway/ACS/v4 support for both token types is acceptable during cutover. **Caller-identification + trust model for the "My CLAs" by-identity read endpoint: see P10.** |
88+
| P4 | **DocuSign never moves in M2–M4.** SS fetches a `sign_url` from v4 and redirects, exactly as the consoles do today; no DocuSign bridge service | Webhooks, PDF storage, envelope state already live in `v2/sign`; duplicating them adds risk with no user value |
89+
| P5 | **Cutover per milestone is a config flip** (SSM redirect base for the PR check; lens feature flags for org/project) — instant rollback | Reversibility is a program success criterion |
90+
| P6 | **All three git platforms in scope** via per-platform sub-milestones, each with its own cutover switch and parity checklist | Prevents Gerrit/GitLab slipping and blocking console retirement late |
91+
| P7 | **The legacy `/v1`/`/v2` Go surface stays until M6**, covered by parity/contract tests — contributor flows still call it | Second API codebase inside the blast radius even for "UI-only" milestones; absorbed/retired at M6 |
92+
| P8 | **Email-based CCLA signatory signing is preserved** — the signatory signs via an emailed DocuSign link, never forced into SS/LF SSO | Documented product behavior; a distinct UX path that must survive M4 |
93+
| P9 | **Audit v4 API payloads for v1 user-service/org-service IDs and plan the mapping lookups** (API shapes unchanged this phase). Users: resolve via the `lfx.lookup_v1_user_sfid.by_username` / `.by_email` NATS RPCs (lfx-v1-sync-helper); orgs: v1 org service via the api-gw secondary token | user-service and org-service are being deprecated in the LFX v2 transition (users collapse to email/username references; orgs to name/domain except true B2B orgs). SS UI must not hard-depend on v1 IDs it cannot resolve later |
94+
| P10 | **"My CLAs" identity read: EasyCLA trusts an SS-supplied identity list rather than re-verifying per request.** SS builds the list server-side via the auth service `lfx.auth-service.user_identity.list` NATS RPC (session user's own token; never client params, never SS→Auth0-Management directly), and EasyCLA confirms the caller is SS via an **`azp` allow-list of SS confidential-client IDs**, checked in the v4 handler, with **in-handler JWKS re-verification** and **deny on missing/unparseable bearer**; the endpoint stays strictly read-only. **Transitional (revisit at M6):** once EasyCLA is on the K8s cluster it should call the auth-service RPC itself over NATS and drop both the trusted payload and the `azp` mechanism | Reviewed & approved by architecture (Eric Searcy, 2026-07-28). It's "SS-queried Auth0 vs. EasyCLA-queried Auth0" — same data, same source — so trusting SS's list is the same data with less machinery (EasyCLA has no NATS transit pre-M6 — confirmed by Eric 2026-07-30: it's a request/reply inbox subject, so EasyCLA would have to be a cluster consumer for the reply, over a cross-region us-east-1 ↔ us-west-2 WAN link; a federated NATS super-cluster is overkill — and it would otherwise need a tenant-wide `read:users` M2M client on a hot path). `azp` allow-listing is sound **only** because SS is a confidential backend client whose tokens never reach a browser; if that client ID were reused by a public/SPA client the boundary silently collapses. Verifying the current EasyCLA-record check (P-note): historical GitHub-only signers have no `lf_username`, so record-based verification wrongly returns empty — the Auth0-sourced list fixes this. Decision: linuxfoundation/lfx-self-serve#1216; follow-ups linuxfoundation/lfx-self-serve#1224 (EasyCLA) / #1225 (SS) |
95+
96+
## 5. Top risks
97+
98+
| Risk | Mitigation |
99+
|------|-----------|
100+
| Identity mapping gaps (LF account ↔ EasyCLA records, esp. pre-LF-login history) | M1 ships mapping + unmatched-user telemetry before any signing moves |
101+
| `azp`-allow-list trust boundary (P10) silently collapses if SS's confidential-client ID is ever reused by a public/SPA client, or its tokens become browser-visible | Code comment stating the assumption at the check; removed at M6 when EasyCLA calls the auth-service RPC directly (P10); read-only endpoint bounds worst case to CLA-match read-disclosure |
102+
| ACS role assignment is async, and warden responses are cached ~30 min (revocations linger too) | Server-side retries in SS; honest pending states; no synchronous-UX promises |
103+
| M4 scope illusion: "migrate a console" hides a ~648-file GraphQL BFF | Sized XL; inventory-driven parity checklist is the contract with PM |
104+
| Dual-console feature drift during migration | Freeze console feature work per area once its SS milestone starts |
105+
| M6 rework of M3–M5 adapters | All SS↔EasyCLA integration behind one server module |
106+
| CLA permissions are **invisible to the automatic docs generation** built on the OpenFGA + v2 Swagger sources of truth (raised at architecture review) | Document role-bridge behavior manually (M4 exit criterion); resolved when CLA enters OpenFGA at M6 |

0 commit comments

Comments
 (0)