This shows you the differences between two versions of the page.
|
rasb:lab:hackathon [2026/07/17 19:36] jan.vaduva [Challenge status] |
rasb:lab:hackathon [2026/07/18 02:15] (current) cezar.zlatea |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Hackathon: GhostTag Apocalypse ====== | + | ====== GhostTag Apocalypse ====== |
| - | ===== Scope ===== | + | ===== Challenge overview ===== |
| - | GhostTag Apocalypse is a firmware, security, and BLE simulation challenge. | + | The Internet is unavailable, GPS is jammed, and only a sparse network of BLE |
| - | Students implement the protocol of an nRF52840 tag that must be found by | + | gateways remains operational. Your task is to complete the firmware protocol |
| - | authorized gateways without transmitting a stable identifier. | + | for battery-powered rescue tags that must remain discoverable without |
| + | broadcasting a permanent identity. | ||
| - | The challenge combines: | + | The protocol must survive unstable power, reject forged and replayed packets, |
| + | limit flash wear, and remain within a strict energy budget. The final system is | ||
| + | tested as a multi-device nRF52840 swarm in Renode. | ||
| - | * C code for the protocol; | + | This is an approximately 8-hour firmware challenge. You will work only in: |
| - | * SipHash-2-4 for ephemeral identity and authentication; | + | |
| - | * Zephyr firmware for ''nrf52840dk/nrf52840''; | + | |
| - | * Renode simulation with multiple boards and a positional BLE environment; | + | |
| - | * Automated validation, HTML report, and execution in Kubernetes. | + | |
| - | This is a hackathon challenge, not a complete protocol for a real product. | + | <code text> |
| - | It does not fully cover secure provisioning, key protection, replay attacks, | + | firmware/ghost_protocol.c |
| - | radio certification, or a complete privacy evaluation. | + | </code> |
| - | ===== Challenge Status ===== | + | The public API, application code, gateway firmware, simulation scenario, and |
| + | validation rules are fixed contracts. | ||
| - | ^ Component ^ Status ^ Remarks ^ | + | ===== Read this documentation thoroughly ===== |
| - | | Challenge code | Ready | Local commit ''29a2b3d'' on branch ''nrf52840-swarm/ghosttag-apocalypse'' | | + | |
| - | | Student starter | Ready | Contains exactly 3 TODOs in ''firmware/ghost_protocol.c'' | | + | |
| - | | Native tests | Ready | SipHash vectors, rotation, tamper, wrong seed, and leakage check | | + | |
| - | | Zephyr build | Ready | Tag and gateway for ''nrf52840dk/nrf52840'' | | + | |
| - | | Renode simulation | Ready | Tags, gateways, clones, deterministic positions and BLE range | | + | |
| - | | Validator and report | Ready | Produces ''validation.json'' and ''output/report.html'' | | + | |
| - | | Platform integration | Ready in repository | Manifest exists for the scenario and the Showcase Job | | + | |
| - | | Local reference validation | Passed | Last local evidence: 6/6 tags, 6/6 rotated, 75 rogue packets rejected | | + | |
| - | | Kubernetes cluster | Healthy | All 3 nodes are ''Ready'', no failed pods, public health 200 | | + | |
| - | | Active student scenario | No | Platform still uses ''rp2040-sensor-filter-tinyml'' | | + | |
| - | | GhostTag image in registry | Unconfirmed | Image must be rebuilt, pushed, and verified before activation | | + | |
| - | | GhostTag showcase | Not running | Job ''ghosttag-apocalypse'' is absent | | + | |
| - | ===== What Is Already Implemented ===== | + | <note important> |
| + | Do not start coding before reading every file in this section, in order. The | ||
| + | fixed C header and the complete problem statement are authoritative. | ||
| + | </note> | ||
| - | ==== Radio Contract ==== | + | - ''problem/problem.md'' -- the complete protocol, journal, recovery, energy, |
| + | and acceptance contract. | ||
| + | - ''firmware/include/ghost_protocol.h'' -- all public types, constants, | ||
| + | callbacks, and function signatures. | ||
| + | - ''firmware/ghost_protocol.c'' -- the starter implementation and the six | ||
| + | TODOs you must complete. | ||
| + | - ''firmware/tests/test_protocol.c'' -- the fast public tests and the expected | ||
| + | storage behavior. | ||
| + | - ''renode/README.md'' -- the files exposed for the simulation and the | ||
| + | reset/attacker sequence. | ||
| + | - ''renode/generate_swarm_resc.py'' and ''renode/run_test.sh'' -- how the swarm | ||
| + | is generated, executed, and passed to validation. | ||
| + | - ''docs/renode_primer.md'' -- the nRF52840 machines, BLE medium, provisioning | ||
| + | region, persistent journal region, and UART evidence. | ||
| + | - ''docs/exercise_guide.md'' -- the recommended solution order and the three | ||
| + | feedback layers. | ||
| - | The BLE payload is exactly 28 bytes: | + | The values and byte layouts below are a summary. If this page and the fixed C |
| + | header differ, follow the header and ''problem/problem.md''. | ||
| - | ^ Bytes ^ Content ^ | + | ===== What you must implement ===== |
| - | | ''0..1'' | Company ID ''0xF00D'' | | + | |
| - | | ''2'' | Protocol version ''2'' | | + | |
| - | | ''3'' | Flags | | + | |
| - | | ''4..7'' | Rotation epoch, little-endian | | + | |
| - | | ''8..11'' | Public sector, little-endian | | + | |
| - | | ''12..19'' | Authenticated ephemeral identifier | | + | |
| - | | ''20..27'' | Authentication tag for bytes ''0..19'' | | + | |
| - | The packet does not contain the stable ID of the device. The radio identity | + | Complete the six TODOs in ''firmware/ghost_protocol.c'': |
| - | changes at every epoch, and the gateway verifies the packet using the small | + | |
| - | authorized key space of the sector. | + | |
| - | ==== Student Environment ==== | + | - canonical SipHash-2-4, including partial final message blocks; |
| + | - the two-lane persistent key-ratchet step; | ||
| + | - construction of the protocol-v3 packet, EID, and domain-separated MAC; | ||
| + | - strict packet verification with constant-time EID and MAC comparison; | ||
| + | - boot-time journal scan, validation, recovery, and lease reservation; | ||
| + | - payload emission with reservation before lease exhaustion and exactly one | ||
| + | in-memory epoch advance. | ||
| - | The student receives in the browser: | + | Keep the existing function signatures. Do not change the packet size, |
| + | constants, storage callbacks, or public structures to work around the | ||
| + | contract. | ||
| - | * ''firmware/'' - Editable source; | + | ===== Packet contract ===== |
| - | * ''problem/'' - Challenge statement; | + | |
| - | * ''output/'' - Logs, results, and HTML report; | + | |
| - | * Run button - Starts the validation pipeline; | + | |
| - | * Results panel - Displays ''output/report.html''. | + | |
| - | Data is kept on the user's PVC. A scenario change archives the previous | + | Every BLE manufacturer payload is exactly 28 bytes: |
| - | workspace. PVCs must not be deleted during the challenge or at rollback. | + | |
| - | ==== Automated Pipeline ==== | + | ^ Bytes ^ Field ^ |
| + | | ''0..1'' | company ID ''0xF00D'', little-endian | | ||
| + | | ''2'' | protocol version ''3'' | | ||
| + | | ''3'' | flags | | ||
| + | | ''4..7'' | persistent ratchet epoch, little-endian | | ||
| + | | ''8..11'' | public city sector, little-endian | | ||
| + | | ''12..19'' | keyed ephemeral identifier | | ||
| + | | ''20..27'' | keyed MAC over bytes ''0..19'' | | ||
| - | Every Run executes, in order: | + | A stable tag identifier must never appear on air in either byte order. |
| - | - Native C17 compilation with ''-Wall -Wextra -Werror''; | + | ==== Key ratchet ==== |
| - | - Known SipHash vectors and tamper tests; | + | |
| - | - Zephyr build for the tag firmware; | + | |
| - | - Zephyr build for the trusted gateway; | + | |
| - | - Renode sector generation; | + | |
| - | - Starting the nRF52840 machines in the BLE environment; | + | |
| - | - Collecting UART logs from gateways; | + | |
| - | - Fleet validation and HTML report generation. | + | |
| - | The validator uses only the traffic observed by gateways. It does not read | + | The provided ''seed_to_key'' function derives the epoch-0 key. To derive the |
| - | the student's C variables and does not inspect the tags' memory. | + | key for ''next_epoch'', compute two SipHash outputs using the current 16-byte |
| + | key: | ||
| - | ==== Scenario Scale ==== | + | <code text> |
| - | + | 0x52 || next_epoch_le32 || lane | |
| - | ^ Mode ^ Tags ^ Gateways ^ Rogue Clones ^ Total Boards ^ | + | |
| - | | Local smoke | 6 | 3 | 2 | 11 | | + | |
| - | | Normal student run | 12 | 3 | 2 | 17 | | + | |
| - | | One showcase sector | 16 | 3 | 2 | 21 | | + | |
| - | | Complete showcase | 192 | 36 | 24 | 252 | | + | |
| - | + | ||
| - | The showcase uses 12 independent sectors and runs a maximum of 4 in parallel. | + | |
| - | Each sector has a limit of ''3 CPU'' and ''2560 MiB'' RAM. These values were | + | |
| - | chosen after real testing; they must not be increased without a new capacity test. | + | |
| - | + | ||
| - | ==== Live Infrastructure ==== | + | |
| - | + | ||
| - | The platform was recovered and hardened after the power incident: | + | |
| - | + | ||
| - | * CoreDNS is functioning and resolving internal and external names; | + | |
| - | * Cloudflare tunnel uses token from mounted file, not token in arguments; | + | |
| - | * Tunnel credential was rotated; | + | |
| - | * Platform frontend is healthy; | + | |
| - | * Provisioner selects only ''Ready=True'' and schedulable workers; | + | |
| - | * All 3 nodes have systemd in ''running'' state; | + | |
| - | * The infinite block in ''plymouth-quit-wait.service'' was removed; | + | |
| - | * systemd journals are persistent on all nodes. | + | |
| - | + | ||
| - | <hidden> | + | |
| - | Platform changes are in commit ''f5081ea'' from | + | |
| - | ''/home/pwd/eg106-platform'' on ''cloud_s1''. | + | |
| - | + | ||
| - | </hidden> | + | |
| - | ===== What Students Need To Do ===== | + | |
| - | + | ||
| - | ==== Workspace ==== | + | |
| - | + | ||
| - | Students only modify: | + | |
| - | + | ||
| - | <code> | + | |
| - | firmware/ghost_protocol.c | + | |
| </code> | </code> | ||
| - | <note warning> | + | Use lane ''0'' for new key bytes ''0..7'' and lane ''1'' for bytes ''8..15''. |
| - | The header, packet size, CMake, gateway, UART format, epoch duration, or | + | Compute both outputs before replacing the current key. |
| - | testing harness must not be changed.</note> | + | |
| - | ==== TODO 1 - SipHash-2-4 ==== | + | ==== EID and MAC ==== |
| - | The student implements ''ghost_siphash24'': | + | For a packet at epoch ''e'': |
| - | * 128-bit key; | + | * ratchet from epoch 0 through ''e'' to obtain the epoch key; |
| - | * 64-bit result; | + | * compute the EID with SipHash over |
| - | * Canonical SipHash-2-4 construction; | + | ''0x45 || epoch_le32 || sector_le32''; |
| - | * Little-endian read; | + | * copy the epoch key and XOR bytes ''0'', ''7'', ''8'', and ''15'' with |
| - | * Correct support for lengths that are not a multiple of 8; | + | ''0x4d'', ''0x41'', ''0x43'', and ''0xa7'' to obtain the MAC key; |
| - | * Result identical to the known vectors included in tests. | + | * authenticate packet bytes ''0..19'' with the MAC key; |
| + | * reject an incorrect company ID, version, EID, MAC, or an epoch above | ||
| + | ''1,000,000''; | ||
| + | * compare all EID and MAC bytes without returning on the first mismatch. | ||
| - | An approximate implementation or with wrong byte order will not pass. | + | ===== Crash-safe persistent state ===== |
| - | ==== TODO 2 - Building The Payload ==== | + | The tag has two 4096-byte journal pages. Erased bytes are ''0xff''; a write may |
| + | only change bits from 1 to 0; an erase operates on one complete page. | ||
| - | The student completes ''ghost_build_payload'': | + | Each journal slot is a 40-byte record: |
| - | * Keeps the existing header and layout; | + | ^ Bytes ^ Field ^ |
| - | * Derives the ephemeral identity using domain ''0x45'', the epoch, and the sector; | + | | ''0..3'' | magic ''0x47535452'' | |
| - | * Writes the ephemeral identity in bytes ''12..19''; | + | | ''4..7'' | generation | |
| - | * Derives the MAC with a different key domain than the one used for EID; | + | | ''8..11'' | future resume epoch | |
| - | * Authenticates bytes ''0..19''; | + | | ''12..15'' | cumulative page-erase count | |
| - | * Writes the MAC in bytes ''20..27''; | + | | ''16..31'' | ratcheted key for the resume epoch | |
| - | * Does not copy ''device_seed'' into the packet. | + | | ''32..35'' | IEEE CRC32 over bytes ''0..31'' | |
| + | | ''36..39'' | commit word ''0xC01117ED'' | | ||
| - | The same raw key must not be reused directly for both roles. | + | Write the 36-byte body first. Write the commit word in a separate final storage |
| + | operation. A missing commit, bad CRC, bad magic, or partially written body is | ||
| + | not a valid record. | ||
| - | ==== TODO 3 - Verifying The Payload ==== | + | The journal reserves leases of 16 epochs: |
| - | The student completes ''ghost_verify_payload'' so that it rejects: | + | * before the first advertisement, durably reserve epochs ''0..15'' by storing |
| + | the resume state for epoch ''16''; | ||
| + | * after a clean reboot, resume at the stored future epoch and reserve the next | ||
| + | lease before transmitting; | ||
| + | * if a non-erased invalid slot follows the newest valid record, skip two full | ||
| + | leases conservatively before reserving again; | ||
| + | * append after the newest record; | ||
| + | * when one page is full, erase the other page and write its first record; | ||
| + | * never erase the page holding the newest valid state before its successor is | ||
| + | committed. | ||
| - | * Wrong company ID; | + | These rules must prevent epoch reuse after a torn body write, torn commit, |
| - | * Wrong version; | + | corrupted CRC, page rollover, or power loss at any storage operation. |
| - | * Wrong ephemeral identity; | + | |
| - | * Wrong MAC; | + | |
| - | * Flags modified after signing; | + | |
| - | * Any modification to authenticated bytes; | + | |
| - | * Verification with an unauthorized seed; | + | |
| - | * Clone packets that only respect the protocol format. | + | |
| - | The verification must reconstruct the expected values, don`t accept a packet | + | ===== Energy and endurance limits ===== |
| - | just because it has the correct size and header. | + | |
| - | ==== Recommended Student Workflow ==== | + | Runtime energy is calculated as: |
| - | - Read ''problem/problem.md'' and the comments for the 3 TODOs; | + | <code text> |
| - | - Implement and verify SipHash first; | + | energy = flash_writes * 8 + page_erases * 40 + advertisements |
| - | - Implement EID and MAC; | + | </code> |
| - | - Implement verification; | + | |
| - | - Run the challenge using the Run button; | + | |
| - | - Fix native errors first, then Zephyr errors; | + | |
| - | - Analyze Renode logs only after builds pass; | + | |
| - | - Open or refresh the Results panel after the run finishes. | + | |
| - | ==== Success Criteria For Students ==== | + | One committed record requires two writes: body, then commit. The power-cut |
| + | simulation must use no more than 80 energy units. | ||
| - | A solution is accepted only if: | + | The endurance tests generate 2000 sequential payloads. A passing solution uses |
| + | at most 252 flash writes and exactly one page erase. Persisting every packet | ||
| + | cannot pass these limits; lease reservation is required. | ||
| - | * All SipHash tests pass; | + | ===== Simulation ===== |
| - | * All 3 gateways start; | + | |
| - | * All authorized tags are observed; | + | |
| - | * Each tag changes its radio identity; | + | |
| - | * Clone traffic is rejected; | + | |
| - | * There are no fatal errors in the firmware; | + | |
| - | * The validator displays ''GHOST_VALIDATION passed=1''; | + | |
| - | * The run finishes with ''GHOSTTAG FLEET SURVIVED THE APOCALYPSE''. | + | |
| - | Compiling without errors is not enough. | + | The complete run starts: |
| - | <hidden> | + | * 6 authorized nRF52840 tags; |
| - | ===== What We Need To Do As Organizers ===== | + | * 3 trusted observer gateways; |
| + | * 1 unregistered clone; | ||
| + | * 1 attacker replaying a captured, correctly authenticated packet. | ||
| - | ==== Before The Challenge ==== | + | At virtual second 3, Renode resets tag 1 while preserving its nonvolatile |
| + | journal. The replay attacker then transmits an old epoch-0 packet from a | ||
| + | different BLE address. | ||
| - | - Check the cluster, frontend, registry, and workers; | + | A correct system must: |
| - | - Rebuild the GhostTag image from the correct branch; | + | |
| - | - Run the smoke test with the reference implementation and 6 tags; | + | |
| - | - Do not continue if the final success marker is missing; | + | |
| - | - Explicitly push the tag | + | |
| - | ''nrf52840-swarm-ghosttag-apocalypse'' to the registry; | + | |
| - | - Verify the image manifest directly in the registry or by pulling on a node; | + | |
| - | - Run server-side dry-run for both Kubernetes manifests; | + | |
| - | - Save the active scenario ConfigMap for rollback; | + | |
| - | - Activate ''k8s/platform/active-scenario.yaml''; | + | |
| - | - Test with a disposable user. | + | |
| - | The disposable test must demonstrate both directions: | + | * discover all six authorized tags; |
| + | * observe identity rotation for every tag; | ||
| + | * reject the unregistered clone; | ||
| + | * reject the captured replay; | ||
| + | * recover tag 1 without reusing an epoch; | ||
| + | * remain within the runtime energy budget; | ||
| + | * finish without firmware fatal errors. | ||
| - | * The starter is properly seeded and fails cleanly; | + | ===== Recommended workflow ===== |
| - | * The reference implementation passes completely; | + | |
| - | * The report appears in Results; | + | |
| - | * Old files are archived upon scenario change. | + | |
| - | Complete operational commands are in ''docs/HACKATHON_RUNBOOK.md''. | + | ^ Goal ^ |
| + | | Read every contract and pass all SipHash vectors. | | ||
| + | | Implement ratchet, packet generation, EID, MAC, and verification. | | ||
| + | | Implement record encoding, CRC32, scanning, append, and leases. | | ||
| + | | Handle torn writes, corruption, reboot, and page rollover. | | ||
| + | | Verify flash-wear and energy bounds. | | ||
| + | | Run Zephyr and Renode; diagnose the complete swarm. | | ||
| - | ==== At The Beginning Of The Session ==== | + | Use the fastest feedback first. Do not repeatedly run the full swarm while the |
| + | native protocol suite still fails. | ||
| - | We must clearly explain: | + | ===== Understanding the Run result ===== |
| - | * The privacy problem: a stable ID allows tracking the person; | + | The Run action has three gates: |
| - | * Why epoch, EID, and MAC exist; | + | |
| - | * Why the trusted gateway searches within the authorized fleet; | + | |
| - | * What the student controls and what the harness controls; | + | |
| - | * Why rogue clones exist; | + | |
| - | * Which is the only editable file; | + | |
| - | * How to read the three feedback levels. | + | |
| - | We do not provide the reference implementation and do not expose fleet seeds. | + | - native C17 tests for cryptography, packet integrity, persistence, recovery, |
| - | </hidden> | + | corruption handling, and endurance; |
| + | - Zephyr builds for the nRF52840 tag and trusted gateway; | ||
| + | - an 8-second Renode swarm followed by validation and report generation. | ||
| - | ==== During The Challenge ==== | + | The complete success output includes lines equivalent to: |
| - | We monitor: | + | <code text> |
| - | + | PROTOCOL_CONTRACT_TESTS failures=0 | |
| - | * Node and pod health; | + | Renode exit status: 0 |
| - | * The Job queue for student runs; | + | GHOST_VALIDATION passed=1 ... tags=6/6 rotated=6/6 ... recovered=1 energy=.../80 |
| - | * Image pull errors; | + | >>> GHOSTTAG FLEET SURVIVED THE APOCALYPSE <<< |
| - | * CPU, RAM, and ephemeral storage usage; | + | </code> |
| - | * Frontend and ingress availability; | + | |
| - | * Participants' PVCs; | + | |
| - | * Renode execution times. | + | |
| - | + | ||
| - | We help students with compilation errors, platform usage, and interpreting | + | |
| - | the report. We do not implement TODOs for them. | + | |
| - | + | ||
| - | ==== Final Showcase ==== | + | |
| - | + | ||
| - | The showcase is optional and separate from student runs. | + | |
| - | + | ||
| - | Before launching: | + | |
| - | + | ||
| - | * Check requested resources on both workers; | + | |
| - | * Confirm there is at least one healthy worker and enough space for the | + | |
| - | approx. 3.76 GB image; | + | |
| - | * Keep ''parallelism: 4''; | + | |
| - | * Do not delete pods or student PVCs to make room. | + | |
| - | + | ||
| - | Launch ''k8s/showcase/indexed-job.yaml'' and monitor all 12 completions. | + | |
| - | The showcase is successful only if all 12 sectors finish successfully and | + | |
| - | each log contains ''GHOST_VALIDATION passed=1''. | + | |
| - | + | ||
| - | ==== After The Challenge Or At Rollback ==== | + | |
| - | + | ||
| - | * Only delete the Showcase Job if it needs to be stopped; | + | |
| - | * Restore the saved ConfigMap if reverting to the previous scenario; | + | |
| - | * Do not delete student PVCs; | + | |
| - | * Keep reports and logs useful for evaluation; | + | |
| - | * Check the public health endpoint again; | + | |
| - | * Document any cluster incident or observed capacity limit. | + | |
| - | + | ||
| - | ===== Definition Of Done For Organizers ===== | + | |
| - | + | ||
| - | The challenge can be opened to students when all points are true: | + | |
| - | * [ ] GhostTag image is built from the desired commit; | + | After the run, refresh the Results panel and inspect ''output/report.html''. |
| - | * [ ] Reference smoke test passes; | + | When a check fails, read the first failing gate and its log before changing |
| - | * [ ] Image tag is confirmed in the registry; | + | code. |
| - | * [ ] All required nodes are ''Ready''; | + | |
| - | * [ ] Frontend and DNS are healthy; | + | |
| - | * [ ] Manifest dry-run passes; | + | |
| - | * [ ] Previous scenario state is saved; | + | |
| - | * [ ] GhostTag scenario is active; | + | |
| - | * [ ] Starter fails in a controlled manner for the disposable user; | + | |
| - | * [ ] Reference passes for the disposable user; | + | |
| - | * [ ] HTML report loads in Results; | + | |
| - | * [ ] Rollback is prepared; | + | |
| - | * [ ] Reference solution is not accessible to participants. | + | |
| - | At the time of writing, the first two remaining operational actions are build/push | + | ===== Completion checklist ===== |
| - | for the image and controlled activation of the scenario. The showcase must not | + | |
| - | be launched before the disposable test. | + | |
| - | ===== Important Files ===== | + | Before considering the challenge complete, confirm that: |
| - | ^ File ^ Role ^ | + | * all six TODOs have real implementations; |
| - | | ''firmware/ghost_protocol.c'' | The only file edited by the student | | + | * all 64 SipHash vectors pass; |
| - | | ''firmware/include/ghost_protocol.h'' | Wire contract and API | | + | * the exact ratchet and 28-byte packet vectors pass; |
| - | | ''firmware/tests/test_protocol.c'' | Public native tests | | + | * malformed, tampered, forged, and unreasonable packets are rejected; |
| - | | ''ide/problem/problem.md'' | Full statement displayed in the browser | | + | * fresh boot reserves a lease before any packet is emitted; |
| - | | ''support/gateway/'' | Trusted gateway | | + | * every recovery path avoids epoch reuse; |
| - | | ''docker/scripts/build_firmware.sh'' | Native tests and Zephyr builds | | + | * torn and corrupted records remain invalid; |
| - | | ''docker/scripts/generate_swarm_resc.py'' | Renode topology generation | | + | * 2000 payloads stay within the write and erase limits; |
| - | | ''docker/scripts/validate_swarm.py'' | Automated acceptance criteria | | + | * both Zephyr applications build; |
| - | | ''docker/scripts/generate_report.py'' | HTML report | | + | * the Renode validator reports all six tags seen and rotated; |
| - | | ''k8s/platform/active-scenario.yaml'' | Scenario activation for students | | + | * clone rejection, replay rejection, recovery, energy, and fatal-error checks |
| - | | ''k8s/showcase/indexed-job.yaml'' | 252-board showcase | | + | all pass. |
| - | | ''docs/HACKATHON_RUNBOOK.md'' | Operational procedure and rollback | | + | |
| - | | ''docs/instructor_notes.md'' | Recommended structure for the 3-hour session | | + | |
| - | <hidden> | ||
| - | | ''reference/firmware/'' | Organizers' solution | | ||
| - | </hidden> | ||