Skip to content

Tighten Only Narrowing

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

Tighten-Only Narrowing

A live permit can be narrowed after it is issued. It can never be widened. Code: PermitStore.tighten(). HTTP: POST /api/permits/<id>/tighten. Dashboard: POST /api/tighten (cap only, {permit_id, cap_cents}); the dashboard state also reports effective_cap_cents.

Parameters (at least one)

Parameter Rule
cap_cents Positive int, strictly below the current effective cap. It may go below reserved + captured: existing holds stand, but nothing new can reserve
remove_merchants Non-empty list. Each entry must be in the effective allowlist. The result must not be empty (use the e-stop to kill a permit)
expiry Timezone-aware, sooner than the current effective expiry, and still in the future. HTTP expects ISO-8601
approval_threshold_cents Positive int below the current threshold. Any value counts as narrowing when no threshold is set
actor Non-empty string recorded on the receipt (default human)

Behaviour

  • Writes TIGHTEN with old → new values for each narrowed clause.
  • Cascades breadth-first to every descendant. Each child keeps the narrower of its own value and the new bound. Cascaded receipts carry cascade_from.
  • Rejected: a revoked permit, an expired permit, a widening, or a no-op. These give HTTP 400. An unknown permit gives 404.
  • Tightened values feed the gate and delegation. Block reasons are tightened_cap_exceeded and tightened_expiry_passed.

Observed limit (re-verified in-process at 9900817)

The release-time admission check in settlement/verifier.py compares against the granted permit.expiry, not the tightened one. In a local run, a hold authorized before a tightened expiry still captured after that expiry had passed, while new spends were blocked with tightened_expiry_passed. See Production Boundaries.

Tests: tests/test_tighten.py (19).

Clone this wiki locally