-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathSPEC.html
More file actions
1138 lines (1116 loc) · 62.5 KB
/
Copy pathSPEC.html
File metadata and controls
1138 lines (1116 loc) · 62.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="GIT Going with GitHub - A two-day accessible open source workshop by Community Access">
<title>SPEC.md - GIT Going with GitHub</title>
<link rel="stylesheet" href="./styles/github-markdown.css">
<link rel="stylesheet" href="./styles/highlight.css">
<link rel="stylesheet" href="./styles/custom.css">
<style>
.markdown-body {
box-sizing: border-box;
min-width: 0;
max-width: 980px;
margin: 0;
padding: 45px;
}
.page-layout {
max-width: 1320px;
margin: 0 auto;
padding: 1rem 1rem 2rem;
display: grid;
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
gap: 1.25rem;
align-items: start;
}
@media (max-width: 767px) {
.page-layout {
grid-template-columns: 1fr;
padding: 0.5rem 0.5rem 1rem;
}
.markdown-body {
padding: 15px;
}
}
</style>
</head>
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<header class="site-header" role="banner">
<div class="site-header-inner">
<nav aria-label="Breadcrumb" class="breadcrumb">
<a href="./index.html">Home</a> <span aria-hidden="true">›</span> <span aria-current="page">SPEC.md</span>
</nav>
<div class="header-actions">
<a href="https://github.com/Community-Access/git-going-with-github/wiki" class="wiki-link" target="_blank" rel="noopener noreferrer">
<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.06l-2.573 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.457 1.457 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 1 1 1.06-1.06l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h.25a.25.25 0 0 0 .25-.25v-5.5Z"/>
</svg>
Wiki
</a>
<form role="search" class="search-form" action="./search.html" method="get">
<label for="site-search" class="search-label">Search docs</label>
<input
type="search"
id="site-search"
class="search-input"
name="q"
placeholder="Search docs…"
autocomplete="off"
aria-label="Search documentation"
>
<button type="submit" class="search-button" aria-label="Submit search">
<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"/>
</svg>
</button>
</form>
</div>
</div>
</header>
<div class="page-layout">
<aside class="guided-sidebar" aria-label="Guided reference">
<h2 class="guided-title">Guided Reference</h2>
<p class="guided-intro">Follow the learning order, then jump into challenge issue views, appendices, and support resources.</p>
<section class="guided-resume" aria-label="Resume learning">
<a id="resume-link" class="guided-resume-link" href="./index.html" hidden>Resume where you left off</a>
</section>
<nav class="guided-nav" aria-label="Workshop navigation">
<section class="guided-section">
<h3>Start Here</h3>
<ul>
<li><a href="./index.html">Workshop Home</a></li>
<li><a href="./docs/course-guide.html">Course Guide</a></li>
<li><a href="./docs/get-going.html">Get Going with GitHub</a></li>
<li><a href="./docs/CHALLENGES.html">Challenge Hub</a></li>
</ul>
</section>
<section class="guided-section">
<h3>Learning Order</h3>
<details>
<summary>Day 1 chapters</summary>
<ul>
<li><a href="./docs/00-pre-workshop-setup.html">Pre-Workshop Setup - GIT Going with GitHub</a></li>
<li><a href="./docs/01-choose-your-tools.html">Choose Your Adventure: A Tool Tour</a></li>
<li><a href="./docs/02-understanding-github.html">Understanding GitHub's Web Structure</a></li>
<li><a href="./docs/03-navigating-repositories.html">Navigating Repositories</a></li>
<li><a href="./docs/04-the-learning-room.html">The Learning Room: Your Personal Practice Repository</a></li>
<li><a href="./docs/05-working-with-issues.html">Working with Issues</a></li>
<li><a href="./docs/06-working-with-pull-requests.html">Working with Pull Requests</a></li>
<li><a href="./docs/07-merge-conflicts.html">Merge Conflicts</a></li>
<li><a href="./docs/08-open-source-culture.html">Culture, Etiquette, and Community Standards</a></li>
<li><a href="./docs/09-labels-milestones-projects.html">Labels, Milestones, and Projects</a></li>
<li><a href="./docs/10-notifications-and-day-1-close.html">Notifications</a></li>
</ul>
</details>
<details>
<summary>Day 2 chapters</summary>
<ul>
<li><a href="./docs/11-vscode-interface.html">VS Code: Interface and Setup</a></li>
<li><a href="./docs/12-vscode-accessibility.html">VS Code: Accessibility Deep Dive</a></li>
<li><a href="./docs/13-how-git-works.html">How Git Works: The Mental Model</a></li>
<li><a href="./docs/14-git-in-practice.html">Git & Source Control in VS Code</a></li>
<li><a href="./docs/15-code-review.html">Code Review: PRs, Diffs, and Constructive Feedback</a></li>
<li><a href="./docs/16-github-copilot.html">GitHub Copilot</a></li>
<li><a href="./docs/17-issue-templates.html">Issue Templates</a></li>
<li><a href="./docs/18-fork-and-contribute.html">Fork and Contribute: The Open Source Workflow</a></li>
<li><a href="./docs/19-accessibility-agents.html">Accessibility Agents</a></li>
<li><a href="./docs/20-build-your-agent.html">Capstone Project: Contribute an Impactful Agent</a></li>
<li><a href="./docs/21-github-accessibility-and-open-source.html">GitHub Accessibility and Open Source at Scale</a></li>
<li><a href="./docs/22-what-comes-next.html">What Comes Next: Your Developer Journey</a></li>
</ul>
</details>
</section>
<section class="guided-section">
<h3>Appendices</h3>
<details>
<summary>Reference library</summary>
<ul>
<li><a href="./docs/appendix-a-glossary.html">Appendix A: GitHub Concepts & Glossary</a></li>
<li><a href="./docs/appendix-b-screen-reader-cheatsheet.html">Appendix B: Screen Reader Cheat Sheet</a></li>
<li><a href="./docs/appendix-c-markdown-reference.html">Appendix C: Markdown and GitHub Flavored Markdown - Complete Guide</a></li>
<li><a href="./docs/appendix-d-git-authentication.html">Appendix D: Git Authentication</a></li>
<li><a href="./docs/appendix-e-advanced-git.html">Appendix E: Advanced Git Operations</a></li>
<li><a href="./docs/appendix-f-git-security.html">Appendix F: Git Security for Contributors</a></li>
<li><a href="./docs/appendix-g-vscode-reference.html">Appendix G: VS Code Accessibility Reference</a></li>
<li><a href="./docs/appendix-h-github-desktop.html">Appendix H: GitHub Desktop</a></li>
<li><a href="./docs/appendix-i-github-cli.html">Appendix I: GitHub CLI Reference</a></li>
<li><a href="./docs/appendix-j-cloud-editors.html">Appendix J: GitHub Codespaces</a></li>
<li><a href="./docs/appendix-k-copilot-reference.html">Appendix K: Copilot Reference</a></li>
<li><a href="./docs/appendix-l-agents-reference.html">Appendix L: Accessibility Agents Reference</a></li>
<li><a href="./docs/appendix-m-accessibility-standards.html">Appendix M: Accessibility Standards Reference</a></li>
<li><a href="./docs/appendix-n-advanced-search.html">Appendix N: GitHub Advanced Search</a></li>
<li><a href="./docs/appendix-o-branch-protection.html">Appendix O: Branch Protection Rules and Repository Rulesets</a></li>
<li><a href="./docs/appendix-p-security-features.html">Appendix P: GitHub Security Features</a></li>
<li><a href="./docs/appendix-q-actions-workflows.html">Appendix Q: GitHub Actions and Workflows</a></li>
<li><a href="./docs/appendix-r-projects-deep-dive.html">Appendix R: GitHub Projects Deep Dive</a></li>
<li><a href="./docs/appendix-s-releases-tags-insights.html">Appendix S: Releases, Tags, and Repository Insights</a></li>
<li><a href="./docs/appendix-t-community-and-social.html">Appendix T: Community and Social</a></li>
<li><a href="./docs/appendix-u-discussions-and-gists.html">Appendix U: Discussions and Gists</a></li>
<li><a href="./docs/appendix-v-github-mobile.html">Appendix V: GitHub Mobile</a></li>
<li><a href="./docs/appendix-w-github-pages.html">Appendix W: Publishing with GitHub Pages</a></li>
<li><a href="./docs/appendix-x-resources.html">Appendix X: Resources</a></li>
<li><a href="./docs/appendix-y-workshop-materials.html">Appendix Y: Accessing and Downloading Workshop Materials</a></li>
<li><a href="./docs/appendix-z-github-skills.html">Appendix Z: GitHub Skills - Complete Course Catalog</a></li>
<li><a href="./docs/appendix-aa-agent-installation-setup.html">Appendix AA: Agent Installation & Setup Across All Platforms</a></li>
<li><a href="./docs/appendix-ab-advanced-agent-patterns.html">Appendix AB: Advanced Agent Patterns & Skills</a></li>
<li><a href="./docs/appendix-ac-document-developer-tools.html">Appendix AC: Document & Developer Tool Agents</a></li>
</ul>
</details>
</section>
<section class="guided-section">
<h3>Challenge Lab</h3>
<ul>
<li><a href="./docs/CHALLENGES.html">Challenge Hub</a></li>
<li><a href="./work.html">Issue-Style Challenge Walkthrough</a></li>
<li><a href="./docs/17-issue-templates.html">Issue Template Deep Dive</a></li>
</ul>
</section>
<section class="guided-section">
<h3>External Resources</h3>
<ul>
<li><a href="https://lp.csedesigns.com/ggg/PODCASTS.html">Podcast Episodes <span class="guided-external">(external)</span></a></li>
<li><a href="https://lp.csedesigns.com/ggg/feed.xml">RSS Feed <span class="guided-external">(external)</span></a></li>
</ul>
</section>
<section class="guided-section">
<h3>Get Help</h3>
<ul>
<li><a href="https://github.com/Community-Access/support">Support Hub <span class="guided-external">(external)</span></a></li>
</ul>
</section>
</nav>
</aside>
<main id="main-content" class="markdown-body page-main">
<nav class="quick-jumps" aria-label="Quick jumps">
<h2 class="quick-jumps-title">Quick Jumps</h2>
<ul class="quick-jumps-list">
<li><a href="#course-content">Jump to course content</a></li><li><a href="#main-content">Jump to main content top</a></li><li><a href="#on-this-page">Jump to on-page sections</a></li><li><a href="./docs/CHALLENGES.html">Open challenge hub</a></li><li><a href="./work.html">Open issue-style challenge walkthrough</a></li>
</ul>
</nav>
<nav id="on-this-page" class="onpage-nav" aria-label="On this page">
<h2 class="onpage-title">On This Page</h2>
<ul class="onpage-list">
<li class="onpage-item"><a href="#table-of-contents">Table of contents</a></li>
<li class="onpage-item"><a href="#1-overview">1. Overview</a></li>
<li class="onpage-item"><a href="#2-goals-and-non-goals">2. Goals and non-goals</a></li>
<li class="onpage-item onpage-sub"><a href="#goals">Goals</a></li>
<li class="onpage-item onpage-sub"><a href="#non-goals">Non-goals</a></li>
<li class="onpage-item"><a href="#3-personas-and-primary-journeys">3. Personas and primary journeys</a></li>
<li class="onpage-item onpage-sub"><a href="#personas">Personas</a></li>
<li class="onpage-item onpage-sub"><a href="#primary-learner-journey">Primary learner journey</a></li>
<li class="onpage-item onpage-sub"><a href="#primary-facilitator-journey">Primary facilitator journey</a></li>
<li class="onpage-item"><a href="#4-system-architecture">4. System architecture</a></li>
<li class="onpage-item"><a href="#5-component-catalog">5. Component catalog</a></li>
<li class="onpage-item onpage-sub"><a href="#the-three-learning-room-automation-systems">The three Learning Room automation systems</a></li>
<li class="onpage-item"><a href="#6-data-model-and-state-of-record">6. Data model and state of record</a></li>
<li class="onpage-item onpage-sub"><a href="#61-roster-of-record">6.1 Roster of record</a></li>
<li class="onpage-item onpage-sub"><a href="#62-progress-of-record">6.2 Progress of record</a></li>
<li class="onpage-item onpage-sub"><a href="#63-provisioning-of-record">6.3 Provisioning of record</a></li>
<li class="onpage-item"><a href="#7-provisioning-subsystem-the-classroom-replacement">7. Provisioning subsystem (the Classroom replacement)</a></li>
<li class="onpage-item onpage-sub"><a href="#71-responsibilities">7.1 Responsibilities</a></li>
<li class="onpage-item onpage-sub"><a href="#72-implementation-options-and-decision">7.2 Implementation options and decision</a></li>
<li class="onpage-item onpage-sub"><a href="#72a-github-app-permission-set">7.2a GitHub App permission set</a></li>
<li class="onpage-item onpage-sub"><a href="#72b-provisioning-algorithm-idempotent-serial">7.2b Provisioning algorithm (idempotent, serial)</a></li>
<li class="onpage-item onpage-sub"><a href="#73-provisioning-contract">7.3 Provisioning contract</a></li>
<li class="onpage-item onpage-sub"><a href="#74-rate-and-scale-considerations">7.4 Rate and scale considerations</a></li>
<li class="onpage-item"><a href="#8-automation-contracts">8. Automation contracts</a></li>
<li class="onpage-item"><a href="#9-curriculum-subsystem">9. Curriculum subsystem</a></li>
<li class="onpage-item"><a href="#10-content-pipeline-docs-html-epub-audio">10. Content pipeline: docs, HTML, EPUB, audio</a></li>
<li class="onpage-item"><a href="#11-optional-flask-companion">11. Optional Flask companion</a></li>
<li class="onpage-item onpage-sub"><a href="#111-scope">11.1 Scope</a></li>
<li class="onpage-item onpage-sub"><a href="#112-hard-constraints">11.2 Hard constraints</a></li>
<li class="onpage-item onpage-sub"><a href="#113-interfaces">11.3 Interfaces</a></li>
<li class="onpage-item"><a href="#12-accessibility-requirements">12. Accessibility requirements</a></li>
<li class="onpage-item"><a href="#13-security-requirements">13. Security requirements</a></li>
<li class="onpage-item"><a href="#14-reliability-observability-and-recovery">14. Reliability, observability, and recovery</a></li>
<li class="onpage-item"><a href="#15-testing-and-quality-strategy">15. Testing and quality strategy</a></li>
<li class="onpage-item"><a href="#16-configuration-surface">16. Configuration surface</a></li>
<li class="onpage-item"><a href="#17-repository-layout">17. Repository layout</a></li>
<li class="onpage-item"><a href="#18-migration-plan-from-classroom-to-owned-provisioning">18. Migration plan: from Classroom to owned provisioning</a></li>
<li class="onpage-item"><a href="#19-acceptance-criteria">19. Acceptance criteria</a></li>
<li class="onpage-item"><a href="#20-open-questions">20. Open questions</a></li>
<li class="onpage-item"><a href="#implementation-status">Implementation status</a></li>
<li class="onpage-item"><a href="#authoritative-sources">Authoritative Sources</a></li>
<li class="onpage-item onpage-sub"><a href="#section-level-source-map">Section-Level Source Map</a></li>
</ul>
</nav>
<div id="course-content"><h1 id="specmd">SPEC.md</h1>
<p>Complete project specification for GIT Going with GitHub (GLOW). This is the technical source of truth: what the system is, what every component does, how data and state flow, the interfaces between parts, and the requirements every piece must meet. It implements the vision in <a href="golden.html">golden.md</a>. Where this spec and reality disagree, fix one of them and note it here.</p>
<h2 id="table-of-contents">Table of contents</h2>
<ul>
<li><a href="#1-overview">1. Overview</a></li>
<li><a href="#2-goals-and-non-goals">2. Goals and non-goals</a></li>
<li><a href="#3-personas-and-primary-journeys">3. Personas and primary journeys</a></li>
<li><a href="#4-system-architecture">4. System architecture</a></li>
<li><a href="#5-component-catalog">5. Component catalog</a></li>
<li><a href="#6-data-model-and-state-of-record">6. Data model and state of record</a></li>
<li><a href="#7-provisioning-subsystem-the-classroom-replacement">7. Provisioning subsystem (the Classroom replacement)</a></li>
<li><a href="#8-automation-contracts">8. Automation contracts</a></li>
<li><a href="#9-curriculum-subsystem">9. Curriculum subsystem</a></li>
<li><a href="#10-content-pipeline-docs-html-epub-audio">10. Content pipeline: docs, HTML, EPUB, audio</a></li>
<li><a href="#11-optional-flask-companion">11. Optional Flask companion</a></li>
<li><a href="#12-accessibility-requirements">12. Accessibility requirements</a></li>
<li><a href="#13-security-requirements">13. Security requirements</a></li>
<li><a href="#14-reliability-observability-and-recovery">14. Reliability, observability, and recovery</a></li>
<li><a href="#15-testing-and-quality-strategy">15. Testing and quality strategy</a></li>
<li><a href="#16-configuration-surface">16. Configuration surface</a></li>
<li><a href="#17-repository-layout">17. Repository layout</a></li>
<li><a href="#18-migration-plan-from-classroom-to-owned-provisioning">18. Migration plan: from Classroom to owned provisioning</a></li>
<li><a href="#19-acceptance-criteria">19. Acceptance criteria</a></li>
<li><a href="#20-open-questions">20. Open questions</a></li>
</ul>
<h2 id="1-overview">1. Overview</h2>
<p>GLOW is a two-day, accessibility-first workshop that teaches blind and low vision technologists to navigate and contribute to open source on GitHub using a screen reader and keyboard alone. It is delivered as:</p>
<ul>
<li>A <strong>curriculum</strong> of 22 chapters plus appendices in <a href="docs/">docs/</a>, published to HTML, EPUB, and an audio podcast series.</li>
<li>A <strong>Learning Room</strong>: a per-student private repository, created from a template, that drives 16 core challenges plus 5 bonus challenges through GitHub-native automation (a PR validation bot named Gandalf, a Student Progression Bot, and a suite of autograders).</li>
<li>A <strong>registration and cohort system</strong> that intakes learners, provisions their Learning Room, releases Day 2 content, and gives facilitators a live status dashboard.</li>
</ul>
<p>The current production system depends on GitHub Classroom for provisioning. This spec defines both the present system and the target system in which Classroom is replaced by owned, GitHub-native provisioning, per <a href="golden.html">golden.md</a>.</p>
<h2 id="2-goals-and-non-goals">2. Goals and non-goals</h2>
<h3 id="goals">Goals</h3>
<ul>
<li>A learner completes the full arc from first GitHub navigation to a real, review-ready open source contribution.</li>
<li>Every learner-facing surface is fully operable with NVDA, JAWS, and VoiceOver, by keyboard alone.</li>
<li>Provisioning, roster, and progress are owned and reconstructable, with no single vendor as a point of failure.</li>
<li>Facilitators can open a cohort, run it, and recover from any failure using documented runbooks.</li>
<li>The system degrades gracefully: every automated step has a manual fallback.</li>
</ul>
<h3 id="non-goals">Non-goals</h3>
<ul>
<li>Email delivery is not a dependency. All flows complete using the GitHub web notification inbox.</li>
<li>The system does not require learners to join an organization, hold a paid plan, or change Actions settings.</li>
<li>The system does not aim to replace VS Code, Copilot, or GitHub itself; it teaches their accessible use.</li>
<li>Real-time chat, grading-for-credit, and LMS integration are out of scope.</li>
</ul>
<h2 id="3-personas-and-primary-journeys">3. Personas and primary journeys</h2>
<h3 id="personas">Personas</h3>
<ul>
<li><strong>Learner.</strong> New-to-GitHub, uses assistive technology, may not code. Needs belonging, clarity, and a forgiving path.</li>
<li><strong>Facilitator.</strong> Runs a cohort, seeds challenges, monitors progress, recovers stuck learners.</li>
<li><strong>Maintainer.</strong> Owns the curriculum, automation, content pipeline, and this spec.</li>
</ul>
<h3 id="primary-learner-journey">Primary learner journey</h3>
<ol>
<li>Register through an accessible front door (Flask companion, GitHub Pages form, or issue form fallback).</li>
<li>Receive a provisioned private Learning Room repository.</li>
<li>Acknowledge readiness (<code>ack</code>), complete Day 1 challenges, signal <code>day1-complete</code>.</li>
<li>Receive Day 2 release, complete Day 2 challenges and the capstone.</li>
<li>Open or prepare a real upstream contribution; continue asynchronously with support hub access.</li>
</ol>
<h3 id="primary-facilitator-journey">Primary facilitator journey</h3>
<ol>
<li>Sync and validate the Learning Room template.</li>
<li>Open a cohort; provisioning creates student repositories.</li>
<li>Seed Challenge 1 (and Challenge 10 for Day 2) per student.</li>
<li>Monitor the dashboard; intervene on watchdog alerts.</li>
<li>Run teardown after the cohort.</li>
</ol>
<h2 id="4-system-architecture">4. System architecture</h2>
<p>The architecture separates a dependable GitHub-native core (the critical learner path) from an optional companion at the edges. The companion can vanish without breaking any learner.</p>
<pre><code class="hljs language-text"> ACCESSIBLE FRONT DOOR
(Flask companion OR GitHub Pages form OR issue form fallback)
|
v
REGISTRATION + ROSTER (owned)
private admin repo roster record <----+ companion mirror (optional)
|
v
PROVISIONING SUBSYSTEM (GitHub-native)
idempotent action: template ----> per-student private Learning Room repo
|
v
THE LEARNING ROOM (per student)
Gandalf (PR bot) | Student Progression Bot | Autograders | Challenge issues
|
v
PROGRESSION + DAY 2 RELEASE (deterministic text signals)
|
v
FACILITATOR DASHBOARD (admin issues + optional companion view)</code></pre>
<p>Architectural rules:</p>
<ul>
<li>The critical path (front door fallback, provisioning, Learning Room, progression, release) runs entirely on GitHub.</li>
<li>The companion only ever renders owned state more nicely; it never holds state the learner depends on.</li>
<li>Each arrow is a documented contract (Section 8) with a manual fallback.</li>
</ul>
<h2 id="5-component-catalog">5. Component catalog</h2>
<p>This table is the index of moving parts. Each component has an owner, a trigger, and a fallback.</p>
<table>
<thead>
<tr>
<th scope="col">Component</th>
<th scope="col">Location</th>
<th scope="col">Trigger</th>
<th scope="col">Responsibility</th>
<th scope="col">Manual fallback</th>
</tr>
</thead>
<tbody><tr>
<td>Registration workflow</td>
<td><a href=".github/workflows/registration.yml">.github/workflows/registration.yml</a></td>
<td>Registration issue opened</td>
<td>Validate, redact, label, store intake, post links</td>
<td>Facilitator labels and replies by hand</td>
</tr>
<tr>
<td>Day 2 release</td>
<td><a href=".github/workflows/day2-release.yml">.github/workflows/day2-release.yml</a></td>
<td>Schedule, dispatch</td>
<td>Post Day 2 link when <code>day1-complete</code> signal present</td>
<td>Facilitator posts Day 2 link</td>
</tr>
<tr>
<td>Dashboard sync</td>
<td><a href=".github/workflows/instructor-dashboard-sync.yml">.github/workflows/instructor-dashboard-sync.yml</a></td>
<td>Schedule, events</td>
<td>Upsert per-student status issue in admin repo</td>
<td>Facilitator reads enrollment issues</td>
</tr>
<tr>
<td>Student grouping</td>
<td><a href=".github/workflows/student-grouping.yml">.github/workflows/student-grouping.yml</a></td>
<td>Schedule, dispatch</td>
<td>Cohort grouping support</td>
<td>Manual roster grouping</td>
</tr>
<tr>
<td>Skills progression</td>
<td><a href=".github/workflows/skills-progression.yml">.github/workflows/skills-progression.yml</a></td>
<td>Issue events</td>
<td>Track skills progression</td>
<td>Manual tracking</td>
</tr>
<tr>
<td>Learning Room validation</td>
<td><a href=".github/workflows/learning-room-validation.yml">.github/workflows/learning-room-validation.yml</a></td>
<td>PR, dispatch</td>
<td>Validate template integrity</td>
<td>Manual template review</td>
</tr>
<tr>
<td>Authoritative sources validation</td>
<td><a href=".github/workflows/validate-authoritative-sources.yml">.github/workflows/validate-authoritative-sources.yml</a></td>
<td>PR</td>
<td>Enforce source maps in content</td>
<td>Run validator script locally</td>
</tr>
<tr>
<td>Gandalf (PR bot)</td>
<td><a href="learning-room/.github/workflows/pr-validation-bot.yml">learning-room/.github/workflows/pr-validation-bot.yml</a></td>
<td>PR events, comments</td>
<td>Welcome, validate PR, answer help</td>
<td>Facilitator comments</td>
</tr>
<tr>
<td>Student Progression Bot</td>
<td><a href="learning-room/.github/workflows/student-progression.yml">learning-room/.github/workflows/student-progression.yml</a></td>
<td>Issue closed, dispatch</td>
<td>Create next challenge issue</td>
<td>Dispatch with <code>start_challenge</code></td>
</tr>
<tr>
<td>Autograders (8)</td>
<td><a href="learning-room/.github/workflows/">learning-room/.github/workflows/autograder-*.yml</a></td>
<td>Issue, PR, push, schedule</td>
<td>Verify objective challenge evidence</td>
<td>Facilitator verifies manually</td>
</tr>
<tr>
<td>Content validation</td>
<td><a href="learning-room/.github/workflows/content-validation.yml">learning-room/.github/workflows/content-validation.yml</a></td>
<td>PR</td>
<td>Validate student content edits</td>
<td>Manual review</td>
</tr>
<tr>
<td>Provisioning action (target)</td>
<td>new, GitHub App or Actions bot</td>
<td>Roster entry created</td>
<td>Create and seed Learning Room repo idempotently</td>
<td>Classroom invite during transition</td>
</tr>
<tr>
<td>Content pipeline</td>
<td><a href=".github/workflows/">.github/workflows/</a> build-* and deploy-*</td>
<td>Push, dispatch</td>
<td>Build HTML, EPUB, diagrams, Pages</td>
<td>Run build scripts locally</td>
</tr>
<tr>
<td>Flask companion (optional)</td>
<td>new service</td>
<td>HTTP</td>
<td>Accessible front door and dashboard</td>
<td>GitHub Pages form and admin issues</td>
</tr>
</tbody></table>
<h3 id="the-three-learning-room-automation-systems">The three Learning Room automation systems</h3>
<ul>
<li><strong>Gandalf (PR Validation Bot).</strong> Welcomes first-time contributors, validates PR structure, responds to help requests, posts real-time feedback on every push. Idempotent comment updates keyed by a marker string so it never spams a thread.</li>
<li><strong>Student Progression Bot.</strong> On close of a <code>Challenge N</code> issue, creates the next challenge issue; on <code>workflow_dispatch</code> with <code>start_challenge</code>, seeds a specific challenge. Concurrency-guarded per issue. See <a href="learning-room/.github/scripts/challenge-progression.js">challenge-progression.js</a>.</li>
<li><strong>Autograders.</strong> Eight workflows verifying objective evidence: issue filed, branch commit, PR link, merge conflicts resolved, local commit, template validity, capstone, and a watchdog. Each posts a single, kind, idempotent pass or fail comment and a separate error notice on workflow failure so the learner is never blamed for infrastructure faults.</li>
</ul>
<h2 id="6-data-model-and-state-of-record">6. Data model and state of record</h2>
<p>The decoupling contract from <a href="golden.html">golden.md</a> requires three owned, reconstructable sources of truth. This section defines their schemas.</p>
<h3 id="61-roster-of-record">6.1 Roster of record</h3>
<p>Canonical store: one record per learner in the private admin repository (optionally mirrored by the companion). Logical schema:</p>
<table>
<thead>
<tr>
<th scope="col">Field</th>
<th scope="col">Type</th>
<th scope="col">Notes</th>
</tr>
</thead>
<tbody><tr>
<td><code>github_handle</code></td>
<td>string</td>
<td>Primary identity key</td>
</tr>
<tr>
<td><code>cohort_id</code></td>
<td>string</td>
<td>Cohort the learner belongs to</td>
</tr>
<tr>
<td><code>path</code></td>
<td>enum</td>
<td><code>day1-day2</code> or <code>day2-only</code></td>
</tr>
<tr>
<td><code>learning_room_repo</code></td>
<td>string</td>
<td>Owner/name of provisioned repo, null until provisioned</td>
</tr>
<tr>
<td><code>provision_state</code></td>
<td>enum</td>
<td><code>pending</code>, <code>provisioned</code>, <code>failed</code>, <code>healed</code></td>
</tr>
<tr>
<td><code>status</code></td>
<td>enum</td>
<td><code>awaiting-ack</code>, <code>active-day1</code>, <code>day1-complete</code>, <code>day2-released</code>, <code>needs-info</code></td>
</tr>
<tr>
<td><code>registered_at</code></td>
<td>timestamp</td>
<td>Intake time</td>
</tr>
<tr>
<td><code>last_signal_at</code></td>
<td>timestamp</td>
<td>Last deterministic signal observed</td>
</tr>
<tr>
<td><code>notes</code></td>
<td>string</td>
<td>Facilitator notes, redacted in any public surface</td>
</tr>
</tbody></table>
<p>Reconstruction rule: the roster can be rebuilt from registration issues plus provisioning results without any third party.</p>
<h3 id="62-progress-of-record">6.2 Progress of record</h3>
<p>Derived, never authored by a vendor. Progress is computed from signals the project controls:</p>
<ul>
<li>Challenge issue state (open or closed) and titles (<code>Challenge N</code>).</li>
<li>PR state and closing-keyword links (<code>Closes #N</code>).</li>
<li>Labels (<code>enrolled</code>, <code>day1-complete</code>, <code>day2-eligible</code>, <code>day2-released</code>).</li>
<li>Deterministic text signals in comments: <code>ack</code>, <code>day1-complete</code>.</li>
</ul>
<p>Reconstruction rule: re-derivable by replaying issue and PR events in the student repository.</p>
<h3 id="63-provisioning-of-record">6.3 Provisioning of record</h3>
<p>A log of provisioning attempts and outcomes per learner, sufficient to prove a repository is correctly seeded and to safely re-run. Fields: <code>github_handle</code>, <code>attempt_at</code>, <code>result</code> (<code>created</code>, <code>already-exists</code>, <code>seeded</code>, <code>healed</code>, <code>error</code>), <code>repo</code>, <code>template_sha</code>, <code>error_detail</code>.</p>
<p>Reconstruction rule: running the idempotent provisioning action against the roster reproduces a healthy state for every learner.</p>
<h2 id="7-provisioning-subsystem-the-classroom-replacement">7. Provisioning subsystem (the Classroom replacement)</h2>
<p>This is the only piece that fundamentally changes when Classroom departs. Everything downstream is unchanged because the resulting student repository is byte-shaped identically to a Classroom-created one.</p>
<h3 id="71-responsibilities">7.1 Responsibilities</h3>
<ol>
<li>Given a roster entry, create a private repository from <code>Community-Access/learning-room-template</code>.</li>
<li>Grant the learner access.</li>
<li>Confirm all required automation files are present (the workflow set listed in the go-live gate).</li>
<li>Record the outcome in the provisioning log.</li>
<li>Be idempotent: re-running heals partial failures and never corrupts an existing repository.</li>
</ol>
<h3 id="72-implementation-options-and-decision">7.2 Implementation options and decision</h3>
<ul>
<li><strong>Option A, GitHub App (decided for production).</strong> A GitHub App with least-privilege permissions (repository administration to create, contents to seed, metadata). Scoped, auditable, installable, and not tied to any human account.</li>
<li><strong>Option B, Actions bot (Phase 1 spike only).</strong> A scheduled or dispatched workflow in the admin repo using a least-privilege fine-grained token with repo-creation scope. Faster to stand up; acceptable only as a throwaway validation of seeding logic, then discarded.</li>
</ul>
<p>Both must implement the same internal contract so the method is swappable via <code>PROVISIONING_MODE</code>.</p>
<p><strong>Decision: build the GitHub App for production; use the PAT path only as an optional Phase 1 spike.</strong> The deciding factor is not rate limits or convenience, it is the first principle in <a href="golden.html">golden.md</a>: never let a single point of failure hold a cohort hostage. A PAT is bound to a facilitator's account, so that person leaving, resetting credentials, or changing 2FA can break provisioning. The App's properties (not tied to a human, short-lived installation tokens, fine-grained least-privilege) are the literal embodiment of the golden vision, and the extra one-time setup is small.</p>
<p>The pros and cons that drove the decision:</p>
<table>
<thead>
<tr>
<th scope="col">Factor</th>
<th scope="col">GitHub App</th>
<th scope="col">Actions bot (PAT)</th>
</tr>
</thead>
<tbody><tr>
<td>Identity</td>
<td>Purpose-built, survives staff change</td>
<td>Bound to a human account, single point of failure</td>
</tr>
<tr>
<td>Token lifetime</td>
<td>Short-lived installation token (~1 hour), minted on demand</td>
<td>Long-lived (months); larger leak blast radius</td>
</tr>
<tr>
<td>Permission granularity</td>
<td>Fine-grained (Administration, Contents, Metadata only)</td>
<td>Account-level even when fine-grained</td>
</tr>
<tr>
<td>Rate limit ceiling</td>
<td>Scales with org (floor 5,000/hour, scaling up)</td>
<td>Flat 5,000/hour</td>
</tr>
<tr>
<td>Audit trail</td>
<td>Clean App identity per action</td>
<td>Everything looks like the token owner</td>
</tr>
<tr>
<td>Setup cost</td>
<td>~30 to 60 minutes one-time (App, App ID, PEM secret, token mint step)</td>
<td>Fastest; afternoon prototype</td>
</tr>
<tr>
<td>Key custody</td>
<td>Holds a signing key; must be in Secrets and rotatable</td>
<td>One token to store and rotate</td>
</tr>
</tbody></table>
<p>Because the downstream system cannot tell which mode created a repository, spiking with a PAT first and graduating to the App loses nothing.</p>
<h3 id="72a-github-app-permission-set">7.2a GitHub App permission set</h3>
<p>Grant only these. Anything beyond this list is over-privileged and fails the security review in Section 13.</p>
<table>
<thead>
<tr>
<th scope="col">Permission</th>
<th scope="col">Level</th>
<th scope="col">Why</th>
</tr>
</thead>
<tbody><tr>
<td>Repository administration</td>
<td>Write</td>
<td>Create the per-student private repository from the template</td>
</tr>
<tr>
<td>Contents</td>
<td>Write</td>
<td>Seed and heal repository content (workflows, issue templates)</td>
</tr>
<tr>
<td>Metadata</td>
<td>Read</td>
<td>Mandatory baseline for any App</td>
</tr>
<tr>
<td>Issues</td>
<td>Write</td>
<td>Seed the first challenge issue at provision time. Not deferrable: the Progression Bot only fires on issue close or manual dispatch, so a room provisioned without a Challenge 1 issue never starts the progression chain</td>
</tr>
</tbody></table>
<p>App configuration rules:</p>
<ul>
<li>Install the App on the <code>Community-Access</code> organization with all-repositories access (creating student repositories requires an organization-wide installation). An App that exists but is not installed fails every token mint with HTTP 404.</li>
<li>Store <code>PROVISIONING_APP_ID</code> and <code>PROVISIONING_APP_PRIVATE_KEY</code> in GitHub Secrets; never in code or public repos. <code>PROVISIONING_APP_INSTALLATION_ID</code> is optional: when unset, provisioning discovers the installation from the App at runtime, which removes a copy-paste failure mode and survives re-installation.</li>
<li>Mint a short-lived installation token at the start of each provisioning run; never persist it.</li>
<li>Verify credentials independently of enrollment: a weekly health check mints a token and confirms template access, and any provisioning failure opens a <code>provisioning-alert</code> issue (the watchdog required by section 7.3).</li>
<li>Document a private-key rotation procedure and rotate on any suspected exposure.</li>
</ul>
<h3 id="72b-provisioning-algorithm-idempotent-serial">7.2b Provisioning algorithm (idempotent, serial)</h3>
<p>Provisioning runs serially with a small delay and exponential backoff, not parallel fan-out, to stay clear of GitHub secondary (abuse) rate limits on content-creating requests. The algorithm is idempotent on <code>(github_handle, cohort_id)</code>, so a re-run resumes a half-finished batch instead of duplicating or corrupting work.</p>
<pre><code class="hljs language-text">for each roster entry where provision_state in (pending, failed):
key = (github_handle, cohort_id)
expected_repo = name_for(key)
1. If expected_repo already exists:
- Verify required workflow files and template SHA.
- If complete: mark provisioned (or healed), log already-exists, continue.
- If incomplete: re-seed missing files, mark healed, log healed.
Else:
- Create private repo from template at the pinned template SHA.
- Log created.
2. Grant the learner access (idempotent; skip if already granted).
3. Seed required content if not already present
(workflows, issue templates). Log seeded.
4. Verify required workflow set is present (go-live gate list).
- On success: provision_state = provisioned (or healed).
- On failure: provision_state = failed, write error_detail,
surface to watchdog. Do NOT leave a half-seeded repo silently.
5. Seed the Challenge 1 issue if no challenge issue exists yet
(idempotent; runs the learning-room progression script against the
student repo). The learner's collaborator invitation is usually still
pending at this point, so the issue is created unassigned; the
Progression Bot assigns subsequent challenges to whoever closes the
previous one. A seeding failure marks the learner failed and surfaces
to the watchdog.
6. Wait a short delay (1 to a few seconds) before the next entry.
On HTTP 403/429 (secondary limit), back off exponentially and retry
the same entry; never skip it.</code></pre>
<p>Invariants: at-most-one repository per key; pinned template SHA; learner has access; required workflows present; every outcome written to the provisioning log; any failure visible to a human before a learner notices.</p>
<h3 id="73-provisioning-contract">7.3 Provisioning contract</h3>
<ul>
<li>Input: roster entry (<code>github_handle</code>, <code>cohort_id</code>, <code>path</code>).</li>
<li>Output: <code>learning_room_repo</code>, <code>provision_state</code>, provisioning log entry.</li>
<li>Idempotency key: <code>(github_handle, cohort_id)</code>.</li>
<li>Guarantees: at-most-one repository per key; correct template SHA; learner has access; required workflows present.</li>
<li>Failure behavior: on any error, set <code>provision_state = failed</code>, write <code>error_detail</code>, and surface to the watchdog. Never leave a half-seeded repo silently.</li>
</ul>
<h3 id="74-rate-and-scale-considerations">7.4 Rate and scale considerations</h3>
<p>For any realistic size of this workshop, documented hourly rate limits are a non-issue. The real risk is GitHub secondary (abuse) rate limits, which trigger on bursts of content-creating requests fired in parallel. The serial-with-delay-and-backoff algorithm in Section 7.2b avoids them, and idempotency means hitting a limit just means run it again, it heals.</p>
<p><strong>Design target: 1 to 100 learners.</strong> That is what a high-touch, accessibility-first, facilitator-led workshop actually is. A cohort large enough to hit real rate limits would also be too large to run with the recover-every-stuck-learner quality bar that defines this project. The constraint that bites first is facilitator attention, not API quota.</p>
<p>This table is the planning guidance by cohort size.</p>
<table>
<thead>
<tr>
<th scope="col">Cohort size</th>
<th scope="col">Reality</th>
<th scope="col">What to do</th>
</tr>
</thead>
<tbody><tr>
<td>1 to 30 (likely case)</td>
<td>Trivial; serial creation finishes in minutes</td>
<td>Serial loop, 1 to 2 second delay, backoff on error</td>
</tr>
<tr>
<td>30 to 100</td>
<td>Comfortable within an hour</td>
<td>Same pattern; rely on idempotent re-run to heal mid-batch failures</td>
</tr>
<tr>
<td>100 to 300</td>
<td>Approaching where bursting would trip secondary limits</td>
<td>Throttle to a steady rate, chunk the batch, resume via idempotency; the App ceiling helps</td>
</tr>
<tr>
<td>300+</td>
<td>Plan deliberately</td>
<td>Stagger provisioning over time windows, pre-flight seat and quota checks, monitor for 403s</td>
</tr>
</tbody></table>
<p>Two design choices make cohort size a permanent non-worry, and both are already specified:</p>
<ul>
<li><strong>Idempotent provisioning keyed on <code>(github_handle, cohort_id)</code></strong> (Section 7.2b). A re-run resumes a half-finished batch rather than duplicating or corrupting it.</li>
<li><strong>Provision on registration, not big-bang.</strong> Creating repositories as learners register (a trickle) rather than all at once on go-live morning (a burst) means you essentially never approach a burst limit regardless of total size.</li>
</ul>
<p>Operational rules:</p>
<ul>
<li>Pre-flight check org seat and repository quotas before a cohort opens.</li>
<li>Template sync and smoke validation (<code>Prepare-LearningRoomTemplate.ps1</code>, <code>Test-LearningRoomTemplate.ps1</code>) run before any provisioning.</li>
</ul>
<h2 id="8-automation-contracts">8. Automation contracts</h2>
<p>Each arrow in the architecture is a stable contract. Contracts are intentionally simple text and label signals so facilitators can test and recover them by hand.</p>
<table>
<thead>
<tr>
<th scope="col">Contract</th>
<th scope="col">Producer</th>
<th scope="col">Consumer</th>
<th scope="col">Signal</th>
<th scope="col">Idempotent</th>
<th scope="col">Manual fallback</th>
</tr>
</thead>
<tbody><tr>
<td>Enroll</td>
<td>Registration workflow</td>
<td>Roster</td>
<td><code>enrolled</code> label, roster record</td>
<td>Yes</td>
<td>Apply label, add record</td>
</tr>
<tr>
<td>Acknowledge</td>
<td>Learner</td>
<td>Roster, dashboard</td>
<td>comment <code>ack</code></td>
<td>Yes</td>
<td>Facilitator marks acked</td>
</tr>
<tr>
<td>Day 1 complete</td>
<td>Learner</td>
<td>Day 2 release</td>
<td>comment <code>day1-complete</code> or label</td>
<td>Yes</td>
<td>Facilitator posts Day 2 link</td>
</tr>
<tr>
<td>Day 2 release</td>
<td>Release workflow</td>
<td>Learner</td>
<td>comment with Day 2 link, <code>day2-released</code> label</td>
<td>Yes</td>
<td>Facilitator posts link</td>
</tr>
<tr>
<td>Next challenge</td>
<td>Progression bot</td>
<td>Learner</td>
<td>new <code>Challenge N+1</code> issue</td>
<td>Yes (per issue)</td>
<td>Dispatch with <code>start_challenge</code></td>
</tr>
<tr>
<td>Autograde</td>
<td>Autograder</td>
<td>Learner</td>
<td>single pass/fail comment keyed by marker</td>
<td>Yes</td>
<td>Facilitator verifies and comments</td>
</tr>
<tr>
<td>Dashboard</td>
<td>Dashboard sync</td>
<td>Facilitator</td>
<td>upserted status issue</td>
<td>Yes</td>
<td>Read enrollment issues</td>
</tr>
<tr>
<td>Provision</td>
<td>Provisioning action</td>
<td>Roster, learner</td>
<td>repo created and seeded</td>
<td>Yes (per key)</td>
<td>Classroom invite during transition</td>
</tr>
</tbody></table>
<p>Contract invariants:</p>
<ul>
<li>Every bot comment is updated in place via a marker string, never duplicated.</li>
<li>Every workflow that can fail posts a separate, blameless error notice on failure.</li>
<li>Every signal is plain text or a label so it is human-testable and human-recoverable.</li>
</ul>
<h2 id="9-curriculum-subsystem">9. Curriculum subsystem</h2>
<ul>
<li>22 chapters (<a href="docs/00-pre-workshop-setup.html">docs/00-pre-workshop-setup.md</a> through <a href="docs/22-what-comes-next.html">docs/22-what-comes-next.md</a>) plus appendices A through Z and AA through AC.</li>
<li>16 core challenges and 5 bonus challenges, indexed in <a href="docs/CHALLENGES.html">docs/CHALLENGES.md</a>, each with instructions, evidence requirements, and a reference solution in <a href="docs/solutions/">docs/solutions/</a>.</li>
<li>Each chapter is authored screen-reader-first: every step keyboard-accessible, every concept explained without sight, an "If You Get Stuck" section, and a Section-Level Source Map enforced by the authoritative-sources validator.</li>
<li>The arc is one journey: browser, then github.dev, then desktop VS Code with Accessibility Agents. Every Day 1 skill maps to a Day 2 agent command.</li>
</ul>
<p>Curriculum requirements:</p>
<ul>
<li>Source maps required on in-scope content; podcasts and non-content paths excluded (per repo memory and the validator).</li>
<li>Every reference URL must resolve; broken links fail the content gate.</li>
<li>Tools-change resilience: chapters teach exploration so learners survive UI drift.</li>
</ul>
<h2 id="10-content-pipeline-docs-html-epub-audio">10. Content pipeline: docs, HTML, EPUB, audio</h2>
<p>The pipeline turns Markdown into every delivery format.</p>
<table>
<thead>
<tr>
<th scope="col">Stage</th>
<th scope="col">Input</th>
<th scope="col">Output</th>
<th scope="col">Workflow or script</th>
</tr>
</thead>
<tbody><tr>
<td>HTML build</td>
<td><code>docs/*.md</code></td>
<td><code>html/</code></td>
<td>build per <a href="BUILD.html">BUILD.md</a></td>
</tr>
<tr>
<td>EPUB build</td>
<td><code>docs/*.md</code>, <code>epub/epub.css</code></td>
<td>EPUB</td>
<td><a href=".github/workflows/build-epub.yml">build-epub.yml</a></td>
</tr>
<tr>
<td>Diagrams</td>
<td>diagram sources</td>
<td>SVGs</td>
<td><a href=".github/workflows/generate-diagram-svgs.yml">generate-diagram-svgs.yml</a></td>
</tr>
<tr>
<td>Pages deploy</td>
<td>HTML</td>
<td>published site</td>
<td><a href=".github/workflows/deploy-pages.yml">deploy-pages.yml</a></td>
</tr>
<tr>
<td>Wiki sync</td>
<td>docs</td>
<td>wiki</td>
<td><a href=".github/workflows/sync-docs-to-wiki.yml">sync-docs-to-wiki.yml</a></td>
</tr>
<tr>
<td>Audio series</td>
<td>transcripts</td>
<td>MP3, RSS</td>
<td>batch scripts, RSS feed builder</td>
</tr>
</tbody></table>
<p>Audio policy: minimize MP3 regeneration. Prefer transcript-only and site-only updates unless a podcast release is explicitly in scope. The catalog currently covers 54 companion episodes plus planned Challenge Coach episodes.</p>
<p>Pipeline requirements:</p>
<ul>
<li>HTML must build from current Markdown before any cohort opens.</li>
<li>The RSS feed validates before publish.</li>
<li>All generated HTML preserves heading structure, descriptive links, and table semantics.</li>
</ul>
<h2 id="11-optional-flask-companion">11. Optional Flask companion</h2>
<p>The companion is strictly optional and lives at the edges. It must add delight without becoming a dependency.</p>
<h3 id="111-scope">11.1 Scope</h3>
<ul>
<li>Accessible registration landing page (writes to the owned roster).</li>
<li>Facilitator cohort dashboard (reads owned roster and progress).</li>
<li>Bulk facilitator operations (trigger provisioning, re-seed, recover) as thin wrappers over the owned provisioning action.</li>
</ul>
<h3 id="112-hard-constraints">11.2 Hard constraints</h3>
<ul>
<li>Stateless about anything critical. If the companion is down, the GitHub-native front door and admin-issue dashboard carry the full workshop.</li>
<li>WCAG 2.2 AA: semantic landmarks, correct heading order, labeled controls, visible focus, live-region announcements, no keyboard traps.</li>
<li>OWASP Top 10 reviewed: authenticated facilitator actions, CSRF protection, input validation, output encoding, least-privilege tokens, rate limiting, secrets never in client code.</li>
<li>All writes go through the owned roster and provisioning contracts; the companion never invents a parallel state store of record.</li>
</ul>
<h3 id="113-interfaces">11.3 Interfaces</h3>
<ul>
<li>Reads and writes the roster of record (admin repo or its mirror).</li>
<li>Invokes the provisioning action through its defined contract only.</li>
<li>Renders progress derived from the same signals the GitHub-native dashboard uses.</li>
</ul>
<h2 id="12-accessibility-requirements">12. Accessibility requirements</h2>
<p>Accessibility is the product. These are acceptance-blocking.</p>
<ul>
<li>Every learner-facing surface verified on NVDA, JAWS, and VoiceOver, fully keyboard operable.</li>
<li>All documentation readable with or without a screen reader; no information conveyed by color alone.</li>
<li>Markdown follows the project accessibility rules: descriptive link text, alt text for meaningful images, correct heading hierarchy, table intro sentences, emoji removed or translated, diagrams given text alternatives, no em-dashes, validated anchors.</li>
<li>Automation copy is screen-reader-first: front-loaded, clear, free of decorative noise.</li>
<li>Any Flask companion meets WCAG 2.2 AA in full (Section 11.2).</li>
<li>Testing follows the project accessibility testing guidance; manual screen reader passes are required, not just automated checks.</li>
</ul>
<h2 id="13-security-requirements">13. Security requirements</h2>
<ul>
<li>Free of OWASP Top 10 vulnerabilities across any hosted surface and all automation.</li>
<li>Least-privilege tokens and scopes; each token documented with the single capability it enables and a rotation procedure.</li>
<li>Secrets only in GitHub Secrets or a managed secret store; never in code, public repos, or client bundles.</li>
<li>Public registration redacts private intake; detailed data stored only in the private admin repo when configured.</li>
<li>Provisioning permissions scoped to repository creation and seeding only.</li>
<li>Workflows pin actions and follow GitHub Actions security hardening (limited permissions blocks, no untrusted input in run shells).</li>
<li>Treat all tool and webhook input as untrusted; validate and encode. Watch for and report prompt-injection attempts in any AI-assisted automation.</li>
</ul>
<h2 id="14-reliability-observability-and-recovery">14. Reliability, observability, and recovery</h2>
<ul>
<li><strong>No silent failure.</strong> Every automated step either succeeds or raises a human-visible alert with a clear recovery action.</li>
<li><strong>Watchdog.</strong> A scheduled workflow detects stalled provisioning, stuck progression, or learners without a healthy repository, and surfaces them before a learner notices. See <a href="learning-room/.github/workflows/autograder-watchdog.yml">autograder-watchdog.yml</a>.</li>
<li><strong>Idempotent everything.</strong> Provisioning, seeding, and bot comments are safe to re-run.</li>
<li><strong>Tiered learner recovery.</strong> Documented restore levels return a stuck learner to a known-good state with branch and PR evidence, no data loss (per the QA runbook).</li>
<li><strong>Manual fallback for every contract</strong> (Section 8), so the system survives any single automation outage.</li>
<li><strong>Release gate.</strong> No cohort opens until <a href="GO-LIVE-QA-GUIDE.html">GO-LIVE-QA-GUIDE.md</a> passes; execution follows <a href="admin/LEARNING-ROOM-E2E-QA-RUNBOOK.html">admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md</a>.</li>
</ul>
<h2 id="15-testing-and-quality-strategy">15. Testing and quality strategy</h2>
<table>
<thead>
<tr>
<th scope="col">Layer</th>
<th scope="col">What it covers</th>
<th scope="col">Where</th>
</tr>
</thead>
<tbody><tr>
<td>Unit and automation tests</td>
<td>Scripts and helpers</td>
<td><code>npm run test:automation</code>, <a href=".github/workflows/automation-tests.yml">automation-tests.yml</a></td>
</tr>
<tr>
<td>Content validation</td>
<td>Links, markdown, source maps, accessibility</td>
<td>content-validation and authoritative-sources workflows and <code>.github/scripts</code> checks</td>
</tr>
<tr>
<td>Template smoke</td>
<td>Template integrity before provisioning</td>
<td><code>Prepare-LearningRoomTemplate.ps1</code>, <code>Test-LearningRoomTemplate.ps1</code></td>
</tr>
<tr>
<td>Challenge reliability matrix</td>
<td>Happy, failure, and recovery path per challenge family</td>
<td><a href="classroom/HUMAN_TEST_MATRIX.html">classroom/HUMAN_TEST_MATRIX.md</a></td>
</tr>
<tr>
<td>End-to-end QA</td>
<td>Registration through completion</td>
<td><a href="admin/LEARNING-ROOM-E2E-QA-RUNBOOK.html">admin/LEARNING-ROOM-E2E-QA-RUNBOOK.md</a></td>
</tr>
<tr>
<td>Accessibility passes</td>
<td>NVDA, JAWS, VoiceOver, keyboard</td>
<td>Project accessibility testing guidance</td>
</tr>
<tr>
<td>Release gate</td>
<td>All of the above consolidated</td>
<td><a href="GO-LIVE-QA-GUIDE.html">GO-LIVE-QA-GUIDE.md</a></td>
</tr>
</tbody></table>
<p>Quality rules:</p>
<ul>
<li>Every challenge has happy-path, failure-path, and recovery-path evidence before go-live.</li>
<li>Provisioning is tested with at least one re-run to prove idempotency.</li>
<li>Any companion ships only after an accessibility pass and an OWASP review.</li>
</ul>
<h2 id="16-configuration-surface">16. Configuration surface</h2>
<p>Variables:</p>
<ul>
<li><code>CLASSROOM_DAY1_ASSIGNMENT_URL</code>, <code>CLASSROOM_DAY2_ASSIGNMENT_URL</code> (transitional; retired after Classroom removal).</li>
<li><code>PRIVATE_STUDENT_DATA_REPO</code>.</li>
<li><code>ENABLE_PUBLIC_CLASSROOM_INTAKE_EXPORT</code>, <code>ENABLE_PUBLIC_REGISTRATION_EXPORT</code>.</li>
<li>New for owned provisioning: <code>LEARNING_ROOM_TEMPLATE_REPO</code>, <code>ADMIN_ROSTER_REPO</code>, <code>PROVISIONING_MODE</code> (<code>github-app</code> or <code>actions-bot</code>).</li>
</ul>
<p>Secrets:</p>
<ul>
<li><code>PRIVATE_STUDENT_DATA_TOKEN</code>, <code>INSTRUCTOR_DASHBOARD_TOKEN</code>.</li>
<li>New for owned provisioning: <code>PROVISIONING_APP_ID</code> and <code>PROVISIONING_APP_PRIVATE_KEY</code> (App mode) or <code>PROVISIONING_TOKEN</code> (bot mode), least-privilege and rotatable.</li>
</ul>
<h2 id="17-repository-layout">17. Repository layout</h2>
<table>
<thead>
<tr>
<th scope="col">Path</th>
<th scope="col">Purpose</th>