Runs a single activity task. Spawned by the
Hourglass.ActivityExecutor.DynamicSupervisor as a Task;
invokes <activity_module>.handle(name, args); classifies the
outcome through Hourglass.Activity.RetryClassifier;
wraps the result into an ActivityTaskCompletion proto; ships back
via Hourglass.BridgeHolder.complete_activity_task/2.
Activities are plain Elixir — no determinism constraint, no special
process model. They do NOT hold a raw bridge reference(); they
call the BridgeHolder API with their task_queue. If the holder no
longer has the task queue registered (Worker is shutting down) the
call returns {:error, :worker_not_registered} and is logged but
not raised — the activity result is lost from this dispatch and
Core will redeliver via heartbeat / start-to-close timeout.
Failure handling
Both the {:error, reason} return path and the rescue clause feed the
failure value through Hourglass.Activity.RetryClassifier.classify/2.
The classifier's verdict drives Failure.application_failure_info:
type→ApplicationFailureInfo.typeandFailure.sourcemessage→Failure.messagedetails→ApplicationFailureInfo.details(when non-nil)non_retryable→ApplicationFailureInfo.non_retryable(computed asclassification != :retryable—:unclassifiedis treated as non-retryable in production for safety)
Failure.application_failure_info.non_retryable: true is what tells
Temporal Server to stop retrying.
Contract violation
An activity's handle/2 must return {:ok, term()} | {:error, term()}.
Any other return shape is a ContractViolation: non-retryable, with a
message naming the offending value.
Test-mode strict
Under MIX_ENV=test (or when :strict_unclassified_activity_failures
is configured true), an :unclassified classification raises an
ArgumentError BEFORE encoding the completion. The error names the
originating activity and points at the classifier so missing clauses
surface at the source. In production the strict guard collapses at
compile time and the runner falls back to the safe non-retryable
encoding.