Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const repoRoot = path.resolve(__dirname, '../../../');

function read(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}

test('challenge issue templates inventory remains complete', () => {
const templateDir = path.join(repoRoot, 'learning-room/.github/ISSUE_TEMPLATE');
const files = fs.readdirSync(templateDir).filter(name => name.endsWith('.yml'));

const core = files.filter(name => /^challenge-\d{2}-.+\.yml$/.test(name));
const bonus = files.filter(name => /^bonus-[a-e]-.+\.yml$/.test(name));

assert.equal(core.length, 16, 'Expected 16 core challenge templates');
assert.equal(bonus.length, 5, 'Expected 5 bonus challenge templates');

const expectedCore = Array.from({ length: 16 }, (_, i) => String(i + 1).padStart(2, '0'));
const actualCore = core.map(name => name.match(/^challenge-(\d{2})-/)[1]).sort();
assert.deepEqual(actualCore, expectedCore, 'Core challenge numbering should be 01..16');

const actualBonus = bonus.map(name => name.match(/^bonus-([a-e])-/)[1]).sort();
assert.deepEqual(actualBonus, ['a', 'b', 'c', 'd', 'e'], 'Bonus challenge lettering should be a..e');
});

test('runbook challenge tracking table includes all 1-16 plus bonus A-E rows', () => {
const runbook = read('admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md');

for (let i = 1; i <= 16; i += 1) {
assert.match(
runbook,
new RegExp(`\\|\\s*${i}\\s*\\|`),
`Runbook tracker missing Challenge ${i} row`
);
}

['A', 'B', 'C', 'D', 'E'].forEach(letter => {
assert.match(
runbook,
new RegExp(`\\|\\s*Bonus\\s+${letter}\\s*\\|`, 'i'),
`Runbook tracker missing Bonus ${letter} row`
);
});
});

