<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ashutosh Mishra</title>
    <description>The latest articles on DEV Community by Ashutosh Mishra (@ashutosh_mishra).</description>
    <link>https://dev.to/ashutosh_mishra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4110303%2F49cadb94-7aac-48af-9a3e-42981d1fe18c.png</url>
      <title>DEV Community: Ashutosh Mishra</title>
      <link>https://dev.to/ashutosh_mishra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashutosh_mishra"/>
    <language>en</language>
    <item>
      <title>7/7 PASS: The Bugs My Acceptance Checker Could Never See</title>
      <dc:creator>Ashutosh Mishra</dc:creator>
      <pubDate>Tue, 06 Oct 2026 06:11:51 +0000</pubDate>
      <link>https://dev.to/ashutosh_mishra/77-pass-the-bugs-my-acceptance-checker-could-never-see-1hcl</link>
      <guid>https://dev.to/ashutosh_mishra/77-pass-the-bugs-my-acceptance-checker-could-never-see-1hcl</guid>
      <description>&lt;p&gt;My portal answers all seven of the acceptance checker's questions correctly. The report is committed in the repo: seven &lt;code&gt;PASS&lt;/code&gt; lines and a tidy last line that says &lt;code&gt;claimed T1 T2, verified T1 T2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the freeze I did the kind of thing that only makes sense once a hackathon is over. I copied the project, put one old bug back in (the exact lines from my own commit history), and ran the checker again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T2  judge sees own scores ............. PASS
T2  judge cannot see peer scores ...... PASS
T2  participant blocked ............... PASS
T2  csv export works .................. PASS

claimed T1 T2, verified T1 T2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same tidy last line. Meanwhile, in that same running copy, a judge who owns exactly one ballot asked for her own scores and received two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nadia asks for her OWN scores and receives 2 ballot(s):
    Nadia's private note: loved it
    Omar's private note: meh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the first half of this post: what a green report is allowed to mean. The second half is about bugs of a different species, the kind you only find by running the shipped thing like a stranger would. I did not do that until it was far too late, and it is my biggest regret of the weekend.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rubric&lt;/strong&gt; is a self-hostable hackathon submission and judging portal (Django, PostgreSQL, one &lt;code&gt;docker compose up&lt;/code&gt;), built for &lt;a href="https://dogfoodhack.com" rel="noopener noreferrer"&gt;DogFood 2026&lt;/a&gt;, whose brief is "build the platform that will judge you". It claims tiers T1 and T2.&lt;/li&gt;
&lt;li&gt;The acceptance checker asks seven questions. I broke a scratch copy of my own code five ways: the checker noticed &lt;strong&gt;2 of 5&lt;/strong&gt;, my own tests noticed &lt;strong&gt;5 of 5&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bugs and their lifespans: an isolation leak (about an hour), a misspelled field that hid the live link from every judge (well over a day), a deliberately leaky debug route (around 45 hours), and a container that served no CSS or JavaScript while CI and the checker stayed green.&lt;/li&gt;
&lt;li&gt;The front door of my own app was undocumented, with a README pointing at a password that did not exist, because nothing I wrote ever tried it.&lt;/li&gt;
&lt;li&gt;Everything reproduces from one commit, &lt;a href="https://github.com/Dev-Am12/Rubric/commit/777bbb5" rel="noopener noreferrer"&gt;&lt;code&gt;777bbb5&lt;/code&gt;&lt;/a&gt;, plus two small scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the thing being judged. Rubric is Django 5.2 and PostgreSQL 16 with server-rendered pages and HTMX; one &lt;code&gt;docker compose up&lt;/code&gt; gives a seeded portal on &lt;code&gt;localhost:8080&lt;/code&gt; with the network off. It has five roles, events, teams by invite link, submissions with a deadline that holds, a public gallery, expiring judge invitations, an organizer-weighted rubric, seeded judge assignment, cross-judge normalization and a live organizer dashboard, with every state change written to a hash-chained audit log. The voting tier is built but cannot be machine-verified, so I claim T1 and T2 only.&lt;/p&gt;

&lt;p&gt;This is the judge's ballot: one project, three weighted criteria, keyboard shortcuts, autosave on every click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2t7ewzwc9bs9rso4faf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2t7ewzwc9bs9rso4faf.png" alt="Rubric's judge ballot: a project on the left, a 1 to 5 scoring form with anchored descriptions on the right" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  72 hours in one picture
&lt;/h2&gt;

&lt;p&gt;My plan had a lovely shape. The original window was the 25th to the 28th in my time: both weekend days in full and exactly one weekday. Then the dates shifted by a day (the 26th to the 29th), and the same 72 hours now held &lt;strong&gt;two weekdays and one fewer weekend day&lt;/strong&gt;. Classes did not get the memo.&lt;/p&gt;

&lt;p&gt;Disrupted schedule, though: is that not the job description? Engineers build inside constraints they did not choose, and the constraint is half the fun. I had a gate-by-gate plan written before kickoff (the grey strip below), and my commit history is a rather honest record of what happened to it once the calendar changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxvrnsr2sg9zttowd3nd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxvrnsr2sg9zttowd3nd.png" alt="Every commit of the 72 hours stacked by what it changed, with the planned gate windows underneath" width="799" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reading the chart like a detective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ahead of plan early.&lt;/strong&gt; Gate G5 was due around hours 42 to 50. It landed around hour 39.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then a weekday happened.&lt;/strong&gt; From Monday afternoon to early Tuesday morning, my time, there is not a single commit, about 15 hours. College does not pause for hackathons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cheap stretch items never got their slot.&lt;/strong&gt; G7 (around hours 58 to 64) was reserved for API parity, import and export, and pairwise mode "only if genuinely ahead". It never started, because the gate before it, voting plus hardening, ran until around hour 63 and ate it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests nearly matched the application.&lt;/strong&gt; Roughly 7,600 lines of tests against 9,500 of application code, plus 4,500 of templates and CSS, about 90% of which landed in the first 39 hours or so. Then the UI stopped changing around hour 63, with roughly nine hours still on the clock. (Lines added, not net; treat the proportions as a mood.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the screens existed early. What I kept postponing was &lt;em&gt;looking&lt;/em&gt; at them. Later is a word that does not appear in a 72-hour timetable, and I will come back to what it cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven questions, and what a PASS is allowed to mean
&lt;/h2&gt;

&lt;p&gt;DogFood gives every team the same small program, &lt;code&gt;run.py&lt;/code&gt;. It never logs in (you hand it one header per role) and it makes exactly seven requests against your running portal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;It passes when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /projects&lt;/code&gt;, no auth&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;same page&lt;/td&gt;
&lt;td&gt;one of the first three fixture project titles appears in the body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /projects/new&lt;/code&gt; as a participant&lt;/td&gt;
&lt;td&gt;any 4xx (the fixture event closed in the past)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;judge A asks for judge scores&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;judge B asks for judge A's scores&lt;/td&gt;
&lt;td&gt;401 or 403&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;a participant asks for judge scores&lt;/td&gt;
&lt;td&gt;401 or 403&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;organizer asks for the CSV&lt;/td&gt;
&lt;td&gt;200 and a comma in the first line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now read that table like an adversary. Every judge in it is a fixture judge. Check 7 is satisfied by a single comma. Neither is a flaw in the checker: the brief says plainly that judges also read the repo, the docs and the demo video, and that the checker is not the whole assessment. It is a floor. My job, as I saw it, was to find out how far that floor sits from the walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that lived for about an hour
&lt;/h2&gt;

