CodeRigor

LESSON

Reproduce the failure and define acceptance

Separate the reported decimal symptom from the complete grammar and result contract.

A coding agent can produce a convincing patch before anyone agrees what correct means. Start with a reproducible failure, then write the acceptance boundary independently. You will repair money.py, whose initial expression int(float(text)*100) produces28 cents for "0.29" on this runtime. Rounding the float repairs that example but still accepts exponents, whitespace and other inputs outside the API contract.

This course builds on Python tests and Git staging/restoring. Its final artifact is a small actual Git patch, not a claim that an agent finished work. Supplied proposals are authored worked examples so everyone can replay the same mistakes without an account or paid model session.

Acceptance before a patch
start / 3
Reproduce the bug, define the full input/output boundary and challenge each candidate.
Acceptance before a patch
Reproduce the bug, define the full input/output boundary and challenge each candidate.

The complete grammar permits an optional minus, a canonical ASCII whole part of at most nine digits, a dot and exactly two fractional digits. No leading plus, spaces, exponents, Unicode digits or leading zeroes on multi-digit whole parts are accepted. Negative zero yields integer zero. The maximum magnitude is99999999999 cents.

Keep both valid and invalid vectors. Test negative values and the size boundary as well as the reported fraction. A valid result must have exact type int: Python considers False equal to0, but the API does not promise booleans. On invalid input the public failure type is ValueError; an accidental TypeError is a different behavior.

Python
for text in ("0.29", "-0.29", "12.34"):
    print(text, int(float(text)*100), round(float(text)*100))

A good oracle covers both what must happen and what must be refused. Catching Exception here is limited to known, synchronous lab candidates so one failure can be recorded without stopping the rest. It is not a timeout, resource limit or safe evaluator for arbitrary Python.

The baseline and rounding proposal give useful red signals before the reference becomes green. That prevents a vacuous acceptance suite from being mistaken for evidence. In the build exercise, return stable labels for every failure rather than printing a success message or stopping at the first one.

Inspect the boundary
start / 3
Reproduce the bug, define the full input/output boundary and challenge each candidate.
Inspect the boundary
Reproduce the bug, define the full input/output boundary and challenge each candidate.
Python
import re


def parse_cents(text: str) -> int:
    """Parse the lab's canonical signed two-decimal amount without floats."""
    if not isinstance(text, str):
        raise ValueError("amount must be text")
    match = re.fullmatch(r"(-?)(0|[1-9][0-9]{0,8})\.([0-9]{2})", text)
    if match is None:
        raise ValueError("amount is outside the canonical decimal grammar")
    magnitude = int(match.group(2)) * 100 + int(match.group(3))
    return -magnitude if match.group(1) else magnitude

for text in ("0.29", "-0.29", "-0.00"):
    print(text, parse_cents(text))
try:
    parse_cents("1e2")
except ValueError:
    print("exponent rejected")

Implement the acceptance runner and use the baseline, rounding and reference candidates to establish red-to-green behavior. Preserve fixture labels so later review panels can point to exact failures. Next, write a task that makes the accepted scope and evidence explicit.

Authored proposals · Actual collected checks

A plausible patch still needs evidence.

Choose a bundled proposal to inspect its real Git diff and observed checks. This is an offline teaching replay, with no live model session. The owner’s final task permits changes only to money.py.

Reject this proposalrounding

1 · Actual observed checks

  • failed
    unitpython -I acceptance_runner.pyObserved exit 1
  • passed
    syntaxpython -I syntax_check.pyObserved exit 0
  • passed
    formatruff format --check money.pyObserved exit 0
  • passed
    typemypy --strict money.pyObserved exit 0
Source and sealed-test snapshotde2089e0cdc68fe40f443d6f1333e12de19fb3ad1a378424afad64f2d4dbde8a

A digest identifies bytes. It does not authenticate arbitrary agent claims.

2 · Review findings

  • acceptance:exponent
  • acceptance:fraction_digits
  • acceptance:leading_zero
  • acceptance:oversize
  • acceptance:plus
  • acceptance:spaces
  • acceptance:type
  • acceptance:unicode_digits
  • failed:unit
  • float_remains:money.py

3 · The real Git diff

Diff
diff --git a/money.py b/money.py
index 98feda2..2605956 100644
--- a/money.py
+++ b/money.py
@@ -1,2 +1,2 @@
 def parse_cents(text: str) -> int:
-    return int(float(text) * 100)
+    return round(float(text) * 100)
Download the task, proposals, collector, checks and replay →

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →