ApiLogicServer 15.2.0__py3-none-any.whl → 15.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. api_logic_server_cli/api_logic_server.py +3 -2
  2. api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +115 -31
  3. api_logic_server_cli/prototypes/base/docs/training/testing.md +116 -18
  4. api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py +55 -29
  5. api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py.bak +285 -0
  6. api_logic_server_cli/prototypes/{base/.github/.copilot-instructionsZ.mdx → basic_demo/.github/.copilot-instructions.md} +111 -30
  7. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +35 -0
  8. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +35 -0
  9. api_logic_server_cli/prototypes/basic_demo/readme.md +12 -4
  10. api_logic_server_cli/prototypes/basic_demo/tutor.md +1196 -0
  11. api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md +50 -23
  12. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +3 -0
  13. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/docs/training/testing.md +305 -21
  14. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py +13 -84
  15. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py.bak +282 -0
  16. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/features/order_processing.feature +59 -50
  17. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/features/steps/order_processing_steps.py +395 -248
  18. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/behave.log +66 -62
  19. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Carbon_Neutral_Discount_A.log +51 -41
  20. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Change_Order_Customer.log +29 -0
  21. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Change_Product_in_Item.log +35 -0
  22. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Delete_Item_Reduces_Order.log +39 -19
  23. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Exceed_Credit_Limit_Rejec.log +36 -45
  24. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Good_Order_Placed_via_B2B.log +50 -40
  25. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Item_Quantity_Change.log +33 -0
  26. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Multi-Item_Order_via_B2B_.log +67 -0
  27. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Ship_Order_Excludes_from_.log +24 -14
  28. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Transaction_Processing.log +26 -17
  29. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Unship_Order_Includes_in_.log +24 -14
  30. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/reports/Behave Logic Report.md +361 -146
  31. api_logic_server_cli/prototypes/manager/system/ApiLogicServer-Internal-Dev/copilot-dev-context.md +275 -4
  32. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py +13 -75
  33. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
  34. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/test/api_logic_server_behave/behave_logic_report.py +13 -75
  35. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
  36. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +17 -0
  37. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +17 -0
  38. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/METADATA +103 -23
  39. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/RECORD +43 -30
  40. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/WHEEL +0 -0
  41. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/entry_points.txt +0 -0
  42. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/licenses/LICENSE +0 -0
  43. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/top_level.txt +0 -0
@@ -12,10 +12,11 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
12
12
  Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
