diff --git a/plans/agent-briefings/sx-gate-loop.md b/plans/agent-briefings/sx-gate-loop.md index 8fd5f450..1649a08e 100644 --- a/plans/agent-briefings/sx-gate-loop.md +++ b/plans/agent-briefings/sx-gate-loop.md @@ -50,7 +50,8 @@ Pin each confirmed-and-fixed finding with a minimal repro. Add suites to - [x] K49 [W8] — five void elements (area/base/embed/param/track) renderable (spec side; native regen drift → see Blocked). NB: the depth/cycle guard is K16 [W8], still OPEN — not a W14 pin target until its fix lands -- [ ] crit-2 [W1] — signal-return frame key (verify the pin is non-vacuous) +- [x] crit-2 [W1] — signal-return kont pinned NON-VACUOUSLY (side-effect + sentinel across two tests; a plain assert would inherit the vacuity) - [ ] C1/C1b [W3] — HTTP-mode concurrency fixes, pin - [ ] S4 [conformance] — housekeeping repro, pin @@ -79,6 +80,16 @@ Pin each confirmed-and-fixed finding with a minimal repro. Add suites to ## Progress log (newest first) +- 2026-07-04 — **crit-2 non-vacuous pin (item A.5)**. The original bug's + signature — handler value becomes the WHOLE program result, discarding + every outer frame *including the covering test's own assert* — means a + plain `(assert= repro expected)` pin would pass vacuously on regression. + Added suite `gate-crit2-signal-return-kont` with a **side-effect sentinel**: + test 1 runs both repros (`("outer" 43 "end")` list shape + `raise-continuable` + → 143) then `set!`s a top-level flag; test 2 independently asserts the flag + — if the continuation is ever dropped again, test 1 "passes" but test 2 + fails loudly. Third test pins the exact shipped-test expr (51). Verified + both repro shapes live via sx_eval first. 267 passed / 0 failed. Test-only. - 2026-07-03 — **K49 void-elements pin (item A.4) + regen-drift DISCOVERY**. Corrected the checklist label first: K49 is "five void elements unrenderable" (core.md:335), not the depth guard (that's K16, OPEN). Added diff --git a/spec/tests/test-gate-pins.sx b/spec/tests/test-gate-pins.sx index 30fc3191..13f87945 100644 --- a/spec/tests/test-gate-pins.sx +++ b/spec/tests/test-gate-pins.sx @@ -161,3 +161,59 @@ (quote (embed)) (quote (param)) (quote (track)))))) + +;; -------------------------------------------------------------------------- +;; crit-2 [W1, critical] signal-return frame stored the saved kont under :f +;; but the reader looked up "saved-kont" — the resume kont was always nil, +;; so the handler value became the WHOLE program's result and every frame +;; outside the signal site (including the covering test's own assert!) was +;; silently discarded. The shipped test "signal returns handler value to +;; call site" passed VACUOUSLY — the bug defeated its own test. +;; +;; A plain assert around the repro would inherit the same vacuity on +;; regression (the dropped continuation includes the assert frame). So this +;; pin uses a side-effect sentinel: test 1 runs the repro and then sets a +;; flag; test 2 independently asserts the flag was reached. If crit-2 +;; regresses, test 1 still "passes" (vacuously) but test 2 FAILS. +;; Repro (core.md). +;; -------------------------------------------------------------------------- +(define *gate-crit2-after-signal* false) +(define *gate-crit2-result* nil) +(define *gate-crit2-rc-result* nil) + +(defsuite + "gate-crit2-signal-return-kont" + (deftest + "continuable signal resumes at the raise site" + (do + (set! + *gate-crit2-result* + (list + "outer" + (handler-bind + (((fn (c) true) (fn (c) 42))) + (+ 1 (signal-condition 5))) + "end")) + (set! + *gate-crit2-rc-result* + (handler-bind + (((fn (c) true) (fn (c) (+ c 100)))) + (+ 1 (raise-continuable 42)))) + (set! *gate-crit2-after-signal* true) + (assert= *gate-crit2-result* (list "outer" 43 "end")) + (assert= *gate-crit2-rc-result* 143))) + (deftest + "non-vacuity sentinel: the continuation after the signal actually ran" + (do + (assert + *gate-crit2-after-signal* + "continuation dropped — crit-2 regressed (previous test passed vacuously)") + (assert= *gate-crit2-result* (list "outer" 43 "end")) + (assert= *gate-crit2-rc-result* 143))) + (deftest + "handler value feeds the arithmetic frame, not the program result" + (assert= + (handler-bind + (((fn (c) true) (fn (c) (* c 10)))) + (+ 1 (signal-condition 5))) + 51)))