test('runbook contains required reliability variation categories', () => {
const runbook = read('admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md');

const categories = [
'Happy path completion',
'Common student error path',
'Recovery path after feedback',
'Automation latency/idempotency behavior',
];

categories.forEach(category => {
assert.match(
runbook,
new RegExp(category.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
`Runbook missing required variation category: ${category}`
);
});

const requiredCrossCutting = [
'Bot comment delay greater than expected window',
'Workflow rerun without creating duplicate progression issues',
'Student posts evidence in wrong place then corrects it',
'Autograder false-negative suspicion escalated and resolved',
'Permission/access prompt encountered and resolved',
];

requiredCrossCutting.forEach(item => {
assert.match(
runbook,
new RegExp(item.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
`Runbook missing cross-cutting failure mode: ${item}`
);
});
});
66 changes: 66 additions & 0 deletions .github/scripts/__tests__/qa-readiness-gates.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const repoRoot = path.resolve(__dirname, '../../../');

function read(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}

test('E2E runbook includes hard no-go readiness gates', () => {
const content = read('admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md');

const requiredSnippets = [
'Critical Precondition Gates (No-Go if any fail)',
'Issue form template `workshop-registration.yml` exists',
'Required labels exist: `registration`, `duplicate`, `waitlist`',
'Learning Room Template Deployment Gate',
'Prove template freshness',
'Challenge Reliability and Failure-Mode Coverage (Required)',
'Student-visible expected state map (required cross-check)',
'Student recovery and reset playbook (required)',
'Cross-cutting failure modes (must be tested at least once)',
'Challenge reliability/failure-mode coverage complete:',
];

requiredSnippets.forEach(snippet => {
assert.match(
content,
new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
`Runbook missing required gate text: ${snippet}`
);
});
});

test('Go-live release checklist contains required non-podcast readiness gates', () => {
const content = read('GO-LIVE-QA-GUIDE.md');

const requiredChecklistItems = [
'Registration issue form template and labels are configured',
'Learning Room source has been synced to `Community-Access/learning-room-template`',
'Template smoke validation from `Community-Access/learning-room-template` succeeded',
'Template freshness proof confirms smoke repo content matches latest merged template sync changes',
'Challenge tracking log includes explicit status and evidence for Challenges 1-16 and Bonus A-E',
'Challenge reliability matrix includes happy path, failure path, and recovery evidence for each challenge family',
'All in-scope automation workflows and facilitator scripts were validated with expected behavior and evidence',
];

requiredChecklistItems.forEach(item => {
assert.match(
content,
new RegExp(item.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
`Go-live checklist missing required item: ${item}`
);
});
});

test('recovery restore script exists and is discoverable in scripts/classroom', () => {
const scriptPath = path.join(repoRoot, 'scripts/classroom/Restore-LearningRoomFiles.ps1');
assert.equal(fs.existsSync(scriptPath), true, 'Missing recovery restore script in scripts/classroom');

const content = fs.readFileSync(scriptPath, 'utf8');
assert.match(content, /Profile\s*=\s*'core-day1'/i, 'Recovery script should provide default restore profile');
assert.match(content, /OpenPullRequest/i, 'Recovery script should support opening a recovery pull request');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const repoRoot = path.resolve(__dirname, '../../../');

function read(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}

test('registration workflow contains required label and duplicate/waitlist flows', () => {
const workflow = read('.github/workflows/registration.yml');

const requiredSnippets = [
"contains(github.event.issue.title, '[REGISTER]')",
"labels: ['duplicate']",
"labels: ['waitlist']",
"labels: ['registration']",
'CLASSROOM_ORG_ADMIN_TOKEN',
'CLASSROOM_DAY1_ASSIGNMENT_URL',
'CLASSROOM_DAY2_ASSIGNMENT_URL',
'createInvitation',
'Upload CSV as artifact',
'Sync Student Roster (No PII)',
];

requiredSnippets.forEach(snippet => {
assert.match(
workflow,
new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
`Registration workflow missing expected behavior marker: ${snippet}`
);
});
});

test('registration issue form template exists', () => {
const templatePath = path.join(repoRoot, '.github/ISSUE_TEMPLATE/workshop-registration.yml');
assert.equal(fs.existsSync(templatePath), true, 'Missing workshop-registration.yml issue form template');

const content = fs.readFileSync(templatePath, 'utf8');
assert.match(content, /name:\s*"?Workshop Registration/i, 'Registration template should have user-facing name');
assert.match(content, /body:/i, 'Registration template should define issue form body');
});
25 changes: 25 additions & 0 deletions GO-LIVE-QA-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Use this guide before a cohort is opened to learners. It is the release gate for curriculum content, GitHub Classroom deployment, Learning Room automation, podcast materials, accessibility, and human test coverage.

For end-to-end execution details, use [admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md](admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md) as the operator procedure. This guide is the release gate summary; the runbook is the required execution playbook.

The goal is simple: a facilitator should be able to create a classroom, seed test repositories, complete every challenge path, validate every generated artifact, and know exactly what remains before students arrive.

## Release Decision
Expand All @@ -13,6 +15,12 @@ Do not mark a cohort ready until all required items in this section are complete
- [ ] Podcast catalog validation passes.
- [ ] RSS feed validation passes for the current audio state.
- [ ] Git diff whitespace check has no actual whitespace or conflict-marker errors.
- [ ] Registration deployment gate completed (issue form template, workflow enablement, required labels, and optional classroom automation settings).
- [ ] Registration issue form template and labels are configured (`workshop-registration.yml`, `registration`, `duplicate`, `waitlist`).
- [ ] Learning Room source has been synced to `Community-Access/learning-room-template` and merged to `main` (or validated as no-change).
- [ ] Template smoke validation from `Community-Access/learning-room-template` succeeded before assignment publishing.
- [ ] Template freshness proof confirms smoke repo content matches latest merged template sync changes.
- [ ] Smoke repo confirms all required workflow files are present (PR validation, content validation, progression, skills progression, and all autograders).
- [ ] Day 1 Classroom assignment has been created from the current Learning Room template.
- [ ] Day 2 Classroom assignment has been created from the current Learning Room template.
- [ ] A test student account accepted the Day 1 invite and received a private repository.
Expand All @@ -24,8 +32,22 @@ Do not mark a cohort ready until all required items in this section are complete
- [ ] Autograding runs and reports results in GitHub Classroom.
- [ ] Peer simulation artifacts can be seeded and used for review practice.
- [ ] Human testers completed the Day 1, Day 2, bonus, accessibility, and content-review passes below.
- [ ] Challenge tracking log includes explicit status and evidence for Challenges 1-16 and Bonus A-E.
- [ ] Challenge reliability matrix includes happy path, failure path, and recovery evidence for each challenge family.
- [ ] Runbook Phase 8 required checklist is complete in [admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md](admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md).
- [ ] Student recovery Level 2 restore test is completed and evidenced with branch and PR links.
- [ ] All in-scope automation workflows and facilitator scripts were validated with expected behavior and evidence.
- [ ] Local non-podcast readiness evidence is recorded in [admin/qa-readiness](admin/qa-readiness/README.md).
- [ ] All blocking findings have a fix, owner, or written release exception.

No-go conditions:

- Any Blocker finding remains open.
- Any required runbook Phase 8 gate is incomplete without explicit release-owner exception.
- Student progression, PR validation, or required autograder behavior is not reproducible in a test student repository.
- Template freshness proof is missing or shows drift from the latest merged template sync.
- Required QA evidence links are missing for release-signoff claims.

## Source Of Truth

The following table lists each release artifact and the document that controls it.
Expand All @@ -34,6 +56,7 @@ The following table lists each release artifact and the document that controls i
|---|---|
| Classroom deployment | [classroom/README.md](classroom/README.md) |
| Classroom copy-paste setup pack | [admin/classroom/README.md](admin/classroom/README.md) |
| End-to-end operator runbook (registration to completion, podcast excluded) | [admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md](admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md) |
| Human challenge walkthrough | [classroom/HUMAN_TEST_MATRIX.md](classroom/HUMAN_TEST_MATRIX.md) |
| Facilitator operations | [admin/FACILITATOR_OPERATIONS.md](admin/FACILITATOR_OPERATIONS.md) |
| Facilitator guide | [admin/FACILITATOR_GUIDE.md](admin/FACILITATOR_GUIDE.md) |
Expand Down Expand Up @@ -75,6 +98,8 @@ Expected results:

Record the command output summary in the release notes or QA issue.

Required evidence destination for local readiness: [admin/qa-readiness/UNIT-TEST-RESULTS-2026-05-08.md](admin/qa-readiness/UNIT-TEST-RESULTS-2026-05-08.md) or an equivalent dated report in the same folder.

## Phase 2: Content Inventory Review

Every content file must be reviewed before go-live. Use this checklist to assign coverage.
Expand Down
Loading
Loading