13
13
  '''
14
14
 
15
- __version__ = "15.02.00" # last public release: 15.01.03
15
+ __version__ = "15.02.07" # last public release: 15.02.03
16
16
  recent_changes = \
17
17
  f'\n\nRecent Changes:\n' +\
18
- "\t10/21/2025 - 15.02.00: Copilot test creation from rules and custom APIs \n"\
18
+ "\t10/26/2025 - 15.02.07: Clarify order created for ship test, security fixes [105], tutor 2.1 \n"\
19
+ "\t10/22/2025 - 15.02.03: Copilot test creation from rules and custom APIs with issues [103, 104] \n"\
19
20
  "\t10/06/2025 - 15.01.06: Incorrect dbml reln arrows [102] \n"\
20
21
  "\t09/27/2025 - 15.01.05: Error reporting for bad test data rebuild [101], Minor fix to ensure react-app cards have links \n"\
21
22
  "\t08/17/2025 - 15.01.03: LogicBan 01.30, with fix for copy_children[19] \n"\
@@ -1,11 +1,54 @@
1
+ ---
2
+ title: Copilot Instructions for GenAI-Logic Projects
3
+ Description: Project-level instructions for working with generated projects
4
+ Source: ApiLogicServer-src/prototypes/base/.github/.copilot-instructions.md
5
+ Propagation: CLI create command → created projects (non-basic_demo)
6
+ Usage: AI assistants read this when user opens any created project
7
+ version: 2.1
8
+ changelog:
9
+ - 2.1 (Oct 2025) - Streamlined OBX for non-tutorial projects, friendly collaborative tone
10
+ - 2.0 (Oct 2025) - Added front matter, updated for OBX improvements
11
+ - 1.0 (Initial) - Comprehensive technical reference
12
+ ---
1
13
 
14
+ # GitHub Copilot Instructions for GenAI-Logic (aka API Logic Server) Projects
2
15
 
16
+ ⚠️ **IMPORTANT FOR AI ASSISTANTS**: When user first opens this project or asks you to read instructions, present the Welcome section below directly to the user. Do not explain that you're following instructions - just present the welcome message naturally. The technical reference sections after the welcome are for later consultation only.
3
17
 
4
- # GitHub Copilot Instructions for GenAI-Logic (aka API Logic Server) Projects
18
+ ---
19
+
20
+ ## Welcome
21
+
22
+ **Welcome! I'm here to help with your GenAI-Logic project.**
23
+
24
+ This is a complete, working microservice auto-generated from your database schema.
25
+
26
+ **What's already built:**
27
+ - **Admin Web Application** - Live at `http://localhost:5656`
28
+ - **REST API Endpoints** - All database tables exposed at `/api/*`
29
+ - **Database Models** - Complete SQLAlchemy ORM in `database/models.py`
30
+ - **Authentication System** - JWT-based auth framework
31
+ - **Business Logic Engine** - Declarative rules system
32
+
33
+ ---
34
+
35
+ **Here are some things I can help you with:**
36
+
37
+ 1. **Add business logic** - Describe your requirements in natural language, I'll generate declarative rules
38
+ 2. **Customize the API** - Add custom endpoints for your specific needs
39
+ 3. **Create custom UIs** - Build React apps with `genai-logic genai-add-app --vibe`
40
+ 4. **Add security** - Set up role-based access control with `genai-logic add-auth`
41
+ 5. **Test your logic** - Create Behave tests with requirements traceability
42
+ 6. **Configure Admin UI** - Customize the auto-generated admin interface
43
+ 7. **Add MCP integration** - Enable Model Context Protocol for external AI access
44
+ 8. **Create B2B APIs** - Complex integration endpoints with partner systems
45
+ 9. **Add events** - Integrate with Kafka, webhooks, or other event systems
46
+ 10. **Customize models** - Add tables, attributes, or derived fields
47
+ 11. **Discovery systems** - Auto-load logic and APIs from discovery folders
5
48
 
6
- ## Project Type: Auto-Generated Microservice
49
+ Want to get started? Press **F5** to start the server with debugger support, then open `http://localhost:5656`. (There are also run configurations for running tests and other tasks.) Or just ask me what you'd like to work on.
7
50
 
8
- This is a **GenAI-Logic (aka API Logic Server) project** - a complete, working microservice - auto-generated from a database schema.
51
+ ---
9
52
 
10
53
  ## 🔑 Key Technical Points
11
54
 
@@ -26,32 +69,15 @@ This is a **GenAI-Logic (aka API Logic Server) project** - a complete, working m
26
69
 
27
70
  3. **Automatic Business Logic**: All APIs (standard and custom) automatically inherit LogicBank rules without additional code.
28
71
 
29
- ## ⚠️ IMPORTANT: What's Already Built
72
+ 4. **CLI Commands**: Use `genai-logic --help` to see all available commands. When CLI commands exist for a task (e.g., `add-auth`, `genai-add-mcp-client`, `genai-add-app`), ALWAYS use them instead of manual configuration - they handle all setup correctly.
30
73
 
31
- **DO NOT recreate these - they're already working:**
74
+ > **📋 Testing:** For comprehensive testing conventions, patterns, and examples, see `docs/training/testing.md` (555 lines - I'll read this before we create any tests)
32
75
 
33
- 1. **Admin Web Application** - Live at `http://localhost:5656`
34
- 2. **REST API Endpoints** - All database tables exposed at `/api/*`
35
- 3. **Database Models** - Complete SQLAlchemy ORM in `database/models.py`
36
- 4. **Authentication System** - JWT-based auth framework
37
- 5. **Business Logic Engine** - Declarative rules system
76
+ ---
38
77
 
39
- > **📋 Testing:** For comprehensive testing conventions, patterns, and examples, see `docs/training/testing.md` (555 lines - MUST READ FIRST before creating any tests)
78
+ ## Detailed Service Documentation
40
79
 
41
- ## 🎯 Common Tasks
42
-
43
- If the user asks "what do I do now?", these subsections are good suggestions.
44
-
45
- List these as choices, do not just do all of them at once - that would be overwhelming.
46
-
47
- ### Set Up Virtual Environment
48
- If it is not already set, you can often find a `venv` in the GenAI-Logic Manager - a parent or grandparent directory.
49
-
50
- ### 🚀 To Run This Project
51
-
52
- Use F5 (pre-configured run configuration - supports debugger for server logic), or (when focused on client apps):
53
-
54
- ```bash
80
+ The sections below provide complete details on each service. I'll reference these as needed when we work together.
55
81
  python api_logic_server_run.py
56
82
  # Then open: http://localhost:5656
57
83
  ```
@@ -395,20 +421,20 @@ Customize using CoPilot chat, with `docs/training`.
395
421
 
396
422
  Configure:
397
423
  ```
398
- als add-auth --provider-type=sql --db-url=
399
- als add-auth --provider-type=sql --db_url=postgresql://postgres:p@localhost/authdb
424
+ genai-logic add-auth --provider-type=sql --db-url=
425
+ genai-logic add-auth --provider-type=sql --db_url=postgresql://postgres:p@localhost/authdb
400
426
 
401
- als add-auth --provider-type=keycloak --db-url=localhost
402
- als add-auth --provider-type=keycloak --db-url=hardened
427
+ genai-logic add-auth --provider-type=keycloak --db-url=localhost
428
+ genai-logic add-auth --provider-type=keycloak --db-url=hardened
403
429
 
404
- als add-auth --provider-type=None # to disable
430
+ genai-logic add-auth --provider-type=None # to disable
405
431
  ```
406
432
 
407
433
  Keycloak quick start [(more information here:)](https://apilogicserver.github.io/Docs/Security-Keycloak/)
408
434
  ```bash
409
435
  cd devops/keycloak
410
436
  docker compose up
411
- als add-auth --provider-type=keycloak --db-url=localhost
437
+ genai-logic add-auth --provider-type=keycloak --db-url=localhost
412
438
  ```
413
439
 
414
440
  For more on KeyCloak: https://apilogicserver.github.io/Docs/Security-Keycloak/
@@ -419,6 +445,64 @@ Declaration:
419
445
  Grant(on_entity=Customer, to_role=sales, filter=lambda: Customer.SalesRep == current_user())
420
446
  ```
421
447
 
448
+
449
+ #### Testing with Security Enabled
450
+
451
+ **CRITICAL:** When `SECURITY_ENABLED=True`, test code must obtain and include JWT authentication tokens.
452
+
453
+ **Pattern for test steps:**
454
+ ```python
455
+ from pathlib import Path
456
+ import os
457
+ from dotenv import load_dotenv
458
+
459
+ # Load config to check SECURITY_ENABLED
460
+ config_path = Path(__file__).parent.parent.parent.parent.parent / 'config' / 'default.env'
461
+ load_dotenv(config_path)
462
+
463
+ # Cache for auth token (obtained once per test session)
464
+ _auth_token = None
465
+
466
+ def get_auth_token():
467
+ """Login and get JWT token if security is enabled"""
468
+ global _auth_token
469
+
470
+ if _auth_token is not None:
471
+ return _auth_token
472
+
473
+ # Login with default admin credentials
474
+ login_url = f'{BASE_URL}/api/auth/login'
475
+ login_data = {'username': 'admin', 'password': 'p'}
476
+
477
+ response = requests.post(login_url, json=login_data)
478
+ if response.status_code == 200:
479
+ _auth_token = response.json().get('access_token')
480
+ return _auth_token
481
+ else:
482
+ raise Exception(f"Login failed: {response.status_code}")
483
+
484
+ def get_headers():
485
+ """Get headers including auth token if security is enabled"""
486
+ security_enabled = os.getenv('SECURITY_ENABLED', 'false').lower() not in ['false', 'no']
487
+
488
+ headers = {'Content-Type': 'application/json'}
489
+
490
+ if security_enabled:
491
+ token = get_auth_token()
492
+ if token:
493
+ headers['Authorization'] = f'Bearer {token}'
494
+
495
+ return headers
496
+
497
+ # Use in all API requests
498
+ response = requests.post(url=api_url, json=data, headers=get_headers())
499
+ ```
500
+
501
+ **Key points:**
502
+ - Tests DO NOT automatically include auth headers - you must code this pattern
503
+ - Token is cached to avoid repeated logins during test session
504
+ - Pattern works for both `SECURITY_ENABLED=True` and `SECURITY_ENABLED=False`
505
+ - See `test/api_logic_server_behave/features/steps/order_processing_steps.py` for complete example
422
506
  ### Adding Custom API Endpoints
423
507
 
424
508
  For simple endpoints:
@@ -25,10 +25,16 @@ Step 5. SUGGEST how to run tests (DO NOT run automatically)
25
25
 
26
26
  **CRITICAL PRE-TEST CHECKLIST:**
27
27
  - [ ] Step 1c completed? (Custom APIs discovered)
28
+ - [ ] **Database values verified?** (Rule #10: Run SQL to check actual prices/flags)
29
+ - [ ] `sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"`
30
+ - [ ] Don't assume product attributes - verify BEFORE writing expectations!
28
31
  - [ ] **Step ordering verified?** (Most specific → Most general)
29
32
  - [ ] @when patterns: carbon neutral > multi-item > single-item
30
33
  - [ ] @given patterns: multi-item > single-item
31
34
  - [ ] Use `grep -n "@when('.*with" steps/*.py` to verify order
35
+ - [ ] **Clear scenario language?** (Rule #13: Action-oriented wording)
36
+ - [ ] Use "Order is created" not "Order exists" (shows when rules fire)
37
+ - [ ] Makes test flow obvious: setup → action → verify
32
38
  - [ ] Test data uses timestamps? (Rule #0: Repeatability)
33
39
  - [ ] Security config read? (SECURITY_ENABLED value)
34
40
 
@@ -164,7 +170,7 @@ def step_impl(context, qty):
164
170
  r = requests.patch(url=patch_uri, json=patch_data)
165
171
  ```
166
172
 
167
- ## The 6 Critical Rules (Read FIRST!)
173
+ ## The Critical Rules (Read FIRST!)
168
174
 
169
175
  ### Rule #0: TEST REPEATABILITY (MOST CRITICAL!) ⚠️
170
176
 
@@ -383,18 +389,65 @@ Scenario: Exceed Credit (Constraint FAIL - negative test)
383
389
 
384
390
  **CRITICAL:** Check `logic/declare_logic.py` for custom Python logic affecting calculations (e.g., discounts, adjustments) before writing constraint test expectations!
385
391
 
386
- ### Rule #5: Security Configuration
392
+ ### Rule #5: Security Configuration - JWT Authentication
393
+
394
+ **CRITICAL:** When `SECURITY_ENABLED=True`, tests MUST obtain JWT token and include Authorization header.
395
+
387
396
  ```python
388
- # Read config/default.env FIRST
389
- SECURITY_ENABLED = False # or True
397
+ from pathlib import Path
398
+ import os
399
+ from dotenv import load_dotenv
390
400
 
391
- # Match in test code
392
- if SECURITY_ENABLED == False:
393
- headers = {} # Empty
394
- else:
395
- headers = test_utils.login()
401
+ # Load config to check SECURITY_ENABLED
402
+ config_path = Path(__file__).parent.parent.parent.parent.parent / 'config' / 'default.env'
403
+ load_dotenv(config_path)
404
+
405
+ # Cache for auth token (obtained once per test session)
406
+ _auth_token = None
407
+
408
+ def get_auth_token():
409
+ """Login and get JWT token if security is enabled"""
410
+ global _auth_token
411
+
412
+ if _auth_token is not None:
413
+ return _auth_token
414
+
415
+ # Login with default admin credentials
416
+ login_url = f'{BASE_URL}/api/auth/login'
417
+ login_data = {'username': 'admin', 'password': 'p'}
418
+
419
+ response = requests.post(login_url, json=login_data)
420
+ if response.status_code == 200:
421
+ _auth_token = response.json().get('access_token')
422
+ return _auth_token
423
+ else:
424
+ raise Exception(f"Login failed: {response.status_code}")
425
+
426
+ def get_headers():
427
+ """Get headers including auth token if security is enabled"""
428
+ security_enabled = os.getenv('SECURITY_ENABLED', 'false').lower() not in ['false', 'no']
429
+
430
+ headers = {'Content-Type': 'application/json'}
431
+
432
+ if security_enabled:
433
+ token = get_auth_token()
434
+ if token:
435
+ headers['Authorization'] = f'Bearer {token}'
436
+
437
+ return headers
438
+
439
+ # Use in ALL API requests
440
+ response = requests.post(url=api_url, json=data, headers=get_headers())
441
+ response = requests.get(url=api_url, headers=get_headers())
442
+ response = requests.patch(url=api_url, json=data, headers=get_headers())
396
443
  ```
397
444
 
445
+ **Key Points:**
446
+ - Tests DO NOT automatically include auth - you must code the pattern above
447
+ - Token is cached globally to avoid repeated login calls
448
+ - Works for both `SECURITY_ENABLED=True` and `SECURITY_ENABLED=False`
449
+ - See complete example: `test/api_logic_server_behave/features/steps/order_processing_steps.py`
450
+
398
451
  ### Rule #6: Logic Logging
399
452
  ```python
400
453
  @when('Good Order Placed')
@@ -470,19 +523,28 @@ Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total,
470
523
  ```python
471
524
  # ALWAYS check actual product prices/flags before writing test expectations!
472
525
 
473
- # Check prices:
474
- # sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM Product;"
526
+ # Check prices AND flags:
527
+ # sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"
475
528
 
476
- # Widget=90, Gadget=150, Green=109
529
+ # Results:
530
+ # 1|Gadget|150|1 ← carbon_neutral = 1 (TRUE)
531
+ # 2|Widget|90| ← carbon_neutral = NULL (not carbon neutral!)
532
+ # 3|Thingamajig|5075|
533
+ # 4|Doodad|110|
534
+ # 5|Green|109|1 ← carbon_neutral = 1 (TRUE)
477
535
 
478
- # ❌ WRONG - Assumed Widget=$100
479
- # Expected: 10 * 100 = 1000
536
+ # ❌ WRONG - Assumed Widget is carbon neutral
537
+ # Scenario: Carbon Neutral Discount
538
+ # When B2B order placed with 10 carbon neutral Widget
539
+ # Then balance should be 810 # Expected 10 * 90 * 0.9 = 810
540
+ # FAILS: Widget is NOT carbon neutral → no discount → balance = 900
480
541
 
481
- # ✅ CORRECT - Verified Widget=$90
482
- # Expected: 10 * 90 = 900
542
+ # ✅ CORRECT - Verified Gadget IS carbon neutral (flag = 1)
543
+ # Scenario: Carbon Neutral Discount
544
+ # When B2B order placed with 10 carbon neutral Gadget
545
+ # Then balance should be 1350 # Correct: 10 * 150 * 0.9 = 1350
483
546
 
484
- # For carbon neutral discount (10% off when qty >= 10):
485
- # Expected: 10 * 90 * 0.9 = 810
547
+ # CRITICAL: Don't assume product attributes - VERIFY with SQL first!
486
548
  ```
487
549
 
488
550
  ### Rule #11: Step Definitions Must Match Feature Files ⚠️ NEW
@@ -523,6 +585,40 @@ def step_impl(context):
523
585
  return # Skip gracefully
524
586
  ```
525
587
 
588
+ ### Rule #13: Use Clear, Action-Oriented Scenario Language ⚠️ NEW
589
+ ```gherkin
590
+ # ❌ AMBIGUOUS - "exists" is passive, doesn't show when balance changes
591
+ Scenario: Ship Order Excludes from Balance
592
+ Given Customer "Charlie" with balance 0 and credit limit 2000
593
+ And Order exists for "Charlie" with 2 Widget
594
+ When Order is shipped
595
+ Then Customer balance should be 0
596
+
597
+ # Question: How can Charlie have balance 0 AND an order for widgets?
598
+ # Answer: The order EXISTS at test start, but balance calculation is unclear
599
+
600
+ # ✅ CLEAR - "is created" shows action that triggers rule
601
+ Scenario: Ship Order Excludes from Balance
602
+ Given Customer "Charlie" with balance 0 and credit limit 2000
603
+ And Order is created for "Charlie" with 2 Widget # Action! Balance becomes 180
604
+ When Order is shipped # Action! Balance drops to 0
605
+ Then Customer balance should be 0 # Verification
606
+
607
+ # Now it's obvious:
608
+ # 1. Customer starts with balance 0
609
+ # 2. Creating unshipped order → rule fires → balance becomes 180
610
+ # 3. Shipping order → WHERE clause excludes it → balance drops to 0
611
+
612
+ # More examples:
613
+ # ❌ "And Shipped order exists for..."
614
+ # ✅ "And Shipped order is created for..."
615
+
616
+ # Why this matters:
617
+ # - Shows WHEN rules fire (on creation, not just existence)
618
+ # - Makes test flow obvious (setup → action → verify)
619
+ # - Clarifies that declarative rules execute automatically on changes
620
+ ```
621
+
526
622
  ## Complete Phase 2 Example
527
623
 
528
624
  ### .feature File
@@ -619,6 +715,8 @@ def step_impl(context, expected):
619
715
  | **"balance: expected 570, got 0.0"** | **Step ordering issue (Rule #0.5)! Multi-item pattern after single-item** |
620
716
  | **"context has no attribute 'item_id'"** | **Step ordering issue! Specific pattern defined after general pattern** |
621
717
  | "Order created but no items" | Check if wrong step matched (print debug in step) |
718
+ | **"expected 810, got 900"** (carbon neutral) | **Rule #10: Verify actual database values! Check product flags with SQL first** |
719
+ | **"How can balance be 0 with an order?"** | **Rule #13: Use "is created" not "exists" - shows when rules fire** |
622
720
 
623
721
  ### Debugging Step Ordering Issues
624
722
 
@@ -95,14 +95,43 @@ def show_logic(scenario: str, logic_logs_dir: str):
95
95
  last_rules_start = -1
96
96
  last_rules_end = -1
97
97
 
98
- # First, find the LAST "These Rules Fired" section
99
- for i, each_logic_line in enumerate(logic_lines):
100
- if "These Rules Fired" in each_logic_line:
101
- last_rules_start = i + 1 # Start collecting from next line
102
- last_rules_end = -1 # Reset end marker to find the next COMPLETE
103
- elif last_rules_start > 0 and last_rules_end == -1:
104
- if 'Logic Phase:' in each_logic_line and 'COMPLETE' in each_logic_line:
105
- last_rules_end = i
98
+ # Find ALL "These Rules Fired" sections and choose the one with the most rules
99
+ rules_sections = []
100
+ i = 0
101
+ while i < len(logic_lines):
102
+ if "These Rules Fired" in logic_lines[i]:
103
+ start_pos = i + 1
104
+ # Find the end of this rules section
105
+ end_pos = start_pos
106
+ while end_pos < len(logic_lines):
107
+ if 'Logic Phase:' in logic_lines[end_pos] and 'COMPLETE' in logic_lines[end_pos]:
108
+ break
109
+ end_pos += 1
110
+
111
+ # Count non-empty rule lines in this section (after removing trailers)
112
+ rule_count = 0
113
+ for j in range(start_pos, end_pos):
114
+ line = remove_trailer(logic_lines[j]).strip()
115
+ if line and not line.startswith('Logic Phase:'):
116
+ rule_count += 1
117
+
118
+ rules_sections.append({
119
+ 'start': start_pos,
120
+ 'end': end_pos,
121
+ 'rule_count': rule_count
122
+ })
123
+ i = end_pos
124
+ else:
125
+ i += 1
126
+
127
+ # Choose the section with the most rules
128
+ if rules_sections:
129
+ best_section = max(rules_sections, key=lambda x: x['rule_count'])
130
+ last_rules_start = best_section['start']
131
+ last_rules_end = best_section['end']
132
+ else:
133
+ last_rules_start = -1
134
+ last_rules_end = -1
106
135
 
107
136
  # Now process the file, collecting logic log and extracting the last rules section
108
137
  for i, each_logic_line in enumerate(logic_lines):
@@ -182,36 +211,27 @@ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
182
211
 
183
212
  just_saw_then = False
184
213
  current_scenario = ""
214
+ previous_scenario = ""
185
215
  for each_line in contents:
186
- # Show logic when we hit a blank line after assertions OR when starting a new scenario
187
- if just_saw_then and (each_line == "\n" or each_line.startswith(" Scenario")):
216
+ if just_saw_then and each_line == "\n":
188
217
  show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
189
218
  just_saw_then = False
190
-
219
+ previous_scenario = ""
191
220
  if each_line.startswith("Feature"):
192
221
  wiki_data.append("&nbsp;")
193
222
  wiki_data.append("&nbsp;")
194
223
  each_line = "## " + each_line
195
-
196
224
  if each_line.startswith(" Scenario"):
197
- # Extract scenario name for logic lookup
198
- current_scenario = each_line.strip().replace("Scenario: ", "").replace(" ", " ").strip()
199
- wiki_data.append("&nbsp;")
200
- wiki_data.append("&nbsp;")
201
- wiki_data.append("### " + each_line[2:]) # Add scenario header
225
+ # Before starting new scenario, show logic for previous one if we saw Then
226
+ if just_saw_then and previous_scenario:
227
+ show_logic(scenario=previous_scenario, logic_logs_dir=scenario_logs)
228
+ just_saw_then = False
202
229
  each_line = tab + each_line
203
-
204
230
  if each_line.startswith(" Given") or \
205
231
  each_line.startswith(" When") or \
206
- each_line.startswith(" Then") or \
207
- each_line.startswith(" And"):
208
- if each_line.startswith(" Then") or each_line.startswith(" And"):
232
+ each_line.startswith(" Then"):
233
+ if each_line.startswith(" Then"):
209
234
  just_saw_then = True
210
- # Add subtle formatting to keywords
211
- for keyword in ["Given", "When", "Then", "And"]:
212
- if f" {keyword} " in each_line:
213
- each_line = each_line.replace(f" {keyword} ", f" **{keyword}** ")
214
- break
215
235
  each_line = tab + tab + each_line
216
236
 
217
237
  each_line = each_line[:-1]
@@ -219,13 +239,19 @@ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
219
239
  if debug_loc > 0:
220
240
  each_line = each_line[0 : debug_loc]
221
241
  each_line = each_line.rstrip()
242
+ if "Scenario" in each_line:
243
+ current_scenario = each_line[18:]
244
+ previous_scenario = current_scenario
245
+ wiki_data.append("&nbsp;")
246
+ wiki_data.append("&nbsp;")
247
+ wiki_data.append("### " + each_line[8:])
222
248
 
223
249
  each_line = each_line + " " # wiki for "new line"
224
250
 
225
251
  wiki_data.append(each_line)
226
-
227
- # Show logic for the last scenario
228
- if current_scenario and just_saw_then:
252
+
253
+ # Show logic for the last scenario if we saw Then
254
+ if just_saw_then and current_scenario:
229
255
  show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
230
256
 
231
257
  with open(wiki, 'w') as rpt: