;; identity/audit.sx — the grant audit ledger. ;; ;; Every transition that changes a grant — issue, refresh, revoke (and, ;; wired from oauth, consent) — appends an immutable event to this ;; append-only process. The ledger is queryable by subject, which is what ;; `(identity/audit subject)` answers. This is the in-memory realisation ;; of the event stream; a persist-backed stream is a later substrate ;; concern (Erlang↔persist bridge), kept out of scope here per the loop's ;; \"in-memory log until persist lands\" allowance — the queryable ;; semantics are identical. ;; ;; Events are {Seq, Subject, Action}; Seq is a monotonic sequence number. ;; Reads return chronological (oldest-first) order: ;; ;; record(A, Subject, Action) -> ok (one-way; FIFO-ordered) ;; audit(A, Subject) -> [{Seq, Subject, Action}, ...] ;; actions(A, Subject) -> [Action, ...] ;; count(A, Subject) -> N ;; all(A) -> [{Seq, Subject, Action}, ...] (define identity-audit-source "-module(identity_audit).\n\n start() ->\n spawn(fun () -> loop([], 0) end).\n\n record(A, Subject, Action) ->\n A ! {event, Subject, Action},\n ok.\n\n audit(A, Subject) ->\n A ! {audit, Subject, self()},\n receive {audit_reply, R} -> R end.\n\n actions(A, Subject) ->\n A ! {actions, Subject, self()},\n receive {audit_reply, R} -> R end.\n\n count(A, Subject) ->\n A ! {count, Subject, self()},\n receive {audit_reply, R} -> R end.\n\n all(A) ->\n A ! {all, self()},\n receive {audit_reply, R} -> R end.\n\n loop(Events, Seq) ->\n receive\n {event, Subject, Action} ->\n loop([{Seq, Subject, Action} | Events], Seq + 1);\n {audit, Subject, From} ->\n From ! {audit_reply, collect(Subject, Events, [])},\n loop(Events, Seq);\n {actions, Subject, From} ->\n From ! {audit_reply, action_list(Subject, Events, [])},\n loop(Events, Seq);\n {count, Subject, From} ->\n From ! {audit_reply, count_subj(Subject, Events, 0)},\n loop(Events, Seq);\n {all, From} ->\n From ! {audit_reply, reverse(Events, [])},\n loop(Events, Seq);\n {stop, From} ->\n From ! {audit_reply, ok}\n end.\n\n collect(_, [], Acc) -> Acc;\n collect(Subject, [{Seq, S, A} | Rest], Acc) ->\n case S =:= Subject of\n true -> collect(Subject, Rest, [{Seq, S, A} | Acc]);\n false -> collect(Subject, Rest, Acc)\n end.\n\n action_list(_, [], Acc) -> Acc;\n action_list(Subject, [{_, S, A} | Rest], Acc) ->\n case S =:= Subject of\n true -> action_list(Subject, Rest, [A | Acc]);\n false -> action_list(Subject, Rest, Acc)\n end.\n\n count_subj(_, [], N) -> N;\n count_subj(Subject, [{_, S, _} | Rest], N) ->\n case S =:= Subject of\n true -> count_subj(Subject, Rest, N + 1);\n false -> count_subj(Subject, Rest, N)\n end.\n\n reverse([], Acc) -> Acc;\n reverse([H | T], Acc) -> reverse(T, [H | Acc]).") (define identity-load-audit! (fn () (erlang-load-module identity-audit-source)))