Complete F02 first, then start from a clean main branch in the same Academy clone.
Keep two surfaces open at the clone root: a native terminal for Academy and shell commands, and your
Claude Code, Codex, or Pi harness for messages to your agent and codeArbiter commands.
This page names the surface for every action. Put a native-terminal command directly in PowerShell
or your shell; it never starts with !. Put a learner prompt or codeArbiter command in the selected
harness; neither starts with !. The ! prefix is only for a shell command deliberately sent
through a harness, and this lesson does not use that route. Do not use git commit yourself: the
agent runs the governed commit gate after you inspect and approve each boundary.
You will turn a real claimant-label defect into durable evidence. A regression is a test that
demonstrates a defect before it is repaired. Red means that new test fails for the intended
reason; green means the same test passes after the repair. A control character is a non-printing
character such as newline, tab, or DEL. This lesson rejects control characters in a claimant label
while preserving an ordinary label such as Sam Allen.
The proof has two commits after Prepare: first a test-only red regression in
tests/test_service.py, then a service-only repair in workshop_queue/service.py. A commit
boundary is the exact path set in one commit. The production boundary is the real
claim_ticket function that receives the label, not a helper, transcript, CLI, or JSON copy.
From a clean main branch at your Academy clone root, run Prepare in a native terminal. The printed attempt number is evidence metadata, not text to type.
Why this matters: Prepare gives the intentionally defective service a separate, reviewable history.
Academy switches this clone to academy/F04-fix-with-evidence/ATTEMPT_NUMBER.
Evidence
The numbered branch preserves the defective starting point.
If that does not happen
If Prepare stops, preserve its message and resolve only the named clean-worktree, branch, or prerequisite condition. Do not force-reset main or delete an earlier attempt.
The printed attempt number is evidence metadata. Academy uses it in the branch name; do not type
the literal word ATTEMPT_NUMBER. The attempt starts from a deterministic defective service and
keeps main untouched.
2
You
Read the defect and its boundary
Read the prepared service and test file before requesting a change. Identify claim_ticket as the live boundary and confirm that the regression name is not already present.
Why this matters: The lesson repairs one claimant-label rule, not the CLI, JSON storage, or a fictional copy of the service.
sed -n '1,240p' workshop_queue/service.py; sed -n '1,180p' tests/test_service.py
Linux
sed -n '1,240p' workshop_queue/service.py; sed -n '1,180p' tests/test_service.py
Expected result
claim_ticket accepts a volunteer label without rejecting newline, tab, or DEL characters, and tests/test_service.py has no claimant-label control-character regression.
Evidence
You identified the real production boundary and the missing executable regression.
If that does not happen
If either file differs from that prepared state, stop. Preserve the branch and use Reset for a new F04 attempt rather than adapting another lesson's state.
3
You
Confirm the prepared baseline is clean
Run the service suite in a native terminal before adding the regression. The existing suite must be green and Git must print no paths.
Why this matters: A green prepared baseline lets the later red result identify the new regression instead of a pre-existing failure.
Windows
git status --short; python -m unittest tests.test_service -v
macOS
git status --short && python3 -m unittest tests.test_service -v
Linux
git status --short && python3 -m unittest tests.test_service -v
Expected result
Git prints nothing and the existing service tests pass.
Evidence
The red test will be attributable to the regression you request next.
If that does not happen
If Git prints a path or a baseline test fails, preserve the output. Do not add a regression until the prepared attempt is understood; use Reset if the attempt is not clean.
Open the governed fix lane before requesting any change. The copied command below belongs in your
selected harness, not in a terminal.
4
Your host
Open the governed fix lane
In the selected codeArbiter harness, paste the copied fix command into the harness prompt and send it. It is not a shell command and never begins with !. The agent opens the governed fix lane before changing production code.
Why this matters: The fix lane records the defect boundary and keeps the upcoming work under the repository's rules.
Any OS · Claude Code
/ca:fix "Reject control characters in a claimant label"
Any OS · Codex
$ca-fix "Reject control characters in a claimant label"
Any OS · Pi
/ca-fix "Reject control characters in a claimant label"
Any OS · Pi
/skill:ca-fix "Reject control characters in a claimant label"
Expected result
The agent identifies the claimant-label defect and waits for a regression-first change.
Evidence
The governed path names the production boundary before it changes.
If that does not happen
If the command blocks, preserve its finding. Do not bypass the lane or edit workshop_queue/service.py from a terminal.
Ask your agent for one direct executable regression and nothing in production. The request already
states the three rejected labels, the expected ValueError, and the ordinary-name control. Send it
unchanged first; it is specific enough to review.
5
You
Request the test-only regression
Paste this request into the selected harness and send it. It is an instruction to your agent, not a terminal command, so it never begins with !.
Why this matters: The first commit must prove the defect exists before any repair can make it pass.
Any OS · Claude Code
Add exactly one direct unittest named test_claim_rejects_control_characters_in_volunteer_label in tests/test_service.py. Loop over Sam\nAdmin, Sam\tAdmin, and Sam\x7fAdmin; for each, use assertRaisesRegex(ValueError, a message containing control characters) around the real claim_ticket call. Then assign the real claim_ticket result for Sam Allen to a variable and assert its claimed_by is Sam Allen. Do not edit workshop_queue/service.py. Run only that test and stop after its semantic assertion failure.
Any OS · Codex
Add exactly one direct unittest named test_claim_rejects_control_characters_in_volunteer_label in tests/test_service.py. Loop over Sam\nAdmin, Sam\tAdmin, and Sam\x7fAdmin; for each, use assertRaisesRegex(ValueError, a message containing control characters) around the real claim_ticket call. Then assign the real claim_ticket result for Sam Allen to a variable and assert its claimed_by is Sam Allen. Do not edit workshop_queue/service.py. Run only that test and stop after its semantic assertion failure.
Any OS · Pi
Add exactly one direct unittest named test_claim_rejects_control_characters_in_volunteer_label in tests/test_service.py. Loop over Sam\nAdmin, Sam\tAdmin, and Sam\x7fAdmin; for each, use assertRaisesRegex(ValueError, a message containing control characters) around the real claim_ticket call. Then assign the real claim_ticket result for Sam Allen to a variable and assert its claimed_by is Sam Allen. Do not edit workshop_queue/service.py. Run only that test and stop after its semantic assertion failure.
Expected result
Only tests/test_service.py changes, and the focused regression fails because claim_ticket does not raise ValueError.
Evidence
The same named test is executable proof of the original defect.
If that does not happen
If the result is an import, syntax, or unrelated failure, reject it as evidence and ask the agent to correct only the regression. If production code changes, do not stage it.
6
You
Observe the semantic red failure
Run the focused regression yourself in a native terminal. This command is expected to fail at this point; read the assertion before continuing.
Why this matters: A test that passes before the repair cannot prove the repair fixed the intended defect.
The test fails because ValueError was not raised for a claimant label containing a control character.
Evidence
You personally observed the red assertion against the prepared production code.
If that does not happen
If the test passes, errors before reaching claim_ticket, or names a different failure, stop. Return to the regression request and preserve the current attempt.
Read the diff before staging. A test that is red because it cannot import, has a typo, or never
calls claim_ticket is not evidence of this defect.
7
You
Inspect the test-only boundary
Inspect whole-worktree status and both relevant paths before staging. The service path must have no diff yet.
Why this matters: The history proof fails if the regression and repair share one commit.
Only tests/test_service.py is modified, with no whitespace error and no workshop_queue/service.py change.
Evidence
The first commit boundary is visibly test-only.
If that does not happen
If another path changed, do not stage. Preserve the attempt and ask the agent to remove the unrelated change before the regression commit.
8
You
Stage only the red regression
Stage exactly tests/test_service.py in a native terminal. This is a shell command you run directly, so it never begins with !. Do not stage production code or any other path.
Why this matters: The review and governed commit need one visible, test-only staged boundary.
The staged path list prints exactly tests/test_service.py and the staged diff has no whitespace error.
Evidence
The first proposed commit has one visible staged test path.
If that does not happen
If another path is staged, unstage only that path and inspect the whole worktree again. Do not commit until the staged list is exact.
9
You
Approve the red-test commit boundary
Paste this review request into the selected harness and send it. It is not a shell command and never begins with !.
Why this matters: The learner approves a narrow test-only commit after inspecting the real diff.
Any OS · Claude Code
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only tests/test_service.py, whether the staged path is exactly tests/test_service.py, and whether the named regression reaches real claim_ticket and is red because ValueError is absent.
Any OS · Codex
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only tests/test_service.py, whether the staged path is exactly tests/test_service.py, and whether the named regression reaches real claim_ticket and is red because ValueError is absent.
Any OS · Pi
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only tests/test_service.py, whether the staged path is exactly tests/test_service.py, and whether the named regression reaches real claim_ticket and is red because ValueError is absent.
Expected result
The agent reports one test path, a real red regression, and no production change for learner approval.
Evidence
The learner approved the exact first commit boundary.
If that does not happen
If the report names another path, a passing test, or a production diff, do not approve. Preserve the evidence and correct the boundary first.
When the report says the whole worktree and staged set contain only tests/test_service.py, let
the agent make the first governed commit.
10
Your host
Commit the red regression
After the learner approves the boundary, paste the copied commit command into the selected harness prompt and send it. It is not a shell command. The agent runs the codeArbiter commit gate; do not use git commit.
Why this matters: The governed commit makes the failing regression durable history before the repair exists.
Any OS · Claude Code
/ca:commit
Any OS · Codex
$ca-commit
Any OS · Pi
/ca-commit
Any OS · Pi
/skill:ca-commit
Expected result
codeArbiter creates one learner commit after Prepare containing only tests/test_service.py.
Evidence
The first immutable learner commit contains the red regression only.
If that does not happen
If the gate blocks, preserve the finding. Do not direct-commit, amend another attempt, or add workshop_queue/service.py to this commit.
11
You
Prove the first commit is still red
Inspect the new head and run the same focused test again in a native terminal. The command is still expected to fail.
Why this matters: This ties the red failure to the committed test-only history, not a transient working-tree edit.
Windows
git show --stat --oneline HEAD; python -m unittest tests.test_service.TicketTransitionTests.test_claim_rejects_control_characters_in_volunteer_label -v
macOS
git show --stat --oneline HEAD; python3 -m unittest tests.test_service.TicketTransitionTests.test_claim_rejects_control_characters_in_volunteer_label -v
Linux
git show --stat --oneline HEAD; python3 -m unittest tests.test_service.TicketTransitionTests.test_claim_rejects_control_characters_in_volunteer_label -v
Expected result
HEAD lists only tests/test_service.py, and the same regression remains red because the service has not changed.
Evidence
The committed red regression predates the repair.
If that does not happen
If HEAD includes service code or the test is not the expected red assertion, preserve the attempt and use Reset. Do not rewrite history to make it appear ordered.
Now request the smallest reachable repair. It must reject characters below U+0020 and U+007F
at the existing claimant-label boundary, leave the committed regression unchanged, add no
dependency, and retain ordinary-name behavior.
12
You
Request the minimal production repair
Paste this request into the selected harness and send it. It is a request to your agent, not a terminal command, so it never begins with !.
Why this matters: The repair must be a reachable claimant-label guard, not a broad rewrite or a disconnected test helper.
Any OS · Claude Code
Add only the smallest reachable control-character validation at the existing claim_ticket volunteer-label boundary in workshop_queue/service.py. Reject characters below U+0020 and U+007F, preserve the existing regression unchanged, preserve valid Sam Allen behavior, add no dependency, and run the focused regression plus tests.test_service.
Any OS · Codex
Add only the smallest reachable control-character validation at the existing claim_ticket volunteer-label boundary in workshop_queue/service.py. Reject characters below U+0020 and U+007F, preserve the existing regression unchanged, preserve valid Sam Allen behavior, add no dependency, and run the focused regression plus tests.test_service.
Any OS · Pi
Add only the smallest reachable control-character validation at the existing claim_ticket volunteer-label boundary in workshop_queue/service.py. Reject characters below U+0020 and U+007F, preserve the existing regression unchanged, preserve valid Sam Allen behavior, add no dependency, and run the focused regression plus tests.test_service.
Expected result
Only workshop_queue/service.py changes and the existing regression is ready to turn green.
Evidence
The repair is separate from the committed regression.
If that does not happen
If the agent changes the test, adds a dependency, or edits another path, do not stage. Ask it to restore the test and reduce the repair to the claimant boundary.
13
You
Prove the same test turns green
Run the named regression and then the complete service suite in a native terminal before staging the repair.
Why this matters: The same test must change from red to green, while an ordinary claimant remains valid.
Only workshop_queue/service.py is modified, with no whitespace error and no new tests/test_service.py diff.
Evidence
The second commit boundary is visibly production-only.
If that does not happen
If another path changed, do not stage. Preserve the attempt and remove only the unintended uncommitted scope before requesting review.
15
You
Stage only the production repair
Stage exactly workshop_queue/service.py in a native terminal. This is a shell command you run directly, so it never begins with !. Do not stage the committed regression or any other path.
Why this matters: The second review and governed commit need one visible, service-only staged boundary.
The staged path list prints exactly workshop_queue/service.py and the staged diff has no whitespace error.
Evidence
The second proposed commit has one visible staged service path.
If that does not happen
If another path is staged, unstage only that path and inspect the whole worktree again. Do not commit until the staged list is exact.
16
You
Approve the service-only boundary
Paste this review request into the selected harness and send it. It is not a shell command and never begins with !.
Why this matters: The learner approves the narrow repair after the regression and full service suite pass.
Any OS · Claude Code
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only workshop_queue/service.py, whether the staged path is exactly workshop_queue/service.py, whether the earlier regression remains unchanged, and whether the guard is reachable from claim_ticket.
Any OS · Codex
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only workshop_queue/service.py, whether the staged path is exactly workshop_queue/service.py, whether the earlier regression remains unchanged, and whether the guard is reachable from claim_ticket.
Any OS · Pi
Show git status --short, the staged path list, and the staged diff. Do not commit. Report whether the whole worktree changes only workshop_queue/service.py, whether the staged path is exactly workshop_queue/service.py, whether the earlier regression remains unchanged, and whether the guard is reachable from claim_ticket.
Expected result
The agent reports one service path, an unchanged regression, and a reachable narrow guard for learner approval.
Evidence
The learner approved the exact second commit boundary.
If that does not happen
If the report names a changed test, another path, or an unreachable guard, do not approve. Preserve the attempt and correct only that defect.
After that report identifies only workshop_queue/service.py, let the agent make the second
governed commit.
17
Your host
Commit the production repair
After the learner approves the boundary, paste the copied commit command into the selected harness prompt and send it. It is not a shell command. The agent runs the codeArbiter commit gate; do not use git commit.
Why this matters: The second governed commit preserves the repair separately from the red regression.
Any OS · Claude Code
/ca:commit
Any OS · Codex
$ca-commit
Any OS · Pi
/ca-commit
Any OS · Pi
/skill:ca-commit
Expected result
codeArbiter creates one later learner commit containing only workshop_queue/service.py.
Evidence
The second immutable learner commit contains the production repair only.
If that does not happen
If the gate blocks, preserve the finding. Do not amend the red regression, direct-commit, or add tests/test_service.py to this repair commit.
18
You
Inspect the exact two-commit proof
Confirm the worktree is clean and read the most recent two learner commits in a native terminal. The older commit must be test-only and the newer commit service-only.
Why this matters: Check reconstructs this history from Git rather than trusting a transcript.
Windows
git status --short; git log --oneline --name-only -2
macOS
git status --short && git log --oneline --name-only -2
Linux
git status --short && git log --oneline --name-only -2
Expected result
Git prints no status paths and shows two commits after Prepare: tests/test_service.py first, then workshop_queue/service.py.
Evidence
The exact two commits are available for the external checkpoint to inspect.
If that does not happen
If the history has another path, another commit, or dirty state, preserve it and use Reset for a numbered retry. Do not rebase, amend, or delete the failed evidence.
Before Check, read the two commit summaries and confirm that git status --short prints nothing.
If a path, commit boundary, or result differs from the lesson, preserve the attempt. It is evidence to
inspect, not clutter to rewrite.
Success is not a reassuring transcript. Git shows a clean worktree and exactly two learner commits
after Prepare: the older commit changes only tests/test_service.py and is still red at that point;
the newer commit changes only workshop_queue/service.py and makes the same regression green.
The full service suite remains green, and Sam Allen still succeeds.
That separation lets another person reconstruct what was wrong, confirm that the first commit
actually detected it, and see the narrow repair without trusting a chat summary.
Run Check from a native terminal. Academy reads the prepared baseline, commit order, retained source shapes, and clean current worktree without executing learner-authored code.
Why this matters: External verification makes the evidence independently reconstructable.
Check prints checkpoint F04-fix-with-evidence: passed; progress: .academy/progress.json.
Evidence
Only a passing external Check records F04 progress.
If that does not happen
If Check fails, preserve the exact history and read its named predicate. Use Reset for a new numbered attempt instead of rewriting committed evidence.
Check is the external Academy verifier. It reads the prepared baseline, your commit order and
path sets, the retained regression and reachable repair shapes, and whether the live worktree is
clean. It does not execute learner-authored code. The red and green commands you ran above are your
real behavioral evidence; Check reconstructs their source and history safely. A green final test
alone is not enough: Check rejects a same-commit fix, a code-first path, an unreachable guard,
unrelated changes, or uncommitted work.
Python can refresh its own cache file while it runs the service test. Check excludes only those
two exercised cache files; every learner-authored, staged, tracked, or untracked change still fails
the clean-worktree condition.
If Check fails, preserve the branch and read the named predicate before attempting anything else.
Do not amend, rebase, force-reset, delete the branch, or hide evidence. Use Reset only after a
failed Check or an attempt whose commit boundary is irrecoverably wrong; it preserves the failed
attempt and creates the next numbered retry.
The first commit must remain red. Inspect it before asking for production work, then keep that test
unchanged while the production repair turns it green.
If a report shows two paths, another commit, a changed regression, or a dirty worktree, stop before
Check. A fresh numbered retry is more useful evidence than rewritten history.
20
You
Create a preserved numbered retry
Use this only after a failed Check or an irrecoverably wrong attempt. Run Reset in a native terminal at the Academy clone root.
Why this matters: Reset keeps the failed red-to-green history reachable and starts the same defect on the next unused branch number.
Academy archives the prior attempt and switches to the next academy/F04-fix-with-evidence/ATTEMPT_NUMBER branch.
Evidence
The failed attempt remains reachable while the retry starts clean.
If that does not happen
If Reset stops, preserve its message and current branch. Never force-reset or delete the prior attempt.
After Check passes, return to main and leave the completed attempt branch available for review.
21
You
Return to main after success
After Check passes, return to main in a native terminal without deleting the completed attempt branch.
Why this matters: The passing attempt remains available for a reviewer to inspect.
Windows
git switch main
macOS
git switch main
Linux
git switch main
Expected result
Git switches to main and leaves academy/F04-fix-with-evidence/ATTEMPT_NUMBER reachable.
Evidence
The completed evidence remains separated from main.
If that does not happen
If Git refuses because work is uncommitted, stop and preserve it. Do not force the switch.
F04 is complete only after the external Check passes. Continue to P01 when that guided Academy
lesson is published; an unpublished source exercise is not a substitute for a course step.
This pattern is a causal proof, not a ritual. Prepare establishes a known defective baseline. The
first commit records a test that reaches the live production boundary and fails for the missing
validation. The second commit adds the smallest reachable validation that makes that exact test
pass. The external verifier checks both snapshots, both path boundaries, and the clean current
worktree.
The operations surface stays small on purpose. Academy prepares, checks, resets, and returns an
attempt; the website teaches the decisions; codeArbiter governs the agent work. Each part has one
job, so a learner can tell which command belongs where and a reviewer can reproduce the result.