Project map — eshop-concepts-0.11.35
Generated by Silex · 2026-07-15 03:19 UTC
Summary
| Item |
Total |
| Entities |
26 |
| State machines |
4 |
| Enums |
7 |
| Business rules |
182 |
| Workflows |
0 |
| Extractors OK |
24/25 |
| Extractors with warnings |
1 |
Entities (26)
| Entity |
Fields |
Relations |
PK |
| Address |
10 |
0 |
Id |
| ApplicationUser |
12 |
0 |
- |
| BasketCheckoutInfo |
12 |
0 |
- |
| BasketItem |
7 |
0 |
Id |
| Campaign |
6 |
0 |
Id |
| CampaignItem |
7 |
0 |
Id |
| CatalogContextSeed |
6 |
0 |
Id |
| CatalogItem |
12 |
3 |
Id |
| CatalogSourceEntry |
6 |
0 |
Id |
| ComplexObject |
2 |
2 |
- |
| CreateOrderCommand |
12 |
0 |
- |
| GrantViewModel |
9 |
0 |
- |
| IntegrationEventLogEntry |
6 |
2 |
- |
| LoggedOutViewModel |
6 |
0 |
- |
| Order |
18 |
2 |
- |
| OrderCheckout |
12 |
1 |
- |
| OrderItem |
8 |
2 |
- |
| OrderItemDTO |
6 |
0 |
- |
| PaymentInfo |
7 |
1 |
Id |
| ScopeViewModel |
6 |
0 |
- |
| UserInfo |
3 |
0 |
- |
| ValueObjectA |
4 |
1 |
- |
| ValueObjectB |
3 |
0 |
- |
| ValueObjectTest |
4 |
0 |
- |
| VerifyCodeViewModel |
5 |
0 |
- |
| WebhookSubscription |
5 |
1 |
Id |
State machines (4)
EventStateEnumStatus
States: NotPublished (initial), 0, InProgress, 1, Published, 2, PublishedFailed, 3
Transitions: NotPublished → [NotPublished_to_0] 0 | 0 → [0_to_InProgress] InProgress | InProgress → [InProgress_to_1] 1 | 1 → [1_to_Published] Published | Published → [Published_to_2] 2 | 2 → [2_to_PublishedFailed] PublishedFailed | PublishedFailed → [PublishedFailed_to_3] 3
IntegrationEventLogEntryStatus
States: get (initial), set
Transitions: get → [get_to_set] set
OrderStatus
States: Submitted (initial), 1, AwaitingValidation, 2, StockConfirmed, 3, Paid, 4, Shipped, 5, Cancelled, 6
Transitions: Submitted → [Submitted_to_1] 1 | 1 → [1_to_AwaitingValidation] AwaitingValidation | AwaitingValidation → [AwaitingValidation_to_2] 2 | 2 → [2_to_StockConfirmed] StockConfirmed | StockConfirmed → [StockConfirmed_to_3] 3 | 3 → [3_to_Paid] Paid | Paid → [Paid_to_4] 4 | 4 → [4_to_Shipped] Shipped | Shipped → [Shipped_to_5] 5 | 5 → [5_to_Cancelled] Cancelled | Cancelled → [Cancelled_to_6] 6
PermissionStatus
States: Denied (initial), Disabled, Granted, Restricted, Unknown
Transitions: Denied → [Denied_to_Disabled] Disabled | Disabled → [Disabled_to_Granted] Granted | Granted → [Granted_to_Restricted] Restricted | Restricted → [Restricted_to_Unknown] Unknown
Business rules (182)
🔧 Heuristic: 85 | 🕒 Agent proposal (pending review): 97
34 unnamed heuristic guards not shown in this view (empty statement — collapsed here, count read from the report metadata).
ApplicationUser (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on ApplicationUser |
ApplicationUser |
validation |
on_save |
⚠️ 60% |
view |
BasketCheckoutInfo (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on BasketCheckoutInfo |
BasketCheckoutInfo |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🕒 agent proposal (pending review) |
Checkout requires a complete shipping address |
BasketCheckoutInfo |
validation |
n/a * |
n/a * |
view |
BasketItem (6)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
Setting quantity to zero or less removes the item |
BasketItem |
business_rule |
n/a * |
n/a * |
view |
| 2 |
🕒 agent proposal (pending review) |
Adding an existing product increments quantity instead of duplicating the line |
BasketItem |
business_rule |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Client basket totals: line total and badge count derived from quantity |
BasketItem |
business_rule |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
Checkout navigation only when the basket has items |
BasketItem |
business_rule |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Basket item quantity must be at least 1 |
BasketItem |
validation |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
Basket persists only product id and quantity; details are re-hydrated from the catalog |
BasketItem |
business_rule |
n/a * |
n/a * |
view |
BasketState (5)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
InvalidOperationException in BasketState |
BasketState |
validation |
on_save |
⚠️ 50% |
view |
| 2 |
🔧 heuristic |
InvalidOperationException in BasketState |
BasketState |
validation |
on_save |
⚠️ 50% |
view |
| 3 |
🔧 heuristic |
BasketState: checkoutInfo.RequestId auto-fill when default/null |
BasketState |
auto_fill |
on_save |
70% |
view |
Buyer (6)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on Buyer |
Buyer |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🕒 agent proposal (pending review) |
Default card type when unspecified |
Buyer |
policy |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Buyer requires identity and name |
Buyer |
validation |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
Payment method verify-or-add (dedup) |
Buyer |
policy |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Buyer verified-or-created at order start |
Buyer |
policy |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
One buyer per user identity |
Buyer |
invariant |
n/a * |
n/a * |
view |
CatalogApi (6)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
CatalogItem (20)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
CatalogItem: guard (quantityDesired <= 0) |
CatalogItem |
validation |
before_create |
70% |
view |
| 2 |
🔧 heuristic |
Required on CatalogItem |
CatalogItem |
validation |
on_save |
⚠️ 60% |
view |
| 3 |
🕒 agent proposal (pending review) |
Price change triggers ProductPriceChangedIntegrationEvent |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
AddStock clears the OnReorder flag |
CatalogItem |
state_transition |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Catalog listing filters by name prefix, type and brand, ordered by name |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
RemoveStock rejects sold-out items |
CatalogItem |
validation |
n/a * |
n/a * |
view |
| 7 |
🕒 agent proposal (pending review) |
V1 update requires the item id in the request body |
CatalogItem |
validation |
n/a * |
n/a * |
view |
| 8 |
🕒 agent proposal (pending review) |
Item embedding is computed from Name + Description, truncated to 384 dimensions |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 9 |
🕒 agent proposal (pending review) |
RemoveStock requires positive quantity |
CatalogItem |
validation |
n/a * |
n/a * |
view |
| 10 |
🕒 agent proposal (pending review) |
Chatbot catalog search uses semantic relevance with top 8 results |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 11 |
🕒 agent proposal (pending review) |
Client catalog filter requires both brand and type |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 12 |
🕒 agent proposal (pending review) |
GetItemById requires a positive id and 404s on missing item |
CatalogItem |
validation |
n/a * |
n/a * |
view |
| 13 |
🕒 agent proposal (pending review) |
Stock can never go negative |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 14 |
🕒 agent proposal (pending review) |
RestockThreshold is the reorder trigger level |
CatalogItem |
domain_concept |
n/a * |
n/a * |
view |
| 15 |
🕒 agent proposal (pending review) |
Chatbot add-to-cart requires an authenticated user |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 16 |
🕒 agent proposal (pending review) |
Semantic search falls back to name search when AI is unavailable |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 17 |
🕒 agent proposal (pending review) |
Semantic search ranks by cosine distance over embedded items only |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 18 |
🕒 agent proposal (pending review) |
Create and update always recompute the item embedding |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
| 19 |
🕒 agent proposal (pending review) |
AddStock caps at MaxStockThreshold |
CatalogItem |
business_rule |
n/a * |
n/a * |
view |
CatalogView (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
CatalogViewModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
ChangePasswordViewModel (3)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on ChangePasswordViewModel |
ChangePasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🔧 heuristic |
StringLength on ChangePasswordViewModel |
ChangePasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 3 |
🔧 heuristic |
Compare on ChangePasswordViewModel |
ChangePasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
CheckoutViewModel (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
Consent (7)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
Denying consent returns access_denied and raises audit event |
Consent |
business_rule |
n/a * |
n/a * |
view |
| 2 |
🕒 agent proposal (pending review) |
Granting consent requires at least one scope selected |
Consent |
validation |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Consent outcome communicated to IdentityServer before redirect |
Consent |
business_rule |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
Required scopes are always pre-checked on consent screen |
Consent |
business_rule |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Consent requires a valid pending authorization context |
Consent |
validation |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
Consent decision must be explicit yes or no |
Consent |
validation |
n/a * |
n/a * |
view |
| 7 |
🕒 agent proposal (pending review) |
OfflineAccess scope stripped when offline access disabled |
Consent |
business_rule |
n/a * |
n/a * |
view |
ConsentController (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
CustomerBasket (11)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
UpdateBasket requires an authenticated caller |
CustomerBasket |
authorization |
n/a * |
n/a * |
view |
| 2 |
🕒 agent proposal (pending review) |
DeleteBasket requires an authenticated caller |
CustomerBasket |
authorization |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Basket is deleted when the order starts |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
One basket per customer, stored in Redis under /basket/{userId} |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Client basket operations are no-ops without an auth token |
CustomerBasket |
authorization |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
Checkout requires buyer id and user name, then creates the order and deletes the basket |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 7 |
🕒 agent proposal (pending review) |
Basket update is a full overwrite followed by read-back |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 8 |
🕒 agent proposal (pending review) |
Basket items are only shown to authenticated users |
CustomerBasket |
authorization |
n/a * |
n/a * |
view |
| 9 |
🕒 agent proposal (pending review) |
Checkout is idempotent via a request id |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 10 |
🕒 agent proposal (pending review) |
Updating a basket that does not persist yields NotFound |
CustomerBasket |
business_rule |
n/a * |
n/a * |
view |
| 11 |
🕒 agent proposal (pending review) |
GetBasket is anonymous-tolerant: no identity yields an empty basket |
CustomerBasket |
authorization |
n/a * |
n/a * |
view |
Device (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
DeviceController (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
ExternalController (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
FadeInAnimation (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
NullReferenceException in FadeInAnimation |
FadeInAnimation |
validation |
on_save |
⚠️ 50% |
view |
FadeOutAnimation (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
NullReferenceException in FadeOutAnimation |
FadeOutAnimation |
validation |
on_save |
⚠️ 50% |
view |
FadeToAnimation (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
NullReferenceException in FadeToAnimation |
FadeToAnimation |
validation |
on_save |
⚠️ 50% |
view |
FixUriService (5)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
IdentityService (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
IntegrationEventLogEntry (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on IntegrationEventLogEntry |
IntegrationEventLogEntry |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🔧 heuristic |
IntegrationEventLogEntry: State → EventStateEnum.NotPublished |
IntegrationEventLogEntry |
state_transition |
on_change |
75% |
view |
LocationService (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
LoginInputModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on LoginInputModel |
LoginInputModel |
validation |
on_save |
⚠️ 60% |
view |
LoginViewModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on LoginViewModel |
LoginViewModel |
validation |
on_save |
⚠️ 60% |
view |
OpenApiOptionsExtensions (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
Order (46)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
OrderingContext: guard (_currentTransaction != null) |
Order |
validation |
before_create |
70% |
view |
| 2 |
🔧 heuristic |
OrderingContext: guard (transaction == null) |
Order |
validation |
before_create |
70% |
view |
| 3 |
🔧 heuristic |
Required on Order |
Order |
validation |
on_save |
⚠️ 60% |
view |
| 4 |
🕒 agent proposal (pending review) |
Paid and Shipped statuses trigger customer webhooks |
Order |
policy |
n/a * |
n/a * |
view |
| 5 |
🕒 agent proposal (pending review) |
Stock rejection cancels the order |
Order |
state_transition |
n/a * |
n/a * |
view |
| 6 |
🕒 agent proposal (pending review) |
Payment outcome decided by gateway simulation |
Order |
policy |
n/a * |
n/a * |
view |
| 7 |
🕒 agent proposal (pending review) |
Grace period confirmed triggers AwaitingValidation transition |
Order |
state_transition |
n/a * |
n/a * |
view |
| 8 |
🕒 agent proposal (pending review) |
Stock deducted only after payment, non-blocking |
Order |
policy |
n/a * |
n/a * |
view |
| 9 |
🕒 agent proposal (pending review) |
Periodic grace period polling |
Order |
process_schedule |
n/a * |
n/a * |
view |
| 10 |
🕒 agent proposal (pending review) |
Duplicate product lines are merged, keeping the higher discount |
Order |
policy |
n/a * |
n/a * |
view |
| 11 |
🕒 agent proposal (pending review) |
Paid or shipped orders cannot be cancelled |
Order |
state_transition |
n/a * |
n/a * |
view |
| 12 |
🕒 agent proposal (pending review) |
Cancel, ship and create require a non-empty request ID |
Order |
policy |
n/a * |
n/a * |
view |
| 13 |
🕒 agent proposal (pending review) |
Users only see their own orders |
Order |
policy |
n/a * |
n/a * |
view |
| 14 |
🕒 agent proposal (pending review) |
Set AwaitingValidation status only for existing orders |
Order |
state_transition |
n/a * |
n/a * |
view |
| 15 |
🕒 agent proposal (pending review) |
Stock rejection auto-cancels order awaiting validation |
Order |
policy |
n/a * |
n/a * |
view |
| 16 |
🕒 agent proposal (pending review) |
Stock confirmed advances order to StockConfirmed |
Order |
state_transition |
n/a * |
n/a * |
view |
| 17 |
🕒 agent proposal (pending review) |
Real-time order status notification per buyer |
Order |
policy |
n/a * |
n/a * |
view |
| 18 |
🕒 agent proposal (pending review) |
Grace period eligibility criteria |
Order |
temporal_condition |
n/a * |
n/a * |
view |
| 19 |
🕒 agent proposal (pending review) |
Ship command fails for non-existent order |
Order |
validation |
n/a * |
n/a * |
view |
| 20 |
🕒 agent proposal (pending review) |
Order creation publishes OrderStarted integration event to clear the basket |
Order |
policy |
n/a * |
n/a * |
view |
| 21 |
🕒 agent proposal (pending review) |
Order total is the sum of units times unit price |
Order |
calculation |
n/a * |
n/a * |
view |
| 22 |
🕒 agent proposal (pending review) |
Basket items without product name are excluded from checkout order |
Order |
validation |
n/a * |
n/a * |
view |
| 23 |
🕒 agent proposal (pending review) |
New order starts as Submitted and raises OrderStarted event |
Order |
invariant |
n/a * |
n/a * |
view |
| 24 |
🕒 agent proposal (pending review) |
Credit card number is masked before order creation and never logged |
Order |
policy |
n/a * |
n/a * |
view |
| 25 |
🕒 agent proposal (pending review) |
Stock validation: any missing item rejects the whole order |
Order |
policy |
n/a * |
n/a * |
view |
| 26 |
🕒 agent proposal (pending review) |
Order linked to buyer and payment only after verification |
Order |
policy |
n/a * |
n/a * |
view |
| 27 |
🕒 agent proposal (pending review) |
Cancel attempt on already-progressed order is reported as failure |
Order |
policy |
n/a * |
n/a * |
view |
| 28 |
🕒 agent proposal (pending review) |
Grace period confirmation event per order |
Order |
event_publication |
n/a * |
n/a * |
view |
| 29 |
🕒 agent proposal (pending review) |
Only out-of-stock items reported on rejection |
Order |
policy |
n/a * |
n/a * |
view |
| 30 |
🕒 agent proposal (pending review) |
Payment success advances order to Paid |
Order |
state_transition |
n/a * |
n/a * |
view |
| 31 |
🕒 agent proposal (pending review) |
Client cancel action only offered while order is Submitted |
Order |
policy |
n/a * |
n/a * |
view |
| 32 |
🕒 agent proposal (pending review) |
StockConfirmed only reachable from AwaitingValidation |
Order |
state_transition |
n/a * |
n/a * |
view |
| 33 |
🕒 agent proposal (pending review) |
AwaitingValidation only reachable from Submitted |
Order |
state_transition |
n/a * |
n/a * |
view |
| 34 |
🕒 agent proposal (pending review) |
Paid only reachable from StockConfirmed |
Order |
state_transition |
n/a * |
n/a * |
view |
| 35 |
🕒 agent proposal (pending review) |
Payment failure triggers order cancellation |
Order |
policy |
n/a * |
n/a * |
view |
| 36 |
🕒 agent proposal (pending review) |
Cancel command fails for non-existent order |
Order |
validation |
n/a * |
n/a * |
view |
| 37 |
🕒 agent proposal (pending review) |
Every status change broadcast with buyer identity |
Order |
policy |
n/a * |
n/a * |
view |
| 38 |
🕒 agent proposal (pending review) |
Only paid orders can be shipped |
Order |
state_transition |
n/a * |
n/a * |
view |
| 39 |
🕒 agent proposal (pending review) |
Outbox: integration events saved with transaction, published after |
Order |
policy |
n/a * |
n/a * |
view |
| 41 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.Submitted |
Order |
state_transition |
on_change |
75% |
view |
| 42 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.AwaitingValidation |
Order |
state_transition |
on_change |
75% |
view |
| 43 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.StockConfirmed |
Order |
state_transition |
on_change |
75% |
view |
| 44 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.Paid |
Order |
state_transition |
on_change |
75% |
view |
| 45 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.Shipped |
Order |
state_transition |
on_change |
75% |
view |
| 46 |
🔧 heuristic |
Order: OrderStatus → OrderStatus.Cancelled |
Order |
state_transition |
on_change |
75% |
view |
OrderCheckout (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on OrderCheckout |
OrderCheckout |
validation |
on_save |
⚠️ 60% |
view |
OrderDraft (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
Order draft is built from basket items, one order item per basket item |
OrderDraft |
business_rule |
n/a * |
n/a * |
view |
OrderItem (11)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
OrderItem: guard (units <= 0) |
OrderItem |
validation |
before_create |
70% |
view |
| 2 |
🔧 heuristic |
OrderItem: guard (discount < 0) |
OrderItem |
validation |
before_create |
70% |
view |
| 3 |
🔧 heuristic |
Required on OrderItem |
OrderItem |
validation |
on_save |
⚠️ 60% |
view |
| 4 |
🔧 heuristic |
OrderingDomainException in OrderItem |
OrderItem |
validation |
on_save |
⚠️ 50% |
view |
| 5 |
🔧 heuristic |
OrderingDomainException in OrderItem |
OrderItem |
validation |
on_save |
⚠️ 50% |
view |
| 6 |
🔧 heuristic |
OrderingDomainException in OrderItem |
OrderItem |
validation |
before_create |
⚠️ 50% |
view |
| 7 |
🔧 heuristic |
OrderingDomainException in OrderItem |
OrderItem |
validation |
before_create |
⚠️ 50% |
view |
| 8 |
🕒 agent proposal (pending review) |
New discount must be non-negative |
OrderItem |
validation |
n/a * |
n/a * |
view |
| 9 |
🕒 agent proposal (pending review) |
Added units must be non-negative |
OrderItem |
validation |
n/a * |
n/a * |
view |
| 10 |
🕒 agent proposal (pending review) |
Discount cannot exceed order item total |
OrderItem |
validation |
n/a * |
n/a * |
view |
| 11 |
🕒 agent proposal (pending review) |
Order item units must be positive |
OrderItem |
validation |
n/a * |
n/a * |
view |
OrderStatus (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
Order lifecycle states |
OrderStatus |
state_machine |
n/a * |
n/a * |
view |
PaymentMethod (4)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on PaymentMethod |
PaymentMethod |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🕒 agent proposal (pending review) |
Payment method identity = card type + number + expiration |
PaymentMethod |
invariant |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Expired card rejected |
PaymentMethod |
validation |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
Payment method mandatory card data |
PaymentMethod |
validation |
n/a * |
n/a * |
view |
ProductPriceChangedIntegrationEvent (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🕒 agent proposal (pending review) |
Event and catalog change persist in one local transaction (outbox) |
ProductPriceChangedIntegrationEvent |
business_rule |
n/a * |
n/a * |
view |
| 2 |
🕒 agent proposal (pending review) |
Event publish lifecycle: InProgress -> Published, Failed on error |
ProductPriceChangedIntegrationEvent |
state_transition |
n/a * |
n/a * |
view |
ProfileService (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
ArgumentException in ProfileService |
ProfileService |
validation |
on_save |
⚠️ 50% |
view |
ProfileViewModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
RabbitMQEventBus (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
InvalidOperationException in RabbitMQEventBus |
RabbitMQEventBus |
validation |
on_save |
⚠️ 50% |
view |
RegisterViewModel (3)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on RegisterViewModel |
RegisterViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🔧 heuristic |
StringLength on RegisterViewModel |
RegisterViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 3 |
🔧 heuristic |
Compare on RegisterViewModel |
RegisterViewModel |
validation |
on_save |
⚠️ 60% |
view |
RequestProvider (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
ResetPasswordViewModel (3)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on ResetPasswordViewModel |
ResetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🔧 heuristic |
StringLength on ResetPasswordViewModel |
ResetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 3 |
🔧 heuristic |
Compare on ResetPasswordViewModel |
ResetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
SecuritySchemeDefinitionsTransformer (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
SetPasswordViewModel (3)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on SetPasswordViewModel |
SetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🔧 heuristic |
StringLength on SetPasswordViewModel |
SetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
| 3 |
🔧 heuristic |
Compare on SetPasswordViewModel |
SetPasswordViewModel |
validation |
on_save |
⚠️ 60% |
view |
TestServerCallContext (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
InvalidOperationException in TestServerCallContext |
TestServerCallContext |
validation |
on_save |
⚠️ 50% |
view |
VerifyCodeViewModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on VerifyCodeViewModel |
VerifyCodeViewModel |
validation |
on_save |
⚠️ 60% |
view |
VerifyPhoneNumberViewModel (1)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on VerifyPhoneNumberViewModel |
VerifyPhoneNumberViewModel |
validation |
on_save |
⚠️ 60% |
view |
VisualElementExtensions (2)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
|
|
|
|
|
|
|
|
WebhookSubscription (4)
| # |
Source |
Rule |
Entity |
Type |
Trigger |
Confidence |
Detail |
| 1 |
🔧 heuristic |
Required on WebhookSubscription |
WebhookSubscription |
validation |
on_save |
⚠️ 60% |
view |
| 2 |
🕒 agent proposal (pending review) |
Grant URL must share origin with hook URL |
WebhookSubscription |
validation |
n/a * |
n/a * |
view |
| 3 |
🕒 agent proposal (pending review) |
Unreachable grant URL cannot be granted |
WebhookSubscription |
error_handling |
n/a * |
n/a * |
view |
| 4 |
🕒 agent proposal (pending review) |
Grant URL verified via OPTIONS challenge with token echo |
WebhookSubscription |
validation |
n/a * |
n/a * |
view |
* n/a — agent rules (proposed or promoted) don't carry trigger/confidence: the submit never computes those fields, not even after human review.
Rule details
ApplicationUser (1)
#1 — Required on ApplicationUser
[Required]
BasketCheckoutInfo (2)
#1 — Required on BasketCheckoutInfo
[Required]
#2 — Checkout requires a complete shipping address
- Source: 🕒 agent proposal (pending review)
- Entity: BasketCheckoutInfo
- Type: validation
- Statement: Street, City, State, Country and ZipCode are all [Required] on the checkout form; card fields are optional in this model (the demo checkout injects test card data).
- Anchor:
src/WebApp/Services/BasketCheckoutInfo.cs:5
- Evidence:
[Required] public string? Street ... [Required] public string? City ... [Required] public string? State ... [Required] public string? Country ... [Required] public string? ZipCode
BasketItem (6)
#1 — Setting quantity to zero or less removes the item
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: business_rule
- Statement: SetQuantityAsync updates the line quantity only when quantity > 0; a value of 0 or negative removes the item from the basket entirely, then the whole basket is persisted.
- Anchor:
src/WebApp/Services/BasketState.cs:58
- Evidence:
if (quantity > 0) { row.Quantity = quantity; } else { existingItems.Remove(row); }
#2 — Adding an existing product increments quantity instead of duplicating the line
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: business_rule
- Statement: AddAsync scans the basket for the product: if found, quantity is incremented by 1; if not, a new line with quantity 1 is added. The same de-duplication rule exists in the MAUI client (CustomerBasket.AddItemToBasket, src/ClientApp/Models/Basket/CustomerBasket.cs:11-23).
- Anchor:
src/WebApp/Services/BasketState.cs:33
- Evidence:
if (existing.ProductId == item.Id) { items[i] = existing with { Quantity = existing.Quantity + 1 }; found = true; } ... if (!found) { items.Add(new BasketQuantity(item.Id, 1)); }
#3 — Client basket totals: line total and badge count derived from quantity
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: business_rule
- Statement: In the MAUI client, a line's Total is Quantity * UnitPrice, the basket badge counts the sum of quantities, and an item is flagged as repriced (HasNewPrice) when OldUnitPrice != 0 (src/ClientApp/Models/Basket/BasketItem.cs:17-31).
- Anchor:
src/ClientApp/ViewModels/BasketViewModel.cs:24
- Evidence:
public int BadgeCount => _basketItems?.Sum(basketItem => basketItem.Quantity) ?? 0; public decimal Total => _basketItems?.Sum(basketItem => basketItem.Quantity * basketItem.UnitPrice) ?? 0m;
#4 — Checkout navigation only when the basket has items
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: business_rule
- Statement: In the MAUI client, the Checkout command navigates to the Checkout page only if the basket contains at least one item; an empty basket cannot start checkout.
- Anchor:
src/ClientApp/ViewModels/BasketViewModel.cs:104
- Evidence:
if (_basketItems?.Any() ?? false) { _appEnvironmentService.BasketService.LocalBasketItems = _basketItems; await NavigationService.NavigateToAsync("Checkout"); }
#5 — Basket item quantity must be at least 1
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: validation
- Statement: A basket item with Quantity < 1 fails validation with the error 'Invalid number of units' on the Quantity member (BasketItem implements IValidatableObject).
- Anchor:
src/Basket.API/Model/BasketItem.cs:13
- Evidence:
if (Quantity < 1) { results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" })); }
#6 — Basket persists only product id and quantity; details are re-hydrated from the catalog
- Source: 🕒 agent proposal (pending review)
- Entity: BasketItem
- Type: business_rule
- Statement: The gRPC contract carries only product_id and quantity per basket item (basket.proto message BasketItem, src/Basket.API/Proto/basket.proto:20-23); when displaying, the WebApp joins each quantity with the catalog to fill name and unit price — prices always come from the current catalog, not from the stored basket.
- Anchor:
src/WebApp/Services/BasketState.cs:117
- Evidence:
var catalogItems = (await catalogService.GetCatalogItems(productIds)).ToDictionary(...); ... ProductName = catalogItem.Name, UnitPrice = catalogItem.Price, Quantity = item.Quantity
BasketState (5)
#1 — InvalidOperationException in BasketState
throw new InvalidOperationException("User does not have a buyer ID"
#2 — InvalidOperationException in BasketState
throw new InvalidOperationException("User does not have a user name"
#3 — BasketState: checkoutInfo.RequestId auto-fill when default/null
- Source: 🔧 heuristic
- Entity: BasketState
- Type: auto_fill
- Statement: BasketState: checkoutInfo.RequestId auto-fill when default/null
- Anchor:
src/WebApp/Services/BasketState.cs:80
- Evidence:
if (checkoutInfo.RequestId == default)
{
checkoutInfo.RequestId = Guid.NewGuid();
#1 — Required on Buyer
[Required]
#2 — Default card type when unspecified
var cardTypeId = domainEvent.CardTypeId != 0 ? domainEvent.CardTypeId : 1;
#3 — Buyer requires identity and name
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
#4 — Payment method verify-or-add (dedup)
- Source: 🕒 agent proposal (pending review)
- Entity: Buyer
- Type: policy
- Statement: When a buyer submits card data, an existing payment method with the same card type, card number and expiration is reused instead of duplicated; a new one is added only when no match exists. In both cases a BuyerAndPaymentMethodVerified domain event is raised for the order.
- Anchor:
src/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs:29
- Evidence:
var existingPayment = _paymentMethods.SingleOrDefault(p => p.IsEqualTo(cardTypeId, cardNumber, expiration));
#5 — Buyer verified-or-created at order start
var buyer = await _buyerRepository.FindAsync(domainEvent.UserId); var buyerExisted = buyer is not null; if (!buyerExisted) { buyer = new Buyer(domainEvent.UserId, domainEvent.UserName); }
#6 — One buyer per user identity
- Source: 🕒 agent proposal (pending review)
- Entity: Buyer
- Type: invariant
- Statement: A user identity GUID maps to at most one buyer: lookup uses a single-or-default query on IdentityGuid, so duplicate buyers for the same identity are treated as a fault.
- Anchor:
src/Ordering.Infrastructure/Repositories/BuyerRepository.cs:33
- Evidence:
.Where(b => b.IdentityGuid == identity).SingleOrDefaultAsync();
CatalogApi (6)
#1 — CatalogItem: guard (quantityDesired <= 0)
if (quantityDesired <= 0)
{
throw
#2 — Required on CatalogItem
[Required]
#3 — Price change triggers ProductPriceChangedIntegrationEvent
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: On item update, if and only if the Price property was modified, a ProductPriceChangedIntegrationEvent (ProductId, NewPrice, OldPrice) is created, saved atomically with the catalog change, and published through the event bus. If the price did not change, the update is saved with a plain SaveChanges and no event is emitted.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:345
- Evidence:
if (priceEntry.IsModified) { var priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, priceEntry.OriginalValue); await services.EventService.SaveEventAndCatalogContextChangesAsync(priceChangedEvent); await services.EventService.PublishThroughEventBusAsync(priceChangedEvent); } else { await services.Context.SaveChangesAsync(); }
#4 — AddStock clears the OnReorder flag
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: state_transition
- Statement: Any restock (AddStock) resets OnReorder to false: once units are added, the item is no longer considered pending reorder.
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:102
- Evidence:
this.OnReorder = false;
#5 — Catalog listing filters by name prefix, type and brand, ordered by name
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: GetAllItems composes optional filters — Name.StartsWith(name) (prefix match, not contains), CatalogTypeId == type, CatalogBrandId == brand — then orders by Name and paginates (Skip pageSize*pageIndex / Take pageSize), returning the total count alongside the page.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:123
- Evidence:
root = root.Where(c => c.Name.StartsWith(name)); ... root.Where(c => c.CatalogTypeId == type) ... OrderBy(c => c.Name).Skip(pageSize * pageIndex).Take(pageSize)
#6 — RemoveStock rejects sold-out items
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: validation
- Statement: RemoveStock throws CatalogDomainException ('Empty stock, product item {Name} is sold out') when AvailableStock == 0; stock removal is impossible on a sold-out item.
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:62
- Evidence:
if (AvailableStock == 0) { throw new CatalogDomainException($"Empty stock, product item {Name} is sold out"); }
#7 — V1 update requires the item id in the request body
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: validation
- Statement: UpdateItemV1 returns 400 BadRequest ('Item id must be provided in the request body.') when the incoming product has no Id, then delegates to the v2 UpdateItem which 404s if the id does not exist.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:315
- Evidence:
if (productToUpdate?.Id == null) { return TypedResults.BadRequest<ProblemDetails>(new (){ Detail = "Item id must be provided in the request body." }); }
#8 — Item embedding is computed from Name + Description, truncated to 384 dimensions
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: CatalogAI embeds a catalog item as the string '{Name} {Description}' and slices every generated embedding vector to exactly 384 dimensions (EmbeddingDimensions constant) before storing/searching. AI features are enabled only when an embedding generator is configured (IsEnabled).
- Anchor:
src/Catalog.API/Services/CatalogAI.cs:9
- Evidence:
private const int EmbeddingDimensions = 384; ... public bool IsEnabled => _embeddingGenerator is not null; ... embedding = embedding[0..EmbeddingDimensions]; ... CatalogItemToString(CatalogItem item) => $"{item.Name} {item.Description}";
#9 — RemoveStock requires positive quantity
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: validation
- Statement: RemoveStock throws CatalogDomainException when quantityDesired <= 0; it is invalid to request removal of zero or negative units.
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:69
- Evidence:
if (quantityDesired <= 0) { throw new CatalogDomainException($"Item units desired should be greater than zero"); }
#10 — Chatbot catalog search uses semantic relevance with top 8 results
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: The AdventureWorks chatbot tool SearchCatalog answers product-description queries via GetCatalogItemsWithSemanticRelevance(page 0, take 8), rewriting each result's PictureUrl through the product image provider before returning it to the LLM.
- Anchor:
src/WebApp/Components/Chatbot/ChatState.cs:117
- Evidence:
var results = await _catalogService.GetCatalogItemsWithSemanticRelevance(0, 8, productDescription!); ... results.Data[i] = results.Data[i] with { PictureUrl = _productImages.GetProductImageUrl(results.Data[i].Id) };
#11 — Client catalog filter requires both brand and type
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: In the MAUI client, the catalog filter can only be applied when BOTH a brand and a type are selected (CanFilter => SelectedBrand is not null && SelectedType is not null); ApplyFilterAsync re-checks the same condition before calling FilterAsync(brandId, typeId).
- Anchor:
src/ClientApp/ViewModels/CatalogViewModel.cs:161
- Evidence:
if (SelectedBrand is not null && SelectedType is not null) { var filteredProducts = await _appEnvironmentService.CatalogService.FilterAsync(SelectedBrand.Id, SelectedType.Id); ... } (CanFilter at line 57: public bool CanFilter => SelectedBrand is not null && SelectedType is not null;)
#12 — GetItemById requires a positive id and 404s on missing item
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: validation
- Statement: GetItemById returns 400 BadRequest ('Id is not valid') when id <= 0, and 404 NotFound when no catalog item exists with that id; the item is loaded with its CatalogBrand included.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:176
- Evidence:
if (id <= 0) { return TypedResults.BadRequest<ProblemDetails>(new (){ Detail = "Id is not valid" }); } ... if (item == null) { return TypedResults.NotFound(); }
#13 — Stock can never go negative
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: RemoveStock removes at most the available quantity: removed = Math.Min(quantityDesired, AvailableStock), so AvailableStock never becomes negative. When stock is insufficient, the method removes what is available and returns the actual removed quantity; the caller is responsible for checking whether removed == desired (partial fulfillment).
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:74
- Evidence:
int removed = Math.Min(quantityDesired, this.AvailableStock); this.AvailableStock -= removed; return removed;
#14 — RestockThreshold is the reorder trigger level
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: domain_concept
- Statement: CatalogItem carries three stock control fields: AvailableStock (quantity in stock), RestockThreshold (available stock level at which the item should be reordered) and MaxStockThreshold (maximum units that can be in stock at any time). RemoveStock's contract documents that breaching RestockThreshold should generate a restock request.
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:28
- Evidence:
// Available stock at which we should reorder / public int RestockThreshold ... // Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses) / public int MaxStockThreshold
#15 — Chatbot add-to-cart requires an authenticated user
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: The chatbot AddToCart tool fetches the catalog item and adds it to the basket; an Unauthenticated gRPC error is translated into the business answer 'Unable to add an item to the cart. You must be logged in.' — anonymous users cannot add items via the chatbot.
- Anchor:
src/WebApp/Components/Chatbot/ChatState.cs:136
- Evidence:
catch (Grpc.Core.RpcException e) when (e.StatusCode == Grpc.Core.StatusCode.Unauthenticated) { return "Unable to add an item to the cart. You must be logged in."; }
#16 — Semantic search falls back to name search when AI is unavailable
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: GetItemsBySemanticRelevance degrades gracefully: if CatalogAI.IsEnabled is false, or the embedding for the search text comes back null, the request is answered by the plain name search (StartsWith) instead of vector search.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:245
- Evidence:
if (!services.CatalogAI.IsEnabled) { return await GetItemsByName(paginationRequest, services, text); } var vector = await services.CatalogAI.GetEmbeddingAsync(text); if (vector is null) { return await GetItemsByName(paginationRequest, services, text); }
#17 — Semantic search ranks by cosine distance over embedded items only
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: AI search considers only catalog items with a non-null Embedding, orders them by ascending cosine distance to the query vector (most similar first) and paginates the result; items never embedded are invisible to semantic search.
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:279
- Evidence:
.Where(c => c.Embedding != null) .OrderBy(c => c.Embedding!.CosineDistance(vector)) .Skip(pageSize * pageIndex) .Take(pageSize)
#18 — Create and update always recompute the item embedding
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: Both CreateItem and UpdateItem set item.Embedding = CatalogAI.GetEmbeddingAsync(item) before saving, keeping the semantic index consistent with the current Name/Description (null when AI disabled).
- Anchor:
src/Catalog.API/Apis/CatalogApi.cs:382
- Evidence:
item.Embedding = await services.CatalogAI.GetEmbeddingAsync(item); (same call in UpdateItem at line 343: catalogItem.Embedding = await services.CatalogAI.GetEmbeddingAsync(catalogItem);)
#19 — AddStock caps at MaxStockThreshold
- Source: 🕒 agent proposal (pending review)
- Entity: CatalogItem
- Type: business_rule
- Statement: AddStock never raises AvailableStock above MaxStockThreshold (physical/logistical warehouse constraint). If the requested quantity would exceed the cap, only the difference up to MaxStockThreshold is added and the excess is silently discarded; the method returns the quantity actually added.
- Anchor:
src/Catalog.API/Model/CatalogItem.cs:86
- Evidence:
if ((this.AvailableStock + quantity) > this.MaxStockThreshold) { this.AvailableStock += (this.MaxStockThreshold - this.AvailableStock); }
#1 — Required on ChangePasswordViewModel
[Required]
#2 — StringLength on ChangePasswordViewModel
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
#3 — Compare on ChangePasswordViewModel
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
CheckoutViewModel (2)
#1 — Denying consent returns access_denied and raises audit event
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: business_rule
- Statement: When the user clicks 'no' on the consent screen, the standard OAuth 'access_denied' error is returned to the client and a ConsentDeniedEvent is emitted with the user subject, client id and requested scopes.
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:92
- Evidence:
if (model?.Button == "no") { grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; await _events.RaiseAsync(new ConsentDeniedEvent(...)); }
#2 — Granting consent requires at least one scope selected
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: validation
- Statement: When the user clicks 'yes', consent is only granted if at least one scope was consented; with no scopes selected the submission is rejected with the MustChooseOne validation error and the consent screen is redisplayed.
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:101
- Evidence:
if (model.ScopesConsented != null && model.ScopesConsented.Any()) { ... } else { result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; }
#3 — Consent outcome communicated to IdentityServer before redirect
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: business_rule
- Statement: Only when a consent response exists (granted or explicitly denied) is the outcome communicated back to IdentityServer via GrantConsentAsync, after which the user may be redirected to the authorization endpoint; otherwise the consent UI is redisplayed.
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:132
- Evidence:
if (grantedConsent != null) { await _interaction.GrantConsentAsync(request, grantedConsent); result.RedirectUri = model.ReturnUrl; result.Client = request.Client; } else { result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); }
#4 — Required scopes are always pre-checked on consent screen
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: business_rule
- Statement: A scope marked as Required by the identity resource or API scope definition is always rendered as checked on the consent screen, regardless of the user's previous selection (Checked = check || Required).
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:217
- Evidence:
return new ScopeViewModel { ... Required = apiScope.Required, Checked = check || apiScope.Required };
#5 — Consent requires a valid pending authorization context
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: validation
- Statement: A consent decision is only processed if the return URL still matches a valid pending authorization request; if no authorization context is found, the consent is ignored (no grant, no redirect, error view).
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:86
- Evidence:
// validate return url is still valid — var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (request == null) return result;
#6 — Consent decision must be explicit yes or no
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: validation
- Statement: A consent postback whose button value is neither 'yes' nor 'no' is treated as an invalid selection: the InvalidSelection validation error is set and no consent is granted.
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:126
- Evidence:
else { result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; } — reached when model?.Button is neither "no" nor "yes"
#7 — OfflineAccess scope stripped when offline access disabled
- Source: 🕒 agent proposal (pending review)
- Entity: Consent
- Type: business_rule
- Statement: If offline access is disabled in ConsentOptions (EnableOfflineAccess == false), the 'offline_access' scope is filtered out of the user's consented scopes even if it was submitted.
- Anchor:
src/Identity.API/Quickstart/Consent/ConsentController.cs:105
- Evidence:
var scopes = model.ScopesConsented; if (ConsentOptions.EnableOfflineAccess == false) { scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); }
ConsentController (1)
#1 — UpdateBasket requires an authenticated caller
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: authorization
- Statement: The gRPC UpdateBasket operation rejects callers without a user identity with gRPC status Unauthenticated ('The caller is not authenticated.'). The basket's BuyerId is always taken from the authenticated identity, not from the request payload.
- Anchor:
src/Basket.API/Grpc/BasketService.cs:36
- Evidence:
var userId = context.GetUserIdentity(); if (string.IsNullOrEmpty(userId)) { ThrowNotAuthenticated(); } ... MapToCustomerBasket(userId, request)
#2 — DeleteBasket requires an authenticated caller
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: authorization
- Statement: The gRPC DeleteBasket operation rejects callers without a user identity with gRPC status Unauthenticated; only the caller's own basket (keyed by its user id) can be deleted.
- Anchor:
src/Basket.API/Grpc/BasketService.cs:59
- Evidence:
var userId = context.GetUserIdentity(); if (string.IsNullOrEmpty(userId)) { ThrowNotAuthenticated(); } await repository.DeleteBasketAsync(userId);
#3 — Basket is deleted when the order starts
public async Task Handle(OrderStartedIntegrationEvent @event) { ... await repository.DeleteBasketAsync(@event.UserId); }
#4 — One basket per customer, stored in Redis under /basket/{userId}
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: business_rule
- Statement: Each customer has a single basket persisted as one Redis string keyed by '/basket/' + userId; GetBasketAsync returns null when no entry exists for the customer.
- Anchor:
src/Basket.API/Repositories/RedisBasketRepository.cs:6
- Evidence:
private static RedisKey BasketKeyPrefix = "/basket/"u8.ToArray(); ... if (data is null || data.Length == 0) { return null; }
#5 — Client basket operations are no-ops without an auth token
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: authorization
- Statement: In the MAUI BasketService, GetBasketAsync returns an empty basket, UpdateBasketAsync returns the input unchanged and ClearBasketAsync returns early when no auth token is available; all gRPC calls attach 'authorization: Bearer {token}'.
- Anchor:
src/ClientApp/Services/Basket/BasketService.cs:32
- Evidence:
var authToken = await _identityService.GetAuthTokenAsync()...; if (string.IsNullOrEmpty(authToken)) { return basket; } ... headers.Add("authorization", $"Bearer {token}") (line 132-137)
#6 — Checkout requires buyer id and user name, then creates the order and deletes the basket
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: business_rule
- Statement: CheckoutAsync fails with InvalidOperationException when the user has no buyer ID or user name; otherwise it builds a CreateOrderRequest from the basket items, calls Ordering to create the order, and immediately deletes the basket.
- Anchor:
src/WebApp/Services/BasketState.cs:78
- Evidence:
var buyerId = await authenticationStateProvider.GetBuyerIdAsync() ?? throw new InvalidOperationException("User does not have a buyer ID"); ... await orderingService.CreateOrder(request, checkoutInfo.RequestId); await DeleteBasketAsync();
#7 — Basket update is a full overwrite followed by read-back
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: business_rule
- Statement: UpdateBasketAsync serializes the whole basket and overwrites the Redis entry; on write failure it returns null (which the gRPC layer surfaces as NotFound), otherwise it returns the basket re-read from Redis.
- Anchor:
src/Basket.API/Repositories/RedisBasketRepository.cs:34
- Evidence:
var created = await _database.StringSetAsync(GetBasketKey(basket.BuyerId), json); if (!created) { ... return null; } return await GetBasketAsync(basket.BuyerId);
#8 — Basket items are only shown to authenticated users
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: authorization
- Statement: GetBasketItemsAsync returns the basket contents only when the user identity is authenticated; anonymous visitors get an empty collection.
- Anchor:
src/WebApp/Services/BasketState.cs:21
- Evidence:
(await GetUserAsync()).Identity?.IsAuthenticated == true ? await FetchBasketItemsAsync() : []
#9 — Checkout is idempotent via a request id
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: business_rule
- Statement: If the checkout info has no RequestId, a new GUID is generated; the order creation call sends it as the 'x-requestid' header so retried checkouts do not create duplicate orders.
- Anchor:
src/WebApp/Services/BasketState.cs:80
- Evidence:
if (checkoutInfo.RequestId == default) { checkoutInfo.RequestId = Guid.NewGuid(); } — sent as x-requestid in OrderingService.CreateOrder (src/WebApp/Services/OrderingService.cs:12-18)
#10 — Updating a basket that does not persist yields NotFound
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: business_rule
- Statement: When the repository returns null for an update, UpdateBasket throws gRPC NotFound with message 'Basket with buyer id {userId} does not exist'.
- Anchor:
src/Basket.API/Grpc/BasketService.cs:50
- Evidence:
if (response is null) { ThrowBasketDoesNotExist(userId); } ... StatusCode.NotFound, $"Basket with buyer id {userId} does not exist"
#11 — GetBasket is anonymous-tolerant: no identity yields an empty basket
- Source: 🕒 agent proposal (pending review)
- Entity: CustomerBasket
- Type: authorization
- Statement: The gRPC GetBasket operation is marked [AllowAnonymous]; when the caller has no user identity it returns an empty CustomerBasketResponse instead of an error. The basket is always resolved by the authenticated user id, never by a client-supplied id.
- Anchor:
src/Basket.API/Grpc/BasketService.cs:12
- Evidence:
[AllowAnonymous] ... var userId = context.GetUserIdentity(); if (string.IsNullOrEmpty(userId)) { return new(); }
Device (2)
#1 — NullReferenceException in FadeInAnimation
throw new NullReferenceException("Null Target property."
FadeOutAnimation (1)
#1 — NullReferenceException in FadeOutAnimation
throw new NullReferenceException("Null Target property."
FadeToAnimation (1)
#1 — NullReferenceException in FadeToAnimation
throw new NullReferenceException("Null Target property."
FixUriService (5)
#1 — Required on IntegrationEventLogEntry
[Required]
#2 — IntegrationEventLogEntry: State → EventStateEnum.NotPublished
State = EventStateEnum.NotPublished;
LocationService (1)
#1 — Required on LoginInputModel
[Required]
LoginViewModel (1)
#1 — Required on LoginViewModel
[Required]
OpenApiOptionsExtensions (1)
#1 — OrderingContext: guard (_currentTransaction != null)
if (_currentTransaction != null) return
#2 — OrderingContext: guard (transaction == null)
if (transaction == null) throw
#3 — Required on Order
[Required]
#4 — Paid and Shipped statuses trigger customer webhooks
var subscriptions = await retriever.GetSubscriptionsOfType(WebhookType.OrderShipped); ... await sender.SendAll(subscriptions, whook);
#5 — Stock rejection cancels the order
orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems);
#6 — Payment outcome decided by gateway simulation
if (options.CurrentValue.PaymentSucceeded) { orderPaymentIntegrationEvent = new OrderPaymentSucceededIntegrationEvent(@event.OrderId); } else { orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId); }
#7 — Grace period confirmed triggers AwaitingValidation transition
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId); await mediator.Send(command); // doc: grace period completed and order will not initially be cancelled; order process continues for validation
#8 — Stock deducted only after payment, non-blocking
//we're not blocking stock/inventory ... catalogItem?.RemoveStock(orderStockItem.Units);
#9 — Periodic grace period polling
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: process_schedule
- Statement: Grace period verification is a recurring background process: the service polls for confirmed grace-period orders in a loop, waiting CheckUpdateTime seconds (configurable) between each check, until shutdown is requested.
- Anchor:
src/OrderProcessor/Services/GracePeriodManagerService.cs:16
- Evidence:
var delayTime = TimeSpan.FromSeconds(_options.CheckUpdateTime); while (!stoppingToken.IsCancellationRequested) { await CheckConfirmedGracePeriodOrders(); await Task.Delay(delayTime, stoppingToken); }
#10 — Duplicate product lines are merged, keeping the higher discount
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: Adding an item for a product already in the order does not create a new line: units are accumulated on the existing line and the discount is replaced only when the new discount is higher than the current one.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:71
- Evidence:
if (discount > existingOrderForProduct.Discount) { existingOrderForProduct.SetNewDiscount(discount); } existingOrderForProduct.AddUnits(units);
#11 — Paid or shipped orders cannot be cancelled
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: state_transition
- Statement: Cancelling an order in Paid or Shipped status throws OrderingDomainException; any earlier status may be cancelled, raising OrderCancelledDomainEvent.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:142
- Evidence:
if (OrderStatus == OrderStatus.Paid || OrderStatus == OrderStatus.Shipped) { StatusChangeException(OrderStatus.Cancelled); }
#12 — Cancel, ship and create require a non-empty request ID
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: The orders API rejects cancel, ship and create requests whose x-requestid header is an empty GUID (idempotency key is mandatory), returning BadRequest.
- Anchor:
src/Ordering.API/Apis/OrdersApi.cs:22
- Evidence:
if (requestId == Guid.Empty) { return TypedResults.BadRequest("Empty GUID is not valid for request ID"); } (same guard in ShipOrderAsync lines 51-78 and CreateOrderAsync lines 118-168)
#13 — Users only see their own orders
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: The orders listing resolves the user identity from the authenticated context and queries only orders whose buyer identity GUID matches that user.
- Anchor:
src/Ordering.API/Application/Queries/OrderQueries.cs:37
- Evidence:
.Where(o => o.Buyer.IdentityGuid == userId) — userId supplied by OrdersApi.GetOrdersByUserAsync via services.IdentityService.GetUserIdentity() (OrdersApi.cs lines 93-98)
#14 — Set AwaitingValidation status only for existing orders
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); if (orderToUpdate == null) { return false; } orderToUpdate.SetAwaitingValidationStatus(); return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
#15 — Stock rejection auto-cancels order awaiting validation
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: When stock is rejected while the order is AwaitingValidation, the order is cancelled automatically and its description lists the product names without stock.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:155
- Evidence:
if (OrderStatus == OrderStatus.AwaitingValidation) { OrderStatus = OrderStatus.Cancelled; ... Description = $"The product items don't have stock: ({itemsStockRejectedDescription})."; }
#16 — Stock confirmed advances order to StockConfirmed
orderToUpdate.SetStockConfirmedStatus(); return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
#17 — Real-time order status notification per buyer
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: Order status change notifications are delivered only to the active subscriptions of the specific buyer (keyed by buyer identity); buyers without subscriptions produce no notification, and a subscription stops receiving updates once disposed.
- Anchor:
src/WebApp/Services/OrderStatus/OrderStatusNotificationService.cs:27
- Evidence:
return _subscriptionsByBuyerId.TryGetValue(buyerId, out var subscriptions) ? Task.WhenAll(subscriptions.Select(s => s.NotifyAsync())) : Task.CompletedTask;
#18 — Grace period eligibility criteria
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: temporal_condition
- Statement: An order is only considered past its grace period when the elapsed time since OrderDate is >= the configured GracePeriodTime (in minutes) AND the order status is still 'Submitted'. Only such orders are selected for confirmation.
- Anchor:
src/OrderProcessor/Services/GracePeriodManagerService.cs:63
- Evidence:
SQL: SELECT "Id" FROM ordering.orders WHERE CURRENT_TIMESTAMP - "OrderDate" >= @GracePeriodTime AND "OrderStatus" = 'Submitted'; parameter bound to TimeSpan.FromMinutes(_options.GracePeriodTime)
#19 — Ship command fails for non-existent order
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); if (orderToUpdate == null) { return false; } orderToUpdate.SetShippedStatus();
#20 — Order creation publishes OrderStarted integration event to clear the basket
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: Handling CreateOrderCommand first saves an OrderStartedIntegrationEvent for the buyer so the basket is cleaned, then builds the Order aggregate (address + payment data) and adds each item through AddOrderItem so aggregate invariants are enforced.
- Anchor:
src/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs:29
- Evidence:
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId); ... foreach (var item in message.OrderItems) { order.AddOrderItem(...); }
#21 — Order total is the sum of units times unit price
public decimal GetTotal() => _orderItems.Sum(o => o.Units * o.UnitPrice);
#22 — Basket items without product name are excluded from checkout order
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: validation
- Statement: When building order items from basket items at checkout, entries with an empty product name are skipped and do not become order items.
- Anchor:
src/ClientApp/ViewModels/CheckoutViewModel.cs:136
- Evidence:
if (!string.IsNullOrEmpty(basketItem.ProductName)) { orderItems.Add(new OrderItem { ... }); }
#23 — New order starts as Submitted and raises OrderStarted event
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: invariant
- Statement: Every order created through the aggregate constructor starts in Submitted status with OrderDate set to UTC now, and an OrderStartedDomainEvent is queued for dispatch after persistence.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:52
- Evidence:
OrderStatus = OrderStatus.Submitted; OrderDate = DateTime.UtcNow; ... AddOrderStartedDomainEvent(...)
#24 — Credit card number is masked before order creation and never logged
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: When creating an order, the API masks the card number keeping only the last 4 digits (rest padded with 'X') before building the CreateOrderCommand, and deliberately does not log the request because it contains the card number.
- Anchor:
src/Ordering.API/Apis/OrdersApi.cs:118
- Evidence:
var maskedCCNumber = request.CardNumber.Substring(request.CardNumber.Length - 4).PadLeft(request.CardNumber.Length, 'X'); //don't log the request as it has CC number
#25 — Stock validation: any missing item rejects the whole order
var hasStock = catalogItem.AvailableStock >= orderStockItem.Units; ... confirmedOrderStockItems.Any(c => !c.HasStock) ? new OrderStockRejectedIntegrationEvent(...) : new OrderStockConfirmedIntegrationEvent(...)
#26 — Order linked to buyer and payment only after verification
// When the Buyer and Buyer's payment method have been created or verified that they existed, then we can update the original Order with the BuyerId and PaymentId (foreign keys) ... orderToUpdate.SetPaymentMethodVerified(domainEvent.Buyer.Id, domainEvent.Payment.Id);
#27 — Cancel attempt on already-progressed order is reported as failure
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: In the client order service, a BadRequest response to the cancel request (order status changed before the user clicked cancel) makes CancelOrderAsync return false instead of throwing.
- Anchor:
src/ClientApp/Services/Order/OrderService.cs:83
- Evidence:
catch (HttpRequestExceptionEx ex) when (ex.HttpCode == HttpStatusCode.BadRequest) { return false; }
#28 — Grace period confirmation event per order
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: event_publication
- Statement: For each order whose grace period has elapsed, a GracePeriodConfirmedIntegrationEvent carrying the OrderId is published to the event bus, signaling that the order will not be cancelled during the grace window and may proceed.
- Anchor:
src/OrderProcessor/Services/GracePeriodManagerService.cs:44
- Evidence:
foreach (var orderId in orderIds) { var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId); await eventBus.PublishAsync(confirmGracePeriodEvent); }
#29 — Only out-of-stock items reported on rejection
var orderStockRejectedItems = @event.OrderStockItems.FindAll(c => !c.HasStock).Select(c => c.ProductId).ToList();
#30 — Payment success advances order to Paid
orderToUpdate.SetPaidStatus(); return await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
#31 — Client cancel action only offered while order is Submitted
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: In the client app, the cancel option is enabled only when the fetched order status equals 'Submitted' (case-insensitive); after a cancel attempt the flag is cleared.
- Anchor:
src/ClientApp/ViewModels/OrderDetailViewModel.cs:31
- Evidence:
IsSubmittedOrder = Order.OrderStatus.Equals("Submitted", StringComparison.OrdinalIgnoreCase);
#32 — StockConfirmed only reachable from AwaitingValidation
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: state_transition
- Statement: An order is marked StockConfirmed (with description 'All the items were confirmed with available stock.') only when it is currently AwaitingValidation.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:108
- Evidence:
if (OrderStatus == OrderStatus.AwaitingValidation) { ... OrderStatus = OrderStatus.StockConfirmed; }
#33 — AwaitingValidation only reachable from Submitted
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: state_transition
- Statement: An order moves to AwaitingValidation (raising OrderStatusChangedToAwaitingValidationDomainEvent with its items) only if it is currently Submitted; otherwise the call is silently ignored.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:99
- Evidence:
if (OrderStatus == OrderStatus.Submitted) { ... OrderStatus = OrderStatus.AwaitingValidation; }
#34 — Paid only reachable from StockConfirmed
if (OrderStatus == OrderStatus.StockConfirmed) { ... OrderStatus = OrderStatus.Paid; }
#35 — Payment failure triggers order cancellation
var command = new CancelOrderCommand(@event.OrderId); ... await mediator.Send(command);
#36 — Cancel command fails for non-existent order
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber); if (orderToUpdate == null) { return false; } orderToUpdate.SetCancelledStatus();
#37 — Every status change broadcast with buyer identity
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: Each order status transition (Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled) publishes an integration event enriched with the buyer's name and identity GUID, loaded from the order's buyer, so downstream consumers can notify the right customer.
- Anchor:
src/Ordering.API/Application/DomainEventHandlers/OrderCancelledDomainEventHandler.cs:23
- Evidence:
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value); var integrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid);
#38 — Only paid orders can be shipped
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: state_transition
- Statement: Shipping an order whose status is not Paid throws OrderingDomainException; on success the order becomes Shipped and OrderShippedDomainEvent is raised.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs:130
- Evidence:
if (OrderStatus != OrderStatus.Paid) { StatusChangeException(OrderStatus.Shipped); }
#39 — Outbox: integration events saved with transaction, published after
- Source: 🕒 agent proposal (pending review)
- Entity: Order
- Type: policy
- Statement: Ordering integration events are first persisted to an event log tied to the current database transaction and only published to the event bus afterwards, per transaction id; each event is marked InProgress before publishing, Published on success and Failed on error (transactional outbox guaranteeing no event without committed state).
- Anchor:
src/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs:13
- Evidence:
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId); await _eventBus.PublishAsync(logEvt.IntegrationEvent); await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId); ... MarkEventAsFailedAsync
#41 — Order: OrderStatus → OrderStatus.Submitted
OrderStatus = OrderStatus.Submitted;
#42 — Order: OrderStatus → OrderStatus.AwaitingValidation
OrderStatus = OrderStatus.AwaitingValidation;
#43 — Order: OrderStatus → OrderStatus.StockConfirmed
OrderStatus = OrderStatus.StockConfirmed;
#44 — Order: OrderStatus → OrderStatus.Paid
OrderStatus = OrderStatus.Paid;
#45 — Order: OrderStatus → OrderStatus.Shipped
OrderStatus = OrderStatus.Shipped;
#46 — Order: OrderStatus → OrderStatus.Cancelled
OrderStatus = OrderStatus.Cancelled;
OrderCheckout (1)
#1 — Required on OrderCheckout
[Required]
OrderDraft (1)
#1 — Order draft is built from basket items, one order item per basket item
- Source: 🕒 agent proposal (pending review)
- Entity: OrderDraft
- Type: business_rule
- Statement: CreateOrderDraftCommandHandler starts a new draft Order and adds one order item per basket item (Units = basket Quantity via ToOrderItemDTO, src/Ordering.API/Extensions/BasketItemExtensions.cs:13-21); the draft total is computed by the Order aggregate. Functional test asserts Total == sum(Quantity * UnitPrice) (tests/Ordering.FunctionalTests/OrderingApiTests.cs:188-205).
- Anchor:
src/Ordering.API/Application/Commands/CreateOrderDraftCommandHandler.cs:7
- Evidence:
var order = Order.NewDraft(); var orderItems = message.Items.Select(i => i.ToOrderItemDTO()); foreach (var item in orderItems) { order.AddOrderItem(...); } return Task.FromResult(OrderDraftDTO.FromOrder(order));
OrderItem (11)
#1 — OrderItem: guard (units <= 0)
if (units <= 0)
{
throw
#2 — OrderItem: guard (discount < 0)
if (discount < 0)
{
throw
#3 — Required on OrderItem
[Required]
#4 — OrderingDomainException in OrderItem
throw new OrderingDomainException("Invalid number of units"
#5 — OrderingDomainException in OrderItem
throw new OrderingDomainException("The total of order item is lower than applied discount"
#6 — OrderingDomainException in OrderItem
throw new OrderingDomainException("Discount is not valid"
#7 — OrderingDomainException in OrderItem
throw new OrderingDomainException("Invalid units"
#8 — New discount must be non-negative
if (discount < 0) { throw new OrderingDomainException("Discount is not valid"); }
#9 — Added units must be non-negative
if (units < 0) { throw new OrderingDomainException("Invalid units"); }
#10 — Discount cannot exceed order item total
- Source: 🕒 agent proposal (pending review)
- Entity: OrderItem
- Type: validation
- Statement: An order item cannot be created when the applied discount is greater than the line total (unitPrice * units); raises OrderingDomainException 'The total of order item is lower than applied discount'.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs:30
- Evidence:
if ((unitPrice * units) < discount) { throw new OrderingDomainException("The total of order item is lower than applied discount"); }
#11 — Order item units must be positive
if (units <= 0) { throw new OrderingDomainException("Invalid number of units"); }
OrderStatus (1)
#1 — Order lifecycle states
- Source: 🕒 agent proposal (pending review)
- Entity: OrderStatus
- Type: state_machine
- Statement: An order has exactly six lifecycle states: Submitted (1), AwaitingValidation (2), StockConfirmed (3), Paid (4), Shipped (5) and Cancelled (6). The happy path progresses Submitted -> AwaitingValidation -> StockConfirmed -> Paid -> Shipped; Cancelled is the terminal failure state.
- Anchor:
src/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs:5
- Evidence:
public enum OrderStatus { Submitted = 1, AwaitingValidation = 2, StockConfirmed = 3, Paid = 4, Shipped = 5, Cancelled = 6 }
PaymentMethod (4)
#1 — Required on PaymentMethod
[Required]
#2 — Payment method identity = card type + number + expiration
- Source: 🕒 agent proposal (pending review)
- Entity: PaymentMethod
- Type: invariant
- Statement: Two payment methods are considered the same if and only if card type id, card number and expiration date all match; alias, holder name and security number do not participate in identity.
- Anchor:
src/Ordering.Domain/AggregatesModel/BuyerAggregate/PaymentMethod.cs:37
- Evidence:
return _cardTypeId == cardTypeId && _cardNumber == cardNumber && _expiration == expiration;
#3 — Expired card rejected
if (expiration < DateTime.UtcNow) { throw new OrderingDomainException(nameof(expiration)); }
#4 — Payment method mandatory card data
_cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber));
ProductPriceChangedIntegrationEvent (2)
#1 — Event and catalog change persist in one local transaction (outbox)
- Source: 🕒 agent proposal (pending review)
- Entity: ProductPriceChangedIntegrationEvent
- Type: business_rule
- Statement: SaveEventAndCatalogContextChangesAsync persists the catalog DB change and the integration event log entry inside a single resilient local transaction, guaranteeing atomicity between the catalog update and the outgoing event (outbox pattern).
- Anchor:
src/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs:28
- Evidence:
await ResilientTransaction.New(catalogContext).ExecuteAsync(async () => { await catalogContext.SaveChangesAsync(); await integrationEventLogService.SaveEventAsync(evt, catalogContext.Database.CurrentTransaction); });
#2 — Event publish lifecycle: InProgress -> Published, Failed on error
- Source: 🕒 agent proposal (pending review)
- Entity: ProductPriceChangedIntegrationEvent
- Type: state_transition
- Statement: Publishing an integration event marks it InProgress in the event log, publishes to the event bus, then marks it Published; any exception marks the event Failed instead of propagating, so the event log always reflects the delivery outcome.
- Anchor:
src/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs:11
- Evidence:
await integrationEventLogService.MarkEventAsInProgressAsync(evt.Id); await eventBus.PublishAsync(evt); await integrationEventLogService.MarkEventAsPublishedAsync(evt.Id); ... catch { await integrationEventLogService.MarkEventAsFailedAsync(evt.Id); }
ProfileService (2)
#1 — ArgumentException in ProfileService
throw new ArgumentException("Invalid subject identifier"
#1 — InvalidOperationException in RabbitMQEventBus
throw new InvalidOperationException("RabbitMQ connection is not open"
RegisterViewModel (3)
#1 — Required on RegisterViewModel
[Required]
#2 — StringLength on RegisterViewModel
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
#3 — Compare on RegisterViewModel
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
RequestProvider (2)
#1 — Required on ResetPasswordViewModel
[Required]
#2 — StringLength on ResetPasswordViewModel
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
#3 — Compare on ResetPasswordViewModel
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
SecuritySchemeDefinitionsTransformer (1)
#1 — Required on SetPasswordViewModel
[Required]
#2 — StringLength on SetPasswordViewModel
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
#3 — Compare on SetPasswordViewModel
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
TestServerCallContext (1)
#1 — InvalidOperationException in TestServerCallContext
throw new InvalidOperationException("Response headers have already been written."
VerifyCodeViewModel (1)
#1 — Required on VerifyCodeViewModel
[Required]
VerifyPhoneNumberViewModel (1)
#1 — Required on VerifyPhoneNumberViewModel
[Required]
VisualElementExtensions (2)
#1 — Required on WebhookSubscription
[Required]
#2 — Grant URL must share origin with hook URL
- Source: 🕒 agent proposal (pending review)
- Entity: WebhookSubscription
- Type: validation
- Statement: A webhook grant URL is only accepted if it belongs to the same origin (same scheme, host and port) as the hook URL; otherwise the grant test fails immediately without any HTTP call.
- Anchor:
src/Webhooks.API/Services/GrantUrlTesterService.cs:7
- Evidence:
if (!CheckSameOrigin(urlHook, url)) { logger.LogWarning(...do not belong to same origin); return false; } — CheckSameOrigin compares Scheme, Port and Host (lines 38-46)
#3 — Unreachable grant URL cannot be granted
- Source: 🕒 agent proposal (pending review)
- Entity: WebhookSubscription
- Type: error_handling
- Statement: Any exception while sending the OPTIONS verification request (unreachable host, network failure, etc.) means the URL cannot be granted: the test returns false instead of propagating the error.
- Anchor:
src/Webhooks.API/Services/GrantUrlTesterService.cs:29
- Evidence:
catch (Exception ex) { logger.LogWarning("Exception {TypeName} when sending OPTIONS request. Url can't be granted.", ex.GetType().Name); return false; }
#4 — Grant URL verified via OPTIONS challenge with token echo
- Source: 🕒 agent proposal (pending review)
- Entity: WebhookSubscription
- Type: validation
- Statement: Before a webhook URL is granted, it must answer an HTTP OPTIONS request carrying the X-eshop-whtoken header: the grant succeeds only if the response has a success status code AND the token echoed back in the X-eshop-whtoken response header equals the expected token (null when no token was provided).
- Anchor:
src/Webhooks.API/Services/GrantUrlTesterService.cs:13
- Evidence:
var msg = new HttpRequestMessage(HttpMethod.Options, url); msg.Headers.Add("X-eshop-whtoken", token); ... return response.IsSuccessStatusCode && tokenReceived == tokenExpected; (tokenExpected = string.IsNullOrWhiteSpace(token) ? null : token)
Analysis depth
85 heuristic rules · 97 agent proposals (pending review) · 0 promoted
In Silex Pro, enrichment runs on its own — in batch and continuously, without depending on your agent.
Enums (7)
| Name |
Values |
Total |
| EasingType |
BounceIn, BounceOut, CubicIn, CubicInOut, CubicOut, Linear, ... (+5) |
11 |
| EventStateEnum |
NotPublished, InProgress, Published, PublishedFailed |
4 |
| FadeDirection |
Up, Down |
2 |
| GeolocationError |
PositionUnavailable, Unauthorized |
2 |
| OpenAITarget |
OpenAI, AzureOpenAI, AzureOpenAIExisting, AzureOpenAIExistingWithKey |
4 |
| Permission |
Unknown, Location, LocationAlways, LocationWhenInUse |
4 |
| WebhookType |
CatalogItemPriceChange, OrderShipped, OrderPaid |
3 |
Gaps found (77)
🔴 Critical: 56 | 🟡 Warnings: 21
| # |
Severity |
Type |
Entity/Rule |
Description |
| 1 |
🔴 error |
rule_orphan_entity |
CustomerBasket |
11 rules reference 'CustomerBasket' — examples: RULE_CLIENT_e61e5356f937, RULE_CLIENT_162e1a2f8a77, RULE_CLIENT_ccb30fba9219 (+8 more) |
| 2 |
🔴 error |
rule_orphan_entity |
Consent |
7 rules reference 'Consent' — examples: RULE_CLIENT_c0c6647cf1d4, RULE_CLIENT_0e8d6a71d853, RULE_CLIENT_1731691a9161 (+4 more) |
| 3 |
🔴 error |
rule_orphan_entity |
Buyer |
6 rules reference 'Buyer' — examples: RULE_0062, RULE_CLIENT_320831bbea45, RULE_CLIENT_6c71c5a06acd (+3 more) |
| 4 |
🔴 error |
rule_orphan_entity |
PaymentMethod |
4 rules reference 'PaymentMethod' — examples: RULE_0063, RULE_CLIENT_e68681c8768c, RULE_CLIENT_2d1258db6699 (+1 more) |
| 5 |
🔴 error |
rule_orphan_entity |
BasketState |
3 rules reference 'BasketState' — examples: RULE_0077, RULE_0078, auto_fill_000123 |
| 6 |
🔴 error |
rule_orphan_entity |
ChangePasswordViewModel |
3 rules reference 'ChangePasswordViewModel' — examples: RULE_0048, RULE_0050, RULE_0051 |
| 7 |
🔴 error |
rule_orphan_entity |
RegisterViewModel |
3 rules reference 'RegisterViewModel' — examples: RULE_0027, RULE_0029, RULE_0030 |
| 8 |
🔴 error |
rule_orphan_entity |
ResetPasswordViewModel |
3 rules reference 'ResetPasswordViewModel' — examples: RULE_0031, RULE_0033, RULE_0034 |
| 9 |
🔴 error |
rule_orphan_entity |
SetPasswordViewModel |
3 rules reference 'SetPasswordViewModel' — examples: RULE_0052, RULE_0053, RULE_0054 |
| 10 |
🔴 error |
rule_orphan_entity |
ProductPriceChangedIntegrationEvent |
2 rules reference 'ProductPriceChangedIntegrationEvent' — examples: RULE_CLIENT_7720ba968692, RULE_CLIENT_dd6b6e9c3a81 |
| 11 |
🔴 error |
rule_orphan_entity |
FadeInAnimation |
1 rules reference 'FadeInAnimation' — examples: RULE_0008 |
| 12 |
🔴 error |
rule_orphan_entity |
FadeOutAnimation |
1 rules reference 'FadeOutAnimation' — examples: RULE_0010 |
| 13 |
🔴 error |
rule_orphan_entity |
FadeToAnimation |
1 rules reference 'FadeToAnimation' — examples: RULE_0012 |
| 14 |
🔴 error |
rule_orphan_entity |
LoginInputModel |
1 rules reference 'LoginInputModel' — examples: RULE_0057 |
| 15 |
🔴 error |
rule_orphan_entity |
LoginViewModel |
1 rules reference 'LoginViewModel' — examples: RULE_0025 |
| 16 |
🔴 error |
rule_orphan_entity |
OrderDraft |
1 rules reference 'OrderDraft' — examples: RULE_CLIENT_46247ee484a3 |
| 17 |
🔴 error |
rule_orphan_entity |
OrderStatus |
1 rules reference 'OrderStatus' — examples: RULE_CLIENT_86a14b523547 |
| 18 |
🔴 error |
rule_orphan_entity |
ProfileService |
1 rules reference 'ProfileService' — examples: RULE_0059 |
| 19 |
🔴 error |
rule_orphan_entity |
RabbitMQEventBus |
1 rules reference 'RabbitMQEventBus' — examples: RULE_0023 |
| 20 |
🔴 error |
rule_orphan_entity |
TestServerCallContext |
1 rules reference 'TestServerCallContext' — examples: RULE_0083 |
| 21 |
🔴 error |
rule_orphan_entity |
VerifyPhoneNumberViewModel |
1 rules reference 'VerifyPhoneNumberViewModel' — examples: RULE_0055 |
| 22 |
🟡 warning |
entity_without_rules |
CatalogContextSeed |
Entidade 'CatalogContextSeed' sem regras de negócio no business_logic |
| 23 |
🟡 warning |
entity_without_rules |
CatalogSourceEntry |
Entidade 'CatalogSourceEntry' sem regras de negócio no business_logic |
| 24 |
🟡 warning |
entity_without_rules |
Campaign |
Entidade 'Campaign' sem regras de negócio no business_logic |
| 25 |
🟡 warning |
entity_without_rules |
CampaignItem |
Entidade 'CampaignItem' sem regras de negócio no business_logic |
| 26 |
🟡 warning |
entity_without_rules |
Address |
Entidade 'Address' sem regras de negócio no business_logic |
| 27 |
🟡 warning |
entity_without_rules |
PaymentInfo |
Entidade 'PaymentInfo' sem regras de negócio no business_logic |
| 28 |
🟡 warning |
entity_without_rules |
UserInfo |
Entidade 'UserInfo' sem regras de negócio no business_logic |
| 29 |
🟡 warning |
entity_without_rules |
ScopeViewModel |
Entidade 'ScopeViewModel' sem regras de negócio no business_logic |
| 30 |
🟡 warning |
entity_without_rules |
LoggedOutViewModel |
Entidade 'LoggedOutViewModel' sem regras de negócio no business_logic |
| 31 |
🟡 warning |
entity_without_rules |
GrantViewModel |
Entidade 'GrantViewModel' sem regras de negócio no business_logic |
| 32 |
🟡 warning |
entity_without_rules |
CreateOrderCommand |
Entidade 'CreateOrderCommand' sem regras de negócio no business_logic |
| 33 |
🟡 warning |
entity_without_rules |
OrderItemDTO |
Entidade 'OrderItemDTO' sem regras de negócio no business_logic |
| 34 |
🟡 warning |
entity_without_rules |
ValueObjectTest |
Entidade 'ValueObjectTest' sem regras de negócio no business_logic |
| 35 |
🟡 warning |
entity_without_rules |
ValueObjectA |
Entidade 'ValueObjectA' sem regras de negócio no business_logic |
| 36 |
🟡 warning |
entity_without_rules |
ValueObjectB |
Entidade 'ValueObjectB' sem regras de negócio no business_logic |
| 37 |
🟡 warning |
entity_without_rules |
ComplexObject |
Entidade 'ComplexObject' sem regras de negócio no business_logic |
| 38 |
🟡 warning |
fsm_without_workflow |
EventStateEnumStatus |
StateMachine 'EventStateEnumStatus' sem workflow associado no business_logic |
| 39 |
🟡 warning |
fsm_without_workflow |
IntegrationEventLogEntryStatus |
StateMachine 'IntegrationEventLogEntryStatus' sem workflow associado no business_logic |
| 40 |
🟡 warning |
fsm_without_workflow |
PermissionStatus |
StateMachine 'PermissionStatus' sem workflow associado no business_logic |
| 41 |
🟡 warning |
fsm_without_workflow |
OrderStatus |
StateMachine 'OrderStatus' sem workflow associado no business_logic |
| 42 |
🟡 warning |
fsm_without_bindings |
- |
StateMachines detectadas mas nenhum behavior_binding na UI |
Next steps
→ Validate the extracted rules with a domain expert
→ Fix the critical gaps before migrating
→ Review the warnings — some may be acceptable for an MVP
Report generated by Silex Arqueologia · Documentation · Support