Skip to content

Commit 7615a94

Browse files
committed
Move product spec from README to dedicated SPEC.md
Extract the authoritative product requirements, page spec, scenario catalog, system behavior spec, and test coverage sections into SPEC.md. Update AGENTS.md canonical references to point to the new location. https://claude.ai/code/session_01TfegzxmcjiWN7ovfm9g1A3
1 parent 585ed97 commit 7615a94

2 files changed

Lines changed: 186 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Repository Guidelines
22

33
## Canonical References
4-
- Product behavior and route/spec details live in `README.md` (`Page Spec`, `Scenario Catalog`, and `System Behavior Spec` sections).
5-
- Local setup, required environment variables, and command usage live in `README.md` (`Requirements (Local Dev)`, `Dev Setup`, and `Lifecycle Commands` sections).
4+
- Product behavior and route/spec details live in `SPEC.md` (`Page Spec`, `Scenario Catalog`, and `System Behavior Spec` sections).
5+
- Local setup, required environment variables, and command usage live in `README.md`.
66

77
## Project Structure & Module Organization
88
This repo is a Next.js 16 + TypeScript app-router project.

SPEC.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# CLA Bot — Product & Behavior Spec
2+
3+
This document is the authoritative behavior contract for the CLA Bot application.
4+
It covers product requirements, page routes, scenario catalog, webhook/PR flows,
5+
and test coverage expectations.
6+
7+
## Product Requirements
8+
9+
- If a contributor has signed a non-current CLA version, they must re-sign before being considered compliant.
10+
- Contributor compliance status is evaluated per org using the contributor's latest signed version for that org.
11+
- Admins can define org-scoped bypass lists for both GitHub users and GitHub Apps/system bots that should always receive a passing CLA check.
12+
- If a contributor has open pull requests and their signature becomes outdated after a CLA update, checks may need to be re-opened/re-evaluated and set to failing until re-signing is completed.
13+
- After a contributor signs/re-signs the latest CLA, the app schedules an async workflow that updates their open PR CLA checks to success and removes stale CLA prompt comments.
14+
- When an org is activated/deactivated, the app schedules an async workflow to re-check open PRs for that org so checks converge to the new enforcement mode.
15+
- GitHub is the user-management source of truth for the app.
16+
- The app has no local signup/password user-management system; DB user rows are GitHub-linked identity mirrors only.
17+
- Authentication/session management is stateless JWT-based (HTTP-only cookie + signed JWT with `jti`).
18+
- Users can only log in via GitHub OAuth.
19+
- Contributors can view and download every CLA version they have signed.
20+
- Admins can download both current and archived CLA versions for managed orgs.
21+
22+
## Page Spec
23+
24+
This section is the behavior contract for UI routes.
25+
26+
| Route | Purpose | Signed-out behavior | Signed-in behavior | Key actions |
27+
| --- | --- | --- | --- | --- |
28+
| `/` | Marketing landing page | Public page | Same | CTA to `/auth/signin`, example CLA link to `/sign/fiveonefour` |
29+
| `/auth/signin` | Start sign-in flow | Shows GitHub sign-in CTA | Same | Sends user to `/api/auth/github?returnTo=...`; `returnTo` is sanitized to internal paths only |
30+
| `/dashboard` | Mode selector page | Public page | Same + session shown in header | Navigate to `/admin` or `/contributor` |
31+
| `/admin` | Org admin overview | Shows "Sign in required" card | Lists organizations user can administer; shows install CTA when none are authorized | Install app (`/api/github/install`), open org manage pages |
32+
| `/admin/[orgSlug]` | Org CLA management | If data unavailable, shows "Organization not found" UI | Shows org details, CLA version, signers, archives, bypass list, branch-protection reminder | Edit/save CLA text with live markdown preview modes (`Edit`, `Split`, `Preview`), activate/deactivate bot, copy signing link, inspect signers/archives, manage bypass users and app/bot slugs, download current/archived CLA text, share tab links via `?tab=cla\|signers\|archives\|bypass` |
33+
| `/contributor` | Contributor agreement dashboard | Shows "Sign in required" card | Lists signed CLA history grouped by org status | Re-sign prompts for outdated orgs, links to `/sign/[orgSlug]`, download previously signed CLA records |
34+
| `/sign/[orgSlug]` | CLA read/sign page | Shows sign-in required (or org not found) | Shows signed state, or sign/re-sign workflow | Requires scroll-to-bottom before sign button enables; handles inactive org warning |
35+
| `/terms` | Legal terms page | Public page | Same | Documents signing/enforcement terms and branch-protection requirement |
36+
| `/privacy` | Privacy policy page | Public page | Same | Documents collected data, retention, and rights workflow |
37+
38+
## Scenario Catalog
39+
40+
### 1) First login with the app
41+
42+
- Users can only authenticate via GitHub OAuth.
43+
- OAuth flow validates `state` and redirects back to a sanitized internal `returnTo`.
44+
- Session is maintained with an HTTP-only cookie containing a JWT payload (`userId`, `githubUsername`, `role`, `jti`).
45+
- GitHub remains the source of truth for identity. The DB stores app-side profile/session linkage metadata, not standalone account management.
46+
47+
### 2) User selects Admin
48+
49+
- If signed out: user sees auth-required state and can start GitHub login.
50+
- If signed in and authorized on at least one installed account: user sees the account list and install button.
51+
- If signed in but authorized on zero installed accounts: user sees install CTA for GitHub App flow.
52+
- Newly installed accounts start with no CLA text. Maintainers must publish their own CLA before external contributors can sign.
53+
- Org manage tabs are URL-synced (`/admin/[orgSlug]?tab=...`) so state is shareable and works with browser back/forward navigation.
54+
55+
### 3) User selects Contributor
56+
57+
- User sees signed CLA records including org, version label/hash prefix, and signed timestamp.
58+
- User can view full signing history and download each signed record they own.
59+
- Org compliance status uses the latest signature per org; older historical rows do not keep an org in a warning state once the latest version is signed.
60+
- User can open a CLA from the list and view full language on `/sign/[orgSlug]`.
61+
- Data detail: full SHA-256 hash is persisted in DB; UI currently shows the short 7-character version label.
62+
63+
### 4) Admin creates a new CLA version
64+
65+
- Saving CLA updates current text/hash.
66+
- While editing, maintainers can preview rendering before save (`Edit`, `Split`, `Preview` modes; desktop defaults to split view).
67+
- Existing signatures remain historical; users on previous hash are treated as outdated and must re-sign.
68+
- If no one has signed prior versions, only current hash/text changes.
69+
- If prior versions were signed, historical signed versions remain in archives/signatures.
70+
- Saving CLA schedules an async workflow recheck of open pull requests; non-member contributors with missing/outdated signatures receive failing checks and updated signing comments once the workflow run completes.
71+
72+
### 5) Contributor opens a PR
73+
74+
- Org member: check passes, no CLA comment.
75+
- Personal-account repository owner: check passes, no CLA comment.
76+
- User or app/bot on org bypass list: check passes, no CLA comment.
77+
- Non-member + current signature: check passes, no CLA comment.
78+
- Non-member + outdated signature: check fails, re-sign comment posted.
79+
- Non-member + never signed: check fails, sign prompt comment posted.
80+
81+
### 6) Contributor signs/re-signs CLA
82+
83+
- Signature is stored with org, user, full CLA hash, accepted hash, assent metadata, immutable GitHub ID at signing time, timestamp, email provenance, and session evidence fields.
84+
- If `repo` + `pr` is provided, the signer must match that PR author before targeted PR sync is applied.
85+
- After signing/re-signing, the app schedules an async workflow to sync open PRs authored by that contributor in the org: latest CLA check runs are updated to success and stale CLA prompt comments are deleted.
86+
87+
### 7) Signed CLA versions cannot be deleted
88+
89+
- There is no route to delete signed CLA archives/signature history.
90+
- Signed version history behaves as append-only.
91+
92+
### 8) CLA downloads
93+
94+
- Contributors can download CLA versions from their own signing history.
95+
- Admins can download both current and archived CLA versions for orgs they administer.
96+
- Download endpoints enforce ownership/authorization and do not expose records across users/orgs.
97+
98+
### 9) Additional scenarios commonly missed
99+
100+
- Org deactivated/uninstalled: signing blocked; webhook events set passing CLA checks and remove managed CLA prompts so PRs are not blocked by CLA while inactive.
101+
- Activating or deactivating an org schedules an async open-PR recheck workflow so existing PR checks/comments converge automatically.
102+
- Updating either bypass section (users or app/bots) schedules async open-PR recheck so existing PRs converge to the latest policy.
103+
- `/recheck` authorization: allowed for PR author, org member, or maintainer; unauthorized users are blocked.
104+
- OAuth and install redirects sanitize `returnTo` to prevent open redirects.
105+
- Webhook hardening: production signature verification and delivery de-duplication.
106+
- Standard error paths: unauthorized, forbidden, missing org, invalid payload combinations.
107+
108+
### 10) OAuth/session lifecycle edge cases
109+
110+
- OAuth callback state mismatch/expired state cookie: sign-in fails safely and redirects back to `/auth/signin?error=...`.
111+
- GitHub token exchange or profile fetch failure: sign-in fails safely and no session cookie is issued.
112+
- Explicit logout clears JWT cookie; expired/invalid JWT is treated as signed-out.
113+
114+
### 11) `/recheck` command behavior edge cases
115+
116+
- `/recheck` is only processed on PR issue comments; non-PR issue comments are ignored.
117+
- Non-command comments (or non-created comment events) are ignored.
118+
- If PR head SHA cannot be resolved in production, `/recheck` fails with an error instead of guessing.
119+
120+
### 12) Webhook delivery/idempotency scenarios
121+
122+
- Duplicate `x-github-delivery` IDs are ignored via persistent DB-backed delivery tracking to reduce duplicate check/comment churn across process restarts.
123+
- Missing/invalid webhook signature is rejected in production when `GITHUB_WEBHOOK_SECRET` is configured.
124+
- Missing required payload fields return `400` and do not mutate DB/check state.
125+
126+
### 13) Installation lifecycle scenarios
127+
128+
- Installation `created` or `unsuspend`: account row is created/reactivated, installation ID refreshed, and installation target metadata (`organization` vs `user`) is persisted.
129+
- New installations are initialized with empty CLA text and `cla_text_sha256 = null` (no built-in agreement/template is auto-published).
130+
- Installation `deleted` or `suspend`: account is deactivated and installation ID cleared.
131+
- Installation repository-change events refresh installation linkage.
132+
133+
### 14) Access-control scenarios
134+
135+
- In production, org installs require live GitHub org-admin verification.
136+
- In production, personal-account installs are authorized when the signed-in GitHub user matches the installation target account.
137+
- In local dev/test, org-admin verification is relaxed to keep tests deterministic.
138+
- `/admin/[orgSlug]` and `/sign/[orgSlug]` handle unknown orgs with explicit not-found states.
139+
140+
## System Behavior Spec (PR/Webhook Flow)
141+
142+
- Pull request webhook checks whether PR author is an org member or has signed current CLA.
143+
- PR signature resolution is keyed by immutable GitHub user ID when available (username is fallback only).
144+
- Contributor dashboard status uses the latest stored signature per org to determine current/outdated state in UI.
145+
- Outcomes:
146+
- Org member: passing check, no CLA comment.
147+
- Bypass-listed user/app/bot: passing check, no CLA comment.
148+
- Signed current CLA: passing check, no CLA comment.
149+
- Unsigned/outdated signature: failing check + bot comment with signing URL.
150+
- App/bot bypass matching is slug-based and treats `<slug>` and `<slug>[bot]` as equivalent actor forms.
151+
- When CLA text changes, contributors on older signatures are marked as requiring re-sign; open PRs may require check re-evaluation and failure until re-signing.
152+
- After signing/re-signing, an async workflow updates signer-authored open PR CLA checks to success and removes stale CLA prompt comments.
153+
- Activating/deactivating CLA enforcement schedules async open-PR rechecks; inactive mode converges CLA checks to success and clears managed CLA prompt comments.
154+
- CLA bot comment updates/deletions are restricted to CLA-managed comments tagged with an internal signature marker, preventing edits to third-party bot comments.
155+
- Repository maintainers must require `CLA Bot / Contributor License Agreement` in GitHub branch protection/rulesets for merge blocking to be enforced.
156+
- Markdown ordered lists preserve explicit authored numbering (for example `1.`, `2.`, `7.` stays `1, 2, 7`), and legal alpha markers (`a.` / `a)`) render as ordered sub-clauses with indentation.
157+
158+
## Test Coverage
159+
160+
Reference UI coverage:
161+
162+
- `tests/e2e/pages-reference.spec.ts`
163+
- `home and dashboard pages render core navigation`
164+
- `sign-in page sanitizes external returnTo`
165+
- `admin page shows auth-gated state when signed out`
166+
- `admin list and org detail pages render for signed-in admin`
167+
- `contributor page shows auth-gated state when signed out`
168+
- `contributor page shows signed agreements when signed in`
169+
- `sign page handles auth-gated and not-found states`
170+
- `sign page supports re-sign flow after CLA update`
171+
172+
Reference API/flow coverage:
173+
174+
- `tests/integration/api-suite.test.ts`
175+
- Broad integration flow coverage for auth/session APIs, org management, signing/re-signing, webhook checks/comments, install/uninstall/suspend lifecycle, and `/recheck` authorization.
176+
- Includes edge cases like stale-signature detection after CLA updates, `/recheck` handling/authorization on open PRs, malformed webhook payload rejection, and duplicate webhook delivery de-duplication.
177+
178+
## Keeping Spec and Tests in Sync
179+
180+
When behavior changes:
181+
182+
1. Update the relevant sections in this file.
183+
2. Update or add coverage in `tests/integration/api-suite.test.ts` and/or `tests/e2e/pages-reference.spec.ts` as appropriate.
184+
3. Run `pnpm test` and `pnpm build` before merging. Run `pnpm test:all` when UI/browser behavior changes.

0 commit comments

Comments
 (0)