acl: Phase 3 explanation + audit, 35 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
explain.sx reconstructs a canonical proof tree (first-rule, first-solution)
by goal-directed search over the saturated db, since Datalog keeps no
provenance; depth-capped for cyclic safety. acl-explain returns
{:allowed? :proof :reason} with the blocking eff_deny proof on denial.
audit.sx is an append-only decision log (monotonic seq, disk serializer).
api gains acl/explain, acl/audit, acl/audit-tail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
82
lib/acl/audit.sx
Normal file
82
lib/acl/audit.sx
Normal file
@@ -0,0 +1,82 @@
|
||||
;; lib/acl/audit.sx — append-only decision log.
|
||||
;;
|
||||
;; Every decision routed through acl-audit-decide! is appended to an in-memory
|
||||
;; log with a monotonic sequence number (no wall-clock — deterministic and
|
||||
;; testable; a host can stamp time at the serializer boundary). The log is
|
||||
;; append-only: there is no mutate or delete, only append, tail, clear, and
|
||||
;; serialize-for-disk.
|
||||
|
||||
(define acl-audit-log (list))
|
||||
(define acl-audit-seq 0)
|
||||
|
||||
(define
|
||||
acl-audit-clear!
|
||||
(fn
|
||||
()
|
||||
(do (set! acl-audit-log (list)) (set! acl-audit-seq 0) nil)))
|
||||
|
||||
;; Append a decision record. Returns the record.
|
||||
(define
|
||||
acl-audit-record!
|
||||
(fn
|
||||
(subj act res allowed?)
|
||||
(let
|
||||
((entry {:allowed? allowed? :act act :subj subj :res res :seq acl-audit-seq}))
|
||||
(do
|
||||
(set! acl-audit-seq (+ acl-audit-seq 1))
|
||||
(append! acl-audit-log entry)
|
||||
entry))))
|
||||
|
||||
;; Decide against db, log the outcome, and return the boolean. This is the
|
||||
;; audited path; acl-permit? remains the pure, side-effect-free decision.
|
||||
(define
|
||||
acl-audit-decide!
|
||||
(fn
|
||||
(db subj act res)
|
||||
(let
|
||||
((allowed? (acl-permit? db subj act res)))
|
||||
(do (acl-audit-record! subj act res allowed?) allowed?))))
|
||||
|
||||
(define acl-audit-count (fn () (len acl-audit-log)))
|
||||
|
||||
;; Most recent n entries (in chronological order). n >= log size returns all.
|
||||
(define
|
||||
acl-audit-tail
|
||||
(fn
|
||||
(n)
|
||||
(let
|
||||
((total (len acl-audit-log)))
|
||||
(if
|
||||
(<= total n)
|
||||
acl-audit-log
|
||||
(acl-audit-drop acl-audit-log (- total n))))))
|
||||
|
||||
(define
|
||||
acl-audit-drop
|
||||
(fn
|
||||
(xs k)
|
||||
(if (<= k 0) xs (acl-audit-drop (rest xs) (- k 1)))))
|
||||
|
||||
;; Serialize the whole log to a disk-ready string: one record per line,
|
||||
;; "seq\tsubj\tact\tres\tallowed?". A host writes this; reload is out of scope.
|
||||
(define
|
||||
acl-audit-serialize
|
||||
(fn
|
||||
()
|
||||
(reduce
|
||||
(fn
|
||||
(acc e)
|
||||
(str
|
||||
acc
|
||||
(get e :seq)
|
||||
"\t"
|
||||
(get e :subj)
|
||||
"\t"
|
||||
(get e :act)
|
||||
"\t"
|
||||
(get e :res)
|
||||
"\t"
|
||||
(get e :allowed?)
|
||||
"\n"))
|
||||
""
|
||||
acl-audit-log)))
|
||||
Reference in New Issue
Block a user