Skip to main content

aion_server/
run.rs

1//! Run loop for the Aion workflow server: tracing initialization,
2//! configuration load, transport startup, and signal-driven graceful
3//! shutdown.
4//!
5//! This is the library entry point behind the `aion server` command. It
6//! preserves the operational contract of the former standalone
7//! `aion-server` binary: exit code 2 for configuration errors, the drain
8//! outcome's exit code on shutdown, and 130 when a second termination
9//! signal forces immediate exit.
10
11use std::process::ExitCode;
12
13use tracing::{error, info, warn};
14
15use crate::{
16    ServerConfig, ServerError, ServerState,
17    config::{CliOverrides, NamespaceMode, StoreBackend},
18    observability,
19    shutdown::ShutdownOutcome,
20};
21
22mod doors;
23mod outbox_commission;
24mod transports;
25
26use doors::{bind_doors, serve_until_shutdown};
27use outbox_commission::{
28    BackpressureSettings, maybe_spawn_cluster_supervisor, maybe_spawn_outbox_dispatcher,
29    rebuild_outbox_boot_state,
30};
31use transports::{serve_grpc, serve_http};
32
33/// Run the Aion workflow server until it shuts down, returning the process
34/// exit code.
35///
36/// Initializes the JSON tracing subscriber, loads and validates the merged
37/// configuration (file, environment, then `overrides`), serves the gRPC and
38/// HTTP transports, and drains gracefully after the first termination
39/// signal. Every failure is logged through tracing and mapped to the exit
40/// code contract above; the caller only has to exit with the returned code.
41pub async fn run(overrides: CliOverrides) -> ExitCode {
42    // `Box::pin`ned because the boot future is large — it owns the whole
43    // pre-serve composition — and an oversized future on the stack of every
44    // caller of this entry point is a cost nothing else here pays for.
45    match Box::pin(run_server(overrides)).await {
46        Ok(code) => code,
47        Err(error) => {
48            error!(%error, "aion-server failed");
49            if error.is_config() {
50                ExitCode::from(2)
51            } else {
52                ExitCode::FAILURE
53            }
54        }
55    }
56}
57
58/// The where-to-edit half of the missing `outbox.liminal_listen_address`
59/// refusal: a liminal outbox refusal must name the FILE to edit, not just the
60/// key — the operator reading it is exactly the operator who did not write
61/// the config (a scaffolded or setup-script home).
62fn liminal_address_hint(source: &crate::config::ConfigSource) -> String {
63    match source {
64        crate::config::ConfigSource::BuiltInDefaults => {
65            "set AION_OUTBOX_LIMINAL_LISTEN_ADDRESS, or add `liminal_listen_address = \
66             \"127.0.0.1:50061\"` to `[outbox]` in a config file"
67                .to_owned()
68        }
69        source => format!(
70            "add `liminal_listen_address = \"127.0.0.1:50061\"` to `[outbox]` in the {source}"
71        ),
72    }
73}
74
75/// Everything `run_server` must capture from the merged config BEFORE
76/// `ServerState::build` consumes it — the wiring below the build reads these,
77/// not the (moved) config.
78struct PreBuildCaptures {
79    /// The selected backend, surfaced so the boot banner records it.
80    store_backend: StoreBackend,
81    /// Static shard assignment (SS-1): the operator's pinned shard set from
82    /// `[store] owned_shards`. Empty means own ALL shards (single-node
83    /// default). The set is carried into `RuntimeConfig` by `into_parts` and
84    /// applied to the `EngineBuilder` during state construction; surfaced
85    /// here so the boot banner records which shards this node serves. No
86    /// election is performed.
87    owned_shards: Vec<usize>,
88    /// The outbox settings, so the (default-off) outbox dispatcher can be
89    /// wired after state is up. The dispatcher shares the engine's
90    /// already-opened haematite store via `state.outbox_store()`, so no
91    /// store settings are needed.
92    outbox_config: crate::config::OutboxConfig,
93    /// Control-Plane Phase 2 (P2-Q2): the keyed-backpressure inputs — the
94    /// generous platform-default ceiling and this node's owned-shard
95    /// fraction. On a single-node / own-all boot the fraction is 1, so
96    /// per-node ceilings equal the cluster-wide quota and, with the generous
97    /// default and no tenant override, the ceiling never engages
98    /// (byte-identical claim).
99    backpressure_settings: BackpressureSettings,
100    /// The SS-5b failover supervisor knobs. Only a distributed haematite
101    /// boot carries a `[store.cluster]` section; this is `None` for every
102    /// single-node boot, so no supervisor is ever spawned.
103    cluster_config: Option<crate::config::ClusterConfig>,
104    /// The managed-worker supervision policy. Resolution already happened
105    /// during config validation, so this cannot surprise an operator at
106    /// boot; it is re-resolved here because the policy is COMMISSIONED onto
107    /// the supervisor built into state below, and a server without the
108    /// section supervises nothing.
109    supervision_policy: Option<crate::worker::SupervisionPolicy>,
110}
111
112impl PreBuildCaptures {
113    fn from_config(config: &ServerConfig) -> Result<Self, ServerError> {
114        Ok(Self {
115            store_backend: config.store.backend,
116            owned_shards: config.store.owned_shards.clone(),
117            outbox_config: config.outbox.clone(),
118            backpressure_settings: BackpressureSettings::from_config(config),
119            cluster_config: config.store.cluster.clone(),
120            supervision_policy: config.worker_supervision.resolve()?,
121        })
122    }
123}
124
125async fn run_server(cli: CliOverrides) -> Result<ExitCode, ServerError> {
126    observability::tracing::init()?;
127
128    // #180: a boot that discovers no config anywhere first scaffolds
129    // `<AION_HOME>/config.toml` from the embedded template (claim-only-when-
130    // empty), then loads it — config LOAD itself stays pure and read-only.
131    let loaded = crate::config::load_or_scaffold(&cli)?;
132    loaded.resolution.ensure_private_home()?;
133    // Arm the death note as early as the home exists, so every later failure
134    // path — including config validation and state build — runs inside the
135    // ARMED/DISARMED bracket. Two anonymous server deaths on 2026-08-16 are
136    // why this exists; see the module docs for the exact coverage.
137    let death_note = crate::death_note::DeathNote::arm(&loaded.resolution.home)?;
138    let home = loaded.resolution.home.clone();
139    loaded.resolution.log_startup();
140    let liminal_address_hint = liminal_address_hint(&loaded.resolution.source);
141    // The boot-side config heal already logged each inserted field by name;
142    // the banner below carries the count so one line summarizes the boot.
143    let config_healed_field_count = loaded.healed.inserted.len();
144    let config = loaded.config;
145    reject_auth_without_feature(&config)?;
146    // The revision, not just the version. A crate version cannot distinguish
147    // two builds from different commits of the same version, and that is the
148    // distinction an operator needs when deciding whether a restart restores
149    // what was running or substitutes something else (#123). Read here, at
150    // the top of the boot, because the BIRTH CLAIM records it: a `status`
151    // against a booting server must be able to say which build is booting.
152    let build = crate::build_identity::BuildIdentity::current();
153    // 🔴 The home is claimed HERE — before the store is opened, before
154    // anything that can take minutes. A record written at bind left the whole
155    // recovery window invisible: `status` said the home was unclaimed, `stop`
156    // said there was nothing to stop, and the launcher's port probe read the
157    // home as empty and started ANOTHER server, which blocked without a word
158    // on the store's writer lock. Four stacked that way on 2026-08-26.
159    //
160    // The claim needs nothing but this process's own identity and the
161    // addresses the (already-loaded) configuration says this boot will bind,
162    // so it can run at stage zero and refuse a colliding sibling outright.
163    let pid_file_guard = claim_home_at_birth(&home, &config, build.commit)?;
164    let boot_stage = pid_file_guard.stage_reporter();
165    boot_stage.report(
166        crate::control::stage::STAGE_CONFIG,
167        format!(
168            "configuration resolved from {} for home {}",
169            loaded.resolution.source,
170            home.display()
171        ),
172    );
173    let captures = PreBuildCaptures::from_config(&config)?;
174    let state = ServerState::build(config, &boot_stage).await?;
175    reject_tls_until_supported(&state)?;
176
177    let runtime = state.runtime_config();
178    let grpc_address = runtime.listen.grpc;
179    let http_address = runtime.listen.http;
180    log_startup_banner(
181        &state,
182        &captures,
183        &build,
184        &death_note,
185        config_healed_field_count,
186    );
187    // #189 slice one: the built-in update check ships the same way, under the
188    // same only-the-empty-case install rule. Installing makes it STARTABLE
189    // and nothing else — no check runs without an explicit operator act.
190    install_embedded_surfaces(&state).await;
191    let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
192    // LSUB-4-1: a distributed haematite boot carries a `[store.cluster]` section.
193    // The single outbox dispatcher task is spawned in BOTH modes; the difference
194    // is only how ownership is enforced. Single-node (`None`) owns all shards by
195    // construction (`owned_shard_scope() == None`), so its claim sweeps see every
196    // row. Clustered (`Some`) relies on `claim_outbox_rows`' `owned_shard_scope()`
197    // filter — already seeded by `set_owned_shards` during `ServerState::build`,
198    // which runs before this point — so each node only ever claims rows on the
199    // shards it owns. Compute the flag here where the cluster section is in
200    // scope; pass it to the gate so the boot banner records the mode.
201    let outbox_clustered = captures.cluster_config.is_some();
202    // Dormant by default: only when `outbox.enabled` is set does the
203    // non-replayed outbox dispatcher task start. With the flag off (the
204    // default) nothing here runs and server behaviour is unchanged.
205    // Hold the liminal worker listener (if any) for the server's lifetime: it is
206    // dropped at the end of `run_server`, after the serve `select!` completes, so
207    // its accept worker stops cleanly on shutdown via the listener's own `Drop`.
208    // #204/#253: rebuild the pause dispatch-hold and settle terminal
209    // workflows' stranded outbox rows BEFORE the dispatcher's first claim.
210    rebuild_outbox_boot_state(&state, &captures.outbox_config).await;
211    let _outbox_worker_listener = maybe_spawn_outbox_dispatcher(
212        &state,
213        &captures.outbox_config,
214        outbox_clustered,
215        captures.backpressure_settings,
216        &shutdown_rx,
217        &liminal_address_hint,
218    )?;
219    // SS-5b: a distributed boot whose peers declare owned shards runs the cluster
220    // supervisor — automatic failover detection. A single-node boot spawns
221    // nothing here (the method returns `false`), so default behaviour is
222    // unchanged.
223    maybe_spawn_cluster_supervisor(&state, captures.cluster_config.as_ref(), &shutdown_rx)?;
224    // #176: the worker heartbeat expiry sweeper is ALWAYS commissioned —
225    // dead-worker detection is a liveness correctness property, not an opt-in
226    // feature. It is the production caller of `fail_expired_workers`: a worker
227    // whose stream stays open while its process wedges (stops heartbeating
228    // without disconnecting) is expired, deregistered with the provable Timeout
229    // reason, and its in-flight tasks surface as TRANSPORT losses, re-dispatched
230    // attempt-neutrally rather than charged to the action's retry budget.
231    // Cadence derives from `worker.heartbeat_window` (quarter-window, clamped to
232    // [1s, window]; the default 30s window sweeps every 7.5s) — deliberately no
233    // separate config knob. It drains on the same shutdown watch as the
234    // transports; dropping the JoinHandle only detaches the task.
235    drop(state.spawn_heartbeat_sweeper(shutdown_rx.clone()));
236    commission_worker_supervision(&state, captures.supervision_policy).await;
237    withdraw_orphaned_auto_workers(&state).await;
238    // A record freezes the address it was minted with and the supervisor
239    // replays that argv verbatim, so an operator who moves
240    // `[outbox] liminal_listen_address` would otherwise restart into a fleet of
241    // built-in agent workers all dialling a port nothing binds. Correct the
242    // connection — and only the connection — before anything else is decided.
243    drop(crate::worker::auto_provision::refresh_dial_addresses(&state).await);
244    // Bind both listeners, then FILL the record this incarnation already
245    // holds: the bound addresses, the resolved drain window, and the move out
246    // of BOOTING into SERVING. The claim itself happened at birth; there is
247    // no second claim, and nothing here can take another server's record.
248    boot_stage.report(
249        crate::control::stage::STAGE_BINDING,
250        format!("binding http {http_address} and grpc {grpc_address}"),
251    );
252    let doors = bind_doors(
253        &pid_file_guard,
254        grpc_address,
255        http_address,
256        state.runtime_config().drain_timeout,
257    )
258    .await?;
259    let identity_pid = doors.identity_pid;
260    // Instant doors: the startup catch-up legs (owed timer fires, schedule
261    // catch-up) run as a background task CONCURRENT with the transports —
262    // the backlog has no upper bound, and a boot that blocks on it keeps the
263    // doors shut for the whole sweep (the 2026-08-24 estate outage shape:
264    // 37+ minutes of healthy catch-up with every listener refusing).
265    // Workflow-residency recovery already ran inside `ServerState::build`,
266    // so every surface the transports serve answers correctly while the
267    // catch-up drains behind them.
268    drop(state.spawn_startup_catchup(shutdown_rx.clone())?);
269    let mut grpc = tokio::spawn(serve_grpc(
270        state.clone(),
271        doors.grpc_listener,
272        doors.bound_grpc,
273        shutdown_rx.clone(),
274    ));
275    let mut http = tokio::spawn(serve_http(
276        state.clone(),
277        doors.http_listener,
278        doors.bound_http,
279        shutdown_rx,
280    ));
281
282    // From here the graceful drain is watching for a termination signal, so
283    // the death note's watcher goes back to merely OBSERVING one. Announced
284    // as late as possible and no later: everything before this line is boot,
285    // and a termination signal during the boot must ABANDON it rather than be
286    // caught and answered by nobody (the 2026-08-26 finding — see
287    // `DeathNote::drain_owns_termination`).
288    death_note.drain_owns_termination();
289    let report =
290        serve_until_shutdown(&state, &pid_file_guard, &shutdown_tx, &mut grpc, &mut http).await?;
291
292    // Every assistant harness this server was holding is shut down with its
293    // configured grace, and its session settled dormant with the reason. A
294    // process that outlived the server that owned it would be an orphan nothing
295    // could reach, cancel, or account for.
296    state.assistant_sessions().shutdown().await;
297
298    let outcome = report.outcome;
299    let exit_code = outcome.exit_code();
300    // The rich outcome crosses the process boundary through the death note
301    // (one file, one writer); the exit code keeps the #207 contract.
302    death_note.record_outcome(&crate::control::outcome::OutcomeRecord::from_report(
303        identity_pid,
304        &report,
305    ));
306    death_note.disarm(&format!(
307        "clean run-loop exit: shutdown outcome {outcome:?}"
308    ));
309    drop(pid_file_guard);
310    Ok(exit_code)
311}
312
313/// The surfaces this binary carries and installs at boot, after the engine has
314/// reloaded every persisted package and before the transports accept traffic.
315///
316/// #189 slice one: the built-in update check is installed only into a catalog
317/// holding no version of it — a fresh home. Installing makes it STARTABLE and
318/// nothing else: no check runs without an explicit operator act.
319///
320/// Assistant sessions whose harness process is gone are settled here, before any
321/// caller can read one: a session left unsettled would project the fallback
322/// state, and the settlement is WRITTEN BACK as an appended record with its
323/// cause, so the next reader projects it rather than recomputing the same
324/// decision. A sweep that cannot run is logged and the boot continues — a server
325/// whose past sessions could not be settled is a server with stale session
326/// states, not a server that must refuse to start.
327async fn install_embedded_surfaces(state: &ServerState) {
328    crate::update_check::install_embedded_update_check_for_server(state).await;
329    if let Err(error) = state.assistant_sessions().sweep_orphans().await {
330        tracing::warn!(
331            %error,
332            "assistant sessions whose harness process is gone could not be settled at boot; \
333             their states will read as unsettled until the next successful sweep"
334        );
335        // The sweep has already recorded WHY on the registry, so the descriptor
336        // answers `sessions_enabled: false` with this store's own error rather
337        // than offering a surface that cannot record a conversation. The boot
338        // continues: a server whose assistant is unavailable is still a server.
339    }
340}
341
342/// The one line an operator greps for when they want to know what this server
343/// actually is: build identity, addresses, backend, which surfaces are on,
344/// which shards it owns, and where its death note lives.
345fn log_startup_banner(
346    state: &ServerState,
347    captures: &PreBuildCaptures,
348    build: &crate::build_identity::BuildIdentity,
349    death_note: &crate::death_note::DeathNote,
350    config_healed_field_count: usize,
351) {
352    let runtime = state.runtime_config();
353    let grpc_address = runtime.listen.grpc;
354    let http_address = runtime.listen.http;
355    let workflow_packages: Vec<String> = runtime
356        .workflow_packages
357        .iter()
358        .map(|path| path.display().to_string())
359        .collect();
360    // #139: the server-resolved workspace root (the aion home's `clones/`
361    // directory) that declared bodies expand `{workspace_root}` with. Reported
362    // here so composition points (setup.sh today, the workspace verb later)
363    // READ the value from the server that will use it instead of re-deriving
364    // it. An unresolvable root is reported as exactly that — never fabricated;
365    // a placeholder-bearing dispatch will refuse terminally with this reason.
366    // The rendering itself is `WorkspaceRoot::banner_value`, pinned by its own
367    // two-case test, so the banner and the tests cannot drift apart.
368    let workspace_root = state.workspace_root().banner_value();
369    info!(
370        version = env!("CARGO_PKG_VERSION"),
371        build = %build.line(),
372        commit = build.commit,
373        grpc_address = %grpc_address,
374        http_address = %http_address,
375        default_namespace = %runtime.default_namespace,
376        namespace_mode = namespace_mode_label(&runtime.namespace.mode),
377        store_backend = store_backend_label(captures.store_backend),
378        auth_enabled = runtime.auth.enabled,
379        deploy_enabled = runtime.deploy.enabled,
380        metrics_enabled = runtime.metrics.enabled,
381        workspace_root = %workspace_root,
382        death_note = %death_note.path().display(),
383        workflow_package_count = workflow_packages.len(),
384        workflow_packages = ?workflow_packages,
385        owned_shards = ?captures.owned_shards,
386        owns_all_shards = captures.owned_shards.is_empty(),
387        config_healed_field_count,
388        "aion-server startup banner"
389    );
390}
391
392/// Claim this home before anything slow happens.
393///
394/// Everything the claim needs is available at stage zero: this process's own
395/// identity, the build's commit, and the addresses the already-loaded
396/// configuration says this boot will bind. Nothing here touches the store,
397/// which is the entire point — the record has to exist BEFORE the minutes.
398fn claim_home_at_birth(
399    home: &std::path::Path,
400    config: &ServerConfig,
401    commit: &str,
402) -> Result<crate::control::PidFileGuard, ServerError> {
403    let intended = crate::control::IntendedAddresses {
404        http: config.server.listen_address,
405        grpc: config.server.grpc_address,
406    };
407    crate::control::claim_at_birth(home, &birth_record(commit, intended)?, intended)
408}
409
410/// This incarnation's record as it exists at BIRTH.
411///
412/// Identity, build, and the addresses the already-loaded configuration says
413/// this boot WILL bind. The BOUND addresses stay `None` — nothing is bound,
414/// and that absence is a FACT rather than a placeholder: it is how a reader
415/// knows the doors are not open yet, and how `aion server status` says
416/// "still booting" instead of "unreachable". The INTENDED pair is recorded
417/// beside them because it is equally a fact at birth, and it is what lets a
418/// concurrent boot on this home decide collision against this server's
419/// configuration instead of presuming it. No drain window either — the
420/// runtime configuration has not been resolved into one yet.
421///
422/// [`Booting`]: crate::control::IncarnationState::Booting
423fn birth_record(
424    commit: &str,
425    intended: crate::control::IntendedAddresses,
426) -> Result<crate::control::PidRecord, ServerError> {
427    let identity = crate::control::incarnation::self_identity()?;
428    Ok(crate::control::PidRecord {
429        pid: identity.pid,
430        started_at_unix_secs: identity.started_at_unix_secs,
431        binary_sha256: identity.binary_sha256,
432        version: env!("CARGO_PKG_VERSION").to_owned(),
433        commit: commit.to_owned(),
434        state: crate::control::IncarnationState::Booting,
435        http_address: None,
436        grpc_address: None,
437        intended_http_address: Some(intended.http),
438        intended_grpc_address: Some(intended.grpc),
439        stage: None,
440        stage_detail: None,
441        stage_seq: 0,
442        stage_updated_at_unix_secs: 0,
443        drain_timeout_seconds: 0,
444    })
445}
446
447/// Install the operator's supervision policy and converge the fleet.
448///
449/// Uncommissioned is a first-class but never SILENT state: a server with no
450/// `[worker_supervision]` section supervises nothing, and every deployment that
451/// wanted to be running is named in the warning, so the gap between "the
452/// operator deployed a worker" and "nothing is running it" is never quiet.
453/// Withdraw every auto-provisioned worker record whose workflow no deployed
454/// package carries.
455///
456/// A record can outlive its workflow — the package it was staged for
457/// withdrawn, or gone with an upgrade — and would then be restarted forever
458/// against a document nothing deploys. The catalogue is read once; one that
459/// cannot be read withdraws nothing and says so.
460async fn withdraw_orphaned_auto_workers(state: &ServerState) {
461    match crate::worker::auto_provision::deployed_workflow_types(state).await {
462        Ok(deployed) => {
463            drop(crate::worker::auto_provision::withdraw_orphaned(state, &deployed).await);
464        }
465        Err(reason) => warn!(
466            %reason,
467            "the package catalogue could not be read at boot, so no auto-provisioned worker \
468             record was judged against it"
469        ),
470    }
471}
472
473async fn commission_worker_supervision(
474    state: &ServerState,
475    policy: Option<crate::worker::SupervisionPolicy>,
476) {
477    let supervisor = state.worker_supervisor();
478    let Some(policy) = policy else {
479        match supervisor.report().await {
480            Ok(report) => {
481                let wanted: Vec<&str> = report
482                    .workers
483                    .iter()
484                    .filter(|worker| worker.desired == aion_store::DesiredState::Running)
485                    .map(|worker| worker.name.as_str())
486                    .collect();
487                if wanted.is_empty() {
488                    info!("managed-worker supervision is not configured; no deployment wants it");
489                } else {
490                    warn!(
491                        deployments = wanted.join(", "),
492                        remedy = crate::worker::supervisor::UNCOMMISSIONED_REMEDY,
493                        "worker deployments want to be running but supervision is not configured"
494                    );
495                }
496            }
497            Err(error) => error!(
498                %error,
499                "managed-worker supervision is not configured and the deployment records \
500                 could not be read to say what that costs"
501            ),
502        }
503        return;
504    };
505    if !supervisor.commission(policy, crate::worker::ManagedExecutable::CurrentServer) {
506        error!("managed-worker supervision was already commissioned before boot completed");
507        return;
508    }
509    match supervisor.reconcile().await {
510        Ok(0) => info!("managed-worker supervision commissioned; no deployment wants to run"),
511        Ok(supervised) => info!(supervised, "managed-worker supervision commissioned"),
512        Err(error) => error!(%error, "managed-worker fleet could not be converged at boot"),
513    }
514}
515
516fn reject_auth_without_feature(config: &ServerConfig) -> Result<(), ServerError> {
517    if cfg!(not(feature = "auth")) && config.auth.enabled {
518        return Err(ServerError::Config {
519            message: "auth.enabled=true but binary compiled without auth feature".to_owned(),
520        });
521    }
522    Ok(())
523}
524
525fn reject_tls_until_supported(state: &ServerState) -> Result<(), ServerError> {
526    if state.runtime_config().tls.is_some() {
527        return Err(ServerError::Config {
528            message: "configured TLS material cannot be served until transport TLS is wired"
529                .to_owned(),
530        });
531    }
532    Ok(())
533}
534
535fn store_backend_label(backend: StoreBackend) -> &'static str {
536    match backend {
537        StoreBackend::Memory => "memory",
538        StoreBackend::Haematite => "haematite",
539    }
540}
541
542fn namespace_mode_label(mode: &NamespaceMode) -> &'static str {
543    match mode {
544        NamespaceMode::SharedEngine => "SharedEngine",
545        NamespaceMode::SingleTenant { .. } => "SingleTenant",
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    #![allow(clippy::expect_used)]
552
553    use super::outbox_commission::{
554        BackpressureSettings, maybe_spawn_outbox_dispatcher, resolve_outbox_reconciler_config,
555    };
556    use crate::ServerState;
557    use crate::config::RuntimeConfig;
558    use crate::config::{OutboxConfig, OutboxTransport};
559    use crate::test_support::StateUnderTest;
560    use aion_store::InMemoryStore;
561    use std::net::SocketAddr;
562    use std::time::Duration;
563
564    /// Own-all, generous-default backpressure settings for the gate tests (the
565    /// single-node default: fraction 1, so the ceiling never engages).
566    fn test_backpressure_settings() -> BackpressureSettings {
567        BackpressureSettings {
568            platform_default: crate::config::DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
569            fraction: crate::worker::OwnedShardFraction::own_all(),
570        }
571    }
572
573    /// A minimal `RuntimeConfig` for building an in-memory `ServerState` in unit
574    /// tests (mirrors `state.rs`'s test `runtime_config`).
575    fn runtime_config() -> RuntimeConfig {
576        use crate::config::{
577            AuthConfig, AuthoringConfig, DeployConfig, DevConfig, ListenConfig, MetricsConfig,
578            NamespaceConfig, NamespaceMode, OpsConsoleAssetSource, OpsConsoleConfig,
579            WebSocketConfig, WorkerConfig,
580        };
581        RuntimeConfig {
582            listen: ListenConfig {
583                grpc: SocketAddr::from(([127, 0, 0, 1], 50051)),
584                http: SocketAddr::from(([127, 0, 0, 1], 8080)),
585            },
586            tls: None,
587            auth: AuthConfig {
588                enabled: false,
589                jwks_url: None,
590                jwks_refresh_seconds: 300,
591            },
592            ops_console: OpsConsoleConfig {
593                source: OpsConsoleAssetSource::Embedded,
594            },
595            namespace: NamespaceConfig {
596                mode: NamespaceMode::SharedEngine,
597            },
598            worker: WorkerConfig {
599                heartbeat_window: Duration::from_secs(30),
600                ..WorkerConfig::default()
601            },
602            websocket: WebSocketConfig {
603                outbound_buffer_bound: 32,
604                event_broadcast_capacity: Some(64),
605                cluster_broadcast_capacity: Some(64),
606            },
607            workflow_packages: Vec::new(),
608            deploy: DeployConfig::default(),
609            authoring: AuthoringConfig::default(),
610            dev: DevConfig::default(),
611            outbox: OutboxConfig::default(),
612            observability: crate::config::ObservabilityConfig::with_flush_policy(64, 0),
613            mcp: crate::config::ResolvedMcpConfig::default(),
614            assistant: crate::config::ResolvedAssistantConfig::default(),
615            scheduler_threads: 1,
616            stop_drain_timeout: Some(std::time::Duration::from_secs(5)),
617            jit_threshold: None,
618            query_timeout: Some(Duration::from_secs(10)),
619            workloop_sweep_interval: Some(Duration::from_millis(50)),
620            default_namespace: "default".to_owned(),
621            auto_create: crate::config::AutoCreate::Open,
622            max_in_flight_activities: crate::config::DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
623            drain_timeout: Duration::from_secs(30),
624            metrics: MetricsConfig { enabled: true },
625            owned_shards: Vec::new(),
626            cors_allowed_origins: Vec::new(),
627        }
628    }
629
630    /// An `OutboxConfig` with `enabled = true` and every required knob present, so
631    /// the only remaining gate is the store-backend / outbox-table availability.
632    fn enabled_outbox_config() -> OutboxConfig {
633        OutboxConfig {
634            enabled: true,
635            poll_interval_ms: Some(250),
636            batch_size: Some(64),
637            max_attempts: Some(5),
638            backoff_base_ms: Some(100),
639            backoff_multiplier: Some(2),
640            backoff_max_ms: Some(30_000),
641            reconcile_interval_ms: None,
642            reconcile_stale_after_ms: None,
643            transport: OutboxTransport::Grpc,
644            liminal_listen_address: None,
645            liminal_max_connection_outbound_bytes: None,
646        }
647    }
648
649    /// LSUB-4-2 / LSUB-4-6 (Memory-backend guard): commissioning the outbox
650    /// dispatcher against the in-memory backend (which has no outbox table, so
651    /// `outbox_store()` is `None`) is a configuration error, and the message names
652    /// haematite as the required durable backend.
653    #[tokio::test]
654    async fn outbox_enabled_on_memory_backend_is_a_config_error() {
655        let state = StateUnderTest::new(
656            ServerState::build_with_store(InMemoryStore::default(), runtime_config())
657                .await
658                .expect("build in-memory state"),
659        );
660        let (_tx, rx) = tokio::sync::watch::channel(false);
661        let error = maybe_spawn_outbox_dispatcher(
662            &state,
663            &enabled_outbox_config(),
664            false,
665            test_backpressure_settings(),
666            &rx,
667            "set outbox.liminal_listen_address in the test config",
668        )
669        .expect_err("outbox.enabled on the memory backend must be a config error");
670        assert!(
671            error.is_config(),
672            "memory-backend outbox error must be Config"
673        );
674        let message = error.to_string();
675        assert!(
676            message.contains("store.backend=haematite"),
677            "message must name the durable backend, got: {message}"
678        );
679    }
680
681    /// LSUB-4-1 (Fork-B fast path): with the outbox disabled (the default), the
682    /// gate is a no-op even on a memory backend — nothing is spawned and no error
683    /// is produced, so a default single-node boot is unchanged.
684    #[tokio::test]
685    async fn disabled_outbox_is_a_noop_on_any_backend() {
686        let state = StateUnderTest::new(
687            ServerState::build_with_store(InMemoryStore::default(), runtime_config())
688                .await
689                .expect("build in-memory state"),
690        );
691        let (_tx, rx) = tokio::sync::watch::channel(false);
692        maybe_spawn_outbox_dispatcher(
693            &state,
694            &OutboxConfig::default(),
695            false,
696            test_backpressure_settings(),
697            &rx,
698            "set outbox.liminal_listen_address in the test config",
699        )
700        .expect("disabled outbox gate must be an infallible no-op");
701    }
702
703    /// LSUB-4-4: the reconciler config resolves to `None` unless BOTH knobs are
704    /// set — the condition under which the clustered-boot WARN fires.
705    #[test]
706    fn reconciler_config_absent_unless_both_knobs_set() {
707        let mut config = enabled_outbox_config();
708        // Neither knob: absent.
709        assert!(
710            resolve_outbox_reconciler_config(&config)
711                .expect("resolve")
712                .is_none()
713        );
714        // Only interval: still absent (the silent-backstop-absent default).
715        config.reconcile_interval_ms = Some(1_000);
716        assert!(
717            resolve_outbox_reconciler_config(&config)
718                .expect("resolve")
719                .is_none()
720        );
721        // Both set: present.
722        config.reconcile_stale_after_ms = Some(60_000);
723        assert!(
724            resolve_outbox_reconciler_config(&config)
725                .expect("resolve")
726                .is_some()
727        );
728    }
729
730    /// LSUB-PROD (13-6): the liminal transport requires `liminal_listen_address`.
731    /// Commissioning the dispatcher with `transport = liminal` but no listen
732    /// address is a configuration error naming the missing knob, rather than a
733    /// panic or a silent fall-through to gRPC. Built over haematite (so
734    /// the outbox-store gate passes and the missing-address check is actually
735    /// reached). (Feature-gated: the liminal arm of `build_liminal_row_dispatch`
736    /// only exists with `liminal-transport` on; in a feature-off build the same
737    /// selection is the missing-feature error instead, covered by the type system
738    /// rather than this test.)
739    #[cfg(feature = "liminal-transport")]
740    #[tokio::test]
741    async fn liminal_transport_requires_listen_address() {
742        use crate::config::{
743            RuntimeSection, ServerConfig, StoreBackend, StoreConfig, WebSocketConfig,
744        };
745
746        let data_dir = std::env::temp_dir().join(format!(
747            "aion-lsub-prod-listen-guard-{}-{}",
748            std::process::id(),
749            std::time::SystemTime::now()
750                .duration_since(std::time::UNIX_EPOCH)
751                .map(|elapsed| elapsed.as_nanos())
752                .unwrap_or_default()
753        ));
754        let mut outbox = enabled_outbox_config();
755        outbox.transport = OutboxTransport::Liminal;
756        outbox.liminal_listen_address = None;
757        let config = ServerConfig {
758            store: StoreConfig {
759                backend: StoreBackend::Haematite,
760                data_dir: Some(data_dir.to_string_lossy().into_owned()),
761                // Required, no default: the haematite boot path refuses a config
762                // that does not rule on the node cache's byte ceiling.
763                node_cache_budget: Some(haematite::NodeCacheBudget::Unlimited),
764                ..StoreConfig::default()
765            },
766            runtime: RuntimeSection {
767                scheduler_threads: 1,
768                stop_drain_timeout_ms: Some(5_000),
769                jit_threshold: None,
770                workloop_sweep_interval_ms: Some(50),
771                query_timeout_ms: Some(10_000),
772            },
773            websocket: WebSocketConfig {
774                outbound_buffer_bound: 32,
775                event_broadcast_capacity: Some(64),
776                cluster_broadcast_capacity: Some(64),
777            },
778            outbox: outbox.clone(),
779            // Required, no default: the transcript drain's flush policy.
780            observability: crate::config::ObservabilityConfig::with_flush_policy(64, 0),
781            ..ServerConfig::default()
782        };
783        let state = StateUnderTest::new(
784            ServerState::build(config, &crate::control::StageReporter::detached())
785                .await
786                .expect("build haematite state"),
787        );
788        let (_tx, rx) = tokio::sync::watch::channel(false);
789
790        let error = maybe_spawn_outbox_dispatcher(
791            &state,
792            &outbox,
793            false,
794            test_backpressure_settings(),
795            &rx,
796            "add `liminal_listen_address = \"127.0.0.1:50061\"` to `[outbox]` in the test config",
797        )
798        .expect_err("liminal transport without a listen address must be a config error");
799        assert!(
800            error.is_config(),
801            "missing-listen-address error must be Config"
802        );
803        assert!(
804            error.to_string().contains("liminal_listen_address"),
805            "error must name the missing knob, got: {error}"
806        );
807        // #180 review MAJ-4: the refusal must carry the caller's threaded
808        // where-to-edit hint, so the production message names the resolved
809        // config FILE, not just the key.
810        assert!(
811            error.to_string().contains("in the test config"),
812            "error must carry the threaded config-location hint, got: {error}"
813        );
814    }
815}
816
817/// LSUB-PROD (13-6): production-boot cross-node round-trip over the REAL wiring.
818///
819/// This is the proof that the production boot now does the full round-trip the
820/// retired stub could not. It drives the EXACT production commissioning function
821/// `run_server` calls — [`maybe_spawn_outbox_dispatcher`] — over a real
822/// [`ServerState`] built with `outbox.enabled`, `transport = liminal`, and a
823/// `liminal_listen_address`. That function lifts the full push wiring
824/// (`build_liminal_row_dispatch`): it hosts the liminal worker listener, builds
825/// the SAME [`WorkerOutboxDispatch`](crate::worker::WorkerOutboxDispatch) the
826/// gRPC arm builds — with the liminal delivery attached, so each selected
827/// worker is served over the transport IT registered on (#52 R4) — over the
828/// SAME registry the gRPC path uses and the SAME
829/// [`ServerOutboxDeliveryCallback`](crate::worker::ServerOutboxDeliveryCallback)
830/// (over the live engine), and spawns the real [`OutboxDispatcher`].
831///
832/// A REAL remote [`LiminalActivityWorker`](aion_worker::LiminalActivityWorker)
833/// connects IN to the listener and self-registers in-band. A `collect_four`
834/// fan-out is started over the REAL HTTP transport, which stages four pending
835/// outbox rows; the production-wired dispatcher claims and pushes each to the
836/// worker, the worker executes it, and its completion re-enters aion through the
837/// production engine callback — `record_fan_out_completion` — driving the
838/// workflow to a recorded terminal. The proof asserts BOTH: the worker observably
839/// executed the activities, AND the terminals were recorded in history (four
840/// `ActivityCompleted` + one `WorkflowCompleted`), which the stub's
841/// publish-and-mark-done path never achieved.
842#[cfg(all(test, feature = "liminal-transport"))]
843mod lsub_prod_xnode_e2e {
844    #![allow(clippy::expect_used)]
845
846    use std::net::SocketAddr;
847    use std::path::PathBuf;
848    use std::sync::Arc;
849    use std::sync::atomic::{AtomicUsize, Ordering};
850    use std::time::{Duration, Instant};
851
852    use aion_core::Event;
853    use aion_package::{
854        ActionContract, BeamModule, BeamSet, CURRENT_FORMAT_VERSION, DeclaredActivity, Manifest,
855        ManifestVersion, PackageBuilder, PackageContract, WorkerContract,
856    };
857    use aion_worker::{ActivityRegistry, LiminalActivityWorker, WorkerConfig};
858    use axum::body;
859    use axum::http::{Request, StatusCode};
860    use serde_json::json;
861    use tower::ServiceExt;
862
863    use super::{BackpressureSettings, maybe_spawn_outbox_dispatcher};
864    use crate::ServerState;
865    use crate::api::http::http_router;
866    use crate::config::{
867        OutboxConfig, OutboxTransport, RuntimeSection, ServerConfig, StoreBackend, StoreConfig,
868        WebSocketConfig,
869    };
870    use crate::error::ServerError;
871    use crate::test_support::StateUnderTest;
872
873    type TestError = Box<dyn std::error::Error + Send + Sync>;
874
875    /// The `collect_four` fixture passes each member the JSON string `"in"` as
876    /// activity input, so the worker handler decodes a [`String`], not a struct.
877    type FanInput = String;
878
879    const NAMESPACE: &str = "default";
880    const TASK_QUEUE: &str = "default";
881    const OUTBOX_MODULE: &str = "aion_outbox_fixture";
882    const OUTBOX_BEAM: &[u8] = include_bytes!("../tests/fixtures/aion_outbox_fixture.beam");
883    const OUTBOX_SOURCE: &[u8] = include_bytes!("../tests/fixtures/aion_outbox_fixture.erl");
884    const FAN_OUT: usize = 4;
885    const FAN_ACTIVITY_TYPES: [&str; FAN_OUT] = ["fan:0", "fan:1", "fan:2", "fan:3"];
886    const POLL_DEADLINE: Duration = Duration::from_secs(20);
887    /// The one fan-out member the reconnect pin holds. Any of the four would do —
888    /// they are dispatched independently and served by identical handlers.
889    const HELD_ACTIVITY_TYPE: &str = FAN_ACTIVITY_TYPES[0];
890
891    fn test_error(message: impl std::fmt::Display) -> TestError {
892        message.to_string().into()
893    }
894
895    /// Reserve a loopback port and return it: the liminal listener binds this exact
896    /// address (the production path binds the configured `liminal_listen_address`,
897    /// so the test must commit to a concrete port the worker can also dial).
898    fn reserve_loopback_port() -> Result<SocketAddr, TestError> {
899        let listener = std::net::TcpListener::bind("127.0.0.1:0").map_err(test_error)?;
900        let address = listener.local_addr().map_err(test_error)?;
901        drop(listener);
902        Ok(address)
903    }
904
905    /// The fixture's queue-scoped `.v4` contract: the four `fan:N` activities
906    /// `collect_four` schedules, declared on the queue its worker actually polls.
907    ///
908    /// Why the archive cannot just carry the manifest-derived record: by design
909    /// `PackageContract::from_manifest` "never invents a queue", so a manifest's
910    /// bare activity names land in `unscoped_activities` — and this server boots
911    /// queue-routed, where an unscoped catalog is a terminal
912    /// `NO_QUEUE_DECLARATION` at start admission
913    /// (`aion::lifecycle::start_admission`). That refusal is EARNED: an unserved
914    /// queue would otherwise wait silently forever. So the derived record is
915    /// amended rather than bypassed — the same four names move out of
916    /// `unscoped_activities` and onto the queue that serves them — and the
917    /// package still loads through the production boot path with the `.v4`
918    /// identity `PackageBuilder` stamps over this exact contract.
919    ///
920    /// The action schemas come from the SAME generator the worker's typed
921    /// registry uses, for the SAME Rust types: `collect_four` passes each member
922    /// the JSON string `"in"` and the handler returns a [`String`]. Deriving both
923    /// sides from `activity_descriptor::<FanInput, String>` means the package's
924    /// declaration and the worker's advertisement cannot drift apart, so
925    /// registration admission (`WORKER_CONTRACT_MISMATCH`) compares two schemas
926    /// with one source.
927    fn fixture_contract(manifest: &Manifest) -> Result<PackageContract, TestError> {
928        let mut actions = Vec::with_capacity(FAN_ACTIVITY_TYPES.len());
929        for activity_type in FAN_ACTIVITY_TYPES {
930            let descriptor = aion_worker::activity_descriptor::<FanInput, String>(activity_type)
931                .map_err(test_error)?;
932            actions.push(ActionContract {
933                name: descriptor.name,
934                input_schema: descriptor.input_schema,
935                output_schema: descriptor.output_schema,
936                node: None,
937                timeout: None,
938                retry: None,
939                advisory: false,
940                // A typed `String -> String` handler serves these, not an agent
941                // harness — the fan fixture's shape merely coincides with an
942                // agent seam's, and marking it would route it somewhere no
943                // handler is.
944                agent: false,
945                // A connected worker serves this fixture's queue, so the
946                // declaration carries no body of its own.
947                body: None,
948            });
949        }
950        let mut contract = PackageContract::from_manifest(manifest);
951        contract.workers = vec![WorkerContract {
952            task_queue: TASK_QUEUE.to_owned(),
953            actions,
954        }];
955        contract.unscoped_activities.clear();
956        Ok(contract)
957    }
958
959    /// Build the `collect_four` package on disk so the production state-build path
960    /// loads it exactly as it loads operator-supplied `workflow_packages`.
961    fn write_package_archive(dir: &std::path::Path) -> Result<PathBuf, TestError> {
962        let beams =
963            BeamSet::new(vec![BeamModule::new(OUTBOX_MODULE, OUTBOX_BEAM)]).map_err(test_error)?;
964        let manifest = Manifest {
965            entry_module: OUTBOX_MODULE.to_owned(),
966            entry_function: "collect_four".to_owned(),
967            input_schema: json!({ "type": "object" }),
968            output_schema: json!({}),
969            timeout: Some(Duration::from_secs(30)),
970            // The four ordinals `collect_four` actually fans out. This manifest
971            // used to name one invented activity, `fixture_activity`, that the
972            // fixture never schedules and no worker ever served.
973            activities: FAN_ACTIVITY_TYPES
974                .iter()
975                .map(|activity_type| DeclaredActivity {
976                    activity_type: (*activity_type).to_owned(),
977                })
978                .collect(),
979            version: ManifestVersion::new("stamped-by-builder"),
980            format_version: CURRENT_FORMAT_VERSION,
981            additional_workflows: Vec::new(),
982        };
983        let contract = fixture_contract(&manifest)?;
984        let archive =
985            PackageBuilder::with_source(manifest, beams, [(OUTBOX_MODULE, OUTBOX_SOURCE.to_vec())])
986                .with_contract(contract)
987                .write_to_bytes()
988                .map_err(test_error)?;
989        let path = dir.join("collect_four.aion");
990        std::fs::write(&path, archive).map_err(test_error)?;
991        Ok(path)
992    }
993
994    /// A production-shaped `ServerConfig`: the haematite backend (so the boot store
995    /// path shares the leaf as the dispatcher's outbox store, exactly as
996    /// `ServerState::build` does in production), `outbox.enabled`,
997    /// `transport = liminal`, the reserved `liminal_listen_address`, and the
998    /// `collect_four` package. Built through `ServerState::build` (not
999    /// `build_with_store`), so this is the real boot store seam, not a test stand-in.
1000    fn server_config(
1001        data_dir: &std::path::Path,
1002        package_path: PathBuf,
1003        listen_address: SocketAddr,
1004        outbound_bound: Option<u64>,
1005    ) -> ServerConfig {
1006        ServerConfig {
1007            store: StoreConfig {
1008                backend: StoreBackend::Haematite,
1009                data_dir: Some(data_dir.to_string_lossy().into_owned()),
1010                // Required, no default: the haematite boot path refuses a config
1011                // that does not rule on the node cache's byte ceiling.
1012                node_cache_budget: Some(haematite::NodeCacheBudget::Unlimited),
1013                ..StoreConfig::default()
1014            },
1015            runtime: RuntimeSection {
1016                scheduler_threads: 1,
1017                stop_drain_timeout_ms: Some(5_000),
1018                jit_threshold: None,
1019                workloop_sweep_interval_ms: Some(50),
1020                query_timeout_ms: Some(10_000),
1021            },
1022            websocket: WebSocketConfig {
1023                outbound_buffer_bound: 32,
1024                event_broadcast_capacity: Some(64),
1025                cluster_broadcast_capacity: Some(64),
1026            },
1027            workflow_packages: vec![package_path],
1028            outbox: OutboxConfig {
1029                enabled: true,
1030                poll_interval_ms: Some(20),
1031                batch_size: Some(16),
1032                max_attempts: Some(5),
1033                backoff_base_ms: Some(50),
1034                backoff_multiplier: Some(2),
1035                backoff_max_ms: Some(1_000),
1036                reconcile_interval_ms: None,
1037                reconcile_stale_after_ms: None,
1038                transport: OutboxTransport::Liminal,
1039                liminal_listen_address: Some(listen_address.to_string()),
1040                liminal_max_connection_outbound_bytes: outbound_bound,
1041            },
1042            // Required, no default: the transcript drain's flush policy.
1043            observability: crate::config::ObservabilityConfig::with_flush_policy(64, 0),
1044            ..ServerConfig::default()
1045        }
1046    }
1047
1048    /// The remote worker self-describes for the fixture's pool `(default, default)`
1049    /// and registers a handler for every `fan:N` activity type, counting executions
1050    /// so the test proves it genuinely ran the pushed dispatches.
1051    fn worker_config() -> Result<WorkerConfig, TestError> {
1052        WorkerConfig::builder()
1053            .endpoint("unused-direct-address")
1054            .namespace(NAMESPACE)
1055            .task_queue(TASK_QUEUE)
1056            .identity("lsub-prod-worker")
1057            // FOUR, and the liminal transport now enforces it: one shared
1058            // execution budget across the plain executor and the agent path,
1059            // announced to the server on the capabilities channel. It used to be
1060            // a number nothing read.
1061            .max_concurrency(4)
1062            .reconnect_initial_backoff(Duration::from_millis(5))
1063            .reconnect_max_backoff(Duration::from_millis(20))
1064            .reconnect_max_attempts(3)
1065            .build()
1066            .map_err(test_error)
1067    }
1068
1069    fn worker_registry(executions: &Arc<AtomicUsize>) -> Result<Arc<ActivityRegistry>, TestError> {
1070        let mut registry = ActivityRegistry::new();
1071        for activity_type in FAN_ACTIVITY_TYPES {
1072            let executions = Arc::clone(executions);
1073            // `register_activity_with_contract`, not `register_activity`: the
1074            // bare form registers a handler with NO descriptor, so the worker
1075            // advertises four names and zero typed contracts, and admission —
1076            // which compares CONTRACTS — refuses the registration outright
1077            // (`WORKER_CONTRACT_MISMATCH`). Deriving the advertisement from
1078            // `<FanInput, String>` is what makes it the same source the
1079            // package's `fixture_contract` declares from, so the two sides
1080            // cannot drift.
1081            registry = registry
1082                .register_activity_with_contract(
1083                    activity_type,
1084                    move |_input: FanInput, _context| {
1085                        let executions = Arc::clone(&executions);
1086                        Box::pin(async move {
1087                            executions.fetch_add(1, Ordering::SeqCst);
1088                            Ok(activity_type.to_owned())
1089                        })
1090                    },
1091                )
1092                .map_err(test_error)?;
1093        }
1094        Ok(Arc::new(registry))
1095    }
1096
1097    /// Spawns the remote worker on its own OS thread with a current-thread runtime
1098    /// (the push receive is blocking), connecting IN to the production listener.
1099    struct WorkerThread {
1100        stop: Arc<std::sync::atomic::AtomicBool>,
1101        handle: Option<std::thread::JoinHandle<()>>,
1102    }
1103
1104    impl WorkerThread {
1105        fn spawn(address: String, config: WorkerConfig, registry: Arc<ActivityRegistry>) -> Self {
1106            let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
1107            let thread_stop = Arc::clone(&stop);
1108            let handle = std::thread::spawn(move || {
1109                let runtime = match tokio::runtime::Builder::new_current_thread()
1110                    .enable_all()
1111                    .build()
1112                {
1113                    Ok(runtime) => runtime,
1114                    Err(error) => {
1115                        eprintln!("worker runtime build failed: {error}");
1116                        return;
1117                    }
1118                };
1119                runtime.block_on(async move {
1120                    let worker = match LiminalActivityWorker::connect(&address, &config, registry) {
1121                        Ok(worker) => worker,
1122                        Err(error) => {
1123                            eprintln!("worker connect failed: {error}");
1124                            return;
1125                        }
1126                    };
1127                    if let Err(error) = worker
1128                        .serve_until(|| thread_stop.load(Ordering::SeqCst))
1129                        .await
1130                    {
1131                        eprintln!("worker serve loop ended with error: {error}");
1132                    }
1133                });
1134            });
1135            Self {
1136                stop,
1137                handle: Some(handle),
1138            }
1139        }
1140
1141        /// Spawn the worker through [`aion_worker::serve_with_redial`] — the entry
1142        /// point every REAL worker uses — so a broken link is survivable.
1143        ///
1144        /// [`Self::spawn`] uses `LiminalActivityWorker::serve_until`, which returns
1145        /// the first transport error by design: a single-connection serve has no
1146        /// survivor to migrate to. That is the right shape for a test whose link
1147        /// never breaks, and the wrong instrument entirely for one whose link is
1148        /// broken on purpose — a worker that dies at the break can only ever show
1149        /// that outstanding work fails, whoever is at fault.
1150        ///
1151        /// The redial driver is SYNCHRONOUS and builds its own current-thread
1152        /// runtime, so it runs on the bare thread rather than inside one.
1153        fn spawn_redialing(
1154            address: String,
1155            config: WorkerConfig,
1156            registry: Arc<ActivityRegistry>,
1157            timing: aion_worker::RedialTiming,
1158        ) -> Self {
1159            let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
1160            let thread_stop = Arc::clone(&stop);
1161            let handle = std::thread::spawn(move || {
1162                if let Err(error) = aion_worker::serve_with_redial(
1163                    vec![address],
1164                    &config,
1165                    &registry,
1166                    timing,
1167                    &thread_stop,
1168                    None,
1169                    || {},
1170                ) {
1171                    eprintln!("redialing worker ended with error: {error}");
1172                }
1173            });
1174            Self {
1175                stop,
1176                handle: Some(handle),
1177            }
1178        }
1179
1180        fn stop(mut self) {
1181            self.stop.store(true, Ordering::SeqCst);
1182            if let Some(handle) = self.handle.take() {
1183                handle.join().ok();
1184            }
1185        }
1186    }
1187
1188    fn count_completed(history: &[Event]) -> usize {
1189        history
1190            .iter()
1191            .filter(|event| matches!(event, Event::ActivityCompleted { .. }))
1192            .count()
1193    }
1194
1195    fn count_workflow_completed(history: &[Event]) -> usize {
1196        history
1197            .iter()
1198            .filter(|event| matches!(event, Event::WorkflowCompleted { .. }))
1199            .count()
1200    }
1201
1202    async fn wait_for_history<F>(
1203        store: &dyn aion_store::ReadableEventStore,
1204        workflow_id: &aion_core::WorkflowId,
1205        description: &str,
1206        predicate: F,
1207    ) -> Result<Vec<Event>, TestError>
1208    where
1209        F: Fn(&[Event]) -> bool,
1210    {
1211        let deadline = Instant::now() + POLL_DEADLINE;
1212        loop {
1213            let history = store.read_history(workflow_id).await.map_err(test_error)?;
1214            if predicate(&history) {
1215                return Ok(history);
1216            }
1217            if Instant::now() > deadline {
1218                return Err(test_error(format!(
1219                    "timed out waiting for {description}: {history:#?}"
1220                )));
1221            }
1222            tokio::time::sleep(Duration::from_millis(25)).await;
1223        }
1224    }
1225
1226    /// Start the loaded `collect_four` workflow over the REAL HTTP transport.
1227    async fn start_over_http(router: &axum::Router) -> Result<aion_core::WorkflowId, TestError> {
1228        let build_request = || -> Result<Request<body::Body>, TestError> {
1229            Request::builder()
1230                .uri("/workflows/start")
1231                .method("POST")
1232                .header("content-type", "application/json")
1233                .header("x-aion-subject", "ci")
1234                .header("x-aion-namespaces", NAMESPACE)
1235                .body(body::Body::from(
1236                    serde_json::to_vec(&json!({
1237                        "namespace": NAMESPACE,
1238                        "workflow_type": OUTBOX_MODULE,
1239                        "input": { "fixture": "input" },
1240                    }))
1241                    .map_err(test_error)?,
1242                ))
1243                .map_err(test_error)
1244        };
1245        let response = router
1246            .clone()
1247            .oneshot(build_request()?)
1248            .await
1249            .map_err(test_error)?;
1250        let status = response.status();
1251        let bytes = body::to_bytes(response.into_body(), usize::MAX)
1252            .await
1253            .map_err(test_error)?
1254            .to_vec();
1255        if status != StatusCode::OK {
1256            return Err(test_error(format!(
1257                "workflow start over HTTP must succeed, got {status}: {}",
1258                String::from_utf8_lossy(&bytes)
1259            )));
1260        }
1261        let body: serde_json::Value = serde_json::from_slice(&bytes).map_err(test_error)?;
1262        // The HTTP wire contract (`clean_dtos::StartWorkflowResponse`) serializes
1263        // `workflow_id` as a plain UUID string, not a nested `{ uuid }` object.
1264        let workflow_id = body["workflow_id"]
1265            .as_str()
1266            .ok_or_else(|| test_error("start response missing workflow id"))?
1267            .parse::<uuid::Uuid>()
1268            .map_err(test_error)?;
1269        Ok(aion_core::WorkflowId::new(workflow_id))
1270    }
1271
1272    /// How long a freshly connected worker needs before the dispatch path may
1273    /// select it, DERIVED from the same two facts the server derives it from.
1274    ///
1275    /// A worker is dispatch-ineligible until it serves an OPENING PROBATION:
1276    /// [`Reachability::is_proved`] requires `DISPATCH_PROBATION_PINGS` consecutive
1277    /// answered liveness pings, at the probe's cadence of
1278    /// [`sweep_interval`](crate::worker::sweep_interval)`(heartbeat_window)`. The
1279    /// constant's own documentation states the cost — *"at the probe's cadence a
1280    /// fresh worker is undispatchable for K cadences while its first dispatches
1281    /// park"* — so this is designed behaviour a test must wait out, not a delay to
1282    /// be shortened.
1283    ///
1284    /// One extra cadence is allowed because the first round lands at an arbitrary
1285    /// offset inside the first interval: the worker connects between rounds, so it
1286    /// can miss up to one whole cadence before its first answer is even counted.
1287    ///
1288    /// # Why this is not a raised timeout
1289    ///
1290    /// It was 5 seconds, fixed, and that is how this test became one of four
1291    /// documented carriers of a load-sensitive flake
1292    /// (`gate-logs/lock-race-attribution/VERDICT.md`). The mechanism, measured:
1293    /// `dispatch_ineligible` starts EMPTY and `select_and_reserve` filters only against
1294    /// what the probe has published, so a run in which **no probe round lands
1295    /// inside the window** selects the worker immediately and passes, while a run
1296    /// in which one does correctly withholds it for ~2 cadences and fails. On the
1297    /// default 30s window that is 7.5s per cadence against a 5s wait.
1298    ///
1299    /// 🔴 The passing runs were the WRONG ones. They dispatched to a worker that
1300    /// had not served its probation — a path production does not permit, because
1301    /// production parks those dispatches. Waiting for genuine eligibility makes
1302    /// this test MORE production-shaped, not more lenient, and that is the reason
1303    /// to do it. Raising a bound until a flake stops is how a liveness bug gets
1304    /// buried; deriving the bound from the mechanism that sets it is not the same
1305    /// act, and the register warns about the first for good reason.
1306    fn eligibility_patience(config: &ServerConfig) -> Duration {
1307        let cadence = crate::worker::sweep_interval(config.worker.heartbeat_window);
1308        cadence * (crate::worker::heartbeat::DISPATCH_PROBATION_PINGS + 1)
1309    }
1310
1311    /// Wait until the worker's in-band registration lands in the SAME registry the
1312    /// dispatch path selects from, with every fan-out activity type eligible.
1313    ///
1314    /// On the deadline this reports the state that DISCRIMINATES the worlds a
1315    /// missed registration can be in, because the bare sentence it replaced —
1316    /// "worker never registered in-band for the pool" — is equally true in at
1317    /// least three of them, and they want different fixes:
1318    ///
1319    /// 1. the liminal listener never bound, so nothing could dial in;
1320    /// 2. the worker never connected, or died dialling;
1321    /// 3. it connected and registration was merely slow;
1322    /// 4. it connected, registered correctly, and the SELECTOR refused it anyway —
1323    ///    because the liveness probe published it as unreachable, or because it is
1324    ///    not indexed for the activity type it advertises.
1325    ///
1326    /// The fourth was not in the first version of this report, and it is the world
1327    /// a real occurrence turned out to be in: the listener was bound, a worker was
1328    /// registered under the right namespace and queue advertising all four activity
1329    /// types, and every `select_and_reserve` still returned nothing. A report that
1330    /// cannot separate "not registered" from "registered and refused" names the
1331    /// wrong half of the system.
1332    ///
1333    /// That is not a hypothetical distinction here. This module's e2e is one of
1334    /// four documented carriers of a load-sensitive flake
1335    /// (`gate-logs/lock-race-attribution/VERDICT.md`), it fails through THIS wait,
1336    /// and the reason the carrier has never been explained is that the failure
1337    /// named the fact and withheld the cause.
1338    async fn wait_for_registration(
1339        registry: &crate::worker::ConnectedWorkerRegistry,
1340        heartbeat: &crate::worker::HeartbeatTracker,
1341        listen_address: SocketAddr,
1342        patience: Duration,
1343    ) -> Result<(), TestError> {
1344        let deadline = Instant::now() + patience;
1345        loop {
1346            let now = Instant::now();
1347            let mut ready = true;
1348            for activity_type in FAN_ACTIVITY_TYPES {
1349                let Some(worker) = registry
1350                    .select_and_reserve(NAMESPACE, TASK_QUEUE, activity_type, None)
1351                    .map(|selected| selected.map(|(worker, _reservation)| worker))
1352                    .map_err(test_error)?
1353                else {
1354                    ready = false;
1355                    break;
1356                };
1357                if !heartbeat
1358                    .is_dispatch_reachable(worker.id(), now)
1359                    .map_err(test_error)?
1360                {
1361                    ready = false;
1362                    break;
1363                }
1364            }
1365            if ready {
1366                return Ok(());
1367            }
1368            if Instant::now() > deadline {
1369                return Err(test_error(format!(
1370                    "worker never registered in-band for the pool within {patience:?}{}",
1371                    registration_diagnosis(registry, listen_address)
1372                )));
1373            }
1374            tokio::time::sleep(Duration::from_millis(10)).await;
1375        }
1376    }
1377
1378    /// The discriminator behind [`wait_for_registration`]'s failure: enough of the
1379    /// world to tell those three apart, gathered at the moment of the failure.
1380    fn registration_diagnosis(
1381        registry: &crate::worker::ConnectedWorkerRegistry,
1382        listen_address: SocketAddr,
1383    ) -> String {
1384        let mut lines = vec![String::from("--- registration diagnosis ---")];
1385        // World 1, PROBED rather than assumed. The port was reserved by binding a
1386        // listener and dropping it, so losing the race for it is a real
1387        // possibility rather than a theoretical one, and it is indistinguishable
1388        // from every other failure unless something asks.
1389        lines.push(
1390            match std::net::TcpStream::connect_timeout(&listen_address, Duration::from_millis(500))
1391            {
1392                Ok(stream) => {
1393                    drop(stream);
1394                    format!("listener {listen_address}: ACCEPTS — the port is bound and dialable")
1395                }
1396                Err(error) => format!(
1397                    "listener {listen_address}: NOT connectable ({error}) — nothing could have \
1398                     registered, so this is not a timing problem"
1399                ),
1400            },
1401        );
1402        // Worlds 2 and 3: did any worker arrive at all, and if one did, what does
1403        // the registry hold for it against what the dispatch path asks of it? A
1404        // worker present under a different pool or advertising different activity
1405        // types is a contract mismatch wearing a timeout's clothes.
1406        match registry.all_workers() {
1407            Err(error) => lines.push(format!("registry: UNREADABLE ({error})")),
1408            Ok(workers) if workers.is_empty() => lines.push(String::from(
1409                "registry: EMPTY — no worker of any pool registered, so no connection ever \
1410                 completed an in-band registration",
1411            )),
1412            Ok(workers) => {
1413                lines.push(format!("registry: {} worker(s) registered", workers.len()));
1414                for worker in &workers {
1415                    lines.push(format!(
1416                        "  id={:?} namespaces={:?} task_queue={:?} node={:?} types={:?}",
1417                        worker.id(),
1418                        worker.namespaces(),
1419                        worker.task_queue(),
1420                        worker.node(),
1421                        worker.activity_types()
1422                    ));
1423                }
1424            }
1425        }
1426        lines.push(format!(
1427            "asked of it: namespace={NAMESPACE:?} task_queue={TASK_QUEUE:?}"
1428        ));
1429        // The liveness probe's reachability verdict. `select_and_reserve` skips every
1430        // worker in this set, so a registered, correctly-advertised worker that is
1431        // listed here is refused for a reason nothing else in this report shows.
1432        lines.push(match registry.dispatch_ineligible() {
1433            Ok(ineligible) if ineligible.is_empty() => {
1434                String::from("dispatch-ineligible: none — reachability is not refusing anyone")
1435            }
1436            Ok(ineligible) => format!(
1437                "dispatch-ineligible: {ineligible:?} — the liveness probe has published these \
1438                 as ineligible and select_and_reserve skips them. The value beside each id is WHY: \
1439                 an OpeningProbation clears itself within seconds, a ReachabilityLost does not"
1440            ),
1441            Err(error) => format!("dispatch-ineligible: UNREADABLE ({error})"),
1442        });
1443        // Which of the four the selector could not satisfy, and — the part that
1444        // discriminates — the pool census beside each refusal.
1445        //
1446        // `select_and_reserve` filters on THREE things: the activity index for
1447        // `(namespace, task_queue) + activity_type`, the node pin, and the
1448        // dispatch-ineligible set. The census counts the first two and does NOT
1449        // apply the third, so the pair of answers separates the remaining worlds
1450        // that a registry dump alone leaves fused:
1451        //
1452        // - census serves it, selector refuses  ⇒ REACHABILITY, not registration;
1453        // - census serves 0 for the activity    ⇒ the worker is in the pool but not
1454        //   indexed for this activity type;
1455        // - census serves 0 for the pool        ⇒ it is not in this pool at all,
1456        //   whatever `all_workers` shows.
1457        //
1458        // Written after the bare registry dump above failed to close a real case:
1459        // it proved the listener was bound and a worker with all four activity
1460        // types was registered, and still could not say why every selection
1461        // returned nothing.
1462        for activity_type in FAN_ACTIVITY_TYPES {
1463            let outcome = match registry
1464                .select_and_reserve(NAMESPACE, TASK_QUEUE, activity_type, None)
1465                .map(|selected| selected.map(|(worker, _reservation)| worker))
1466            {
1467                Ok(Some(handle)) => format!("worker {:?}", handle.id()),
1468                Ok(None) => String::from("NO worker"),
1469                Err(error) => format!("error: {error}"),
1470            };
1471            let census = match registry.pool_census(NAMESPACE, TASK_QUEUE, activity_type, None) {
1472                Ok(census) => format!(
1473                    "in_pool={} serving_activity={} compatible={} last_compatible_age={:?}",
1474                    census.workers_in_pool,
1475                    census.workers_serving_activity,
1476                    census.compatible_workers,
1477                    census.last_compatible_poller_age
1478                ),
1479                Err(error) => format!("census UNREADABLE ({error})"),
1480            };
1481            lines.push(format!(
1482                "select_and_reserve({activity_type}) -> {outcome}  [census: {census}]"
1483            ));
1484        }
1485        format!("\n  {}", lines.join("\n  "))
1486    }
1487
1488    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1489    async fn production_boot_dispatches_executes_and_records_over_liminal() -> Result<(), TestError>
1490    {
1491        let dir = crate::test_support::private_tempdir().map_err(test_error)?;
1492        let db_path = dir.path().join("aion.db");
1493        let package_path = write_package_archive(dir.path())?;
1494        // The production path binds the CONFIGURED listen address, so commit to a
1495        // concrete reserved loopback port the worker can also dial.
1496        let listen_address = reserve_loopback_port()?;
1497
1498        // (A) Build a real ServerState through the production boot path
1499        // (ServerState::build over a haematite ServerConfig): outbox enabled,
1500        // transport = liminal, the listen address set, collect_four loaded. This
1501        // shares the haematite leaf as the dispatcher's outbox store (the real boot
1502        // store seam) and installs the production ServerOutboxDeliveryCallback over
1503        // the live engine (gated on outbox.enabled).
1504        let config = server_config(&db_path, package_path, listen_address, None);
1505        let outbox_config = config.outbox.clone();
1506        // Captured before `build` consumes the config: the wait below is derived
1507        // from the very window this server is about to run its liveness probe on.
1508        let patience = eligibility_patience(&config);
1509        let state = StateUnderTest::new(
1510            ServerState::build(config, &crate::control::StageReporter::detached())
1511                .await
1512                .map_err(test_error)?,
1513        );
1514
1515        // (B) Drive the EXACT production commissioning function run_server calls:
1516        // it hosts the liminal listener, builds the shared WorkerOutboxDispatch
1517        // with the liminal delivery attached over the shared registry + engine
1518        // callback, and spawns the real OutboxDispatcher.
1519        // Hold the returned listener guard for the test's lifetime, exactly as
1520        // run_server holds it.
1521        let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
1522        // Own-all, generous-default backpressure (single-node e2e): fraction 1 and
1523        // the platform default, so the ceiling never engages — the claim behaves
1524        // exactly as before, proving the production path is byte-identical on default.
1525        let backpressure_settings = BackpressureSettings {
1526            platform_default: crate::config::DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
1527            fraction: crate::worker::OwnedShardFraction::own_all(),
1528        };
1529        let listener_guard = maybe_spawn_outbox_dispatcher(
1530            &state,
1531            &outbox_config,
1532            false,
1533            backpressure_settings,
1534            &shutdown_rx,
1535            "set outbox.liminal_listen_address in the test config",
1536        )
1537        .map_err(test_error)?;
1538
1539        // (C) A REAL remote worker connects IN to the production listener and
1540        // self-registers in-band for the fixture's pool.
1541        let executions = Arc::new(AtomicUsize::new(0));
1542        let worker = WorkerThread::spawn(
1543            listen_address.to_string(),
1544            worker_config()?,
1545            worker_registry(&executions)?,
1546        );
1547
1548        // Wait until the in-band registration landed in the SAME registry the
1549        // dispatch path selects from (every fan-out activity type is eligible).
1550        let registry = state.worker_registry().clone();
1551        if let Err(error) = wait_for_registration(
1552            &registry,
1553            state.heartbeat_tracker(),
1554            listen_address,
1555            patience,
1556        )
1557        .await
1558        {
1559            worker.stop();
1560            return Err(error);
1561        }
1562
1563        // (D) Start collect_four over the REAL HTTP transport: the engine stages
1564        // four pending outbox rows; the production-wired dispatcher claims and
1565        // pushes each to the worker.
1566        let router = http_router(state.clone()).map_err(test_error)?;
1567        let workflow_id = start_over_http(&router).await?;
1568
1569        // (E) THE PROOF: the worker executed all four activities AND every terminal
1570        // was recorded through the production engine callback (record_fan_out_completion)
1571        // — four ActivityCompleted + one WorkflowCompleted in durable history. This
1572        // is the full round-trip the retired stub never achieved.
1573        let reader = state.engine().map_err(test_error)?.store();
1574        let settled =
1575            wait_for_history(reader.as_ref(), &workflow_id, "fan-out settled", |events| {
1576                count_completed(events) == FAN_OUT && count_workflow_completed(events) == 1
1577            })
1578            .await?;
1579        assert_eq!(
1580            count_completed(&settled),
1581            FAN_OUT,
1582            "every fan-out member must record a terminal through the production callback"
1583        );
1584        assert_eq!(
1585            count_workflow_completed(&settled),
1586            1,
1587            "the workflow must complete exactly once"
1588        );
1589        assert_eq!(
1590            executions.load(Ordering::SeqCst),
1591            FAN_OUT,
1592            "the remote worker must have executed every pushed dispatch exactly once"
1593        );
1594
1595        // Teardown: stop the dispatcher + worker, drop the listener guard (its Drop
1596        // stops the accept worker), shut the engine down so durable appends finish.
1597        shutdown_tx.send(true).ok();
1598        worker.stop();
1599        drop(listener_guard);
1600        state.shutdown().map_err(test_error)?;
1601        Ok(())
1602    }
1603
1604    /// The door this test sets with `outbox.liminal_max_connection_outbound_bytes`:
1605    /// 1 MiB, deliberately SMALLER than liminal's own 4 MiB default, so a refusal
1606    /// naming this number can only have come from the operator's key reaching the
1607    /// connection supervisor. The number the refusal must name.
1608    const DOOR_BYTES: u64 = 1_048_576;
1609    /// The push sent through it: 2 MiB of payload bytes, above the door in every
1610    /// wire shape the payload codec can emit (base64 is 2.67 MiB; the integer
1611    /// array is about 8 MiB), so the outcome does not depend on the encoder.
1612    const OVERSIZE_PAYLOAD_BYTES: usize = 2 * 1_048_576;
1613    /// Liminal's default outbound bound. Its ABSENCE from the refusal is the
1614    /// other half of the proof: a supervisor still on default limits refuses the
1615    /// same push naming this number instead.
1616    const LIMINAL_DEFAULT_DOOR: &str = "4194304";
1617
1618    /// A valid JSON document of [`OVERSIZE_PAYLOAD_BYTES`] bytes: one string
1619    /// value, so anything that decodes it decodes a real payload.
1620    fn oversize_json_payload() -> Result<Vec<u8>, TestError> {
1621        let filler = "x".repeat(OVERSIZE_PAYLOAD_BYTES - 2);
1622        let bytes = serde_json::to_vec(&serde_json::Value::String(filler)).map_err(test_error)?;
1623        if bytes.len() != OVERSIZE_PAYLOAD_BYTES {
1624            return Err(test_error(format!(
1625                "oversize payload must be exactly {OVERSIZE_PAYLOAD_BYTES} bytes, got {}",
1626                bytes.len()
1627            )));
1628        }
1629        Ok(bytes)
1630    }
1631
1632    /// The `frame is N bytes` number the unservable refusal names.
1633    fn refused_frame_bytes(detail: &str) -> Result<u64, TestError> {
1634        let after = detail
1635            .split_once("frame is ")
1636            .map(|(_, rest)| rest)
1637            .ok_or_else(|| test_error(format!("refusal names no frame size: {detail}")))?;
1638        let digits: String = after.chars().take_while(char::is_ascii_digit).collect();
1639        digits
1640            .parse::<u64>()
1641            .map_err(|error| test_error(format!("frame size unreadable ({error}): {detail}")))
1642    }
1643
1644    /// Push one oversize dispatch to the selected worker over the connection the
1645    /// COMMISSIONED supervisor owns, and return the refusal it came back with.
1646    ///
1647    /// The delivery handle is the registry's own — the exact `WorkerDelivery` the
1648    /// outbox dispatch path selects — so the push crosses the same connection
1649    /// process, under the same limits, as a production dispatch. The wait runs on
1650    /// a blocking thread because the reply awaiter blocks by contract.
1651    async fn push_oversize_through_the_door(
1652        registry: &crate::worker::ConnectedWorkerRegistry,
1653    ) -> Result<ServerError, TestError> {
1654        let (worker, _reservation) = registry
1655            .select_and_reserve(NAMESPACE, TASK_QUEUE, FAN_ACTIVITY_TYPES[0], None)
1656            .map_err(test_error)?
1657            .ok_or_else(|| test_error("no eligible worker to push the oversize frame to"))?;
1658        let crate::worker::registry::WorkerDelivery::Liminal(delivery) = worker.delivery().clone()
1659        else {
1660            return Err(test_error(
1661                "the registered worker must be on the liminal transport for this door to apply",
1662            ));
1663        };
1664        let request = crate::worker::liminal_transport::DispatchRequest {
1665            activity_type: FAN_ACTIVITY_TYPES[0].to_owned(),
1666            workflow_id: aion_core::WorkflowId::new_v4(),
1667            ordinal: 0,
1668            run_id: None,
1669            completion_token: "door-probe".to_owned(),
1670            idempotency_key: "door-probe".to_owned(),
1671            input: oversize_json_payload()?,
1672            attempt: 1,
1673            labels: std::collections::BTreeMap::new(),
1674            heartbeat_window_ms: 0,
1675        };
1676        let pushed = tokio::task::spawn_blocking(move || delivery.dispatch_held(&request, || true))
1677            .await
1678            .map_err(test_error)?;
1679        match pushed {
1680            Err(refusal) => Ok(refusal),
1681            Ok(reply) => Err(test_error(format!(
1682                "a {OVERSIZE_PAYLOAD_BYTES}-byte push must be refused at a {DOOR_BYTES}-byte door, \
1683                 but it was carried and answered: {reply:?}"
1684            ))),
1685        }
1686    }
1687
1688    /// Stage one outbox row carrying the oversize payload for a Running workflow
1689    /// the store knows, so the PRODUCTION dispatcher claims it, pushes it through
1690    /// the same door, and has a live row to dead-letter. The history goes through
1691    /// the engine's own event store and the row through the SAME outbox store the
1692    /// dispatcher claims from. Returns the workflow id and the row's dispatch key.
1693    async fn stage_oversize_row(
1694        state: &ServerState,
1695    ) -> Result<(aion_core::WorkflowId, String), TestError> {
1696        use aion_core::{ActivityId, ContentType, EventEnvelope, Payload, RunId, WorkflowId};
1697        use aion_store::{OutboxRow, WriteToken};
1698
1699        let events_store = state.engine().map_err(test_error)?.store();
1700        let outbox = state
1701            .outbox_store()
1702            .ok_or_else(|| test_error("the haematite boot store must carry the outbox"))?;
1703
1704        let workflow_id = WorkflowId::new_v4();
1705        let run_id = RunId::new_v4();
1706        let envelope = |seq: u64| EventEnvelope {
1707            seq,
1708            recorded_at: chrono::Utc::now(),
1709            workflow_id: workflow_id.clone(),
1710        };
1711        let input = Payload::new(ContentType::Json, oversize_json_payload()?);
1712        let events = vec![
1713            Event::WorkflowStarted {
1714                envelope: envelope(1),
1715                workflow_type: OUTBOX_MODULE.to_owned(),
1716                input: Payload::from_json(&json!({})).map_err(test_error)?,
1717                run_id: run_id.clone(),
1718                parent_run_id: None,
1719                parent_workflow_id: None,
1720                package_version: aion_core::PackageVersion::new("a".repeat(64)),
1721            },
1722            Event::ActivityScheduled {
1723                envelope: envelope(2),
1724                activity_id: ActivityId::from_sequence_position(0),
1725                activity_type: FAN_ACTIVITY_TYPES[0].to_owned(),
1726                input: input.clone(),
1727                task_queue: TASK_QUEUE.to_owned(),
1728                node: None,
1729            },
1730            Event::ActivityStarted {
1731                envelope: envelope(3),
1732                activity_id: ActivityId::from_sequence_position(0),
1733                attempt: 1,
1734            },
1735        ];
1736        events_store
1737            .append(WriteToken::recorder(), &workflow_id, &events, 0)
1738            .await
1739            .map_err(test_error)?;
1740        let row = OutboxRow::pending(
1741            workflow_id.clone(),
1742            0,
1743            FAN_ACTIVITY_TYPES[0].to_owned(),
1744            input,
1745            chrono::Utc::now(),
1746        )
1747        .with_run_id(Some(run_id));
1748        let dispatch_key = row.dispatch_key.clone();
1749        outbox
1750            .append_outbox_batch(std::slice::from_ref(&row))
1751            .await
1752            .map_err(test_error)?;
1753        Ok((workflow_id, dispatch_key))
1754    }
1755
1756    /// Wait for the production dispatcher to dead-letter the staged row, and
1757    /// return the row as the store holds it. Read through the SAME store handle
1758    /// the dispatcher writes, by the workflow's dead-letter enumeration.
1759    async fn wait_for_dead_letter(
1760        state: &ServerState,
1761        workflow_id: &aion_core::WorkflowId,
1762        dispatch_key: &str,
1763    ) -> Result<aion_store::OutboxRow, TestError> {
1764        let outbox = state
1765            .outbox_store()
1766            .ok_or_else(|| test_error("the haematite boot store must carry the outbox"))?;
1767        let deadline = Instant::now() + POLL_DEADLINE;
1768        loop {
1769            let dead = outbox
1770                .list_dead_lettered_outbox_rows(workflow_id)
1771                .await
1772                .map_err(test_error)?;
1773            if let Some(row) = dead
1774                .into_iter()
1775                .find(|row| row.dispatch_key == dispatch_key)
1776            {
1777                return Ok(row);
1778            }
1779            if Instant::now() > deadline {
1780                let in_flight = outbox
1781                    .count_inflight_outbox_rows(NAMESPACE)
1782                    .await
1783                    .map_err(test_error)?;
1784                return Err(test_error(format!(
1785                    "the oversize row {dispatch_key} was never dead-lettered within \
1786                     {POLL_DEADLINE:?}; {in_flight} row(s) still in flight in `{NAMESPACE}`"
1787                )));
1788            }
1789            tokio::time::sleep(Duration::from_millis(25)).await;
1790        }
1791    }
1792
1793    /// The door-capacity acceptance, END TO END on the commission site.
1794    ///
1795    /// `outbox.liminal_max_connection_outbound_bytes` is set to 1 MiB, the EXACT
1796    /// production commissioning function builds the listener AND the connection
1797    /// supervisor from it, a REAL worker connects in, and a 2 MiB dispatch is
1798    /// pushed through that real connection twice over:
1799    ///
1800    /// 1. directly, on the registry's own delivery handle — the refusal must be
1801    ///    the unservable class and must name 1048576, not liminal's 4194304
1802    ///    default. This is the fact a config-field assertion cannot give: the
1803    ///    number the CONNECTION PROCESS enforces, which comes from the supervisor
1804    ///    the commission site built, not from the listener config it also built.
1805    ///    (The evening this was written, a `LimitsConfig` assertion passed while
1806    ///    every worker connection stayed at 4 MiB, because the supervisor was
1807    ///    still being built with default limits.) The refusal reaching the caller
1808    ///    at all, as unservable, is the reply-awaiter classification: liminal
1809    ///    settles this slot from the connection process after the push call has
1810    ///    returned, so it arrives on the awaiter, where it used to be wrapped as
1811    ///    the retryable dispatch class;
1812    /// 2. through the PRODUCTION outbox path — a staged row carrying the same
1813    ///    payload is claimed by the real dispatcher, pushed through the same door,
1814    ///    and dead-lettered on its FIRST observation: `failed` with the attempt
1815    ///    still at its seeded zero. A retryable reading would have re-armed it
1816    ///    (attempt bumped, backoff) and spent the whole budget on a certainty.
1817    ///
1818    /// The fixture's `collect_four` package and worker are the same as the boot
1819    /// round-trip's; only the door and the payload differ.
1820    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1821    async fn the_commissioned_door_reads_the_operator_key_and_refuses_an_oversize_push_by_name()
1822    -> Result<(), TestError> {
1823        let dir = crate::test_support::private_tempdir().map_err(test_error)?;
1824        let db_path = dir.path().join("aion.db");
1825        let package_path = write_package_archive(dir.path())?;
1826        let listen_address = reserve_loopback_port()?;
1827
1828        let config = server_config(&db_path, package_path, listen_address, Some(DOOR_BYTES));
1829        let outbox_config = config.outbox.clone();
1830        let patience = eligibility_patience(&config);
1831        let state = StateUnderTest::new(
1832            ServerState::build(config, &crate::control::StageReporter::detached())
1833                .await
1834                .map_err(test_error)?,
1835        );
1836        let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
1837        let backpressure_settings = BackpressureSettings {
1838            platform_default: crate::config::DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
1839            fraction: crate::worker::OwnedShardFraction::own_all(),
1840        };
1841        let listener_guard = maybe_spawn_outbox_dispatcher(
1842            &state,
1843            &outbox_config,
1844            false,
1845            backpressure_settings,
1846            &shutdown_rx,
1847            "set outbox.liminal_listen_address in the test config",
1848        )
1849        .map_err(test_error)?;
1850
1851        let executions = Arc::new(AtomicUsize::new(0));
1852        let worker = WorkerThread::spawn(
1853            listen_address.to_string(),
1854            worker_config()?,
1855            worker_registry(&executions)?,
1856        );
1857
1858        let outcome = observe_the_door(&state, listen_address, patience).await;
1859
1860        // Teardown on every path, as the other pins in this venue do.
1861        shutdown_tx.send(true).ok();
1862        worker.stop();
1863        drop(listener_guard);
1864        state.shutdown().map_err(test_error)?;
1865
1866        assert_eq!(
1867            executions.load(Ordering::SeqCst),
1868            0,
1869            "no oversize dispatch may ever reach the worker's handler"
1870        );
1871        outcome
1872    }
1873
1874    /// The measurement behind
1875    /// [`the_commissioned_door_reads_the_operator_key_and_refuses_an_oversize_push_by_name`],
1876    /// split out so its early returns cannot skip the harness teardown.
1877    async fn observe_the_door(
1878        state: &ServerState,
1879        listen_address: SocketAddr,
1880        patience: Duration,
1881    ) -> Result<(), TestError> {
1882        let registry = state.worker_registry().clone();
1883        wait_for_registration(
1884            &registry,
1885            state.heartbeat_tracker(),
1886            listen_address,
1887            patience,
1888        )
1889        .await?;
1890
1891        // (1) The door itself, on the registry's own delivery handle.
1892        let refusal = push_oversize_through_the_door(&registry).await?;
1893        let ServerError::WorkerDispatchUnservable { detail, .. } = &refusal else {
1894            return Err(test_error(format!(
1895                "an oversize push must be refused as UNSERVABLE (never lost-connection or a \
1896                 retryable dispatch fault), got: {refusal}"
1897            )));
1898        };
1899        let door = DOOR_BYTES.to_string();
1900        if !detail.contains(&format!("buffer is {door} bytes")) {
1901            return Err(test_error(format!(
1902                "the refusal must name the operator's door ({door} bytes): {detail}"
1903            )));
1904        }
1905        if detail.contains(LIMINAL_DEFAULT_DOOR) {
1906            return Err(test_error(format!(
1907                "the refusal names liminal's default door ({LIMINAL_DEFAULT_DOOR}); the supervisor \
1908                 was built without the operator's limits: {detail}"
1909            )));
1910        }
1911        if !detail.contains(crate::worker::liminal_transport::OUTBOUND_BOUND_KEY) {
1912            return Err(test_error(format!(
1913                "the refusal must name the key an operator would change: {detail}"
1914            )));
1915        }
1916        let frame = refused_frame_bytes(detail)?;
1917        let payload = u64::try_from(OVERSIZE_PAYLOAD_BYTES).map_err(test_error)?;
1918        if frame < payload || frame <= DOOR_BYTES {
1919            return Err(test_error(format!(
1920                "the refused frame must carry the {payload}-byte payload and exceed the \
1921                 {DOOR_BYTES}-byte door, but the refusal names {frame} bytes: {detail}"
1922            )));
1923        }
1924
1925        // (2) The production outbox path through the same door.
1926        let (workflow_id, dispatch_key) = stage_oversize_row(state).await?;
1927        let dead = wait_for_dead_letter(state, &workflow_id, &dispatch_key).await?;
1928        if dead.status != aion_store::OutboxStatus::Failed {
1929            return Err(test_error(format!(
1930                "the production dispatcher must dead-letter an unservable row, got {:?}",
1931                dead.status
1932            )));
1933        }
1934        if dead.attempt != 0 {
1935            return Err(test_error(format!(
1936                "an unservable row is dead-lettered on its FIRST observation, never re-armed for \
1937                 a retry; the attempt was bumped to {}",
1938                dead.attempt
1939            )));
1940        }
1941        Ok(())
1942    }
1943
1944    /// One dispatch as the WORKER saw it: the identity the server sent it under,
1945    /// and when it arrived.
1946    #[derive(Clone, Debug)]
1947    struct SeenDispatch {
1948        activity_type: String,
1949        activity_id: String,
1950        attempt: u32,
1951        at: Instant,
1952    }
1953
1954    /// A loopback TCP relay the test can BREAK, sitting between the worker and the
1955    /// production liminal listener.
1956    ///
1957    /// The worker dials this instead of the listener, so the test owns a socket it
1958    /// can shut from the outside. That is the only way to make a REAL
1959    /// [`LiminalActivityWorker`] lose its connection mid-flight without reaching
1960    /// inside either the worker or the server — and a link broken from the inside
1961    /// would be a different experiment, because the code under test would be the
1962    /// code doing the breaking.
1963    ///
1964    /// # Why this is not the relay in `tests/dead_man_switch_e2e.rs`
1965    ///
1966    /// That file has `WedgeableRelay`, which can both wedge and sever, and this is
1967    /// deliberately not it. The two cannot be one, for a structural reason rather
1968    /// than a matter of taste: an integration test links this crate as an ordinary
1969    /// dependency, so it can see neither `#[cfg(test)] pub(crate) mod test_support`
1970    /// nor the private `maybe_spawn_outbox_dispatcher` this harness is built on,
1971    /// and `src/` cannot see `tests/`. Sharing one instrument would mean exporting
1972    /// a public, feature-gated test surface from a production crate.
1973    ///
1974    /// So the split is stated rather than hidden, and this half is a strict subset:
1975    /// it only severs. Wedging — which leaves both sockets open and merely discards
1976    /// bytes, so writes keep succeeding into the kernel buffer — is a DIFFERENT
1977    /// instrument answering a different question. #69 is about a broken link, not
1978    /// a silent one.
1979    struct SeverableRelay {
1980        address: SocketAddr,
1981        /// Every relayed socket, held so [`Self::sever`] can break them.
1982        sockets: Arc<std::sync::Mutex<Vec<std::net::TcpStream>>>,
1983        stop: Arc<std::sync::atomic::AtomicBool>,
1984        handle: Option<std::thread::JoinHandle<()>>,
1985    }
1986
1987    impl SeverableRelay {
1988        /// Bind a loopback port and relay every accepted connection to `upstream`.
1989        fn spawn(upstream: SocketAddr) -> Result<Self, TestError> {
1990            let listener = std::net::TcpListener::bind("127.0.0.1:0").map_err(test_error)?;
1991            let address = listener.local_addr().map_err(test_error)?;
1992            // Non-blocking accept so the relay can be shut down deterministically
1993            // rather than by parking a thread in `accept` until something happens
1994            // to connect. Accepted sockets are put back into blocking mode
1995            // explicitly: on this platform they would otherwise inherit the flag
1996            // and every pump would spin on `WouldBlock`.
1997            listener.set_nonblocking(true).map_err(test_error)?;
1998            let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
1999            let sockets: Arc<std::sync::Mutex<Vec<std::net::TcpStream>>> =
2000                Arc::new(std::sync::Mutex::new(Vec::new()));
2001            let accept_stop = Arc::clone(&stop);
2002            let accept_sockets = Arc::clone(&sockets);
2003            let handle = std::thread::spawn(move || {
2004                while !accept_stop.load(Ordering::SeqCst) {
2005                    match listener.accept() {
2006                        Ok((downstream, _)) => {
2007                            if let Err(error) =
2008                                Self::relay_one(&downstream, upstream, &accept_sockets)
2009                            {
2010                                // The worker redials, so a connection this relay
2011                                // fails to carry surfaces as a slower recovery
2012                                // rather than as a wrong answer — but silence here
2013                                // would make that indistinguishable from the
2014                                // server never pushing, which is exactly the
2015                                // confusion this pin exists to resolve.
2016                                eprintln!("relay could not carry a connection: {error}");
2017                            }
2018                        }
2019                        Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
2020                            std::thread::sleep(Duration::from_millis(2));
2021                        }
2022                        Err(error) => {
2023                            eprintln!("relay accept failed: {error}");
2024                            return;
2025                        }
2026                    }
2027                }
2028            });
2029            Ok(Self {
2030                address,
2031                sockets,
2032                stop,
2033                handle: Some(handle),
2034            })
2035        }
2036
2037        /// Dial upstream for one accepted connection and pump both directions.
2038        fn relay_one(
2039            downstream: &std::net::TcpStream,
2040            upstream: SocketAddr,
2041            sockets: &Arc<std::sync::Mutex<Vec<std::net::TcpStream>>>,
2042        ) -> Result<(), TestError> {
2043            downstream.set_nonblocking(false).map_err(test_error)?;
2044            let up = std::net::TcpStream::connect(upstream).map_err(test_error)?;
2045            let down_read = downstream.try_clone().map_err(test_error)?;
2046            let down_write = downstream.try_clone().map_err(test_error)?;
2047            let up_read = up.try_clone().map_err(test_error)?;
2048            let up_write = up.try_clone().map_err(test_error)?;
2049            let held = downstream.try_clone().map_err(test_error)?;
2050            let mut parked = sockets
2051                .lock()
2052                .map_err(|_| test_error("relay socket register poisoned"))?;
2053            parked.push(held);
2054            parked.push(up);
2055            drop(parked);
2056            for (from, to) in [(down_read, up_write), (up_read, down_write)] {
2057                std::thread::spawn(move || Self::pump(from, to));
2058            }
2059            Ok(())
2060        }
2061
2062        /// Copy one direction until the connection ends.
2063        ///
2064        /// A read or write error here IS the severed link in the expected case, and
2065        /// in every case it means the peer this pump exists to serve is gone: there
2066        /// is no party left to propagate to, so ending the pump is the handling,
2067        /// not an omission of it.
2068        fn pump(mut from: std::net::TcpStream, mut to: std::net::TcpStream) {
2069            use std::io::{Read, Write};
2070            let mut buffer = [0_u8; 8192];
2071            loop {
2072                match from.read(&mut buffer) {
2073                    Ok(0) | Err(_) => return,
2074                    Ok(read) => {
2075                        if to.write_all(&buffer[..read]).is_err() {
2076                            return;
2077                        }
2078                    }
2079                }
2080            }
2081        }
2082
2083        const fn address(&self) -> SocketAddr {
2084            self.address
2085        }
2086
2087        /// BREAK every relayed socket, and report how many were broken.
2088        ///
2089        /// The count is returned, and asserted non-zero by the caller, so that a
2090        /// sever which severed nothing can never masquerade as a measurement — the
2091        /// pin would otherwise pass by never having run its own experiment.
2092        fn sever(&self) -> Result<usize, TestError> {
2093            let mut parked = self
2094                .sockets
2095                .lock()
2096                .map_err(|_| test_error("relay socket register poisoned"))?;
2097            let mut severed = 0;
2098            for socket in parked.iter() {
2099                if socket.shutdown(std::net::Shutdown::Both).is_ok() {
2100                    severed += 1;
2101                }
2102            }
2103            parked.clear();
2104            Ok(severed)
2105        }
2106
2107        fn shutdown(mut self) {
2108            self.stop.store(true, Ordering::SeqCst);
2109            if let Some(handle) = self.handle.take() {
2110                handle.join().ok();
2111            }
2112        }
2113    }
2114
2115    /// Registry for the reconnect pin: every dispatch is RECORDED with the identity
2116    /// the server sent it under, and [`HELD_ACTIVITY_TYPE`]'s FIRST dispatch holds
2117    /// — the work is finished, its reply is not yet on the wire — until released.
2118    ///
2119    /// Only the first is held. A blanket hold would stall the re-delivery this pin
2120    /// exists to observe, and the pin would then measure its own instrument.
2121    fn recording_registry(
2122        seen: &Arc<std::sync::Mutex<Vec<SeenDispatch>>>,
2123        release: &Arc<std::sync::atomic::AtomicBool>,
2124    ) -> Result<Arc<ActivityRegistry>, TestError> {
2125        let mut registry = ActivityRegistry::new();
2126        for activity_type in FAN_ACTIVITY_TYPES {
2127            let seen = Arc::clone(seen);
2128            let release = Arc::clone(release);
2129            let arrivals = Arc::new(AtomicUsize::new(0));
2130            registry = registry
2131                .register_activity_with_contract(
2132                    activity_type,
2133                    move |_input: FanInput, context: &aion_worker::ActivityContext| {
2134                        let seen = Arc::clone(&seen);
2135                        let release = Arc::clone(&release);
2136                        let arrivals = Arc::clone(&arrivals);
2137                        let record = SeenDispatch {
2138                            activity_type: activity_type.to_owned(),
2139                            activity_id: context.activity_id().to_string(),
2140                            attempt: context.attempt(),
2141                            at: Instant::now(),
2142                        };
2143                        Box::pin(async move {
2144                            // Recorded BEFORE the hold: a dispatch that arrives and
2145                            // is never answered must still be visible, or the pin
2146                            // cannot tell "never re-delivered" from "re-delivered
2147                            // and lost again".
2148                            match seen.lock() {
2149                                Ok(mut log) => log.push(record),
2150                                Err(_) => {
2151                                    return Err(aion_worker::ActivityFailure::terminal(
2152                                        "the pin's dispatch log is poisoned, so this run can \
2153                                         observe nothing — failing loudly rather than \
2154                                         returning a result no assertion could trust",
2155                                    ));
2156                                }
2157                            }
2158                            let first = arrivals.fetch_add(1, Ordering::SeqCst) == 0;
2159                            if activity_type == HELD_ACTIVITY_TYPE && first {
2160                                while !release.load(Ordering::SeqCst) {
2161                                    tokio::time::sleep(Duration::from_millis(5)).await;
2162                                }
2163                            }
2164                            Ok(activity_type.to_owned())
2165                        })
2166                    },
2167                )
2168                .map_err(test_error)?;
2169        }
2170        Ok(Arc::new(registry))
2171    }
2172
2173    fn dispatches_of(
2174        seen: &Arc<std::sync::Mutex<Vec<SeenDispatch>>>,
2175        activity_type: &str,
2176    ) -> Result<Vec<SeenDispatch>, TestError> {
2177        let log = seen
2178            .lock()
2179            .map_err(|_| test_error("the pin's dispatch log is poisoned"))?;
2180        Ok(log
2181            .iter()
2182            .filter(|record| record.activity_type == activity_type)
2183            .cloned()
2184            .collect())
2185    }
2186
2187    fn dispatch_log(seen: &Arc<std::sync::Mutex<Vec<SeenDispatch>>>) -> String {
2188        match seen.lock() {
2189            Ok(log) => format!("{:#?}", *log),
2190            Err(_) => String::from("<poisoned>"),
2191        }
2192    }
2193
2194    /// aion #69 at the ENGINE level: what the system DOES after an activity's
2195    /// completion is lost to a broken link.
2196    ///
2197    /// # What this measures, and why the transport-level pin cannot
2198    ///
2199    /// #69's existing red-first pin lives on its fix branch rather than here (it
2200    /// is red on purpose and lands with the fix), and it establishes that the
2201    /// completion is DISCARDED: the server abandons the correlated reply-wait the
2202    /// moment the delivering connection closes. It drives `WorkerDelivery`
2203    /// directly, with no engine, no store and no workflow behind it, so it can say
2204    /// nothing at all about what happens NEXT. That gap is the whole severity of
2205    /// #69: "the work is repeated once" and "the work is lost" are priced very
2206    /// differently, and nothing in-tree could tell them apart.
2207    ///
2208    /// So this pin observes four things, and asserts only what must hold in EVERY
2209    /// world — including the one a #69 fix creates:
2210    ///
2211    /// - **O4, ASSERTED** — the workflow still reaches a recorded terminal. This is
2212    ///   the invariant: a broken link must not cost the workflow. It is not a weak
2213    ///   assertion, because `collect_four` consumes all four members, so the
2214    ///   workflow cannot complete while any member's work is missing;
2215    /// - **O1, REPORTED** — whether the held activity is dispatched a SECOND time.
2216    ///   This is the MECHANISM, and the mechanism is what a fix changes: a fix that
2217    ///   carries the completion across the reconnect would produce NO re-delivery,
2218    ///   and a pin asserting one would read that fix as a regression.
2219    ///   regression;
2220    /// - **O2, asserted CONDITIONALLY** — if a re-delivery happened it must carry
2221    ///   the activity's OWN identity. That is what makes the finished work
2222    ///   discarded rather than recovered; a re-delivery under a different identity
2223    ///   is a different defect and must not pass quietly;
2224    /// - **O3, REPORTED** — the elapsed time from the break to the re-delivery, as
2225    ///   a NUMBER asserted against nothing. No threshold is invented here: the
2226    ///   right bound is a conversation to have with the measurement in hand.
2227    ///
2228    /// ⚠️ **O3 is recovery LATENCY, and latency is not COST.** The number is
2229    /// measured on a fixture activity that is a pure `String -> String`, so its
2230    /// repeat costs microseconds. The real cost of a repeat is the repeated
2231    /// activity's own runtime plus its repeated SIDE EFFECTS, which this pin does
2232    /// not measure and structurally cannot: #69's own exhibit was an *agent*
2233    /// activity, whose repeat is minutes of compute and files written twice.
2234    /// Quote the finding — *repeated work, not lost work, one repeat per in-flight
2235    /// activity* — rather than the milliseconds, which carry their premise (a
2236    /// trivial activity) only for as long as someone remembers to attach it.
2237    ///
2238    /// The settle-wait below is bounded by [`POLL_DEADLINE`], so this pin cannot
2239    /// hang; but that bound is ~100x the observed recovery, so it is a liveness
2240    /// guard and NOT a latency guard. A large latency regression would still pass
2241    /// here, reported in O3 and asserted by nothing — deliberately, because the
2242    /// correct bound is not derivable from the samples taken so far.
2243    ///
2244    /// Executions are REPORTED, never asserted equal to the fan-out. A transport
2245    /// that can lose a reply gives at-least-once delivery, so the sibling test's
2246    /// `executions == FAN_OUT` is the wrong shape here and must not be copied
2247    /// across.
2248    ///
2249    /// # The world this models
2250    ///
2251    /// One server process with its transport-loss ledger live in memory, a worker
2252    /// that redials the SAME address, and a SINGLE loss — well inside
2253    /// `TRANSPORT_LOSS_BUDGET_WINDOWS`. It is NOT a server restart and NOT budget
2254    /// exhaustion, both of which are different worlds with different recoveries.
2255    /// The re-delivery this venue can produce is the outbox dispatcher's re-claim
2256    /// under the `max_attempts`/backoff this test's config sets, not the #266
2257    /// recovery replay — which is what gives O3's number a slot to mean anything in.
2258    ///
2259    /// The relay's own accept poll (2ms) sits inside the measured elapsed.
2260    ///
2261    /// ⚠️ This pin shares a venue with
2262    /// `production_boot_dispatches_executes_and_records_over_liminal`, one of four
2263    /// documented carriers of a load-sensitive flake — 2/24 on a base that
2264    /// predates it (`gate-logs/lock-race-attribution/VERDICT.md`). It inherits that
2265    /// sensitivity, and a red here should be read against that register first.
2266    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
2267    async fn a_completion_lost_to_a_severed_link_is_re_dispatched_and_the_workflow_settles()
2268    -> Result<(), TestError> {
2269        let dir = crate::test_support::private_tempdir().map_err(test_error)?;
2270        let db_path = dir.path().join("aion.db");
2271        let package_path = write_package_archive(dir.path())?;
2272        let listen_address = reserve_loopback_port()?;
2273
2274        let config = server_config(&db_path, package_path, listen_address, None);
2275        let outbox_config = config.outbox.clone();
2276        // Captured before `build` consumes the config: the wait below is derived
2277        // from the very window this server is about to run its liveness probe on.
2278        let patience = eligibility_patience(&config);
2279        let state = StateUnderTest::new(
2280            ServerState::build(config, &crate::control::StageReporter::detached())
2281                .await
2282                .map_err(test_error)?,
2283        );
2284        let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
2285        let backpressure_settings = BackpressureSettings {
2286            platform_default: crate::config::DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
2287            fraction: crate::worker::OwnedShardFraction::own_all(),
2288        };
2289        let listener_guard = maybe_spawn_outbox_dispatcher(
2290            &state,
2291            &outbox_config,
2292            false,
2293            backpressure_settings,
2294            &shutdown_rx,
2295            "set outbox.liminal_listen_address in the test config",
2296        )
2297        .map_err(test_error)?;
2298
2299        // The worker dials the RELAY, which carries it to the production listener.
2300        let relay = SeverableRelay::spawn(listen_address)?;
2301        let seen = Arc::new(std::sync::Mutex::new(Vec::new()));
2302        let release = Arc::new(std::sync::atomic::AtomicBool::new(false));
2303        // The redial timings are the ones this module's `worker_config` already
2304        // declares, read off it rather than re-chosen here: a reconnect pin that
2305        // picked its own recovery timings would be measuring a world of its own.
2306        let config = worker_config()?;
2307        let timing = aion_worker::RedialTiming::new(
2308            config.reconnect.initial_backoff,
2309            config.reconnect.max_backoff,
2310        );
2311        let worker = WorkerThread::spawn_redialing(
2312            relay.address().to_string(),
2313            config,
2314            recording_registry(&seen, &release)?,
2315            timing,
2316        );
2317
2318        let outcome =
2319            observe_reconnect(&state, &relay, &seen, &release, listen_address, patience).await;
2320
2321        // Teardown runs on EVERY path, including a failing one: a leaked worker
2322        // thread or listener poisons whatever runs next, and this venue is already
2323        // load-sensitive enough without the pin adding to it.
2324        shutdown_tx.send(true).ok();
2325        release.store(true, Ordering::SeqCst);
2326        worker.stop();
2327        relay.shutdown();
2328        drop(listener_guard);
2329        state.shutdown().map_err(test_error)?;
2330        outcome
2331    }
2332
2333    /// The measurement behind
2334    /// [`a_completion_lost_to_a_severed_link_is_re_dispatched_and_the_workflow_settles`],
2335    /// split out so its many early returns cannot skip the harness teardown.
2336    /// Wait until the held member is dispatched and holding — the moment the link
2337    /// can be broken — and report how many of its siblings had already settled.
2338    ///
2339    /// The split at the break is REPORTED, never required. An earlier draft
2340    /// demanded that the other three settle first, for a single-variable
2341    /// experiment. Measured across runs it simply varies: the four pushes land
2342    /// within microseconds of each other and which records a terminal first is a
2343    /// race, so requiring a particular split would fail the pin for a reason that
2344    /// has nothing to do with what it measures.
2345    async fn await_held_dispatch(
2346        reader: &dyn aion_store::ReadableEventStore,
2347        workflow_id: &aion_core::WorkflowId,
2348        seen: &Arc<std::sync::Mutex<Vec<SeenDispatch>>>,
2349    ) -> Result<(SeenDispatch, usize), TestError> {
2350        let deadline = Instant::now() + POLL_DEADLINE;
2351        loop {
2352            if let Some(first) = dispatches_of(seen, HELD_ACTIVITY_TYPE)?.first() {
2353                let at_the_break = reader.read_history(workflow_id).await.map_err(test_error)?;
2354                return Ok((first.clone(), count_completed(&at_the_break)));
2355            }
2356            if Instant::now() > deadline {
2357                let history = reader.read_history(workflow_id).await.map_err(test_error)?;
2358                return Err(test_error(format!(
2359                    "{HELD_ACTIVITY_TYPE} was never dispatched at all within {POLL_DEADLINE:?}, \
2360                     so there was no held completion to lose and this run measured nothing.\n\
2361                     dispatch log: {}\nhistory: {history:#?}",
2362                    dispatch_log(seen),
2363                )));
2364            }
2365            tokio::time::sleep(Duration::from_millis(25)).await;
2366        }
2367    }
2368
2369    async fn observe_reconnect(
2370        state: &ServerState,
2371        relay: &SeverableRelay,
2372        seen: &Arc<std::sync::Mutex<Vec<SeenDispatch>>>,
2373        release: &Arc<std::sync::atomic::AtomicBool>,
2374        listen_address: SocketAddr,
2375        patience: Duration,
2376    ) -> Result<(), TestError> {
2377        wait_for_registration(
2378            state.worker_registry(),
2379            state.heartbeat_tracker(),
2380            listen_address,
2381            patience,
2382        )
2383        .await?;
2384
2385        let router = http_router(state.clone()).map_err(test_error)?;
2386        let workflow_id = start_over_http(&router).await?;
2387        let reader = state.engine().map_err(test_error)?.store();
2388
2389        let (first, settled_before) =
2390            await_held_dispatch(reader.as_ref(), &workflow_id, seen).await?;
2391
2392        // BREAK the link while the finished work is still holding its reply.
2393        let severed = relay.sever()?;
2394        let severed_at = Instant::now();
2395        if severed == 0 {
2396            return Err(test_error(
2397                "the relay severed NOTHING, so no link was ever broken and this run measured \
2398                 nothing — a pass here would have been an artefact of the instrument",
2399            ));
2400        }
2401        // Release the hold: the worker now writes its reply into a dead socket.
2402        release.store(true, Ordering::SeqCst);
2403
2404        // O4 FIRST, because it is the INVARIANT: a broken link must not cost the
2405        // workflow. Every other observable here describes the MECHANISM by which
2406        // that holds, and the mechanism is exactly what a #69 fix is expected to
2407        // change — so asserting today's mechanism would make the fix read as a
2408        // regression, and would be asserting the enumeration rather than the
2409        // invariant.
2410        //
2411        // O4 is load-bearing rather than weak because `collect_four` CONSUMES all
2412        // four members: the workflow cannot reach a completed terminal while any
2413        // member's work is missing, so "the workflow settled" is not a state that
2414        // silently lost work can also produce.
2415        let settled = wait_for_history(
2416            reader.as_ref(),
2417            &workflow_id,
2418            "the workflow to settle after the severed link",
2419            |events| count_completed(events) == FAN_OUT && count_workflow_completed(events) == 1,
2420        )
2421        .await
2422        .map_err(|error| {
2423            test_error(format!(
2424                "O4 FAILED — the workflow did not settle after the link broke ({severed} \
2425                 socket(s) severed), so the lost completion cost the workflow rather than \
2426                 costing a repeat of the work.\n{error}\ndispatch log: {}",
2427                dispatch_log(seen),
2428            ))
2429        })?;
2430        assert_eq!(
2431            count_completed(&settled),
2432            FAN_OUT,
2433            "every fan-out member must still record a terminal after the link broke"
2434        );
2435        assert_eq!(
2436            count_workflow_completed(&settled),
2437            1,
2438            "the workflow must complete exactly once even though a completion was lost"
2439        );
2440
2441        // O1/O2/O3 — the MECHANISM, reported. O2 is asserted only CONDITIONALLY:
2442        // if a re-delivery happened it must have carried the activity's own
2443        // identity, because a re-delivery under a different identity would be a
2444        // different defect entirely and must not pass quietly. If no re-delivery
2445        // happened, the completion survived the reconnect — which is what a fixed
2446        // #69 looks like, and this pin should report it, not fail on it.
2447        let held = dispatches_of(seen, HELD_ACTIVITY_TYPE)?;
2448        match held.get(1) {
2449            None => println!(
2450                "aion#69 — {HELD_ACTIVITY_TYPE} ({}) was NOT re-dispatched and the workflow \
2451                 still settled, so the held completion survived the break; {settled_before} of \
2452                 {FAN_OUT} members had settled when it broke, {severed} socket(s) severed",
2453                first.activity_id,
2454            ),
2455            Some(second) => {
2456                if second.activity_id != first.activity_id {
2457                    return Err(test_error(format!(
2458                        "O2 FAILED — the re-delivery carried a DIFFERENT activity identity. The \
2459                         first dispatch was {} (attempt {}) and the second was {} (attempt {}), \
2460                         so the work was not re-run under its own identity and #69's framing \
2461                         does not describe what happened here.",
2462                        first.activity_id, first.attempt, second.activity_id, second.attempt,
2463                    )));
2464                }
2465                if second.attempt != first.attempt {
2466                    return Err(test_error(format!(
2467                        "O2 FAILED — the re-delivery of {} carried attempt {} where the first \
2468                         delivery carried attempt {}. A transport redelivery is the SAME attempt \
2469                         (NOI-0: only a new ActivityStarted mints a new one); a different number \
2470                         means the wire was stamped with the outbox delivery count, so the lease \
2471                         and completion would name an attempt no start recorded.",
2472                        first.activity_id, second.attempt, first.attempt,
2473                    )));
2474                }
2475                let recovery = second.at.saturating_duration_since(severed_at);
2476                println!(
2477                    "aion#69 O3 — re-delivery of {} ({}) took {}ms from the link breaking; \
2478                     first attempt {}, second attempt {}; {settled_before} of {FAN_OUT} members \
2479                     had already recorded a terminal when the link broke; {severed} socket(s) \
2480                     severed",
2481                    HELD_ACTIVITY_TYPE,
2482                    first.activity_id,
2483                    recovery.as_millis(),
2484                    first.attempt,
2485                    second.attempt,
2486                );
2487            }
2488        }
2489
2490        let all = seen
2491            .lock()
2492            .map_err(|_| test_error("the pin's dispatch log is poisoned"))?
2493            .len();
2494        println!(
2495            "aion#69 — {all} dispatch(es) served for {FAN_OUT} activities; the transport is \
2496             at-least-once, so the excess is the repeated work a broken link costs"
2497        );
2498        Ok(())
2499    }
2500}