The budget of the preflight: the step that runs in the caller, before a worker exists, and decides what the job closure
copied into the worker holds (the internal run_prepared entry point of Feel.Worker). Internal: not a supported API.
A worker is another process, so what it runs is copied into its heap and counted against :max_input_bytes. A selector
(Dmn.Execution.prepare/6 for DMN, the binding selection of Feel.run/3 for FEEL) picks the part of the model and the
bindings a call needs, and this module bounds that work, because no worker's deadline or heap policy protects it yet:
project/2gives the selector a state with a step budget of at most 100,000 (less for a small memory or input policy) and the deadline. The deadline is read when the selection starts and then every 64 steps (step/1; a step is constant work, and the transfer walk checks every 256).- Only library-owned, shallow work runs here, never host code: no extension, callback or resolver is called.
- What it cannot decide, or cannot afford (
fallback/0: a key that is too long, a spent budget, uncertain metadata), makes it send the original, complete argument instead, which faces the same transfer check. Expiry is a{:error, :timeout}of the whole operation, not a fresh deadline.
Selection is an optimization of what is copied and never validation: the worker validates again. See the preflight in the architecture.
Summary
Types
@type t() :: %{deadline: integer(), left: non_neg_integer()}