&lt;p&gt;The spec has a favourite sentence, paraphrased: hiding another judge's scores in your template is not refusing, because the check has to live in the backend, where &lt;code&gt;curl&lt;/code&gt; arrives. I believed it. Before writing real code I wrote the one function that decides who may see judge scores, and put my paper drafts through a stress test. The first draft never checked that the caller was a judge: a participant asking for judge scores computed a target of &lt;code&gt;None&lt;/code&gt;, watched &lt;code&gt;None != None&lt;/code&gt; come out false, skipped the permission check and got an empty &lt;code&gt;200&lt;/code&gt;, which is exactly what the checker's "participant blocked" question is waiting for. The second draft fixed that so enthusiastically that it locked out organizers too. Both wrong, in different ways. That felt like a triumph, and it was a trap, because afterwards the function was declared authoritative, and authoritative things stop getting looked at. Keep an eye on &lt;code&gt;None&lt;/code&gt;. It is about to come back.&lt;/p&gt;

&lt;p&gt;It went into the code around hour 25, in the commit that introduced judging. This is the function as it was committed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# commit d62c0d8, around hour 25
&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;judge_external_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;judge_external_id&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;judge_external_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;PermissionDenied&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Ballot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;assignment__judge__external_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;assignment__event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the adversarial rounds, which I ran with Gemini and Claude and pointed at the isolation function and nothing else, caught it. One of them asked the question I had not: &lt;em&gt;what about judges who are not in the fixture file?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fixture judges carry an id like &lt;code&gt;jdg_07&lt;/code&gt;. A judge invited through an organizer's link in a real event does not. Their &lt;code&gt;external_id&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt;, and when you hand Django &lt;code&gt;None&lt;/code&gt; it writes &lt;code&gt;IS NULL&lt;/code&gt;. So every id-less judge matched every other id-less judge, and each could read the others' ballots. About an hour after it landed, this went in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# commit b410a0a, about an hour later
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;judge_external_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;judge_external_id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;judge_external_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;PermissionDenied&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Ballot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;assignment__judge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;assignment__event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3tvel9snz8rzhxx3r22i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3tvel9snz8rzhxx3r22i.png" alt="Two judges with a NULL external_id both match the same filter; the fix scopes by the authenticated account instead" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Django documents this behaviour openly: an &lt;code&gt;exact&lt;/code&gt; lookup with &lt;code&gt;None&lt;/code&gt; is interpreted as SQL &lt;code&gt;NULL&lt;/code&gt; (&lt;a href="https://docs.djangoproject.com/en/5.2/ref/models/querysets/#exact" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). It is sensible, and it is exactly the wrong thing to have underneath a security decision. The &lt;code&gt;external_id&lt;/code&gt; is still used for one thing, validating an id a caller explicitly asks for. It is never used to decide who the caller is.&lt;/p&gt;

&lt;p&gt;The same fix commit repaired two neighbours that the review turned up. Autosave could still edit a ballot after it had been submitted. And scores had no range check at all, so a 9,999 or a minus 3 went straight into the database (the column only limits digits). One review, one commit, three defects. Bugs travel in groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the checker could never see it
&lt;/h3&gt;

&lt;p&gt;The checker's judges are &lt;code&gt;judge_a&lt;/code&gt; and &lt;code&gt;judge_b&lt;/code&gt;, both fixture judges with ids, and its peer probe asks for &lt;code&gt;jdg_07&lt;/code&gt;, also a fixture judge. The bug only exists in a population the demo data does not contain: real judges, invited the way a real organizer invites them. It would have passed every acceptance run and leaked on the first real event. This is the unglamorous shape of the number-one item on OWASP's list, &lt;a href="https://owasp.org/Top10/2021/A01_2021-Broken_Access_Control" rel="noopener noreferrer"&gt;broken access control&lt;/a&gt;: the check exists, and is even correct, for the inputs somebody thought of.&lt;/p&gt;

&lt;p&gt;The regression test builds the missing population: two judges with &lt;code&gt;external_id=None&lt;/code&gt; on the same project, each with a completed ballot, asserting that judge one sees only their own. It is &lt;code&gt;test_judges_without_external_ids_only_see_their_own_ballots&lt;/code&gt;, and it is the one that goes red in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five ways to break a portal on purpose
&lt;/h2&gt;

&lt;p&gt;Once I had one bug the checker could not see, the obvious question was how many more kinds there could be. So I wrote a small harness. For each experiment it copies the submitted code to a temp directory, changes one thing, seeds a throwaway database, starts the server, runs the real &lt;code&gt;run.py&lt;/code&gt;, and then runs the repo's own tests for that area. This is &lt;a href="https://en.wikipedia.org/wiki/Mutation_testing" rel="noopener noreferrer"&gt;mutation testing&lt;/a&gt; done by hand, at the level of "somebody deleted the security check".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7nbrvg5brswpn1yrubi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7nbrvg5brswpn1yrubi.png" alt="A scoreboard: the acceptance checker caught 2 of 5 deliberate breakages; the repo tests caught all 5" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The historic bug (row 2):&lt;/strong&gt; seven of seven &lt;code&gt;PASS&lt;/code&gt;, as promised in the opening, and the regression test goes red alone. &lt;strong&gt;The control (row 3):&lt;/strong&gt; I deleted the peer-score check entirely, and both the checker and the repo test fail. Every experiment needs a control that is supposed to be caught, or you cannot tell a blind checker from a broken harness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deadline (row 4)&lt;/strong&gt; has history. During the build, "closed event refuses submissions" turned green for the wrong reason: the checker's POST was refused by CSRF protection, so the 4xx happened whether or not any deadline logic existed. The fix was precision about who needs CSRF protection. Requests that authenticate with an &lt;code&gt;Authorization: Bearer&lt;/code&gt; header skip it, because a browser never attaches that header on its own and a cross-site attacker cannot forge it; cookie-session requests keep full enforcement. Now removing the deadline turns the report to &lt;code&gt;verified nothing&lt;/code&gt;, because T1 is the floor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CSV (row 5):&lt;/strong&gt; I replaced the export with &lt;code&gt;placeholder,rows&lt;/code&gt;: seven of seven &lt;code&gt;PASS&lt;/code&gt;, while the real CSV test (per-project rows, raw and normalized means, ranks, review counts, flags) goes red. &lt;strong&gt;The tallies (row 6):&lt;/strong&gt; showing vote tallies during the voting window is a T3 leak. The checker covers T1 and T2 only, so it says nothing, and two repo tests catch it.&lt;/p&gt;

