{"id":311,"date":"2026-09-07T12:55:41","date_gmt":"2026-09-04T12:55:41","guid":{"rendered":"https:\/\/softcrony.com\/blog\/?p=311"},"modified":"2026-09-04T12:55:41","modified_gmt":"2026-09-04T12:55:41","slug":"business-logic-audit-ai-generated-code","status":"publish","type":"post","link":"https:\/\/softcrony.com\/blog\/business-logic-audit-ai-generated-code\/","title":{"rendered":"Business Logic Audit: How to Catch What AI Gets Wrong in Your App"},"content":{"rendered":"<p>This is Part 2 of our <a href=\"https:\/\/softcrony.com\/blog\/ai-agentic-editor-code-audit-guide-developers\/\">AI Code Audit Series<\/a> \u2014 covering the essential audits every team should run when building with AI agentic editors.<\/p>\n<p>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 \u2014 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.<\/p>\n<p>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 \u2014 often silently, often without anyone realising something is wrong until significant damage has already occurred.<\/p>\n<p>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.<\/p>\n<h2>Why AI Gets Business Logic Wrong<\/h2>\n<p>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 \u2014 because the developer writing the prompt already understands the business context and unconsciously omits things they consider obvious.<\/p>\n<p>When you ask an AI to implement a discount system, you describe the main cases. The AI implements exactly what you described \u2014 cleanly, correctly, and completely missing the edge cases you didn&#8217;t mention because you assumed they were implied. The combination discount that shouldn&#8217;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.<\/p>\n<p>These aren&#8217;t AI failures in the sense of the AI doing something wrong. They&#8217;re gaps between what was asked and what was needed \u2014 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.<\/p>\n<p>A second reason is that AI generates code for the happy path. It implements the primary flow \u2014 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&#8217;re underrepresented in prompts.<\/p>\n<p>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&#8217;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 \u2014 not because the AI made an error, but because it made two different locally reasonable decisions without knowing they needed to be consistent.<\/p>\n<h2>What Business Logic Errors Actually Look Like<\/h2>\n<p>Before covering how to find them, it&#8217;s worth being concrete about what these errors look like in real Laravel applications.<\/p>\n<p><strong>Incorrect calculation logic<\/strong> is the most immediately dangerous category. AI generates calculation code that works for the cases in the prompt and fails for cases that weren&#8217;t mentioned.<\/p>\n<pre><code>\/\/ What AI generated \u2014 works for simple cases\r\npublic function calculateOrderTotal(Order $order): float\r\n{\r\n    $subtotal = $order->items->sum(fn($item) => $item->price * $item->quantity);\r\n    \r\n    if ($order->discount_code) {\r\n        $discount = $subtotal * ($order->discount_code->percentage \/ 100);\r\n        $subtotal -= $discount;\r\n    }\r\n    \r\n    return $subtotal + $order->shipping_cost;\r\n}\r\n\r\n\/\/ What the business actually requires:\r\n\/\/ 1. Discount applies only to eligible items (not sale items)\r\n\/\/ 2. Minimum order value check BEFORE discount, not after\r\n\/\/ 3. Shipping is free above \u20b9999 AFTER discount\r\n\/\/ 4. Loyalty points discount cannot stack with promotional codes\r\n\/\/ 5. Tax calculated on post-discount subtotal, not pre-discount\r\n\r\npublic function calculateOrderTotal(Order $order): float\r\n{\r\n    \/\/ Only eligible items qualify for discount\r\n    $eligibleSubtotal = $order->items\r\n        ->where('is_sale_item', false)\r\n        ->sum(fn($item) => $item->price * $item->quantity);\r\n    \r\n    $fullSubtotal = $order->items\r\n        ->sum(fn($item) => $item->price * $item->quantity);\r\n\r\n    \/\/ Minimum order check on full subtotal before discount\r\n    if ($fullSubtotal < $this->minimumOrderValue) {\r\n        throw new OrderBelowMinimumException($this->minimumOrderValue);\r\n    }\r\n\r\n    \/\/ Discounts cannot stack \u2014 loyalty points OR promo code, not both\r\n    $discount = 0;\r\n    if ($order->discount_code && !$order->loyalty_points_applied) {\r\n        $discount = $eligibleSubtotal * ($order->discount_code->percentage \/ 100);\r\n    } elseif ($order->loyalty_points_applied && !$order->discount_code) {\r\n        $discount = $order->loyalty_discount_amount;\r\n    }\r\n\r\n    $discountedSubtotal = $fullSubtotal - $discount;\r\n\r\n    \/\/ Tax on post-discount amount\r\n    $tax = $discountedSubtotal * $this->taxRate;\r\n\r\n    \/\/ Free shipping threshold applies after discount\r\n    $shipping = $discountedSubtotal >= 999 ? 0 : $order->shipping_cost;\r\n\r\n    return $discountedSubtotal + $tax + $shipping;\r\n}<\/code><\/pre>\n<p>The AI&#8217;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.<\/p>\n<p><strong>Missing state transition validation<\/strong> is where AI generates code that allows entities to move between states that the business rules don&#8217;t permit.<\/p>\n<pre><code>\/\/ What AI generated \u2014 allows any status change\r\npublic function updateOrderStatus(Order $order, string $newStatus): void\r\n{\r\n    $order->update(['status' => $newStatus]);\r\n    event(new OrderStatusUpdated($order));\r\n}\r\n\r\n\/\/ What the business actually requires \u2014 valid transitions only\r\npublic function updateOrderStatus(Order $order, string $newStatus): void\r\n{\r\n    $validTransitions = [\r\n        'pending'    => ['confirmed', 'cancelled'],\r\n        'confirmed'  => ['processing', 'cancelled'],\r\n        'processing' => ['shipped'],\r\n        'shipped'    => ['delivered', 'returned'],\r\n        'delivered'  => ['returned'],\r\n        'cancelled'  => [], \/\/ terminal state\r\n        'returned'   => [], \/\/ terminal state\r\n    ];\r\n\r\n    $currentStatus = $order->status;\r\n\r\n    if (!in_array($newStatus, $validTransitions[$currentStatus] ?? [])) {\r\n        throw new InvalidStatusTransitionException(\r\n            \"Cannot transition order from {$currentStatus} to {$newStatus}\"\r\n        );\r\n    }\r\n\r\n    $order->update(['status' => $newStatus]);\r\n    event(new OrderStatusUpdated($order));\r\n}<\/code><\/pre>\n<p>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 \u2014 all of which produce incorrect downstream effects in inventory, accounting, and customer communications.<\/p>\n<p><strong>Incorrect permission boundaries<\/strong> occur when AI implements role-based logic that doesn&#8217;t match the actual permission model.<\/p>\n<pre><code>\/\/ What AI generated \u2014 manager can do everything admin can\r\npublic function canApproveExpense(User $user, Expense $expense): bool\r\n{\r\n    return in_array($user->role, ['admin', 'manager']);\r\n}\r\n\r\n\/\/ What the business actually requires:\r\n\/\/ - Managers can approve expenses up to \u20b950,000\r\n\/\/ - Managers cannot approve their own expenses\r\n\/\/ - Regional managers can approve up to \u20b92,00,000 within their region\r\n\/\/ - Only admins can approve above \u20b92,00,000\r\n\/\/ - Finance team can approve any amount but only for their department\r\n\r\npublic function canApproveExpense(User $user, Expense $expense): bool\r\n{\r\n    \/\/ Nobody approves their own expenses\r\n    if ($user->id === $expense->submitted_by) {\r\n        return false;\r\n    }\r\n\r\n    return match($user->role) {\r\n        'admin' => true,\r\n        'regional_manager' => \r\n            $expense->amount <= 200000 &#038;&#038; \r\n            $expense->region_id === $user->region_id,\r\n        'manager' => $expense->amount <= 50000,\r\n        'finance' => $expense->department_id === $user->department_id,\r\n        default => false,\r\n    };\r\n}<\/code><\/pre>\n<p><strong>Inconsistent business rule implementation<\/strong> is where the same concept is handled differently in different parts of the codebase because the AI made independent decisions in separate sessions.<\/p>\n<pre><code>\/\/ In OrderController.php \u2014 generated in week 1\r\n$tax = $amount * 0.18; \/\/ GST hardcoded\r\n\r\n\/\/ In InvoiceController.php \u2014 generated in week 3  \r\n$tax = $amount * config('tax.gst_rate'); \/\/ Uses config\r\n\r\n\/\/ In ReportController.php \u2014 generated in week 5\r\n$taxRate = TaxRate::where('type', 'GST')->first()->rate;\r\n$tax = $amount * ($taxRate \/ 100); \/\/ Queries database\r\n\r\n\/\/ What should exist \u2014 one canonical implementation\r\n\/\/ In app\/Services\/TaxCalculationService.php\r\npublic function calculateGST(float $amount, string $category = 'standard'): float\r\n{\r\n    $rate = $this->getTaxRate('GST', $category);\r\n    return round($amount * ($rate \/ 100), 2);\r\n}<\/code><\/pre>\n<p>Three different implementations of the same business rule mean that changing the GST rate requires finding and updating three places \u2014 and the risk that one gets missed.<\/p>\n<h2>How to Run a Business Logic Audit<\/h2>\n<p>A business logic audit is not a code review. It&#8217;s a requirements traceability exercise \u2014 verifying that every business rule is implemented correctly, completely, and consistently across the codebase. Here&#8217;s how to run one systematically.<\/p>\n<p><strong>Step 1 \u2014 Compile your business rules documentation<\/strong><\/p>\n<p>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&#8217;t been written down. This last category is the most important and the hardest to surface \u2014 schedule a short session with the business owner or product manager specifically to identify rules that &#8220;everyone knows&#8221; but nobody has written down.<\/p>\n<p>Create a simple audit document with every business rule listed as a testable statement: &#8220;A discount code cannot be combined with loyalty points redemption in the same order.&#8221; &#8220;Managers cannot approve expenses submitted by themselves.&#8221; &#8220;Free shipping applies when the post-discount order value exceeds \u20b9999.&#8221; These become your audit checklist.<\/p>\n<p><strong>Step 2 \u2014 Map rules to code<\/strong><\/p>\n<p>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 \u2014 and this is a significant finding \u2014 will have no corresponding code at all, meaning the rule was never implemented.<\/p>\n<p>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.<\/p>\n<p><strong>Step 3 \u2014 Test boundary conditions for each rule<\/strong><\/p>\n<p>For every business rule, identify the boundary conditions \u2014 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.<\/p>\n<p>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?<\/p>\n<p>Write test cases for each boundary condition. Run them against the current implementation. The failures are your findings.<\/p>\n<pre><code>\/\/ Example boundary condition tests for discount logic\r\nclass DiscountCalculationAuditTest extends TestCase\r\n{\r\n    \/** @test *\/\r\n    public function discount_does_not_apply_to_sale_items()\r\n    {\r\n        $order = Order::factory()->create();\r\n        $order->items()->create(['price' => 500, 'quantity' => 1, 'is_sale_item' => true]);\r\n        $order->update(['discount_code_id' => DiscountCode::factory()->create(['percentage' => 10])->id]);\r\n        \r\n        $total = $this->calculator->calculateOrderTotal($order);\r\n        \r\n        \/\/ Discount should NOT have been applied to the sale item\r\n        $this->assertEquals(500, $total->subtotal_after_discount);\r\n    }\r\n\r\n    \/** @test *\/\r\n    public function loyalty_points_and_promo_code_cannot_stack()\r\n    {\r\n        $order = Order::factory()->create([\r\n            'discount_code_id' => DiscountCode::factory()->create()->id,\r\n            'loyalty_points_applied' => true,\r\n            'loyalty_discount_amount' => 100,\r\n        ]);\r\n        \r\n        \/\/ Should throw exception or apply only one discount\r\n        $this->expectException(DiscountStackingException::class);\r\n        $this->calculator->calculateOrderTotal($order);\r\n    }\r\n\r\n    \/** @test *\/\r\n    public function minimum_order_check_uses_pre_discount_subtotal()\r\n    {\r\n        \/\/ Order is \u20b9600 before discount, \u20b9480 after 20% discount\r\n        \/\/ Minimum order is \u20b9500\r\n        \/\/ Should PASS because pre-discount amount meets minimum\r\n        $order = Order::factory()->withSubtotal(600)->withDiscount(20)->create();\r\n        \r\n        $this->assertDoesNotThrow(\r\n            fn() => $this->calculator->calculateOrderTotal($order)\r\n        );\r\n    }\r\n}<\/code><\/pre>\n<p><strong>Step 4 \u2014 Check for consistency across the codebase<\/strong><\/p>\n<p>Search the codebase for every place a key business concept appears \u2014 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 \u2014 inconsistency means future changes will be error-prone.<\/p>\n<p>Grep is useful here:<\/p>\n<pre><code># Find all places tax\/GST is calculated\r\ngrep -r \"0\\.18\\|gst_rate\\|tax_rate\\|GST\" app\/ --include=\"*.php\" -l\r\n\r\n# Find all places order status is updated\r\ngrep -r \"status.*=.*'\" app\/ --include=\"*.php\" -n\r\n\r\n# Find all discount calculations\r\ngrep -r \"discount\\|percentage.*100\\|promo\" app\/ --include=\"*.php\" -l<\/code><\/pre>\n<p>Every file in the results is a place where a business rule is implemented. Each should be reviewed against your business rules checklist.<\/p>\n<p><strong>Step 5 \u2014 Use AI to cross-check its own logic<\/strong><\/p>\n<p>Once you&#8217;ve identified the key business rules, use the AI to review the implementation against them explicitly:<\/p>\n<pre><code>Here are our business rules for the discount system:\r\n1. Discounts apply only to non-sale items\r\n2. Minimum order value (\u20b9500) is checked on pre-discount subtotal\r\n3. Loyalty points and promotional codes cannot be combined\r\n4. Free shipping threshold (\u20b9999) applies to post-discount subtotal\r\n5. GST (18%) is calculated on post-discount, pre-shipping amount\r\n\r\nHere is the current implementation:\r\n[paste calculateOrderTotal method]\r\n\r\nFor each business rule, confirm whether the implementation correctly handles it.\r\nFor any rule that is not correctly implemented, show the specific line where \r\nthe error occurs and provide the corrected implementation.<\/code><\/pre>\n<p>This doesn&#8217;t replace the manual audit \u2014 the AI will miss things, particularly around rules that require understanding context it doesn&#8217;t have. But it catches a significant proportion of straightforward mismatches quickly.<\/p>\n<h2>Business Logic Audit Checklist<\/h2>\n<p>Use this checklist for every significant AI-generated feature:<\/p>\n<p><strong>Calculations and financial logic<\/strong><\/p>\n<ul>\n<li>Are all calculation inputs validated before use?<\/li>\n<li>Are boundary values (zero, negative, maximum) handled correctly?<\/li>\n<li>Is rounding applied consistently and at the correct point in the calculation?<\/li>\n<li>Do combinations of inputs (discounts + taxes + shipping) produce correct totals?<\/li>\n<li>Are currency amounts stored and compared as integers (paise\/cents) not floats?<\/li>\n<\/ul>\n<p><strong>State machines and workflows<\/strong><\/p>\n<ul>\n<li>Are all valid state transitions explicitly defined?<\/li>\n<li>Are invalid transitions rejected with appropriate errors?<\/li>\n<li>Are terminal states protected from further transitions?<\/li>\n<li>Do state changes trigger the correct downstream effects?<\/li>\n<li>Is concurrent state change handled safely?<\/li>\n<\/ul>\n<p><strong>Permissions and access control<\/strong><\/p>\n<ul>\n<li>Do permission rules match the actual role hierarchy?<\/li>\n<li>Are self-approval and self-access restrictions in place where required?<\/li>\n<li>Are amount\/scope limits enforced for each role?<\/li>\n<li>Are permission checks applied consistently across all entry points?<\/li>\n<\/ul>\n<p><strong>Business rule consistency<\/strong><\/p>\n<ul>\n<li>Is each business concept implemented in one canonical location?<\/li>\n<li>Are there multiple implementations of the same rule that could diverge?<\/li>\n<li>Do all implementations of a shared concept use the same service or utility?<\/li>\n<li>Are configuration values (rates, thresholds, limits) stored in config files, not hardcoded?<\/li>\n<\/ul>\n<p><strong>Edge cases and error conditions<\/strong><\/p>\n<ul>\n<li>What happens when required related records don&#8217;t exist?<\/li>\n<li>What happens at exactly the boundary value of a threshold rule?<\/li>\n<li>What happens when two rules conflict for the same input?<\/li>\n<li>Are error conditions handled explicitly or silently ignored?<\/li>\n<\/ul>\n<h2>What to Do With Audit Findings<\/h2>\n<p>A business logic audit produces a list of findings \u2014 rules that aren&#8217;t implemented, rules that are implemented incorrectly, and rules that are implemented inconsistently. Prioritise them by impact before fixing.<\/p>\n<p>Financial calculation errors and permission boundary violations are critical \u2014 fix these immediately before any further development. State transition errors are high priority \u2014 fix before the next release. Consistency issues are medium priority \u2014 create shared services and refactor over the next sprint cycle. Documentation gaps \u2014 rules that exist in people&#8217;s heads but aren&#8217;t written down \u2014 are ongoing \u2014 address in your next requirements session with the business stakeholder.<\/p>\n<p>Every finding also informs how you write prompts going forward. If the AI consistently misses the rule that discounts don&#8217;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.<\/p>\n<p>Next in the series: <a href=\"https:\/\/softcrony.com\/blog\/security-audit-ai-generated-code-checklist\/\">Security Audit for AI-Generated Code<\/a> \u2014 the systematic checklist for catching the vulnerabilities that agentic editors introduce most frequently.<\/p>\n<p>If you want a business logic audit run on your existing codebase \u2014 to identify where AI-generated code has diverged from your actual business requirements \u2014 <a href=\"https:\/\/softcrony.com\/contact\/\">our team at Softcrony is happy to help<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is Part 2 of our AI Code Audit Series \u2014 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 \u2014 security audits [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":312,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[223,208,226,222,227,53,19,228],"class_list":["post-311","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-agentic-editor","tag-ai-coding","tag-business-logic","tag-code-audit","tag-code-review","tag-devops","tag-laravel","tag-software-quality"],"_links":{"self":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/comments?post=311"}],"version-history":[{"count":1,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/311\/revisions"}],"predecessor-version":[{"id":313,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/311\/revisions\/313"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media\/312"}],"wp:attachment":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media?parent=311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/categories?post=311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/tags?post=311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}