Proposal: streamline LFX Mentorship program intake
- Dominant language
- JavaScript
- Stars
- 3.1k
- Forks
- 816
- Avg merge
- 12h 32m
- Merged PRs (30d)
- 8
Description
## TL;DR
Replace today's "open a PR against `project_ideas.md`" intake with a **GitHub Issue Form** + a small set of **GitHub Actions workflows** that:
- lower the barrier for maintainers/mentors to submit a proposal (web form, no git knowledge),
- validate the submission automatically (format checks now; LFID/email verification once we coordinate with the LFX platform team),
- track each program (not each PR) through the lifecycle on [Project board #92][project-92],
- produce a **machine-readable artifact** the LFX platform team can ingest in bulk instead of us re-keying every field by hand.
This issue is a discussion document. Feedback welcome from maintainers, mentors, the LFX Mentorship platform team, and CNCF program admins. See [§10 How to give feedback](#10-how-to-give-feedback) at the bottom.
*Authored by @nate-double-u with assistance from Copilot CLI.*
---
## 1. Problem
The current intake process for each LFX Mentorship term requires maintainers and prospective mentors to open pull requests against `programs/lfx-mentorship///project_ideas.md`, adding a free-form markdown block per program. This is painful in three ways:
1. **High barrier for proposers.** Maintainers must understand git/PR mechanics to propose a program, even when they have no other reason to touch the repo.
2. **High overhead for the program admin.** Each accepted program must be re-keyed (or copy-pasted) into the LFX Mentorship platform by hand, field by field, from prose markdown.
3. **Mismatched lifecycle tracking.** [Project board #92][project-92] tracks each *PR* through an 11-stage status pipeline, but the actual unit of work is a *program* — and a single PR often contains several programs. The board and the reality drift apart after merge.
[project-92]: https://github.com/orgs/cncf/projects/92/views/2
## 2. Goals
- Lower the barrier for maintainers/mentors to submit a program proposal.
- Produce a **machine-readable artifact** per term that the LFX platform team can ingest in bulk (or that we can ingest via an API, eventually).
- Track each program (not each PR) through the existing project-board lifecycle.
- Catch the most common data-quality failures — wrong mentor email, malformed upstream URL — *before* a human reviews the proposal.
## 3. Non-goals
- Replacing the LFX Mentorship platform itself.
- Automating selection of mentees (still owned by mentors / LFX).
- Building anything Outreachy- or GSoC-scale (custom web app, applicant DB).
## 4. Proposed approach
### 4.1 Intake: GitHub Issue Form
A new `.github/ISSUE_TEMPLATE/lfx-program-proposal.yml` form replaces the
near-empty existing template. Each submitted issue represents **one program proposal**.
Fields (subject to refinement once LFX import schema is known):
| Field | Type | Required | Notes |
| ----------------------------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CNCF Project | dropdown | ✓ | Generated from `cncf/landscape`. See §4.2. |
| Term | dropdown | ✓ | Current + next two terms. Value is the bare token (e.g. `2026 Term 1`); months shown as helper text. |
| Program Name | input | ✓ | Bare middle of LFX Program Name; export composes the full convention. See §4.1.2. |
| Program Description | textarea | ✓ | Up to 3000 characters. Maps directly to LFX's single Program Description field. Use placeholder/sample text in the form to prompt for both **Description** (what the program is) and **Expected Outcomes** (what success looks like) within the single field — see §4.1.5. |
| Technologies | input | ✓ | Comma-separated. Maps to LFX Step 1 "Technologies" chips. |
| Required/Desirable Skills | input | ✗ | Comma-separated. Maps to LFX Step 2 "Required and/or desirable skills and training" chips. Leave blank if same as Technologies (and keep the checkbox above checked). |
| Skills same as Technologies? | checkboxes | ✗ | Single checkbox, default **checked**. If checked, the field below may be left blank and the export uses Technologies for both. See §4.1.4. |
| Mentors | textarea | ✓ | One mentor per line. Format: `Full Name \| @github-handle \| lfid-email`. The first line is the **primary mentor**; subsequent lines are additional mentors. See §4.1.3 for rationale and validation. |
| Upstream Issue URL | input | ✓ | Exactly one URL. If the program covers multiple issues, create an umbrella issue upstream and link to that. Validation rejects multiple URLs. Maps to LFX's Repository URL field on export — see §4.1.1. |
| Application Requirements | checkboxes | ✗ | Common patterns: writing sample, project proposal, code/portfolio link. All treated as required. See §4.7. |
| Additional Application Requirements | textarea | ✗ | Free-form for anything not covered above. Configured manually on LFX as required items. |
Auto-applied labels on submission: `lfx mentorship`, `proposal`, `needs-validation`.
**Why one issue per program (not one issue per maintainer with multiple programs):**
The project-board lifecycle is per-program. Bundling forces multiple programs into one row and re-creates today's mismatch.
#### 4.1.1 Mapping to the LFX platform wizard
The form fields above are designed to populate LFX's program-creation wizard
(see screenshots attached to this issue). Important constraints learned from
the LFX UI:
| LFX wizard field | LFX limit / type | Our form field | Notes |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Program Name | 100 chars, plain text | Program Name (bare middle only — see below) | Form collects only the program name; export composes `CNCF - : ()`. |
| Linux Foundation Open Source Project | dropdown, parent foundation only | (none — implicit) | "CNCF Project" on our form is for our own labeling, not an LFX field. |
| Technologies (chips) | multi-chip | Technologies | Parsed from comma-separated input. |
| Program Description | 3000 chars, rich text | Program Description | 1:1 mapping. The form field has the same 3000-char limit as LFX. Validation enforces it. |
| Repository URL | URL, required | Upstream Issue URL | The form's **Upstream Issue URL** field flows into LFX's Repository URL slot. This is what gives applicants a single landing point for the work — the issue (or umbrella issue, §4.1) is more useful than the project's root repo URL, which they can find from the Website URL anyway. |
| Website URL | URL, required | (auto-populated from landscape) | Required by LFX. Auto-pulled from `cncf/landscape` for the selected project. If landscape is missing the value, fix it upstream (in `cncf/landscape`) — don't add a manual-override field on our form. |
| CII / OpenSSF Best Practices Project ID | numeric, optional | (auto-populated from landscape) | |
| Code of Conduct URL | URL, optional | (auto-populated from landscape, fallback to CNCF CoC) | Auto-pulled from `cncf/landscape` for the selected project. If landscape doesn't have one, default to the [CNCF Code of Conduct](https://www.cncf.io/conduct/) — every CNCF project is bound by it regardless. |
| Program Logo | file upload, JPG/PNG/SVG, ≤2MB, 420×420 recommended | (auto-fetched from landscape) | Landscape stores SVGs. |
| Required and/or desirable skills and training (chips) | multi-chip | Required/Desirable Skills (or = Technologies via checkbox) | Two separate fields; "Skills same as Technologies?" checkbox lets the proposer signal the common case (same set) without retyping. See §4.1.4. |
| Program Terms | list of {Name (50 char), Start Month, Start Year, End Month, End Year} | Term (dropdown) | Our dropdown selects one term; export translates to LFX's Term object. |
| Standard Prerequisites (Resume, Cover Letter, School Enrollment, Participation Permission, Coding Challenge) | toggle on/off, each with editable description | Application Requirements (checkboxes) | Our "Project Proposal" maps to enabling LFX Cover Letter. See §4.7. |
| Standard course prerequisite (LFC102 / Inclusive Community) | fixed name/description, "requires file upload" flag, term-wide due date | (injected automatically, term-wide) | Mandatory for every mentee per #1866; not maintainer-authored. Surfaced on the form as an informational note; injected into the export from one constant. Shipped in #1905. See §4.6. |
| Custom Prerequisite | Name 20 chars, Description 500 chars, "requires file upload" flag | Per-program, maintainer-authored | Optional per-program task (e.g. Writing Sample). Shares the term-wide due date. See §4.6 / §4.7. |
> Note on scope/sizing fields: CNCF LFX programs are full-time (~350 hours
> across the term), so we deliberately don't ask for Difficulty or
> Time Commitment on the form.
These are valuable for our own intake and project-board tracking, and the mentor list also seeds LFX's post-approval Mentors tab.
#### 4.1.2 Program Name composition
CNCF programs follow a strict naming convention on LFX:
```
CNCF - : ()
```
Example: `CNCF - LitmusChaos: Add Prometheus Metrics to Control Plane Service (2026 Term 1)`
The fixed scaffolding consumes 23 characters plus the project name (and term):
- `CNCF - ` (7)
- `: ` (2)
- ` (` + `)` around the term (3)
- `` itself (e.g. `2026 Term 1` = 11)
So for the typical `` value of `YYYY Term N` (11 chars), the available
budget for the bare program name depends on the project name length:
| Project name | Project name length | Program-name budget (within LFX's 100-char limit) |
| ------------------------ | ------------------- | ------------------------------------------------- |
| OTel | 4 | 73 |
| Koordinator | 11 | 66 |
| LitmusChaos | 11 | 66 |
| OpenTelemetry | 13 | 64 |
| kubernetes-sigs/headlamp | 24 | 53 |
**Form treatment:** the issue form collects **only the bare middle** in the
"Program Name" field. The CNCF Project (§4.2 dropdown) and Term (§4.1
dropdown) supply the surrounding pieces. The export workflow (§4.5)
composes the full LFX Program Name value at export time. The validation
workflow (§4.3) checks that the bare program name fits within the budget for
the selected project and warns the author with a precise character count if
not. This both prevents truncation (as seen in the screenshot, where 98/100
chars chopped the front of "Native Support for AI Agent...") and eliminates
the naming-convention drift visible across recent PRs (`Term 1` vs `T1`,
missing year, missing "CNCF -" prefix, etc.).
The **Term dropdown values** (§4.1) should produce just the `` token
expected by the convention (e.g. `2026 Term 1`), not a longer label including
months. If you want the months visible to the form author, render them as
helper text below the dropdown rather than as part of the option string.
#### 4.1.3 Mentors field shape
We need three things per mentor: **full name**, **GitHub handle**, and **the
email address tied to their LFID** (transitional — once we can look up LFIDs
directly, the email column becomes the LFID).
GitHub Issue Forms don't support repeating field groups, so a variable-length
list of structured records has to live in a single textarea. We use one line
per mentor with a pipe-separated format:
```
Full Name | @github-handle | lfid-email
```
Example:
```
Jane Doe | @janedoe | jane@example.com
Sam Lee | @samlee | sam@example.com
```
The **first line is the primary mentor**; subsequent lines are additional
mentors. There is exactly one form field for both — no separate "Primary
Mentor" block — which keeps the data shape consistent across mentor counts
and avoids the awkward "why does the primary mentor get three fields but the
additional mentors only get a textarea" inconsistency.
**Why pipe-separated, not comma-separated:** names contain commas
("Smith, John, PhD"); pipes don't appear in real names, GitHub handles, or
emails, so parsing is unambiguous.
**Validation rules** (also reflected in §4.3):
- At least one line (primary mentor required)
- Each line has exactly 3 pipe-separated fields after trimming whitespace
- Field 1 (name): non-empty
- Field 2 (handle): matches `@[A-Za-z0-9-]{1,39}` (GitHub handle rules)
- Field 3 (email): matches email regex
- No duplicate handles or emails across rows
> **Dependency on the LFX team:** the email-vs-LFID part of this design is
> contingent on coordination with the LFX Mentorship platform team. Today
> we collect "the email address used with their LFID" because that's what
> we can verify by hand. Once LFX exposes an LFID lookup (see §5 Ask #1),
> we'd switch this column to LFID directly and drop the email collection.
> The textarea shape is designed to make that swap trivial — same field,
> different per-line format.
#### 4.1.4 Technologies vs. Required/Desirable Skills
The LFX wizard exposes two chip-style fields that look similar but live in
different wizard steps (see screenshots, §4.1.1):
- **Technologies** (Step 1, Program Details): the technical surface area of
the program — languages, frameworks, platforms.
- **Required and/or desirable skills and training** (Step 2, Program Setup):
what an applicant should know coming in.
In the common case these are the same set ("if the program is in Go on
Kubernetes, applicants need Go and Kubernetes"). In the less-common case
they diverge — e.g. a documentation program might be *about* a particular
codebase (Technologies = the project's language/stack) but only require
prose skills of the applicant (Skills = "technical writing, Markdown").
We collect both fields so we can populate the LFX wizard accurately, but
add a single checkbox **"Skills same as Technologies?"** (default checked)
so the proposer doesn't retype the same set in 90% of cases. Form behavior:
- Checkbox checked + Required/Desirable Skills blank → export uses
Technologies for both wizard fields
- Checkbox checked + Required/Desirable Skills also filled → validation
warns about the contradiction (likely user error); admin clarifies
- Checkbox unchecked + Required/Desirable Skills blank → validation fails
("either check the box or fill in the field")
- Checkbox unchecked + Required/Desirable Skills filled → export uses each
field as given
Issue Forms can't conditionally show/hide fields, so both inputs are always
visible; the checkbox is a *signal* the workflow reads at validation and
export time, not a UX trigger.
> **Open question carried forward (§5 Ask #7):** whether these are truly
> two LFX fields or one rendered twice. If LFX confirms they're the same
> field, we can collapse this back to a single input and drop the checkbox.
#### 4.1.5 Program Description prompt
LFX has one combined Program Description field (3000 chars). Mentors vary
on how they'd want to split that — some lead with outcomes, some with
context, some weave them together. Rather than impose a structure, we
collect a single field but use the form's placeholder/sample text to
prompt for both halves so nothing important gets dropped:
```yaml
# .github/ISSUE_TEMPLATE/lfx-program-proposal.yml (excerpt)
- type: textarea
id: program_description
attributes:
label: Program Description
description: |
Up to 3000 characters. This is what applicants will see on LFX.
Cover both what the program is about and what success looks like —
use the structure below or your own.
placeholder: |
## Description
What the program is about, the problem it solves, and the context
a candidate needs to understand the work.
## Expected outcomes
What "done" looks like at the end of the term — concrete deliverables,
contributions, or capabilities the mentee should have produced.
value: ""
validations:
required: true
```
The `placeholder` text appears greyed-out inside the textarea until the
proposer starts typing — so it shows the suggested structure without
forcing it. The `description` (above the textarea) gives the rules.
Validation enforces the 3000-char hard limit (§4.3) but doesn't enforce the
structure — proposers can use the suggested headings, their own headings,
or none at all.
### 4.2 CNCF Project dropdown — keeping it current
GitHub Issue Form dropdowns are **static** — options are baked into the form's
YAML at commit time, not fetched at issue-creation time. To keep the CNCF
Project dropdown in sync with reality without manual edits, a scheduled
workflow regenerates the form's `options:` block from the canonical landscape
data.
**Source:** [`landscape.yml` in `cncf/landscape`](https://github.com/cncf/landscape/blob/master/landscape.yml).
Filter to category `CNCF Graduated, Incubating, and Sandbox` (i.e., active
CNCF-hosted projects), excluding archived projects and the many landscape
entries that are not CNCF-hosted (member companies, end-user companies, etc.).
That yields ~200 projects, well within issue-form dropdown limits.
**Workflow shape:**
- Trigger: weekly cron + `workflow_dispatch` for ad-hoc refresh.
- Steps: download `landscape.yml` → filter → diff against current options block in `lfx-program-proposal.yml` → if changed, rewrite the YAML and open a PR (or auto-commit, if low-risk).
- Bot commits use the `Assisted-by:` trailer per CNCF convention.
**Edge cases:**
- **CNCF project sub-projects sometimes use LFX Mentorship.** Real example: PR [#1861](https://github.com/cncf/mentoring/pull/1861) added a `sigs.k8s.io/node-readiness-controller` proposal. The dropdown should include an explicit `Other / not yet listed` option that reveals (via the validation workflow's bot comment) a freeform follow-up where the proposer explains. Avoids forcing edge cases through an overly-narrow dropdown.
- **Naming canonicalization.** Use the landscape's `name:` field as-is so naming flows downstream consistently. (Examples in recent PRs include both `kgateway` and `Kgateway` — pick the landscape spelling and stick to it.)
- **Regeneration noise.** Only commit when the filtered list actually changes.
### 4.3 Validation: GitHub Actions workflow
`.github/workflows/lfx-proposal-validate.yml` triggers on `issues.opened` /
`issues.edited` for issues carrying the `lfx mentorship` + `proposal` labels.
It performs:
**Format checks (immediate, no external deps):**
- Email regex on each mentor's email (transitional — see §4.1.3 on switching to LFID)
- URL regex on upstream issue, plus a check that exactly one URL is provided (umbrella the work upstream if it spans multiple issues)
- GitHub-handle regex on each mentor's handle
- Per-line shape check on the Mentors textarea (3 pipe-separated fields per row; at least 1 row; no duplicate handles or emails — see §4.1.3)
- Program Name length within the per-project budget (§4.1.2)
- Program Description ≤ 3000 characters (LFX limit, §4.1.1)
- Technologies / Skills consistency (§4.1.4): if "Skills same as Technologies?" is unchecked, Required/Desirable Skills must be non-empty; if checked, Required/Desirable Skills should be blank (warn if both are filled)
**LFX checks (stretch — requires LFX cooperation; see "Asks of LFX" below):**
- Verify each mentor email maps to a registered LFID on the LFX Mentorship platform
- Verify the CNCF project name is recognized on the LFX side
**CNCF checks (no external dependencies; details in §4.3.1–§4.3.4):**
- Per-project per-term proposal quota (§4.3.1)
- Maintainer / ContribEx approval status (§4.3.2)
- Mentor confirmation tracking (§4.3.3)
- CNCF Mentorship admin approval (§4.3.4)
**Output:**
- Bot comment summarizing ✅ pass / ❌ block / ⚠️ warning checks. Upserted (one comment per issue, edited in place on re-validation).
- Labels: `validation-passed` / `validation-failed`. `needs-validation` removed once a verdict exists.
**Implementation note (security):**
A draft of the validation workflow uses `peter-murray/issue-forms-body-parser@v4.1.0` for body parsing. Recommend **inlining the parser** (~30 lines inside `actions/github-script`) for the production version to avoid a third-party supply-chain dependency in a workflow that will hold the `LFX_API_TOKEN` secret. If we keep the third-party action, **pin to a full commit SHA**, not a tag.
#### 4.3.1 Per-project per-term proposal quota
CNCF projects are currently capped at **4–5 program proposals per term**
(subject to change in future terms). Today this is enforced by program-admin
attention; we can move it into validation:
- **Source of truth:** a small config file in this repo, e.g.
`programs/lfx-mentorship/quotas.yml` with a global default and per-project
overrides:
```yaml
default_per_project_per_term: 5
overrides:
kubernetes: 8 # large project with many SIGs
open-telemetry: 6
```
- **Check:** on `issues.opened` / `issues.edited`, count open proposal issues
with the same `` + `` labels. If `count > quota`, label
`over-quota` and post a comment listing the existing proposals so the
project can decide which to withdraw.
- **Override:** an admin can remove the `over-quota` label manually after
confirming the exception is intentional.
- **Tone:** this should be a warning, not a hard block — the goal is to
surface the situation, not to litigate it via a bot.
#### 4.3.2 Maintainer / project approval verification
Each program needs at least one approval from a maintainer of the CNCF
project, or from an equivalent project-governance role for larger projects
(e.g., a Kubernetes SIG-ContribEx lead, or an OpenTelemetry SIG/Governance
Committee liaison).
**Sources of truth (checked in order):**
The approvals workflow uses a **four-tier authorization chain**. It checks
each tier in order and stops at the first match:
1. **`.project` maintainers** — if the project's GitHub org has adopted the
[CNCF `.project` tooling](https://github.com/cncf/automation/tree/main/utilities/dot-project),
the workflow fetches `{org}/.project/maintainers.yaml` and checks the
`project-maintainers` team members list. This is only attempted when the
landscape sync has flagged `has_dot_project: true` for the project in
`projects.yml`.
2. **`cncf/foundation/project-maintainers.csv`** —
[`cncf/foundation/blob/main/project-maintainers.csv`](https://github.com/cncf/foundation/blob/main/project-maintainers.csv)
(columns: lifecycle stage, Project, Maintainer Name, Company, Github Name,
OWNERS link). Parse rows scoped to the selected project; the `Github Name`
column gives us the candidate approver list.
3. **Per-project fallbacks** — `programs/lfx-mentorship/automation/approvers.yml`:
```yaml
kubernetes:
fallback_teams:
- kubernetes/sig-contribex
open-telemetry:
fallback_teams:
- open-telemetry/governance-committee
fallback_handles:
- maryliag # GC liaison for mentorship
```
4. **Global approvers** — also in `approvers.yml`. These handles can
`/approve` for any project (useful for CNCF staff and cross-project
liaisons):
```yaml
global_approvers:
- nate-double-u
- dkrook
```
**Verification mechanism:** a comment containing `/approve` (Prow-style)
from an authorized user. The workflow scans every line of the comment for
the command (so context text before the `/approve` is fine). Unauthorized
users who attempt `/approve` receive a reply explaining why it didn't work
and listing the recognized approver sources.
**Workflow behavior:**
1. On `issue_comment.created`, the workflow detects `/approve` and checks
the commenter against the four-tier auth chain above.
2. If authorized, the workflow applies the `Maintainer/Contribex Approved`
label and posts a confirmation comment.
3. If unauthorized, the workflow replies explaining that the commenter isn't
recognized as a maintainer for that project and lists the sources checked.
4. The proposer's own `/approve` is permitted if they appear in any of the
four tiers — maintainers often self-propose.
#### 4.3.3 Mentor confirmation
Every mentor named on the proposal — primary plus any additional — needs to
confirm in writing that they're willing to mentor for this term. Today this
happens implicitly (mentor opens or co-authors the PR), or they 👍 or
approve the PR. With the issue-form flow, the proposer may not be one of
the mentors, so we need an explicit confirmation step.
**Verification mechanism:** a comment containing `/confirm` from a mentor
whose GitHub handle is listed in the Mentors field of the issue body. The
workflow parses the Mentors table to extract GitHub handles and checks the
commenter against that list.
**Workflow behavior:**
1. On `issue_comment.created`, the workflow detects `/confirm` and checks
whether the commenter's GitHub handle appears in the Mentors field.
2. If recognized, the workflow applies the `Mentors Confirmed` label and
posts a confirmation comment. *(Current MVP confirms on any single
mentor's `/confirm`; tracking per-mentor confirmation status is a future
enhancement.)*
3. If the commenter isn't listed as a mentor, the workflow replies explaining
why it didn't work.
**Identity check:** confirmation must come from the GitHub handle listed on
the form. If `@alice` is listed and `@alice2` comments `/confirm`, that
doesn't count — this catches typos in the form before they propagate to LFX.
**Workflow behavior:**
1. On `issues.opened` and on `issues.edited` (when the mentor list changes),
the bot posts a comment that @-mentions every listed mentor and asks each
of them to either 👍-react on the issue or comment `/confirm`.
2. The bot tracks which mentors have confirmed. As long as one or more
confirmations are missing, the issue stays in `Awaiting approvals /
confirmations` and the bot's tracker comment is upserted with the current
state ("✅ confirmed: @a, @b — ⏳ pending: @c").
3. Once *all* listed mentors have confirmed, apply the existing
`Mentors Confirmed` label.
4. If the mentor list is later edited to add a new mentor, the
`Mentors Confirmed` label is cleared and the bot re-pings just the new
names.
**Identity check:** confirmation must come from the GitHub handle listed on
the form. If `@alice` is listed and `@alice2` reacts, that doesn't count —
this catches typos in the form before they propagate to LFX.
#### 4.3.4 CNCF Mentorship admin approval
The final gate before a proposal moves to "CNCF Approved" is sign-off from
the CNCF Mentorship admin team.
**Source of truth:** the `global_approvers` list in
`programs/lfx-mentorship/automation/approvers.yml`. *(Current MVP; can
migrate to a GitHub team like `cncf/mentorship-admins` later.)*
**Workflow behavior:**
1. The bot does not ping admins automatically — admin review is intentional,
not automatic. Admins find proposals via the project board's `Awaiting
approvals / confirmations` and `approved/confirmed` columns.
2. An admin signals approval via a `/cncf-approve` comment.
3. On detection, the bot checks that both `Maintainer/Contribex Approved`
and `Mentors Confirmed` labels are present. If either is missing, it
replies with a warning explaining which gate(s) are still open, and does
**not** apply the label.
4. If both gates are satisfied, the bot applies the `CNCF Approved` label,
which moves the issue to the `CNCF Approved` column on the project board
(§4.4).
**Sequencing summary** (all gated on validation passing):
| Gate | Who | Mechanism | Label applied |
| ------------------- | ---------------------------------- | ---------------------------- | -------------------------------------- |
| Validation | bot | format checks (§4.3) | `Awaiting Maintainer/Contribex Approval` + `Awaiting Mentor Confirmation` |
| Project approval | maintainer (4-tier auth) | `/approve` comment (§4.3.2) | `Maintainer/Contribex Approved` |
| Mentor confirmation | each named mentor | `/confirm` comment (§4.3.3) | `Mentors Confirmed` |
| CNCF admin approval | mentorship admin / global approver | `/cncf-approve` comment (§4.3.4) | `CNCF Approved` |
#### 4.3.5 LFX URL recording
After a program is created on the LFX platform, a CNCF admin records the URL it
returns by commenting `/lfx-url ` on the proposal issue. This replaces the
manual "edit the export files by hand and drag the board card" step. The handler
lives in the same `issue_comment` workflow as `/approve`, `/confirm`, and
`/cncf-approve`.
**Source of truth for authorization:** the same `global_approvers` allowlist as
`/cncf-approve` (§4.3.4).
**Workflow behavior:**
1. **Authorization** — the commenter must be in `global_approvers`
(`programs/lfx-mentorship/automation/approvers.yml`).
2. **Precondition** — the issue must carry the `Exported` label; otherwise the
bot replies that the program must be exported first.
3. **URL validation** — the argument must be a syntactically valid URL. If the
host is not `mentorship.lfx.linuxfoundation.org`, the bot warns but proceeds
(catches paste mistakes without blocking a legitimate redirect or host
change).
4. **Record on the issue** — the bot posts a parseable comment
(`LFX URL recorded from @user: `), the durable source of truth, mirroring
the approval-record pattern the export reads back (§4.5).
5. **Update the files automatically** — the URL is filled into that term's
`lfx-export.json`, `README.md`, and `lfx-tracking.csv`, landing the change in
an accumulating per-term pull request. The file generation is shared with the
export (§4.5) via tested libraries so both produce identical output.
6. **Advance the board** — the bot explicitly moves the card
`Exported → Posted to LFX` (§4.4).
### 4.4 Lifecycle: Project board automation
A second workflow keeps Project #92 (and successor boards each term) in sync
with the issue's state, mirroring today's manual flow:
| Status | Trigger |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Inbox | `issues.opened` with `proposal` label → add to project, set Status |
| Awaiting approvals/confirmations | `validation-passed` set, but `Maintainer/Contribex Approved` and/or `Mentors Confirmed` missing (§4.3.2, §4.3.3) |
| approved/confirmed | Both `Maintainer/Contribex Approved` and `Mentors Confirmed` present |
| CNCF Approved | `cncf-approved` label applied via §4.3.4 |
| Exported | Export workflow (§4.5) adds `Exported` label + comments on each issue linking to the export files |
| Posted to LFX | Admin-invoked `/lfx-url ` command after the program is created on LFX (§4.3.5) |
| LFX Approved | Manual — *future automation pending LFX webhook/API* |
| Mentors added | Manual — *future automation pending LFX API* |
| Listed in README | Auto when a PR touching the term README that closes this issue merges |
| Open for Applications | Scheduled / manual — driven by LFX calendar |
| Applications Closed | Scheduled / manual |
| Closed | Issue closed |
**Ownership boundary.** Automatic board sync stops at `Exported`. `Posted to
LFX` and the columns beyond it stay admin-owned: validation, approvals,
board-sync, and export never advance a card into them or pull a manually
advanced card back. The `/lfx-url` command (§4.3.5) is the single intentional
exception — an explicit, admin-invoked `Exported` → `Posted to LFX` advance. A
posted issue still carries the `Exported` label, so this regression guard must
remain, or the normal status cascade would map the card back to `Exported`.
**Setup instructions for the Project v2 token:**
The board sync workflow requires a PAT stored as the `PROJECT_TOKEN` repo
secret. The token type differs between dev and prod because fine-grained
tokens don't yet support Projects permission for user-owned projects.
#### Development (nate-double-u/mentoring fork) — classic PAT
1. Go to [**Settings → Tokens (classic)**](https://github.com/settings/tokens/new)
2. **Note:** `MENTORING_PROJECT_BOARD_DEV`
3. **Expiration:** 90 days
4. **Scopes:** check `project` (Full control of projects)
5. Generate and store as a repo secret:
```bash
gh secret set PROJECT_TOKEN --repo nate-double-u/mentoring
```
#### Production (cncf/mentoring) — fine-grained PAT
1. Go to [**Settings → Fine-grained tokens**](https://github.com/settings/personal-access-tokens/new)
2. **Token name:** `MENTORING_PROJECT_BOARD_PROD`
3. **Expiration:** 90 days (set a calendar reminder to rotate)
4. **Resource owner:** `cncf` (requires org admin approval)
5. **Repository access:** select `cncf/mentoring`
6. **Permissions:**
- Organization → **Projects:** Read and write
7. Generate, get org admin approval if required, and store as a repo secret:
```bash
gh secret set PROJECT_TOKEN --repo cncf/mentoring
```
> **Why the difference?** Fine-grained PATs don't yet expose the Projects
> permission for user-owned projects (only org-owned). Classic PATs have a
> broad `project` scope that works for both. Once GitHub adds fine-grained
> support for user-owned projects, the dev setup can switch to match prod.
>
> **Long-term:** migrate to a GitHub App with `projects: write` to avoid
> PAT expiration and single-user bus factor.
### 4.5 Export: machine-readable artifact, human-readable README, and tracking CSV for LFX
A manual `workflow_dispatch` workflow walks all open issues with `Proposal` +
`CNCF Approved` labels for a given term and produces **three outputs**:
1. **`lfx-export.json`** — structured JSON for the LFX platform team to
bulk-import. One record per program with all fields parsed from the issue
form (project metadata, mentors, prerequisites, etc.). The schema should
mirror the LFX bulk-import schema as closely as possible — **this is the
single most important thing to confirm with LFX up front.** See §4.1.1
for the field-mapping inferred from the LFX wizard UI.
- Mentor identities are included in the export (parsed from the textarea
per §4.1.3, primary = first row).
- For fields auto-populated from `cncf/landscape` (Website URL, CoC URL,
Logo, OpenSSF Best Practices ID), the export workflow looks up the
selected CNCF project at export time and injects the values.
- Includes an `lfx_url` field, populated from the issue's recorded LFX URL
(§4.3.5) when present and left empty otherwise.
2. **`README.md`** — human-readable accepted-programs list in the existing
format used by `cncf/mentoring`. Generated from the same issue data.
Includes:
- Term header, status, duration
- Table of Contents (nested: CNCF Project → program titles)
- Accepted Projects section with full details per program (description
that covers both what the program is about and expected outcomes,
recommended skills, technologies, mentors, upstream issue, and LFX URL).
The `LFX URL:` line is populated from the issue's recorded URL (§4.3.5)
when present, otherwise `TBD`.
3. **`lfx-tracking.csv`** — flat CSV for importing into the program admin's
tracking spreadsheet. Columns:
| Column | Notes |
| ------ | ----- |
| `PROJECT` | Full LFX program name (`CNCF - : ()`) |
| `LFX URL` | Populated from the issue's recorded LFX URL (§4.3.5) when present; blank otherwise |
| `Upstream issue` | From the form |
| *(empty)* | Spacer column |
| `mentor count` | Number of mentors listed |
| `Mentor 1` … `Mentor 4` | Name |
| `Mentor 1 GitHub` … `Mentor 4 GitHub` | GitHub handle |
| `Mentor 1 email address` … `Mentor 4 email address` | Email |
Up to 4 mentors per program (flattened into columns). Programs with
fewer mentors leave the extra columns blank.
Both files are written to `programs/lfx-mentorship///` and the
workflow opens a PR for review.
**Issue notifications:** After generating the export files, the workflow:
1. Adds the `Exported` label to each included issue
2. Comments on each issue with a link to the export files on the PR branch
This keeps mentors and maintainers informed without requiring them to
watch the repo's Actions tab.
Stretch: replace the export PR with a direct API call to LFX
(`POST /programs`) once such an endpoint exists. The intermediate file is
still useful as an audit trail.
### 4.6 Mentee prerequisites & LF Education integration
LFX programs can attach **prerequisite tasks** with a name, due date, description,
and an optional "requires file upload" flag. LFX provides a **standard prerequisite
library** (Resume, Cover Letter, School Enrollment Verification, Participation
Permission from school/employer, Coding Challenge), each toggle-able and with an
editable description. **Custom Prerequisites** can also be added (name capped at 20
chars, description at 500 chars; these limits are tight, see §6 ask). LFX accepts
several prerequisites per program, so a standing requirement and a per-program
custom task can coexist.
**Standard course prerequisite (LFC102 / Inclusive Community).** Per #1866, every
CNCF mentee must complete the [Inclusive Open Source Community Orientation
(LFC102)](https://training.linuxfoundation.org/training/inclusive-open-source-community-orientation-lfc102/)
course. This is CNCF policy, uniform across every program in a term, and is not
authored by maintainers. LFX exposes exactly four fields for a prerequisite (name,
capped at 20 chars; a due date; a description; and a "requires file upload"
checkbox), so the values below are used verbatim:
- **Prerequisite name:** Inclusive Community
- **Requires file upload:** Yes
- **Due date:** the term-wide prerequisite due date (see below)
Description (verbatim):
Please upload the completion certificate for the Inclusive Open Source Community Orientation (LFC102) course: https://training.linuxfoundation.org/training/inclusive-open-source-community-orientation-lfc102/
Once completed you can find the certificate in your openprofile.dev dashboard under Training & Certifications -> Certificates of Completion.
**Due dates are term-wide.** One due date applies to every prerequisite task in a
term (LFC102 and any per-program custom task), set once by the admin at upload time
rather than captured per-prerequisite on the form. Last term's value was 2026-05-19
(roughly two weeks after program start).
**4.6a. Shipped: single source of truth, wired into intake and export (#1905).** The
LFC102 task lives as one constant in the automation lib
(`programs/lfx-mentorship/automation/lib/prerequisites.js`):
- **Form:** an informational note at the end of the proposal form's Prerequisites
section tells maintainers every mentee must complete LFC102. It is added
automatically; maintainers do not author it. It is a note, not a checkbox: the
requirement is mandatory, and GitHub issue forms cannot pre-check or lock a box.
- **Export:** each program's `prerequisites.standard_courses` in the export JSON
always includes the LFC102 task, materialized from the constant. It is
deliberately not written to the README or tracking CSV; mentor awareness comes
from the form note.
- **Upload (Phase 2):** the upload will map the JSON prerequisites to LFX tasks and
stamp them with the term-wide due date supplied as a workflow input, so the LFC102
task lands on every program.
**4.6b. Distinct from application requirements.** Prerequisites (this section) are
tasks the mentee completes after selection, with due dates. Application requirements
(§4.7) are things applicants submit during application, before selection: writing
samples, project proposals, etc. The two are configured separately on LFX and serve
different purposes.
**4.6c. LF Education integration (stretch).** LFC102 is issued by LF Training &
Certification (training.linuxfoundation.org) and surfaces on openprofile.dev. If the
LFX team can wire LFX to LF Education to check completion programmatically, keyed on
the mentee's LFID, we can drop the manual certificate-PDF review entirely. This is
the LF-Education-side ask worth coordinating with the LFX team on.
The remaining manual steps (configuring the due date and checking the certificate)
persist until the LFX-side asks land (see "Asks of LFX" -> ask #6).
### 4.7 Per-program application requirements
Unlike prerequisites (§4.6a, term-wide and uniform), **application requirements**
are configured per program by the maintainer to filter / evaluate applicants
*before* selection. The most common patterns surfaced so far:
- **Writing sample** (file upload) — common for technical-writing programs.
- **Project proposal** (file upload or long-text) — some mentors want
applicants to articulate how they'd approach the work before being selected.
- **Code sample or portfolio link** — common for senior-skewing programs.
**All application requirements are treated as required** (hard gate;
applicant cannot submit without satisfying them). We deliberately do not offer
an "optional" variant: if it's optional, it isn't a requirement, and offering
the choice multiplies administrative overhead without changing outcomes.
**Mapping to LFX standard prerequisites:**
LFX provides built-in toggle-able application items (see §4.6 / screenshots).
Our form's checkboxes should map to those rather than create parallel custom
prerequisites where a standard one already exists:
| Our checkbox | LFX equivalent | Notes |
| ------------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| Project proposal (file upload) | Standard: **Cover Letter** | LFX's Cover Letter has a built-in structured prompt (motivation, experience, goals). Customize the prompt at export time if needed. |
| Code sample or portfolio link | Standard: **Coding Challenge** | URL-based; works for portfolio links too. |
| Writing sample (file upload) | **Custom Prerequisite** | No LFX standard for this. Add as a Custom Prerequisite with name "Writing Sample" and "requires file upload" flag set. |
| Other / describe below | Custom Prerequisite | Manually configured. |
**Form treatment:** structured checkboxes (one per common pattern) plus an
"Other / describe below" textarea escape hatch for items that don't fit the
common patterns. The textarea is also treated as describing required items.
**Export treatment:** the export schema (§4.5) should set the corresponding
LFX standard prerequisite toggles where they exist, and emit a Custom
Prerequisite block where they don't. All entries are implicitly required.
**LFX-side ask:** confirm that the bulk-import schema can set application
questions per program (it almost certainly can, since the admin UI does — but
worth verifying before we design the form fields to match). See "Asks of LFX"
ask #2.
### 4.8 Human-readable view
Today's `project_ideas.md` is **replaced** by the auto-generated `README.md`
from §4.5. There is no separate "proposed ideas" file in the new flow — the
issue form is the proposal, and the exported README is the accepted-programs
list.
- Source of truth is the issues; the markdown file is a convenience index.
- `README.md` is generated by the export workflow (§4.5), not hand-edited.
- The Description field in the README covers both what the program is about
and expected outcomes (merged into a single "Program Description" in the
issue form, matching the LFX platform's single description field).
## 5. Asks of LFX (in priority order)
These determine how much of the above we can actually automate.
1. **LFID/email lookup endpoint.**
`GET https://api.../v1/users?email=...` returning `{ lfid, verified, accepted_tos }` or 404.
*Why first:* this single endpoint eliminates the most common day-of-launch failure (mentor email mismatch) and is the most user-visible win.
2. **Documented bulk-import schema** for programs.
CSV or JSON, with field names, character/byte limits (see §4.1.1 for our
reverse-engineered field map and pain points like the 100-char Program Name
and 20-char Custom Prerequisite Name), required/optional, enum values for
term / standard prerequisites. Must include support for **per-program
prerequisites** (§4.6a, both standard-toggles and custom slots) and
confirmation that the standard prerequisites (Resume, Cover Letter, etc.)
can be enabled via the import. Even without an API, this lets us generate
exactly the file you need.
3. **Project-create / program-create API.**
`POST /programs` accepting the schema from (2). Lets us skip the manual upload entirely.
4. **Test/staging environment** for the above. The validation workflow shouldn't hit prod LFX on every issue edit.
5. **Webhooks for state changes** (`program.approved`, `mentor.added`, `applications.opened`, `applications.closed`). Lets us drop the manual Status flips on the project board.
6. **LF Education integration for prerequisite verification.**
The LFC102 ("Inclusive Open Source Community Orientation") certificate is
currently uploaded as a PDF by mentees and verified by hand. If LFX can
query LF Education / openprofile.dev for course completion status keyed on
LFID, we can mark the prerequisite complete automatically. This likely
requires LFX-team coordination with LF Training & Certification rather than
something they own end-to-end. See §4.6 for context.
7. **Field-level confirmations from the LFX wizard.** Specifically:
- Confirm: are "Technologies" (Program Details) and "Required and/or desirable skills and training" (Program Setup) two genuinely independent fields on the back end, or one field rendered twice? Our form treats them as independent with a "same as Technologies" shortcut (§4.1.4); if they're actually the same field on the LFX side, we can simplify.
- Can the Custom Prerequisite Name limit (20 chars, currently breaking on "Inclusive Community" at 19/20) and Description limit (500 chars) be raised?
- Can the Program Name limit (100 chars) be raised, given common naming conventions consume ~16 chars on the term suffix alone?
### Operational asks (not strictly LFX)
- Service token / API key, with documented rate limits and rotation policy.
- Confirmation that it's OK to comment on a public issue with the result of "email X is/is-not registered on LFX" — or whether we should keep that to a private signal.
## 6. Open questions
- **Approval expression mechanism.** §4.3.2 leans toward 👍 reactions or
`/approve` comments from candidate approvers. Other valid options: require
a comment containing the literal word "approve" (looser, more false
positives), or open a parallel PR for each issue and use PR review-approve
(heavier). 👍/`/approve` feels like the right balance, but worth socializing.
- **Quota enforcement vs. warning.** §4.3.1 treats over-quota as a warning,
not a hard block. If project admins want a hard block, the workflow can
refuse to advance the issue past `Inbox` until the count is back in range.
- **ContribEx-equivalent registry maintenance.** The
`programs/lfx-mentorship/approvers.yml` overrides file (§4.3.2) needs to
be kept current. Recommend treating it like CODEOWNERS — a small admin
task, edited via PR, reviewed by program admin and the affected project's
liaison.
- **Multi-term programs.** LFX models a single program as hosting multiple
terms (you `Add Term` to an existing program rather than creating a new
program each term — see §4.1.1). CNCF currently treats each term as a fresh
unit (per-term `project_ideas.md`, separate project board per term). Worth
deciding whether to align with LFX's model — would meaningfully reduce
per-term setup effort but changes the lifecycle picture.
- **Program-specific extra prerequisites.** Some projects may want to attach
their own prerequisite tasks beyond the term-wide Inclusive Community
requirement (e.g. "complete project X's onboarding tutorial"). Recommend
*not* adding this to the v1 issue form — keep the form tight, handle extras
as a follow-up issue comment that the admin configures manually in LFX. If
this becomes common, add an optional textarea field in v2.
- **Application-requirement enumeration.** v1 form covers writing sample /
project proposal / code sample, all treated as required. If maintainers
routinely ask for things outside that set (e.g. specific assessments,
language proficiency proofs), promote them from the "Other" textarea to
first-class checkboxes in v2. Worth reviewing after one term.
- **File-upload destination.** LFX's application form handles applicant file
uploads natively. Confirm with LFX that uploaded writing samples / proposals
remain accessible to mentors *and* to the program admin during selection
(and what the retention policy is post-selection).
- **One issue per program vs. one issue per maintainer batch.** Recommend per-program for lifecycle clarity, but maintainers proposing many programs may push back. Mitigate with a `gh`-based CLI helper or a "duplicate this issue" link in the bot comment.
- **Migration of in-flight terms.** Don't migrate 2026 Term 2 mid-flight; cut over for 2026 Term 3 or 2027 Term 1.
- **GSoC reuse.** GSoC has its own template (`PROJECT_IDEA_TEMPLATE.md`) with subtly different fields (e.g. requires ≥2 mentors). Worth deciding whether GSoC gets its own form, or one form with a "program type" dropdown.
- **Notification fatigue.** Every issue edit re-runs validation and may re-comment. Mitigate by upserting the comment (one comment per issue, edited in place) and only re-validating on edits to the relevant fields.
- **Spam / abuse.** Public issue forms invite drive-by submissions. The required-approval gates (§4.3.2) act as the real spam filter — no proposal moves past `Awaiting Maintainer/Contribex Approval` without a 👍 / `/approve` from a known project maintainer or fallback approver. Admin triage on `Inbox` catches the rest. May want CODEOWNERS-style auto-assign of admin reviewers.
- **How `/lfx-url` materializes committed files (resolved).** Via an
accumulating per-term pull request, not direct commits: `main` is
branch-protected and the repo's automation is already PR-based. `/lfx-url`
records the URL and advances the board immediately; the file change lands when
the term's PR merges (minor, acceptable skew). Alternatives rejected: direct
commit to `main` (branch protection, no review), and deferring to the next
export run (leaves files stale, misses the goal of an automatic update).
7. Risks & mitigations
| Risk | Mitigation |
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| LFX team can't / won't expose APIs | All format checks still work; admin still has to manually verify mentor LFIDs (status quo, but with structured input — still a win) |
| Maintainers prefer the PR flow ("muscle memory") | Keep PRs working as fallback for one term; deprecate after one full cycle |
| Bot comments get noisy on edited issues | Upsert single comment; debounce on field-level diff |
| Project-board automation breaks if PAT expires | Use GitHub App; alert on workflow failure |
| Third-party action compromised | Inline the parser, or pin to full SHA |
| Landscape regeneration commits churn | Diff before commit; only update on real changes |
| Quota config drift (`programs/lfx-mentorship/quotas.yml`, §4.3.1) | Treat like a config doc; review at term-rollover; default applies if file missing |
| Approver registry drift (`programs/lfx-mentorship/approvers.yml`, §4.3.2; `admins.yml`, §4.3.4) | CODEOWNERS-style PR review; fall back to project-maintainers.csv if file missing or project not listed |
| Maintainer CSV format changes upstream | Pin to a known-good revision; test on `dependabot`-style update |
## 8. Implementation status
**Phase 1 (issue-form intake through export) is merged into `cncf/mentoring` and
running.** Form submission, validation, `/approve` (maintainer), `/confirm` (every
listed mentor), `/cncf-approve` (admin), project-board sync, and a single-PR export
(JSON artifact, generated README index, tracking CSV) run the full lifecycle. The
shared logic lives in a CI-tested lib (`node --test`).
- [x] **Issue form** (`lfx-program-proposal.yml`), §4.1
- [x] **CNCF Project dropdown sync** (`landscape-projects-sync.yml`), §4.2; includes `.project` detection; ported from inline Python to JS (#1903)
- [x] **Validation workflow** (`lfx-proposal-validate.yml`), §4.3
- [x] **Approvals workflow** (`lfx-proposal-approvals.yml`), §4.3.2 to 4.3.4 (`/approve`, `/confirm`, `/cncf-approve`; four-tier auth)
- [x] **Board sync workflow** (`lfx-proposal-board-sync.yml`), §4.4
- [x] **Export workflow** (`lfx-export.yml`), §4.5 (JSON, README, tracking CSV)
- [x] **Term dropdown**, single source of truth in `terms.yml`
- [x] **Config files**: `approvers.yml`, `projects.yml`, `quotas.yml`, `terms.yml`, `board.json`, labels
- [x] **Standing project board**, resolved per repository via `board.json` so dev and prod run identical code
- [x] **CI-tested automation lib** (`programs/lfx-mentorship/automation/lib`, #1897)
- [x] **LFC102 standard prerequisite** (#1905), §4.6; form note plus export injection from a single constant
- [x] **End-to-end test on the fork**: a full proposal walked through every gate
- [ ] **LFX URL command** (`/lfx-url`), §4.3.5 — record the URL on the issue, fill it into the term's export files via a per-term PR, and advance the board `Exported → Posted to LFX`
- [ ] **Phase 2, automate the LFX upload**: push the export to the LFX platform instead of re-keying it by hand (§5 asks #2 and #3)
Changes from the spec above, now that it is built: one standing board rather than a
per-term board; every listed mentor must `/confirm`; and the legacy `project_ideas.md`
pull-request flow is retired in favor of the form.
## 9. Recommended next steps
- [x] Cutover term chosen: **2026 Term 3 (Sep-Nov)**, the first term on the new system.
- [x] Project-board token strategy resolved: classic PAT (dev) and fine-grained PAT (prod), resolved per repository via `board.json`. A GitHub App remains the long-term goal.
- [ ] Coordinate with the LFX Mentorship platform team on the §5 asks, especially #1 (LFID/email lookup), #2 (bulk-import schema), #3 (program-create API), and #6 (LFC102 verification).
- [ ] Build Phase 2: automate the upload to LFX, developed on the fork against the LFX dev environment first.
## 10. How to give feedback
- 👍 / 👎 reactions on this issue if you broadly support / oppose the direction.
- **Comment** with your role (maintainer / mentor / LFX team / admin) and any specific concerns. If your feedback targets one section, please reference the section number (e.g. "re §4.7 application requirements: …").
- **For LFX platform team:** I'd love a steer on §5 asks — even an early "this one's feasible / this one isn't" is hugely valuable for prioritizing the build.
- **For maintainers / mentors:** thoughts on whether the form fields in §4.1 capture what you'd actually want to express, and whether the application-requirement defaults in §4.7 match the patterns you use.
Contributor guide
Assessment
This issue has not been assessed yet.