fix(porch-store): correct reset_at unit on the two fresh-window paths

- keypool.wo:78,89 passed msg.window (µs) straight into pool_pack's
  remaining_ms (ms) parameter on the first-hit and post-prune-reset
  paths; the third call site already divided by 1000 and was correct
- fix: pool_pack(1, msg.window / 1000) at both sites — a 60s window
  no longer reports reset_at ~16.7h away
- count/allowed were unaffected (computed independently); this only
  hit the client-visible reset instant, on the two most common cases
  (new key, window rollover)
- extended gate leg 16 to assert reset_at falls within a 5s band of
  time.now() + window_ms, not just on count — verified the assertion
  itself by reverting the fix, confirming leg 16 failed with the
  exact defect shape, then restoring it and confirming green
- woc docs/examples/porch/ exits 0; web-app-accept.sh: 47 checks,
  0 failures

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 153fd295d37905dd083823536c6781843699e455)
This commit is contained in:
shoney.arickathil 2026-08-29 23:46:29 +02:00
parent 88b61f7522
commit 9af42c8e69
2 changed files with 25 additions and 10 deletions

View file

@ -75,7 +75,7 @@ class KeyActor {
if len(hits) == 0 {
insert RateLimitCounter { key: msg.key, count: 1, window: now };
return pool_pack(1, msg.window);
return pool_pack(1, msg.window / 1000);
}
let row = hits[0];
@ -86,7 +86,7 @@ class KeyActor {
-- sweeper; this lazy expiry on access is it.
delete row;
insert RateLimitCounter { key: msg.key, count: 1, window: now };
return pool_pack(1, msg.window);
return pool_pack(1, msg.window / 1000);
}
row.count = row.count + 1;

View file

@ -484,7 +484,7 @@ sleep 0.5
expect "product survives a restart (WAL)" "$(hit GET /products)" 200 '"name":"mug"'
kill -TERM "$SRV" 2>/dev/null; SRV=""
# ---- 16. porch-store task 2: the key pool counts 1 then 2 ----
# ---- 16. porch-store task 2: the key pool counts 1 then 2, reset_at is wall-clock ----
# A flat copy of porch (manifest stripped, so it compiles as one ordinary
# multi-file program with a real entry point rather than the manifest's
# library build) plus a tiny driver dropped into middleware/ — same
@ -494,20 +494,35 @@ KP="$W/keypool-check"
cp -r "$ROOT/docs/examples/porch" "$KP"
rm -f "$KP/wo.toml"
rm -rf "$KP/target"
cat >"$KP/middleware/kptest_main.wo" <<'WOEOF'
KP_WINDOW_US=60000000
cat >"$KP/middleware/kptest_main.wo" <<WOEOF
fn main() -> Int {
let pool = make_pool(4);
let v1 = pool_count(pool, "ip:test", 5, 60_000_000);
let v2 = pool_count(pool, "ip:test", 5, 60_000_000);
print("${v1.count} ${v2.count}");
let v1 = pool_count(pool, "ip:test", 5, ${KP_WINDOW_US});
let v2 = pool_count(pool, "ip:test", 5, ${KP_WINDOW_US});
print("\${v1.count} \${v2.count} \${v1.reset_at}");
return 0;
}
WOEOF
if kp_out="$("$WOC" --emit "$KP" -o "$KP/kptest.wob" 2>&1)"; then
kp_before_ms="$(date +%s%3N)"
kp_vm="$("$WOVM" "$KP/kptest.wob" 2>&1)"
[[ "$kp_vm" == "1 2" ]] \
&& ok "keypool: two sequential counts return 1 then 2" \
|| bad "keypool" "expected '1 2', got '$kp_vm'"
kp_after_ms="$(date +%s%3N)"
read -r kp_c1 kp_c2 kp_reset <<<"$kp_vm"
# v1 is the FIRST-ever hit for this key — the "fresh window" path
# (msg.window skipped its µs->ms conversion before the fix, landing
# reset_at ~1000x too far out: a 60s window read back as ~16.7h away).
# A tight few-second band around time.now() + window_ms catches that
# regression without being timing-flaky.
kp_window_ms=$((KP_WINDOW_US / 1000))
kp_lo=$((kp_before_ms + kp_window_ms - 5000))
kp_hi=$((kp_after_ms + kp_window_ms + 5000))
if [[ "$kp_c1" == "1" && "$kp_c2" == "2" && "$kp_reset" =~ ^[0-9]+$ \
&& "$kp_reset" -ge "$kp_lo" && "$kp_reset" -le "$kp_hi" ]]; then
ok "keypool: counts 1 then 2, reset_at ~window ms ahead of time.now()"
else
bad "keypool" "counts=$kp_c1,$kp_c2 reset_at=$kp_reset expected in [$kp_lo,$kp_hi]"
fi
else
bad "keypool" "compile: $(printf '%s' "$kp_out" | head -1)"
fi