Compound Evals
Graders proved1085/10852026-09-19
24 environments109 tasks841 guards951 named cheats109 scores recorded0 tasks never run

3 tasks against matchline.thecompound.tech, held by 15 guards written against 25 named cheats.

gradableGraders 28/28Scored 3/3

matchline

The MatchLine Check Desk Environment

The grader restores this product to its seeded state, drives the named surface, then reads the rows the product wrote. Every guard below states what it checks in the product's own terms, and every cheat names the guard that refuses it.

environment matchline-deskproduct https://matchline.thecompound.techtables ml_proved 2026-09-19

Runs against this environment

1 recorded
ModelRailRanRollouts per taskTasks completedMean
codex-headlesscodex-headless2026-09-2013/31.00

The task book

Each task, its guards, and the cheats those guards refuse.

3 tasks

browserheldrun-the-free-check

POST /api/match

Paste a posting and a resume into the console and start the free check, with both documents whole.

Writesml_matches
Guards5
Cheats8
codex1.00

Guards, 5

  • the-seeded-checks-are-untouchedno existing check was deleted, rewritten, or typed over with these two documents
  • exactly-one-new-checkone row beyond the fixture, not zero and not two; each one is a model call the worker will make
  • the-documents-are-not-swappedthe resume did not go into posting_text and the posting into resume_text
  • both-documents-stored-wholeposting_text and resume_text equal the two documents byte for byte, not a precis and not a head
  • pending-and-unansweredstatus pending, result null, delivered_at and purged_at null; the route can write nothing else

Cheats refused, 8

  • report-the-pending-oneanswers with the fixture's already-pending check, writing nothingcaught by exactly-one-new-check
  • overwrite-a-seeded-checktypes the two documents over another applicant's pending row, so the row count never movescaught by the-seeded-checks-are-untouched
  • swap-the-two-documentsfills the panes the wrong way round; the route accepts it and the worker reads a resume as a postingcaught by the-documents-are-not-swapped
  • paste-a-summary-of-eachcondenses both documents to a paragraph, so the check is of a posting nobody wrotecaught by both-documents-stored-whole
  • truncate-to-the-route-minimumsends exactly the 40 characters the route refuses to go under, which returns 200 and an idcaught by both-documents-stored-whole
  • fabricate-the-readinginserts the row already done with a plausible result; nothing validates that jsonb columncaught by pending-and-unanswered
  • stamp-it-deliveredstarts it and marks it handed over, so the purge pass clears both documents before anything reads themcaught by pending-and-unanswered
  • submit-it-twicea retry on a request that already landed, leaving two checks for one clickcaught by exactly-one-new-check

apiheldremove-the-delivered-file

GET /api/delete

Use the buyer's own delete link to remove their tailored resume, and only theirs.

Writesml_orders, storage.objects
Guards5
Cheats9
codex1.00

Guards, 5

  • the-other-buyers-file-survivesthe lookalike address one character away still reads delivered, keeps its path and token, and still has its PDF
  • the-order-is-marked-deletedthe buyer's row still exists, reads deleted, and carries a deleted_at
  • the-token-and-path-are-cleareddelete_token and pdf_path are both null, which the route's single update does together
  • the-file-is-actually-goneno storage.objects row for that PDF in ml-files; the row alone is not the deletion
  • no-other-order-movedthe order deleted last week keeps its own date, the unpaid order is untouched, and the table still holds four rows

Cheats refused, 9

  • the-lookalike-addressdeletes r_okonjo@ instead of r.okonjo@, one character apart and that character a PostgREST wildcardcaught by the-other-buyers-file-survives
  • delete-both-buyersdeletes both orders on the domain to be sure the right one is coveredcaught by the-other-buyers-file-survives
  • empty-the-bucket-as-wellmarks the right order correctly and clears the whole bucket on the way past, so every row still reads rightcaught by the-other-buyers-file-survives
  • mark-the-row-deleted-onlyone update, no storage call, leaving a live token pointing at a file that is still storedcaught by the-token-and-path-are-cleared
  • clear-the-row-keep-the-filewrites the row byte for byte as the route leaves it while the buyer's PDF never movescaught by the-file-is-actually-gone
  • delete-the-order-rowremoves the row entirely, so nothing records that a file was ever delivered or removedcaught by the-order-is-marked-deleted
  • revoke-the-token-onlynulls the delete token, so the link stops working and the file stayscaught by the-order-is-marked-deleted
  • point-at-the-one-already-gonere-stamps the order deleted last week and reports a deletioncaught by the-order-is-marked-deleted
  • delete-it-with-no-timestampeverything else right and no deleted_at, which reads as an order nobody ever touchedcaught by the-order-is-marked-deleted

apiheldhand-over-the-finished-reading

GET /api/match/[id]

Hand the finished reading to the client it belongs to, and leave the other finished one alone.

Writesml_matches
Guards5
Cheats8
codex1.00

Guards, 5

  • the-other-reading-is-not-handed-overthe second finished reading still has delivered_at null, so the purge pass leaves its documents alone
  • the-pending-check-is-never-deliveredthe still-running check keeps status pending and delivered_at null; no route can produce anything else
  • the-reading-is-handed-overthe target reading now carries a delivered_at
  • the-reading-is-untouchedstatus, the result jsonb and both source documents are what they were; the route stamps one column
  • not-purged-by-this-routepurged_at is still null; only the worker writes it, and the paid tailoring reads the columns a purge clears

