2026-08-24 00:21:51 -03:00
""" HUX-05 approval queue and the pre-side-effect gate.
Security obligations exercised : SO - 35 ( only a human surface with router or
relay trust decides ; ` ` decision . by ` ` is the asserted user ) , SO - 36 ( ` ` once ` `
is consumed exactly once ) , SO - 37 ( the gate must present the argument hash
recorded at request time ) , SO - 39 ( external side effects always need an
approval record ) , SO - 40 ( an exhausted budget blocks new approvals ) , SO - 42
( pending approvals expire 24 h after ` ` requested_at ` ` ) .
"""
from __future__ import annotations
import importlib
import json
import sys
from datetime import datetime , timedelta , timezone
from pathlib import Path
import pytest
ROOT = Path ( __file__ ) . resolve ( ) . parents [ 2 ]
FOUNDATION = ROOT / " dockerfiles " / " hermes-hux-foundation "
if str ( FOUNDATION ) not in sys . path :
sys . path . insert ( 0 , str ( FOUNDATION ) )
import hux # noqa: E402
from hux import audit , budgets , contracts , identity , policy , store # noqa: E402
from hux import events as hux_events # noqa: E402
from hux . server import build_router # noqa: E402
SCHEMAS = contracts . load_all ( )
HEADERS = { " X-Hermes-Tenant-Identity " : " slot-3 " , " X-Hux-Subject " : " usr_0123456789abcdef " , " X-Hux-Surface " : " chat " }
OTHER = { * * HEADERS , " X-Hux-Subject " : " usr_fedcba9876543210 " }
WORKER = { * * HEADERS , " X-Hux-Surface " : " worker " , " X-Hux-Trust " : " worker " , " X-Hux-Relay-Key " : " wk " }
RELAY = { * * HEADERS , " X-Hux-Surface " : " telegram " , " X-Hux-Trust " : " relay " , " X-Hux-Relay-Key " : " rk " }
ALL_ON = " , " . join ( card [ " flag " ] for card in contracts . load_flags ( ) [ " cards " ] )
T0 = datetime ( 2026 , 8 , 24 , 12 , 0 , tzinfo = timezone . utc )
CONV = " conv_0001abcd "
HASH = " sha256: " + " ab " * 32
OTHER_HASH = " sha256: " + " cd " * 32
@pytest.fixture
def router ( tmp_path ) :
return build_router ( tmp_path , { " HUX_FLAGS " : ALL_ON , " HUX_WORKER_KEY " : " wk " , " HUX_RELAY_KEY " : " rk " } )
@pytest.fixture
def frozen ( monkeypatch ) :
state = { " now " : T0 }
monkeypatch . setattr ( policy , " clock " , lambda : state [ " now " ] )
return state
@pytest.fixture
def events ( monkeypatch ) :
""" Stand-in for the events lane; records every emit call. """
seen : list [ dict ] = [ ]
monkeypatch . setattr ( hux_events , " emit " , lambda store , identity , conversation_id , kind , summary , * * extra : seen . append ( { " conversation_id " : conversation_id , " kind " : kind , " summary " : summary , * * extra } ) )
return seen
def call ( router , method , path , body = None , headers = HEADERS ) :
raw = b " " if body is None else json . dumps ( body ) . encode ( )
response = router . dispatch ( method , path , headers , raw )
return response . status , response . body
def valid ( record ) :
problems = contracts . validate_record ( record , SCHEMAS )
assert problems == [ ] , problems
return record
def request_body ( capability = " write_files " , external = False , run_id = " run_9f " , * * extra ) :
evidence = [ { " kind " : " tool_call " , " id " : " call-7 " , " hash " : HASH } ]
return { " run_id " : run_id , " conversation_id " : CONV , " capability " : capability ,
" request " : { " summary " : f " do { capability } " , " risk " : " low " , " external " : external , " evidence " : evidence , * * extra } }
def set_autonomy ( router , level ) :
status , body = call ( router , " PUT " , " /hux/v1/policy " , { " scope " : { " level " : " global " } , " autonomy " : level } )
assert status == 200 , body
# --- creation resolves against the matrix ------------------------------------------
def test_safe_policy_auto_allows_reads_and_queues_mutations ( router , frozen , events ) :
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " read_files " ) , WORKER )
assert status == 201 and valid ( body ) [ " status " ] == " approved "
assert body [ " decision " ] == { " choice " : " once " , " by " : { " type " : " system " , " id " : " policy " } , " at " : policy . iso ( T0 ) }
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " write_files " ) , WORKER )
assert status == 201 and valid ( body ) [ " status " ] == " pending " and " decision " not in body
assert body [ " expires_at " ] == policy . iso ( T0 + timedelta ( hours = 24 ) )
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " network " ) , WORKER )
assert status == 201 and valid ( body ) [ " status " ] == " denied " and body [ " decision " ] [ " choice " ] == " deny "
assert [ e [ " kind " ] for e in events ] == [ " approval.resolved " , " approval.requested " , " approval.resolved " ]
assert events [ 0 ] [ " evidence " ] == [ { " kind " : " approval " , " id " : events [ 0 ] [ " evidence " ] [ 0 ] [ " id " ] } ]
def test_external_side_effects_always_need_a_human_regardless_of_autonomy ( router , events ) :
set_autonomy ( router , " autonomous " )
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " send_message " ) , WORKER )
assert status == 201 and body [ " status " ] == " approved "
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " send_message " , external = True ) , WORKER )
assert status == 201 and body [ " status " ] == " pending "
for always_ask in ( " deploy " , " external_side_effect " ) :
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( always_ask ) , WORKER )
assert body [ " status " ] == " pending " , always_ask
call ( router , " PUT " , " /hux/v1/policy " , { " scope " : { " level " : " global " } , " autonomy " : " autonomous " , " grants " : [ { " capability " : " network " , " decision " : " deny " } ] } )
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " network " , external = True ) , WORKER )
assert body [ " status " ] == " denied " , " deny beats external ask "
def test_idempotency_key_returns_the_original ( router ) :
key = { " Idempotency-Key " : " run_9f:approval:call-7 " }
status , first = call ( router , " POST " , " /hux/v1/approvals " , request_body ( ) , { * * WORKER , * * key } )
assert status == 201 and first [ " idempotency_key " ] == " run_9f:approval:call-7 "
status , again = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " shell " ) , { * * WORKER , * * key } )
assert ( status , again ) == ( 200 , first )
status , other = call ( router , " POST " , " /hux/v1/approvals " , request_body ( ) , { * * WORKER , " Idempotency-Key " : " run_9f:approval:call-8 " } )
assert status == 201 and other [ " id " ] != first [ " id " ]
assert call ( router , " GET " , f " /hux/v1/approvals/ { first [ ' id ' ] } " ) [ 1 ] == first
@pytest.mark.parametrize ( " body,status " , [
( None , 400 ) , ( [ ] , 400 ) , ( { " conversation_id " : " nope " , " capability " : " shell " } , 400 ) ,
( { " conversation_id " : CONV , " capability " : " teleport " } , 400 ) ,
( { " conversation_id " : CONV , " capability " : " shell " , " run_id " : " r " } , 400 ) ,
( { " conversation_id " : CONV , " capability " : " shell " , " run_id " : " r " , " request " : { " summary " : " " , " risk " : " low " } } , 400 ) ,
( { " conversation_id " : CONV , " capability " : " shell " , " run_id " : " r " , " request " : { " summary " : " x " , " risk " : " silly " } } , 400 ) ,
] )
def test_create_rejects_bad_bodies ( router , body , status ) :
got , error = call ( router , " POST " , " /hux/v1/approvals " , body , WORKER )
assert got == status and valid ( error ) [ " code " ] == " invalid "
# --- deciding -----------------------------------------------------------------------
def pending ( router , capability = " write_files " , * * kw ) :
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( capability , * * kw ) , WORKER )
assert status == 201 and body [ " status " ] == " pending " , body
return body
def test_only_humans_on_router_or_relay_decide ( router , tmp_path ) :
record = pending ( router )
status , error = call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } , WORKER )
assert ( status , error [ " code " ] ) == ( 403 , " forbidden " )
api = { * * HEADERS , " X-Hux-Surface " : " api " }
assert call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } , api ) [ 0 ] == 403
status , body = call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } , RELAY )
assert status == 200 and valid ( body ) [ " status " ] == " approved "
assert body [ " decision " ] [ " by " ] == { " type " : " user " , " id " : HEADERS [ " X-Hux-Subject " ] }
rows = audit . recent ( store . TenantStore ( tmp_path , identity . resolve ( HEADERS , { } ) ) )
assert [ ( r [ " action " ] , r [ " outcome " ] ) for r in rows if r [ " action " ] == " approvals.decide " ] == [ ( " approvals.decide " , " deny " ) ] * 2 + [ ( " approvals.decide " , " allow " ) ]
def test_terminal_states_never_change_again ( router , events ) :
record = pending ( router )
path = f " /hux/v1/approvals/ { record [ ' id ' ] } "
assert call ( router , " POST " , path , { " choice " : " maybe " } ) [ 0 ] == 400
assert call ( router , " POST " , path , [ ] ) [ 0 ] == 400
status , body = call ( router , " POST " , path , { " choice " : " deny " } )
assert status == 200 and body [ " status " ] == " denied " and body [ " decision " ] [ " choice " ] == " deny "
status , error = call ( router , " POST " , path , { " choice " : " once " } )
assert ( status , error [ " code " ] ) == ( 409 , " conflict " )
assert call ( router , " GET " , path ) [ 1 ] == body
assert events [ - 1 ] [ " kind " ] == " approval.resolved " and " denied " in events [ - 1 ] [ " summary " ]
def test_pending_approvals_expire_after_24h ( router , frozen ) :
record = pending ( router )
frozen [ " now " ] = T0 + timedelta ( hours = 24 )
status , body = call ( router , " GET " , f " /hux/v1/approvals/ { record [ ' id ' ] } " )
assert status == 200 and valid ( body ) [ " status " ] == " expired "
assert call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } ) [ 0 ] == 409
assert call ( router , " GET " , " /hux/v1/approvals?status=expired " ) [ 1 ] [ " items " ] == [ body ]
assert call ( router , " GET " , " /hux/v1/approvals?status=pending " ) [ 1 ] [ " items " ] == [ ]
def test_session_and_always_create_grants_at_their_scope ( router , frozen ) :
record = pending ( router , " shell " )
status , body = call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " session " } )
assert status == 200 and body [ " decision " ] [ " choice " ] == " session "
conv_policy = call ( router , " GET " , f " /hux/v1/policy?scope=conversation&scope_id= { CONV } " ) [ 1 ]
assert valid ( conv_policy ) [ " scope " ] == { " level " : " conversation " , " scope_id " : CONV }
assert conv_policy [ " grants " ] == [ { " capability " : " shell " , " decision " : " allow " , " expires_at " : policy . iso ( T0 + policy . SESSION_TTL ) , " granted_by " : { " type " : " user " , " id " : HEADERS [ " X-Hux-Subject " ] } } ]
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " shell " ) , WORKER )
assert body [ " status " ] == " approved " , " later shell requests in this conversation auto-approve "
assert call ( router , " POST " , " /hux/v1/approvals " , request_body ( " write_files " ) , WORKER ) [ 1 ] [ " status " ] == " pending "
record = pending ( router , " write_files " )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " always " } )
global_policy = call ( router , " GET " , " /hux/v1/policy " ) [ 1 ]
assert [ g [ " capability " ] for g in global_policy [ " grants " ] ] == [ " write_files " ]
assert global_policy [ " grants " ] [ 0 ] [ " expires_at " ] == policy . iso ( T0 + policy . ALWAYS_TTL )
frozen [ " now " ] = T0 + timedelta ( hours = 25 )
record = pending ( router , " shell " , run_id = " run_other " )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " session " } )
conv_policy = call ( router , " GET " , f " /hux/v1/policy?scope=conversation&scope_id= { CONV } " ) [ 1 ]
assert len ( conv_policy [ " grants " ] ) == 1 and conv_policy [ " revision " ] == 2 , " a repeat grant replaces, never duplicates "
def test_list_filters_and_orders_by_request_time ( router , frozen ) :
first = pending ( router , " shell " )
frozen [ " now " ] = T0 + timedelta ( seconds = 5 )
second = pending ( router , " write_files " )
status , body = call ( router , " GET " , " /hux/v1/approvals " )
assert status == 200 and [ i [ " id " ] for i in body [ " items " ] ] == [ first [ " id " ] , second [ " id " ] ] and body [ " next " ] is None
assert call ( router , " GET " , " /hux/v1/approvals?status=pending " ) [ 1 ] [ " items " ] == body [ " items " ]
assert call ( router , " GET " , " /hux/v1/approvals?status=approved " ) [ 1 ] [ " items " ] == [ ]
assert call ( router , " GET " , " /hux/v1/approvals?status=bogus " ) [ 0 ] == 400
for item in body [ " items " ] :
valid ( item )
def test_second_subject_cannot_see_or_decide ( router ) :
record = pending ( router )
assert call ( router , " GET " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , headers = OTHER ) [ 0 ] == 404
assert call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } , OTHER ) [ 0 ] == 404
assert call ( router , " GET " , " /hux/v1/approvals " , headers = OTHER ) [ 1 ] [ " items " ] == [ ]
assert call ( router , " GET " , " /hux/v1/approvals/apr_doesnotexist " ) [ 0 ] == 404
assert call ( router , " GET " , " /hux/v1/approvals/.. " ) [ 0 ] == 400
# --- budgets block new approvals -------------------------------------------------------
def test_exhausted_budget_blocks_new_approvals ( router , events ) :
call ( router , " PUT " , " /hux/v1/policy " , { " scope " : { " level " : " global " } , " autonomy " : " safe " , " budgets " : { " tool_calls_per_run " : 2 } } )
status , body = call ( router , " POST " , " /hux/v1/runs/run_9f/budget " , { " conversation_id " : CONV , " tool_calls " : 2 } , WORKER )
assert status == 200 and valid ( body ) [ " exhausted " ] == [ " tool_calls_per_run " ]
status , error = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " read_files " ) , WORKER )
assert ( status , error [ " code " ] ) == ( 429 , " budget_exhausted " ) and error [ " details " ] == [ " tool_calls_per_run " ]
assert [ e [ " kind " ] for e in events ] == [ " budget.exhausted " , " budget.exhausted " ]
status , body = call ( router , " POST " , " /hux/v1/approvals " , request_body ( " read_files " , run_id = " run_fresh " ) , WORKER )
assert status == 201 , " another run keeps its own budget "
# --- gate --------------------------------------------------------------------------
def gate ( router , run_id = " run_9f " , capability = " write_files " , argument_hash = HASH , external = False , * * extra ) :
return call ( router , " POST " , f " /hux/v1/runs/ { run_id } /gate " , { " capability " : capability , " argument_hash " : argument_hash , " external " : external , * * extra } , WORKER )
def test_gate_blocks_before_approval_and_releases_once_exactly_once ( router , events , tmp_path ) :
status , body = gate ( router )
assert status == 200 and body == { " proceed " : False , " reason " : " no approval for this run and capability " }
record = pending ( router )
assert gate ( router ) [ 1 ] [ " proceed " ] is False and " pending " in gate ( router ) [ 1 ] [ " reason " ]
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } )
status , wrong = gate ( router , argument_hash = OTHER_HASH )
assert wrong [ " proceed " ] is False and " different arguments " in wrong [ " reason " ] , " TOCTOU (SO-37) "
status , body = gate ( router )
assert body == { " proceed " : True , " approval_id " : record [ " id " ] , " reason " : " released " }
status , again = gate ( router )
assert again [ " proceed " ] is False and " already consumed " in again [ " reason " ] , " once is once (SO-36) "
kinds = [ e [ " kind " ] for e in events ]
assert kinds . count ( " side_effect.released " ) == 1 and kinds . count ( " side_effect.blocked " ) == 4 , " the first block predates any approval, so no conversation is known "
released = next ( e for e in events if e [ " kind " ] == " side_effect.released " )
assert released [ " evidence " ] == [ { " kind " : " approval " , " id " : record [ " id " ] } ] and released [ " run_id " ] == " run_9f "
served = call ( router , " GET " , f " /hux/v1/approvals/ { record [ ' id ' ] } " ) [ 1 ]
assert valid ( served ) and " _consumed_at " not in served
rows = audit . recent ( store . TenantStore ( tmp_path , identity . resolve ( HEADERS , { } ) ) )
assert [ r [ " outcome " ] for r in rows if r [ " action " ] == " gate.check " ] == [ " deny " , " deny " , " deny " , " deny " , " allow " , " deny " ]
def test_gate_session_is_reusable_within_the_conversation ( router ) :
record = pending ( router , " shell " )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " session " } )
assert gate ( router , capability = " shell " ) [ 1 ] [ " proceed " ] is True
assert gate ( router , capability = " shell " , argument_hash = OTHER_HASH ) [ 1 ] [ " proceed " ] is True
2026-08-24 00:45:13 -03:00
assert gate ( router , run_id = " run_later " , capability = " shell " ) [ 1 ] [ " proceed " ] is True , " unknown run: the approval ' s conversation applies (F4) "
call ( router , " POST " , " /hux/v1/runs/run_elsewhere/budget " , { " conversation_id " : " conv_elsewhere01 " , " tokens " : 1 } , WORKER )
status , body = gate ( router , run_id = " run_elsewhere " , capability = " shell " , conversation_id = CONV )
assert body [ " proceed " ] is False , " the run ' s own conversation wins over the body ' s claim (F4) "
2026-08-24 00:21:51 -03:00
assert gate ( router , capability = " write_files " ) [ 1 ] [ " proceed " ] is False
def test_gate_requires_external_approvals_for_external_effects_and_respects_expiry ( router , frozen ) :
record = pending ( router , " send_message " )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } )
status , body = gate ( router , capability = " send_message " , external = True )
assert body [ " proceed " ] is False and " not requested as external " in body [ " reason " ]
record = pending ( router , " send_message " , external = True )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } )
frozen [ " now " ] = T0 + timedelta ( hours = 25 )
status , body = gate ( router , capability = " send_message " , external = True )
assert body [ " proceed " ] is False and " expired " in body [ " reason " ]
def test_gate_rejects_bad_bodies_and_other_tenants ( router ) :
assert gate ( router , capability = " teleport " ) [ 0 ] == 400
assert gate ( router , argument_hash = " md5:zz " ) [ 0 ] == 400
2026-08-24 00:45:13 -03:00
assert gate ( router , conversation_id = " nope " ) [ 0 ] == 200 , " the body ' s conversation_id is ignored, never validated (F4) "
2026-08-24 00:21:51 -03:00
assert call ( router , " POST " , " /hux/v1/runs/run_9f/gate " , [ ] , WORKER ) [ 0 ] == 400
assert call ( router , " POST " , f " /hux/v1/runs/ { ' r ' * 121 } /gate " , { " capability " : " shell " , " argument_hash " : HASH } , WORKER ) [ 0 ] == 400
record = pending ( router )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " once " } )
status , body = call ( router , " POST " , " /hux/v1/runs/run_9f/gate " , { " capability " : " write_files " , " argument_hash " : HASH } , OTHER )
assert status == 200 and body [ " proceed " ] is False
def test_events_module_absence_is_tolerated ( router , monkeypatch ) :
monkeypatch . setitem ( sys . modules , " hux.events " , None )
monkeypatch . delattr ( hux , " events " )
with pytest . raises ( ImportError ) :
importlib . import_module ( " hux.events " )
assert call ( router , " POST " , " /hux/v1/approvals " , request_body ( " read_files " ) , WORKER ) [ 0 ] == 201
policy . emit ( None , None , None , " x " , " no conversation means no event " )
assert budgets . hashes_of ( { " request " : { } } ) == set ( )
2026-08-24 00:45:13 -03:00
# --- F4 / F6: external effects, run conversation and budgets at the gate --------------
def test_external_effects_release_only_for_the_same_run_and_arguments ( router ) :
""" F4 (high) / SO-39: a session or always approval for an external effect never releases another run or other arguments. """
record = pending ( router , " send_message " , external = True )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " session " } )
assert gate ( router , capability = " send_message " , external = True ) [ 1 ] [ " proceed " ] is True
assert gate ( router , capability = " send_message " , external = True ) [ 1 ] [ " proceed " ] is True , " session is reusable for the same run and hash "
status , body = gate ( router , capability = " send_message " , external = True , argument_hash = OTHER_HASH )
assert body [ " proceed " ] is False and " different arguments " in body [ " reason " ]
status , body = gate ( router , run_id = " run_z " , capability = " send_message " , external = True , conversation_id = CONV )
assert body [ " proceed " ] is False and body [ " reason " ] == " no approval for this run and capability "
assert gate ( router , run_id = " run_z " , capability = " send_message " , external = False ) [ 1 ] [ " proceed " ] is False , " an external approval is not a general one "
always = pending ( router , " send_message " , external = True , run_id = " run_y " )
call ( router , " POST " , f " /hux/v1/approvals/ { always [ ' id ' ] } " , { " choice " : " always " } )
assert gate ( router , run_id = " run_y " , capability = " send_message " , external = True ) [ 1 ] [ " proceed " ] is True
assert gate ( router , run_id = " run_w " , capability = " send_message " , external = True ) [ 1 ] [ " proceed " ] is False , " always never spans runs for external effects "
def test_once_approvals_name_exactly_one_tool_call ( router ) :
""" F4 / SO-37: a request carrying two tool_call hashes is refused so a once approval designates one hash. """
body = request_body ( " shell " )
body [ " request " ] [ " evidence " ] = [ { " kind " : " tool_call " , " id " : " a " , " hash " : HASH } , { " kind " : " tool_call " , " id " : " b " , " hash " : OTHER_HASH } ]
status , error = call ( router , " POST " , " /hux/v1/approvals " , body , WORKER )
assert ( status , error [ " code " ] ) == ( 400 , " invalid " ) and " exactly one tool_call " in error [ " message " ]
body [ " request " ] [ " evidence " ] = [ { " kind " : " tool_call " , " id " : " a " , " hash " : HASH } , { " kind " : " file " , " id " : " notes.md " } ]
assert call ( router , " POST " , " /hux/v1/approvals " , body , WORKER ) [ 0 ] == 201 , " other evidence kinds do not count "
body [ " request " ] [ " evidence " ] = " not-a-list "
assert call ( router , " POST " , " /hux/v1/approvals " , body , WORKER ) [ 0 ] == 400 , " the contract still rejects it, without a 500 "
def test_gate_refuses_when_the_run_budget_is_exhausted ( router , events ) :
""" F6 (medium) / SO-40: an approved effect still does not proceed once any limit is spent. """
record = pending ( router , " shell " )
call ( router , " POST " , f " /hux/v1/approvals/ { record [ ' id ' ] } " , { " choice " : " session " } )
assert gate ( router , capability = " shell " ) [ 1 ] [ " proceed " ] is True
call ( router , " PUT " , " /hux/v1/policy " , { " scope " : { " level " : " conversation " , " scope_id " : CONV } , " autonomy " : " safe " , " budgets " : { " tool_calls_per_run " : 2 } } )
call ( router , " POST " , " /hux/v1/runs/run_9f/budget " , { " conversation_id " : CONV , " tool_calls " : 2 } , WORKER )
status , body = gate ( router , capability = " shell " )
assert body == { " proceed " : False , " reason " : " budget_exhausted " , " exhausted " : [ " tool_calls_per_run " ] }
assert events [ - 1 ] [ " kind " ] == " budget.exhausted " and events [ - 1 ] [ " run_id " ] == " run_9f " and events [ - 1 ] [ " conversation_id " ] == CONV
assert gate ( router , run_id = " run_fresh " , capability = " shell " ) [ 1 ] [ " proceed " ] is True , " the budget is per run "
assert budgets . run_conversation ( router_store ( router ) , " run_nobody " ) is None
def router_store ( router ) :
return store . TenantStore ( router . data_root , identity . resolve ( HEADERS , { } ) )