-module(aion@workflow@run). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/aion/workflow/run.gleam"). -export([timestamp_to_milliseconds/1, run_with_default/2, run/1, id/0, now/0, random/0, random_int/2]). -export_type([timestamp/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" workflow.run (recorded activity dispatch) + now + random (determinism bindings)\n"). -opaque timestamp() :: {timestamp, integer()}. -file("src/aion/workflow/run.gleam", 21). ?DOC(" Return the canonical millisecond representation of a deterministic timestamp.\n"). -spec timestamp_to_milliseconds(timestamp()) -> integer(). timestamp_to_milliseconds(Timestamp) -> erlang:element(2, Timestamp). -file("src/aion/workflow/run.gleam", 44). ?DOC( " Dispatch an activity, supplying the workflow-level default task queue used\n" " when the activity itself selects none.\n" "\n" " Resolution precedence (activity override > workflow default > the named\n" " `\"default\"` queue) is applied once at the engine schedule seam; this SDK\n" " only carries both unresolved selections across the boundary. A `None`\n" " workflow default behaves exactly as `run`.\n" ). -spec run_with_default( aion@activity:activity(any(), GJM), gleam@option:option(binary()) ) -> {ok, GJM} | {error, aion@error:activity_error()}. run_with_default(Activity_value, Workflow_default_task_queue) -> aion@internal@activity_dispatch:run( Activity_value, Workflow_default_task_queue ). -file("src/aion/workflow/run.gleam", 33). ?DOC( " Dispatch an activity through the single recorded side-effect boundary.\n" "\n" " Plain Gleam workflow code is re-run on replay. The only recorded\n" " side-effectful path exposed by this SDK is `run` (and later concurrency\n" " combinators over activities); there is intentionally no generic\n" " `side_effect(fn)` escape hatch. The activity input is encoded with the\n" " activity's input `Codec`, the engine dispatches and records via AD, and the\n" " returned payload is decoded with the output `Codec`.\n" ). -spec run(aion@activity:activity(any(), GJG)) -> {ok, GJG} | {error, aion@error:activity_error()}. run(Activity_value) -> run_with_default(Activity_value, none). -file("src/aion/workflow/run.gleam", 58). ?DOC( " Return AD's recorded deterministic timestamp.\n" "\n" " Return the current workflow execution's unique identifier.\n" "\n" " The engine assigns a v4 UUID at workflow start and records it in the\n" " `WorkflowStarted` event. This NIF reads the identifier from the\n" " process's registered workflow handle, so it is stable across replay.\n" ). -spec id() -> {ok, binary()} | {error, aion@error:engine_error()}. id() -> case aion_flow_ffi:workflow_id() of {ok, Workflow_id} -> {ok, Workflow_id}; {error, Raw_error} -> {error, {engine_failure, Raw_error}} end. -file("src/aion/workflow/run.gleam", 104). -spec parse_timestamp(binary()) -> {ok, timestamp()} | {error, aion@error:engine_error()}. parse_timestamp(Raw) -> case gleam_stdlib:parse_int(Raw) of {ok, Milliseconds} -> {ok, {timestamp, Milliseconds}}; {error, _} -> {error, {engine_failure, <<"Invalid deterministic timestamp: "/utf8, Raw/binary>>}} end. -file("src/aion/workflow/run.gleam", 68). ?DOC( " This is the only time source exposed to workflow code. Workflow authors must\n" " not call wall-clock APIs such as Gleam/Erlang clocks from workflow logic, as\n" " ambient time would desynchronise replay.\n" ). -spec now() -> {ok, timestamp()} | {error, aion@error:engine_error()}. now() -> case aion_flow_ffi:now() of {ok, Raw_timestamp} -> parse_timestamp(Raw_timestamp); {error, Raw_error} -> {error, {engine_failure, Raw_error}} end. -file("src/aion/workflow/run.gleam", 114). -spec parse_float(binary(), binary()) -> {ok, float()} | {error, aion@error:engine_error()}. parse_float(Raw, Label) -> case gleam_stdlib:parse_float(Raw) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {engine_failure, <<<<<<"Invalid deterministic "/utf8, Label/binary>>/binary, ": "/utf8>>/binary, Raw/binary>>}} end. -file("src/aion/workflow/run.gleam", 79). ?DOC( " Draw a deterministic floating-point value from AD's seeded RNG.\n" "\n" " The engine keys the RNG seed on WorkflowId + RunId so replay observes the\n" " same sequence. Workflow authors must not call ambient entropy sources.\n" ). -spec random() -> {ok, float()} | {error, aion@error:engine_error()}. random() -> case aion_flow_ffi:random() of {ok, Raw_random} -> parse_float(Raw_random, <<"random"/utf8>>); {error, Raw_error} -> {error, {engine_failure, Raw_error}} end. -file("src/aion/workflow/run.gleam", 124). -spec parse_int(binary(), binary()) -> {ok, integer()} | {error, aion@error:engine_error()}. parse_int(Raw, Label) -> case gleam_stdlib:parse_int(Raw) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {engine_failure, <<<<<<"Invalid deterministic "/utf8, Label/binary>>/binary, ": "/utf8>>/binary, Raw/binary>>}} end. -file("src/aion/workflow/run.gleam", 90). ?DOC( " Draw a deterministic integer in the engine-defined inclusive range.\n" "\n" " Values come from AD's seeded RNG through the FFI boundary; no wall-clock or\n" " ambient entropy binding is exposed by the SDK.\n" ). -spec random_int(integer(), integer()) -> {ok, integer()} | {error, aion@error:engine_error()}. random_int(Min, Max) -> case Min > Max of true -> {error, {engine_failure, <<"Invalid deterministic random_int range: min is greater than max"/utf8>>}}; false -> case aion_flow_ffi:random_int( erlang:integer_to_binary(Min), erlang:integer_to_binary(Max) ) of {ok, Raw_random} -> parse_int(Raw_random, <<"random_int"/utf8>>); {error, Raw_error} -> {error, {engine_failure, Raw_error}} end end.