1. Write down the rule before reading the code
Start by writing down a rule the feature must follow. In a product that sells credits, a purchase should add the credits once, and only an authorised operation should spend them. Keep the wording independent of function names or database tables. You can then compare the code with that rule instead of accepting it because the implementation looks plausible. Include a simple example with starting data, the action taken and the result you expect to see.
- Describe one important rule without naming a function.
- List the entry points that can change the relevant state.
- Identify the persistent record that demonstrates the rule was followed.
2. Follow an operation through the product
Choose an operation and follow it from the request through the database and any external service. Note where the input is checked, where permissions are enforced and when data is saved. Watch for gaps between functions: the route may assume a helper checks team membership while the helper assumes the route already did it. Reading those files together makes the missing check easier to spot. Write down any step whose responsibility is unclear.
3. Check permissions on the resource
OWASP recommends checking permissions on every request and denying access when no rule allows it. Apply that to the resource being requested. Being signed in might let someone read their own report, but it should not let them read every report. Write the expected result for the owner, a teammate and an unrelated account. Compare those cases with the code, including administrative routes and background jobs that may use different permissions from the browser.
4. Try repeated and interrupted operations
If the product receives Stripe webhooks, account for duplicate deliveries and events arriving out of order, as described in Stripe’s documentation. Check whether receiving the same event again would repeat a purchase or add credits twice. Also try stopping an operation partway through in a controlled test environment. Inspect the saved data as well as the response. You need to know what remains after the failure and what the next attempt will do.
- A repeated event does not repeat an effect that should happen once.
- A failed step leaves a state that can be identified and handled.
- Recovery does not depend on a user reopening a browser page.
5. Check what the tests actually prove
A passing test can still contain the same mistake as the code. Compare it with the product rule from step one. Would it fail if the permission check were removed? Would it notice the same purchase being applied twice? Focus on behaviours that affect access, money, saved data or the ability to run the service. You do not need a separate test for every helper, but important failures should produce a result the tests can detect.
6. Find who handles each step
For each operation, identify which code handles validation, retries, caching and error reporting. Two implementations of the same rule can disagree; a missing implementation can leave a failure unhandled. Describe the specific gap before suggesting a restructuring. Similar-looking files do not always need to share an abstraction: first check whether they represent the same rule and are expected to change together. The proposed change should make an actual problem easier to fix or prevent.
7. Record the result of the review
Record the version reviewed, the cases tested and any questions still open. For a problem, include enough detail for someone else to reproduce it and explain its effect on the product. If you accept a change with a known limitation, name who will follow it up and when. Keep this record with the code so the next person can see what was checked without reconstructing the review from comments and chat messages.
- Evidence: what was observed and under which conditions?
- Decision: fix now, narrow the release, or accept an explicit limitation?
- Handoff: what will the next engineer need to understand?
