mod: Ext 13 — SLA sweep over pending lifecycle cases, 307/307
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 6m28s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 6m28s
Composes lifecycle (Phase 3) with time (Ext 12): a timed-case pairs a case with its state-entry tick; mod/overdue? flags pending cases (open/triaged/appealed) past a deadline; mod/sla-sweep returns the breached report ids. Terminal states never breach. Pure overlay — lifecycle stays timeless, caller stamps entry. +15 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ PRELOADS=(
|
|||||||
lib/mod/whatif.sx
|
lib/mod/whatif.sx
|
||||||
lib/mod/batch.sx
|
lib/mod/batch.sx
|
||||||
lib/mod/temporal.sx
|
lib/mod/temporal.sx
|
||||||
|
lib/mod/sla.sx
|
||||||
lib/mod/lifecycle.sx
|
lib/mod/lifecycle.sx
|
||||||
lib/mod/audit.sx
|
lib/mod/audit.sx
|
||||||
lib/mod/api.sx
|
lib/mod/api.sx
|
||||||
@@ -44,4 +45,5 @@ SUITES=(
|
|||||||
"whatif:lib/mod/tests/whatif.sx:(mod-whatif-tests-run!)"
|
"whatif:lib/mod/tests/whatif.sx:(mod-whatif-tests-run!)"
|
||||||
"batch:lib/mod/tests/batch.sx:(mod-batch-tests-run!)"
|
"batch:lib/mod/tests/batch.sx:(mod-batch-tests-run!)"
|
||||||
"temporal:lib/mod/tests/temporal.sx:(mod-temporal-tests-run!)"
|
"temporal:lib/mod/tests/temporal.sx:(mod-temporal-tests-run!)"
|
||||||
|
"sla:lib/mod/tests/sla.sx:(mod-sla-tests-run!)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"lang": "mod",
|
"lang": "mod",
|
||||||
"total_passed": 292,
|
"total_passed": 307,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 292,
|
"total": 307,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"decide","passed":31,"failed":0,"total":31},
|
{"name":"decide","passed":31,"failed":0,"total":31},
|
||||||
{"name":"audit","passed":29,"failed":0,"total":29},
|
{"name":"audit","passed":29,"failed":0,"total":29},
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
{"name":"trace","passed":15,"failed":0,"total":15},
|
{"name":"trace","passed":15,"failed":0,"total":15},
|
||||||
{"name":"whatif","passed":13,"failed":0,"total":13},
|
{"name":"whatif","passed":13,"failed":0,"total":13},
|
||||||
{"name":"batch","passed":17,"failed":0,"total":17},
|
{"name":"batch","passed":17,"failed":0,"total":17},
|
||||||
{"name":"temporal","passed":15,"failed":0,"total":15}
|
{"name":"temporal","passed":15,"failed":0,"total":15},
|
||||||
|
{"name":"sla","passed":15,"failed":0,"total":15}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-06T19:00:19+00:00"
|
"generated": "2026-06-06T19:08:06+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# mod scoreboard
|
# mod scoreboard
|
||||||
|
|
||||||
**292 / 292 passing** (0 failure(s)).
|
**307 / 307 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
@@ -18,3 +18,4 @@
|
|||||||
| whatif | 13 | 13 | ok |
|
| whatif | 13 | 13 | ok |
|
||||||
| batch | 17 | 17 | ok |
|
| batch | 17 | 17 | ok |
|
||||||
| temporal | 15 | 15 | ok |
|
| temporal | 15 | 15 | ok |
|
||||||
|
| sla | 15 | 15 | ok |
|
||||||
|
|||||||
47
lib/mod/sla.sx
Normal file
47
lib/mod/sla.sx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
;; lib/mod/sla.sx — service-level sweep over pending lifecycle cases.
|
||||||
|
;;
|
||||||
|
;; Composes the Phase-3 lifecycle with the Ext-12 time dimension: a case left in a
|
||||||
|
;; pending state (open / triaged / appealed) past a deadline has breached SLA and
|
||||||
|
;; should resurface. A timed-case pairs a case with the tick it entered its
|
||||||
|
;; current state (the caller stamps this — the lifecycle stays timeless and pure).
|
||||||
|
;; Terminal states (decided / final) never breach.
|
||||||
|
|
||||||
|
(define mod/pending-states (list "open" "triaged" "appealed"))
|
||||||
|
(define mod/pending-state? (fn (s) (mod/member? s mod/pending-states)))
|
||||||
|
|
||||||
|
(define mod/mk-timed-case (fn (c entered-at) {:entered-at entered-at :case c}))
|
||||||
|
(define mod/tc-case (fn (tc) (get tc :case)))
|
||||||
|
(define mod/tc-entered-at (fn (tc) (get tc :entered-at)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mod/overdue?
|
||||||
|
(fn
|
||||||
|
(tc now deadline)
|
||||||
|
(if
|
||||||
|
(mod/pending-state? (mod/case-state (mod/tc-case tc)))
|
||||||
|
(< deadline (- now (mod/tc-entered-at tc)))
|
||||||
|
false)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mod/sla-sweep
|
||||||
|
(fn
|
||||||
|
(timed-cases now deadline)
|
||||||
|
(reduce
|
||||||
|
(fn
|
||||||
|
(acc tc)
|
||||||
|
(if
|
||||||
|
(mod/overdue? tc now deadline)
|
||||||
|
(append
|
||||||
|
acc
|
||||||
|
(list (mod/report-id (mod/case-report (mod/tc-case tc)))))
|
||||||
|
acc))
|
||||||
|
(list)
|
||||||
|
timed-cases)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mod/overdue-count
|
||||||
|
(fn
|
||||||
|
(timed-cases now deadline)
|
||||||
|
(len (mod/sla-sweep timed-cases now deadline))))
|
||||||
|
|
||||||
|
(define mod/age (fn (tc now) (- now (mod/tc-entered-at tc))))
|
||||||
108
lib/mod/tests/sla.sx
Normal file
108
lib/mod/tests/sla.sx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
;; lib/mod/tests/sla.sx — Ext 13: SLA sweep over pending lifecycle cases.
|
||||||
|
|
||||||
|
(define mod-sla-count 0)
|
||||||
|
(define mod-sla-pass 0)
|
||||||
|
(define mod-sla-fail 0)
|
||||||
|
(define mod-sla-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
mod-sla-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! mod-sla-count (+ mod-sla-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! mod-sla-pass (+ mod-sla-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! mod-sla-fail (+ mod-sla-fail 1))
|
||||||
|
(append!
|
||||||
|
mod-sla-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
;; ── pending-state? ──
|
||||||
|
|
||||||
|
(mod-sla-test! "open is pending" (mod/pending-state? "open") true)
|
||||||
|
(mod-sla-test! "triaged is pending" (mod/pending-state? "triaged") true)
|
||||||
|
(mod-sla-test! "appealed is pending" (mod/pending-state? "appealed") true)
|
||||||
|
(mod-sla-test! "decided is not pending" (mod/pending-state? "decided") false)
|
||||||
|
(mod-sla-test! "final is not pending" (mod/pending-state? "final") false)
|
||||||
|
|
||||||
|
;; build cases in known states
|
||||||
|
(define mod-sla-spam (mod/mk-report "r1" "u" "bob" "this is spam"))
|
||||||
|
(define mod-sla-spam-reports (list mod-sla-spam))
|
||||||
|
(define
|
||||||
|
mod-sla-triaged
|
||||||
|
(mod/case-triage
|
||||||
|
(mod/mk-case mod-sla-spam)
|
||||||
|
mod-sla-spam-reports
|
||||||
|
mod/default-rules))
|
||||||
|
(define mod-sla-decided (mod/case-resolve mod-sla-triaged))
|
||||||
|
(define mod-sla-open (mod/mk-case (mod/mk-report "r2" "u" "eve" "hello")))
|
||||||
|
|
||||||
|
;; ── overdue? ──
|
||||||
|
|
||||||
|
(define mod-sla-tc-old (mod/mk-timed-case mod-sla-triaged 0))
|
||||||
|
(define mod-sla-tc-fresh (mod/mk-timed-case mod-sla-triaged 90))
|
||||||
|
(define mod-sla-tc-done (mod/mk-timed-case mod-sla-decided 0))
|
||||||
|
|
||||||
|
(mod-sla-test!
|
||||||
|
"old triaged case is overdue"
|
||||||
|
(mod/overdue? mod-sla-tc-old 100 50)
|
||||||
|
true)
|
||||||
|
(mod-sla-test!
|
||||||
|
"fresh triaged case not overdue"
|
||||||
|
(mod/overdue? mod-sla-tc-fresh 100 50)
|
||||||
|
false)
|
||||||
|
(mod-sla-test!
|
||||||
|
"decided case never overdue"
|
||||||
|
(mod/overdue? mod-sla-tc-done 100 50)
|
||||||
|
false)
|
||||||
|
(mod-sla-test!
|
||||||
|
"age computes elapsed ticks"
|
||||||
|
(mod/age mod-sla-tc-old 100)
|
||||||
|
100)
|
||||||
|
(mod-sla-test!
|
||||||
|
"boundary: exactly at deadline not overdue"
|
||||||
|
(mod/overdue?
|
||||||
|
(mod/mk-timed-case mod-sla-triaged 50)
|
||||||
|
100
|
||||||
|
50)
|
||||||
|
false)
|
||||||
|
(mod-sla-test!
|
||||||
|
"boundary: one past deadline overdue"
|
||||||
|
(mod/overdue?
|
||||||
|
(mod/mk-timed-case mod-sla-triaged 49)
|
||||||
|
100
|
||||||
|
50)
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── sweep over a mixed queue ──
|
||||||
|
|
||||||
|
(define
|
||||||
|
mod-sla-queue
|
||||||
|
(list
|
||||||
|
(mod/mk-timed-case mod-sla-triaged 0)
|
||||||
|
(mod/mk-timed-case mod-sla-decided 0)
|
||||||
|
(mod/mk-timed-case mod-sla-open 90))) ;; r2, pending, age 10 → not
|
||||||
|
|
||||||
|
(mod-sla-test!
|
||||||
|
"sweep finds only the overdue pending case"
|
||||||
|
(mod/sla-sweep mod-sla-queue 100 50)
|
||||||
|
(list "r1"))
|
||||||
|
(mod-sla-test!
|
||||||
|
"overdue-count agrees"
|
||||||
|
(mod/overdue-count mod-sla-queue 100 50)
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; tighten deadline so the young open case also breaches
|
||||||
|
(mod-sla-test!
|
||||||
|
"tighter deadline catches the open case too"
|
||||||
|
(mod/overdue-count mod-sla-queue 100 5)
|
||||||
|
2)
|
||||||
|
(mod-sla-test!
|
||||||
|
"empty queue → no breaches"
|
||||||
|
(mod/sla-sweep (list) 100 50)
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(define mod-sla-tests-run! (fn () {:failures mod-sla-failures :total mod-sla-count :passed mod-sla-pass :failed mod-sla-fail}))
|
||||||
@@ -16,7 +16,7 @@ federation extension.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/mod/conformance.sh` → **292/292** (roadmap + 12 extensions complete)
|
`bash lib/mod/conformance.sh` → **307/307** (roadmap + 13 extensions complete)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -147,6 +147,11 @@ lib/mod/fed.sx
|
|||||||
derivation goal-by-goal with `[proved]`/`[unproved]` marks and unification
|
derivation goal-by-goal with `[proved]`/`[unproved]` marks and unification
|
||||||
bindings. E.g. `Report rc: escalate (rule: repeated-escalate)` … `[proved]
|
bindings. E.g. `Report rc: escalate (rule: repeated-escalate)` … `[proved]
|
||||||
report(rc, B, S), report_count(S, N), N >= 3 {B=ann, N=3, S=dave}`.
|
report(rc, B, S), report_count(S, N), N >= 3 {B=ann, N=3, S=dave}`.
|
||||||
|
- [x] **Ext 13 — SLA sweep over pending cases** (`lib/mod/sla.sx`, +15). Composes
|
||||||
|
lifecycle (Phase 3) with time (Ext 12): a timed-case pairs a case with the tick
|
||||||
|
it entered its state; `mod/overdue?` flags pending cases (open/triaged/appealed)
|
||||||
|
past a deadline; `mod/sla-sweep` returns the breached report ids. Terminal states
|
||||||
|
never breach. Pure overlay — lifecycle stays timeless, the caller stamps entry.
|
||||||
- [x] **Ext 12 — temporal burst detection** (`lib/mod/temporal.sx`, +15). Reports
|
- [x] **Ext 12 — temporal burst detection** (`lib/mod/temporal.sx`, +15). Reports
|
||||||
gain an `:at` tick (deterministic, supplied — never clock-read).
|
gain an `:at` tick (deterministic, supplied — never clock-read).
|
||||||
`mod/decide-temporal now window` counts reports about the subject within
|
`mod/decide-temporal now window` counts reports about the subject within
|
||||||
@@ -202,6 +207,11 @@ lib/mod/fed.sx
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- **Ext 13 — SLA sweep, 307/307** (+15). Two subsystems compose cleanly: lifecycle
|
||||||
|
states + temporal ticks → "which pending cases have sat too long". Kept lifecycle
|
||||||
|
pure by having the SLA layer carry entry-time externally (timed-case wrapper)
|
||||||
|
rather than stamping the case — same separation-of-concerns as keeping the state
|
||||||
|
machine out of Prolog.
|
||||||
- **Ext 12 — temporal burst detection, 292/292** (+15). Adds the time dimension:
|
- **Ext 12 — temporal burst detection, 292/292** (+15). Adds the time dimension:
|
||||||
a windowed count distinguishes a burst from slow accumulation, where the plain
|
a windowed count distinguishes a burst from slow accumulation, where the plain
|
||||||
count rule cannot. Time is a supplied tick (`:at`), keeping everything
|
count rule cannot. Time is a supplied tick (`:at`), keeping everything
|
||||||
|
|||||||
Reference in New Issue
Block a user