Build an Idempotent Reservation API
Build a traceable local API that handles normalized retries and cancellations.
Introduction
30 Second Summary
Submitting the same booking form twice should not reserve two seats. Small differences in email capitalization or spacing can make that mistake hard to spot.
In this project, you will turn an underspecified workshop reservation request into a working local HTTP API. Claude Code will help you resolve the contract before coding. Requirement-labeled acceptance tests will prove every agreed behavior.
What You'll Build
You will run one local proof command to see six requirement-labeled results pass against a reservation API that treats email casing and surrounding whitespace as the same identity.
By the end of this project, you'll have:
- A working reservation endpoint where you submit a workshop ID plus an email address. You receive a confirmation with a stable reservation ID.
- Proof of idempotent retries where Avery@Example.com and avery@example.com return the same reservation ID. The retry leaves the remaining seat count unchanged.
- A traceable evidence chain that connects each stakeholder decision to its requirement ID. You can follow every requirement through the implementation plan to its named acceptance result.
- Secret Mission: Add idempotent cancellation that returns the same result on every retry. Repeated cancellation never restores capacity twice.
Are there any prerequisites?
You should be comfortable reading JavaScript and HTTP responses. This project assumes Claude Code plus Node.js 24.20.0 are already installed on your Mac.
Before We Start
You are building a local workshop reservation API to practice turning ambiguous product language into requirement-linked implementation evidence. Requirement IDs connect each passing result to an agreed behavior, which makes the evidence stronger than a green test count alone.
Verify the Local Toolchain
Your reservation API needs a reproducible starting point. Version evidence keeps later acceptance results tied to the same environment.
This step verifies Claude Code before it changes any project files. It also pins Node.js 24.20.0 as the runtime for the workshop API.
In this step, get ready to:
- Verify that Claude Code meets the required version.
- Verify that Node.js matches the project runtime.
- Create the empty workspace and inspect Claude Code health diagnostics.
Verify Claude Code
The specification workflow depends on features from a current Claude Code release. Checking the installed version proves that your agent environment supports the documented workflow.
- Press Cmd+Space to open macOS search.
- Type Terminal into the search field.
- Press Enter to open Terminal.
- Check your Claude Code version by running this command:
claude --version
What Does This Check Prove?
The version check reports the Claude Code release available in this Terminal session. The result tells you whether the documented agent workflow is ready.
✔️ I see the required version or newer
Your output shows 2.1.257 or a newer version. Claude Code is ready for the specification workflow.
ⓧ I see an older version
An older release needs an update before it can anchor a reproducible workflow.
- Update Claude Code by running this command:
claude update
What Does This Command Do?
The update command brings the existing native installation forward. A fresh Terminal session then loads the updated installation.
- Close the current Terminal window after the update completes.
- Press Cmd+Space to reopen macOS search.
- Type Terminal into the search field.
- Press Enter to start a fresh Terminal session.
- Check the updated version by running this command:
claude --version
Why Check Again?
The second check proves that the new Terminal session can see the updated installation. This closes the loop between changing the tool and verifying the result.
You should now see 2.1.257 or a newer version in the output.
Still Seeing the Older Version?
- Confirm that the update completed without an error before you closed Terminal.
- Start another fresh Terminal session after the update finishes.
Ask for help with the version that remains: Help me troubleshoot why Claude Code still reports an older version after an update.
ⓧ Command not found
Terminal cannot reach the Claude Code installation yet. The official setup guide provides the current macOS installation path.
- Open the official Claude Code setup guide.
- Follow the macOS installation instructions to install Claude Code.
- Close the current Terminal window after installation completes.
- Press Cmd+Space to reopen macOS search.
- Type Terminal into the search field.
- Press Enter to start a fresh Terminal session.
- Check the installed version by running this command:
claude --version
What Does This Check Confirm?
A version response proves that the new Terminal session can reach Claude Code. The reported release must be 2.1.257 or newer before you continue.
Still Cannot Reach Claude Code?
- Confirm that the installation finished before you reopened Terminal.
- Review the macOS setup instructions for any incomplete installation step.
Ask for help with the output you see: Help me troubleshoot why Terminal cannot reach Claude Code after installation.
Verify Node.js
The project pins Node.js to v24.20.0. Matching that runtime keeps the HTTP behavior and acceptance evidence reproducible.
- Check your Node.js version by running this command:
node --version
What Does This Check Prove?
The version check reports the Node.js runtime used by later project commands. The project requires an exact match so every acceptance result comes from the same runtime.
✔️ I see the required Node.js version
Your output shows v24.20.0. The runtime now matches the project pin.
ⓧ I see another version
The official macOS package installs the project runtime. macOS may ask you to approve the system change during installation.
- Download the official macOS package for Node.js v24.20.0.
- Open the downloaded .pkg file from your Downloads folder.
- Follow the macOS installer to install Node.js v24.20.0.
- Close Terminal after the installer finishes.
- Press Cmd+Space to reopen macOS search.
- Type Terminal into the search field.
- Press Enter to start a fresh Terminal session.
- Check the replacement runtime by running this command:
node --version
Why Check the New Session?
The new Terminal session loads the runtime installed by the package. Its output provides direct evidence that the replacement succeeded.
You should now see exactly v24.20.0.
Still Seeing Another Version?
- Confirm that the macOS installer completed successfully.
- Start another fresh Terminal session after installation.
Ask for help identifying the active runtime: Help me troubleshoot why macOS still reports another Node.js version.
ⓧ Command not found
The official macOS package supplies the missing runtime. macOS may ask you to approve the system change during installation.
- Download the official macOS package for Node.js v24.20.0.
- Open the downloaded .pkg file from your Downloads folder.
- Follow the macOS installer to install Node.js v24.20.0.
- Close Terminal after the installer finishes.
- Press Cmd+Space to reopen macOS search.
- Type Terminal into the search field.
- Press Enter to start a fresh Terminal session.
- Check the installed runtime by running this command:
node --version
What Does This Check Confirm?
A version response proves that Terminal can reach the installed Node.js runtime. The output must show exactly v24.20.0.
Still Cannot Reach Node.js?
- Confirm that the package installation finished before you reopened Terminal.
- Restart your Mac if the completed installer has not updated the Terminal environment.
Ask for help with the missing command: Help me troubleshoot why Terminal cannot reach Node.js after installation.
Create the workspace and inspect health
The empty workspace separates application modules from HTTP acceptance tests. The src directory holds the application modules while test holds the acceptance fixture.
- Move to your Desktop by running this command:
cd ~/Desktop
Why Start on the Desktop?
The Desktop gives the workspace a predictable location on your Mac. You can find the project without depending on whichever folder Terminal opened in.
- Create the reservation-retry-trap workspace with its empty directories by running this command:
mkdir -p reservation-retry-trap/{src,test} && cd reservation-retry-trap
What Does This Command Create?
The command creates the reservation-retry-trap folder with empty src and test directories. It then moves Terminal into the new workspace.
- Confirm that both directories exist by running this command:
ls
What Does This Check Show?
The listing shows the contents of the current workspace. Seeing both directory names proves that the required project structure exists before any files are added.
You should see src and test in the output.
Missing a Workspace Directory?
- Confirm that your Terminal prompt shows the reservation-retry-trap workspace.
- Rerun the workspace creation command if either directory is missing.
Ask for help with the directory listing: Help me troubleshoot why my reservation workspace is missing src or test.
Workspace Checkpoint
The reservation-retry-trap/src directory exists for application modules.
The reservation-retry-trap/test directory exists for HTTP acceptance tests.
Before you run the final check, what do you expect the diagnostics to confirm about your Claude Code installation?
- Inspect Claude Code health from inside the reservation-retry-trap workspace by running this command:
claude doctor
What Does This Health Check Prove?
The diagnostics command reports read-only installation and settings information. This gives you visible evidence that Claude Code is healthy before it touches project files.
You should see Claude Code installation and settings diagnostics in Terminal. The prompt remains inside the reservation-retry-trap workspace.
Your runtime now matches the project pin. Next, you will seed the plausible reservation flow that hides the identity gap.
Ship the Plausible First Pass
Your Claude Code environment is healthy. Your Node.js runtime is also ready to execute the local API.
This step creates a plausible first version of the reservation HTTP API. It uses each submitted email string exactly as received, which leaves the meaning of duplicate identity unresolved.
In this step, get ready to:
- Capture the ambiguous workshop reservation request.
- Seed the raw-email reservation service with its HTTP adapter.
- Demonstrate a successful retry using two identical email strings.
Capture the ambiguous request
The product brief describes the desired outcome without defining how email identity works. The workflow file protects future acceptance evidence from being weakened when a test exposes that ambiguity.
- Start a Claude Code session in the Terminal from earlier by running this command:
claude
What does this command do?
This starts an interactive Claude Code session inside the existing reservation-retry-trap workspace. The agent can now create the project files you request.
You will see Claude Code wait for your project instructions.
Claude Code did not start?
Return to the Terminal session where the earlier health check succeeded. Confirm that you are still inside the reservation-retry-trap workspace.
Ask for targeted help if the verified command now fails. Help me diagnose why Claude Code no longer starts in my verified Terminal session.
- Ask Claude Code to create BRIEF.md in reservation-retry-trap with this exact content:
# Workshop Reservation API
Build a local API that reserves seats for workshops.
A customer submits a workshop ID and email address. If seats remain, return a confirmation. Prevent duplicate reservations and never overbook. Keep all data in memory.
The API should make success and failure clear to its caller.
What does this brief establish?
- The brief establishes workshop capacity as a business constraint.
- The phrase about duplicate reservations leaves email identity undefined.
- The in-memory boundary keeps the project focused on behavior instead of persistence.
- Confirm that Claude Code reports BRIEF.md as created in reservation-retry-trap.
Brief created in the wrong place?
Tell Claude Code that BRIEF.md belongs directly inside reservation-retry-trap. The file should sit beside the existing src directory.
Use the file location in your help request. Help me place BRIEF.md directly inside my reservation-retry-trap workspace.
✔️ Awesome, I've got everything!
Great. Your product request now preserves the ambiguity that the acceptance fixture will examine.
ⓧ I'd like to double check the full code
Compare your BRIEF.md with this complete reference.
# Workshop Reservation API
Build a local API that reserves seats for workshops.
A customer submits a workshop ID and email address. If seats remain, return a confirmation. Prevent duplicate reservations and never overbook. Keep all data in memory.
The API should make success and failure clear to its caller.
What should match?
The heading and all four brief statements should match exactly. Preserve the phrase about duplicate reservations because its missing identity rule drives the next requirements exercise.
- Ask Claude Code to create CLAUDE.md in reservation-retry-trap with this exact content:
# Workflow
- Read `SPEC.md`, `TRACEABILITY.md`, and `PLAN.md` before implementation changes.
- Do not change acceptance criteria or tests merely to make a failure disappear.
- Keep the domain service separate from the HTTP adapter.
- Run `node --test` after implementation changes.
- Every acceptance test name must begin with exactly one requirement ID.
- Completion requires a passing result for every requirement ID in `TRACEABILITY.md`.
Why add project instructions?
- Claude Code reads CLAUDE.md as project-specific workflow guidance.
- The guardrails keep implementation separate from the HTTP boundary.
- The evidence rules prevent a failing requirement from disappearing through a test edit.
- Confirm that Claude Code reports CLAUDE.md as created beside BRIEF.md.
Workflow rules look different?
Ask Claude Code to replace the file with the supplied content verbatim. Pay particular attention to the requirement-ID rules near the bottom.
Use the complete reference if any wording drifted. Help me compare CLAUDE.md with the supplied workflow rules without rewriting those rules.
✔️ Awesome, I've got everything!
Your project now has a durable rule against weakening acceptance evidence.
ⓧ I'd like to double check the full code
Compare your CLAUDE.md with this complete reference.
# Workflow
- Read `SPEC.md`, `TRACEABILITY.md`, and `PLAN.md` before implementation changes.
- Do not change acceptance criteria or tests merely to make a failure disappear.
- Keep the domain service separate from the HTTP adapter.
- Run `node --test` after implementation changes.
- Every acceptance test name must begin with exactly one requirement ID.
- Completion requires a passing result for every requirement ID in `TRACEABILITY.md`.
What should match?
All six workflow rules should appear under the heading. The filenames and requirement-ID wording must remain unchanged.
Seed the raw-email API
The domain service owns capacity and reservation state. The HTTP adapter translates those outcomes into responses while keeping transport details outside the business logic.
Why use a domain service?
A separate domain service gives the reservation rules one home. The HTTP layer can expose those rules without deciding how identity or capacity works.
This separation makes later requirement-to-component mapping concrete. Each contract can point to the code that owns it.
- Ask Claude Code to create src/reservations.mjs from the four reference chunks below.
- Tell Claude Code to preserve the chunks in the displayed order.
export function createReservationService(workshopCapacities) {
const reservationsByWorkshop = new Map();
let nextReservationNumber = 1;
function reservationsFor(workshopId) {
if (!reservationsByWorkshop.has(workshopId)) {
reservationsByWorkshop.set(workshopId, new Map());
}
return reservationsByWorkshop.get(workshopId);
}
What does this section do?
- The outer function receives the available capacity for each workshop.
- The map keeps each workshop's reservations separate in memory.
- The counter provides stable reservation IDs for new confirmations.
- Continue src/reservations.mjs with the validation and lookup section below.
function reserve({ workshopId, email }) {
const capacity = workshopCapacities[workshopId];
if (!Number.isInteger(capacity) || capacity < 1) {
return { status: 'unknown-workshop', workshopId };
}
if (typeof email !== 'string' || !email.trim()) {
return { status: 'invalid-email', workshopId };
}
const reservations = reservationsFor(workshopId);
const existing = reservations.get(email);
What does this section do?
- The capacity lookup rejects workshop IDs without a positive integer capacity.
- The email check rejects blank values before reservation state changes.
- The existing-reservation lookup uses the submitted email string as its exact key.
- Continue src/reservations.mjs with the retry and capacity branches below.
if (existing) {
return {
...existing,
created: false,
remainingSeats: capacity - reservations.size,
};
}
if (reservations.size >= capacity) {
return { status: 'full', workshopId, remainingSeats: 0 };
}
const confirmation = {
status: 'confirmed',
reservationId: `r-${String(nextReservationNumber++).padStart(3, '0')}`,
workshopId,
email,
};
What does this section do?
- An exact-key retry returns the saved confirmation before checking capacity.
- A distinct key receives a full outcome when the workshop has no seats left.
- A new confirmation records the submitted email without changing its casing or whitespace.
- Finish src/reservations.mjs with the storage and return section below.
reservations.set(email, confirmation);
return {
...confirmation,
created: true,
remainingSeats: capacity - reservations.size,
};
}
return { reserve };
}
What does this section do?
- The map stores the new confirmation under the raw submitted email.
- The response marks the reservation as newly created.
- The returned service exposes the reservation operation to the HTTP adapter.
- Ask Claude Code to save the assembled module as src/reservations.mjs.
- Confirm that Claude Code reports the module as created inside src.
Reservation module is incomplete?
Check that all four chunks appear once in the displayed order. The final line should close createReservationService().
Ask for a structural comparison if braces or sections are missing. Help me compare src/reservations.mjs with the four supplied chunks without changing its behavior.
✔️ Awesome, I've got everything!
The domain service now enforces basic validation and capacity while preserving raw email keys.
ⓧ I'd like to double check the full code
Compare your src/reservations.mjs with this complete reference.
export function createReservationService(workshopCapacities) {
const reservationsByWorkshop = new Map();
let nextReservationNumber = 1;
function reservationsFor(workshopId) {
if (!reservationsByWorkshop.has(workshopId)) {
reservationsByWorkshop.set(workshopId, new Map());
}
return reservationsByWorkshop.get(workshopId);
}
function reserve({ workshopId, email }) {
const capacity = workshopCapacities[workshopId];
if (!Number.isInteger(capacity) || capacity < 1) {
return { status: 'unknown-workshop', workshopId };
}
if (typeof email !== 'string' || !email.trim()) {
return { status: 'invalid-email', workshopId };
}
const reservations = reservationsFor(workshopId);
const existing = reservations.get(email);
if (existing) {
return {
...existing,
created: false,
remainingSeats: capacity - reservations.size,
};
}
if (reservations.size >= capacity) {
return { status: 'full', workshopId, remainingSeats: 0 };
}
const confirmation = {
status: 'confirmed',
reservationId: `r-${String(nextReservationNumber++).padStart(3, '0')}`,
workshopId,
email,
};
reservations.set(email, confirmation);
return {
...confirmation,
created: true,
remainingSeats: capacity - reservations.size,
};
}
return { reserve };
}
What should match?
Confirm that both reservations.get(email) and reservations.set(email, confirmation) use the submitted email directly. This is the deliberate first-pass behavior.
- Ask Claude Code to create src/http.mjs from the two reference chunks below.
- Tell Claude Code to preserve the chunks in the displayed order.
import * as http from 'node:http';
async function readJson(request) {
let body = '';
for await (const chunk of request) body += chunk;
return JSON.parse(body);
}
function writeJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'content-type': 'application/json' });
response.end(JSON.stringify(payload));
}
function statusCodeFor(result) {
if (result.status === 'confirmed') return result.created ? 201 : 200;
if (result.status === 'invalid-email') return 400;
if (result.status === 'unknown-workshop') return 404;
if (result.status === 'full') return 409;
return 500;
}
What does this section do?
- The built-in HTTP module provides the local server boundary.
- The JSON helpers read request bodies and serialize response payloads.
- The status mapper converts domain outcomes into caller-visible HTTP status codes.
- Finish src/http.mjs with the server adapter below.
export function createReservationHttpServer(service) {
return http.createServer(async (request, response) => {
if (request.method !== 'POST' || request.url !== '/reservations') {
writeJson(response, 404, { status: 'not-found' });
return;
}
try {
const result = service.reserve(await readJson(request));
writeJson(response, statusCodeFor(result), result);
} catch {
writeJson(response, 400, { status: 'invalid-json' });
}
});
}
What does this section do?
- The adapter accepts reservation requests at POST /reservations.
- A valid JSON body reaches the domain service.
- Unsupported routes and malformed JSON receive explicit failure responses.
- Ask Claude Code to save the assembled adapter as src/http.mjs.
- Confirm that Claude Code reports the adapter as created inside src.
HTTP adapter is incomplete?
Check that the import appears first. Confirm that createReservationHttpServer() appears after the three helper functions.
Use the full reference to locate a missing route or status mapping. Help me compare src/http.mjs with the supplied HTTP adapter without adding new routes.
✔️ Awesome, I've got everything!
The domain outcomes now have a local HTTP boundary.
ⓧ I'd like to double check the full code
Compare your src/http.mjs with this complete reference.
import * as http from 'node:http';
async function readJson(request) {
let body = '';
for await (const chunk of request) body += chunk;
return JSON.parse(body);
}
function writeJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'content-type': 'application/json' });
response.end(JSON.stringify(payload));
}
function statusCodeFor(result) {
if (result.status === 'confirmed') return result.created ? 201 : 200;
if (result.status === 'invalid-email') return 400;
if (result.status === 'unknown-workshop') return 404;
if (result.status === 'full') return 409;
return 500;
}
export function createReservationHttpServer(service) {
return http.createServer(async (request, response) => {
if (request.method !== 'POST' || request.url !== '/reservations') {
writeJson(response, 404, { status: 'not-found' });
return;
}
try {
const result = service.reserve(await readJson(request));
writeJson(response, statusCodeFor(result), result);
} catch {
writeJson(response, 400, { status: 'invalid-json' });
}
});
}
What should match?
Confirm that the adapter exposes only POST /reservations. The confirmation mapping should return 201 for a new reservation and 200 for an exact retry.
- Ask Claude Code to create src/server.mjs with this exact content:
import { createReservationService } from './reservations.mjs';
import { createReservationHttpServer } from './http.mjs';
const service = createReservationService({ 'spec-101': 2 });
const server = createReservationHttpServer(service);
server.listen(3000, '127.0.0.1', () => {
console.log('Reservation API listening at http://127.0.0.1:3000');
});
What does this entry point do?
- The service configures spec-101 with two available seats.
- The adapter listens on local port 3000.
- The startup message provides the local API address.
- Confirm that Claude Code reports src/server.mjs as created.
Server entry point looks different?
Confirm that the imports point to the two modules inside src. The workshop capacity should remain 2.
Ask for a line comparison before continuing. Help me compare src/server.mjs with the supplied local server entry point.
✔️ Awesome, I've got everything!
The starter API now has a local entry point configured for the workshop fixture.
ⓧ I'd like to double check the full code
Compare your src/server.mjs with this complete reference.
import { createReservationService } from './reservations.mjs';
import { createReservationHttpServer } from './http.mjs';
const service = createReservationService({ 'spec-101': 2 });
const server = createReservationHttpServer(service);
server.listen(3000, '127.0.0.1', () => {
console.log('Reservation API listening at http://127.0.0.1:3000');
});
What should match?
The service configuration and local listening address should match exactly. This entry point remains available for running the API as a persistent local process later.
Demonstrate the exact-string retry
The demo sends the same email string twice through the HTTP boundary. This example makes the starter look correct because the second map lookup receives the exact key stored by the first request.
- Ask Claude Code to create src/demo.mjs with this exact content:
import { createReservationService } from './reservations.mjs';
import { createReservationHttpServer } from './http.mjs';
const service = createReservationService({ 'spec-101': 2 });
const server = createReservationHttpServer(service);
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const address = server.address();
const baseUrl = `http://127.0.0.1:${address.port}`;
async function reserve(email) {
const response = await fetch(`${baseUrl}/reservations`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ workshopId: 'spec-101', email }),
});
return { statusCode: response.status, body: await response.json() };
}
try {
console.log(await reserve('Avery@Example.com'));
console.log(await reserve('Avery@Example.com'));
} finally {
await new Promise((resolve) => server.close(resolve));
}
What does this demo do?
- The demo starts the same HTTP adapter on an available local port.
- The helper submits Avery@Example.com to the reservation route.
- The two calls use byte-for-byte identical email strings.
- The final block closes the temporary server after both responses are printed.
- Confirm that Claude Code reports src/demo.mjs as created.
Demo file is incomplete?
Check that both reserve() calls use the same email string. Confirm that the finally block closes the server.
Use the complete reference before running the demo. Help me compare src/demo.mjs with the supplied exact-string retry demo.
✔️ Awesome, I've got everything!
Your demo is ready to test the starter through its HTTP boundary.
ⓧ I'd like to double check the full code
Compare your src/demo.mjs with this complete reference.
import { createReservationService } from './reservations.mjs';
import { createReservationHttpServer } from './http.mjs';
const service = createReservationService({ 'spec-101': 2 });
const server = createReservationHttpServer(service);
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const address = server.address();
const baseUrl = `http://127.0.0.1:${address.port}`;
async function reserve(email) {
const response = await fetch(`${baseUrl}/reservations`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ workshopId: 'spec-101', email }),
});
return { statusCode: response.status, body: await response.json() };
}
try {
console.log(await reserve('Avery@Example.com'));
console.log(await reserve('Avery@Example.com'));
} finally {
await new Promise((resolve) => server.close(resolve));
}
What should match?
Both request calls should contain exactly Avery@Example.com. The temporary server should start before the requests and close in the final block.
Before you run the demo, predict whether the second identical request receives a new reservation ID.
- Ask Claude Code to run the demo with this command:
node src/demo.mjs
What does this command prove?
The command executes the demo through Node.js. Both requests cross the HTTP boundary before reaching the raw-email domain service.
You will see a first response with status code 201. The confirmation includes reservation ID r-001 and one remaining seat.
You will then see a retry response with status code 200. It carries the same reservation ID and leaves the remaining-seat count at 1.
Demo did not show 201 then 200?
- Confirm that both calls in src/demo.mjs submit exactly Avery@Example.com.
- Confirm that src/reservations.mjs checks reservations.get(email) before the capacity branch.
- Confirm that src/http.mjs maps an existing confirmation to status code 200.
Share the visible response objects without changing the test guardrails. Help me debug why node src/demo.mjs does not print a 201 confirmation followed by a 200 retry with the same reservation ID.
That first pass is working. You now have a convincing API result whose unresolved identity rule can be tested with a more demanding retry.
Next, you will add requirement-labeled acceptance evidence that challenges the raw-email behavior with casing and surrounding whitespace.
Expose the Requirements Gap
Your exact-string demo returned one confirmation for the same email twice. That success only covers byte-for-byte identical input.
You will use Claude Code to turn the missing identity rule into an explicit contract. Acceptance testing then checks that contract through HTTP.
In this step, get ready to:
- Create six requirement-labeled HTTP acceptance tests.
- Resolve the ambiguous email identity rule in a written specification.
- Preserve the focused test failures that expose the requirements gap.
Add the requirement-labeled acceptance fixture
An acceptance fixture checks the API through the same boundary that a caller uses. Requirement IDs make each result traceable to one promised behavior.
- Start a Claude Code session from the existing reservation-retry-trap folder by running this command:
claude
What happens when Claude Code starts?
Claude Code reads CLAUDE.md at the start of the conversation. That file protects the acceptance criteria from edits made only to hide a failure.
Claude Code Does Not Start?
Return to the toolchain diagnostics from the previous step if the session does not open. Help me diagnose why Claude Code is not starting in my project folder.
- Ask Claude Code to read BRIEF.md.
- Ask Claude Code to read the workflow rules in CLAUDE.md.
- Ask Claude Code to create test/acceptance.test.mjs with the withApi helper.
- Ask Claude Code to add the postReservation helper.
- Ask Claude Code to add six HTTP tests labeled R-01 through R-06.
- Specify Avery@Example.com as the first email in the critical retry test.
- Specify avery@example.com as the second email in the critical retry test.
- Allow the edit if Claude Code asks for permission to create the test file.
✔️ Awesome, I've got everything!
- Save test/acceptance.test.mjs after confirming that it contains six requirement-labeled tests.
ⓧ I'd like to double check the full code
import test from 'node:test';
import assert from 'node:assert/strict';
import { createReservationService } from '../src/reservations.mjs';
import { createReservationHttpServer } from '../src/http.mjs';
async function withApi(workshopCapacities, run) {
const service = createReservationService(workshopCapacities);
const server = createReservationHttpServer(service);
await new Promise((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', resolve);
});
const address = server.address();
try {
await run(`http://127.0.0.1:${address.port}`);
} finally {
await new Promise((resolve) => server.close(resolve));
}
}
async function postReservation(baseUrl, payload) {
const response = await fetch(`${baseUrl}/reservations`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
});
return { statusCode: response.status, body: await response.json() };
}
test('[R-01] confirms the first valid reservation', async () => {
await withApi({ 'spec-101': 2 }, async (baseUrl) => {
const result = await postReservation(baseUrl, { workshopId: 'spec-101', email: 'avery@example.com' });
assert.equal(result.statusCode, 201);
assert.equal(result.body.status, 'confirmed');
assert.equal(result.body.reservationId, 'r-001');
assert.equal(result.body.remainingSeats, 1);
});
});
test('[R-02] refuses a distinct reservation after capacity is exhausted', async () => {
await withApi({ 'spec-101': 1 }, async (baseUrl) => {
await postReservation(baseUrl, { workshopId: 'spec-101', email: 'avery@example.com' });
const result = await postReservation(baseUrl, { workshopId: 'spec-101', email: 'blake@example.com' });
assert.equal(result.statusCode, 409);
assert.equal(result.body.status, 'full');
assert.equal(result.body.remainingSeats, 0);
});
});
test('[R-03] returns the normalized email identity', async () => {
await withApi({ 'spec-101': 2 }, async (baseUrl) => {
const result = await postReservation(baseUrl, { workshopId: 'spec-101', email: ' Avery@Example.com ' });
assert.equal(result.body.email, 'avery@example.com');
});
});
test('[R-04] treats case and whitespace variants as one idempotent retry', async () => {
await withApi({ 'spec-101': 2 }, async (baseUrl) => {
const first = await postReservation(baseUrl, { workshopId: 'spec-101', email: 'Avery@Example.com' });
const retry = await postReservation(baseUrl, { workshopId: 'spec-101', email: ' avery@example.com ' });
assert.equal(retry.statusCode, 200);
assert.equal(retry.body.reservationId, first.body.reservationId);
assert.equal(retry.body.remainingSeats, first.body.remainingSeats);
assert.equal(retry.body.created, false);
});
});
test('[R-05] rejects a blank email without consuming capacity', async () => {
await withApi({ 'spec-101': 2 }, async (baseUrl) => {
const invalid = await postReservation(baseUrl, { workshopId: 'spec-101', email: ' ' });
const valid = await postReservation(baseUrl, { workshopId: 'spec-101', email: 'avery@example.com' });
assert.equal(invalid.statusCode, 400);
assert.equal(invalid.body.status, 'invalid-email');
assert.equal(valid.body.remainingSeats, 1);
});
});
test('[R-06] rejects an unknown workshop', async () => {
await withApi({ 'spec-101': 2 }, async (baseUrl) => {
const result = await postReservation(baseUrl, { workshopId: 'missing', email: 'avery@example.com' });
assert.equal(result.statusCode, 404);
assert.equal(result.body.status, 'unknown-workshop');
});
});
The fixture now covers first confirmations, capacity limits, normalized identity, retries, invalid email, and unknown workshops. Its critical pair gives the vague duplicate rule a deterministic test case.
Turn ambiguity into an explicit specification
Requirements extraction separates stated behavior from assumptions. The phrase “prevent duplicate reservations” leaves the meaning of email identity unresolved.
- Ask Claude Code to extract the functional requirements from BRIEF.md.
- Ask Claude Code to list the constraints from BRIEF.md.
- Ask Claude Code to list its assumptions about reservation identity.
- Ask Claude Code to identify unresolved questions about retries.
- Confirm that the brief does not define whether email casing or surrounding whitespace changes identity.
- Give Claude Code the stakeholder decision that reservation identity is (workshopId, normalizedEmail).
- Define normalizedEmail as the submitted email after trim() followed by toLowerCase().
- State that a retry returns the original reservation ID.
- State that a retry does not consume another seat.
- Ask Claude Code to create SPEC.md with requirements R-01 through R-06.
- Tell Claude Code to leave the implementation files unchanged.
✔️ Awesome, I've got everything!
- Save SPEC.md after confirming that every requirement has one requirement ID.
ⓧ I'd like to double check the full code
# Reservation Specification
## Stakeholder decisions
- Reservation identity is the pair `(workshopId, normalizedEmail)`.
- `normalizedEmail` is the submitted email after `trim()` and `toLowerCase()`.
- A retry for an existing identity returns the original reservation ID.
- A retry does not consume another seat.
- Data remains in memory and resets when the process exits.
## Requirements
- R-01: A valid first request for a known workshop with capacity returns HTTP 201 and a confirmation with a stable reservation ID.
- R-02: A workshop never confirms more reservations than its configured capacity; the next distinct identity receives HTTP 409 with status `full`.
- R-03: The API trims and lowercases email before using or returning the reservation identity.
- R-04: A retry for the same normalized identity returns HTTP 200, the original reservation ID, and the same remaining-seat count produced by the first request.
- R-05: A blank or non-string email returns HTTP 400 with status `invalid-email` and consumes no seat.
- R-06: An unknown workshop returns HTTP 404 with status `unknown-workshop`.
## HTTP contract
`POST /reservations`
Request body:
```json
{
"workshopId": "spec-101",
"email": "Avery@Example.com"
}
```
Outcomes:
- First confirmation: HTTP 201
- Idempotent retry: HTTP 200
- Invalid email: HTTP 400
- Unknown workshop: HTTP 404
- Full workshop: HTTP 409
## Out of scope
Persistence, authentication, distributed concurrency, cancellation, and deployment are not part of the core project.
Why Make Identity Explicit?
The pair scopes one email identity to one workshop. Normalizing before lookup makes casing or surrounding whitespace irrelevant to idempotency.
The specification also fixes the expected HTTP outcomes before implementation changes begin. That keeps the contract stable when a test exposes a gap.
Strong progress. SPEC.md now records the stakeholder decision that the original brief left undefined.
- Ask Claude Code to confirm that src/reservations.mjs remains unchanged.
- Ask Claude Code to confirm that src/http.mjs remains unchanged.
- Return to the shell by pressing Ctrl+D twice.
Run the contract against the starter
The specification now defines normalized identity. The starter still stores reservations under the submitted email string.
Before you run the suite, which requirements do you think the raw-email implementation satisfies?
- Run all six acceptance tests against the unchanged starter by running:
node --test
The Requirements Gap Is Real
You should see passing results for R-01, R-02, R-05, and R-06. You should see failures for R-03 and R-04.
The first failure shows that the response preserves raw casing or whitespace. The second failure shows that the variant retry creates another reservation identity.
Seeing a Different Test Result?
- Compare test/acceptance.test.mjs with the full-file reference if more than two tests fail.
- Restore the raw-email starter if all six tests pass.
- Confirm that existing still comes from reservations.get(email).
Help me compare my Node.js test output with the expected R-01 through R-06 results.
You have turned one vague product phrase into two reproducible failures. The four passing requirements prove that the service has a focused identity gap.
The contract now detects the raw-email flaw without changing implementation. Next, you will map every requirement to a component and plan the smallest supported fix.
Build the Traceable Plan
Your acceptance suite now makes the requirements gap visible. R-03 fails because the API returns the submitted email. R-04 fails because case variants create separate reservations.
Editing immediately could turn those failures into isolated patches. You will use Claude Code plan mode to connect the identity decision to every affected layer before any implementation changes.
In this step, get ready to:
- Build a traceability row for every requirement.
- Use Claude Code plan mode to inspect the failing system.
- Approve an implementation plan that preserves the failure evidence.
Map every requirement to evidence
A traceability matrix gives every requirement a visible path from contract to implementation. It also names the exact acceptance result that proves the behavior.
- Create TRACEABILITY.md inside the reservation-retry-trap folder with this content:
# Traceability Matrix
| Requirement | Contract | Component | Acceptance result |
| --- | --- | --- | --- |
| R-01 | First valid reservation returns 201 and a confirmation | `createReservationService().reserve`, `createReservationHttpServer` | `[R-01] confirms the first valid reservation` |
| R-02 | Capacity cannot be exceeded | Capacity branch in `reserve` | `[R-02] refuses a distinct reservation after capacity is exhausted` |
| R-03 | Email identity is trimmed and lowercased | `normalizeEmail` | `[R-03] returns the normalized email identity` |
| R-04 | Retry returns the original result without consuming a seat | Existing-identity branch in `reserve` | `[R-04] treats case and whitespace variants as one idempotent retry` |
| R-05 | Invalid email returns 400 and consumes no seat | Validation branch in `reserve` | `[R-05] rejects a blank email without consuming capacity` |
| R-06 | Unknown workshop returns 404 | Workshop lookup in `reserve` | `[R-06] rejects an unknown workshop` |
How does the matrix create traceability?
- Requirement IDs make the agreed scope countable.
- Each contract points to its implementing component.
- Each acceptance result repeats its exact test title.
- Save TRACEABILITY.md.
- Count the requirement rows in TRACEABILITY.md.
You should find exactly six rows covering R-01 through R-06.
Does a mapping look incomplete?
Compare the requirement ID with its exact test title in test/acceptance.test.mjs. Keep the title unchanged so the test output remains requirement-linked.
Ask for help if a contract does not have a clear component: Help me check this traceability row against SPEC.md and test/acceptance.test.mjs.
✔️ Awesome, I've got everything!
Your matrix now gives all six requirements a named implementation path.
ⓧ I'd like to double check the full code
# Traceability Matrix
| Requirement | Contract | Component | Acceptance result |
| --- | --- | --- | --- |
| R-01 | First valid reservation returns 201 and a confirmation | `createReservationService().reserve`, `createReservationHttpServer` | `[R-01] confirms the first valid reservation` |
| R-02 | Capacity cannot be exceeded | Capacity branch in `reserve` | `[R-02] refuses a distinct reservation after capacity is exhausted` |
| R-03 | Email identity is trimmed and lowercased | `normalizeEmail` | `[R-03] returns the normalized email identity` |
| R-04 | Retry returns the original result without consuming a seat | Existing-identity branch in `reserve` | `[R-04] treats case and whitespace variants as one idempotent retry` |
| R-05 | Invalid email returns 400 and consumes no seat | Validation branch in `reserve` | `[R-05] rejects a blank email without consuming capacity` |
| R-06 | Unknown workshop returns 404 | Workshop lookup in `reserve` | `[R-06] rejects an unknown workshop` |
Plan without editing implementation
Plan mode separates investigation from implementation. Claude Code can inspect the specification and failing tests while the raw-email source remains untouched.
- Start Claude Code in plan mode by running this command in the Terminal from earlier:
claude --permission-mode plan
Why use plan mode here?
The current failures are evidence of a contract gap. Plan mode protects that evidence while Claude Code reasons about the smallest implementation change.
- Give Claude Code this inspection task:
Inspect BRIEF.md.
Inspect SPEC.md.
Inspect TRACEABILITY.md.
Inspect src/reservations.mjs.
Inspect src/http.mjs.
Inspect src/server.mjs.
Inspect test/acceptance.test.mjs.
Create a minimal architecture plan for R-03.
Extend that plan to R-04.
Include the implementation sequence.
Do not edit any files.
Keep existing HTTP outcomes unchanged.
Keep all six acceptance tests unchanged.
What does this inspection cover?
- The source files reveal the current raw-email behavior.
- The specification defines the resolved identity contract.
- The acceptance suite preserves the observable HTTP boundary.
- Review the architecture proposed by Claude Code.
- Confirm that business rules remain in src/reservations.mjs.
- Confirm that protocol translation remains in src/http.mjs.
You should see a proposed implementation sequence without any source file changes.
Did Claude propose an immediate edit?
Reject the edit request. Remind Claude Code that this session must finish the plan before implementation begins.
Use this if the proposal still skips planning: Help me keep Claude Code focused on a no-edit plan for the failing reservation requirements.
Review and approve the implementation order
The order of operations determines whether a retry is safe. Normalization must happen before state lookup or mutation.
- Press Ctrl+G to open the proposed plan in a text editor.
- Find identity normalization before storage lookup.
- Find normalized-value validation before state mutation.
- Find the existing-confirmation return before capacity checks.
- Confirm that implementation work preserves the acceptance tests.
These checks keep the final change tied to the stakeholder decision instead of the failing assertion alone.
- Ask Claude Code to save the reviewed content below exactly as PLAN.md in the reservation-retry-trap folder:
# Approved Implementation Plan
## Architecture
- `src/reservations.mjs` owns identity normalization, capacity, and idempotent reservation state.
- `src/http.mjs` translates HTTP requests and domain outcomes without owning business rules.
- `src/server.mjs` supplies local workshop configuration and starts the adapter.
- `test/acceptance.test.mjs` verifies all requirements through the HTTP boundary.
## Implementation sequence
1. Add `normalizeEmail(email)` using `trim()` followed by `toLowerCase()`.
2. Validate the normalized value before reading or changing reservation state.
3. Use the normalized value as the per-workshop map key and response email.
4. Return an existing confirmation before checking remaining capacity.
5. Preserve the HTTP status mapping already defined in `SPEC.md`.
6. Run `node --test` and fix implementation only until R-01 through R-06 pass.
## Guardrails
- Do not edit requirement IDs or acceptance expectations during implementation.
- Do not add persistence, authentication, cancellation, or external packages.
- Completion requires one passing output line for each requirement in `TRACEABILITY.md`.
What does the approved plan protect?
- The architecture keeps domain behavior separate from protocol translation.
- The implementation sequence puts normalization before state access.
- The guardrails limit iteration to implementation changes.
- Approve only the action that writes PLAN.md.
- Return to the Terminal from earlier after Claude Code saves the file.
- Count the numbered implementation steps in PLAN.md.
You should find six implementation steps followed by three guardrails.
Does PLAN.md contain extra work?
Remove any proposal for persistence or cancellation. Remove any proposal that changes requirement IDs or acceptance expectations.
Ask for a scope check if the plan still exceeds the specification: Help me compare PLAN.md with the core scope in SPEC.md.
✔️ Awesome, I've got everything!
Your approved plan now preserves the contract boundary before implementation starts.
ⓧ I'd like to double check the full code
# Approved Implementation Plan
## Architecture
- `src/reservations.mjs` owns identity normalization, capacity, and idempotent reservation state.
- `src/http.mjs` translates HTTP requests and domain outcomes without owning business rules.
- `src/server.mjs` supplies local workshop configuration and starts the adapter.
- `test/acceptance.test.mjs` verifies all requirements through the HTTP boundary.
## Implementation sequence
1. Add `normalizeEmail(email)` using `trim()` followed by `toLowerCase()`.
2. Validate the normalized value before reading or changing reservation state.
3. Use the normalized value as the per-workshop map key and response email.
4. Return an existing confirmation before checking remaining capacity.
5. Preserve the HTTP status mapping already defined in `SPEC.md`.
6. Run `node --test` and fix implementation only until R-01 through R-06 pass.
## Guardrails
- Do not edit requirement IDs or acceptance expectations during implementation.
- Do not add persistence, authentication, cancellation, or external packages.
- Completion requires one passing output line for each requirement in `TRACEABILITY.md`.
The Node.js test runner now checks whether planning left the implementation evidence unchanged.
Before you rerun the suite, which requirement results should change after a no-edit planning session?
- Verify the unchanged implementation by running the acceptance suite:
node --test
What does this prove?
You should see R-01 pass. You should see R-02 pass.
You should see R-05 pass. You should see R-06 pass.
You should still see R-03 fail. You should still see R-04 fail.
That unchanged result proves the planning session did not mutate the raw-email implementation.
Did the requirement results change?
Check whether Claude Code edited src/reservations.mjs or test/acceptance.test.mjs. Restore the prior artifact if either file changed.
Use this to investigate an unexpected result: Help me determine why the acceptance results changed during a plan-only step.
That is disciplined progress: your plan is approved while the implementation gap remains visible. Next, you will implement only the changes that R-03 and R-04 demand.
Implement the Contract and Prove Coverage
Your approved plan now connects each failing requirement to a precise implementation change. R-03 and R-04 still fail, so the evidence remains honest.
Now you will use Claude Code to implement only the approved contract. You will then use Node.js HTTP acceptance testing to prove every requirement.
In this step, get ready to:
- Implement the normalized identity contract through Claude Code.
- Resolve implementation failures without changing the specification or acceptance fixture.
- Prove R-01 through R-06 with requirement-labeled acceptance results.
Implement the approved contract
The approved plan defines the smallest change that closes the identity gap. Keeping the requirements and acceptance tests fixed ensures the implementation must satisfy the agreed contract.
- Switch back to the Claude Code session in Terminal.
- Submit this implementation request:
Implement PLAN.md.
Do not modify SPEC.md, TRACEABILITY.md, PLAN.md, or test/acceptance.test.mjs.
Make implementation changes only in src/reservations.mjs.
Add normalizeEmail(email).
Normalize email before validation or lookup.
Use normalizedEmail as the storage key and response email.
Return an existing confirmation before the capacity check.
Stop after editing.
Summarize the files you changed.
What does this request protect?
- The fixed SPEC.md keeps the stakeholder decisions unchanged.
- The fixed TRACEABILITY.md preserves the mapping from each requirement to its evidence.
- The fixed test/acceptance.test.mjs prevents Claude Code from weakening a failing assertion.
- The approved PLAN.md keeps the edit focused on the observed identity gap.
- Approve the src/reservations.mjs edit if Claude Code requests permission.
- Review the changed-file summary to confirm that only src/reservations.mjs changed.
You should see normalizeEmail(email) added before the service. The reservation map should now use normalizedEmail.
Did Claude Code change a protected file?
- Reject any proposed edit to SPEC.md, TRACEABILITY.md, PLAN.md, or test/acceptance.test.mjs.
- Ask Claude Code to revert the protected file before continuing.
Help me check whether Claude Code stayed within the approved implementation scope.
Test and refine the implementation
A plausible code change still needs executable evidence. Claude Code can use the same failing acceptance suite as a feedback loop while the contract remains fixed.
Before Claude Code runs the suite, predict whether R-03 and R-04 now pass.
- Submit this verification request:
Run node --test.
Inspect any failures.
Iterate only on src/reservations.mjs until R-01 through R-06 pass.
Do not modify SPEC.md, TRACEABILITY.md, PLAN.md, or test/acceptance.test.mjs.
Show the final test output.
How does this keep iteration honest?
Each retry changes implementation code against the same six acceptance tests. Claude Code must close the observed gap without moving the evidence.
The final output matters because every result carries the requirement ID it proves. Coverage stays visible at the contract level.
Claude Code should finish with six passing results and zero failures. Each displayed result should begin with one label from [R-01] through [R-06].
Are R-03 or R-04 still failing?
- Check that normalizeEmail(email) runs before the reservation map lookup.
- Check that both reservations.get(normalizedEmail) and reservations.set(normalizedEmail, confirmation) use the normalized key.
- Check that the existing-confirmation branch appears before the capacity branch.
Help me diagnose the remaining requirement failure.
- Compare Claude Code's final src/reservations.mjs with the complete reference below:
✔️ Awesome, I've got everything!
Your implementation matches the approved contract. Keep the file saved before running your independent proof.
ⓧ I'd like to double check the full code
Compare every branch in src/reservations.mjs with this complete reference.
export function normalizeEmail(email) {
if (typeof email !== 'string') return '';
return email.trim().toLowerCase();
}
export function createReservationService(workshopCapacities) {
const reservationsByWorkshop = new Map();
let nextReservationNumber = 1;
function reservationsFor(workshopId) {
if (!reservationsByWorkshop.has(workshopId)) {
reservationsByWorkshop.set(workshopId, new Map());
}
return reservationsByWorkshop.get(workshopId);
}
function reserve({ workshopId, email }) {
const capacity = workshopCapacities[workshopId];
if (!Number.isInteger(capacity) || capacity < 1) {
return { status: 'unknown-workshop', workshopId };
}
const normalizedEmail = normalizeEmail(email);
if (!normalizedEmail) {
return { status: 'invalid-email', workshopId };
}
const reservations = reservationsFor(workshopId);
const existing = reservations.get(normalizedEmail);
if (existing) {
return {
...existing,
created: false,
remainingSeats: capacity - reservations.size,
};
}
if (reservations.size >= capacity) {
return { status: 'full', workshopId, remainingSeats: 0 };
}
const confirmation = {
status: 'confirmed',
reservationId: `r-${String(nextReservationNumber++).padStart(3, '0')}`,
workshopId,
email: normalizedEmail,
};
reservations.set(normalizedEmail, confirmation);
return {
...confirmation,
created: true,
remainingSeats: capacity - reservations.size,
};
}
return { reserve };
}
Prove requirement coverage
Claude Code's test run guided the implementation. Your own run supplies the independent completion evidence required by the traceability matrix.
Before you run the proof, predict whether any requirement label will still fail.
- Run the complete acceptance suite yourself with this command:
node --test
What does this command prove?
The Node.js test runner exercises all six requirements through the local HTTP boundary. This checks the response codes and serialized reservation results that a caller receives.
The result names preserve the link back to TRACEABILITY.md. A green total now carries requirement-level evidence.
You should see exactly six passing acceptance results labeled from [R-01] through [R-06]. You should see zero failures.
- Match each displayed requirement label to its row in TRACEABILITY.md.
- Confirm that no acceptance result appears without a mapped requirement ID.
Still seeing a failing result?
- Read the failing requirement label before changing any code.
- Compare that label with its contract and component in TRACEABILITY.md.
- Keep every fix inside src/reservations.mjs unless the approved plan names another implementation file.
Help me trace a failing acceptance result back to the implementation.
Strong work. Your reservation API now proves every agreed behavior with a labeled acceptance result.
Secret mission
Make Cancellation Idempotent
A cancellation retry can restore capacity twice unless the service remembers the first result. Extend the specification chain with R-07 and R-08. Then add a cancellation tombstone that returns stable evidence on every retry.
Clean Up Your Resources
Clean Up Your Resources
Your reservation API runs entirely on your Mac, with no cloud resources or ongoing costs. Choose whether to keep the workspace ready, pause your local session, or delete the project files.
Resources you used:
- The local reservation-retry-trap folder. It contains your specification, traceability matrix, implementation plan, source code, and eight requirement-labeled acceptance tests.
Keep everything running
No action needed. Choose this if you want to keep extending the reservation and cancellation contracts.
- Keep reservation-retry-trap in its current parent folder.
- Continue using the open Terminal window for further project work.
Your specification chain and all eight requirement-labeled acceptance results remain ready to reproduce.
Pause - I'll come back to this later
Pausing ends your local session while keeping every project file. The test runner has already exited, so no server or background process needs to be stopped.
- Close the open Terminal window.
- Keep reservation-retry-trap in its current parent folder.
Your source files and requirement evidence remain available for your next session.
Delete - I don't want to use this again
Deleting the folder is permanent. It only removes this local workspace, so your Claude Code and Node.js installations remain untouched.
- Remove the project folder from its parent directory by running these commands:
cd ..
rm -rf reservation-retry-trap
What does this cleanup do?
- The cd .. command moves Terminal from reservation-retry-trap to its parent folder.
- The rm -rf reservation-retry-trap command deletes the local project directory. Every file inside it is removed.
The command returns you to the prompt. The reservation-retry-trap folder is no longer present in its parent folder.
Folder Still Present?
Check that Terminal was inside reservation-retry-trap before the first line ran. The cd .. line depends on that starting point.
Help me safely delete the local project folder.
Nice Work!
Nice Work!
The reservation retry trap is solved. Your local API now proves explicit retry contracts with requirement-labeled HTTP acceptance results from R-01 through R-08.
What you learned:
- Built a local HTTP reservation API with explicit capacity limits. Normalized email identity maps each casing or whitespace variant to one reservation. Idempotent retries return stable evidence without consuming another seat.
- Turned ambiguous product language into explicit contracts in SPEC.md. Used Claude Code plan mode to review PLAN.md before implementation edits began.
- Built a traceability matrix that maps every requirement to its contract. Each row names the implementing component. Each row also names the exact acceptance result. Finished the core project with six named passes from R-01 through R-06.
- Secret Mission: Extended the specification chain through R-08. Added idempotent cancellation with a saved cancellation tombstone. Repeated cancellation now returns the original result without restoring capacity twice.
Ready to quiz yourself?