budget.mojo (4063B)
1 """Shared absolute request budget and frozen timeout/retry/circuit constants 2 (ADR-0010 D21; policy table 3 ``docs/spec/hyf_http_v1/policy/hyf_http_v1_policy.v1.toml``). 4 5 :class:`Budget` is one monotonic absolute cap with no per-stage reset. The 6 named constants freeze the Codex-selected local bounds so the dependent slices 7 (H092 timeout derivation, C006/C007 transport, C043 circuit) consume exact 8 values instead of restating them; they are not production SLOs. 9 """ 10 11 comptime CONNECT_READ_CAP_MS: Int = 1000 12 comptime OUTPUT_BLOCK_CAP_MS: Int = 1000 13 comptime MAX_WIRE_ATTEMPTS: Int = 4 14 comptime MAX_RETRIES_PER_CALL: Int = 1 15 comptime RETRY_BACKOFF_MS: Int = 100 16 comptime CIRCUIT_OPEN_THRESHOLD: Int = 3 17 comptime CIRCUIT_COOLDOWN_MS: Int = 30000 18 comptime CIRCUIT_HALF_OPEN_PROBES: Int = 1 19 20 21 @fieldwise_init 22 struct Budget(Copyable, Movable): 23 var start_monotonic_ns: Int 24 var cap_ms: Int 25 26 27 def budget(deadline_ms: Int, server_cap_ms: Int) -> Budget: 28 var cap = server_cap_ms 29 if deadline_ms > 0 and deadline_ms < cap: 30 cap = deadline_ms 31 if cap <= 0: 32 cap = server_cap_ms 33 return Budget(start_monotonic_ns=0, cap_ms=cap) 34 35 36 def budget_from_clock( 37 deadline_ms: Int, server_cap_ms: Int, now_ns: Int 38 ) -> Budget: 39 var value = budget(deadline_ms, server_cap_ms) 40 value.start_monotonic_ns = now_ns 41 return value^ 42 43 44 def budget_remaining_ms(value: Budget, now_ns: Int) -> Int: 45 var elapsed_ms = (now_ns - value.start_monotonic_ns) // 1_000_000 46 var remaining = value.cap_ms - elapsed_ms 47 if remaining < 0: 48 return 0 49 return remaining 50 51 52 def budget_exhausted(value: Budget, now_ns: Int) -> Bool: 53 return budget_remaining_ms(value, now_ns) == 0 54 55 56 def request_budget_cap_ms( 57 request_deadline_ms: Int, configured_request_timeout_ms: Int 58 ) raises -> Int: 59 """ADR-0010 D21 absolute request-budget derivation. 60 61 The assisted-work cap is ``min(positive request deadline_ms, configured 62 provider request_timeout_ms)``. A non-positive configured timeout is an 63 invalid configuration and is rejected; a non-positive request deadline 64 means the caller supplied none, so the configured cap governs. The result 65 is always positive and neither input can enlarge the other. 66 """ 67 if configured_request_timeout_ms <= 0: 68 raise Error("configured provider request_timeout_ms must be positive") 69 if request_deadline_ms <= 0: 70 return configured_request_timeout_ms 71 if request_deadline_ms < configured_request_timeout_ms: 72 return request_deadline_ms 73 return configured_request_timeout_ms 74 75 76 def derived_stage_cap_ms(stage_cap_ms: Int, remaining_budget_ms: Int) -> Int: 77 """``min(stage cap, remaining overall budget)``, never negative. 78 79 D21 connect/read and output caps are derived from the positive remaining 80 absolute budget; this helper never replaces or extends the total deadline. 81 A non-positive stage cap has no admissible budget and collapses to zero. 82 """ 83 if stage_cap_ms <= 0 or remaining_budget_ms <= 0: 84 return 0 85 if remaining_budget_ms < stage_cap_ms: 86 return remaining_budget_ms 87 return stage_cap_ms 88 89 90 def derived_connect_read_cap_ms(remaining_budget_ms: Int) -> Int: 91 return derived_stage_cap_ms(CONNECT_READ_CAP_MS, remaining_budget_ms) 92 93 94 def derived_output_block_cap_ms(remaining_budget_ms: Int) -> Int: 95 return derived_stage_cap_ms(OUTPUT_BLOCK_CAP_MS, remaining_budget_ms) 96 97 98 def budget_stage_cap_ms(value: Budget, now_ns: Int, stage_cap_ms: Int) -> Int: 99 """Absolute-deadline-aware stage cap: ``min(stage cap, remaining budget)``. 100 """ 101 return derived_stage_cap_ms( 102 stage_cap_ms, budget_remaining_ms(value, now_ns) 103 ) 104 105 106 def request_budget_from_config( 107 request_deadline_ms: Int, 108 configured_request_timeout_ms: Int, 109 now_ns: Int, 110 ) raises -> Budget: 111 """One monotonic absolute budget from an explicit D21 derivation.""" 112 var cap_ms = request_budget_cap_ms( 113 request_deadline_ms, configured_request_timeout_ms 114 ) 115 return budget_from_clock(cap_ms, cap_ms, now_ns)