Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
153 lines
6.6 KiB
Python
153 lines
6.6 KiB
Python
"""Case study: E-commerce orders.
|
|
|
|
Architectural pattern demonstrated: **compound state machine + reactions**.
|
|
An order moves through cart -> placed -> paid -> shipped -> delivered, with
|
|
refund inverting the state from delivered back to refunded. Each transition
|
|
has invariants (placed orders cannot be modified; only paid orders can ship).
|
|
The state-machine validator + reactions handle the bookkeeping; the handler
|
|
just describes the user intent (place / pay / ship / refund).
|
|
|
|
Object types: customer, product, order, order_item, audit_log
|
|
Key invariants:
|
|
- order status follows: cart -> placed -> paid -> shipped -> delivered, or any -> cancelled, delivered -> refunded
|
|
- only the order owner can place / cancel; only admin can ship; only paid orders can ship
|
|
- inventory decremented on payment (via reaction)
|
|
- refund inverts inventory (via reaction)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pedo.core.models import (
|
|
AccessContext, DataObject, ObjectType, Operation,
|
|
PermissionRule, PrivilegeType, ReactionDeclaration,
|
|
Relationship, RelationshipAction,
|
|
)
|
|
from pedo.core.store import ObjectStore
|
|
|
|
|
|
ORDER_TRANSITIONS = {
|
|
None: ["cart"],
|
|
"cart": ["placed", "cancelled"],
|
|
"placed": ["paid", "cancelled"],
|
|
"paid": ["shipped", "refunded"], # paid can be refunded
|
|
"shipped": ["delivered"],
|
|
"delivered": ["refunded"], # delivered can be refunded
|
|
"cancelled": [],
|
|
"refunded": [],
|
|
}
|
|
|
|
|
|
def validate_order_status(proposed, existing, accessor, store):
|
|
new_status = proposed.content.get("status")
|
|
if existing is None:
|
|
if new_status not in ("cart", None):
|
|
return f"New orders must start as 'cart', got {new_status!r}"
|
|
return True
|
|
old_status = existing.content.get("status")
|
|
if new_status and new_status != old_status:
|
|
valid = ORDER_TRANSITIONS.get(old_status, [])
|
|
if new_status not in valid:
|
|
return f"Invalid order transition {old_status} -> {new_status}; valid: {valid}"
|
|
return True
|
|
|
|
|
|
def validate_only_paid_can_ship(proposed, existing, accessor, store):
|
|
if existing is None:
|
|
return True
|
|
if proposed.content.get("status") == "shipped":
|
|
if existing.content.get("status") != "paid":
|
|
return f"Only paid orders can ship; current status is {existing.content.get('status')}"
|
|
return True
|
|
|
|
|
|
def adjust_inventory_on_state_change(event, store):
|
|
"""Reaction: decrement inventory on 'paid', increment on 'refunded'."""
|
|
sys_ctx = AccessContext(user_id="system", role="system", org_id=event["object_org"])
|
|
new_status = event["object_content"].get("status")
|
|
if new_status not in ("paid", "refunded"):
|
|
return
|
|
direction = -1 if new_status == "paid" else +1
|
|
# Find order_items for this order, adjust each product's stock
|
|
order_id = event["object_id"]
|
|
items = store.raw_query("order_item")
|
|
for item in items:
|
|
if item.content.get("order_id") != order_id:
|
|
continue
|
|
product_id = item.content.get("product_id")
|
|
qty = item.content.get("quantity", 0)
|
|
product = store.raw_read(product_id)
|
|
if product is None:
|
|
continue
|
|
current = product.content.get("stock", 0)
|
|
store.update(product_id, {"stock": current + direction * qty},
|
|
sys_ctx, _reaction_depth=event["depth"])
|
|
|
|
|
|
def register_ecommerce_types(store: ObjectStore) -> None:
|
|
store.register_reaction_handler("adjust_inventory_on_state_change",
|
|
adjust_inventory_on_state_change)
|
|
|
|
store.register_type(ObjectType(
|
|
name="customer",
|
|
fields={"name": "str", "email": "str"},
|
|
permission_rules=[
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"is_owner": True}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "admin"}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "system"}),
|
|
],
|
|
default_policy=Operation.DENY,
|
|
))
|
|
|
|
store.register_type(ObjectType(
|
|
name="product",
|
|
fields={"name": "str", "price": "int", "stock": "int"},
|
|
permission_rules=[
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {}), # public catalog
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.SELECT, {}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {"role": "admin"}),
|
|
# Stock writes only via reactions (system role)
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"role": "system"}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"role": "admin"}),
|
|
],
|
|
default_policy=Operation.DENY,
|
|
))
|
|
|
|
store.register_type(ObjectType(
|
|
name="order",
|
|
fields={"status": "str", "total": "int"},
|
|
permission_rules=[
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"is_owner": True}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "admin"}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "system"}),
|
|
# Owner can update during cart/place/cancel; admin can ship; system runs reactions.
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"is_owner": True}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"role": "admin"}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.WRITE, {"role": "system"}),
|
|
],
|
|
validators=[validate_order_status, validate_only_paid_can_ship],
|
|
reactions=[
|
|
ReactionDeclaration(event="after_update:status",
|
|
handler="adjust_inventory_on_state_change"),
|
|
],
|
|
default_policy=Operation.DENY,
|
|
))
|
|
|
|
store.register_type(ObjectType(
|
|
name="order_item",
|
|
fields={"order_id": "str", "product_id": "str", "quantity": "int", "unit_price": "int"},
|
|
permission_rules=[
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.INSERT, {}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"is_owner": True}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "admin"}),
|
|
PermissionRule(Operation.ACCEPT, PrivilegeType.READ, {"role": "system"}),
|
|
],
|
|
relationships=[
|
|
Relationship(name="order", target_type="order",
|
|
on_delete=RelationshipAction.CASCADE),
|
|
Relationship(name="product", target_type="product",
|
|
on_delete=RelationshipAction.RESTRICT),
|
|
],
|
|
default_policy=Operation.DENY,
|
|
))
|