feat(provisioning): Hybrid owned, GitHub-native provisioning + accessible companion#230
Merged
Merged
Conversation
…g + accessible companion Implements the Hybrid plan (Option C) from golden.md and SPEC.md: a dependable GitHub-native core that replaces GitHub Classroom provisioning, plus an optional, fully accessible Flask companion at the edges. Owned core (no third-party npm deps; built-in crypto + fetch): - roster.js + roster.schema.json: owned roster of record (parse/validate/upsert/ serialize/redact), keyed on (github_handle, cohort_id) - progress.js: owned progress derived from deterministic signals (ack, day1-complete, labels, closed challenge issues, PR closing keywords) - github-app-auth.js: RS256 App JWT + short-lived installation token minting - github-client.js: minimal GitHub REST client (fetch + Octokit adapters) - provision-core.js: idempotent, serial, backoff provisioning algorithm with self-healing and loud failure (SPEC 7.2b) - provision-cli.js + provision-learning-rooms.yml: runner and scheduled/manual workflow supporting github-app and actions-bot modes Optional accessible companion (companion/): - Flask app: accessible registration front door + facilitator dashboard that read/write the same owned roster contract; non-critical by design - WCAG 2.2 AA templates and OWASP-aware controls (CSRF, auth with constant-time compare, input validation, rate limiting, CSP/security headers) Docs and tests: - admin/OWNED_PROVISIONING.md operator guide; companion/README.md; example roster; golden.md and SPEC.md committed as canonical vision/spec with source maps - 55 Node tests (npm run test:provisioning) + 25 Python tests; both wired into the automation-tests workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Step-by-step deployment for the owned, GitHub-native provisioning core and the optional Flask companion: admin roster repo, GitHub App creation with least-privilege permissions, variables/secrets, smoke test with idempotency proof, companion deployment, go-live checklist, and rollback/fallback. Cross-linked from OWNED_PROVISIONING.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Single entry point cataloging every part of the Hybrid deliverable (planning docs, provisioning core, companion, CI wiring, tests) with links and a suggested review order. Functional files stay in their conventional locations; the index points to them. Cross-linked from the deployment and operator guides. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add html/ equivalents for the Hybrid plan markdown (golden, SPEC, review index, deployment guide, owned provisioning, cohort provisioning, and companion README) via the repo's build:html pipeline. Internal .md links are rewritten to .html. Excludes companion/.venv build artifacts and unrelated full-site churn. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Peer Review AssignedHi @accesswatch! Your PR has been automatically paired with @taylorarndt for peer review. For @taylorarndt:This is a great opportunity to practice code review skills! Here's what to look for: Content Quality:
Accessibility:
Documentation:
Review Guidelines:
Resources: Pairing by Learning Room Grouping Engine |
There was a problem hiding this comment.
Pull request overview
This PR introduces the Hybrid “owned provisioning” architecture for GIT Going with GitHub (GLOW): a GitHub-native provisioning core (as a replacement path for GitHub Classroom) plus an optional accessibility-focused Flask companion that reads/writes the same owned roster.json contract.
Changes:
- Adds an owned roster contract (
roster.schema.json) and provisioning implementation (Node scripts + new scheduled/manual GitHub Actions workflow). - Adds an optional Flask companion app (registration front door + facilitator dashboard) with tests and CI wiring.
- Adds operator/deployment documentation (plus generated HTML equivalents) describing the Hybrid plan and operational procedures.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| roster.schema.json | Adds JSON Schema for the owned roster-of-record contract. |
| package.json | Adds test:provisioning script to run provisioning unit tests. |
| html/golden.html | Generated HTML for the new Hybrid vision doc. |
| html/companion/index.html | Generated HTML for companion documentation landing page. |
| html/admin/OWNED_PROVISIONING.html | Generated HTML operator guide for owned provisioning. |
| html/admin/HYBRID_REVIEW_INDEX.html | Generated HTML review index for the Hybrid deliverable. |
| html/admin/HYBRID_DEPLOYMENT_GUIDE.html | Generated HTML deployment guide for Hybrid architecture. |
| html/admin/COHORT_PROVISIONING.html | Generated HTML updated to reference owned provisioning path. |
| golden.md | Adds Hybrid plan vision/decision record (“golden” north star). |
| companion/tests/test_roster_store.py | Unit tests for the companion’s roster store/contract. |
| companion/tests/test_app.py | Unit tests for companion routes, CSRF, headers, auth, and rate limiting. |
| companion/templates/register.html | Accessible registration form template with error summary. |
| companion/templates/register_success.html | Registration confirmation template. |
| companion/templates/login.html | Facilitator sign-in template. |
| companion/templates/error.html | Error page template for 400/404/429 flows. |
| companion/templates/dashboard.html | Read-only facilitator dashboard template rendering the roster. |
| companion/templates/base.html | Base layout with navigation, skip link, and flash live region. |
| companion/static/styles.css | Companion accessibility-first CSS (focus styles, contrast, layout). |
| companion/roster_store.py | Python implementation of the owned roster contract for the companion. |
| companion/requirements.txt | Declares Flask dependency range. |
| companion/README.md | Companion overview and run/security/accessibility guidance. |
| companion/app.py | Flask app implementation: routes, CSRF, headers, auth, and rate limiting. |
| companion/.gitignore | Prevents committing local env/instance/bytecode artifacts. |
| admin/OWNED_PROVISIONING.md | Operator guide for the owned provisioning subsystem. |
| admin/HYBRID_REVIEW_INDEX.md | Central index for reviewing all Hybrid plan deliverables. |
| admin/HYBRID_DEPLOYMENT_GUIDE.md | Step-by-step deployment and verification guide for Hybrid architecture. |
| admin/examples/roster.example.json | Example roster JSON illustrating the owned contract. |
| admin/COHORT_PROVISIONING.md | Adds forward-looking note linking to owned provisioning docs. |
| .github/workflows/provision-learning-rooms.yml | New scheduled/manual workflow to provision student repos from roster. |
| .github/workflows/automation-tests.yml | Extends CI to run provisioning tests and companion Python tests. |
| .github/scripts/provisioning/roster.js | Node roster contract: parse/validate/upsert/serialize/redact. |
| .github/scripts/provisioning/provision-core.js | Idempotent serial provisioning algorithm with backoff and verification. |
| .github/scripts/provisioning/provision-cli.js | CLI runner for provisioning (GitHub App or PAT “actions-bot” mode). |
| .github/scripts/provisioning/progress.js | Deterministic progress/status derivation from controlled signals. |
| .github/scripts/provisioning/github-client.js | Minimal GitHub REST client for provisioning operations. |
| .github/scripts/provisioning/github-app-auth.js | Node crypto GitHub App JWT + installation-token minting. |
| .github/scripts/provisioning/tests/roster.test.js | Node tests for roster contract behavior. |
| .github/scripts/provisioning/tests/provision-core.test.js | Node tests for provisioning algorithm/idempotency/backoff. |
| .github/scripts/provisioning/tests/provision-cli.test.js | Node tests for CLI arg parsing and token resolution. |
| .github/scripts/provisioning/tests/progress.test.js | Node tests for progress derivation and signal parsing. |
| .github/scripts/provisioning/tests/github-client.test.js | Node tests for fetch client + Octokit adapter. |
| .github/scripts/provisioning/tests/github-app-auth.test.js | Node tests for JWT creation and installation-token minting. |
Comment on lines
+33
to
+41
| async function request(method, path, body) { | ||
| const res = await fetchImpl(`${apiBaseUrl}${path}`, { | ||
| method, | ||
| headers: { | ||
| Authorization: `token ${token}`, | ||
| Accept: 'application/vnd.github+json', | ||
| 'X-GitHub-Api-Version': '2022-11-28', | ||
| 'User-Agent': userAgent, | ||
| ...(body ? { 'Content-Type': 'application/json' } : {}) |
Comment on lines
+24
to
+28
| function isSecondaryRateLimit(err) { | ||
| if (!err) return false; | ||
| if (err.status === 403 || err.status === 429) return true; | ||
| return /HTTP (403|429)/.test(String(err.message || '')); | ||
| } |
Comment on lines
+192
to
+195
| let templateSha = null; | ||
| if (typeof client.getDefaultBranchSha === 'function') { | ||
| templateSha = await client.getDefaultBranchSha({ owner: studentOwner, repo: repoName }); | ||
| } |
Comment on lines
+227
to
+231
| target = request.args.get("next") or url_for("dashboard") | ||
| # Only allow local redirects. | ||
| if not target.startswith("/"): | ||
| target = url_for("dashboard") | ||
| return redirect(target) |
| /> | ||
| </div> | ||
|
|
||
| <fieldset class="field"> |
Comment on lines
+76
to
+95
| learners = data.get("learners") | ||
| if not isinstance(learners, list): | ||
| raise RosterError("Roster.learners must be a list") | ||
| seen = set() | ||
| for learner in learners: | ||
| problems = validate_learner(learner) | ||
| if problems: | ||
| raise RosterError( | ||
| f"Invalid learner {learner.get('github_handle')}: {'; '.join(problems)}" | ||
| ) | ||
| key = learner_key(learner["github_handle"], learner["cohort_id"]) | ||
| if key in seen: | ||
| raise RosterError(f"Duplicate learner for key {key}") | ||
| seen.add(key) | ||
| return Roster( | ||
| version=1, | ||
| cohorts=data.get("cohorts", []) or [], | ||
| learners=learners, | ||
| generated_at=data.get("generated_at"), | ||
| ) |
Comment on lines
+119
to
+122
| existing = self.find(candidate["github_handle"], candidate["cohort_id"]) | ||
| if existing is not None: | ||
| existing.update({k: v for k, v in entry.items() if v is not None}) | ||
| return existing |
| </div> | ||
| {% endif %} | ||
|
|
||
| <form method="post" action="{{ url_for('login') }}{% if request.args.get('next') %}?next={{ request.args.get('next') }}{% endif %}" novalidate> |
Comment on lines
+50
to
+58
| - name: Run provisioning test suite | ||
| run: npm run test:provisioning | ||
|
|
||
| - name: Install companion dependencies | ||
| run: pip install -r companion/requirements.txt | ||
|
|
||
| - name: Run companion test suite | ||
| working-directory: companion | ||
| run: python -m unittest discover -s tests |
Comment on lines
+53
to
+55
| - name: Install companion dependencies | ||
| run: pip install -r companion/requirements.txt | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Delivers Hybrid owned provisioning system with GitHub-native workflow orchestration and optional Flask companion for accessibility.
Changes
Deployment
Tests passing:
Ready for production deployment.