{"id":5007,"date":"2026-09-08T00:54:31","date_gmt":"2026-09-08T03:54:31","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/08\/if-your-agent-wrote-the-test-ignore-the-green-build\/"},"modified":"2026-09-08T00:54:31","modified_gmt":"2026-09-08T03:54:31","slug":"if-your-agent-wrote-the-test-ignore-the-green-build","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/08\/if-your-agent-wrote-the-test-ignore-the-green-build\/","title":{"rendered":"If Your Agent Wrote the Test, Ignore the Green Build"},"content":{"rendered":"<div>\n<div><\/div>\n<p>Do not hide fixtures inside pytest helpers. Pytest may wrap the grader later. It must not replace the oracle files.<\/p>\n<h3> <a name=\"humanwritten-cases\" href=\"#humanwritten-cases\"> <\/a> Human-written cases <\/h3>\n<div>\n<pre><code><span>{<\/span><span> <\/span><span>\"cases\"<\/span><span>:<\/span><span> <\/span><span>[<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"id\"<\/span><span>:<\/span><span> <\/span><span>\"refund-partial-usd\"<\/span><span>,<\/span><span> <\/span><span>\"input\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"order_id\"<\/span><span>:<\/span><span> <\/span><span>\"ord_1001\"<\/span><span>,<\/span><span> <\/span><span>\"paid_cents\"<\/span><span>:<\/span><span> <\/span><span>5000<\/span><span>,<\/span><span> <\/span><span>\"refund_cents\"<\/span><span>:<\/span><span> <\/span><span>1200<\/span><span>,<\/span><span> <\/span><span>\"currency\"<\/span><span>:<\/span><span> <\/span><span>\"USD\"<\/span><span> <\/span><span>},<\/span><span> <\/span><span>\"expect\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"status\"<\/span><span>:<\/span><span> <\/span><span>\"ok\"<\/span><span>,<\/span><span> <\/span><span>\"refunded_cents\"<\/span><span>:<\/span><span> <\/span><span>1200<\/span><span>,<\/span><span> <\/span><span>\"remaining_cents\"<\/span><span>:<\/span><span> <\/span><span>3800<\/span><span> <\/span><span>}<\/span><span> <\/span><span>},<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"id\"<\/span><span>:<\/span><span> <\/span><span>\"refund-overpay-rejected\"<\/span><span>,<\/span><span> <\/span><span>\"input\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"order_id\"<\/span><span>:<\/span><span> <\/span><span>\"ord_1002\"<\/span><span>,<\/span><span> <\/span><span>\"paid_cents\"<\/span><span>:<\/span><span> <\/span><span>5000<\/span><span>,<\/span><span> <\/span><span>\"refund_cents\"<\/span><span>:<\/span><span> <\/span><span>5001<\/span><span>,<\/span><span> <\/span><span>\"currency\"<\/span><span>:<\/span><span> <\/span><span>\"USD\"<\/span><span> <\/span><span>},<\/span><span> <\/span><span>\"expect\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"status\"<\/span><span>:<\/span><span> <\/span><span>\"rejected\"<\/span><span>,<\/span><span> <\/span><span>\"reason\"<\/span><span>:<\/span><span> <\/span><span>\"amount_exceeds_paid\"<\/span><span> <\/span><span>}<\/span><span> <\/span><span>},<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"id\"<\/span><span>:<\/span><span> <\/span><span>\"refund-zero-rejected\"<\/span><span>,<\/span><span> <\/span><span>\"input\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"order_id\"<\/span><span>:<\/span><span> <\/span><span>\"ord_1003\"<\/span><span>,<\/span><span> <\/span><span>\"paid_cents\"<\/span><span>:<\/span><span> <\/span><span>5000<\/span><span>,<\/span><span> <\/span><span>\"refund_cents\"<\/span><span>:<\/span><span> <\/span><span>0<\/span><span>,<\/span><span> <\/span><span>\"currency\"<\/span><span>:<\/span><span> <\/span><span>\"USD\"<\/span><span> <\/span><span>},<\/span><span> <\/span><span>\"expect\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"status\"<\/span><span>:<\/span><span> <\/span><span>\"rejected\"<\/span><span>,<\/span><span> <\/span><span>\"reason\"<\/span><span>:<\/span><span> <\/span><span>\"amount_not_positive\"<\/span><span> <\/span><span>}<\/span><span> <\/span><span>}<\/span><span> <\/span><span>]<\/span><span> <\/span><span>}<\/span><span> <\/span><\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Those numbers came from you, not the model. Guard them like production credentials. They are the only truth for this task.<\/p>\n<h3> <a name=\"minimal-handler-to-grade\" href=\"#minimal-handler-to-grade\"> <\/a> Minimal handler to grade <\/h3>\n<p>Label this stub as an example, not production. <\/p>\n<div>\n<pre><code><span># src\/handler.py <\/span><span>def<\/span> <span>handle<\/span><span>(<\/span><span>payload<\/span><span>:<\/span> <span>dict<\/span><span>)<\/span> <span>-&gt;<\/span> <span>dict<\/span><span>:<\/span> <span>paid<\/span> <span>=<\/span> <span>int<\/span><span>(<\/span><span>payload<\/span><span>[<\/span><span>\"<\/span><span>paid_cents<\/span><span>\"<\/span><span>])<\/span> <span>refund<\/span> <span>=<\/span> <span>int<\/span><span>(<\/span><span>payload<\/span><span>[<\/span><span>\"<\/span><span>refund_cents<\/span><span>\"<\/span><span>])<\/span> <span>if<\/span> <span>refund<\/span> <span>&lt;=<\/span> <span>0<\/span><span>:<\/span> <span>return<\/span> <span>{<\/span><span>\"<\/span><span>status<\/span><span>\"<\/span><span>:<\/span> <span>\"<\/span><span>rejected<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>reason<\/span><span>\"<\/span><span>:<\/span> <span>\"<\/span><span>amount_not_positive<\/span><span>\"<\/span><span>}<\/span> <span>if<\/span> <span>refund<\/span> <span>&gt;<\/span> <span>paid<\/span><span>:<\/span> <span>return<\/span> <span>{<\/span><span>\"<\/span><span>status<\/span><span>\"<\/span><span>:<\/span> <span>\"<\/span><span>rejected<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>reason<\/span><span>\"<\/span><span>:<\/span> <span>\"<\/span><span>amount_exceeds_paid<\/span><span>\"<\/span><span>}<\/span> <span>return<\/span> <span>{<\/span> <span>\"<\/span><span>status<\/span><span>\"<\/span><span>:<\/span> <span>\"<\/span><span>ok<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>refunded_cents<\/span><span>\"<\/span><span>:<\/span> <span>refund<\/span><span>,<\/span> <span>\"<\/span><span>remaining_cents<\/span><span>\"<\/span><span>:<\/span> <span>paid<\/span> <span>-<\/span> <span>refund<\/span><span>,<\/span> <span>}<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>An agent may replace this file later. It cannot replace <code>oracle\/cases.json<\/code> now. That restriction is the entire method.<\/p>\n<h3> <a name=\"grader\" href=\"#grader\"> <\/a> Grader <\/h3>\n<div>\n<pre><code><span># tools\/grade.py <\/span><span>from<\/span> <span>__future__<\/span> <span>import<\/span> <span>annotations<\/span> <span>import<\/span> <span>json<\/span> <span>import<\/span> <span>sys<\/span> <span>from<\/span> <span>pathlib<\/span> <span>import<\/span> <span>Path<\/span> <span>ROOT<\/span> <span>=<\/span> <span>Path<\/span><span>(<\/span><span>__file__<\/span><span>).<\/span><span>resolve<\/span><span>().<\/span><span>parents<\/span><span>[<\/span><span>1<\/span><span>]<\/span> <span>ORACLE<\/span> <span>=<\/span> <span>ROOT<\/span> <span>\/<\/span> <span>\"<\/span><span>oracle<\/span><span>\"<\/span> <span>\/<\/span> <span>\"<\/span><span>cases.json<\/span><span>\"<\/span> <span>def<\/span> <span>load_cases<\/span><span>()<\/span> <span>-&gt;<\/span> <span>list<\/span><span>[<\/span><span>dict<\/span><span>]:<\/span> <span>return<\/span> <span>json<\/span><span>.<\/span><span>loads<\/span><span>(<\/span><span>ORACLE<\/span><span>.<\/span><span>read_text<\/span><span>())[<\/span><span>\"<\/span><span>cases<\/span><span>\"<\/span><span>]<\/span> <span>def<\/span> <span>main<\/span><span>()<\/span> <span>-&gt;<\/span> <span>int<\/span><span>:<\/span> <span>from<\/span> <span>src.handler<\/span> <span>import<\/span> <span>handle<\/span> <span>from<\/span> <span>oracle.invariants<\/span> <span>import<\/span> <span>check<\/span> <span>as<\/span> <span>check_invariants<\/span> <span>failures<\/span> <span>=<\/span> <span>[]<\/span> <span>for<\/span> <span>case<\/span> <span>in<\/span> <span>load_cases<\/span><span>():<\/span> <span>got<\/span> <span>=<\/span> <span>handle<\/span><span>(<\/span><span>case<\/span><span>[<\/span><span>\"<\/span><span>input<\/span><span>\"<\/span><span>])<\/span> <span>try<\/span><span>:<\/span> <span>check_invariants<\/span><span>(<\/span><span>got<\/span><span>)<\/span> <span>except<\/span> <span>AssertionError<\/span> <span>as<\/span> <span>exc<\/span><span>:<\/span> <span>failures<\/span><span>.<\/span><span>append<\/span><span>(<\/span> <span>{<\/span><span>\"<\/span><span>id<\/span><span>\"<\/span><span>:<\/span> <span>case<\/span><span>[<\/span><span>\"<\/span><span>id<\/span><span>\"<\/span><span>],<\/span> <span>\"<\/span><span>invariant<\/span><span>\"<\/span><span>:<\/span> <span>str<\/span><span>(<\/span><span>exc<\/span><span>),<\/span> <span>\"<\/span><span>got<\/span><span>\"<\/span><span>:<\/span> <span>got<\/span><span>}<\/span> <span>)<\/span> <span>continue<\/span> <span>if<\/span> <span>got<\/span> <span>!=<\/span> <span>case<\/span><span>[<\/span><span>\"<\/span><span>expect<\/span><span>\"<\/span><span>]:<\/span> <span>failures<\/span><span>.<\/span><span>append<\/span><span>(<\/span> <span>{<\/span><span>\"<\/span><span>id<\/span><span>\"<\/span><span>:<\/span> <span>case<\/span><span>[<\/span><span>\"<\/span><span>id<\/span><span>\"<\/span><span>],<\/span> <span>\"<\/span><span>expected<\/span><span>\"<\/span><span>:<\/span> <span>case<\/span><span>[<\/span><span>\"<\/span><span>expect<\/span><span>\"<\/span><span>],<\/span> <span>\"<\/span><span>got<\/span><span>\"<\/span><span>:<\/span> <span>got<\/span><span>}<\/span> <span>)<\/span> <span>if<\/span> <span>failures<\/span><span>:<\/span> <span>print<\/span><span>(<\/span><span>json<\/span><span>.<\/span><span>dumps<\/span><span>({<\/span><span>\"<\/span><span>pass<\/span><span>\"<\/span><span>:<\/span> <span>False<\/span><span>,<\/span> <span>\"<\/span><span>failures<\/span><span>\"<\/span><span>:<\/span> <span>failures<\/span><span>},<\/span> <span>indent<\/span><span>=<\/span><span>2<\/span><span>))<\/span> <span>return<\/span> <span>1<\/span> <span>print<\/span><span>(<\/span><span>json<\/span><span>.<\/span><span>dumps<\/span><span>({<\/span><span>\"<\/span><span>pass<\/span><span>\"<\/span><span>:<\/span> <span>True<\/span><span>,<\/span> <span>\"<\/span><span>count<\/span><span>\"<\/span><span>:<\/span> <span>len<\/span><span>(<\/span><span>load_cases<\/span><span>())}))<\/span> <span>return<\/span> <span>0<\/span> <span>if<\/span> <span>__name__<\/span> <span>==<\/span> <span>\"<\/span><span>__main__<\/span><span>\"<\/span><span>:<\/span> <span>raise<\/span> <span>SystemExit<\/span><span>(<\/span><span>main<\/span><span>())<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<div>\n<pre><code><span># oracle\/invariants.py <\/span><span>def<\/span> <span>check<\/span><span>(<\/span><span>result<\/span><span>:<\/span> <span>dict<\/span><span>)<\/span> <span>-&gt;<\/span> <span>None<\/span><span>:<\/span> <span>if<\/span> <span>\"<\/span><span>remaining_cents<\/span><span>\"<\/span> <span>in<\/span> <span>result<\/span><span>:<\/span> <span>assert<\/span> <span>result<\/span><span>[<\/span><span>\"<\/span><span>remaining_cents<\/span><span>\"<\/span><span>]<\/span> <span>&gt;=<\/span> <span>0<\/span><span>,<\/span> <span>\"<\/span><span>remaining_cents is negative<\/span><span>\"<\/span> <span>if<\/span> <span>result<\/span><span>.<\/span><span>get<\/span><span>(<\/span><span>\"<\/span><span>status<\/span><span>\"<\/span><span>)<\/span> <span>==<\/span> <span>\"<\/span><span>ok<\/span><span>\"<\/span><span>:<\/span> <span>assert<\/span> <span>\"<\/span><span>refunded_cents<\/span><span>\"<\/span> <span>in<\/span> <span>result<\/span><span>,<\/span> <span>\"<\/span><span>ok result missing refunded_cents<\/span><span>\"<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Run it before the agent starts work. Run it after every candidate patch. A red grader is information you wanted. <\/p>\n<div>\n<pre><code><span>export <\/span><span>PYTHONPATH<\/span><span>=<\/span><span>.<\/span> python tools\/grade.py <span>echo<\/span> <span>$?<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>A rewritten oracle is contamination. Treat that diff as an incident. Do not chat the model through it.<\/p>\n<h2> <a name=\"mechanical-freeze\" href=\"#mechanical-freeze\"> <\/a> Mechanical freeze <\/h2>\n<p>Policy comments do not stop an agent loop. Git status does stop an agent loop. Enforce the freeze with a command. <\/p>\n<div>\n<pre><code><span># tools\/freeze_check.py <\/span><span>from<\/span> <span>__future__<\/span> <span>import<\/span> <span>annotations<\/span> <span>import<\/span> <span>subprocess<\/span> <span>import<\/span> <span>sys<\/span> <span>FROZEN<\/span> <span>=<\/span> <span>(<\/span> <span>\"<\/span><span>oracle\/<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>tools\/grade.py<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>tools\/freeze_check.py<\/span><span>\"<\/span><span>,<\/span> <span>)<\/span> <span>def<\/span> <span>changed_files<\/span><span>()<\/span> <span>-&gt;<\/span> <span>list<\/span><span>[<\/span><span>str<\/span><span>]:<\/span> <span>staged<\/span> <span>=<\/span> <span>subprocess<\/span><span>.<\/span><span>check_output<\/span><span>(<\/span> <span>[<\/span><span>\"<\/span><span>git<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>diff<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>--name-only<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>--cached<\/span><span>\"<\/span><span>],<\/span> <span>text<\/span><span>=<\/span><span>True<\/span><span>,<\/span> <span>)<\/span> <span>unstaged<\/span> <span>=<\/span> <span>subprocess<\/span><span>.<\/span><span>check_output<\/span><span>(<\/span> <span>[<\/span><span>\"<\/span><span>git<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>diff<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>--name-only<\/span><span>\"<\/span><span>],<\/span> <span>text<\/span><span>=<\/span><span>True<\/span><span>,<\/span> <span>)<\/span> <span>names<\/span> <span>=<\/span> <span>set<\/span><span>()<\/span> <span>for<\/span> <span>block<\/span> <span>in<\/span> <span>(<\/span><span>staged<\/span><span>,<\/span> <span>unstaged<\/span><span>):<\/span> <span>names<\/span><span>.<\/span><span>update<\/span><span>(<\/span> <span>line<\/span><span>.<\/span><span>strip<\/span><span>()<\/span> <span>for<\/span> <span>line<\/span> <span>in<\/span> <span>block<\/span><span>.<\/span><span>splitlines<\/span><span>()<\/span> <span>if<\/span> <span>line<\/span><span>.<\/span><span>strip<\/span><span>()<\/span> <span>)<\/span> <span>return<\/span> <span>sorted<\/span><span>(<\/span><span>names<\/span><span>)<\/span> <span>def<\/span> <span>is_frozen<\/span><span>(<\/span><span>path<\/span><span>:<\/span> <span>str<\/span><span>)<\/span> <span>-&gt;<\/span> <span>bool<\/span><span>:<\/span> <span>return<\/span> <span>any<\/span><span>(<\/span><span>path<\/span> <span>==<\/span> <span>prefix<\/span> <span>or<\/span> <span>path<\/span><span>.<\/span><span>startswith<\/span><span>(<\/span><span>prefix<\/span><span>)<\/span> <span>for<\/span> <span>prefix<\/span> <span>in<\/span> <span>FROZEN<\/span><span>)<\/span> <span>def<\/span> <span>main<\/span><span>()<\/span> <span>-&gt;<\/span> <span>int<\/span><span>:<\/span> <span>blocked<\/span> <span>=<\/span> <span>[<\/span><span>path<\/span> <span>for<\/span> <span>path<\/span> <span>in<\/span> <span>changed_files<\/span><span>()<\/span> <span>if<\/span> <span>is_frozen<\/span><span>(<\/span><span>path<\/span><span>)]<\/span> <span>if<\/span> <span>blocked<\/span><span>:<\/span> <span>print<\/span><span>(<\/span><span>\"<\/span><span>frozen path edited:<\/span><span>\"<\/span><span>)<\/span> <span>for<\/span> <span>path<\/span> <span>in<\/span> <span>blocked<\/span><span>:<\/span> <span>print<\/span><span>(<\/span><span>f<\/span><span>\"<\/span><span>- <\/span><span>{<\/span><span>path<\/span><span>}<\/span><span>\"<\/span><span>)<\/span> <span>return<\/span> <span>2<\/span> <span>print<\/span><span>(<\/span><span>\"<\/span><span>freeze check passed<\/span><span>\"<\/span><span>)<\/span> <span>return<\/span> <span>0<\/span> <span>if<\/span> <span>__name__<\/span> <span>==<\/span> <span>\"<\/span><span>__main__<\/span><span>\"<\/span><span>:<\/span> <span>raise<\/span> <span>SystemExit<\/span><span>(<\/span><span>main<\/span><span>())<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Wire one command for humans and CI: <\/p>\n<div>\n<pre><code>python tools\/freeze_check.py <span>&amp;&amp;<\/span> <span>PYTHONPATH<\/span><span>=<\/span><span>.<\/span> python tools\/grade.py <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>If freeze check returns 2, reset frozen paths. Do not negotiate with the model. Restore the answer key immediately. <\/p>\n<div>\n<pre><code>git checkout <span>--<\/span> oracle tools\/grade.py tools\/freeze_check.py <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<h2> <a name=\"decision-table\" href=\"#decision-table\"> <\/a> Decision table <\/h2>\n<p>Paste this table into the pull request.<\/p>\n<div>\n<table>\n<thead>\n<tr>\n<th>Observed state<\/th>\n<th>Agent may edit <code>src\/<\/code> <\/th>\n<th>Merge?<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>No oracle committed<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Freeze check dirty<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Grader red, oracle untouched<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Grader green, freeze clean<\/td>\n<td>Stop generating<\/td>\n<td>Review only<\/td>\n<\/tr>\n<tr>\n<td>Patch includes oracle hunks<\/td>\n<td>Reject the patch<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Snapshots regenerated with src<\/td>\n<td>Treat as incident<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Invariant assertion deleted<\/td>\n<td>Reject the patch<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>If two rows apply, take the stricter row. Do not average conflicting rows. Strictness is the point of the table.<\/p>\n<h2> <a name=\"cheap-generation-still-needs-a-local-grade\" href=\"#cheap-generation-still-needs-a-local-grade\"> <\/a> Cheap generation still needs a local grade <\/h2>\n<p>You do not need a paid API here. You need retries, a branch, and freeze. Grade locally after every generated hunk.<\/p>\n<p>Disclosure: This article was prepared as part of MonkeyCode&#8217;s product outreach.<\/p>\n<p>MonkeyCode is an open-source coding assistant. It provides free model access and a free server option. Point it at <code>src\/<\/code> only. Keep <code>oracle\/<\/code> on your machine. Grade every candidate patch locally.<\/p>\n<p>A practical sequence looks like this: <\/p>\n<div>\n<pre><code>git switch <span>-c<\/span> agent\/refund-oracle <span>PYTHONPATH<\/span><span>=<\/span><span>.<\/span> python tools\/grade.py <span>&gt;<\/span> \/tmp\/grade.json <span># give the assistant \/tmp\/grade.json<\/span> <span># do not grant write access to oracle\/<\/span> git apply <span>--check<\/span> \/tmp\/src.patch git apply \/tmp\/src.patch python tools\/freeze_check.py <span>||<\/span> git checkout <span>--<\/span> oracle tools <span>PYTHONPATH<\/span><span>=<\/span><span>.<\/span> python tools\/grade.py <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Feed the failing JSON into the assistant. Do not grant oracle write permission. Hidden fixtures are a feature, not a problem.<\/p>\n<p>The free server is useful for patch generation. It is not useful as a grader host. Your laptop already knows the answer key.<\/p>\n<h2> <a name=\"reproducible-test-plan\" href=\"#reproducible-test-plan\"> <\/a> Reproducible test plan <\/h2>\n<p>Run this plan on a clean clone. No extra services are required today.<\/p>\n<ol>\n<li>Commit the three oracle cases above.<\/li>\n<li>Replace <code>src\/handler.py<\/code> with a constant ok response.<\/li>\n<li>Run the grader and confirm exit code 1.<\/li>\n<li>Confirm the JSON lists all three case ids.<\/li>\n<li>Restore reject paths only, then grade again.<\/li>\n<li>Confirm <code>refund-partial-usd<\/code> still fails loudly.<\/li>\n<li>Restore remaining_cents math and expect exit 0.<\/li>\n<li>Edit oracle remaining_cents to the wrong number.<\/li>\n<li>Run freeze_check and confirm exit code 2.<\/li>\n<li>Restore the oracle and confirm freeze_check exit 0.<\/li>\n<\/ol>\n<p>If step 9 does not fail, your freeze is theater. Fix the path prefixes before inviting agents.<\/p>\n<p>Add one pytest wrapper if your CI demands pytest. Keep it thin. The wrapper should call <code>grade.py<\/code> and assert exit code 0. <\/p>\n<div>\n<pre><code><span># tests\/test_oracle_wiring.py <\/span><span>import<\/span> <span>subprocess<\/span> <span>import<\/span> <span>sys<\/span> <span>from<\/span> <span>pathlib<\/span> <span>import<\/span> <span>Path<\/span> <span>ROOT<\/span> <span>=<\/span> <span>Path<\/span><span>(<\/span><span>__file__<\/span><span>).<\/span><span>resolve<\/span><span>().<\/span><span>parents<\/span><span>[<\/span><span>1<\/span><span>]<\/span> <span>def<\/span> <span>test_grader_exits_zero<\/span><span>():<\/span> <span>result<\/span> <span>=<\/span> <span>subprocess<\/span><span>.<\/span><span>run<\/span><span>(<\/span> <span>[<\/span><span>sys<\/span><span>.<\/span><span>executable<\/span><span>,<\/span> <span>\"<\/span><span>tools\/grade.py<\/span><span>\"<\/span><span>],<\/span> <span>cwd<\/span><span>=<\/span><span>ROOT<\/span><span>,<\/span> <span>env<\/span><span>=<\/span><span>{<\/span><span>**<\/span><span>dict<\/span><span>(<\/span><span>**<\/span><span>{<\/span><span>k<\/span><span>:<\/span> <span>v<\/span> <span>for<\/span> <span>k<\/span><span>,<\/span> <span>v<\/span> <span>in<\/span> <span>__import__<\/span><span>(<\/span><span>\"<\/span><span>os<\/span><span>\"<\/span><span>).<\/span><span>environ<\/span><span>.<\/span><span>items<\/span><span>()}),<\/span> <span>\"<\/span><span>PYTHONPATH<\/span><span>\"<\/span><span>:<\/span> <span>str<\/span><span>(<\/span><span>ROOT<\/span><span>)},<\/span> <span>)<\/span> <span>assert<\/span> <span>result<\/span><span>.<\/span><span>returncode<\/span> <span>==<\/span> <span>0<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>That test proves wiring. It does not prove the agent is honest. Honesty comes from the frozen fixtures.<\/p>\n<h2> <a name=\"failure-analysis\" href=\"#failure-analysis\"> <\/a> Failure analysis <\/h2>\n<p>When the grader stays red, read the JSON. Do not open a long chat thread.<\/p>\n<ul>\n<li>One field differs: fix <code>src\/<\/code> and regrade.<\/li>\n<li>Every case returns one shape: input is ignored.<\/li>\n<li>An invariant fires: a written law was broken.<\/li>\n<li>Freeze check fires: the loop edited the key.<\/li>\n<\/ul>\n<p>Teams excuse that last case constantly. Do not excuse it this time. Drop the entire patch.<\/p>\n<p>A useful debug habit is boring. Save <code>\/tmp\/grade.json<\/code> per attempt. Diff those files, not chat logs. Chat logs are not replayable evidence. <\/p>\n<div>\n<pre><code><span>mkdir<\/span> <span>-p<\/span> \/tmp\/grades <span>PYTHONPATH<\/span><span>=<\/span><span>.<\/span> python tools\/grade.py <span>&gt;<\/span> \/tmp\/grades\/<span>$(<\/span><span>date<\/span> +%s<span>)<\/span>.json <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>If later attempts only change wording, stop. The model is negotiating. Your oracle already answered.<\/p>\n<h2> <a name=\"limitations\" href=\"#limitations\"> <\/a> Limitations <\/h2>\n<p>Oracles are narrow on purpose. Equality is not product taste. This method will feel slow during spikes.<\/p>\n<p>Do not use this approach when:<\/p>\n<ul>\n<li>you are still inventing the API shape<\/li>\n<li>expected output is honestly unknown<\/li>\n<li>the artifact is a plot, not a function<\/li>\n<li>there is no git history to freeze<\/li>\n<li>a human already watches every hunk live<\/li>\n<\/ul>\n<p>An oracle can still be wrong. Change it in a dedicated commit. Never bundle an oracle edit with agent <code>src\/<\/code> edits.<\/p>\n<p>The grader will not invent missing cases. You still add fixtures yourself. That work remains human by design.<\/p>\n<p>This also fails for flaky time-based output. Freeze clocks in the handler boundary. Do not freeze live timestamps inside cases.<\/p>\n<h2> <a name=\"what-this-argument-is-not\" href=\"#what-this-argument-is-not\"> <\/a> What this argument is not <\/h2>\n<p>This is not a model leaderboard post. This is not a latency or throughput claim. This is not a promise that free servers replace reviewers.<\/p>\n<p>Cheap generation is useful in bounded loops. Self-graded generation is not quality control. Split those jobs in the repository.<\/p>\n<p>Own the expected result in git. Let the loop struggle against that file. If the loop can edit the answer key, the green build is theater.<\/p>\n<p>If you need a cheap <code>src\/<\/code>-only patch generator against that gate, MonkeyCode&#8217;s free model access and free server option are enough to run the loop. Keep grading on your side of the freeze.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/airs_6907\/if-your-agent-wrote-the-test-ignore-the-green-build-28ma\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Do not hide fixtures inside pytest helpers. Pytest may wrap the grader later. It must not replace the oracle files. Human-written cases { &#8220;cases&#8221;: [ { &#8220;id&#8221;: &#8220;refund-partial-usd&#8221;, &#8220;input&#8221;: { &#8220;order_id&#8221;: &#8220;ord_1001&#8221;, &#8220;paid_cents&#8221;: 5000, &#8220;refund_cents&#8221;: 1200, &#8220;currency&#8221;: &#8220;USD&#8221; }, &#8220;expect&#8221;: { &#8220;status&#8221;: &#8220;ok&#8221;, &#8220;refunded_cents&#8221;: 1200, &#8220;remaining_cents&#8221;: 3800 } }, { &#8220;id&#8221;: &#8220;refund-overpay-rejected&#8221;, &#8220;input&#8221;: { &#8220;order_id&#8221;: [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":5006,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"webixso_pending_account_ids":""},"categories":[41],"tags":[],"class_list":["post-5007","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5007","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=5007"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5007\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/5006"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=5007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=5007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=5007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}