&lt;p&gt;None of this says the checker is bad. One of the three misses is outside anything it claims to check, and the other two slipped through for reasons visible in its own source. A floor tells you the floor holds. "Two of five versus five of five" tells you far more than "seven of seven".&lt;/p&gt;

&lt;h2&gt;
  
  
  The alarm that stayed for about 45 hours
&lt;/h2&gt;

&lt;p&gt;My favourite piece of boring engineering is the route-by-role matrix. &lt;code&gt;tests/authz_expectations.yaml&lt;/code&gt; declares, for every named route, who may call it and what each kind of caller must get back, and a test walks the URLconf and &lt;strong&gt;fails the build if any route has no declaration&lt;/strong&gt;. Forty-seven routes, forty-eight declarations (one extra covers the checker's peer-scores probe).&lt;/p&gt;

&lt;p&gt;An alarm nobody has heard go off is a rumour. So around hour 18, in the commit that built the auth and policy layer, I planted a deliberately unprotected route, &lt;code&gt;debug/_leaky_test_only/&amp;lt;judge_id&amp;gt;/scores&lt;/code&gt;, and watched the coverage test scream. Satisfying. The docstring said, in capitals, to remove it before the freeze.&lt;/p&gt;

&lt;p&gt;The part I am less proud of: it was a plain route in the URLconf, with no debug flag around it. It returned an empty list, so it never actually leaked anything, but it sat in the main branch for &lt;strong&gt;around 45 hours&lt;/strong&gt;, until around hour 63. A backdoor built to test your backdoor detector is still a backdoor, and "I will remove it later" is the most expensive sentence in software.&lt;/p&gt;

&lt;p&gt;It finally went in the commit that added the safer drill: a synthetic URLconf holding one undeclared route, and a test asserting the alarm fires and names that exact route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROOT_URLCONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tests.urls_synthetic_undeclared&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_coverage_alarm_fails_on_undeclared_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;coverage_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuthzCoverageTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;coverage_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_every_route_has_declared_expectation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;synthetic_control_case_undeclared&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A second test asserts that the production URLconf has no such route and that the old path returns a plain 404. The alarm is still proven, and nothing leaky ships. I should have done it in that order from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reviewers found, and what they missed
&lt;/h2&gt;

&lt;p&gt;The adversarial rounds did not stop at isolation. I pointed them at the voting code too, and a few of their findings deserve a table, because each row is a place where my own tests had been quietly agreeing with me.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;What would have happened&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project links were stored raw&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;javascript:&lt;/code&gt; URL typed into a project link reached an &lt;code&gt;href&lt;/code&gt;. Forms validate URLs, but my services bypassed forms.&lt;/td&gt;
&lt;td&gt;Validation in the service layer (absolute http or https, a host, no whitespace or control characters, 1,024 characters at most), plus a render-time &lt;code&gt;safe_link&lt;/code&gt; filter so bad data written straight to the database still renders no link.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Organizer role was checked against the &lt;em&gt;current&lt;/em&gt; event&lt;/td&gt;
&lt;td&gt;An organizer of one event could edit a different event.&lt;/td&gt;
&lt;td&gt;One helper, &lt;code&gt;require_event_role(actor, event, role)&lt;/code&gt;, checks the event being edited, with named isolation tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voter identity in the audit log&lt;/td&gt;
&lt;td&gt;Signed-in votes stored the raw user id, and the audit entry carried actor and project together, so an organizer could connect a voter to a choice.&lt;/td&gt;
&lt;td&gt;A secret ballot: an HMAC pseudonym keyed by the server secret and an event seed. Voting audit entries carry no user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open-link fingerprint included the User-Agent&lt;/td&gt;
&lt;td&gt;Cycling the header from one machine counted as a new voter every time.&lt;/td&gt;
&lt;td&gt;The fingerprint is the IP address only.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is easy to check yourself. Five different User-Agent strings from one address, against the submitted code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UA A/1 -&amp;gt; 302   (accepted)
UA B/2 -&amp;gt; 409
UA C/3 -&amp;gt; 409
UA D/4 -&amp;gt; 409
UA E/5 -&amp;gt; 409
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The one nobody found.&lt;/strong&gt; On the judge's ballot there is a button that opens the project's live site. I wrote the template against a field called &lt;code&gt;live_site_url&lt;/code&gt;. The model's field is &lt;code&gt;live_url&lt;/code&gt;. A Django template treats a missing attribute as empty, so the button simply never rendered: no error, no log line, no failing test, for every judge on every project. It went in with the same commit as the isolation function, around hour 25, and stayed invisible for well over a day, until the link-handling fix was read line by line. The regression test is called &lt;code&gt;test_judge_ballot_renders_live_url_from_the_model_field&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I would like to say the reviewers should have caught it. A reviewer reads code for what is wrong, though, and a misspelled attribute is not wrong in any way a linter can see. It is merely absent. Absences are what people find, because people open the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Green CI, unstyled app
&lt;/h2&gt;

&lt;p&gt;Here is a cousin of that typo, one layer further out. The container runs with &lt;code&gt;DEBUG=false&lt;/code&gt;. My settings declared a &lt;code&gt;STATIC_URL&lt;/code&gt;, but nothing in a production configuration actually served static files: no &lt;code&gt;STATIC_ROOT&lt;/code&gt;, no static-serving middleware, no &lt;code&gt;collectstatic&lt;/code&gt;. Every page loads &lt;code&gt;/static/css/rubric.css&lt;/code&gt; and &lt;code&gt;/static/js/htmx.min.js&lt;/code&gt;. In the container both would have been 404s, which means an unstyled app with every HTMX interaction dead. Django's development server serves static files whenever debug is on, so every local run looked fine, and the acceptance checker only looks at status codes on data routes, so it stayed happy too.&lt;/p&gt;

&lt;p&gt;I reproduced it on the commit just before the fix (&lt;code&gt;7632286&lt;/code&gt; is the fix; check out its parent) with &lt;code&gt;DJANGO_DEBUG=false&lt;/code&gt;, which is how the container runs. Pages return 200, both static files return 404, and the checker prints &lt;code&gt;7/7 PASS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdj8cqe0809r5srru5o4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdj8cqe0809r5srru5o4d.png" alt="Left: the gallery with CSS and JS returning 404, a raw unstyled page. Right: the same page after the fix. Both run with DEBUG=false." width="799" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is boring in the best way: WhiteNoise in the middleware, a &lt;code&gt;STATIC_ROOT&lt;/code&gt;, and &lt;code&gt;collectstatic&lt;/code&gt; as a build step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /app/src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;DJANGO_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build-only-secret &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;DJANGO_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite:////tmp/collectstatic.sqlite3 &lt;span class="se"&gt;\
&lt;/span&gt;    python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CI now fetches the CSS and the JavaScript from the running container and fails if either is missing. It landed around hour 53, three quarters of the way in, which is late for a bug that makes your product look like a 1996 homepage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proving "runs offline" instead of asserting it.&lt;/strong&gt; The first rule of the event is that the portal starts with the network off. I did not want to write that sentence, I wanted a job that fails when it is false. The design: build and pull the images while online, add a firewall rule that drops public egress from the Compose network, check that the rule really blocks things (a control request from inside the container must fail and the rule's drop counter must go up), run the acceptance checker, and require the offline report to be identical to the online one. Merely detaching a container from a network proves nothing, because another attached network or a runtime path could still reach outside, so the check watches the actual boundary.&lt;/p&gt;

&lt;p&gt;It failed on its first run in CI. The script read the network's ID off a container that had been created but never started, and the ID came back empty. The fix reads the network's name instead. The same commit added a rule that lets already-established connections through, so only new outbound connections are dropped, and a diagnostics step that runs only on failure. That was around hour 60. &lt;/p&gt;

&lt;h2&gt;
  
  
  The bug you only find by running it
&lt;/h2&gt;

&lt;p&gt;Now the confession. Every check in this post is a machine asking a machine a question. For three days that was the whole development loop, on purpose: I wanted the logic solid and fully tested first, and I told myself the polish, and the looking, would get their turn. I wanted the interface to be clean, quietly sophisticated and easy to use, because a judging portal is a UX problem before it is anything else. I did not run the project by hand until the last day, and by then it was far too late to do anything about what I saw.&lt;/p&gt;

&lt;p&gt;So after the freeze I did it properly. I wrote a crawler (a page of Playwright) that logs in as each seeded persona, follows every internal link, and records errors and sideways scrolling at five screen widths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SIDEWAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document.documentElement.scrollWidth &amp;gt; document.documentElement.clientWidth + 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It visited 218 pages at each width, 1,090 page loads in all. (The organizer crawl hit my 70-page cap, so treat that persona as a sample.) The good news is real: no server errors and no tracebacks anywhere. Here is the rest.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The organizer's nav does not fit a laptop.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;At 1600 px wide it is fine. At 1440 px every organizer page scrolls sideways by 32 px, at 1280 px by 112 px, and "Log Out" slides off the right edge.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;There is no phone layout.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;At 390 px, nearly every page for every persona scrolls sideways, and the nav is cut off after "Results".&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"My Submissions" is a trap for visitors.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logged out, the nav link returns a raw JSON &lt;code&gt;authentication_required&lt;/code&gt; error. "Create Team" next to it politely redirects to login. Same visitor, two behaviours.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The front door had no handle.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;See below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah24avgdi7p3a3dqvkug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah24avgdi7p3a3dqvkug.png" alt="At 1280 px the organizer's navigation overflows the page: the user chip and Log Out fall off the right edge" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The front door
&lt;/h3&gt;

&lt;p&gt;This is the one that stings. Run &lt;code&gt;docker compose up&lt;/code&gt;, open the portal, and click Log In. Who are you? The seed script prints four logins, but as &lt;code&gt;Authorization: Bearer&lt;/code&gt; headers for &lt;code&gt;curl&lt;/code&gt; and for the checker. No seeded account has a password. My README, at the submitted commit, told you to sign in as an organizer with a password that did not exist. A judge who wanted to click around my portal had to know how to create an organizer on the command line, and nothing told them.&lt;/p&gt;

&lt;p&gt;The funny part is that a working door existed all along. The auth middleware accepts the same token from a cookie named &lt;code&gt;session&lt;/code&gt;, so pasting the seeded token into your browser as that cookie logs you in as that persona. I only found it while writing the crawler, because that is how the crawler gets in. It was in none of my docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# log in as the seeded organizer in your browser: add a cookie&lt;/span&gt;
&lt;span class="c"&gt;#   name:  session&lt;/span&gt;
&lt;span class="c"&gt;#   value: rubric_seed_organizer_tok_9f8e7d6c5b4a&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Cookie: session=rubric_seed_organizer_tok_9f8e7d6c5b4a"&lt;/span&gt; http://localhost:8080/organizer
&lt;span class="c"&gt;# 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root cause is a neat little irony. The checker never logs in, by design. And, as it turns out, neither did my seed script, on behalf of a human. Every layer of evidence I built was shaped like that checker. None of them was shaped like a person arriving at the door. The shape was set on day one: my authentication is a standalone token system, chosen because the checker attaches a header and never logs in.&lt;/p&gt;

&lt;p&gt;I fixed the README's login instructions after the freeze, in a docs-only commit, and left the code exactly as submitted. The nav and the phone layout stay as they were. I would rather show you the scar than repaint it.&lt;/p&gt;

&lt;p&gt;If you take one habit from this section: &lt;strong&gt;put a human-shaped check in by hour 36.&lt;/strong&gt; Ten minutes of "log in, click everything, shrink the window" would have found every row in that table while there was still time to fix them. It costs a page of code, and mine took a page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The maths that fought back
&lt;/h2&gt;

&lt;p&gt;The brief asks for cross-judge normalization "documented and defended". The problem in one sentence: some judges are stingy, some are generous, and some give everything the same number, so a raw average partly measures &lt;em&gt;who happened to be assigned to you&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A judge who gave everything a 4
&lt;/h3&gt;

&lt;p&gt;Judge &lt;code&gt;jdg_07&lt;/code&gt; scored three projects and every criterion on every one is a 4. Divide by their spread and you divide by zero, which is a fine way to crash a hackathon.&lt;/p&gt;

&lt;p&gt;My method is shrinkage in the spirit of &lt;a href="https://en.wikipedia.org/wiki/Empirical_Bayes_method" rel="noopener noreferrer"&gt;empirical Bayes&lt;/a&gt; (deliberately not a full Bayesian posterior, which a statistician would poke). Distrust each judge's own average and spread in proportion to how few ballots you have seen from them, by blending in &lt;strong&gt;four imaginary ballots from the average judge&lt;/strong&gt;. The &lt;a href="https://en.wikipedia.org/wiki/James%E2%80%93Stein_estimator" rel="noopener noreferrer"&gt;James-Stein estimator&lt;/a&gt; is the famous ancestor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mu_s  = (n * ybar + 4 * mu0)  / (n + 4)      # shrunk mean
s2_s  = (n * s2   + 4 * var0) / (n + 4)      # shrunk variance
z     = (y - mu_s) / sqrt(s2_s)              # ballot value as a z-score
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mu0&lt;/code&gt; and &lt;code&gt;var0&lt;/code&gt; are the pool's own mean and variance over all counted ballots. For &lt;code&gt;jdg_07&lt;/code&gt;: &lt;code&gt;n = 3&lt;/code&gt;, &lt;code&gt;ybar = 4&lt;/code&gt;, raw variance &lt;code&gt;0&lt;/code&gt;. The shrunk variance is &lt;code&gt;0.2246&lt;/code&gt;, strictly positive, so the judge gets a finite z-score of &lt;code&gt;0.5115&lt;/code&gt; and nothing divides by zero. Average a project's z-scores, map back to the 1 to 5 scale, done.&lt;/p&gt;

&lt;h3&gt;
  
  
  The sentence I had to take back
&lt;/h3&gt;

&lt;p&gt;My first write-up said a constant judge's influence "converges toward zero". It read well, and it was a claim about a formula I had not run on real data. When I ran it, &lt;code&gt;jdg_07&lt;/code&gt; contributed &lt;strong&gt;0.5115&lt;/strong&gt; to each of their three projects: a fixed offset that does not &lt;em&gt;differentiate&lt;/em&gt; between them. I corrected the docs and logged the correction.&lt;/p&gt;

&lt;p&gt;Then the temptation. Remove &lt;code&gt;jdg_07&lt;/code&gt;, recompute, and their three projects move 8, 9 and 13 places. Damning? I ran the same experiment on an ordinary judge, &lt;code&gt;jdg_06&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge removed&lt;/th&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Rank before&lt;/th&gt;
&lt;th&gt;Rank after&lt;/th&gt;
&lt;th&gt;Places moved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;jdg_07&lt;/code&gt; (constant)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_09&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_19&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;jdg_06&lt;/code&gt; (ordinary control)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_13&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_26&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prj_35&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ordinary judge's projects move too; rank is brittle in a tightly bunched field. The biggest jump, &lt;code&gt;prj_19&lt;/code&gt;, is the best story in the data: it has only two reviews, one of them &lt;code&gt;jdg_07&lt;/code&gt;'s flat 4, 4, 4, so its only relative signal is the other reviewer, &lt;code&gt;jdg_29&lt;/code&gt; (3, 2, 5). Take the constant judge away and the project is judged by one person. So I did not assert "constant judges cannot move ranks" in the tests. I wrote the sensitivity down as a limitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  A second constant judge the brief never mentioned
&lt;/h3&gt;

&lt;p&gt;The spec promises one awkward judge. My dashboard flags &lt;strong&gt;two&lt;/strong&gt;. Judge &lt;code&gt;jdg_19&lt;/code&gt; reviewed four projects, one of which (&lt;code&gt;prj_07&lt;/code&gt;) is the earlier half of a duplicate submission, superseded by the team's later &lt;code&gt;prj_41&lt;/code&gt;, so its ballot is excluded. The three that remain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Functionality, quality, innovation&lt;/th&gt;
&lt;th&gt;Ballot mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prj_03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3, 5, 3&lt;/td&gt;
&lt;td&gt;3.667&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prj_24&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3, 4, 4&lt;/td&gt;
&lt;td&gt;3.667&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prj_41&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5, 4, 2&lt;/td&gt;
&lt;td&gt;3.667&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three different score sets, one identical average: zero variance across the ballots the method sees. The flag fires on the weighted ballot value, not on repetitive-looking raw numbers. The duplicate decision moves the headline numbers too: dropping the superseded copy's five ballots changes the pooled mean from 3.5661 to 3.5758 and the variance from 0.4202 to 0.3930. Those two numbers have a story of their own. Around hour 62 I corrected them: an early draft of my judging document said 3.6190 and 0.6019, the real run said otherwise, and prose does not fail a build. Now a test recomputes the statistics from a seeded run and asserts that the document's digits match.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the pictures show, and what they do not
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7s8412gaz4ob5ytghqla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7s8412gaz4ob5ytghqla.png" alt="Slope chart of project ranks before and after normalization: seven unchanged, fifteen up, eighteen down" width="800" height="914"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the real fixture, 40 projects are ranked from 121 ballots by 29 judges: seven ranks unchanged, fifteen up, eighteen down. A two-way tie for first at 4.3333 raw is broken in favour of &lt;code&gt;prj_34&lt;/code&gt;, and the biggest single move is &lt;code&gt;prj_28&lt;/code&gt;, down seven places. None of that proves the new ranking is &lt;em&gt;right&lt;/em&gt;. A fixture has no ground truth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3paoo2th66l89cfmw0b3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3paoo2th66l89cfmw0b3.png" alt="Judge means before and after shrinkage, showing the spread narrowing from 0.3235 to 0.2023" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The spread of judge averages narrows from 0.3235 to 0.2023, about 37.5 percent. That is largely &lt;em&gt;by construction&lt;/em&gt;: shrinkage pulls things toward the middle whether or not the middle is correct. To test closeness to a truth you have to plant one: 20 projects with known quality, 10 judges with leniency offsets from -1.5 to +1.5, a little noise, three judges per project, and a &lt;a href="https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient" rel="noopener noreferrer"&gt;Spearman correlation&lt;/a&gt; against the truth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp0vdpky9y98zk686pn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp0vdpky9y98zk686pn6.png" alt="Scatter of 100 simulation seeds, all above the diagonal: normalized ranking beats raw ranking in 100 of 100 seeds" width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over 100 seeds the correlation rises from 0.7347 to 0.8949, and normalization wins in 100 of 100. I did not tune anything, and it is still a &lt;strong&gt;simulation&lt;/strong&gt; whose judges follow the additive model the method assumes, which is why the proof page labels it SYNTHETIC. One more unflattering fact: eight of the 41 projects have fewer than three reviews, and the dashboard says so out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I planned, what I shipped
&lt;/h2&gt;

&lt;p&gt;My written cut order, made before kickoff, started with pairwise mode and ended with T3 itself. Reality followed it almost to the letter, which is the only reason none of this feels like a disaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of T4 did not ship&lt;/strong&gt;: no webhooks, certificates, embeddable widget or OpenAPI spec. A small JSON and CSV surface does exist (judge scores, ballot submission, organizer progress, audit log and verification, a voting summary, comment flagging, the CSV export). The commit history has a small joke about it: &lt;code&gt;django-ninja&lt;/code&gt;, picked to generate the OpenAPI schema, sat in &lt;code&gt;requirements.txt&lt;/code&gt; from the first hour to the very last, imported by nothing, and left in the commit literally named &lt;code&gt;final&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pairwise mode and email-gated voting are not built.&lt;/strong&gt; Comparing two projects at a time and recovering a ranking with a Bradley-Terry style estimator, as &lt;a href="https://github.com/anishathalye/gavel" rel="noopener noreferrer"&gt;Gavel&lt;/a&gt; does for HackMIT, is the elegant way around judge calibration, and also a second judging engine. I would rather ship one hard bonus with a simulation behind it than two with excuses. Email-gated voting either fails offline or makes an identity claim I cannot back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The audit chain has a stated ceiling.&lt;/strong&gt; Every state change is hash-chained and a database trigger refuses updates and deletes. That makes tampering visible against a trusted head hash. It does not stop someone with full write access from rebuilding the chain, so the honest advice is to publish the head hash somewhere the database cannot reach. "Tamper-evident" and "tamper-proof" are different words, and I only claim the first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs that outlive their folder.&lt;/strong&gt; My design notes lived in a folder that was always going to be deleted at submission. A late sweep found nearly a hundred references to them in code comments, the stylesheet header and the subtitle of the organizer dashboard. A test now fails the build if any file cites a document that does not ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The regret I would fix first is the UI.&lt;/strong&gt; I planned something clean and easy. What shipped works, has a ballot screen I am happy with, and has a navigation bar that does not fit a laptop. With another day I would not add a feature; I would spend the first hour clicking through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are building one of these tomorrow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask which population your demo data cannot contain.&lt;/strong&gt; Fixture data is a tidy little world. My bug lived in the untidy one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never scope access by a column that is allowed to be empty.&lt;/strong&gt; Scope by the row that logged in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break your own thing on purpose, and keep the drills in the test suite.&lt;/strong&gt; A check you have never seen fail is a rumour. A deliberately broken route is a debt with a due date, so pay it early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure before you assert, and write the correction down.&lt;/strong&gt; "Converges toward zero" felt true and was not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score your checkers.&lt;/strong&gt; List what each layer cannot see. "Two of five and five of five" is information; "seven of seven" is a mood.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the shipped artifact like a stranger by hour 36.&lt;/strong&gt; Log in the way a visitor would, click everything, shrink the window, and test the container, not just the dev server. The cheapest bug report in the world is a person arriving at your door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for absences.&lt;/strong&gt; A reviewer reads code for what is wrong. A misspelled template attribute is not wrong, it is missing, and it can hide for a day.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The scoreboard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Acceptance checker&lt;/td&gt;
&lt;td&gt;7 of 7 &lt;code&gt;PASS&lt;/code&gt;; T1 and T2 claimed and verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;271 (270 pass; 1 PostgreSQL-only test skipped on SQLite; the PostgreSQL CI job is green on the evaluated commit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routes with a declared access policy&lt;/td&gt;
&lt;td&gt;47 (48 declarations)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deliberate breakages caught: checker / repo tests&lt;/td&gt;
&lt;td&gt;2 of 5 / 5 of 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation leak, committed to fixed&lt;/td&gt;
&lt;td&gt;about an hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden live-link typo&lt;/td&gt;
&lt;td&gt;well over a day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deliberately leaky debug route, planted to purged&lt;/td&gt;
&lt;td&gt;around 45 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container before the static fix&lt;/td&gt;
&lt;td&gt;pages 200, CSS and JS 404, checker 7 of 7 &lt;code&gt;PASS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crawl, five widths&lt;/td&gt;
&lt;td&gt;218 pages each, no server errors; organizer nav overflows by 32 px at 1440 wide and 112 px at 1280&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real fixture&lt;/td&gt;
&lt;td&gt;40 of 41 ranked, 121 ballots, 29 judges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Judge-mean spread, before to after&lt;/td&gt;
&lt;td&gt;0.3235 to 0.2023 (by construction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulated recovery of a planted truth&lt;/td&gt;
&lt;td&gt;0.7347 to 0.8949, better in 100 of 100 seeds (synthetic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bonuses claimed / not built&lt;/td&gt;
&lt;td&gt;Normalization Proof, Threat Model / pairwise mode, email-gated voting, most of T4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Dev-Am12/Rubric.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;Rubric
git checkout 777bbb5              &lt;span class="c"&gt;# the evaluated commit&lt;/span&gt;
docker compose up                 &lt;span class="c"&gt;# seeded portal on http://localhost:8080&lt;/span&gt;
python3 run.py .dogfood.toml      &lt;span class="c"&gt;# the seven questions&lt;/span&gt;

pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt   &lt;span class="c"&gt;# once, for the two scripts below&lt;/span&gt;
python mutation_check.py &lt;span class="nb"&gt;.&lt;/span&gt;        &lt;span class="c"&gt;# five breakages, two kinds of evidence&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;playwright &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium
python crawl_check.py http://localhost:8080   &lt;span class="c"&gt;# a stranger's walkthrough&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both scripts only touch temporary copies or issue GET requests. I ran them on Linux, and they need nothing beyond the standard library, Playwright and the project's own dependencies. Grab them here: &lt;a href="https://gist.github.com/Dev-Am12/9d9782e47cfa9fadff1fd5a6b3f90b9a" rel="noopener noreferrer"&gt;https://gist.github.com/Dev-Am12/9d9782e47cfa9fadff1fd5a6b3f90b9a&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository is at &lt;a href="https://github.com/Dev-Am12/Rubric" rel="noopener noreferrer"&gt;github.com/Dev-Am12/Rubric&lt;/a&gt;. The design decisions, including every correction above, are in &lt;code&gt;DECISIONS.md&lt;/code&gt;, and the normalization method is defended in &lt;code&gt;JUDGING.md&lt;/code&gt;. Demo: &lt;a href="https://drive.google.com/drive/folders/1sVArFhpSnswrgRyhIGcacO7rwGqx6_lW" rel="noopener noreferrer"&gt;https://drive.google.com/drive/folders/1sVArFhpSnswrgRyhIGcacO7rwGqx6_lW&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The line I keep coming back to is small. A green report is a receipt for the questions somebody thought to ask. The useful thing you can do the day after a hackathon is ask a few more, and one of them should be: &lt;em&gt;can a stranger get in?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth every hour
&lt;/h2&gt;

&lt;p&gt;I will end on the part no table can hold. I enjoyed this. Seventy-two hours with a shifting calendar, a navigation bar that does not fit a laptop and a checker that was far too polite about my bugs, and I had fun for all of it. &lt;a href="https://www.raptors.dev/" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt; events are always a good time to take part in, and DogFood was no exception.&lt;/p&gt;

&lt;p&gt;Whatever the result turns out to be, these 72 hours were not a waste. I built something I am proud of, broke it on purpose, learned where my own blind spots live, and wrote it all down so the next person gets to skip a few mistakes. That sounds like a pretty good weekend and a half to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DogFood 2026 brief and spec: &lt;a href="https://dogfoodhack.com" rel="noopener noreferrer"&gt;dogfoodhack.com&lt;/a&gt; and &lt;a href="https://dogfoodhack.com/spec" rel="noopener noreferrer"&gt;dogfoodhack.com/spec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Rubric, evaluated commit: &lt;a href="https://github.com/Dev-Am12/Rubric/commit/777bbb5" rel="noopener noreferrer"&gt;Dev-Am12/Rubric@777bbb5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP Top 10 2021, A01 Broken Access Control: &lt;a href="https://owasp.org/Top10/2021/A01_2021-Broken_Access_Control" rel="noopener noreferrer"&gt;owasp.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Django &lt;code&gt;exact&lt;/code&gt; lookup and &lt;code&gt;None&lt;/code&gt;: &lt;a href="https://docs.djangoproject.com/en/5.2/ref/models/querysets/#exact" rel="noopener noreferrer"&gt;docs.djangoproject.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Mutation testing: &lt;a href="https://en.wikipedia.org/wiki/Mutation_testing" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Empirical Bayes and the James-Stein estimator: &lt;a href="https://en.wikipedia.org/wiki/Empirical_Bayes_method" rel="noopener noreferrer"&gt;Empirical Bayes method&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/James%E2%80%93Stein_estimator" rel="noopener noreferrer"&gt;James-Stein estimator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spearman's rank correlation: &lt;a href="https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gavel, HackMIT's pairwise judging system: &lt;a href="https://github.com/anishathalye/gavel" rel="noopener noreferrer"&gt;github.com/anishathalye/gavel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built solo for DogFood 2026 by Hackathon Raptors. #hackathonraptors #DogfoodHackathon&lt;/p&gt;

</description>
      <category>hackathonraptors</category>
      <category>testing</category>
      <category>django</category>
      <category>security</category>
    </item>
    <item>
      <title>BlitzBroker by Team CodeBlitz. A from-scratch MQTT Broker in Rust.</title>
      <dc:creator>Ashutosh Mishra</dc:creator>
      <pubDate>Tue, 08 Sep 2026 18:33:07 +0000</pubDate>
      <link>https://dev.to/ashutosh_mishra/blitzbroker-by-team-codeblitz-a-from-scratch-mqtt-broker-in-rust-2cff</link>
      <guid>https://dev.to/ashutosh_mishra/blitzbroker-by-team-codeblitz-a-from-scratch-mqtt-broker-in-rust-2cff</guid>
      <description>&lt;h2&gt;
  
  
  118 Green Tests. One Client Still Hanging.
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mosquitto_pub -q 1&lt;/code&gt; doesn't print much when it works. Connect, publish, get an ack, exit. Half a second, if that.&lt;/p&gt;

&lt;p&gt;Ours took four seconds. Then it timed out.&lt;/p&gt;

&lt;p&gt;Nothing had crashed. Nothing had printed an error. Our broker was up, accepting connections, parsing every byte correctly. 62 tests passing at that exact commit, every one of them green. A real MQTT client just sat there, waiting for an acknowledgment that was never going to come, because we had built half of a feature and tested only the half we'd built.&lt;/p&gt;

&lt;p&gt;That gap between "the tests pass" and "the thing works" turned out to be the actual subject of this project, more than the protocol was.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we were avoiding
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://zerodepshack.com/" rel="noopener noreferrer"&gt;Zero Dependency&lt;/a&gt;'s rule is simple to state and expensive to keep: an empty manifest. No crates. We picked Track C, that is, Web &amp;amp; Network, and built &lt;a href="https://github.com/Dev-Am12/BlitzBroker" rel="noopener noreferrer"&gt;&lt;strong&gt;BlitzBroker&lt;/strong&gt;&lt;/a&gt;, an MQTT 3.1.1-subset pub/sub broker in Rust, &lt;code&gt;std&lt;/code&gt; only.&lt;/p&gt;

&lt;p&gt;MQTT, if you haven't touched it: clients connect to a broker, subscribe to named topics, publish messages, and the broker fans each message out to every current subscriber of that topic. It's the protocol under most "smart home" and IoT stacks: lightweight, binary, built for flaky connections. Normally you get it by running Mosquitto or EMQX, or by pulling in a client library and pointing it at someone else's broker. We wrote the broker.&lt;/p&gt;

&lt;p&gt;The honest reason to pick Rust for this specific event, rather than for its own sake: &lt;a href="https://doc.rust-lang.org/std/" rel="noopener noreferrer"&gt;Rust's &lt;code&gt;std&lt;/code&gt;&lt;/a&gt; gives you almost nothing for the "Web &amp;amp; Network" track. No async runtime. No HTTP. No JSON. No MQTT, obviously. Real Rust network code leans on &lt;code&gt;tokio&lt;/code&gt; and &lt;code&gt;serde&lt;/code&gt; almost by reflex, avoiding them here isn't a small flex, it's undoing the thing most Rust web code assumes on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the thing
&lt;/h2&gt;

&lt;p&gt;One broker thread, later four, each owning a disjoint slice of the topic registry, talking to connection threads only through channels. No shared-state locking on the registry, a topic either belongs to a thread or it doesn't, so there's nothing to race. &lt;code&gt;Register&lt;/code&gt; and &lt;code&gt;Disconnect&lt;/code&gt; broadcast to every shard, since a single client might end up with subscriptions scattered across more than one; everything else routes to exactly one shard, chosen by hashing the topic.&lt;/p&gt;

&lt;p&gt;That routing decision is also where the more interesting bug lived, but first: the queue.&lt;/p&gt;

&lt;p&gt;Every subscriber gets a bounded outbound queue. When it fills, a slow client, a burst of traffic, something has to give. &lt;code&gt;std::sync::mpsc::sync_channel(N)&lt;/code&gt; looked like the obvious tool here, until we actually read what it does under pressure: it blocks the &lt;em&gt;sender&lt;/em&gt; once full. That's exactly backwards for a broker. A stalled subscriber blocking the thread that's trying to serve every &lt;em&gt;other&lt;/em&gt; subscriber isn't backpressure, it's a single client taking the whole broker hostage. We wanted drop-oldest — discard the stalest buffered message, keep moving and &lt;code&gt;std&lt;/code&gt; doesn't hand you that. So it's a &lt;code&gt;Mutex&amp;lt;VecDeque&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; and a &lt;code&gt;Condvar&lt;/code&gt;, maybe forty lines, and it's the one piece of this project I'd point to as "yes, this is genuinely what the stdlib made painful": not that the primitives were missing, but that the one primitive that &lt;em&gt;was&lt;/em&gt; there had the wrong policy baked in, and there was no way to ask it for a different one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that actually ate an afternoon
&lt;/h2&gt;

&lt;p&gt;Wildcard subscriptions in MQTT let a client ask for &lt;code&gt;sensors/+/temp&lt;/code&gt; and receive publishes to &lt;code&gt;sensors/kitchen/temp&lt;/code&gt;, &lt;code&gt;sensors/garage/temp&lt;/code&gt;, anything matching that shape. We wrote the matcher iterative, deliberately not recursive, since both the topic and the filter are attacker-controlled strings arriving off the wire and a recursive matcher is a stack-exhaustion attack waiting to happen, tested it thoroughly against the spec's own worked examples, wired it into the broker's publish path. Subscribed with a wildcard, published to a matching topic, watched it arrive. Shipped it.&lt;/p&gt;

&lt;p&gt;Then we added sharding, and it broke, the same way the QoS1 gap did. No error, no panic, just a wildcard subscriber that stopped receiving anything.&lt;/p&gt;

&lt;p&gt;The routing rule hashes the topic &lt;em&gt;string&lt;/em&gt; to pick a shard. That's correct for exact matches: a subscription to &lt;code&gt;"sensors/temp"&lt;/code&gt; and a publish to &lt;code&gt;"sensors/temp"&lt;/code&gt; are the same string, same hash, same shard, every time. It's wrong for wildcards, and it took an embarrassingly long time to see why: &lt;code&gt;"sensors/+/temp"&lt;/code&gt; and &lt;code&gt;"sensors/kitchen/temp"&lt;/code&gt; are &lt;em&gt;different strings&lt;/em&gt;. They hash to different shards, almost certainly. The subscription succeeds. The shard holding it just never sees the publish that should have matched it, because that publish landed on a different shard entirely, one that has no idea the wildcard filter exists.&lt;/p&gt;

&lt;p&gt;The fix, once we saw it clearly, was one sentence: treat a wildcard subscription like &lt;code&gt;Register&lt;/code&gt; or &lt;code&gt;Disconnect&lt;/code&gt;, broadcast it to every shard, not just one. Exact-match subscriptions keep routing by hash; wildcard ones go everywhere, so whichever shard eventually gets the matching publish already has the filter to check it against. We verified it the only way that felt honest after the first miss: real &lt;code&gt;mosquitto_pub&lt;/code&gt;/&lt;code&gt;mosquitto_sub&lt;/code&gt;, eight different retained topics deliberately spread across shards, one wildcard subscriber, all eight arriving. Not because a unit test said so, because we watched it happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The half-built feature
&lt;/h2&gt;

&lt;p&gt;Which brings back the four-second hang. MQTT's QoS 1 means "the broker must acknowledge receipt of this publish." We built the wire format for that acknowledgment: encode it, decode it, round-trip it and wrote tests proving the bytes were exactly right. What we hadn't built was the broker actually &lt;em&gt;sending&lt;/em&gt; one when a client published at QoS 1. Every test we'd written was protocol-layer: bytes in, struct out, struct in, bytes out. None of them ever ran the connection-handling code that's supposed to notice "this was QoS 1, better say something back" because that logic didn't exist yet, and a test can't fail to cover code that isn't there.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mosquitto_pub -q 1&lt;/code&gt; was the first thing that ever actually waited for that acknowledgment instead of just checking whether the bytes we produced were well-formed. It hung, because we'd never asked anything to wait before.&lt;/p&gt;

&lt;p&gt;Same lesson, twice, in one project: encode/decode tests prove your wire format is correct in isolation. They prove nothing about whether the broker &lt;em&gt;behaves&lt;/em&gt; correctly end to end. The only way we found either gap was pointing a real, independent MQTT client at the compiled binary and watching what it actually did not what our own code claimed or the AI agent claimed it would do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bonus we didn't take
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://zerodepshack.com/" rel="noopener noreferrer"&gt;Zero Dependency&lt;/a&gt; offers a "Single File" bonus, that is, the whole submission as one source file someone could read top to bottom. We tried. A branch exists, ~3,885 lines, compiles, passes the full test suite.&lt;/p&gt;

&lt;p&gt;It's not a single-file program. It's a single &lt;em&gt;file&lt;/em&gt;. Every &lt;code&gt;mod&lt;/code&gt; boundary from the multi-file layout got preserved wholesale, the module walls are all still there, just copy-pasted into one document instead of split across several. That's a stapled concatenation, not the thing the bonus is actually asking for, and we knew the difference the moment we looked at it honestly.&lt;/p&gt;

&lt;p&gt;We didn't claim it. It's disclosed in the README as attempted and not achieved, the branch stayed public, and the bonus tally doesn't include it. It would have been trivial to point at "one file, compiles, tests pass" and call it done and nobody skimming a submission list would have caught the difference on the first pass. But this whole event is built around the idea that a naive, honestly-disclosed gap is worth more than a confident claim that doesn't survive a second look, and once you've internalized that watching your own QoS1 hang, it's hard to selectively unlearn it for the bonus you'd rather have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually got killed
&lt;/h2&gt;

&lt;p&gt;No &lt;a href="https://github.com/tokio-rs/tokio" rel="noopener noreferrer"&gt;&lt;code&gt;tokio&lt;/code&gt;&lt;/a&gt;, thread-per-connection plus a channel-based actor model instead of an async runtime. No &lt;a href="https://github.com/serde-rs/serde" rel="noopener noreferrer"&gt;&lt;code&gt;serde&lt;/code&gt;&lt;/a&gt;, the entire MQTT codec, every packet type, hand-rolled against the spec. No &lt;a href="https://github.com/crossbeam-rs/crossbeam" rel="noopener noreferrer"&gt;&lt;code&gt;crossbeam&lt;/code&gt;&lt;/a&gt;, the drop-oldest bounded queue above, built because the stdlib's one bounded-channel primitive had a backpressure policy we didn't want, not because a policy was missing entirely. No &lt;code&gt;mqtt&lt;/code&gt;/&lt;code&gt;aedes&lt;/code&gt;, the packages you'd actually reach for to do exactly this locally, without thinking about it, in about four lines and one &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision I'd take back
&lt;/h2&gt;

&lt;p&gt;Small, and it cost real time rather than teaching us anything: our decision log kept colliding. More than once, someone edited it from a copy that predated the last person's changes, and a decision we'd already written down quietly vanished, replaced by someone else's entry claiming the same slot. Not a code bug but rather a coordination one, which is anyways understandable for a team of 4. The fix was never clever, just "pull the actual current version of a shared file before you touch it," and we should have said that out loud on day one instead of relearning it three separate times.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, honestly
&lt;/h2&gt;

&lt;p&gt;118 automated tests, all passing, none of them the reason we found either real gap, live testing against actual clients was. A four-shard broker pushing roughly 495k msg/s against a single-shard broker's 402k at high concurrency across 64 topics. A real improvement, and a modest one, because at this scale the network syscalls dominate over how many threads are splitting the registry, and we'd rather say that plainly than let a comparison table imply more than it earned. Thirteen documented stdlib substitutions. Zero entries in &lt;code&gt;cargo tree&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The bug that mattered most wasn't in the MQTT spec, and it wasn't really in our code either. It was in trusting a test suite to tell us something it was never actually testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;Win or not, this was again one of the most fun 72 hours I've spent building something on a Hackathon Raptor's Hackthon. I cannot thank enough to my teammates at Team CodeBlitz for the experience and knowledge we shared together. We're eagerly waiting for results and feedback from the esteemed judges.&lt;/p&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/Dev-Am12/BlitzBroker" rel="noopener noreferrer"&gt;https://github.com/Dev-Am12/BlitzBroker&lt;/a&gt;&lt;br&gt;
Demo: &lt;a href="https://drive.google.com/drive/folders/1ScvHPNuxs_JTPaasKMEvzKrL9Wo5OLah?usp=sharing" rel="noopener noreferrer"&gt;https://drive.google.com/drive/folders/1ScvHPNuxs_JTPaasKMEvzKrL9Wo5OLah?usp=sharing&lt;/a&gt;&lt;br&gt;
Built for &lt;strong&gt;Zero Dependency 2026&lt;/strong&gt;, run by &lt;a href="https://www.raptors.dev/" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hackathonraptors</category>
      <category>zerodependency</category>
      <category>rust</category>
      <category>network</category>
    </item>
  </channel>
</rss>
