Reviewed the official APESS 2026 programme against the app as shipped.
The structure held up; several published facts and one scoring bug did not.
Fixes a real scoring defect: AddReview (what judges read) still carried
pre-pivot layer titles - "Reasoning policy", "Action contract", "Failure
modes", "AI-native redesign" - so judges scored "Skills" under the heading
"Reasoning policy". Root cause was two duplicated title lists that drifted
after the domain-node reshape, so both surfaces now render from a single
ADD_LAYERS constant and cannot diverge again.
Also removes dead classifier telemetry from the graded artifact: the ADD
printed "N frames - N nominal - N anomalous - N critical", but nothing has
called recordEvent since the simulator was deleted, so those counters were
permanently zero - and the vocabulary predates the pivot.
Corrects the schedule. The app advertised the lecture at 14:00, inside the
hackathon block; the programme puts it at 10:45-12:15 as a separate morning
session, with the hackathon 14:00-19:00. Moving it out recovers an hour,
which the new timings spend on the sensor work rather than setup.
Reframes the session around designing for failure, per the workshop premise
and the school's "proactive resilient systems" theme:
L3 becomes Policies & failure - which way each failure fails, with the
governing rule that a fail-safe must never quietly report "nominal"
L4 becomes where each decision runs - the degradation path, cloud to
on-board to fully offline, not just the happy path
L5 gains what the loop does when a cycle fails - stale reads, missed
ticks, partial data
Students arrive having spent a week on their own sensor work with these
boards, so the hands-on now points the agent at hardware they already
wired (discover the bus, read it, act on a threshold) instead of only
scrolling text, and the framing invites the domain they are already
measuring. The domain stays free-text.
Adds backup-uno-q.sh: boards are reflashed on the day and a week of
student work is irreplaceable. Denylist rather than allowlist, because we
cannot know where a given team put their data; verifies the archive is
readable and non-trivial before reporting success. Deliberately keeps the
App Lab examples dir - stock, but exactly what someone would edit in place.
Verified end to end: byte-identical restore of a real sketch.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
110 lines
4.6 KiB
Bash
Executable File
110 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Back up a student's Uno Q before reflashing it for the APESS workshop.
|
|
#
|
|
# ./backup-uno-q.sh <adb-serial> [team-label] [outdir]
|
|
#
|
|
# Students arrive having spent a week doing their own sensor work on these
|
|
# boards. THAT WORK IS IRREPLACEABLE — this runs before provision-uno-q.sh and
|
|
# must succeed before anything is overwritten.
|
|
#
|
|
# Strategy: DENYLIST, not allowlist. We do not know where a given team put
|
|
# their data (App Lab project dir, a loose CSV, a sketch folder), so we capture
|
|
# all of /home/arduino and exclude only what we can reproduce ourselves:
|
|
# the GGUF models, the Arduino core cache, our llama/zeroclaw binaries, the
|
|
# embedded SPA, and build output. On a reference board that leaves ~1-2 MB;
|
|
# a board with a week of logged data will be larger but still quick.
|
|
#
|
|
# The archive is VERIFIED READABLE before the script reports success — an
|
|
# unverified backup is not a backup.
|
|
set -euo pipefail
|
|
|
|
SERIAL="${1:?usage: backup-uno-q.sh <adb-serial> [team-label] [outdir]}"
|
|
LABEL="${2:-$SERIAL}"
|
|
OUTDIR="${3:-./backups}"
|
|
|
|
a() { adb -s "$SERIAL" "$@"; }
|
|
|
|
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
NAME="unoq-${LABEL}-${STAMP}"
|
|
REMOTE_TAR="/tmp/${NAME}.tar.gz"
|
|
LOCAL_TAR="${OUTDIR}/${NAME}.tar.gz"
|
|
LOCAL_MANIFEST="${OUTDIR}/${NAME}.manifest.txt"
|
|
|
|
# Reproducible — ours, or regenerable. Everything else is theirs and is kept.
|
|
#
|
|
# DELIBERATELY NOT EXCLUDED: ~/.local/share/arduino-app-cli/examples (~37M).
|
|
# Those are Arduino's stock App Lab examples, so in principle reproducible —
|
|
# but several of them (real-time-accelerometer, anomaly-detection,
|
|
# air-quality-monitoring) are exactly what a student doing sensor work would
|
|
# open and then edit IN PLACE. Excluding them would silently discard a week of
|
|
# work to save 25MB. We keep them. A backup is insurance, not a size contest.
|
|
EXCLUDES=(
|
|
'--exclude=./models' # 1.5G of GGUF weights — we push these
|
|
'--exclude=./.arduino15' # ~570M Arduino cores/index — arduino-cli refetches
|
|
'--exclude=./llama' # llama.cpp binaries — we push these
|
|
'--exclude=./web-dist' # embedded SPA build — we push this
|
|
'--exclude=./.cache' # regenerable
|
|
'--exclude=./lost+found'
|
|
'--exclude=./zeroclaw' # the daemon binary...
|
|
'--exclude=./zeroclaw.*' # ...and its .bak copies
|
|
'--exclude=./llama8083.log'
|
|
'--exclude=./zc-daemon.log'
|
|
'--exclude=./zc-supervisor.log'
|
|
'--exclude=*/build' # sketch build output — recompiled on demand
|
|
)
|
|
|
|
mkdir -p "$OUTDIR"
|
|
|
|
echo "==> [1/5] board reachable?"
|
|
a shell 'echo ok' >/dev/null 2>&1 || { echo " FAIL: board $SERIAL not reachable over adb"; exit 1; }
|
|
a shell 'test -d /home/arduino' >/dev/null 2>&1 || { echo " FAIL: /home/arduino missing"; exit 1; }
|
|
echo " $SERIAL ok"
|
|
|
|
echo "==> [2/5] inventory what will be captured"
|
|
a shell '
|
|
cd /home/arduino || exit 1
|
|
echo " user-work directories:"
|
|
for d in sketches Arduino ArduinoApps .arduino-bricks; do
|
|
if [ -e "$d" ]; then printf " %-18s %s\n" "$d" "$(du -sh "$d" 2>/dev/null | cut -f1)"; fi
|
|
done
|
|
echo " data-shaped files (top 10 by size):"
|
|
find . -maxdepth 4 \( -name "*.csv" -o -name "*.tsv" -o -name "*.dat" -o -name "*.json" -o -name "*.txt" -o -name "*.py" \) \
|
|
-not -path "./.arduino15/*" -not -path "./.cache/*" -not -path "*/build/*" -not -path "./.zeroclaw/*" \
|
|
-printf " %s\t%p\n" 2>/dev/null | sort -rn | head -10
|
|
echo " (none found)"
|
|
' 2>/dev/null || true
|
|
|
|
echo "==> [3/5] archive on board"
|
|
a shell "cd /home/arduino && tar czf '$REMOTE_TAR' ${EXCLUDES[*]} . 2>/dev/null; echo done" >/dev/null
|
|
SIZE="$(a shell "du -h '$REMOTE_TAR' 2>/dev/null | cut -f1" | tr -d '\r\n ')"
|
|
echo " archive: $SIZE"
|
|
|
|
echo "==> [4/5] pull + verify"
|
|
a pull "$REMOTE_TAR" "$LOCAL_TAR" >/dev/null 2>&1 || { echo " FAIL: could not pull archive"; exit 1; }
|
|
|
|
# An unverified backup is not a backup: list the archive and require real content.
|
|
if ! tar tzf "$LOCAL_TAR" > "$LOCAL_MANIFEST" 2>/dev/null; then
|
|
echo " FAIL: archive is not readable — DO NOT REFLASH THIS BOARD"
|
|
exit 1
|
|
fi
|
|
ENTRIES="$(wc -l < "$LOCAL_MANIFEST" | tr -d ' ')"
|
|
if [ "$ENTRIES" -lt 5 ]; then
|
|
echo " FAIL: archive has only $ENTRIES entries — suspiciously empty, DO NOT REFLASH"
|
|
exit 1
|
|
fi
|
|
echo " verified: $ENTRIES entries, manifest at $LOCAL_MANIFEST"
|
|
|
|
echo "==> [5/5] clean up board temp"
|
|
a shell "rm -f '$REMOTE_TAR'" >/dev/null 2>&1 || true
|
|
|
|
cat <<EOF
|
|
|
|
BACKUP OK — safe to reflash $SERIAL
|
|
archive : $LOCAL_TAR ($SIZE)
|
|
manifest: $LOCAL_MANIFEST
|
|
|
|
restore (after reflash):
|
|
adb -s $SERIAL push $LOCAL_TAR /tmp/restore.tar.gz
|
|
adb -s $SERIAL shell 'cd /home/arduino && tar xzf /tmp/restore.tar.gz && rm /tmp/restore.tar.gz'
|
|
EOF
|