{
  "filename": "orders/cancellation.py",
  "rule": "- name: tenant-scoped-lock\n  where: \"orders/cancellation.py\"\n  ensure: >-\n    Before mutating an order, the locked queryset must filter tenant_id by actor.tenant_id. Matching owner_id without the tenant restriction is insufficient.",
  "corrected": "from django.db import transaction\nfrom .models import Order\n\ndef cancel_order(actor, order_id):\n    with transaction.atomic():\n        order = (Order.objects.select_for_update()\n                 .filter(pk=order_id, tenant_id=actor.tenant_id,\n                         owner_id=actor.pk).first())\n        if order is None:\n            raise PermissionError(\"Order not available\")\n        if order.status != \"pending\":\n            raise ValueError(\"Only pending orders can be cancelled\")\n        order.status = \"cancelled\"\n        order.save(update_fields=[\"status\"])\n        return order.pk\n",
  "broken": "from django.db import transaction\nfrom .models import Order\n\ndef cancel_order(actor, order_id):\n    with transaction.atomic():\n        order = (Order.objects.select_for_update()\n                 .filter(pk=order_id, owner_id=actor.pk).first())\n        if order is None:\n            raise PermissionError(\"Order not available\")\n        if order.status != \"pending\":\n            raise ValueError(\"Only pending orders can be cancelled\")\n        order.status = \"cancelled\"\n        order.save(update_fields=[\"status\"])\n        return order.pk\n",
  "regression": [
    "tenant_id=actor.tenant_id,\n                         ",
    ""
  ],
  "test_scenarios": [
    [
      "Owned pending order in tenant",
      "Row changes to cancelled"
    ],
    [
      "Owned id in another tenant",
      "PermissionError and unchanged row"
    ],
    [
      "Paid or shipped order",
      "ValueError and unchanged row"
    ],
    [
      "Concurrent paid/cancelled state change",
      "Transactions serialize on the locked row"
    ]
  ],
  "test_source": null,
  "test_command": "python manage.py test orders.tests.test_cancellation"
}