Stops out-of-date lab hosts from polluting the dataset with episodes
generated by buggy code. The valid-commits set mirrors the maintainer's
working clone on the Pi automatically — when the maintainer pulls or
pushes a new commit, the receiver picks it up within the 5-second
cache TTL with no service restart.
Receiver changes:
- receiver/version_gate.py (new): VersionGate(repo_path, window).
Each check() consults a frozenset of the last `window` commit
hashes from `git -C <repo> log --format=%H -n <window>`, refreshed
every 5s under a lock. Resilient to transient git failure (keeps
prior cache so a flaky `git` doesn't lock out every shipper).
- receiver/app.py: PUT extracts X-Cis490-Code-Commit; gate.check()
before ingest. Rejects with:
400 + remediation if header missing or malformed
412 + remediation + your_commit + head_commit if not in window
Remediation block is verbatim copy-pasteable into the lab-host
shell:
cd /opt/cis490 && sudo -u cis490 git pull origin main
sudo /opt/cis490/scripts/install-lab-host.sh
sudo systemctl restart cis490-orchestrator
- receiver/store.py: ingest_stream takes commit kwarg, stamps it on
the index.jsonl row (new optional field). Backfilled rows from
index_backfill.py also pull commit out of meta.json.
- receiver/config.py + etc/receiver.toml.example: new [version_gate]
section. enabled=true, repo_path=/home/max/cis490, window=100 by
default. Enabled toggle exists for emergency disable-and-collect.
Shipper changes:
- shipper/transport.py: ship_tarball() takes commit kwarg, sends
X-Cis490-Code-Commit header. 412 maps to status='fatal' so the
queue doesn't infinite-retry — operator must pull and reinstall
before the next ship will succeed.
- shipper/queue.py: reads meta.json::code_version.commit per
episode, passes through. On 412, logs the receiver's full
remediation block at ERROR level so journalctl on the lab host
shows exactly what to run.
Tests: 9 in test_version_gate (including 2 end-to-end via
starlette.testclient), 2 cover the boundary where new commits land
mid-cache and where missing-repo gracefully keeps prior cache.
157/157 total.
Index schema: existing rows stay valid (commit field is optional
on read). New rows from receiver-direct AND from index_backfill.py
include commit.
25 lines
945 B
Text
25 lines
945 B
Text
# CIS490 receiver — copy to /etc/cis490/receiver.toml and edit.
|
|
|
|
listen_addr = "127.0.0.1:8444"
|
|
store_root = "/var/lib/cis490/episodes"
|
|
incoming_root = "/var/lib/cis490/incoming"
|
|
index_path = "/var/lib/cis490/index.jsonl"
|
|
|
|
[limits]
|
|
max_episode_bytes = 268_435_456 # 256 MiB
|
|
|
|
# Optional: require a bearer token on every PUT.
|
|
# In production, prefer mTLS via Caddy (see docs/deploy.md). The bearer token
|
|
# is for dev testing or as a belt-and-suspenders alongside mTLS.
|
|
# [auth]
|
|
# bearer_token = "REPLACE_ME_WITH_SECRET"
|
|
|
|
# Code-version gate. Every PUT must carry X-Cis490-Code-Commit and that
|
|
# commit must be in the receiver's allow-list. The allow-list is the
|
|
# last `window` commits of `repo_path` (auto-refreshed every 5s, so a
|
|
# `git pull` on the Pi makes new commits acceptable instantly). This
|
|
# keeps episodes from out-of-date lab hosts out of the index.
|
|
[version_gate]
|
|
enabled = true
|
|
repo_path = "/home/max/cis490"
|
|
window = 100
|