Skip to content

E Stop and Revocation

Marcus Richards edited this page Oct 7, 2026 · 2 revisions

E-Stop and Revocation

Two different verbs. They are not the same in code.

E-stop Revoke cascade
HTTP POST /api/permits/<id>/estop POST /api/permits/<id>/revoke-cascade
Code flow.estop() → permits.estop() flow.revoke_cascade() → permits.revoke_subtree()
Dashboard control Yes (POST /api/estop) Yes (POST /api/revoke-cascade, added with the delegation graph panel)
Revokes this permit Yes (E-STOP) Yes (E-STOP)
Revokes descendants No Yes (REVOKED_CASCADE each)
Voids in-flight holds This permit only Whole subtree
Releases unspent carves No Yes, post-order (CARVE_RELEASED)
Response revoked, voided_escrows, receipt adds cascaded_to (descendant ids)

⚠️ Code vs docstring

The server.py docstring says /estop "revokes the permit and every descendant (cascade)". The code does not do this. A local in-process check, repeated at 9900817, showed:

  • After an e-stop on the parent, the child stays revoked: false and its hold stays in flight.
  • New child spends are blocked (ancestor_revoked) by the lineage gate.
  • The child's existing escrow could still capture on release, because capture admission checks only the child permit, not its ancestors.

Use revoke-cascade to stop a delegation tree.

In-flight authorization races

  • The operation is tracked before the external authorize call.
  • After authorize returns, the permit is rechecked. If it was revoked or expired, the hold is voided (revoked_during_auth / expired_during_auth).
  • Escrow registration rechecks again.
  • E-stop also voids authorized-but-unregistered holds.
  • Capture admission rechecks revoked and expired just before capture.

Limits

  • A completed capture cannot be voided. It would need a refund, and refunds are out of scope.
  • E-stop/void on a registered escrow calls paypal.void() without catching a timeout (ReleaseVerifier.void). How that behaves on the sandbox rail: Unknown (not exercised here).

Tests: e-stop races in tests/test_authority_layer.py (test_p1_1_*). E-stop in tests/test_flow.py, tests/test_permit.py and tests/test_server.py::test_estop_voids_in_flight. Cascade in tests/test_delegation.py and tests/test_server.py::test_delegate_and_parent_revoke_cascades. Approval race: tests/test_approval.py::test_approval_fails_closed_when_parent_revoked (uses e-stop on the permit itself, with no children). No test at 9900817 covers an e-stop on a parent that has an in-flight child hold.

Clone this wiki locally