;; identity/delegation.sx — the identity -> acl delegation boundary. ;; ;; This is the loop's central architectural rule made concrete: ;; AUTHENTICATION is identity's job; AUTHORIZATION is acl's. A request is ;; checked in two stages, and the order matters: ;; ;; 1. identity proves WHO via the opaque token (introspect). If the token ;; is inactive, the answer is {error, unauthenticated} — a 401. acl is ;; NEVER consulted; \"I don't know who you are\" is not a permission ;; question. ;; 2. only for an authenticated subject does identity construct the ;; permission query {Subject, Scope, Action, Resource} and HAND IT OFF ;; to acl. acl returns permit | deny; deny is {error, forbidden} — a ;; 403. identity itself never decides permission. ;; ;; The real decider is acl-on-sx (Datalog), which runs as a different ;; guest language on SX and is wired in at the integration layer. Here the ;; acl side is a labelled STUB process so the boundary is exercised: it ;; permits when the Action is within the token's granted Scope. Swap the ;; stub pid for the acl adapter and the boundary is unchanged. ;; ;; check(TokReg, Acl, Token, Action, Resource) -> ;; {ok, Subject} | {error, unauthenticated} | {error, forbidden} (define identity-delegation-source "-module(identity_delegation).\n\n check(TokReg, Acl, Token, Action, Resource) ->\n case identity_tokens:introspect(TokReg, Token) of\n {inactive} ->\n {error, unauthenticated};\n {active, Subject, _Client, Scope} ->\n Acl ! {acl_query, Subject, Scope, Action, Resource, self()},\n receive {acl_verdict, V} ->\n case V of\n permit -> {ok, Subject};\n deny -> {error, forbidden}\n end\n end\n end.\n\n %% --- stub acl decider (stands in for acl-on-sx / Datalog) ---\n %% Permits iff the Action is one of the token's granted scopes. The real\n %% acl decides on rules + facts; this only exercises the handoff shape.\n stub_acl() ->\n spawn(fun () -> acl_loop() end).\n\n acl_loop() ->\n receive\n {acl_query, _Subject, Scope, Action, _Resource, From} ->\n From ! {acl_verdict, decide(Action, Scope)},\n acl_loop();\n stop ->\n ok\n end.\n\n decide(Action, Scope) ->\n case member(Action, Scope) of\n true -> permit;\n false -> deny\n end.\n\n member(_, []) -> false;\n member(X, [Y | Rest]) ->\n case X =:= Y of\n true -> true;\n false -> member(X, Rest)\n end.") (define identity-load-delegation! (fn () (identity-load-token!) (erlang-load-module identity-delegation-source)))