A **contingency design**, not a plan of record. Recitations run on paper today and VISION section 12 records that as decided. This epic holds the full design for the case where that decision is ever reversed, broken into the work it would actually take, so the analysis is not redone from scratch under time pressure.
Source: `ARCHITECTURE.md`, which specified this fleet in full. Nothing in it exists at Oregon State today.
## Sub-issues
**Decide first**
- [ ] #18 Decide the trigger conditions for switching recitations to machines
**Build (about two person-weeks total)**
- [ ] #20 Build the golden image
- [ ] #21 Build the on-device filter
- [ ] #22 Pin the allowlist empirically, and settle the two known side channels
- [ ] #24 Rework session content delivery for the fleet
- [ ] #25 Write the TA runbook and rehearse the failure ladder
**Procure and deploy**
- [ ] #19 Cost and procure the device fleet (about $5,600)
- [ ] #23 Make the WiFi enrollment ask to Network Services
- [ ] #26 Run a pilot term before buying the full fleet
**Parked**
- [ ] #27 Escalation variants if device-level filtering proves insufficient
- [x] #16 Decide ARCHITECTURE.md's fate once its content lives here
## The design in one paragraph
A **self-filtering device fleet**: Raspberry Pi 500 workstations (keyboard-integrated) plug into existing classroom monitors, join campus WiFi, and enforce a per-device allowlist locally. No gateway, no switch, no cabling, no network the course has to own. Students submit directly to Gradescope from their seats with the same workflow they use at home. The chokepoint moves from a room gateway onto each device, which is not a security downgrade at this threat level: students have no root, the rules are baked into a read-only image, and every device is identical.
Design goals in order: **cheap** (low thousands, not tens), **self-supported** (one small one-time IT ask; operable by an instructor and two undergraduate TAs), **boring** (commodity hardware, stock OS, standard Linux filtering), and **fails safe** (every failure mode degrades to something a session survives, ending at the paper variant every recitation ships with).
## What already exists, so it is not rebuilt
`check` and `pack` are written and in use: they live once in `starters/_lib/` and are copied into each starter by `scripts/build-starters.sh`. That was one row of the original build table and it is done. The Gradescope autograder image is separately tracked in #14 and is needed **regardless of this epic**, because assignments depend on it.
## What changed since the document was written
The design assumed OSU GitLab hosted both the starter repositories and the boot-time allowlist file, and that students would clone the week's repo at session start. Neither holds: starters ship as Canvas downloads and git left the required path. #21 and #24 carry that delta.
## Why not BYOD, answered once
Recorded here because it returns every budget cycle. A blocking script on student laptops is **trust inversion**: the referee runs on the player's hardware, where it can be not-run, edited, killed, run inside a VM while real browsing happens outside it, or satisfied on one laptop while a second sits in the bag. It is also the commercial-proctoring privacy model in homemade form, invasive for everyone and still bypassed by the people it targets, with breakage liability the course owns. And it cannot carry the no-AI claim at all, because the modern threat is ambient AI inside normal tools (editor copilots, OS assistants, autocomplete), which is invisible to visual proctoring and requires controlling the endpoint, the one thing BYOD surrenders by definition.
The least-bad variant, for the record: Safe Exam Browser pointed at a course-hosted web IDE, with only the IDE, docs, and Gradescope reachable. Honest accounting: a real server returns to the architecture (25 concurrent Rust compiles need a serious box), SEB does not exist for Linux, and students with unsupported laptops need loaners anyway, so the course ends up owning devices regardless, just without controlling them. Acceptable as a make-up mechanism or a bridge term; not a primary design.
At about $200 per seat, the course-owned fleet is the cheapest mechanism that actually closes the loop.
## Archive: the full text of ARCHITECTURE.md
The file was deleted from the working tree on 2026-08-17 once this epic and its issues carried the work. It was never committed, so this comment is the only remaining copy. Preserved verbatim, including the parts the issues summarise rather than reproduce: the section 2 topology diagram, the full six-item threat model, and the sourcing note.
<details><summary>ARCHITECTURE.md, 397 lines, verbatim</summary>
# ENGR 103 Recitation Lab: Technical Architecture
This document specifies the physical and software infrastructure for the
proctored recitations described in `VISION.md` (section 5). Nothing described
here exists at Oregon State today. The design goals, in order:
1. **Cheap.** Capital budget in the low thousands of dollars, not tens.
2. **Self-supported.** Runs without central IT involvement beyond one small,
one-time ask; operable by an instructor and two undergraduate TAs.
3. **Boring.** Every component is a commodity: Raspberry Pi hardware, stock
Raspberry Pi OS, standard Linux filtering, campus WiFi. Nothing exotic to
maintain, nothing that only one person understands.
4. **Fails safe.** Every failure mode degrades to something a session can
survive, ending at the paper variant that every recitation ships with.
The architecture is a **self-filtering device fleet**: Raspberry Pi 500
workstations (keyboard-integrated) plug into existing classroom monitors,
join campus WiFi, and enforce a per-device allowlist (official docs, the
course starter repositories on OSU GitLab, Gradescope) locally. There is no
gateway, no switch, no cabling, and no network the course has to own.
Students submit directly to Gradescope from their seats, with the same
workflow they use at home. Escalation variants (a filtering gateway, a full
air gap) are kept in section 11 for the case where device-level filtering
ever proves insufficient in practice, and the recurring "why not BYOD with a
blocking script" question is answered once, in full, in section 12.
---
## 1. Requirements
What one recitation session needs (25 students, 110 minutes, weekly):
- A workstation per student with: Python 3, the Rust toolchain, VS Code and
vim, a `check` command that runs the provided tests locally, and browser
plus git access to exactly: the official Python and Rust documentation,
the course starter repositories, and Gradescope.
- No search engines, no AI tools, no mail or chat, no personal cloud
storage, from any seat.
- Direct, continuous submission to Gradescope (submit as often as you like,
last one before the window closes counts).
- A room reset between back-to-back sections in under 10 minutes, leaving no
trace of the previous student at any seat.
- Weekly content updates and allowlist changes without touching 25 devices
by hand.
Constraints: no dedicated sysadmin, no course-owned network, existing
classroom monitors, and campus WiFi as the only connectivity.
---
## 2. System overview
```
Campus WiFi ──── Gradescope, OSU GitLab, docs.python.org,
│ doc.rust-lang.org, SSO/Duo (and nothing else)
│
┌────┴──────────────────────────────────────────┐
│ 25 × RASPBERRY PI 500 (+2 spares) │
│ keyboard-integrated, existing monitors │
│ Raspberry Pi OS, overlay FS (reboot = clean) │
│ no sudo for students │
│ ON-DEVICE ALLOWLIST: │
│ dnsmasq resolves only allowlisted names │
│ nftables permits only resolved IPs │
│ allowlist file pulled from GitLab at boot │
│ browser policy: no DoH, no ECH, no extensions│
└───────────────────────────────────────────────┘
```
The chokepoint moves from a room gateway onto each device. That is not a
security downgrade at this threat level: students have no root, the filter
rules are baked into a read-only image, and every device runs the identical
configuration. What it removes is an entire class of infrastructure (gateway
box, switch, cables, uplink negotiation). What it costs is central logging
and path-level filtering, both accepted and documented in section 8.
---
## 3. Client hardware
### Raspberry Pi 500, not Pi 400
The Pi 400 (2020, Pi 4 class, 4 GB RAM) is below the comfortable floor for
VS Code plus rust-analyzer plus a documentation browser running together; it
will swap and students will feel it. The **Pi 500** (Pi 5 class, 8 GB RAM,
integrated keyboard, $200 as the desktop bundle with power supply, mouse, SD
card, and cables) handles that workload with room to spare, and
compiles the course's one-file Rust scaffolds in single-digit seconds with
the crate cache pre-warmed. If extra budget appears, the Pi 500+ (16 GB,
built-in SSD) is a premium option, not a requirement.
| Item | Qty | Unit | Total |
|---|---|---|---|
| Raspberry Pi 500 desktop bundle (8 GB Pi with integrated keyboard, PSU, mouse, microSD, HDMI cable) | 27 | $200 | $5,400 |
| Spare 64 GB A2-class microSD cards (flashed, on the shelf) | 5 | $12 | $60 |
| Storage tote / small cart | 1 | | $150 |
| **Total** | | | **about $5,600** |
Monitors are the room's existing ones (the Pi 500 drives any HDMI display);
if the room has none, used 22 to 24 inch panels add about $35 per seat. The
whole fleet fits in a lockable tote, so no dedicated room is required: any
classroom with monitors (or a cart of monitors) becomes the lab for 110
minutes.
Two spare Pis plus five spare flashed SD cards make the swap procedure
trivial: a misbehaving seat gets a new card first (30 seconds) and a new Pi
second (2 minutes).
### The golden image
Raspberry Pi OS (Debian-based, ARM64), one image flashed identically to
every card with Raspberry Pi Imager (parallelizable with a USB hub). On it:
- **Overlay filesystem mode on** (a stock `raspi-config` feature built for
kiosks): the root filesystem is read-only with a tmpfs overlay, so every
reboot returns the device to factory state. The between-section room reset
is "power cycle everything," under 5 minutes, zero custom engineering.
- Accounts: `student` (no sudo) and `proctor` (sudo, password held by TAs).
- Toolchains: Python 3 with pytest vendored; Rust via rustup (ARM64) with a
pre-warmed cargo cache for every crate any scaffold uses; git.
- Editors: VS Code for ARM64 with rust-analyzer and the Python extension
pinned; vim.
- Browser: Firefox or Chromium under a managed policy that disables
DNS-over-HTTPS, Encrypted Client Hello, sync, and extensions, so hostname
filtering cannot be tunneled around from inside the browser.
- **No course-specific software.** The image carries toolchains, editors,
the browser policy, and the filter, nothing else. Everything
course-specific (the problem spec as the repo README, the twin scaffolds,
the visible tests, and the `check` and `pack` scripts) arrives inside each
assignment's starter repository, cloned at session start. Consequences:
the fleet serves any section or any course that adopts the same pattern,
tool fixes ship weekly with content instead of requiring re-imaging, and
take-home parity is automatic because students clone the identical repo at
home. `check` runs the provided tests locally, printing exactly what
Gradescope's autograder will print; `pack` zips the problem directory into
`submission.zip` for the Gradescope upload box; both are plain Python
scripts so they run anywhere the course does.
- **On-device filter:** dnsmasq resolves only allowlisted hostnames (all
other queries return NXDOMAIN); nftables permits outbound traffic only to
IPs that came from those resolutions and drops everything else, including
raw-IP connections. The allowlist file itself is fetched at boot over
HTTPS from a URL named in a small per-deployment config file, normally a
file in the course's OSU GitLab group, so an allowlist change is one
commit, effective at next boot, and never a 25-device hand-edit; a shared
fleet points at a different course's list (or a merged one) by editing
that one config, not by rebuilding the image. If the fetch fails, the
device uses the copy baked into the image and shows a warning on the
login screen.
- Lockdown: USB mass storage disabled for `student`, enabled for `proctor`
(the emergency collection path); no extra login TTYs.
### WiFi enrollment: the one IT ask
Campus WiFi is WPA2-Enterprise, so 27 Linux devices need either MAC
registration on OSU's device/IoT network or a course service account. This
is the design's single dependency on central IT: one conversation, once,
with Network Services. It is a registration request, not an infrastructure
request (no VLANs, no firewall rules, no imaging), which is what keeps it
small. The credentials live in the read-only image outside the student
account's reach.
---
## 4. The allowlist
| Purpose | Hosts | Notes |
|---|---|---|
| Python docs | `docs.python.org` | exactly this host |
| Rust docs | `doc.rust-lang.org` | **not** `*.rust-lang.org`: `play.rust-lang.org` executes code and `users.rust-lang.org` is a forum |
| Starter repos + allowlist file + course site | OSU GitLab host, course site domain | see the GitLab caveat below |
| Submission | `www.gradescope.com` plus its pinned asset hosts | discover exact CDN hostnames in log-only mode; never wildcard a CDN domain |
| Login | `login.oregonstate.edu`, the Duo API hosts | the SSO chain Gradescope redirects through |
Built empirically: the image ships with a log-only mode (same dnsmasq and
nftables rules, logging instead of dropping); two mock sessions in that mode
produce the exact hostname set, which then gets pinned.
Two caveats, accepted knowingly rather than discovered later:
- **GitLab is a side channel.** Hostname filtering cannot restrict paths, so
allowlisting OSU's GitLab reaches all of it, including snippets and repos
any student can create and share mid-session. At first-year threat level,
with proctors in the room and the oral check asking students to explain
their own code, this is an accepted risk; the tighter alternative (serve
starter packages from the course site and drop GitLab from the session
list) stays available if a term demonstrates abuse.
- **Canvas is recommended off, even for instructors who have no course
website.** Canvas Inbox is a live messaging channel to anyone in any of a
student's courses, it cannot be disabled per course, and hostname
filtering cannot carve it out. The starter-repo pattern removes the last
in-session need for it: the problem spec is the repo's README, so GitLab
delivers everything in and Gradescope carries everything out, and no
website is required either. An instructor who allowlists Canvas anyway
should do it as a knowing decision, accepting the channel and telling the
proctors what to watch for, not as a default.
Operational wrinkle for the runbook: OSU logins require Duo, so phones come
out for the first five minutes to authenticate to Gradescope under proctor
supervision, then go away. Gradescope sessions persist, so this happens once
per session.
---
## 5. Submission and grading flow
Students submit **directly to Gradescope from the seat**, as often as they
like; the last submission before the TA closes the assignment window counts.
Identical workflow to take-home assignments, so recitation day introduces
zero new mechanics. Gradescope's own submission list is the TA dashboard
(including for oral-check sampling: visit students whose code exists), the
hidden tests run in the same Docker autograder used for assignments, and TAs
do the human 20% (style spot-checks, stretch Polya plans) in Gradescope's
rubric view afterward.
Failure ladder, in order:
1. **Gradescope hiccup, WiFi fine:** students keep working; `check` is fully
local. Submit when it recovers.
2. **Classroom AP has a bad day:** a TA phone hotspot is a legitimate
emergency uplink, because enforcement lives on the device, not the
network; the filter rules do not care which network carries the packets.
3. **WiFi gone entirely:** students keep working locally to the end, then
the proctor account USB-collects `submission.zip` files, 25 seats in
about ten minutes, and a TA uploads them from any online machine.
4. **Everything dies:** the paper variant, which every recitation ships with
anyway for DAS and make-up cases.
---
## 6. Weekly content pipeline
Authoring happens in this repository. A `make lab-week-N` target assembles
the week's starter repository and pushes it to the course's OSU GitLab
group. Each starter repo is self-contained: the problem spec as its README,
the twin Python and Rust scaffolds, the visible tests, any data files, and
the `check` and `pack` scripts, all versioned together. The same GitLab
group hosts the device allowlist file. At session start students clone the
week's repo (one command, written on the board). Client images never change
during a term; a tooling fix ships with next week's repo, and an allowlist
change is a git commit plus a reboot.
---
## 7. Session lifecycle (the TA runbook, condensed)
**Before (10 min):** hand out or power on the Pis (they boot clean into
overlay mode), spot-check one seat (docs load, Gradescope loads, a search
engine does not), open the Gradescope assignment window.
**Start (5 min):** students log into Gradescope (phones out for Duo, then
away), clone the week's starter repo, begin.
**During:** students work, `check` locally, upload to Gradescope whenever
they want. TAs proctor, run the sampled oral checks off the Gradescope
submission list, and swap any misbehaving seat (SD card first, whole Pi
second; prior submissions are already on Gradescope, nothing is lost).
**End:** TA closes the Gradescope window; students log out.
**After (10 min):** power cycle (overlay mode discards everything), pack the
tote. Grading happens on Gradescope on its own schedule.
---
## 8. Security and integrity model
Threats, in decreasing order of realism, with the honest residuals:
1. **AI or search access from a seat.** Blocked by the on-device resolver
and firewall, reinforced by the browser policy and the read-only image.
Residual: personal phones, a proctoring matter, as in any exam.
2. **Side channels inside allowed hosts.** GitLab snippets and shared repos
(accepted, documented above), Gradescope regrade-request text boxes (low
bandwidth, visible to instructors). Canvas is kept off precisely to avoid
the biggest of these.
3. **Tampering with the device filter.** Students have no root, the root
filesystem is read-only, and the overlay discards any runtime state at
reboot. Residual: physical attacks (pulling the SD card) are conspicuous
in a proctored room and unproductive (the card holds no secrets beyond
WiFi enrollment, which is revocable).
4. **A student sees the previous section's work.** Overlay mode plus the
between-section power cycle; cross-section problem leakage by
conversation is handled with per-section value variants (cheap under the
family model).
5. **No central logging.** Without a gateway there is no single audit point
for network activity; Gradescope timestamps and the oral check carry the
integrity evidence instead. Accepted: the pedagogy (fresh variants,
explain-your-code) is the deep defense, not packet logs.
6. **Session network dependence.** A graded event relies on campus WiFi and
Gradescope uptime; mitigated by the failure ladder (local `check`, phone
hotspot, USB collection, paper variant).
If a term demonstrates real, repeated circumvention, escalate to section 11.
---
## 9. What must be built, and by whom
| Artifact | Size | Effort |
|---|---|---|
| Golden image build script (pi-gen or shell over Raspberry Pi OS) | small | 4 to 5 days including filter, browser policy, overlay mode, and lockdown testing |
| On-device filter config + boot-time allowlist fetch + log-only mode | config + scripts | 2 to 3 days |
| `check` / `pack` scripts (shipped inside every starter repo) | under 1k lines | 3 days |
| Weekly packaging (`make lab-week-N` to GitLab) | small | 1 to 2 days |
| TA runbook + failure drills | prose | 2 days, then refined each term |
Total: roughly **two person-weeks**, the smallest of the three designs this
document has considered, because there is no server, no gateway, and no
export tooling: Gradescope and GitLab do the serving. Budget-compatible
sourcing: a few weeks of one funded student developer, or an **EECS senior
capstone team** ("build and harden a self-filtering exam fleet" is a real
project with a real adversarial model). Everything lands in this repository
or a sibling repo, MIT licensed, reusable by any course facing the same
GenAI assessment problem.
---
## 10. Rollout plan
- **Term minus one:** buy 5 Pis, build the image, run the filter in log-only
mode through two mock sessions to pin the allowlist, make the WiFi
registration ask, fix what breaks.
- **Pilot term:** one section uses the fleet while others run the paper
variant; compare outcomes, tune the runbook, then buy the remaining seats
with the pilot as evidence for the budget request.
- **Full deployment:** all sections, spares in the tote, documentation
handed off.
---
## 11. Escalation variants
Kept for the case where device-level filtering proves insufficient in
practice. Both reuse the fleet; neither is the starting posture.
- **Filtering gateway:** put the Pis on their own switch behind one
course-owned mini PC running squid in SNI-peek mode (no client
certificates, fails closed). Restores central logging and a single
enforcement point at the cost of owning a switch, a gateway, cabling, and
an uplink. About one extra person-week and $300.
- **Air-gapped island:** unplug the uplink entirely; the gateway becomes a
room server with mirrored docs, a small signed-bundle submission API with
an SQLite audit trail, and a post-session batch upload to Gradescope (or a
local run of the same autograder container with a CSV import to Canvas).
Maximum integrity, maximum friction: mirrored docs instead of live, a
submission path that differs from take-home, and about two more
person-weeks of software. The paper variant remains beneath even this.
---
## 12. Appendix: the BYOD question, answered once
Every budget cycle someone will ask: instead of buying devices, could
students run a script on their own laptops that blocks or monitors sites
during the session? Documented here so the analysis does not have to be
redone.
**Why student-run enforcement cannot work: trust inversion.** The script is
a guest on hardware the adversary owns with administrator rights. It can be
not-run, edited, killed, run inside a virtual machine while the real
browsing happens outside it, or satisfied on one laptop while a second
device sits in the bag. "The script phones home to prove it is running" is
spoofable without TPM-backed remote attestation, which cannot be deployed
across an uncontrolled fleet of personal Windows, macOS, Linux, and ARM
machines. The referee must not run on the player's hardware.
**Why it is also the privacy nightmare it tries to avoid.** A
course-mandated tool with admin rights that inspects or filters network
traffic on personal devices is the commercial-proctoring model (Respondus,
Honorlock) in homemade form: invasive for everyone, legally delicate,
resented by students, and still bypassed by the people it targets. It also
creates breakage liability: firewall-manipulating scripts across hundreds of
personal laptops will corrupt someone's VPN or WiFi configuration, and the
course owns that damage. The cost lands on the honest majority; the benefit
rounds to zero.
**Why BYOD cannot carry the "no AI" claim at all.** The modern threat is
not visiting a chatbot site, which a proctor might notice. It is ambient AI
inside normal tools: editor copilots, OS-level assistants, AI autocomplete.
On a personal machine those are invisible to visual proctoring and
indistinguishable from typing. Blocking them requires controlling the
endpoint, which is the one thing BYOD surrenders by definition.
**The least-bad BYOD variant, for the record.** Safe Exam Browser (free,
open source, non-invasive: it locks the browser rather than monitoring the
machine) pointed at a course-hosted web IDE (code-server), with only the
IDE, the docs, and Gradescope reachable from inside it. Honest accounting:
a real server returns to the architecture (25 concurrent Rust compiles need
a serious box), SEB does not exist for Linux, and students with unsupported
or failing laptops need loaners anyway, so the course ends up owning devices
regardless, just without controlling them. Acceptable as a make-up-session
mechanism or a bridge term before capital arrives; not a primary design.
**Bottom line.** At about $200 per seat, the course-owned fleet is the
cheapest mechanism that actually closes the loop. BYOD's capital savings are
spent back, with interest, in support burden, privacy exposure, and a
certification claim the course can no longer honestly make.
</details>