{-# LANGUAGE RankNTypes #-}

{- | The domain-span tracing ports: the abstract interfaces the core serve path and
mirror worker open their hand-added spans through, decoupled from any tracing backend.

The serve path brackets two domain spans an operator cares about -- the per-version
rule verdict and the synchronous-to-asynchronous mirror hand-off -- and the mirror
worker brackets one -- the per-job fetch → verify → publish. This module defines those
bracket operations as records of functions (the Handle pattern), each parametric in the
bracketed action's result so the span wraps the real work without seeing its shape. A
consumer records through its port and never names an OpenTelemetry tracer; the
application supplies the OTel-backed implementations behind them (see
@Ecluse.Telemetry.Tracing@), and a test supplies a pass-through double that simply runs
the body.

Two ports are defined: 'TracingPort' for the serve path's two spans and
'WorkerTracingPort' for the worker's mirror-job span; each carries exactly the spans its
consumer opens.
-}
module Ecluse.Core.Telemetry.Span (
    -- * The serve-path tracing port
    TracingPort (..),

    -- * The worker tracing port
    WorkerTracingPort (..),
    JobSpanOutcome (..),
) where

import Ecluse.Core.Package (PackageName)
import Ecluse.Core.Queue (RemoteSpanContext)
import Ecluse.Core.Server.Response (ServeDecision)
import Ecluse.Core.Version (Version)

{- | The domain-span tracing port -- a record of bracket operations over a backend
whose closure captures its tracer. Each field runs a bracketed @IO@ action within a
span and returns its result; the fields are rank-2 (parametric in the result) so one
port value serves every call site whatever the body yields. The implementation is
inert when tracing is off, so the serve path brackets unconditionally.
-}
data TracingPort = TracingPort
    { TracingPort
-> forall a.
   PackageName -> Version -> IO (a, ServeDecision) -> IO a
spanRuleEval :: forall a. PackageName -> Version -> IO (a, ServeDecision) -> IO a
    {- ^ Bracket the per-version rule evaluation: the body yields its result and the
    verdict to record on the span (the decision and, on a denial, the deciding rule,
    reason class, and message), so a refusal is explainable from the trace alone.
    -}
    , TracingPort
-> forall a.
   PackageName
   -> Version
   -> Text
   -> (a -> Maybe Text)
   -> (Maybe RemoteSpanContext -> IO a)
   -> IO a
spanMirrorEnqueue ::
        forall a.
        PackageName ->
        Version ->
        Text ->
        (a -> Maybe Text) ->
        (Maybe RemoteSpanContext -> IO a) ->
        IO a
    {- ^ Bracket the serve-time hand-off to the asynchronous mirror, carrying the
    package, version, and the artifact's authoritative URL. The body is handed the
    enqueueing span's trace context (or 'Nothing' when tracing is off) to stamp onto
    the mirror job, so the worker's per-job span can link back across the async hop.
    The projection maps the body's result onto an optional failure detail: a 'Just'
    marks the span errored, so a swallowed best-effort enqueue failure is still
    explainable from the trace.
    -}
    , TracingPort -> forall a. PackageName -> IO a -> IO a
spanPackumentGate ::
        forall a.
        PackageName ->
        IO a ->
        IO a
    , TracingPort -> forall a. PackageName -> IO a -> IO a
spanMetadataFetch ::
        forall a.
        PackageName ->
        IO a ->
        IO a
    , TracingPort -> forall a. PackageName -> IO a -> IO a
spanMetadataDecode ::
        forall a.
        PackageName ->
        IO a ->
        IO a
    -- ^ Bracket the gating phase of a packument request, which runs the rules and filter on the public upstream document.
    }

{- | The mirror worker's domain-span tracing port -- the worker analogue of 'TracingPort',
kept a separate record so the worker brackets exactly its own span. The single field
brackets the per-job fetch → verify → publish, projecting the job's terminal result onto
the span's outcome ('JobSpanOutcome'); it is rank-2 (parametric in the result) so one
port value serves the call site whatever the body yields. The implementation is inert
when tracing is off, so the worker brackets unconditionally.
-}
newtype WorkerTracingPort = WorkerTracingPort
    { WorkerTracingPort
-> forall a.
   PackageName
   -> Version
   -> Maybe RemoteSpanContext
   -> (a -> JobSpanOutcome)
   -> IO a
   -> IO a
wtpMirrorJobSpan ::
        forall a.
        PackageName ->
        Version ->
        Maybe RemoteSpanContext ->
        (a -> JobSpanOutcome) ->
        IO a ->
        IO a
    {- ^ Bracket the worker's per-job fetch → verify → publish, carrying the package and
    version, the trace context the job was enqueued under (to __link__ the per-job span
    back to the enqueueing request across the async hop, or 'Nothing' for a job that
    carried none), and, once the job finishes, the projected outcome (the bounded outcome
    label always, and a failure detail that marks the span errored when the job did not
    publish).
    -}
    }

{- | The projection a caller supplies for the mirror-job span: the bounded outcome label
always, and, for a job that did not publish, the detail that marks the span errored. A
small record (rather than the worker's own outcome type) so the tracing port does not
depend on the worker loop.
-}
data JobSpanOutcome = JobSpanOutcome
    { JobSpanOutcome -> Text
jobSpanLabel :: Text
    -- ^ The bounded outcome label (e.g. @succeeded@ \/ @dropped@ \/ @retried@).
    , JobSpanOutcome -> Maybe Text
jobSpanError :: Maybe Text
    -- ^ The failure detail when the job did not publish; 'Nothing' on success.
    }
    deriving stock (JobSpanOutcome -> JobSpanOutcome -> Bool
(JobSpanOutcome -> JobSpanOutcome -> Bool)
-> (JobSpanOutcome -> JobSpanOutcome -> Bool) -> Eq JobSpanOutcome
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: JobSpanOutcome -> JobSpanOutcome -> Bool
== :: JobSpanOutcome -> JobSpanOutcome -> Bool
$c/= :: JobSpanOutcome -> JobSpanOutcome -> Bool
/= :: JobSpanOutcome -> JobSpanOutcome -> Bool
Eq, Int -> JobSpanOutcome -> ShowS
[JobSpanOutcome] -> ShowS
JobSpanOutcome -> String
(Int -> JobSpanOutcome -> ShowS)
-> (JobSpanOutcome -> String)
-> ([JobSpanOutcome] -> ShowS)
-> Show JobSpanOutcome
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> JobSpanOutcome -> ShowS
showsPrec :: Int -> JobSpanOutcome -> ShowS
$cshow :: JobSpanOutcome -> String
show :: JobSpanOutcome -> String
$cshowList :: [JobSpanOutcome] -> ShowS
showList :: [JobSpanOutcome] -> ShowS
Show)