mod: Ext 14 — decision wire format for federation transport, 323/323
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s

mod/decision->wire emits a versioned pipe-delimited line (MOD1|r1|hide|spam-hide);
mod/wire->decision parses it back (mod/wire-valid? guards). split-char built over
slice/len (loaded env has no split). Integration test runs the full federated
path: serialize → wire → deserialize → fed-receive-decision trust-gating
(untrusted→advisory, trusted→applied). +16 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 19:17:19 +00:00
parent 68c8e39508
commit b43901d297
6 changed files with 172 additions and 6 deletions

View File

@@ -22,6 +22,7 @@ PRELOADS=(
lib/mod/batch.sx
lib/mod/temporal.sx
lib/mod/sla.sx
lib/mod/wire.sx
lib/mod/lifecycle.sx
lib/mod/audit.sx
lib/mod/api.sx
@@ -46,4 +47,5 @@ SUITES=(
"batch:lib/mod/tests/batch.sx:(mod-batch-tests-run!)"
"temporal:lib/mod/tests/temporal.sx:(mod-temporal-tests-run!)"
"sla:lib/mod/tests/sla.sx:(mod-sla-tests-run!)"
"wire:lib/mod/tests/wire.sx:(mod-wire-tests-run!)"
)

View File

@@ -1,8 +1,8 @@
{
"lang": "mod",
"total_passed": 307,
"total_passed": 323,
"total_failed": 0,
"total": 307,
"total": 323,
"suites": [
{"name":"decide","passed":31,"failed":0,"total":31},
{"name":"audit","passed":29,"failed":0,"total":29},
@@ -18,7 +18,8 @@
{"name":"whatif","passed":13,"failed":0,"total":13},
{"name":"batch","passed":17,"failed":0,"total":17},
{"name":"temporal","passed":15,"failed":0,"total":15},
{"name":"sla","passed":15,"failed":0,"total":15}
{"name":"sla","passed":15,"failed":0,"total":15},
{"name":"wire","passed":16,"failed":0,"total":16}
],
"generated": "2026-06-06T19:08:06+00:00"
"generated": "2026-06-06T19:16:49+00:00"
}

View File

@@ -1,6 +1,6 @@
# mod scoreboard
**307 / 307 passing** (0 failure(s)).
**323 / 323 passing** (0 failure(s)).
| Suite | Passed | Total | Status |
|-------|--------|-------|--------|
@@ -19,3 +19,4 @@
| batch | 17 | 17 | ok |
| temporal | 15 | 15 | ok |
| sla | 15 | 15 | ok |
| wire | 16 | 16 | ok |

96
lib/mod/tests/wire.sx Normal file
View File

@@ -0,0 +1,96 @@
;; lib/mod/tests/wire.sx — Ext 14: decision wire format + federated transport.
(define mod-w-count 0)
(define mod-w-pass 0)
(define mod-w-fail 0)
(define mod-w-failures (list))
(define
mod-w-test!
(fn
(name got expected)
(begin
(set! mod-w-count (+ mod-w-count 1))
(if
(= got expected)
(set! mod-w-pass (+ mod-w-pass 1))
(begin
(set! mod-w-fail (+ mod-w-fail 1))
(append!
mod-w-failures
(str name "\n expected: " expected "\n got: " got)))))))
;; ── split-char ──
(mod-w-test! "split on pipe" (mod/split-char "a|b|c" "|") (list "a" "b" "c"))
(mod-w-test! "split single field" (mod/split-char "abc" "|") (list "abc"))
(mod-w-test!
"split four fields"
(len (mod/split-char "MOD1|r1|hide|spam-hide" "|"))
4)
;; ── serialize ──
(define
mod-w-dec
(mod/decide-report
(mod/mk-report "r1" "a" "bob" "this is spam")
(list (mod/mk-report "r1" "a" "bob" "this is spam"))
mod/default-rules))
(define mod-w-line (mod/decision->wire mod-w-dec))
(mod-w-test!
"wire is versioned + delimited"
mod-w-line
"MOD1|r1|hide|spam-hide")
(mod-w-test!
"wire-valid? accepts well-formed"
(mod/wire-valid? mod-w-line)
true)
(mod-w-test!
"wire-valid? rejects junk"
(mod/wire-valid? "not a wire line")
false)
(mod-w-test!
"wire-valid? rejects wrong version"
(mod/wire-valid? "MOD9|r1|hide|x")
false)
;; ── round-trip ──
(define mod-w-back (mod/wire->decision mod-w-line))
(mod-w-test! "round-trip report-id" (get mod-w-back :report-id) "r1")
(mod-w-test! "round-trip action" (get mod-w-back :action) "hide")
(mod-w-test! "round-trip rule" (get mod-w-back :rule) "spam-hide")
(mod-w-test! "round-trip tags :wire" (get mod-w-back :wire) true)
(mod-w-test! "malformed → nil" (mod/wire->decision "garbage") nil)
;; ── full federated transport: serialize → wire → deserialize → trust-gate ──
(mod/fed-reset!)
(define mod-w-peer-dec (mod/wire->decision mod-w-line))
;; untrusted peer: decision is advisory, not applied
(define mod-w-recv1 (mod/fed-receive-decision "peerX" mod-w-peer-dec))
(mod-w-test!
"wired decision from untrusted peer → advisory"
(get mod-w-recv1 :applied)
false)
(mod-w-test!
"untrusted wired decision not applied locally"
(mod/fed-applied-action "r1")
nil)
;; trusted peer: decision binds locally
(mod/grant-trust "peerY" :mod)
(define mod-w-recv2 (mod/fed-receive-decision "peerY" mod-w-peer-dec))
(mod-w-test!
"wired decision from trusted peer → applied"
(get mod-w-recv2 :applied)
true)
(mod-w-test!
"trusted wired decision binds locally"
(get (mod/fed-applied-action "r1") :action)
"hide")
(define mod-wire-tests-run! (fn () {:failures mod-w-failures :total mod-w-count :passed mod-w-pass :failed mod-w-fail}))

55
lib/mod/wire.sx Normal file
View File

@@ -0,0 +1,55 @@
;; lib/mod/wire.sx — portable decision wire format for federation transport.
;;
;; fed.sx shares decisions as in-memory dicts and leaves mod/fed-send! as the
;; transport seam. This is the bytes that cross it: a versioned, pipe-delimited
;; line encoding the verdict a peer needs (report id, action, rule) — enough to
;; trust-gate and apply/advise, without shipping the whole proof tree. The
;; loaded env has no string split, so split is built over slice/len.
(define
mod/split-loop
(fn
(s ch n start pos acc)
(if
(= pos n)
(append acc (list (slice s start n)))
(if
(= (slice s pos (+ pos 1)) ch)
(mod/split-loop
s
ch
n
(+ pos 1)
(+ pos 1)
(append acc (list (slice s start pos))))
(mod/split-loop s ch n start (+ pos 1) acc)))))
(define
mod/split-char
(fn (s ch) (mod/split-loop s ch (len s) 0 0 (list))))
(define
mod/decision->wire
(fn
(d)
(str "MOD1|" (get d :report-id) "|" (get d :action) "|" (get d :rule))))
(define
mod/wire-valid?
(fn
(w)
(let
((parts (mod/split-char w "|")))
(if
(= (len parts) 4)
(= (nth parts 0) "MOD1")
false))))
(define
mod/wire->decision
(fn
(w)
(if
(mod/wire-valid? w)
(let ((parts (mod/split-char w "|"))) {:action (nth parts 2) :wire true :rule (nth parts 3) :report-id (nth parts 1)})
nil)))

View File

@@ -16,7 +16,7 @@ federation extension.
## Status (rolling)
`bash lib/mod/conformance.sh`**307/307** (roadmap + 13 extensions complete)
`bash lib/mod/conformance.sh`**323/323** (roadmap + 14 extensions complete)
## Ground rules
@@ -147,6 +147,12 @@ lib/mod/fed.sx
derivation goal-by-goal with `[proved]`/`[unproved]` marks and unification
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}`.
- [x] **Ext 14 — decision wire format** (`lib/mod/wire.sx`, +16). The bytes that
cross `fed/fed-send!`: `mod/decision->wire` emits a versioned pipe-delimited line
(`MOD1|r1|hide|spam-hide`), `mod/wire->decision` parses it back (`mod/wire-valid?`
guards). Built `mod/split-char` over `slice`/`len` (loaded env has no split).
Integration test exercises the full path: serialize → wire → deserialize →
`fed-receive-decision` trust-gating (untrusted→advisory, trusted→applied).
- [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)
@@ -207,6 +213,11 @@ lib/mod/fed.sx
## Progress log
- **Ext 14 — decision wire format, 323/323** (+16). Fills the federation transport
seam: decisions now serialize to a portable line and parse back, and the
integration test runs the whole federated path end-to-end (serialize on one
instance → trust-gated apply on another). Needed a hand-rolled `split-char`
(loaded env has no split) — over `slice`/`len`, same toolkit as `str-contains?`.
- **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)