Add hierarchical evaluation cancellation to Boa

boa-dev/boa

Add cancellation controllers and signals that propagate through nested script, module, and job execution.

Task instructions
Hosts need cancellation across nested evaluations, module phases, and queued jobs without discarding `Context`. Implement evaluation cancellation with parent/child controllers, signals, and cancellation checkpoints. ## Required public capabilities - Public entry points must include: `Context::{new_evaluation_controller, new_child_evaluation_controller, eval_with_signal, enqueue_job_with_signal, run_jobs_with_signal}`, `Script::evaluate_with_signal`, `Module::{evaluate_with_signal, load_link_evaluate_with_signal}`, `EvaluationController::{new, signal, cancel, cancel_with_reason}`, and `EvaluationSignal::{child_controller, is_cancelled, cancellation_reason}`. - `EvaluationController::signal()` returns a signal observing the controller's cancellation state. `EvaluationSignal::child_controller()` creates a controller whose signal is a child of that signal. - Cancellation authority is capability-separated: evaluation, enqueue, and run APIs accept signals, and cancellation methods are exposed by controllers rather than signals. - Controller clones share cancellation authority and state. Signal clones share the same observed state and reason lineage. - Controller and signal values must both be usable as captured values in engine callback/job closures. ## Interface clarifications - APIs that evaluate, enqueue, or run under a signal must take the signal by shared reference, not ownership. - For `Script::evaluate_with_signal` and both `Module::*_with_signal` entry points, argument order is `(signal, context)` after `&self`. - `Context` signal-aware argument order is: `eval_with_signal(source, signal)`, `enqueue_job_with_signal(job, signal)`, and `run_jobs_with_signal(signal)`. - `Context::{eval_with_signal, enqueue_job_with_signal, run_jobs_with_signal}` must each return a fallible result with the same result-shape category as its non-signal analog. - `cancel_with_reason` must accept any caller value convertible into the engine value type. - `cancel` and `cancel_with_reason` return `bool` indicating whether that call performed the first effective cancellation. - `cancellation_reason(context)` must return an optional value (`None` when not cancelled, `Some(reason)` when cancelled). - For descendant signals, `cancellation_reason(context)` must surface an inherited ancestor cancellation reason unless the descendant's controller already established its own first effective reason. - Module evaluate under a signal must return a fallible result whose success value is a promise. - Module load-link-evaluate under a signal must return a promise directly (not a fallible wrapper). ## Required behavior 1. Parent-controller cancellation must cascade to all descendant signals. 2. Cancelling a child controller must not cancel its parent signal. 3. Cancellation is first-wins: the first effective cancellation determines its reason and later attempts cannot replace it. `cancel` and `cancel_with_reason` must report whether the call performed the first effective cancellation. 4. Starting script evaluation with an already-cancelled signal must fail before user code runs. 5. Cancelling during script execution must stop before later side effects and not corrupt future `Context` usage. 6. `Module::evaluate_with_signal` and `Module::load_link_evaluate_with_signal` must reject with the same cancellation reason value that cancelled the signal. For an already-cancelled signal, `Module::evaluate_with_signal` must still return success with a rejected promise. 7. `Module::load_link_evaluate_with_signal` must check cancellation at phase boundaries so cancellation after load but before evaluate still rejects and prevents side effects. 8. `Context::enqueue_job_with_signal(job, signal)` must fail immediately when `signal` is already cancelled and must not enqueue that job. 9. Jobs enqueued with an evaluation signal are associated with the exact signal used when enqueueing. 10. Jobs spawned by code that is running under an evaluation signal are automatically associated with that same signal. 11. Before each associated job starts, if its signal is cancelled (directly or via parent), that job is skipped. 12. Queue behavior when cancellation happens mid-drain: started jobs may complete, while later not-yet-started jobs for the cancelled signal are skipped. 13. If cancellation happens without a custom reason, `cancellation_reason(context)` must produce an Error-like value whose string contains `AbortError`. 14. `Context::run_jobs_with_signal(signal)` must fail immediately when `signal` is already cancelled and must not drain queued jobs in that failed call. IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
Outcome
  1. Passed 241 steps$30.62319k tokens
  2. Failed 185 steps$19.78269k tokens