🕮12 min read · 2,395 words
This is Part 2 of our AI Code Audit Series — covering the essential audits every team should run when building with AI agentic editors.
Of all the audit types we run on AI-generated codebases, the business logic audit is the one that finds the most expensive problems. Not the most numerous — security audits typically surface more individual issues. But the most expensive, because business logic errors directly affect how your application behaves for real users, real transactions, and real money.
A security vulnerability might be exploited by an attacker who finds it. A business logic error is experienced by every user who triggers the affected workflow — often silently, often without anyone realising something is wrong until significant damage has already occurred.
This post covers what business logic errors in AI-generated code look like, why they happen, how to find them systematically, and how to fix them correctly.
Why AI Gets Business Logic Wrong
AI agentic editors are remarkably good at implementing logic that is fully and precisely described in a prompt. The problem is that business logic is rarely fully and precisely described in a prompt — because the developer writing the prompt already understands the business context and unconsciously omits things they consider obvious.
When you ask an AI to implement a discount system, you describe the main cases. The AI implements exactly what you described — cleanly, correctly, and completely missing the edge cases you didn’t mention because you assumed they were implied. The combination discount that shouldn’t stack with a promotional code. The minimum order value that applies to the subtotal, not the post-discount total. The loyalty tier that changes at midnight on the renewal date, not at the time of the first qualifying purchase.
These aren’t AI failures in the sense of the AI doing something wrong. They’re gaps between what was asked and what was needed — and they consistently appear in business logic because business rules are complex, interdependent, and full of implicit knowledge that exists in the business but never makes it into the prompt.
A second reason is that AI generates code for the happy path. It implements the primary flow — the normal case where everything works as expected. Edge cases, error conditions, boundary values, and the specific combinations of inputs that create unusual behaviour are systematically underrepresented in AI-generated code because they’re underrepresented in prompts.
A third reason is that AI has no memory of your previous decisions. If you established a business rule in one part of the codebase three weeks ago, the AI doesn’t know about it when generating related code today. You end up with the same business concept implemented inconsistently in different parts of the application — not because the AI made an error, but because it made two different locally reasonable decisions without knowing they needed to be consistent.
What Business Logic Errors Actually Look Like
Before covering how to find them, it’s worth being concrete about what these errors look like in real Laravel applications.
Incorrect calculation logic is the most immediately dangerous category. AI generates calculation code that works for the cases in the prompt and fails for cases that weren’t mentioned.
// What AI generated — works for simple cases
public function calculateOrderTotal(Order $order): float
{
$subtotal = $order->items->sum(fn($item) => $item->price * $item->quantity);
if ($order->discount_code) {
$discount = $subtotal * ($order->discount_code->percentage / 100);
$subtotal -= $discount;
}
return $subtotal + $order->shipping_cost;
}
// What the business actually requires:
// 1. Discount applies only to eligible items (not sale items)
// 2. Minimum order value check BEFORE discount, not after
// 3. Shipping is free above ₹999 AFTER discount
// 4. Loyalty points discount cannot stack with promotional codes
// 5. Tax calculated on post-discount subtotal, not pre-discount
public function calculateOrderTotal(Order $order): float
{
// Only eligible items qualify for discount
$eligibleSubtotal = $order->items
->where('is_sale_item', false)
->sum(fn($item) => $item->price * $item->quantity);
$fullSubtotal = $order->items
->sum(fn($item) => $item->price * $item->quantity);
// Minimum order check on full subtotal before discount
if ($fullSubtotal < $this->minimumOrderValue) {
throw new OrderBelowMinimumException($this->minimumOrderValue);
}
// Discounts cannot stack — loyalty points OR promo code, not both
$discount = 0;
if ($order->discount_code && !$order->loyalty_points_applied) {
$discount = $eligibleSubtotal * ($order->discount_code->percentage / 100);
} elseif ($order->loyalty_points_applied && !$order->discount_code) {
$discount = $order->loyalty_discount_amount;
}
$discountedSubtotal = $fullSubtotal - $discount;
// Tax on post-discount amount
$tax = $discountedSubtotal * $this->taxRate;
// Free shipping threshold applies after discount
$shipping = $discountedSubtotal >= 999 ? 0 : $order->shipping_cost;
return $discountedSubtotal + $tax + $shipping;
}
The AI’s version works perfectly for a standard order with a simple discount. It silently produces wrong results for every edge case the business actually encounters daily.
Missing state transition validation is where AI generates code that allows entities to move between states that the business rules don’t permit.
// What AI generated — allows any status change
public function updateOrderStatus(Order $order, string $newStatus): void
{
$order->update(['status' => $newStatus]);
event(new OrderStatusUpdated($order));
}
// What the business actually requires — valid transitions only
public function updateOrderStatus(Order $order, string $newStatus): void
{
$validTransitions = [
'pending' => ['confirmed', 'cancelled'],
'confirmed' => ['processing', 'cancelled'],
'processing' => ['shipped'],
'shipped' => ['delivered', 'returned'],
'delivered' => ['returned'],
'cancelled' => [], // terminal state
'returned' => [], // terminal state
];
$currentStatus = $order->status;
if (!in_array($newStatus, $validTransitions[$currentStatus] ?? [])) {
throw new InvalidStatusTransitionException(
"Cannot transition order from {$currentStatus} to {$newStatus}"
);
}
$order->update(['status' => $newStatus]);
event(new OrderStatusUpdated($order));
}
Without state transition validation, an order can be moved from cancelled back to processing, from delivered back to pending, or directly from pending to returned — all of which produce incorrect downstream effects in inventory, accounting, and customer communications.
Incorrect permission boundaries occur when AI implements role-based logic that doesn’t match the actual permission model.
// What AI generated — manager can do everything admin can
public function canApproveExpense(User $user, Expense $expense): bool
{
return in_array($user->role, ['admin', 'manager']);
}
// What the business actually requires:
// - Managers can approve expenses up to ₹50,000
// - Managers cannot approve their own expenses
// - Regional managers can approve up to ₹2,00,000 within their region
// - Only admins can approve above ₹2,00,000
// - Finance team can approve any amount but only for their department
public function canApproveExpense(User $user, Expense $expense): bool
{
// Nobody approves their own expenses
if ($user->id === $expense->submitted_by) {
return false;
}
return match($user->role) {
'admin' => true,
'regional_manager' =>
$expense->amount <= 200000 &&
$expense->region_id === $user->region_id,
'manager' => $expense->amount <= 50000,
'finance' => $expense->department_id === $user->department_id,
default => false,
};
}
Inconsistent business rule implementation is where the same concept is handled differently in different parts of the codebase because the AI made independent decisions in separate sessions.
// In OrderController.php — generated in week 1
$tax = $amount * 0.18; // GST hardcoded
// In InvoiceController.php — generated in week 3
$tax = $amount * config('tax.gst_rate'); // Uses config
// In ReportController.php — generated in week 5
$taxRate = TaxRate::where('type', 'GST')->first()->rate;
$tax = $amount * ($taxRate / 100); // Queries database
// What should exist — one canonical implementation
// In app/Services/TaxCalculationService.php
public function calculateGST(float $amount, string $category = 'standard'): float
{
$rate = $this->getTaxRate('GST', $category);
return round($amount * ($rate / 100), 2);
}
Three different implementations of the same business rule mean that changing the GST rate requires finding and updating three places — and the risk that one gets missed.
How to Run a Business Logic Audit
A business logic audit is not a code review. It’s a requirements traceability exercise — verifying that every business rule is implemented correctly, completely, and consistently across the codebase. Here’s how to run one systematically.
Step 1 — Compile your business rules documentation
Before touching the code, gather every source of business rules: the original project brief, user stories, client emails describing requirements, any specification documents, and implicit rules that exist in the minds of the business stakeholders but haven’t been written down. This last category is the most important and the hardest to surface — schedule a short session with the business owner or product manager specifically to identify rules that “everyone knows” but nobody has written down.
Create a simple audit document with every business rule listed as a testable statement: “A discount code cannot be combined with loyalty points redemption in the same order.” “Managers cannot approve expenses submitted by themselves.” “Free shipping applies when the post-discount order value exceeds ₹999.” These become your audit checklist.
Step 2 — Map rules to code
For each business rule in your checklist, find the code that implements it. Some rules will map to a single function. Others will be spread across multiple files. Some — and this is a significant finding — will have no corresponding code at all, meaning the rule was never implemented.
This mapping process frequently reveals two types of gaps: rules that exist in documentation but have no implementation, and rules that have multiple inconsistent implementations in different parts of the codebase.
Step 3 — Test boundary conditions for each rule
For every business rule, identify the boundary conditions — the values and combinations where the rule transitions from applying to not applying, or where different rules interact. These are where AI-generated logic most frequently fails.
For a discount rule: what happens at exactly the minimum order value? What happens when two discount codes exist but only one should apply? What happens when a discount makes the order total negative? What happens when the discount code expires at midnight and an order is placed at 11:59pm?
Write test cases for each boundary condition. Run them against the current implementation. The failures are your findings.
// Example boundary condition tests for discount logic
class DiscountCalculationAuditTest extends TestCase
{
/** @test */
public function discount_does_not_apply_to_sale_items()
{
$order = Order::factory()->create();
$order->items()->create(['price' => 500, 'quantity' => 1, 'is_sale_item' => true]);
$order->update(['discount_code_id' => DiscountCode::factory()->create(['percentage' => 10])->id]);
$total = $this->calculator->calculateOrderTotal($order);
// Discount should NOT have been applied to the sale item
$this->assertEquals(500, $total->subtotal_after_discount);
}
/** @test */
public function loyalty_points_and_promo_code_cannot_stack()
{
$order = Order::factory()->create([
'discount_code_id' => DiscountCode::factory()->create()->id,
'loyalty_points_applied' => true,
'loyalty_discount_amount' => 100,
]);
// Should throw exception or apply only one discount
$this->expectException(DiscountStackingException::class);
$this->calculator->calculateOrderTotal($order);
}
/** @test */
public function minimum_order_check_uses_pre_discount_subtotal()
{
// Order is ₹600 before discount, ₹480 after 20% discount
// Minimum order is ₹500
// Should PASS because pre-discount amount meets minimum
$order = Order::factory()->withSubtotal(600)->withDiscount(20)->create();
$this->assertDoesNotThrow(
fn() => $this->calculator->calculateOrderTotal($order)
);
}
}
Step 4 — Check for consistency across the codebase
Search the codebase for every place a key business concept appears — discount calculation, permission checking, status transitions, price computation. Verify that each implementation uses the same logic and ideally the same shared service or utility. Inconsistencies are findings regardless of whether individual implementations are correct — inconsistency means future changes will be error-prone.
Grep is useful here:
# Find all places tax/GST is calculated
grep -r "0\.18\|gst_rate\|tax_rate\|GST" app/ --include="*.php" -l
# Find all places order status is updated
grep -r "status.*=.*'" app/ --include="*.php" -n
# Find all discount calculations
grep -r "discount\|percentage.*100\|promo" app/ --include="*.php" -l
Every file in the results is a place where a business rule is implemented. Each should be reviewed against your business rules checklist.
Step 5 — Use AI to cross-check its own logic
Once you’ve identified the key business rules, use the AI to review the implementation against them explicitly:
Here are our business rules for the discount system:
1. Discounts apply only to non-sale items
2. Minimum order value (₹500) is checked on pre-discount subtotal
3. Loyalty points and promotional codes cannot be combined
4. Free shipping threshold (₹999) applies to post-discount subtotal
5. GST (18%) is calculated on post-discount, pre-shipping amount
Here is the current implementation:
[paste calculateOrderTotal method]
For each business rule, confirm whether the implementation correctly handles it.
For any rule that is not correctly implemented, show the specific line where
the error occurs and provide the corrected implementation.
This doesn’t replace the manual audit — the AI will miss things, particularly around rules that require understanding context it doesn’t have. But it catches a significant proportion of straightforward mismatches quickly.
Business Logic Audit Checklist
Use this checklist for every significant AI-generated feature:
Calculations and financial logic
- Are all calculation inputs validated before use?
- Are boundary values (zero, negative, maximum) handled correctly?
- Is rounding applied consistently and at the correct point in the calculation?
- Do combinations of inputs (discounts + taxes + shipping) produce correct totals?
- Are currency amounts stored and compared as integers (paise/cents) not floats?
State machines and workflows
- Are all valid state transitions explicitly defined?
- Are invalid transitions rejected with appropriate errors?
- Are terminal states protected from further transitions?
- Do state changes trigger the correct downstream effects?
- Is concurrent state change handled safely?
Permissions and access control
- Do permission rules match the actual role hierarchy?
- Are self-approval and self-access restrictions in place where required?
- Are amount/scope limits enforced for each role?
- Are permission checks applied consistently across all entry points?
Business rule consistency
- Is each business concept implemented in one canonical location?
- Are there multiple implementations of the same rule that could diverge?
- Do all implementations of a shared concept use the same service or utility?
- Are configuration values (rates, thresholds, limits) stored in config files, not hardcoded?
Edge cases and error conditions
- What happens when required related records don’t exist?
- What happens at exactly the boundary value of a threshold rule?
- What happens when two rules conflict for the same input?
- Are error conditions handled explicitly or silently ignored?
What to Do With Audit Findings
A business logic audit produces a list of findings — rules that aren’t implemented, rules that are implemented incorrectly, and rules that are implemented inconsistently. Prioritise them by impact before fixing.
Financial calculation errors and permission boundary violations are critical — fix these immediately before any further development. State transition errors are high priority — fix before the next release. Consistency issues are medium priority — create shared services and refactor over the next sprint cycle. Documentation gaps — rules that exist in people’s heads but aren’t written down — are ongoing — address in your next requirements session with the business stakeholder.
Every finding also informs how you write prompts going forward. If the AI consistently misses the rule that discounts don’t apply to sale items, that rule gets added to every discount-related prompt from now on. The audit findings are training data for better prompting, not just bugs to fix.
Next in the series: Security Audit for AI-Generated Code — the systematic checklist for catching the vulnerabilities that agentic editors introduce most frequently.
If you want a business logic audit run on your existing codebase — to identify where AI-generated code has diverged from your actual business requirements — our team at Softcrony is happy to help.
Leave a comment