docs(easycla-ss): P10 — confirm EasyCLA has no NATS transit pre-M6 - #5131
docs(easycla-ss): P10 — confirm EasyCLA has no NATS transit pre-M6#5131mlehotskylf wants to merge 3 commits into
Conversation
M1 "read-only My CLAs" spec-kit set and the migration architecture-review package that grounds it: - specs/001-easycla-ss-integration-fable/m1-my-cla/ — plan, spec, research, data-model, contracts, quickstart, tasks (31), and UI mockups. - docs/easycla-ss-migration/ — milestone architecture proposal, EasyCLA↔SS role-mapping feasibility memo, and the spike runbook M1 references. Docs only. M1 implementation: linuxfoundation/lfx-self-serve#1203. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
Add P10 documenting the architecture-approved decision to trust an SS-supplied identity list (azp allow-list + in-handler JWKS verify, read-only, transitional to M6) rather than re-verify per request, plus the corresponding trust-boundary risk row and a P3 pointer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
Eric Searcy confirmed (2026-07-30) that EasyCLA has no NATS transit pre-M6. It is not merely IP reachability: the auth-service identity RPC is 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 — and a federated NATS super-cluster is overkill. This settles the P10 open dependency in favor of the SS payload bridge + azp mechanism pre-M6, with EasyCLA calling the RPC directly deferred to M6. Replaces the stale "pending confirmation from Eric" parenthetical. Signed-off-by: Michal Lehotsky <mlehotsky@linuxfoundation.org>
WalkthroughThe PR adds migration architecture and authorization documentation for moving EasyCLA user-facing functionality into LFX Self Serve, plus Milestone 1 specifications for a read-only “My CLAs” experience, including API contracts, research, task planning, mockups, and validation instructions. ChangesEasyCLA Self Serve migration
Estimated code review effort: 2 (Simple) | ~15 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds M1 Self Serve migration specifications and records that EasyCLA cannot use NATS before M6.
Changes:
- Documents M1 architecture, tasks, contracts, and UI mockups.
- Adds authorization feasibility and spike guidance.
- Confirms the transitional SS payload bridge and
azptrust model.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
tasks.md |
Defines M1 implementation tasks. |
research.md |
Records M1 technical decisions. |
quickstart.md |
Provides validation instructions. |
mockups/README.md |
Describes UI mockups. |
my-clas-populated.html |
Shows populated CLA view. |
my-clas-empty-state.html |
Shows empty CLA view. |
upstream-easycla-api.md |
Documents EasyCLA APIs. |
ss-me-clas-api.md |
Defines Self Serve API contract. |
spike-runbook.md |
Documents authentication spikes. |
role-mapping-feasibility.md |
Analyzes authorization integration. |
README.md |
Indexes architecture materials. |
architecture-proposal.md |
Records P10 NATS decision. |
Comments suppressed due to low confidence (2)
specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md:42
- These direct calls exercise the superseded username/signature APIs rather than the merged M1 contract. Update the verification flow to call
/v4/my-clasand/v4/my-clas/{signatureID}/pdfwith the same complete session-derived identity parameters that SS forwards; otherwise this quickstart does not validate the implementation being shipped.
# identity
curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v3/users/username/<lf-username>"
# agreements
curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v4/signatures/user/<userID>"
# pdf url
curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v4/signatures/<signatureID>/signed-document"
specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md:26
- The acceptance check compares against the superseded per-user endpoint. Since M1 now uses the consolidated API, the expected list should be compared with
/v4/my-clasusing the same session-derived identity parameters; otherwise the check bypasses EasyCLA's aggregation and validity logic.
| US1-AS1: ICLAs listed + PDF works | Click Download PDF → browser gets S3 URL, file opens; compare list against `GET /v4/signatures/user/{userID}` called directly with a dev token |
| - [ ] T007 Add `GET /v4/users/by-identity` to CLA `swagger/cla.v2.yaml` (query params: `lfUsername`, repeated `email`, repeated `githubId`; response: array of existing v2 user models, deduped) and run `make swagger` to regenerate `gen/` (never hand-edit `gen/`). License header on any new file. | ||
| - [ ] T008 Implement the service method in CLA `v2/users/` (or extend the existing users service per module pattern): union `GetUserByLFUserName` + per-email `GetUserByEmail`/`user_emails` match + per-githubId `GetUserByGitHubID`, reusing the existing GSI-backed repository methods in `users/repository.go` (`lf-username-index`, `lf-email-index`, `github-id-index` — githubId must stay **N-typed** or the GSI misses); dedupe by `user_id`. Unit tests for union/dedupe/empty-input in the same package. | ||
| - [ ] T009 Wire the swagger-generated operation in the module's `handlers.go` `Configure(...)` (request/response translation only, per three-layer convention); register in `cmd/server.go` alongside the other v2 modules. Verify the path is on the **secured** router (gateway `cla-service.yaml` prefix rules) — it must NOT land on the public router; note in `contracts/upstream-easycla-api.md` that SS remains the authorization boundary. | ||
| - [ ] T010 Run CLA `make fmt && make build-mac && make test && make lint`; open the PR against `dev` with DCO signoff; after merge, verify the endpoint on dev via curl (quickstart-style call) and record the verified request/response in `contracts/upstream-easycla-api.md`. | ||
| - [ ] T010a **Add `projectName` to the signature response** (research R6 decision, 2026-07-28): add a `projectName` string to `swagger/common/signature.yaml`, `make swagger`, and populate it in the `/v4/signatures/user/{userID}` converter from the already-loaded CLA group (`claGroup.ProjectName`, cf. `v2/signatures/service.go:399,403,417`). Bundle with the T007–T010 PR. Closes the SS `projectName`-shows-ID gap; SS then maps `EasyClaSignature.projectName` straight through in `toMyClaAgreement` (drop the `projectID` fallback). |
| **Decision**: resolve server-side in SS using **three keys, unioned**: | ||
| 1. **LF username** — `GET /v3/users/username/{userName}` (handler `users/handlers.go:198`; GSI `lf-username-index`). | ||
| 2. **Verified emails** — all verified emails on the LF identity (session claims, enriched via `lfx-v2-auth-service` NATS lookup which SS already uses); matched against `lf_email` and `user_emails`. | ||
| 3. **Linked GitHub account(s)** — SS/Auth0 supports linking GitHub to the LF identity (existing social-connection flow, `social-verification.service.ts`, NATS `user_identity.link`); the linked identity provides the GitHub numeric ID + username. GitHub-derived EasyCLA records (typically missing `lf_username`) are keyed on exactly this — it is the **highest-precision key** for pre-LF-login history. Prefer the immutable numeric `github_id` over username (renames/recycling). **Verified in SS**: nothing is persisted in SS itself — Auth0 stores the linked identity, whose `user_id` is GitHub's numeric ID (`Auth0Identity.user_id`, `packages/shared/src/interfaces/profile.interface.ts:520`; username only in `profileData.nickname`), and `Auth0Service.getUserIdentities()` already fetches identities server-side via NATS auth-service. Spike check: confirm the auth-service returns the bare numeric ID (not a `github|<id>`-prefixed form) — a mismatched key silently returns zero matches, so cover with a fixture test. |
|
|
||
| **GitHub ID storage (verified — no new storage needed)**: `user_github_id` is already stored on the users table as a DynamoDB **Number** with GSI `github-id-index` (`users/repository.go:103-105`, `:1046`). It is populated on exactly the records M1 needs: the console's GitHub OAuth get-or-create writes it N-typed (`cla-backend-legacy/internal/api/github_oauth.go:282,306`), and the employee-signature precheck backfills missing id↔username pairs via GitHub API lookups (`cla-backend-legacy/internal/api/handlers.go:8854-8884`). Caveats: very old records may be username-only (treat username matches as hints, numeric ID as authority); any new endpoint must reuse the existing repository methods — the GSI hash key is N-typed and an S-typed write/query misses the index (documented in the backfill code). | ||
|
|
||
| **Upstream API gap (verified — changes the contingency assessment)**: the GSI-backed lookups all exist server-side (`lf-username-index`, `lf-email-index`, `github-id-index` in `users/repository.go`), but **`GetUserByGitHubID`/`GetUserByGitHubUsername` are not exposed over HTTP**, and the only generic search that is (`GET /v3/users/search`) performs a **DynamoDB table scan** with a filter expression (`users/repository.go:1279`) — unsuitable for per-request resolution. Therefore the previously-contingent EasyCLA endpoint is now **expected to be required**: `GET /v4/users/by-identity?lfUsername=…&email=…&githubId=…` (one small read endpoint wrapping the existing GSI queries; swagger-first; no schema changes). See `contracts/upstream-easycla-api.md`. |
| ## Prerequisites | ||
|
|
||
| - `lfx-self-serve` running locally (`apps/lfx-one`): Node 22+, `.env` per `apps/lfx-one/.env.example`, Auth0 dev tenant login working. | ||
| - Network access to EasyCLA **dev**: lfx-gateway dev URL for `/cla-service/v3|v4` (set `CLA_SERVICE_BASE_URL`), plus whichever token config the R3 spike selected (user bearer passthrough, or exchange/M2M client credentials). |
| ``` | ||
|
|
||
| 1. Log in with the test user. | ||
| 2. Open **Me lens → My CLAs** (`/me/clas`). |
|
|
||
| ## Phase 3: User Story 1 — My CLAs list + ICLA PDF download (Priority: P1) 🎯 MVP | ||
|
|
||
| **Goal**: logged-in user sees all their signed ICLAs (any status, labeled) and currently valid ECLAs under Me lens `/me/clas`, downloads ICLA PDFs via short-lived links, sees the GitHub-link CTA when unlinked, and gets a clear empty state. Read-only; signing links out to Contributor Console. |
|
|
||
| - [ ] T013 [P] [US1] Unit tests for pure logic in `server/services/__tests__/cla.service.spec.ts` (repo test convention): ICLA/ECLA classification per data-model.md rule (`type=cla & referenceType=user`, `cclaCompanyID` set ⇒ ECLA); status derivation per R6 (signed+approved ⇒ valid; signed+!approved ⇒ "no longer valid"; !signed excluded/labeled; superseded only if document version available per T004); multi-record merge + dedupe by `signatureID`; ECLA-invalid filtering; sort by `signedOn` desc. Use T004 fixtures. | ||
| - [ ] T014 [P] [US1] Unit tests for identity resolution in the same suite: union of username/email/githubId matches; GitHub-ID parsing per T003 outcome (fixture test for the prefixed-vs-bare form); no-match ⇒ empty `easyclaUserIds` + `unmatched=true`; no linked GitHub identity ⇒ `githubLinked=false`. Mock the upstream client and (per T002 outcome) the token source. | ||
| - [ ] T015 [P] [US1] Route/controller tests in `server/routes/__tests__/clas.route.spec.ts`: 401 without session; userID derived **only** from session (a request supplying user IDs is ignored); `GET /api/me/clas/:signatureId/pdf-url` returns **404** (never 403) for unknown, not-owned, and ECLA signature IDs; 502 `{ code: "UPSTREAM_ERROR" }` on upstream failure; flag off ⇒ 404 on both routes. |
| ``` | ||
|
|
||
| - **Behavior**: ICLAs listed in any status with labels; ECLAs only when valid; sorted `signedOn` desc; empty `agreements` + `identity.unmatched=true` drives the "no CLA history found for your account" empty state. | ||
| - **GitHub-link CTA**: when `identity.githubLinked=false` (no GitHub account linked to the LF identity), the UI shows "Don't see your CLAs? Link your GitHub account" pointing into SS's existing identity-linking flow (`/social/callback` social-connection pattern); on return, the page re-fetches and resolution now includes the linked GitHub ID (research R2). |
| Issues a short-lived download URL for a signed ICLA PDF. | ||
|
|
||
| - **Auth**: session required. | ||
| - **Guard**: `signatureId` MUST be an ICLA belonging to the session's resolved agreement set (server re-fetches/uses per-request resolution — 404 otherwise; never 403, to avoid existence oracle). |
| <tr> | ||
| <th>Project</th> | ||
| <th>Type</th> | ||
| <th>Signed</th> | ||
| <th>Document</th> |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (1)
specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md (1)
66-66: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winBase classification tests on the authoritative
claTypefield.Research says
claTypeis authoritative and the legacycclaCompanyIDrule is fallback, but T013 specifies only the old rule. Add expliciticla,ecla, andcclacases plus fallback coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md` at line 66, Update the T013 unit-test task description to make claType the authoritative classification field, adding explicit icla, ecla, and ccla cases and coverage for the legacy cclaCompanyID fallback when claType is unavailable. Preserve the existing classification, status, merge, filtering, and sorting test requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/easycla-ss-migration/spike-runbook.md`:
- Line 41: Update the refresh-token instructions in the runbook to prohibit
retrieving tokens from debug logs, and recommend only controlled session-store
access or the authorization-code+PKCE flow. State that logs must redact tokens
and that any test tokens used for the spike must be revoked or rotated
afterward.
- Line 36: Update the JWT inspection command in the runbook to decode the
payload with a base64url-safe decoder instead of plain base64. Also make the
TOKEN fetch fail closed by validating that the exchange succeeds and returns a
non-empty token before proceeding to claim inspection.
In
`@specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.md`:
- Line 39: Update the GitHub-link CTA contract in the identity resolution
documentation to use the implemented user-facing route `/profile/identities`
instead of `/social/callback`, and keep the surrounding return and re-fetch
behavior unchanged.
- Around line 51-53: Choose and document one feature-flag enforcement model
across all three sites: either revise the API contract to specify Angular-only
gating or require server-side route gating. Update research.md to describe the
same behavior, and align T015/T018 in tasks.md with the selected model,
including the corresponding route-test expectations.
In
`@specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md`:
- Line 1: The M1 documentation uses conflicting EasyCLA identity-resolution and
authorization contracts; establish one authoritative endpoint and flow. In
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md:1,
rewrite the contract for the current endpoints or explicitly mark old sections
legacy; in docs/easycla-ss-migration/architecture-proposal.md:94, state whether
P10 is the M1 contract or a later transition; update R2 in
specs/001-easycla-ss-integration-fable/m1-my-cla/research.md:17-30, scope
T007–T010a in specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md:43-47,
revise the userID read-path analysis in
docs/easycla-ss-migration/role-mapping-feasibility.md:112-118, target the
authoritative endpoint in docs/easycla-ss-migration/spike-runbook.md:61-68, and
align acceptance checks and direct calls in
specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md:26-42.
In
`@specs/001-easycla-ss-integration-fable/m1-my-cla/mockups/my-clas-populated.html`:
- Around line 144-199: Reorder the sample rows in the populated CLA table so
their signed dates follow the documented signedOn descending order, placing the
2024 CDM entry before the 2023 FDC3 entry and continuing in descending order for
all rows. Update the row ordering around the table’s project entries without
changing their displayed content.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md`:
- Around line 5-20: Update the quickstart configuration prerequisites to remove
CLA_SERVICE_BASE_URL and document API_GW_AUDIENCE instead, using the implemented
authentication/API gateway configuration. In the Run steps, replace the /me/clas
URL and “Me lens” reference with the Profile-tab route profile/clas, while
preserving the existing expected CLA results.
- Around line 34-42: Define or source the GW shell variable before the “Useful
direct calls (dev, with token)” curl snippets in quickstart.md, using the
appropriate development gateway value so $GW expands to a valid host when the
examples are copied.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md`:
- Around line 26-33: Keep the M1 release checkpoint blocked until T001 and T002
are completed with deployed-dev token and role-less-user results recorded in
research.md R3; in specs/001-easycla-ss-integration-fable/m1-my-cla/research.md
lines 38-43, add those results. In tasks.md line 72, distinguish completed
username-only work from the unresolved production path, and at line 90 require
completion of the remaining telemetry, component, E2E, and aggregation tasks
before marking US1 or the release gate complete.
- Around line 106-110: Update the dependency and ownership map in tasks.md to
include T010a alongside T007–T010, including the Phase 2 chain and the
cross-repo note identifying cla-backend-go work. Ensure T010a’s projectName
response work is represented in the upstream release scope.
---
Nitpick comments:
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md`:
- Line 66: Update the T013 unit-test task description to make claType the
authoritative classification field, adding explicit icla, ecla, and ccla cases
and coverage for the legacy cclaCompanyID fallback when claType is unavailable.
Preserve the existing classification, status, merge, filtering, and sorting test
requirements.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b7b24d61-0d86-4309-ad83-030f37e500f1
📒 Files selected for processing (12)
docs/easycla-ss-migration/README.mddocs/easycla-ss-migration/architecture-proposal.mddocs/easycla-ss-migration/role-mapping-feasibility.mddocs/easycla-ss-migration/spike-runbook.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/mockups/README.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/mockups/my-clas-empty-state.htmlspecs/001-easycla-ss-integration-fable/m1-my-cla/mockups/my-clas-populated.htmlspecs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/research.mdspecs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md
| --data-urlencode "audience=$AUDIENCE" \ | ||
| | jq -r .access_token) | ||
|
|
||
| echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq . # inspect claims |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
TOKEN="$(curl ... | jq -er '.access_token')"
PAYLOAD="$(printf '%s' "$TOKEN" | cut -d. -f2)"
python - "$PAYLOAD" <<'PY'
import base64
import json
import sys
part = sys.argv[1]
part += "=" * ((4 - len(part) % 4) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(part)), indent=2))
PYRepository: linuxfoundation/easycla
Length of output: 533
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
file="docs/easycla-ss-migration/spike-runbook.md"
echo "== line count =="
wc -l "$file"
echo
echo "== relevant sections =="
sed -n '1,120p' "$file" | cat -nRepository: linuxfoundation/easycla
Length of output: 6552
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import base64, subprocess, textwrap, json, os, tempfile, sys
samples = {
# URL-safe alphabet, no padding
"urlsafe_no_padding": "eyJmb28iOiJiYXIifQ",
# URL-safe alphabet with characters that differ from standard base64
"urlsafe_chars": "eyJfIjoiLSJ9", # {"_":"-"} in base64url form
# Standard base64 with padding, for comparison
"standard_padded": base64.b64encode(b'{"foo":"bar"}').decode(),
}
for name, s in samples.items():
print(f"\n== {name} ==")
print("input:", s)
p = subprocess.run(
["bash", "-lc", f"printf '%s' '{s}' | base64 -d 2>&1"],
capture_output=True,
text=True,
)
print("base64 -d rc:", p.returncode)
print("base64 -d out:", p.stdout.strip())
print("base64 -d err:", p.stderr.strip())
# show equivalent urlsafe decode in Python
pad = s + "=" * ((4 - len(s) % 4) % 4)
try:
decoded = base64.urlsafe_b64decode(pad)
print("urlsafe decoded:", decoded.decode("utf-8", "replace"))
except Exception as e:
print("urlsafe decode error:", e)
PYRepository: linuxfoundation/easycla
Length of output: 594
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import base64, subprocess
samples = {
"urlsafe_no_padding": "eyJmb28iOiJiYXIifQ",
"urlsafe_chars": "eyJfIjoiLSJ9",
"standard_padded": base64.b64encode(b'{"foo":"bar"}').decode(),
}
for name, s in samples.items():
print(f"\n== {name} ==")
print("input:", s)
p = subprocess.run(
["bash", "-lc", f"printf '%s' '{s}' | base64 -d 2>&1"],
capture_output=True,
text=True,
)
print("base64 -d rc:", p.returncode)
print("base64 -d out:", p.stdout.strip())
print("base64 -d err:", p.stderr.strip())
pad = s + "=" * ((4 - len(s) % 4) % 4)
try:
decoded = base64.urlsafe_b64decode(pad)
print("urlsafe decoded:", decoded.decode("utf-8", "replace"))
except Exception as e:
print("urlsafe decode error:", e)
PYRepository: linuxfoundation/easycla
Length of output: 594
Use a base64url-safe decoder and guard the token fetch. base64 -d is brittle for JWT payloads, and the current TOKEN=$(...) step can leave an empty token after a bad exchange unless the command fails closed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/easycla-ss-migration/spike-runbook.md` at line 36, Update the JWT
inspection command in the runbook to decode the payload with a base64url-safe
decoder instead of plain base64. Also make the TOKEN fetch fail closed by
validating that the exchange succeeds and returns a non-empty token before
proceeding to claim inspection.
|
|
||
| **Checkpoint (this is spike 1's core):** the decoded token must contain `http://lfx.dev/claims/username`. If it's present, Auth0 is granting the api-gw audience to this client and stamping the claim the gateway needs — spike 1 passes. If the exchange returns an error (e.g. `invalid_grant`, `access_denied`, or unauthorized audience), spike 1 has found the gap: the SS Auth0 client isn't authorized for that audience in dev — that's an Auth0 client-grant config item, not a code change. | ||
|
|
||
| > Getting a refresh token: easiest is to pull it from a live dev SS session (`req.appSession.refresh_token`) — log into dev SS, grab it from the session store / a debug log. Alternatively run a one-off authorization-code+PKCE login against the dev client with `scope=openid offline_access`. Either way the token must carry `offline_access`. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Never retrieve refresh tokens from debug logs.
A refresh token is a bearer credential. Recommending that operators copy it from a debug log creates a credential-disclosure path; use controlled session-store access, redact logs, and revoke or rotate test tokens after the spike.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/easycla-ss-migration/spike-runbook.md` at line 41, Update the
refresh-token instructions in the runbook to prohibit retrieving tokens from
debug logs, and recommend only controlled session-store access or the
authorization-code+PKCE flow. State that logs must redact tokens and that any
test tokens used for the spike must be revoked or rotated afterward.
| ``` | ||
|
|
||
| - **Behavior**: ICLAs listed in any status with labels; ECLAs only when valid; sorted `signedOn` desc; empty `agreements` + `identity.unmatched=true` drives the "no CLA history found for your account" empty state. | ||
| - **GitHub-link CTA**: when `identity.githubLinked=false` (no GitHub account linked to the LF identity), the UI shows "Don't see your CLAs? Link your GitHub account" pointing into SS's existing identity-linking flow (`/social/callback` social-connection pattern); on return, the page re-fetches and resolution now includes the linked GitHub ID (research R2). |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Point the identity-link CTA at the implemented route.
The contract references /social/callback, while the implementation notes and tasks use /profile/identities. Confirm the user-facing entry point and document it consistently; otherwise the recovery flow may dead-end.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.md`
at line 39, Update the GitHub-link CTA contract in the identity resolution
documentation to use the implemented user-facing route `/profile/identities`
instead of `/social/callback`, and keep the surrounding return and re-fetch
behavior unchanged.
| ## Feature flag | ||
|
|
||
| Both routes and the Angular module are gated by LaunchDarkly flag `my-clas-enabled` (name TBD per repo convention). Flag off ⇒ routes 404, sidebar item hidden, route guard redirects to Me dashboard. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Choose one feature-flag enforcement model.
The contract promises route-level 404s, while the implementation notes and tasks specify Angular-only gating. This affects both runtime behavior and the required route tests.
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.md#L51-L53: change the contract to Angular-only behavior, or require server-side gating.specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L62-L68: make the implementation note match the chosen behavior.specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L74-L74: align T015/T018 with that decision.
📍 Affects 3 files
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.md#L51-L53(this comment)specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L62-L68specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L74-L74
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/ss-me-clas-api.md`
around lines 51 - 53, Choose and document one feature-flag enforcement model
across all three sites: either revise the API contract to specify Angular-only
gating or require server-side route gating. Update research.md to describe the
same behavior, and align T015/T018 in tasks.md with the selected model,
including the corresponding route-test expectations.
| @@ -1,4 +1,4 @@ | |||
| # Contract: EasyCLA endpoints consumed by M1 | |||
| # Contract: EasyCLA endpoints consumed by M1 (existing APIs — no changes) | |||
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Reconcile the M1 API and authorization model before implementation.
The documents simultaneously describe P10’s trusted identity-list/consolidated flow and the older client-side by-identity plus per-user signature flow. They must identify one authoritative contract.
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md#L1-L1: rewrite the contract around the current endpoints, or label the old sections as legacy.docs/easycla-ss-migration/architecture-proposal.md#L94-L94: state whether P10 is the M1 contract or a later transition.specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L17-L30: update R2’s identity-resolution decision and rationale.specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L43-L47: replace or explicitly legacy-scope T007–T010a.docs/easycla-ss-migration/role-mapping-feasibility.md#L112-L118: mark the userID flow as historical or update the read-path analysis.docs/easycla-ss-migration/spike-runbook.md#L61-L68: run the spike against the authoritative endpoint.specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md#L26-L42: update acceptance checks and direct calls.
📍 Affects 7 files
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md#L1-L1(this comment)docs/easycla-ss-migration/architecture-proposal.md#L94-L94specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L17-L30specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L43-L47docs/easycla-ss-migration/role-mapping-feasibility.md#L112-L118docs/easycla-ss-migration/spike-runbook.md#L61-L68specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md#L26-L42
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md`
at line 1, The M1 documentation uses conflicting EasyCLA identity-resolution and
authorization contracts; establish one authoritative endpoint and flow. In
specs/001-easycla-ss-integration-fable/m1-my-cla/contracts/upstream-easycla-api.md:1,
rewrite the contract for the current endpoints or explicitly mark old sections
legacy; in docs/easycla-ss-migration/architecture-proposal.md:94, state whether
P10 is the M1 contract or a later transition; update R2 in
specs/001-easycla-ss-integration-fable/m1-my-cla/research.md:17-30, scope
T007–T010a in specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md:43-47,
revise the userID read-path analysis in
docs/easycla-ss-migration/role-mapping-feasibility.md:112-118, target the
authoritative endpoint in docs/easycla-ss-migration/spike-runbook.md:61-68, and
align acceptance checks and direct calls in
specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md:26-42.
| <table class="clatable"> | ||
| <tr> | ||
| <th>Project</th> | ||
| <th>Type</th> | ||
| <th>Signed</th> | ||
| <th>Document</th> | ||
| </tr> | ||
| <tr> | ||
| <td> | ||
| <div class="projcell"><div class="projicon"></div> | ||
| <div><div class="projname">Linux Foundation</div><div class="projsub">Linux Foundation</div></div> | ||
| </div> | ||
| </td> | ||
| <td><span class="typepill icla">ICLA</span></td> | ||
| <td>Jul 15, 2019</td> | ||
| <td><a class="doclink" href="#">⬇ Download PDF</a></td> | ||
| </tr> | ||
| <tr> | ||
| <td> | ||
| <div class="projcell"><div class="projicon"></div> | ||
| <div><div class="projname">Academy Software Foundation</div><div class="projsub">Academy Software Foundation (ASWF)</div></div> | ||
| </div> | ||
| </td> | ||
| <td><span class="typepill icla">ICLA</span></td> | ||
| <td>Feb 2, 2021</td> | ||
| <td><a class="doclink" href="#">⬇ Download PDF</a></td> | ||
| </tr> | ||
| <tr> | ||
| <td> | ||
| <div class="projcell"><div class="projicon"></div> | ||
| <div><div class="projname">Cloud Native Computing Foundation (CNCF)</div><div class="projsub">Cloud Native Computing Foundation (CNCF)</div></div> | ||
| </div> | ||
| </td> | ||
| <td><span class="typepill ecla">ECLA · IBM</span></td> | ||
| <td>Sep 10, 2022</td> | ||
| <td><span class="docmuted">Covered by Corporate CLA (CCLA)</span></td> | ||
| </tr> | ||
| <tr> | ||
| <td> | ||
| <div class="projcell"><div class="projicon"></div> | ||
| <div><div class="projname">CDM</div><div class="projsub">FINOS CDM Community Specification License</div></div> | ||
| </div> | ||
| </td> | ||
| <td><span class="typepill icla">ICLA</span></td> | ||
| <td>May 6, 2024</td> | ||
| <td><a class="doclink" href="#">⬇ Download PDF</a></td> | ||
| </tr> | ||
| <tr> | ||
| <td> | ||
| <div class="projcell"><div class="projicon"></div> | ||
| <div><div class="projname">FDC3</div><div class="projsub">FDC3</div></div> | ||
| </div> | ||
| </td> | ||
| <td><span class="typepill icla">ICLA</span></td> | ||
| <td>Nov 30, 2023</td> | ||
| <td><a class="doclink" href="#">⬇ Download PDF</a></td> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Match the mockup order to the documented sort.
The sample dates are 2019, 2021, 2022, 2024, then 2023, while the contract requires signedOn descending. Reorder the sample rows or label the mockup as illustrative so it does not contradict acceptance checks.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@specs/001-easycla-ss-integration-fable/m1-my-cla/mockups/my-clas-populated.html`
around lines 144 - 199, Reorder the sample rows in the populated CLA table so
their signed dates follow the documented signedOn descending order, placing the
2024 CDM entry before the 2023 FDC3 entry and continuing in descending order for
all rows. Update the row ordering around the table’s project entries without
changing their displayed content.
| - `lfx-self-serve` running locally (`apps/lfx-one`): Node 22+, `.env` per `apps/lfx-one/.env.example`, Auth0 dev tenant login working. | ||
| - Network access to EasyCLA **dev**: lfx-gateway dev URL for `/cla-service/v3|v4` (set `CLA_SERVICE_BASE_URL`), plus whichever token config the R3 spike selected (user bearer passthrough, or exchange/M2M client credentials). | ||
| - A dev test user with CLA history. To create one: use the dev Contributor Console flow against a dev CLA-gated repo (sign an ICLA; for an ECLA, use a dev company with a signed CCLA and approval-list entry), or pick an existing dev identity from the `cla-dev-signatures` table. | ||
| - LaunchDarkly dev flag `my-clas-enabled` (or repo-convention name) turned on for your user. | ||
|
|
||
| ## Run | ||
|
|
||
| ```bash | ||
| cd lfx-self-serve | ||
| pnpm install # or repo-standard package manager | ||
| pnpm dev # starts lfx-one with SSR server | ||
| ``` | ||
|
|
||
| 1. Log in with the test user. | ||
| 2. Open **Me lens → My CLAs** (`/me/clas`). | ||
| 3. Expect: ICLA row(s) with status + **Download PDF**; ECLA row(s) with company name and no download; empty state if the account has no history. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Refresh the quickstart to match the implemented route and configuration.
CLA_SERVICE_BASE_URL was dropped, and the UI route is profile/clas, not /me/clas. As written, developers configure a dead env var and open the wrong URL. Document API_GW_AUDIENCE and the Profile-tab route instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md` around lines
5 - 20, Update the quickstart configuration prerequisites to remove
CLA_SERVICE_BASE_URL and document API_GW_AUDIENCE instead, using the implemented
authentication/API gateway configuration. In the Run steps, replace the /me/clas
URL and “Me lens” reference with the Profile-tab route profile/clas, while
preserving the existing expected CLA results.
| ## Useful direct calls (dev, with token) | ||
|
|
||
| ```bash | ||
| # identity | ||
| curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v3/users/username/<lf-username>" | ||
| # agreements | ||
| curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v4/signatures/user/<userID>" | ||
| # pdf url | ||
| curl -H "Authorization: Bearer $TOK" "$GW/cla-service/v4/signatures/<signatureID>/signed-document" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Define GW before using the direct-call snippets.
The shell examples interpolate $GW, but this file never assigns it. Copy-pasting them therefore produces an empty host. Add GW=... or source it before these examples.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/quickstart.md` around lines
34 - 42, Define or source the GW shell variable before the “Useful direct calls
(dev, with token)” curl snippets in quickstart.md, using the appropriate
development gateway value so $GW expands to a valid host when the examples are
copied.
| - [ ] T001 Run **spike 1** (SS-minted user token → secured v4 read) exactly per `docs/easycla-ss-migration/spike-runbook.md` steps 1–2 (user A); record token-claims checkpoint result and HTTP status in `specs/001-easycla-ss-integration-fable/m1-my-cla/research.md` R3. If the audience exchange fails, file the Auth0 client-grant config item (auth0-terraform) — that is the fix, not a code change. | ||
| - [ ] T002 Run **spike 2** (role-less user → `GET /cla-service/v4/signatures/user/{userID}`) per the runbook (user B). Decision point: **200** ⇒ user-token model confirmed; **403** ⇒ choose between the small ACS policy addition and the M2M fallback (SS server binds session userID itself) and record the decision + rationale in `research.md` R3. Blocks T014. | ||
| - [ ] T003 [P] Spike: confirm the GitHub-ID format returned by `Auth0Service.getUserIdentities()` in SS dev — bare numeric ID vs `github|<id>`-prefixed (`Auth0Identity.user_id`, SS `packages/shared/src/interfaces/profile.interface.ts`). Record in `research.md` R2; the parsing rule feeds T016/T018 fixtures (a mismatched key silently returns zero matches). | ||
| - [ ] T004 [P] Spike: curl dev `GET /cla-service/v4/signatures/user/{userID}` and `GET /cla-service/v4/signatures/{signatureID}/signed-document` for a known test user (quickstart "Useful direct calls"); capture raw JSON responses as fixtures into SS `apps/lfx-one/src/server/services/__fixtures__/cla/` (or repo-convention fixture location). Verify which fields the v2 model actually exposes: document major/minor version (R6 superseded detection), `signedOn`, company name/ID, `signed`/`approved`. Record field availability in `research.md` R6; if document version is absent, the superseded badge is dropped per R6. | ||
| - [ ] T005 [P] Audit the T004 fixture payloads for v1 user-service/org-service IDs per architecture-proposal P9 (company references on ECLA rows are the likely spot); note in `research.md` which display fields depend on v1 IDs and the planned lookup (`lfx.lookup_v1_user_sfid.by_username`/`.by_email` NATS RPCs) or confirm none are needed for M1's read-only display. | ||
| - [ ] T006 Confirm with the EasyCLA team that `GET /v4/users/by-identity` will be accepted (contract `contracts/upstream-easycla-api.md` §1) and agree on the LaunchDarkly flag name per SS repo convention (plan says `my-clas-enabled`, name TBD); create the dev flag. Record final endpoint shape + flag name in the contracts/plan. | ||
|
|
||
| **Checkpoint**: token model decided (T002), fixtures captured (T004), upstream endpoint agreed (T006). |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Do not declare the M1 release gate complete while auth and acceptance work remain open.
The auth spikes are unchecked and inconclusive, while the task list marks implementation complete and calls US1 fully functional.
specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L26-L33: keep the checkpoint blocked until T001/T002 results are recorded.specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L38-L43: record deployed-dev token and role-less-user results.specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L72-L72: split completed username-only work from the unresolved production path.specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L90-L90: require the remaining telemetry, component, E2E, and aggregation tasks before claiming completion.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~28-~28: The official name of this software platform is spelled with a capital “H”.
Context: ...ities()in SS dev — bare numeric ID vsgithub|-prefixed (Auth0Identity.user_id`...
(GITHUB)
📍 Affects 2 files
specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L26-L33(this comment)specs/001-easycla-ss-integration-fable/m1-my-cla/research.md#L38-L43specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L72-L72specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md#L90-L90
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md` around lines 26 -
33, Keep the M1 release checkpoint blocked until T001 and T002 are completed
with deployed-dev token and role-less-user results recorded in research.md R3;
in specs/001-easycla-ss-integration-fable/m1-my-cla/research.md lines 38-43, add
those results. In tasks.md line 72, distinguish completed username-only work
from the unresolved production path, and at line 90 require completion of the
remaining telemetry, component, E2E, and aggregation tasks before marking US1 or
the release gate complete.
| - **Phase 2**: T007 → T008 → T009 → T010 (swagger-first chain, CLA repo); T011/T012 parallel with all of it (SS repo). T002 gates T016's token wiring; T004 gates T013's fixtures. | ||
| - **Phase 3 (US1)**: T013/T014/T015 first (tests, parallel) → T016 → T017 → T018 → T019 (server chain); T020/T021 parallel with the server chain once T012 exists → T022 → T023 → T024; T025/T026 last (need dev deployment of T010's endpoint for full resolution; T025 can run with username-only resolution earlier). | ||
| - **Phase 4**: after US1 checkpoint; T027/T028 parallel. | ||
|
|
||
| Cross-repo note: only T007–T010 (+T028) touch `cla-backend-go`; everything else is `lfx-self-serve`. The SS work does not hard-block on T010 — username-only resolution via `/v3/users/username/{userName}` is the interim path, with full three-key resolution switched on when the endpoint lands. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Include T010a in the dependency and ownership map.
T010a edits cla-backend-go, but the dependency chain and cross-repo note mention only T007–T010. Add T010a to both, or the projectName response work can be omitted from the upstream release.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specs/001-easycla-ss-integration-fable/m1-my-cla/tasks.md` around lines 106 -
110, Update the dependency and ownership map in tasks.md to include T010a
alongside T007–T010, including the Phase 2 chain and the cross-repo note
identifying cla-backend-go work. Ensure T010a’s projectName response work is
represented in the upstream release scope.
Updates P10 in
docs/easycla-ss-migration/architecture-proposal.mdto record Eric Searcy's confirmation (2026-07-30) that EasyCLA has no NATS transit pre-M6, replacing the stale "pending confirmation from Eric" parenthetical.Why
P10 (trust-SS "My CLAs" identity read +
azpallow-list) hinged on one open dependency: whether EasyCLA could reach NATS pre-M6. If it could, "same data, less machinery" would argue for EasyCLA callinglfx.auth-service.user_identity.listdirectly and skipping both the SS payload bridge and theazpmechanism.Eric confirmed it cannot, and not merely for IP-reachability reasons:
So the SS payload bridge +
azpmechanism is the right pre-M6 design; EasyCLA calling the RPC directly is deferred to M6.Scope
Docs-only change: adds the EasyCLA → Self Serve M1 spec/architecture docs (
docs/easycla-ss-migration/,specs/001-easycla-ss-integration-fable/m1-my-cla/) and the P10 rationale update. No behavioral/code change.Supersedes #5130, which incorrectly targeted the stale
spec/easycla-ss-m1base branch and pulled in unrelated commits.Decision: linuxfoundation/lfx-self-serve#1216 · Follow-ups: linuxfoundation/lfx-self-serve#1224 (EasyCLA) / linuxfoundation/lfx-self-serve#1225 (SS)
🤖 Generated with Claude Code