;; 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)))
