Make edge filter failure mode configurable

ober

14e2bdc9ea1c1d948bde5b2ef9c13b9b18518392

diff --git a/edge.ss b/edge.ss
index d8b57d5..9a88b9f 100644
--- a/edge.ss
+++ b/edge.ss
@@ -32,7 +32,11 @@
           atom? meta)
         (except (jerboa prelude)
           make-time split cpu-count or-else
-          json-object->string string->json-object write-json read-json)
+          json-object->string string->json-object write-json read-json
+          tcp-write-string tcp-write tcp-read
+          tcp-close tcp-accept tcp-listen tcp-connect
+          sqlite-open sqlite-close sqlite-exec sqlite-execute sqlite-query
+          sqlite-prepare sqlite-finalize sqlite-step sqlite-bind)
         (std net fiber-httpd)
         (std net fiber-ws)
         (except (std csp) go go-named)
@@ -320,7 +324,8 @@
 ;;   (let ([p (cdr (assoc "payload" evt))])
 ;;     (> (cdr (assoc "amount" p)) 1000))
 ;;
-;; Errors and timeouts → #t (permissive — broken filter never drops all events).
+;; Errors and timeouts fail open by default for observability. Set
+;; JERBOA_EDGE_FILTER_FAIL_CLOSED=1 to turn filter failures into drops.
 ;; ═══════════════════════════════════════════════════════════════
 
 ;; Recursively convert hash tables → alists so event data can be
@@ -333,19 +338,27 @@
     [(pair? x) (cons (hash->alist-deep (car x)) (hash->alist-deep (cdr x)))]
     [else x]))
 
+(def (filter-fail-closed?)
+  (let ([v (getenv "JERBOA_EDGE_FILTER_FAIL_CLOSED")])
+    (and (string? v)
+         (not (member (string-downcase v) '("" "0" "false" "no" "off"))))))
+
+(def (filter-failure-result)
+  (not (filter-fail-closed?)))
+
 ;; Evaluate one filter expression against an event in a restricted
 ;; environment (allowlist-only: no I/O, no FFI, no network).
 ;; Uses Chez engine-based timeout — no fork, safe to call from fibers.
-;; Returns #t (pass) or #f (drop). Errors and timeouts → #t (permissive).
+;; Returns #t (pass) or #f (drop). Failure behavior is deployment-configurable.
 (def (eval-user-filter filter-code event)
   (let* ([evt-data (hash->alist-deep event)]
          [code (format "(let ([evt '~s]) ~a)" evt-data filter-code)])
     (guard (exn [#t
                  (jlog "filter eval error" "err"
                    (if (message-condition? exn) (condition-message exn) (format "~a" exn)))
-                 #t])  ;; permissive on error
+                 (filter-failure-result)])
       ;; with-timeout uses Chez engines (no fork) — fiber-safe
-      (let ([result (with-timeout 2.0 #t
+      (let ([result (with-timeout 2.0 (filter-failure-result)
                       (lambda () (restricted-eval-string code)))])
         (and result #t)))))