Repository navigation
Epic: Level 2 · Easy recipes #76
Copy link
Copy link
Closed
13 / 1313 of 13 issues completedLabels
level: 2 easyCandidate sets and explicit uncertain or no-match outcomesCandidate sets and explicit uncertain or no-match outcomespriority: p1Next up once unblockedNext up once unblockedtype: epicTracking issue that groups other issuesTracking issue that groups other issues
Milestone
Description
Recipes 11 to 23. Context-sensitive matching, scoring, or verification with candidate sets and explicit uncertain or no-match outcomes.
Recipes (13)
- Recipe 11: Clarification selection #11 Clarification selection ·
Choice· Agent orchestration - Recipe 12: Thesaurus word selection #12 Thesaurus word selection ·
Choice· Language & content - Recipe 13: Candidate rewrite selection #13 Candidate rewrite selection ·
Choice· Language & content - Recipe 14: Source span selection #14 Source span selection ·
Choice· Data quality & knowledge graphs - Recipe 15: Sensitive text triage #15 Sensitive text triage ·
Noul· Trust & security - Recipe 16: Discord moderation triage #16 Discord moderation triage ·
Choice· Trust & security - Recipe 17: Passage reranking #17 Passage reranking ·
Score· Search & retrieval - Recipe 18: Duplicate incident matching #18 Duplicate incident matching ·
Choice· Workflow & service operations - Recipe 19: CI failure classification #19 CI failure classification ·
Choice· Software engineering - Recipe 20: Claim support classification #20 Claim support classification ·
Choice· Search & retrieval - Recipe 21: Quiz answer adjudication #21 Quiz answer adjudication ·
Choice· Games & simulation - Recipe 22: CMDB asset matching #22 CMDB asset matching ·
Choice· Data quality & knowledge graphs - Recipe 23: Pairwise answer evaluation #23 Pairwise answer evaluation ·
Choice· Evaluation & optimization
Done when
- Every recipe above is merged and its issue closed
- The README catalog is regenerated (
python tools/render_catalog.py) and shows these recipes as published - CI is green on
main - A consistency pass across the level is done: section order, terminology, chart style, run-mode header
Dependencies
- Scheduled after Epic: Level 1 · Beginner recipes #75. A recipe here may start earlier once its own blockers are closed.
- Tracked in Cookbook roadmap: 60 recipes from foundation to release #80
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
level: 2 easyCandidate sets and explicit uncertain or no-match outcomesCandidate sets and explicit uncertain or no-match outcomespriority: p1Next up once unblockedNext up once unblockedtype: epicTracking issue that groups other issuesTracking issue that groups other issues
Activity
CompleteDotTech commentedon Oct 9, 2026
Wave 2 consistency review
Read-only cross-recipe pass over the thirteen Level 2 recipes
recipes/11-*throughrecipes/23-*atorigin/maintip65978eeafb2ed7a8a19d5f02c6ce8087b0f5e943(the recipe 19 merge, PR #180), with recipes 01 and 08 andrecipes/_template/as references, againstCONTRIBUTING.md,docs/recipe-template.md,docs/evaluation.md,docs/notebook-style.mdanddocs/glossary.md. No notebook was executed and no test suite was run. Correctness of individual numbers is out of scope (each recipe had two to four Opus rounds); this is about whether the thirteen read as one cookbook, and about residual contract slips.Findings are grouped by route. Each gives recipe, file/cell, the exact text, and the fix.
Route → #163 (recipe-side follow-ups)
R1. Recipe 18 prints
{selection}alone on every validation line — 14 lines, 5 cells.notebook.ipynbcellspythons-part,gate-sweep,evaluation-validation,side-effect. E.g.pythons-partoutput:chosen confidence gate, frozen from here on: 0.8400 (over 9 real-candidate matches in validation, 1 of them wrong) (a selection step, not a reported result). The other twelve all print{selection}{check}. This is exactly the under-disclosuredocs/recipe-template.mdwarns about ("in a synthetic or scripted run avalidationnumber needs both, so it prints{selection}{check}, never{selection}alone"). Fix: append{check}to all 14 lines.R2. Validation figure titles take four different forms across the thirteen.
{check}only — 13evaluation-gate, 19gate-sweep-code, 20rule-sweep, 21eval-curve, 22gate-sweep{selection}{check}— 14sweep-code:title=f"Validation confidence gate, named picks only{selection}{check}"{selection}only — 15gate-curve:title=f"Validation confidence gate{selection}"; 15sweep-chart:title=f"Validation, {len(val_examples)} examples{selection}"; 18gate-sweep:title=f"Confidence sweep over validation{selection}"gate-sweep:title="Validation confidence sweep"Fix: one form everywhere (
{check}only is the majority and reads best in a title); see #164 T2 to write the convention down.R3. Recipe 17 prints six metric rows with no disclosure label — the only unlabelled metric lines in the thirteen.
notebook.ipynbcellselective:print(f" gate {t:.4f}: coverage {cov:.4f}, accuracy {acc:.4f}, risk {risk:.4f}"), output e.g.gate 0.5600: coverage 0.9000, accuracy 0.9444, risk 0.0556. These aretestnumbers in a synthetic run. Fix: append{check}(and to therisk-coverage curve (test), …header line above them).R4. Recipe 23 is the only confusion matrix in the thirteen plotted with nothing printed.
Cell
eval-confusionbuildsmatrixand callsplot_confusion_matrix(...)with noprint. 11, 13, 16, 18, 19, 20 and 21 all print the gold-by-predicted table beside the figure (21'seval-confusionis the cleanest model). Related: recipe 14notstated-codeprintsprint(matrix.labels)thenprint(matrix.matrix.tolist())— a raw list dump (['not_stated', 'named']/[[4, 0], [1, 16]]) rather than a labelled table. Fix: print the labelled table in both.R5. Recipe 18 exempts
no_matchfrom the gate and never prints the counterfactual.side-effect-md: "no_matchis never gated on confidence at all" and "there is no side effect a confidence gate could protect there" (pythons-part-md). There is noungatedor counterfactual computation anywhere in the file. Its structural twin 22 does it properly —side-effectprintscounterfactual on test, no_match gated too (not what this notebook ships):/no_match gated too: coverage 0.7000, accuracy 0.9286, risk 0.0714, andside-effect-notereads the trade off it. Fix: copy 22's counterfactual cell shape. 18 is the only exempting recipe of the six with no gated counterfactual.R6. Recipe 19 uses
evaluate_selectivefor a rule that has a second, unconditional review branch.eval-md: "Every outcome goes through the same gate, soclassify's own accept/review split is exactlyconfidence >= threshold". But an acceptedunknownis written to theReviewQueuewith reason"no option fits"—python-mdsays so itself: "both land in the queue, but only one of them also clears the gate". So three of the fifteentestbuilds insidecoverage 0.7895are a person's decision, not an answer. Recipe 21, the same shape (needs_review→ReviewQueue), reportsevaluate_outcomesand gets a materially different number (coverage 0.7500against the gate-only0.8750) and prints both. 19 also redefines the term:eval-mdsays "coverage is the fraction of builds the gate accepts", where the other twelve say "answered rather than held for review". Fix: reportevaluate_outcomes(accepted, correct)withacceptedfalse for a queuedunknown, or print both views as 21 does; align the coverage definition.R7.
test_stored_answers_are_not_all_rightis a real guard in eight recipes and a stub in five.11, 14, 15, 17, 18, 21, 22, 23 recompute the frozen threshold from
validationexactly as the notebook does and assert a wrongtestanswer at or above it (17:assert wrong_and_confident, "expected at least one confidently wrong test answer"). 12, 13, 16, 19, 20 only assertwrong, "the fixtures should contain some wrong answers"over all splits — they pass even if every mistake is caught by the gate, so they do not guard the lesson they are named for. Fix: give 12/13/16/19/20 the 17/21 form.R8. Five recipes ship the pre-#162
tests/test_build_fixtures.py.11, 15, 16, 17, 18 have no inputs/labels regeneration assertion (no
build_inputs_and_labels, no_jsonlhelper); 12, 13, 14, 19, 20, 21, 22, 23 match the current template. Fix: copyrecipes/_template/tests/test_build_fixtures.py(the current one, not a merged recipe's).R9. Internal issue numbers in a shipped recipe file.
recipes/16-discord-moderation-triage/tests/test_build_fixtures.py:1:"""build_fixtures.py follows the recipes/_template/build_fixtures.py pattern (#124, #129):. The only occurrence in the thirteen; the template's own docstring carries no numbers. Fix: drop(#124, #129).R10. "the issue" / "the issue's build note" cited in 11 of the 13.
Counts of
the issue/build note: 11×5, 12×3, 13×1, 15×3, 16×6, 17×5, 18×2, 19×3, 20×3, 22×5, 23×3. 14 and 21 are clean. E.g. 11python-md: "acceptsno_clarification_neededas a final, un-gated result whatever its confidence (the issue's build note: options come from a predefined catalog…)"; 22helpers.py×3; 17helpers.py×2. A reader cannot see a private issue. Fix: rewrite as "this recipe's use case" / "the use case names", the way 14 and 21 already do.R11. Terminology outside the fixed lexicon.
helpers.py:114"modal level below the business cutoff",:115"modal level clears the business cutoff", pinned bytests/test_helpers.py:47,53, repeated inREADME.md:13and in the notebook (python-md, and thepython-ruleoutputKB-301: level 3 at confidence 0.78 -> match (modal level clears the business cutoff)). 15 and 20, by contrast, say "business threshold". Fix: rename in all four places.eval-selective-md: "a response the normaliser settled has no confidence to gate and is accepted with certainty." "Certainty" is a banned synonym. Fix: "is accepted without a confidence check".gold-positions-md("which position … its gold set occupies" is fine, but the preceding "it actually spreads the gold answer across positions" is not), 17build_fixtures.py:20andhelpers.py:211.questions-md: "and Jev commits to exactly one of them." This is the exact phrasing the lexicon bans for attributing behaviour to Jev. Fix: "aChoiceanswer names exactly one of them".R12. Lexicon terms used before they are defined, in four recipes.
upclose-ambiguous-proseand defines it two cells later inupclose-wrong-prose; 15 first uses it inquestions-mdand defines it inanswer-md.gate-sweep, defined inselective-md) and 20 (printed inrule-sweep, defined ineval-selective-intro). The other eleven define each in the markdown cell immediately above the first cell that prints it.outcomes-md) defines it.R13. Glossary linking is effectively absent and wildly uneven.
Links to
docs/glossary.md: 16 has six (#state,#choice,#confidence,#gold-label,#provenance,#run-mode); 13, 14, 20, 23 have one (#gold-label); 18, 22 have one (#confidence); 11, 12, 15, 17, 19, 21 have none. Fix: adopt 16's pattern. (The deeper cause is #164 T1: six of the seven lexicon terms have no glossary entry to link to.)R14. Stored-answer count missing from the intro in 19, 20, 21, 23.
11 ("The 40 stored answers in
fixtures/are all synthetic"), 15 (52 documents), 16 (41 messages), 17 ("43 stored, synthetic answers"), 18 ("31 hand-written synthetic answers"), 22 ("38 stored, hand-written synthetic answers") and 13 (40) all state it; 12 and 14 state split sizes only; 19, 20, 21, 23 state nothing. The template's own intro says "22 made-up messages". Fix: one clause in eachintro.R15. Recipe 11's README install paragraph does not match the post-#162 template.
11 replaces the
pip install -e ".[dev]"line withpip install -e ".[ml]" -c .github/constraints-notebooks.txtas the environment for all three commands, and drops the "Reproducing the committed notebook outputs byte for byte…" paragraph. The other twelve match the template exactly. Fix: restore the template shape.R16. Recipe 22's README quotes metric numbers in prose.
"Validation's link accuracy (0.95), both baselines (0.70, 0.80) and the rule's own coverage (0.90), accuracy (1.00) and risk (0.00) … link accuracy (0.85) … (0.70, 0.75) … 0.80 and 0.80 (support 5) … coverage (0.85), accuracy (0.88) and risk (0.12) … false-link rate (0.08)". No other README of the thirteen quotes a metric; a README cannot derive its numbers, so these go stale on any fixture edit. Fix: describe the shape, not the digits.
R17. Option-order evidence missing where the recipe claims it.
13
questions-mdargues "Fixed positions, not fixed content … Python deliberately varies which position holds the correct rewrite" but never prints the distribution. 12 (gold-positions), 14 (position-code), 22 (rank) and 23 (position-table) all print one. 18 — a ranked-candidate recipe like 22 — prints none either. Fix: add the gold-position table to 13 and 18.R18. Recipe 16 asserts the
evaluate_outcomes/evaluate_selectiveagreement instead of showing it.Cells
evaluation-outcomes-validationandevaluation-outcomes-testcontainassert (val_outcomes.coverage, val_outcomes.accuracy, val_outcomes.risk) == (val_selective.coverage, …)and print only one triple plus(evaluate_outcomes and evaluate_selective agree on all three). Recipe 12selectiveprints both triples and a derivedthe two agree on every field: True. A bareassertin a notebook also hard-fails theNotebook (<recipe>)job on any fixture edit. Separately, the two sides are built from different correctness vectors (test_correct = [r.label == g …]vstest_selectivefromtest_correct_raw = [a.choice == g …]), so the equality is not quite the claim the prose makes. Fix: print both triples, drop the assert, use one correctness vector.R19. Recipe 21's
coveragedenominator is the called subset, not the split.eval-selective:validation, called subset: answered 5 of 8 responses sent to Jev (coverage 0.6250). It is labelled clearly, but it is the only one of the thirteen whosecoveragedoes not denominate over the split's scored examples, so a reader meeting the word for the eleventh time meets a new denominator. Fix: one sentence giving the whole-split figure too (the normaliser settles 11 of 19 validation responses with no request).R20. Recipe 21 links an unpublished neighbour.
next-md:[36-card-game-action-selection](../36-card-game-action-selection/). The slug matchescatalog/recipes.jsonrank 36, butrecipes/36-card-game-action-selection/does not exist at65978ee. It is the only dangling Next-steps target in the thirteen. Apply whichever way #164 T8 resolves (the contract currently blesses this link and the Wave-2 rule forbids it).R21. Seven of the thirteen have no inbound Next-steps link from any published recipe.
Inbound counts over all published recipes:
08×10,01×6,02×6,04×6,05×5,03×4,07×4,10×4,18×2,11/13/15/16/20×1 — and zero for12,14,17,19,21,22,23. Level 2 links overwhelmingly backward into Level 1 (eight of the thirteen link08-faq-selection), so a reader finishing 23 is sent back to 03/08/10 and never forward through the level. Fix: have each Level-2 recipe link at least one Level-2 neighbour by slug, so no recipe in the band is an orphan.R22. Recipe 22's fixtures are real products at real versions — CONTRIBUTING §5.
fixtures/inputs.jsonlandbuild_fixtures.pyuse real vendors and real products at real, published build numbers:Adobe Systems Incorporated | Acrobat | 11.0.23,Adobe | Acrobat Reader DC | 2023.008.20470,Oracle Corp | Database | 19.3.0.0.0 (19c),Oracle | Java SE | 17.0.9,SAP SE | ECC | 6.0 EHP8,Red Hat, Inc. | RHEL | 8.6,Atlassian | Jira Software | 9.4.1,Atlassian | Confluence | 8.5.4,MS Corp | SQL Server | SQL2019 CU18 (15.0.4261.1),Microsoft | SQL Server | 13.0.6300.2 (2016 SP3),16.0.1000.6, SQL 2022, plus Salesforce, Zoom, AgileBits/1Password, Docker, GitHub, GitLab, Notion, Figma.helpers.pyhard-codes the real legal names as alias tables:"adobe systems incorporated","salesforce.com, inc.","red hat, inc.","sap ag","oracle corp","microsoft corporation","erp central component". §5 is "Fabricated names and numbers only", and recipe 19 — after its r2 review — solves the identical problem the other way, fabricating its packages (acme-sdk==4.2.0,search_index==1.8.3) inside otherwise realisticpytest/pipoutput. These are the two recipes a reader is most likely to read back to back on this point. Fix: rename to fabricated vendors and products with fabricated build numbers, keeping the look-alike / major-version-sibling / edition-sibling structure that carries the lesson. Lesser instance: 18build_fixtures.py:194"The internal Slack integration stopped posting deploy notifications to the team channel."R23. Recipe 21's cost framing.
eval-baseline-md: "Calling Jev is only worth it if it beats something much cheaper. Both baselines below are…". No figure, but it is a sentence about Jev's cost in a recipe with no recorded run (CONTRIBUTING §2 allows none). Fix: "…only worth it if it beats a rule you could have written in a few lines."Route → #164 (template / docs / contract)
T1. Six of the seven fixed-lexicon terms have no glossary entry.
docs/glossary.mddefines state, question, Choice, Noul, Score, criteria, probabilities, confidence, gold label, threshold, accuracy, replay key, fixture, provenance, run mode — and not business threshold, confidence gate, review, coverage, risk or selective prediction. That is the root cause of R12 and R13: thirteen recipes each define these in their own words, and there is nothing to link. Fix: add the six entries (splittingthresholdinto business threshold vs confidence gate, and pinning the review outcome value"review"and reason string"confidence below the threshold"), and state indocs/recipe-template.mdthat each is defined on first prose use with a glossary link.T2. The template does not settle the label on a figure title. The template has no validation figure, so the thirteen invented four conventions (R2). Decide one (
{check}only) and write it beside the existing{selection}{check}bullet indocs/recipe-template.md.T3. "Print what you plot" is not in the template.
recipes/_template/notebook.ipynbcellevaluation-matrixplots the confusion matrix with no printed table — so recipe 23 matches the template, and seven recipes exceed it. Fix: print the table in the template cell and state the rule (it is currently only in the Wave-1 lesson list).T4. CONTRIBUTING §4's fallback clause gives no test to apply, and the thirteen split on the same premise.
Each recipe's §4 rule, as shipped:
no_clarification_needed→proceedcomparison: outcomes 0.9474/0.8889/0.1111 vs selective 0.8947/0.9412/0.0588)keep_originalno_suitable_rewrite→kept_originalevaluation-selective: test 0.8421/0.8750/0.1250 vs 0.7895/0.9333/0.0667, plus the disagreeing ids)not_statedconfonly-code: 0.6190/0.8462/0.1538 vs 0.4286/0.8889/0.1111)review_needed(andallowed)escalateno_match→no_duplicateunknown→manual_triageunknowngoes to theReviewQueueunknownas coverage — R6)needs_reviewneeds_review; below gateeval-compare: test 0.7500/0.8333/0.1667 vs 0.8750/0.8571/0.1429)no_matchside-effect:no_match gated too: coverage 0.7000, accuracy 0.9286, risk 0.0714)insufficient_evidencea/b/tiebelow gateeval-selective-vs-outcomes: 0.7000/0.8571/0.1429 vs 0.9000/0.7222/0.2778, with both disagreement lists)The contradiction is not in the outcomes but in the argument. Six exempt their fallback because "choosing it triggers no side effect"; two reach the opposite conclusion from the identical premise:
python-md: "keep_originalhere is a real, final answer … it triggers no side effect of its own, andresolveholds it to exactly the same confidence gate as every other option rather than an automatic review".python-md: "allowedresolves toignore(no side effect, so there is nothing left for a later step to protect)" — followed three sentences later by "allowedgets no exemption: a confidently wrongallowedanswer leaves a message standing that a person never sees". That parenthetical contradicts 16's own conclusion.A real distinguishing test exists — 11/13/14/18/22/23's fallback has no payload to protect and its being wrong leaves nothing standing that a person would otherwise have caught, whereas a wrong
allowedleaves a violating message up and a wrongkeep_originalis a positive, scorable claim — but no recipe states it and none cross-references a neighbour that decides the other way. Recipe 11 comes closest and then undercuts itself: "no queue, no routing, no action is taken on it … in a real pipeline the cost would be the task moving on to its next step without the information it actually needed", which is the same "nothing happens but the harm stands" shape 16 refuses to exempt.Fix: put the two-part test in §4 and in the template bullet; require the exemption to be shown with gated counterfactual numbers (18 is the one gap today); and have each of the seven arguing recipes name the test in one sentence and point at the neighbour that decides the other way.
T5. Nothing says whether an "answered-but-queued" outcome counts toward coverage. 19 counts an accepted
unknown(routed to theReviewQueue) insidecoverage; 21 does not count an accepted-branchneeds_review. Fix: settle it indocs/evaluation.md"Selective prediction", and say plainly that a rule with any branch sending an accepted answer to a person is not confidence-only and needsevaluate_outcomes.T6.
evaluate_selectiveand sentinel confidences — the doc and the best recipe disagree.docs/evaluation.mdanddocs/recipe-template.mdsay never hand a sentinel confidence toevaluate_selective. Recipe 14confonly-codedoes:confidence_only = [1.0 if a is None else a.confidence for a in test_answers], andconfonly-mddiscloses it and quantifies the distortion ("covers 9 of 21 test documents (0.4286) rather than the 8 of 20 asked documents a gate could actually apply to (0.4000)"). This is the right handling if it is allowed at all. Fix: bless the disclosed, in-range stand-in explicitly, or forbid it and say what to do instead.T7. Extra sections:
##sibling or###under Evaluation?docs/recipe-template.mdsays "Nine sections, in a fixed order … A recipe keeps these headings." 14 (### Where the gold span sits,### Three baselines with no model at all,### What the confidence gate actually buys on test,### A confidence-only view of the same test answers) and 21 and 22 use###under Evaluation. 11 (## What the rule's own outcomes say,## Why this is not the same as reapplying one confidence gate), 16 (## Simulating the moderator queue, before Evaluation), 17 (## Candidate sets and the lexical baseline,## Ranking: nDCG and top-1 accuracy), 18 and 22 (## The simulated side effect(s)) add##siblings. Fix: pick one and say so.T8. Forward neighbour links: the contract and the review rule contradict.
docs/recipe-template.md: "A folder link 404s on GitHub until that recipe'snotebook.ipynbis committed; that is expected, not a defect to fix by removing the link." The Wave-2 rule applied from recipe 22's review: "Next-steps neighbours must exist onmainat review time." Only recipe 21 is affected (R20). Fix: resolve once, in one place.T9. No rule on citing "the issue". Eleven of the thirteen cite a private issue as a source in shipped prose and code (R10); the convention exists only as a one-line lesson from recipe 20's review, and 20 itself still carries three. Fix: state in CONTRIBUTING §6 that recipe files cite only reader-visible sources (the catalog's
Snumbers, repository docs, the recipe's own files).T10. The template defines "gold label" in a code comment.
recipes/_template/notebook.ipynbcellevaluation-validation:# The metrics below are about the examples that have a gold label (the example's recorded correct answer, from fixtures/labels.jsonl, used only to score an answer after the fact, never shown to Jev); the demo ones do not.CONTRIBUTING §6: "Prose in markdown cells, not in code comments." Recipes copied the placement, which is part of why 12 and 20 never define the term in prose at all. Fix: move it intoevaluation-mdwith the glossary link from T1.T11. The cell-id suffix convention is unstated. Discussion cells are
-note(11, 15, 16, 18, 22),-discuss(12, 17, 19),-prose(13, 14) or-md(20, 21, 23); the last cell isnext-md(11-17, 21),next-steps(18, 19, 20, 22) ornext-steps-md(23); recipe 20 uses-introfor every section header (setup-intro,state-intro,questions-intro, …) and 19 appends-codeto code cells (setup-code,measured-code). Every id is semantic, so this is cosmetic — but it is the first thing anyone diffing two recipes notices. Fix: name the suffixes in the template.Route → #165 (parametrised guard test)
G1. Fold these into the parametrised suite over published recipes so they cannot drift per recipe:
tests/test_build_fixtures.pymatches the current template modulo the slug — catches R8 (five recipes behind today).#NNNin any recipe file — catches R9.test_stored_answers_are_not_all_rightexists and recomputes the frozen threshold and asserts a wrongtestanswer at or above it. A name-only check passes today for 12/13/16/19/20 — catches R7.validationcell carries{selection}{check}, and every printed metric line carries at least{check}.tests/test_template.py::test_every_metric_line_of_the_evaluation_carries_the_pipeline_check_labelalready does this for the template only — catches R1 (14 lines) and R3 (6 lines).plot_*call has aprintof the same data in the same cell, and figure titles follow the single convention T2 settles — catches R4 and R2.../NN-slug/in a Next-steps cell resolves to a folder containingnotebook.ipynb(once T8 is decided) — catches R20; and every published recipe is the target of at least one other recipe's Next steps — catches R21.Route → #161 / #172 / #175 / #181 (shared package)
S1 → #181. The stable seeded permutation helper has now been written twice, independently, with the same convention: 12
build_fixtures.py:67-70(hashlib.sha256(f"{SEED}:{item_id}".encode()).hexdigest()→random.Random(int(digest[:16], 16))) and 23helpers.py:83(hashlib.sha256(f"{seed}:{comparison_id}".encode()).digest(), first-byte parity). Take both as the interface test, and place the helper where abuild_fixtures.pyand ahelpers.pycan both call it (12 keeps it in the builder, 23 in helpers).S2 → #161. No outcome-based risk–coverage sweep exists, and five recipes each write their own paragraph apologising for it: 13
evaluation-gate, 18gate-sweep, 19gate-sweep-code, 21eval-curveand 22gate-sweepall plotselective_curveand then explain in prose that the curve is confidence-only and therefore does not describe the rule they just reported. 21eval-curve-mdspends three sentences on it ("No outcome-based sweep helper exists yet injev_cookbook.evaluation"). Anoutcome_curvethat sweeps the gate through the rule itself would delete five bespoke caveats.S3 → #161. Seven spellings of the
(accepted, correct)pairevaluate_outcomestakes: 11helpers.outcome_accounting, 12 inline[r.outcome == helpers.ACCEPTED …], 13/14/16 inline[r.outcome != helpers.REVIEW …], 18 and 22helpers.accepted_and_correct, 21 inline insideselective_report, 23 inline. A sharedaccepted_and_correct(results, gold, *, accepted_when, label_of)would make the §4 story look identical in every recipe.S4 → #161.
helpers.false_merge_rate(18) andhelpers.false_link_rate(22) are the same function under two names: the error rate within the accepted subset that actually carries the side effect. Both notebooks explain in prose that "evaluate_outcomeshas no notion of a second, narrower outcome inside 'answered'". That belongs in the shared helper (evaluate_outcomes(..., side_effect=...), or anaction_error_rate).S5 → #172 / #175. No new instances in the thirteen. 15 is the only
Noulrecipe and itsselective_curveshows no duplicate row. No recipe builds a single-optionChoice: 22no_candidate_resolutionand 14helpers.no_candidatesboth short-circuit before a question is built, as #175 prescribes. Replay keys are unique within every recipe (key slots equal distinct keys in all thirteen: 11/13/19/20 ×40, 12/17 ×43, 14 ×40 of 43 inputs, 15 ×52, 16 ×41, 18 ×31, 21 ×17 of 40, 22 ×38 of 43, 23 ×82), so no cross-split leak.Verified clean across all thirteen
So the findings above are read against what is already uniform: the run-mode header (every recipe calls
run_header(N, title, backend=backend, n_examples=len(scored))and prints the exact three-line synthetic text); the mode-conditionalmeasuredcell (byte-identical to the template in all thirteen, with only additive extra prints in 16, 20 and 23); the nine section headings in the template's order; semantic cell ids throughout; the chart style (no directmatplotlib/pltanywhere — every figure comes fromjev_cookbook.style); the review reason string, exactly"confidence below the threshold"in all thirteenhelpers.py; the sources lists (README and notebook intro both equalcatalog/recipes.jsonfor all thirteen); the live budget note (equal to the stored-answer count in all thirteen, 21 correctly noting 17 is below the default 25); the "What was and was not measured" README sections (all thirteen close with "they say nothing about how Jev performs, how fast it is, or what it costs", and all say "not measured live"); fabricated-data discipline outside R22 (15's PII fixtures use the reserved555-01xxrange and.testemail domains throughout); andtest_stored_answers_are_not_all_rightpresent in every recipe.Overall judgement
The thirteen read as one cookbook on the things that were mechanised — the header, the measured cell, the section order, the chart helpers, the review reason string, the sources and budget notes are indistinguishable across all of them, and the best of these recipes (14, 21, 22, 23) teach the
evaluate_outcomes-versus-evaluate_selectivedistinction better than the template does. Where they stop reading as one cookbook is everywhere the convention lived only in a reviewer's head rather than indocs/or a test: four different labels on a validation figure title, six recipes with no glossary link and six lexicon terms with no glossary entry to link to, five recipes whosetest_stored_answers_are_not_all_rightis a stub while eight recompute the frozen threshold, five recipes on the pre-#162 fixture-builder test, and eleven recipes citing a private issue at the reader. The two findings that would actually mislead a learner are narrower: recipe 19 reports confidence-only coverage for a rule that routes acceptedunknownanswers to a person (R6), which contradicts recipe 21's handling of the identical shape two recipes later; and the §4 fallback argument is used to justify both exempting and gating a no-side-effect fallback, with recipe 16's own parenthetical contradicting its own conclusion (T4). Neither is a defect in any single recipe's numbers — both are the cost of §4 anddocs/evaluation.mdleaving the test unstated. Fix T4, T5 and T1 in the contract and docs, land the #165 guards, and the thirteen converge; leave them, and recipes 24 onward will each re-derive a slightly different answer.Reviewed read-only at
65978eeafb2ed7a8a19d5f02c6ce8087b0f5e943. No notebook executed, no suite run, no repository file changed.CompleteDotTech commentedon Oct 9, 2026
All thirteen Level 2 recipes merged and their issues closed; README catalog regenerated (23 of 60); CI green on
main65978ee; consistency pass posted above. Close-out report on #80.Recipe 22: fabricate vendor, product and version names (#182)
Foundation: parametrised recipe guard with a known-failure allowlist (#…