;; lib/mod/lifecycle.sx — report lifecycle state machine (pure SX over the engine). ;; ;; Lifecycle state is deliberately separate from policy: the Prolog rules answer ;; "what action?", this module answers "where in the process is this report?". ;; ;; :open ──triage──▶ :triaged ──resolve/review──▶ :decided ──appeal──▶ :appealed ;; │ │ ;; └────finalize───▶ :final ◀┘ ;; ;; A case is an immutable value {:report :state :decision :tier :error :history}. ;; Every transition returns a NEW case; illegal transitions return the case ;; unchanged with :error set. Tiers: triage runs the engine (auto-tier); a ;; terminal action (hide/remove/keep) resolves immediately, an :escalate action ;; flags the case for human review (human-tier) before it can be resolved. (define mod/case* (fn (report state decision tier err history) {:history history :state state :report report :error err :tier tier :decision decision})) (define mod/mk-case (fn (report) (mod/case* report "open" nil nil nil (list)))) (define mod/case-report (fn (c) (get c :report))) (define mod/case-state (fn (c) (get c :state))) (define mod/case-decision (fn (c) (get c :decision))) (define mod/case-tier (fn (c) (get c :tier))) (define mod/case-error (fn (c) (get c :error))) (define mod/case-history (fn (c) (get c :history))) ;; ── transition table ── (define mod/lc-transitions {:final (list) :appealed (list "final") :decided (list "appealed" "final") :open (list "triaged") :triaged (list "decided")}) (define mod/member? (fn (x lst) (mod/any? (fn (y) (= y x)) lst))) (define mod/lc-can-transition? (fn (from to) (let ((outs (get mod/lc-transitions from))) (if (nil? outs) false (mod/member? to outs))))) ;; ── core transition: validate, record history, or flag :error ── (define mod/case-goto (fn (c to note report decision tier) (let ((from (mod/case-state c))) (if (mod/lc-can-transition? from to) (mod/case* report to decision tier nil (append (mod/case-history c) (list {:note note :to to :from from}))) (mod/case* (mod/case-report c) from (mod/case-decision c) (mod/case-tier c) (str "illegal transition: " from " -> " to) (mod/case-history c)))))) (define mod/case-error-set (fn (c msg) (mod/case* (mod/case-report c) (mod/case-state c) (mod/case-decision c) (mod/case-tier c) msg (mod/case-history c)))) ;; ── lifecycle operations ── ;; :open → :triaged — run the auto-tier first pass. (define mod/case-triage (fn (c reports rules) (let ((d (mod/decide-report (mod/case-report c) reports rules))) (let ((tier (if (= (get d :action) "escalate") "human" "auto"))) (mod/case-goto c "triaged" "auto-tier first pass" (mod/case-report c) d tier))))) ;; :triaged → :decided — auto-tier resolves; human-tier is blocked until review. (define mod/case-resolve (fn (c) (if (= (mod/case-tier c) "human") (mod/case-error-set c "awaiting human review (escalated)") (mod/case-goto c "decided" "auto-tier resolved" (mod/case-report c) (mod/case-decision c) (mod/case-tier c))))) ;; :triaged → :decided — human review: attach evidence, re-decide, resolve. (define mod/case-review (fn (c kind val reports rules) (let ((nr (mod/attach-evidence (mod/case-report c) (mod/mk-evidence kind val)))) (let ((d (mod/decide-report nr reports rules))) (mod/case-goto c "decided" (str "human review: " kind) nr d "human"))))) ;; :decided → :appealed — appeal: attach evidence, re-decide (may override). (define mod/case-appeal (fn (c kind val reports rules) (let ((nr (mod/attach-evidence (mod/case-report c) (mod/mk-evidence kind val)))) (let ((d (mod/decide-report nr reports rules))) (mod/case-goto c "appealed" (str "appeal: " kind) nr d (mod/case-tier c)))))) ;; :decided | :appealed → :final (define mod/case-finalize (fn (c) (mod/case-goto c "final" "finalized" (mod/case-report c) (mod/case-decision c) (mod/case-tier c)))) (define mod/case-action (fn (c) (let ((d (mod/case-decision c))) (if (nil? d) nil (get d :action)))))