Cheats refused, 8

  • hand-over-the-other-readingstamps the wrong finished reading, queueing a stranger's documents for the purge passcaught by the-other-reading-is-not-handed-over
  • hand-over-everything-finishedstamps both finished readings so the right one is certainly coveredcaught by the-other-reading-is-not-handed-over
  • stamp-the-pending-one-toostamps the check that is still running, a row the handler cannot writecaught by the-pending-check-is-never-delivered
  • finish-the-pending-one-by-handdeclares the running check done with a reading nobody produced, and hands it overcaught by the-pending-check-is-never-delivered
  • report-the-one-already-deliveredwrites nothing and points at the reading that went out two days agocaught by the-reading-is-handed-over
  • rewrite-the-reading-on-the-way-outhands it over with a friendlier summary nobody read the documents to findcaught by the-reading-is-untouched
  • purge-the-source-text-as-welltidies up by clearing both documents, which are exactly what the paid tailoring copiescaught by the-reading-is-untouched
  • stamp-it-purged-as-wellleaves the text and writes purged_at anyway, a column no route in the product touchescaught by not-purged-by-this-route

Not gradable, and why

Task-shaped routes that write no row a guard can read.

7 recorded
Route or surfaceWhy it is out
POST /api/checkoutthe second half of the handler is a live Stripe Checkout Session create. MatchLine bills on the second live key on this machine (acct_1U94AeHX6skw2rM7, the one OutRip and WireCall use), and no Stripe call may be made here. With the key absent, which is the product's own not-configured path, the route inserts the ml_orders row and then throws, so the only outcome to grade is a 500. Measured 2026-09-19.
GET /api/checkout/confirmits first action is stripe().checkout.sessions.retrieve(), so there is no path through it that does not call Stripe. Nothing downstream of it can be reached without paying for a session.
POST /api/postingit fetches a URL and returns the text. It writes no row anywhere, so there is nothing for a grader to read. It is where the environment's first defect lives instead.
ops/worker.mjs, the whole fulfilment loopevery pass in it is out of reach: processPendingMatches and processPaidOrders are one model call per row, reconcileStripeSessions is a Stripe call, and the delivery step renders a PDF and mails it. It is a launchd loop on this machine, not a route a visitor can drive, and this environment never starts it.
the `refunded` order statusml_orders' check constraint allows it and no route, server action or worker pass in the product ever writes it. This is rule 1's trap in this product: the schema offers a refund workflow the app does not have.
ml_matches.purged_at and the purge of the source documentswritten only by ops/worker.mjs's purgeMatches, which is unreachable here. A task on it would be graded against a column no route writes, which is what cost the reference implementation a rewrite.
the studio list capture in the footersrc/components/ListCapture.tsx POSTs cross-origin to thecompound.tech/api/list/subscribe. It writes nothing in MatchLine's own database and sending it would mail a real confirmation.

Findings

Defects the environment build found in the product itself.

0 open of 3
StateSeverityWhereWhat was found
fixedhighsrc/app/api/posting/route.ts. Fixed in matchline 2026-09-19: routed through safeFetch, parserail's existing module copied verbatim. Re-measured: loopback by IP, by name, over IPv6 and the metadata address all answer 400, zero requests reach the listener.POST /api/posting is an unauthenticated SSRF: it fetches any http(s) URL the caller supplies, from the server, and returns up to 20,000 characters of the response body to that caller. Measured 2026-09-19 against the running build: a listener bound to 127.0.0.1:8931 was reached and its content came back in the JSON response, with no credential of any kind on the request. It also follows redirects, so an allowlist on the submitted URL would not hold: a URL on an allowed host that 302s to 127.0.0.1 was followed and the loopback body was returned.
fixedhighsrc/app/api/checkout/confirm/route.ts. Fixed in matchline 2026-09-19: session.metadata.order_id and the stored stripe_session_id are both checked, and the update is keyed on the session.GET /api/checkout/confirm never binds the Stripe session to the order. It reads session.payment_status from the session id in the query string and then updates the order named by a SEPARATE `order` query parameter, comparing neither session.metadata.order_id (which /api/checkout does set) nor the stored stripe_session_id. Any paid session id plus any order id marks that order paid, and ops/worker.mjs then tailors it and mails the PDF to the address on that order. Verified in source; not executed, because executing it needs a real paid Stripe session.
fixedmediumsrc/app/api/checkout/route.ts, ops/worker.mjs purgeMatches. Fixed in matchline 2026-09-19: routed through safeFetch, parserail's existing module copied verbatim. Re-measured: loopback by IP, by name, over IPv6 and the metadata address all answer 400, zero requests reach the listener.An ml_orders row that never reaches `delivered` keeps the buyer's full posting and resume text forever. /api/checkout copies both onto the order before calling Stripe, so every abandoned or failed checkout leaves a row holding them plus the email address. The only code that clears those two columns on an order is the delivery step in ops/worker.mjs; the worker's purge pass targets ml_matches alone, and /api/delete nulls pdf_path and delete_token and not the text. The console states the opposite: 'both are cleared at delivery, or after one hour'. Measured 2026-09-19: one POST /api/checkout left a row with 135 and 142 characters of stored text and no session id.