projectatlas_cli/
parser_supervisor.rs

1//! Bounded process supervision for the separately shipped optional parser pack.
2
3use std::fs::{self, File, Metadata};
4#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5use std::io::Seek;
6use std::io::{self, Read, Write};
7#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
8use std::os::fd::AsRawFd;
9use std::path::{Path, PathBuf};
10use std::process::{Child, ChildStdout, Command, ExitStatus, Stdio};
11use std::sync::OnceLock;
12#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
13use std::sync::atomic::AtomicU64;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender, TryRecvError, TrySendError};
16#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
17use std::sync::{Arc, Mutex};
18use std::thread::{self, JoinHandle};
19use std::time::{Duration, Instant, SystemTime};
20
21#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
22use crate::parser_linux_authority::{
23    ACCEPTED_FD_ARGUMENT, ARTIFACT_FD_ARGUMENT, GRAMMAR_FD_ARGUMENT, POLICY_FD_ARGUMENT,
24    SERVE_ARGUMENT,
25};
26use projectatlas_core::IndexCancellation;
27use projectatlas_core::optional_parser_pack::{
28    OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES, OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES,
29    OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES, OptionalParserPackArtifactManifest,
30    OptionalParserPackManifest, OptionalParserPackManifestError, PackPlatform,
31    ParserPackMemoryProbe, ParserPackPayloadRole,
32};
33#[cfg(any(
34    all(target_os = "linux", target_arch = "x86_64"),
35    all(target_os = "windows", target_arch = "x86_64")
36))]
37use projectatlas_core::optional_parser_pack::{ParserPackMemoryControl, ParserPackVerifiedControl};
38#[cfg(windows)]
39use projectatlas_core::optional_parser_protocol::PARSER_WINDOWS_BROKER_MEMORY_LIMIT_EXIT_CODE;
40use projectatlas_core::optional_parser_protocol::{
41    PARSER_FRAME_HEADER_BYTES, PARSER_MAX_NODE_COUNT, PARSER_MAX_OUTPUT_BYTES,
42    PARSER_MAX_SOURCE_BYTES, PARSER_MAX_STDERR_BYTES, PARSER_MAX_TREE_DEPTH,
43    PARSER_SESSION_ENTROPY_BYTES, PARSER_WINDOWS_BROKER_ADMISSION_RECORD, ParserArtifactIdentity,
44    ParserCompletionEvidence, ParserContainmentKind, ParserControl, ParserFailureCode, ParserFrame,
45    ParserFrameHeader, ParserFrameKind, ParserLanguageIdentity, ParserProgress,
46    ParserProgressDisposition, ParserProtocolError, ParserRequest, ParserRequestIdentity,
47    ParserRequestLimits, ParserSessionIdentity, ParserSessionOpen, ParserSourceIdentity,
48    decode_parser_completion_for_request, decode_parser_failure_for_request,
49    decode_parser_progress_for_request, decode_parser_ready_for_launch, encode_parser_control,
50};
51use projectatlas_core::optional_parser_protocol::{
52    PARSER_WORKER_JOB_MEMORY_BYTES, PARSER_WORKER_PROCESS_MEMORY_BYTES,
53};
54use sha2::{Digest, Sha256};
55use thiserror::Error;
56
57/// Exact logical capability manifest packaged beside the worker.
58const ACCEPTED_MANIFEST_FILE_NAME: &str = "accepted-capabilities.json";
59/// Exact immutable artifact manifest packaged beside the worker.
60const ARTIFACT_MANIFEST_FILE_NAME: &str = "artifact-manifest.json";
61/// Only accepted Windows broker operation.
62#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
63const BROKER_SERVE_ARGUMENT: &str = "serve-worker";
64/// Poll interval for cancellation and bounded child state.
65const SUPERVISOR_POLL_INTERVAL: Duration = Duration::from_millis(20);
66/// Parent-only random record that orders each stdout frame against stderr.
67const PARSER_DIAGNOSTIC_FENCE_BYTES: usize = 32;
68/// Grace period for a healthy worker to close after its input pipe closes.
69const SUPERVISOR_GRACEFUL_CLOSE: Duration = Duration::from_millis(500);
70/// Hard cleanup ceiling after a child session becomes terminal.
71const SUPERVISOR_CLEANUP_TIMEOUT: Duration = Duration::from_secs(5);
72/// Absolute deadline shared by both fixtures for one artifact grammar admission.
73const ARTIFACT_ADMISSION_TIMEOUT: Duration = Duration::from_secs(15);
74/// Aggregate ceiling for one complete lifecycle/release artifact admission.
75const ARTIFACT_ADMISSION_AGGREGATE_TIMEOUT: Duration = Duration::from_secs(20 * 60);
76/// Maximum interval without meaningful worker progress during artifact admission.
77const ARTIFACT_ADMISSION_NO_PROGRESS_TIMEOUT: Duration = Duration::from_secs(5);
78/// Test-only launch allowance for hostile fixtures that do not stall admission.
79#[cfg(test)]
80const ADVERSARIAL_NON_STALL_LAUNCH_NO_PROGRESS: Duration = Duration::from_secs(2);
81/// Source bytes that force post-admission parser allocation through the Windows job limit.
82const WINDOWS_MEMORY_PROBE_SOURCE_BYTES: usize = 1024 * 1024;
83/// Declared maximum interval between sampled Linux resident-memory observations.
84pub const PARSER_LINUX_RSS_OBSERVATION_INTERVAL: Duration = Duration::from_millis(20);
85
86/// Select the launch/admission allowance without changing the operation allowance.
87#[cfg(test)]
88fn adversarial_launch_no_progress(scenario: &str, operation_no_progress: Duration) -> Duration {
89    match scenario {
90        "pre-ready-stall" | "admission-stall" => operation_no_progress,
91        _ => ADVERSARIAL_NON_STALL_LAUNCH_NO_PROGRESS,
92    }
93}
94
95/// Require an adversarial absolute-deadline failure from the intended protocol phase.
96#[cfg(test)]
97fn adversarial_deadline_matches(
98    error: &ParserSupervisorError,
99    expected_phase: &'static str,
100) -> bool {
101    matches!(
102        error,
103        ParserSupervisorError::DeadlineExceeded { phase } if *phase == expected_phase
104    )
105}
106
107/// Closed memory ceilings owned by one supervisor instance.
108#[derive(Clone, Copy)]
109struct ParserMemoryLimits {
110    /// Maximum resident or committed bytes for the contained worker.
111    process_bytes: u64,
112    /// Maximum aggregate bytes for the contained process tree.
113    process_tree_bytes: u64,
114}
115
116impl ParserMemoryLimits {
117    /// Production parser-worker ceilings.
118    const PRODUCTION: Self = Self {
119        process_bytes: PARSER_WORKER_PROCESS_MEMORY_BYTES,
120        process_tree_bytes: PARSER_WORKER_JOB_MEMORY_BYTES,
121    };
122
123    /// Validate a release-probe limit before it reaches an OS adapter.
124    fn checked(self) -> Result<Self, ParserSupervisorError> {
125        if self.process_bytes == 0
126            || self.process_bytes > PARSER_WORKER_PROCESS_MEMORY_BYTES
127            || self.process_tree_bytes < self.process_bytes
128            || self.process_tree_bytes > PARSER_WORKER_JOB_MEMORY_BYTES
129        {
130            return Err(ParserSupervisorError::InvalidMemoryLimits {
131                process_bytes: self.process_bytes,
132                process_tree_bytes: self.process_tree_bytes,
133            });
134        }
135        Ok(self)
136    }
137}
138/// Bounded chunks used while reading artifact files.
139const ARTIFACT_READ_CHUNK_BYTES: usize = 64 * 1024;
140/// Request phase used while reading parser-pack launch authority.
141const ARTIFACT_IO_PHASE: &str = "artifact authority";
142/// Request phase covering process creation and synchronous supervisor setup.
143const PROCESS_LAUNCH_PHASE: &str = "process launch";
144/// Only one potentially blocked artifact reader may exist per process.
145/// ponytail: use a killable helper process if stuck kernel reads become an observed problem.
146static ARTIFACT_IO_ACTIVE: AtomicBool = AtomicBool::new(false);
147/// Process-wide lease that caps potentially blocked child creation at one.
148static PROCESS_SPAWN_ACTIVE: AtomicBool = AtomicBool::new(false);
149/// Sticky fail-closed ownership for cleanup that completed after its caller returned.
150static PROCESS_SPAWN_CLEANUP_FAILURE: std::sync::Mutex<Option<String>> =
151    std::sync::Mutex::new(None);
152/// One-shot deterministic handoff used only by debug-build Linux race tests.
153#[cfg(all(debug_assertions, target_os = "linux", target_arch = "x86_64"))]
154static LINUX_LAUNCH_TEST_HOOK: Mutex<Option<Box<dyn FnOnce() + Send>>> = Mutex::new(None);
155/// One-shot debug-test delay at the real currentness boundary.
156#[cfg(debug_assertions)]
157static CURRENTNESS_TEST_HOOK: std::sync::Mutex<Option<Box<dyn FnOnce() + Send>>> =
158    std::sync::Mutex::new(None);
159/// One-shot debug-test delay immediately before the cumulative process-launch bound check.
160#[cfg(debug_assertions)]
161static PRE_SPAWN_TEST_HOOK: std::sync::Mutex<Option<Box<dyn FnOnce() + Send>>> =
162    std::sync::Mutex::new(None);
163/// One-shot unit-test delay after an owner-retained rendezvous and before final bounds.
164#[cfg(test)]
165static PROCESS_SPAWN_AFTER_RENDEZVOUS_TEST_HOOK: std::sync::Mutex<
166    Option<Box<dyn FnOnce() + Send>>,
167> = std::sync::Mutex::new(None);
168/// One-shot unit-test delay after the final bounds decision and before owner notification.
169#[cfg(test)]
170static PROCESS_SPAWN_AFTER_FINAL_CHECK_TEST_HOOK: std::sync::Mutex<
171    Option<Box<dyn FnOnce() + Send>>,
172> = std::sync::Mutex::new(None);
173/// One-shot unit-test delay before owner-side unadmitted-child cleanup.
174#[cfg(test)]
175static PROCESS_SPAWN_BEFORE_CLEANUP_TEST_HOOK: std::sync::Mutex<Option<Box<dyn FnOnce() + Send>>> =
176    std::sync::Mutex::new(None);
177/// Maximum bytes read from one kernel-owned Linux accounting record.
178#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
179const LINUX_MEMORY_RECORD_MAX_BYTES: u64 = 64 * 1024;
180/// Canonical unified-cgroup mount used only when the current user already owns a delegation.
181#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
182const CGROUP_V2_ROOT: &str = "/sys/fs/cgroup";
183/// Maximum unified-cgroup ancestors inspected while locating an existing delegation.
184#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
185const MAX_CGROUP_ANCESTORS: usize = 32;
186/// Process-local collision guard for delegated child-cgroup names.
187#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
188static CGROUP_SEQUENCE: AtomicU64 = AtomicU64::new(1);
189
190/// Closed Linux resident-memory accounting mode attached to one worker session.
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
192pub enum ParserMemoryAccountingKind {
193    /// Kernel-enforced delegated cgroup-v2 memory accounting.
194    LinuxCgroupV2,
195    /// Bounded supervisor sampling of the single-worker `VmRSS` record.
196    LinuxProcStatus,
197}
198
199/// Failure while validating, running, or cleaning up the optional parser supervisor.
200#[derive(Debug, Error)]
201pub enum ParserSupervisorError {
202    /// The current host has no accepted optional-pack containment adapter.
203    #[error("optional parser containment is unsupported on {os}/{architecture}")]
204    UnsupportedContainment {
205        /// Host operating-system identity.
206        os: &'static str,
207        /// Host architecture identity.
208        architecture: &'static str,
209    },
210    /// A required pack path could not be canonicalized or inspected.
211    #[error("could not inspect optional parser pack path {path:?}")]
212    PackPath {
213        /// Path being inspected.
214        path: PathBuf,
215        /// Filesystem failure.
216        #[source]
217        source: io::Error,
218    },
219    /// A required pack path violated the immutable artifact boundary.
220    #[error("optional parser pack path {path:?} is invalid: {reason}")]
221    InvalidPackPath {
222        /// Rejected path.
223        path: PathBuf,
224        /// Stable rejection reason.
225        reason: &'static str,
226    },
227    /// One bounded artifact file could not be read.
228    #[error("could not read optional parser artifact file {path:?}")]
229    ArtifactRead {
230        /// Artifact file being read.
231        path: PathBuf,
232        /// Filesystem failure.
233        #[source]
234        source: io::Error,
235    },
236    /// Linux could not construct one immutable launch payload.
237    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
238    #[error("could not construct sealed Linux parser launch authority for {role}")]
239    LinuxLaunchAuthority {
240        /// Stable payload responsibility.
241        role: &'static str,
242        /// Operating-system failure.
243        #[source]
244        source: io::Error,
245    },
246    /// One artifact file exceeded its declared byte ceiling.
247    #[error("optional parser artifact file {path:?} has {actual} bytes; maximum is {maximum}")]
248    ArtifactFileTooLarge {
249        /// Oversized file.
250        path: PathBuf,
251        /// Observed bytes.
252        actual: u64,
253        /// Inclusive maximum.
254        maximum: u64,
255    },
256    /// The strict artifact manifest could not be decoded.
257    #[error("optional parser artifact manifest is invalid")]
258    ArtifactManifestJson {
259        /// Strict JSON decoding failure.
260        #[source]
261        source: serde_json::Error,
262    },
263    /// A logical or artifact manifest invariant failed.
264    #[error("optional parser pack manifest validation failed")]
265    ManifestValidation {
266        /// Typed manifest failure.
267        #[source]
268        source: OptionalParserPackManifestError,
269    },
270    /// An artifact payload did not match its immutable manifest row.
271    #[error("optional parser artifact payload {path:?} is invalid: {reason}")]
272    PayloadMismatch {
273        /// Rejected payload path.
274        path: PathBuf,
275        /// Stable rejection reason.
276        reason: &'static str,
277    },
278    /// A requested grammar is absent from the accepted capability manifest.
279    #[error("optional parser grammar {language_id:?} is not accepted by the verified artifact")]
280    GrammarNotAccepted {
281        /// Rejected language identity.
282        language_id: String,
283    },
284    /// One accepted fixture produced the opposite root-error state.
285    #[error(
286        "optional parser grammar {language_id:?} fixture {case_name:?} error state was {actual}; expected {expected}"
287    )]
288    FixtureExpectationMismatch {
289        /// Accepted grammar identity under admission.
290        language_id: String,
291        /// Manifest-owned fixture case name.
292        case_name: String,
293        /// Identity-validated worker result.
294        actual: bool,
295        /// Manifest-declared positive or negative expectation.
296        expected: bool,
297    },
298    /// An internal release probe requested invalid worker or process-tree ceilings.
299    #[error(
300        "optional parser memory limits are invalid: process {process_bytes} bytes; process tree {process_tree_bytes} bytes"
301    )]
302    InvalidMemoryLimits {
303        /// Requested per-worker ceiling.
304        process_bytes: u64,
305        /// Requested aggregate process-tree ceiling.
306        process_tree_bytes: u64,
307    },
308    /// The exact worker completed under a deliberately reduced release-probe ceiling.
309    #[error(
310        "optional parser memory-boundary probe did not breach its {process_bytes}-byte worker ceiling"
311    )]
312    MemoryProbeDidNotBreach {
313        /// Deliberately reduced ceiling that should be below exact-worker residency.
314        process_bytes: u64,
315    },
316    /// A parser protocol invariant failed.
317    #[error("optional parser protocol validation failed")]
318    Protocol {
319        /// Typed protocol failure.
320        #[source]
321        source: ParserProtocolError,
322    },
323    /// Operating-system entropy was unavailable for a fresh worker session.
324    #[error("operating-system entropy was unavailable for the optional parser session")]
325    EntropyUnavailable,
326    /// The exact worker or containment broker could not be started.
327    #[error("could not launch optional parser program {program:?}")]
328    Spawn {
329        /// Exact verified executable.
330        program: PathBuf,
331        /// Process creation failure.
332        #[source]
333        source: io::Error,
334    },
335    /// A required child protocol pipe was not created.
336    #[error("optional parser child did not expose its {stream} protocol pipe")]
337    MissingPipe {
338        /// Missing standard-stream identity.
339        stream: &'static str,
340    },
341    /// The Windows broker did not emit the exact admission record.
342    #[error("optional parser Windows containment admission did not validate")]
343    InvalidAdmission,
344    /// A bounded I/O thread failed.
345    #[error("optional parser {phase} I/O failed: {message}")]
346    IoThread {
347        /// Stable I/O phase.
348        phase: &'static str,
349        /// Bounded failure detail.
350        message: String,
351    },
352    /// The caller requested cooperative cancellation.
353    #[error("optional parser operation was cancelled during {phase}")]
354    Cancelled {
355        /// Stable operation phase.
356        phase: &'static str,
357    },
358    /// The caller-owned absolute deadline elapsed.
359    #[error("optional parser absolute deadline elapsed during {phase}")]
360    DeadlineExceeded {
361        /// Stable operation phase.
362        phase: &'static str,
363    },
364    /// No meaningful progress occurred within the caller limit.
365    #[error("optional parser made no progress during {phase}")]
366    NoProgress {
367        /// Stable operation phase.
368        phase: &'static str,
369    },
370    /// The direct child exited before completing its protocol operation.
371    #[error("optional parser child exited during {phase} with code {code:?}")]
372    ChildExited {
373        /// Stable operation phase.
374        phase: &'static str,
375        /// Portable process exit code when available.
376        code: Option<i32>,
377    },
378    /// Linux resident memory reached its configured ceiling and the worker group was terminated.
379    #[error(
380        "optional parser resident-memory ceiling reached during {phase}: observed {observed_bytes} bytes with {accounting:?}; maximum {maximum_bytes} bytes; observation interval {observation_interval_millis} ms"
381    )]
382    ResidentMemoryLimitExceeded {
383        /// Stable operation phase.
384        phase: &'static str,
385        /// Active Linux accounting path.
386        accounting: ParserMemoryAccountingKind,
387        /// Last observed resident or cgroup memory bytes.
388        observed_bytes: u64,
389        /// Inclusive configured ceiling.
390        maximum_bytes: u64,
391        /// Declared maximum sampling interval.
392        observation_interval_millis: u64,
393    },
394    /// Linux resident-memory accounting became unreadable while the worker was live.
395    #[error(
396        "optional parser resident-memory observation failed during {phase} with {accounting:?}: {message}"
397    )]
398    ResidentMemoryObservationFailed {
399        /// Stable operation phase.
400        phase: &'static str,
401        /// Accounting path that failed closed.
402        accounting: ParserMemoryAccountingKind,
403        /// Bounded failure detail.
404        message: String,
405    },
406    /// The Windows broker observed an exact Job process/job memory-limit completion message.
407    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
408    #[error("optional parser Windows Job memory ceiling was reached during {phase}")]
409    WindowsJobMemoryLimitExceeded {
410        /// Stable operation phase in which the broker terminated.
411        phase: &'static str,
412    },
413    /// The worker returned an identity-validated closed failure.
414    #[error("optional parser worker returned {code:?}")]
415    WorkerFailure {
416        /// Closed worker failure code.
417        code: ParserFailureCode,
418    },
419    /// The session-local request identity space was exhausted.
420    #[error("optional parser request identity space was exhausted")]
421    RequestIdentityExhausted,
422    /// Child-tree termination, pipe draining, reaping, or thread joining failed.
423    #[error("optional parser cleanup failed: {message}")]
424    Cleanup {
425        /// Bounded cleanup detail.
426        message: String,
427    },
428    /// An operation failed and its mandatory cleanup also failed.
429    #[error("optional parser operation failed: {operation}; cleanup also failed: {cleanup}")]
430    OperationAndCleanup {
431        /// Original typed operation failure.
432        operation: Box<Self>,
433        /// Typed cleanup failure.
434        cleanup: Box<Self>,
435    },
436}
437
438/// Install one debug-build hook after sealed authority is ready and before spawn.
439#[cfg(all(debug_assertions, target_os = "linux", target_arch = "x86_64"))]
440#[doc(hidden)]
441pub fn install_linux_launch_test_hook(
442    hook: impl FnOnce() + Send + 'static,
443) -> Result<(), ParserSupervisorError> {
444    let mut slot =
445        LINUX_LAUNCH_TEST_HOOK
446            .lock()
447            .map_err(|_poisoned| ParserSupervisorError::IoThread {
448                phase: "Linux launch test hook",
449                message: "test hook lock is poisoned".to_owned(),
450            })?;
451    if slot.is_some() {
452        return Err(ParserSupervisorError::IoThread {
453            phase: "Linux launch test hook",
454            message: "another test hook is already installed".to_owned(),
455        });
456    }
457    *slot = Some(Box::new(hook));
458    Ok(())
459}
460
461/// Invoke and remove the one installed debug-build Linux launch hook.
462#[cfg(all(debug_assertions, target_os = "linux", target_arch = "x86_64"))]
463fn invoke_linux_launch_test_hook() -> Result<(), ParserSupervisorError> {
464    let hook = LINUX_LAUNCH_TEST_HOOK
465        .lock()
466        .map_err(|_poisoned| ParserSupervisorError::IoThread {
467            phase: "Linux launch test hook",
468            message: "test hook lock is poisoned".to_owned(),
469        })?
470        .take();
471    if let Some(hook) = hook {
472        hook();
473    }
474    Ok(())
475}
476
477/// Install one debug-build hook at the first launch-input currentness observation.
478#[cfg(debug_assertions)]
479#[doc(hidden)]
480pub fn install_currentness_test_hook(
481    hook: impl FnOnce() + Send + 'static,
482) -> Result<(), ParserSupervisorError> {
483    let mut slot =
484        CURRENTNESS_TEST_HOOK
485            .lock()
486            .map_err(|_poisoned| ParserSupervisorError::IoThread {
487                phase: ARTIFACT_IO_PHASE,
488                message: "currentness test hook lock is poisoned".to_owned(),
489            })?;
490    if slot.is_some() {
491        return Err(ParserSupervisorError::IoThread {
492            phase: ARTIFACT_IO_PHASE,
493            message: "another currentness test hook is already installed".to_owned(),
494        });
495    }
496    *slot = Some(Box::new(hook));
497    Ok(())
498}
499
500/// Invoke and remove the installed currentness test hook.
501#[cfg(debug_assertions)]
502fn invoke_currentness_test_hook() -> Result<(), ParserSupervisorError> {
503    let hook = CURRENTNESS_TEST_HOOK
504        .lock()
505        .map_err(|_poisoned| ParserSupervisorError::IoThread {
506            phase: ARTIFACT_IO_PHASE,
507            message: "currentness test hook lock is poisoned".to_owned(),
508        })?
509        .take();
510    if let Some(hook) = hook {
511        hook();
512    }
513    Ok(())
514}
515
516/// Install one debug-build delay before the final pre-spawn bound check.
517#[cfg(debug_assertions)]
518#[doc(hidden)]
519pub fn install_pre_spawn_test_hook(
520    hook: impl FnOnce() + Send + 'static,
521) -> Result<(), ParserSupervisorError> {
522    let mut slot =
523        PRE_SPAWN_TEST_HOOK
524            .lock()
525            .map_err(|_poisoned| ParserSupervisorError::IoThread {
526                phase: PROCESS_LAUNCH_PHASE,
527                message: "pre-spawn test hook lock is poisoned".to_owned(),
528            })?;
529    if slot.is_some() {
530        return Err(ParserSupervisorError::IoThread {
531            phase: PROCESS_LAUNCH_PHASE,
532            message: "another pre-spawn test hook is already installed".to_owned(),
533        });
534    }
535    *slot = Some(Box::new(hook));
536    Ok(())
537}
538
539/// Invoke and remove the installed pre-spawn test hook.
540#[cfg(debug_assertions)]
541fn invoke_pre_spawn_test_hook() -> Result<(), ParserSupervisorError> {
542    let hook = PRE_SPAWN_TEST_HOOK
543        .lock()
544        .map_err(|_poisoned| ParserSupervisorError::IoThread {
545            phase: PROCESS_LAUNCH_PHASE,
546            message: "pre-spawn test hook lock is poisoned".to_owned(),
547        })?
548        .take();
549    if let Some(hook) = hook {
550        hook();
551    }
552    Ok(())
553}
554
555impl ParserSupervisorError {
556    /// Return whether the caller stopped an otherwise live protocol operation.
557    const fn is_caller_stop(&self) -> bool {
558        matches!(
559            self,
560            Self::Cancelled { .. } | Self::DeadlineExceeded { .. } | Self::NoProgress { .. }
561        )
562    }
563
564    /// Return whether mandatory process, pipe, reap, or thread cleanup failed.
565    ///
566    /// [`Self::OperationAndCleanup`] is itself a cleanup failure even when its
567    /// operation or cleanup branch contains another nested combined failure.
568    #[must_use]
569    pub const fn has_mandatory_cleanup_failure(&self) -> bool {
570        matches!(
571            self,
572            Self::Cleanup { .. } | Self::OperationAndCleanup { .. }
573        )
574    }
575}
576
577impl From<ParserProtocolError> for ParserSupervisorError {
578    fn from(source: ParserProtocolError) -> Self {
579        Self::Protocol { source }
580    }
581}
582
583impl From<OptionalParserPackManifestError> for ParserSupervisorError {
584    fn from(source: OptionalParserPackManifestError) -> Self {
585        Self::ManifestValidation { source }
586    }
587}
588
589/// Constant-size filesystem identity used to detect mutation without rehashing a hot path.
590#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
591struct FileChangeEpoch {
592    /// Observed file length.
593    bytes: u64,
594    /// Modification timestamp when the host filesystem exposes one.
595    modified: Option<SystemTime>,
596    /// Filesystem device identity.
597    #[cfg(unix)]
598    device: u64,
599    /// Filesystem inode identity.
600    #[cfg(unix)]
601    inode: u64,
602    /// Last metadata-change time, which cannot be restored through ordinary mtime APIs.
603    #[cfg(unix)]
604    changed_seconds: i64,
605    /// Nanosecond component of the last metadata-change time.
606    #[cfg(unix)]
607    changed_nanoseconds: i64,
608    /// Windows file attributes captured while an owned handle denies writes and replacement.
609    #[cfg(windows)]
610    attributes: u32,
611    /// Windows creation time captured while an owned handle denies writes and replacement.
612    #[cfg(windows)]
613    created: u64,
614}
615
616impl FileChangeEpoch {
617    /// Capture the platform metadata that changes when an observed file is replaced or mutated.
618    fn from_metadata(metadata: &Metadata) -> Self {
619        if !metadata.is_file() {
620            return Self::default();
621        }
622
623        #[cfg(unix)]
624        {
625            use std::os::unix::fs::MetadataExt;
626
627            Self {
628                bytes: metadata.len(),
629                modified: metadata.modified().ok(),
630                device: metadata.dev(),
631                inode: metadata.ino(),
632                changed_seconds: metadata.ctime(),
633                changed_nanoseconds: metadata.ctime_nsec(),
634            }
635        }
636        #[cfg(windows)]
637        {
638            use std::os::windows::fs::MetadataExt;
639
640            Self {
641                bytes: metadata.len(),
642                modified: metadata.modified().ok(),
643                attributes: metadata.file_attributes(),
644                created: metadata.creation_time(),
645            }
646        }
647        #[cfg(not(any(unix, windows)))]
648        {
649            Self {
650                bytes: metadata.len(),
651                modified: metadata.modified().ok(),
652            }
653        }
654    }
655}
656
657/// Open one observed file while denying Windows write and replacement sharing.
658fn open_observed_file(path: &Path) -> Result<File, ParserSupervisorError> {
659    let mut options = fs::OpenOptions::new();
660    options.read(true);
661    #[cfg(windows)]
662    {
663        use std::os::windows::fs::OpenOptionsExt;
664
665        const FILE_SHARE_READ: u32 = 1;
666        options.share_mode(FILE_SHARE_READ);
667    }
668    options
669        .open(path)
670        .map_err(|source| ParserSupervisorError::ArtifactRead {
671            path: path.to_path_buf(),
672            source,
673        })
674}
675
676/// One file observed before digest verification and kept write-locked on Windows.
677#[derive(Debug)]
678struct FileObservation {
679    /// Canonical file path.
680    path: PathBuf,
681    /// Constant-size identity captured before digest verification.
682    epoch: FileChangeEpoch,
683    /// Owned handle that denies Windows write and replacement sharing.
684    #[cfg(windows)]
685    write_guard: Option<File>,
686}
687
688/// Owned constant-size file identity safe to move behind bounded filesystem I/O.
689#[derive(Debug)]
690struct FileCurrentnessProbe {
691    /// Canonical path whose current identity must still match.
692    path: PathBuf,
693    /// Identity captured before digest verification.
694    epoch: FileChangeEpoch,
695    /// Whether Windows still owns the deny-write/delete handle.
696    #[cfg(windows)]
697    guarded: bool,
698    /// Deterministic metadata-boundary blocker for cancellation tests.
699    #[cfg(test)]
700    blocker: Option<std::sync::Arc<MetadataProbeBlocker>>,
701}
702
703/// Deterministically pauses the test-only path-observation boundary.
704#[cfg(test)]
705#[derive(Debug)]
706struct MetadataProbeBlocker {
707    /// Signals that the filesystem worker reached the metadata boundary.
708    entered: SyncSender<()>,
709    /// Releases the worker so the real metadata lookup can continue.
710    release: std::sync::Mutex<Receiver<()>>,
711}
712
713#[cfg(test)]
714impl MetadataProbeBlocker {
715    /// Pause immediately before the real metadata lookup.
716    fn wait(&self) -> Result<(), ParserSupervisorError> {
717        self.entered
718            .send(())
719            .map_err(|_closed| ParserSupervisorError::IoThread {
720                phase: ARTIFACT_IO_PHASE,
721                message: "metadata-probe entry receiver closed".to_owned(),
722            })?;
723        self.release
724            .lock()
725            .map_err(|_poisoned| ParserSupervisorError::IoThread {
726                phase: ARTIFACT_IO_PHASE,
727                message: "metadata-probe release lock was poisoned".to_owned(),
728            })?
729            .recv()
730            .map_err(|_closed| ParserSupervisorError::IoThread {
731                phase: ARTIFACT_IO_PHASE,
732                message: "metadata-probe release sender closed".to_owned(),
733            })
734    }
735}
736
737impl FileCurrentnessProbe {
738    /// Observe the path and compare it with the verified change epoch.
739    fn is_current(&self) -> Result<bool, ParserSupervisorError> {
740        #[cfg(debug_assertions)]
741        invoke_currentness_test_hook()?;
742        #[cfg(test)]
743        if let Some(blocker) = &self.blocker {
744            blocker.wait()?;
745        }
746        #[cfg(windows)]
747        if !self.guarded {
748            return Ok(false);
749        }
750        let metadata =
751            fs::metadata(&self.path).map_err(|source| ParserSupervisorError::ArtifactRead {
752                path: self.path.clone(),
753                source,
754            })?;
755        Ok(metadata.is_file() && FileChangeEpoch::from_metadata(&metadata) == self.epoch)
756    }
757}
758
759impl FileObservation {
760    /// Capture one regular file before its digest is read and verified.
761    fn capture(path: PathBuf) -> Result<Self, ParserSupervisorError> {
762        let write_guard = open_observed_file(&path)?;
763        let metadata =
764            write_guard
765                .metadata()
766                .map_err(|source| ParserSupervisorError::ArtifactRead {
767                    path: path.clone(),
768                    source,
769                })?;
770        if !metadata.is_file() {
771            return Err(ParserSupervisorError::PayloadMismatch {
772                path,
773                reason: "payload is not a regular file",
774            });
775        }
776        Ok(Self {
777            path,
778            epoch: FileChangeEpoch::from_metadata(&metadata),
779            #[cfg(windows)]
780            write_guard: Some(write_guard),
781        })
782    }
783
784    /// Return whether the guarded path still resolves to the captured file identity.
785    #[cfg(test)]
786    fn is_current(&self) -> Result<bool, ParserSupervisorError> {
787        self.currentness_probe().is_current()
788    }
789
790    /// Copy only the bounded path and metadata needed by the filesystem worker.
791    fn currentness_probe(&self) -> FileCurrentnessProbe {
792        FileCurrentnessProbe {
793            path: self.path.clone(),
794            epoch: self.epoch,
795            #[cfg(windows)]
796            guarded: self.write_guard.is_some(),
797            #[cfg(test)]
798            blocker: None,
799        }
800    }
801
802    /// Build deliberately unavailable file authority for process-free tests.
803    #[cfg(test)]
804    fn unavailable(path: PathBuf) -> Self {
805        Self {
806            path,
807            epoch: FileChangeEpoch::default(),
808            #[cfg(windows)]
809            write_guard: None,
810        }
811    }
812}
813
814/// Metadata used to detect mutation around and after payload digest verification.
815#[derive(Debug)]
816struct PayloadObservation {
817    /// Guarded canonical payload file.
818    file: FileObservation,
819    /// Manifest-owned payload responsibility.
820    role: ParserPackPayloadRole,
821    /// Exact manifest-owned byte count.
822    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
823    bytes: u64,
824    /// Exact manifest-owned SHA-256 digest.
825    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
826    sha256: String,
827}
828
829impl PayloadObservation {
830    /// Return whether this payload can affect one grammar-affined worker launch.
831    fn contributes_to_launch(&self, language_id: &str) -> bool {
832        let shared_launch_input = matches!(
833            &self.role,
834            ParserPackPayloadRole::Worker
835                | ParserPackPayloadRole::ContainmentBroker
836                | ParserPackPayloadRole::AcceptedManifest
837        );
838        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
839        let shared_launch_input =
840            shared_launch_input || matches!(&self.role, ParserPackPayloadRole::NativeImportPolicy);
841        shared_launch_input
842            || matches!(
843                &self.role,
844                ParserPackPayloadRole::GrammarLibrary {
845                    language_id: payload_language
846                } if payload_language == language_id
847            )
848    }
849
850    /// Return whether one payload retains the identity captured before digest verification.
851    #[cfg(test)]
852    fn is_current(&self) -> Result<bool, ParserSupervisorError> {
853        self.file.is_current()
854    }
855
856    /// Retain the immutable manifest row without retaining the source handle.
857    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
858    fn linux_spec(&self) -> VerifiedLinuxPayloadSpec {
859        VerifiedLinuxPayloadSpec {
860            path: self.file.path.clone(),
861            epoch: self.file.epoch,
862            bytes: self.bytes,
863            sha256: self.sha256.clone(),
864        }
865    }
866}
867
868/// Request-owned stop bounds shared by every pre-READY artifact phase.
869struct ArtifactIoControl<'a> {
870    /// Immutable absolute request deadline.
871    absolute_deadline: Instant,
872    /// Fixed pre-READY progress epoch; artifact work does not extend the bound.
873    last_progress: Instant,
874    /// Maximum pre-READY interval without validated parser progress.
875    no_progress_timeout: Duration,
876    /// Request-owned cooperative cancellation signal.
877    cancellation: &'a IndexCancellation,
878}
879
880impl ArtifactIoControl<'_> {
881    /// Reject cancellation or an expired request bound before more reload work.
882    fn poll(&self) -> Result<(), ParserSupervisorError> {
883        poll_stop(
884            ARTIFACT_IO_PHASE,
885            self.absolute_deadline,
886            self.last_progress,
887            self.no_progress_timeout,
888            self.cancellation,
889        )
890    }
891}
892
893/// Process-wide lease that caps potentially blocked artifact readers at one.
894struct ArtifactIoLease;
895
896impl ArtifactIoLease {
897    /// Acquire the only artifact-reader slot.
898    fn acquire() -> Result<Self, ParserSupervisorError> {
899        ARTIFACT_IO_ACTIVE
900            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
901            .map(|_inactive| Self)
902            .map_err(|_active| ParserSupervisorError::IoThread {
903                phase: ARTIFACT_IO_PHASE,
904                message: "another parser-pack artifact reader is still active".to_owned(),
905            })
906    }
907}
908
909impl Drop for ArtifactIoLease {
910    fn drop(&mut self) {
911        ARTIFACT_IO_ACTIVE.store(false, Ordering::Release);
912    }
913}
914
915/// One metadata-probe request owned by the process-wide filesystem worker.
916struct ArtifactCurrentnessRequest {
917    /// Exact constant-size path observations for this parse request.
918    probe: ArtifactCurrentnessProbe,
919    /// Immutable absolute request deadline.
920    absolute_deadline: Instant,
921    /// Caller-owned pre-READY progress epoch.
922    last_progress: Instant,
923    /// Maximum metadata-probe duration.
924    no_progress_timeout: Duration,
925    /// Request-owned cooperative cancellation signal.
926    cancellation: IndexCancellation,
927    /// One-shot response channel; a canceled caller may close it before completion.
928    response: SyncSender<Result<bool, ParserSupervisorError>>,
929    /// Process-wide admission retained even when a filesystem call remains blocked.
930    lease: ArtifactIoLease,
931}
932
933/// Start the single lazy process-wide metadata worker.
934fn artifact_currentness_sender()
935-> Result<&'static SyncSender<ArtifactCurrentnessRequest>, ParserSupervisorError> {
936    static WORKER: OnceLock<Result<SyncSender<ArtifactCurrentnessRequest>, String>> =
937        OnceLock::new();
938
939    match WORKER.get_or_init(|| {
940        let (sender, receiver) = mpsc::sync_channel::<ArtifactCurrentnessRequest>(1);
941        thread::Builder::new()
942            .name("projectatlas-artifact-currentness".to_owned())
943            .spawn(move || {
944                while let Ok(request) = receiver.recv() {
945                    let ArtifactCurrentnessRequest {
946                        probe,
947                        absolute_deadline,
948                        last_progress,
949                        no_progress_timeout,
950                        cancellation,
951                        response,
952                        lease,
953                    } = request;
954                    let control = ArtifactIoControl {
955                        absolute_deadline,
956                        last_progress,
957                        no_progress_timeout,
958                        cancellation: &cancellation,
959                    };
960                    let result = probe.is_current(Some(&control));
961                    drop(lease);
962                    let _send_result = response.try_send(result);
963                }
964            })
965            .map(|worker| {
966                drop(worker);
967                sender
968            })
969            .map_err(|source| bounded_message(source.to_string()))
970    }) {
971        Ok(sender) => Ok(sender),
972        Err(message) => Err(ParserSupervisorError::IoThread {
973            phase: ARTIFACT_IO_PHASE,
974            message: message.clone(),
975        }),
976    }
977}
978
979/// Run one hot-path metadata probe without exposing blocking filesystem calls to the caller.
980fn run_bounded_artifact_currentness(
981    probe: ArtifactCurrentnessProbe,
982    control: &ArtifactIoControl<'_>,
983) -> Result<bool, ParserSupervisorError> {
984    control.poll()?;
985    let sender = artifact_currentness_sender()?;
986    let lease = ArtifactIoLease::acquire()?;
987    let (response, receiver) = mpsc::sync_channel(1);
988    let request = ArtifactCurrentnessRequest {
989        probe,
990        absolute_deadline: control.absolute_deadline,
991        last_progress: control.last_progress,
992        no_progress_timeout: control.no_progress_timeout,
993        cancellation: control.cancellation.clone(),
994        response,
995        lease,
996    };
997    match sender.try_send(request) {
998        Ok(()) => {}
999        Err(TrySendError::Full(_request)) => {
1000            return Err(ParserSupervisorError::IoThread {
1001                phase: ARTIFACT_IO_PHASE,
1002                message: "parser-pack currentness worker is still active".to_owned(),
1003            });
1004        }
1005        Err(TrySendError::Disconnected(_request)) => {
1006            return Err(ParserSupervisorError::IoThread {
1007                phase: ARTIFACT_IO_PHASE,
1008                message: "parser-pack currentness worker disconnected".to_owned(),
1009            });
1010        }
1011    }
1012
1013    loop {
1014        control.poll()?;
1015        match receiver.recv_timeout(next_poll_wait(
1016            control.absolute_deadline,
1017            control.last_progress,
1018            control.no_progress_timeout,
1019        )) {
1020            Ok(result) => {
1021                control.poll()?;
1022                return result;
1023            }
1024            Err(RecvTimeoutError::Timeout) => {}
1025            Err(RecvTimeoutError::Disconnected) => {
1026                return Err(ParserSupervisorError::IoThread {
1027                    phase: ARTIFACT_IO_PHASE,
1028                    message: "parser-pack currentness response disconnected".to_owned(),
1029                });
1030            }
1031        }
1032    }
1033}
1034
1035/// Run potentially blocking artifact I/O behind a request-bounded worker.
1036fn run_bounded_artifact_io<T>(
1037    operation: impl FnOnce() -> Result<T, ParserSupervisorError> + Send + 'static,
1038    control: &ArtifactIoControl<'_>,
1039) -> Result<T, ParserSupervisorError>
1040where
1041    T: Send + 'static,
1042{
1043    control.poll()?;
1044    let lease = ArtifactIoLease::acquire()?;
1045    let (sender, receiver) = mpsc::sync_channel(1);
1046    let worker = thread::Builder::new()
1047        .name("projectatlas-artifact-authority".to_owned())
1048        .spawn(move || {
1049            let result = operation();
1050            drop(lease);
1051            let _send_result = sender.send(result);
1052        })
1053        .map_err(|source| ParserSupervisorError::IoThread {
1054            phase: ARTIFACT_IO_PHASE,
1055            message: bounded_message(source.to_string()),
1056        })?;
1057    drop(worker);
1058
1059    loop {
1060        control.poll()?;
1061        match receiver.recv_timeout(next_poll_wait(
1062            control.absolute_deadline,
1063            control.last_progress,
1064            control.no_progress_timeout,
1065        )) {
1066            Ok(result) => {
1067                control.poll()?;
1068                return result;
1069            }
1070            Err(RecvTimeoutError::Timeout) => {}
1071            Err(RecvTimeoutError::Disconnected) => {
1072                return Err(ParserSupervisorError::IoThread {
1073                    phase: ARTIFACT_IO_PHASE,
1074                    message: "parser-pack artifact reader disconnected".to_owned(),
1075                });
1076            }
1077        }
1078    }
1079}
1080
1081/// Process-wide lease retained until a blocked spawn returns and any late child is reaped.
1082struct ProcessSpawnLease;
1083
1084impl ProcessSpawnLease {
1085    /// Acquire the only potentially blocked process-creation slot.
1086    fn acquire() -> Result<Self, ParserSupervisorError> {
1087        PROCESS_SPAWN_ACTIVE
1088            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1089            .map(|_inactive| Self)
1090            .map_err(|_active| ParserSupervisorError::IoThread {
1091                phase: PROCESS_LAUNCH_PHASE,
1092                message: "another optional-parser process creation is still active".to_owned(),
1093            })
1094    }
1095}
1096
1097impl Drop for ProcessSpawnLease {
1098    fn drop(&mut self) {
1099        PROCESS_SPAWN_ACTIVE.store(false, Ordering::Release);
1100    }
1101}
1102
1103/// Child that must be reaped unless the caller explicitly accepts ownership.
1104struct UnadmittedChild {
1105    /// Direct worker or broker child.
1106    child: Option<Child>,
1107    /// Process-creation slot retained until admission or mandatory cleanup.
1108    _lease: ProcessSpawnLease,
1109}
1110
1111impl UnadmittedChild {
1112    /// Retain cleanup ownership across a bounded caller handoff.
1113    const fn new(child: Child, lease: ProcessSpawnLease) -> Self {
1114        Self {
1115            child: Some(child),
1116            _lease: lease,
1117        }
1118    }
1119
1120    /// Transfer the child to the normal resident-session owner.
1121    fn admit(mut self) -> Result<Child, ParserSupervisorError> {
1122        self.child
1123            .take()
1124            .ok_or_else(|| ParserSupervisorError::IoThread {
1125                phase: PROCESS_LAUNCH_PHASE,
1126                message: "process-spawn worker returned no child".to_owned(),
1127            })
1128    }
1129}
1130
1131impl Drop for UnadmittedChild {
1132    fn drop(&mut self) {
1133        let Some(mut child) = self.child.take() else {
1134            return;
1135        };
1136        #[cfg(test)]
1137        if let Ok(mut slot) = PROCESS_SPAWN_BEFORE_CLEANUP_TEST_HOOK.lock()
1138            && let Some(hook) = slot.take()
1139        {
1140            hook();
1141        }
1142        if let Err(error) = cleanup_partial_launch(&mut child, Vec::new(), None, None, None) {
1143            record_process_spawn_cleanup_failure(&error);
1144        }
1145    }
1146}
1147
1148/// Preserve the first late cleanup failure for every later launch attempt.
1149fn record_process_spawn_cleanup_failure(error: &ParserSupervisorError) {
1150    if let Ok(mut slot) = PROCESS_SPAWN_CLEANUP_FAILURE.lock()
1151        && slot.is_none()
1152    {
1153        *slot = Some(bounded_message(format!(
1154            "late optional-parser process cleanup failed: {error}"
1155        )));
1156    }
1157}
1158
1159/// Reject new launches after a late cleanup failure has made process ownership uncertain.
1160fn require_process_spawn_cleanup_health() -> Result<(), ParserSupervisorError> {
1161    let slot = PROCESS_SPAWN_CLEANUP_FAILURE.lock().map_err(|_poisoned| {
1162        ParserSupervisorError::IoThread {
1163            phase: PROCESS_LAUNCH_PHASE,
1164            message: "process-spawn cleanup state is poisoned".to_owned(),
1165        }
1166    })?;
1167    if let Some(message) = slot.as_ref() {
1168        return Err(ParserSupervisorError::Cleanup {
1169            message: message.clone(),
1170        });
1171    }
1172    Ok(())
1173}
1174
1175/// Run one potentially blocking `Command::spawn` without retaining the bounded caller.
1176fn run_bounded_process_spawn(
1177    command: Command,
1178    absolute_deadline: Instant,
1179    last_progress: Instant,
1180    no_progress_timeout: Duration,
1181    cancellation: &IndexCancellation,
1182) -> Result<Child, ParserSupervisorError> {
1183    run_bounded_process_spawn_with(
1184        command,
1185        absolute_deadline,
1186        last_progress,
1187        no_progress_timeout,
1188        cancellation,
1189        |mut command| command.spawn(),
1190    )
1191}
1192
1193/// Execute the concrete spawn operation behind one owner-side admission handshake.
1194fn run_bounded_process_spawn_with(
1195    command: Command,
1196    absolute_deadline: Instant,
1197    last_progress: Instant,
1198    no_progress_timeout: Duration,
1199    cancellation: &IndexCancellation,
1200    spawn: impl FnOnce(Command) -> io::Result<Child> + Send + 'static,
1201) -> Result<Child, ParserSupervisorError> {
1202    poll_stop(
1203        PROCESS_LAUNCH_PHASE,
1204        absolute_deadline,
1205        last_progress,
1206        no_progress_timeout,
1207        cancellation,
1208    )?;
1209    require_process_spawn_cleanup_health()?;
1210    let lease = ProcessSpawnLease::acquire()?;
1211    let program = PathBuf::from(command.get_program());
1212    let (ready_sender, ready_receiver) = mpsc::sync_channel(1);
1213    let (rendezvous_sender, rendezvous_receiver) = mpsc::sync_channel(0);
1214    let (handoff_commit_sender, handoff_commit_receiver) = mpsc::sync_channel(1);
1215    let (child_sender, child_receiver) = mpsc::sync_channel(0);
1216    let worker = thread::Builder::new()
1217        .name("projectatlas-process-spawn".to_owned())
1218        .spawn(move || {
1219            let child = match spawn(command) {
1220                Ok(child) => UnadmittedChild::new(child, lease),
1221                Err(source) => {
1222                    let _undelivered =
1223                        ready_sender.send(Err(ParserSupervisorError::Spawn { program, source }));
1224                    return;
1225                }
1226            };
1227            if ready_sender.send(Ok(())).is_err() {
1228                return;
1229            }
1230            if rendezvous_sender.send(()).is_err() {
1231                return;
1232            }
1233            if handoff_commit_receiver.recv().is_err() {
1234                return;
1235            }
1236            if let Err(undelivered) = child_sender.send(child) {
1237                drop(undelivered);
1238            }
1239        })
1240        .map_err(|source| ParserSupervisorError::IoThread {
1241            phase: PROCESS_LAUNCH_PHASE,
1242            message: bounded_message(source.to_string()),
1243        })?;
1244    drop(worker);
1245
1246    loop {
1247        poll_stop(
1248            PROCESS_LAUNCH_PHASE,
1249            absolute_deadline,
1250            last_progress,
1251            no_progress_timeout,
1252            cancellation,
1253        )?;
1254        match ready_receiver.recv_timeout(next_poll_wait(
1255            absolute_deadline,
1256            last_progress,
1257            no_progress_timeout,
1258        )) {
1259            Ok(ready) => {
1260                ready?;
1261                poll_stop(
1262                    PROCESS_LAUNCH_PHASE,
1263                    absolute_deadline,
1264                    last_progress,
1265                    no_progress_timeout,
1266                    cancellation,
1267                )?;
1268                loop {
1269                    poll_stop(
1270                        PROCESS_LAUNCH_PHASE,
1271                        absolute_deadline,
1272                        last_progress,
1273                        no_progress_timeout,
1274                        cancellation,
1275                    )?;
1276                    match rendezvous_receiver.recv_timeout(next_poll_wait(
1277                        absolute_deadline,
1278                        last_progress,
1279                        no_progress_timeout,
1280                    )) {
1281                        Ok(()) => {
1282                            #[cfg(test)]
1283                            if let Ok(mut slot) = PROCESS_SPAWN_AFTER_RENDEZVOUS_TEST_HOOK.lock()
1284                                && let Some(hook) = slot.take()
1285                            {
1286                                hook();
1287                            }
1288                            poll_stop(
1289                                PROCESS_LAUNCH_PHASE,
1290                                absolute_deadline,
1291                                last_progress,
1292                                no_progress_timeout,
1293                                cancellation,
1294                            )?;
1295                            // The successful final check commits ownership. This bounded
1296                            // acknowledgement only notifies the owner; later stops belong
1297                            // to the normal resident-session owner.
1298                            #[cfg(test)]
1299                            if let Ok(mut slot) = PROCESS_SPAWN_AFTER_FINAL_CHECK_TEST_HOOK.lock()
1300                                && let Some(hook) = slot.take()
1301                            {
1302                                hook();
1303                            }
1304                            handoff_commit_sender.send(()).map_err(|_closed| {
1305                                ParserSupervisorError::IoThread {
1306                                    phase: PROCESS_LAUNCH_PHASE,
1307                                    message:
1308                                        "process-spawn owner disconnected before handoff commit"
1309                                            .to_owned(),
1310                                }
1311                            })?;
1312                            return child_receiver
1313                                .recv()
1314                                .map_err(|_closed| ParserSupervisorError::IoThread {
1315                                    phase: PROCESS_LAUNCH_PHASE,
1316                                    message:
1317                                        "process-spawn owner disconnected during committed handoff"
1318                                            .to_owned(),
1319                                })?
1320                                .admit();
1321                        }
1322                        Err(RecvTimeoutError::Timeout) => {}
1323                        Err(RecvTimeoutError::Disconnected) => {
1324                            return Err(ParserSupervisorError::IoThread {
1325                                phase: PROCESS_LAUNCH_PHASE,
1326                                message: "process-spawn owner disconnected during rendezvous"
1327                                    .to_owned(),
1328                            });
1329                        }
1330                    }
1331                }
1332            }
1333            Err(RecvTimeoutError::Timeout) => {}
1334            Err(RecvTimeoutError::Disconnected) => {
1335                return Err(ParserSupervisorError::IoThread {
1336                    phase: PROCESS_LAUNCH_PHASE,
1337                    message: "process-spawn worker disconnected".to_owned(),
1338                });
1339            }
1340        }
1341    }
1342}
1343
1344/// Bytes and digest produced together by one bounded artifact-file pass.
1345struct BoundedArtifactRead {
1346    /// Exact bounded file bytes.
1347    bytes: Vec<u8>,
1348    /// Lowercase SHA-256 computed during the bounded read.
1349    sha256: String,
1350}
1351
1352/// Manifest-owned identity needed to re-read one launch payload exactly.
1353#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1354#[derive(Clone, Debug)]
1355struct VerifiedLinuxPayloadSpec {
1356    /// Canonical path already constrained to the parser-pack root.
1357    path: PathBuf,
1358    /// File identity captured before the artifact digest was accepted.
1359    epoch: FileChangeEpoch,
1360    /// Exact declared byte count.
1361    bytes: u64,
1362    /// Exact lowercase SHA-256 digest.
1363    sha256: String,
1364}
1365
1366/// Read-only, fully sealed Linux launch payload.
1367#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1368#[derive(Debug)]
1369struct SealedLinuxPayload {
1370    /// Read-only descriptor for the sealed memfd inode.
1371    file: File,
1372}
1373
1374/// Create a modern memfd and retry only the unsupported-flag legacy-kernel case.
1375#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1376fn create_memfd_with_legacy_fallback<T>(
1377    flags: nix::sys::memfd::MFdFlags,
1378    mode_flag: nix::libc::c_uint,
1379    mut create: impl FnMut(nix::sys::memfd::MFdFlags) -> Result<T, nix::errno::Errno>,
1380) -> Result<T, nix::errno::Errno> {
1381    let requested_flags = flags | nix::sys::memfd::MFdFlags::from_bits_retain(mode_flag);
1382    match create(requested_flags) {
1383        Err(nix::errno::Errno::EINVAL) => create(flags),
1384        result => result,
1385    }
1386}
1387
1388#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1389impl SealedLinuxPayload {
1390    /// Copy already verified bytes into one immutable anonymous file.
1391    fn from_verified_bytes(
1392        role: &'static str,
1393        name: &str,
1394        bytes: &[u8],
1395        executable: bool,
1396        control: &ArtifactIoControl<'_>,
1397    ) -> Result<Self, ParserSupervisorError> {
1398        use nix::sys::memfd::memfd_create;
1399
1400        Self::from_verified_bytes_with_create(role, name, bytes, executable, control, |flags| {
1401            memfd_create(name, flags)
1402        })
1403    }
1404
1405    /// Copy verified bytes through an injected memfd creator for fallback-path proof.
1406    fn from_verified_bytes_with_create(
1407        role: &'static str,
1408        name: &str,
1409        bytes: &[u8],
1410        executable: bool,
1411        control: &ArtifactIoControl<'_>,
1412        create: impl FnMut(nix::sys::memfd::MFdFlags) -> Result<std::os::fd::OwnedFd, nix::errno::Errno>,
1413    ) -> Result<Self, ParserSupervisorError> {
1414        use nix::fcntl::{FcntlArg, SealFlag, fcntl};
1415        use nix::libc;
1416        use nix::sys::memfd::MFdFlags;
1417        use nix::sys::stat::{Mode, fchmod};
1418
1419        let authority_error =
1420            |source: nix::errno::Errno| ParserSupervisorError::LinuxLaunchAuthority {
1421                role,
1422                source: io::Error::from_raw_os_error(source as i32),
1423            };
1424        let flags = MFdFlags::MFD_CLOEXEC | MFdFlags::MFD_ALLOW_SEALING;
1425        let mode_flag = if executable {
1426            libc::MFD_EXEC
1427        } else {
1428            libc::MFD_NOEXEC_SEAL
1429        };
1430        let descriptor =
1431            create_memfd_with_legacy_fallback(flags, mode_flag, create).map_err(authority_error)?;
1432        let mut file = File::from(descriptor);
1433        let mode = if executable {
1434            Mode::S_IRUSR | Mode::S_IXUSR
1435        } else {
1436            Mode::S_IRUSR
1437        };
1438        fchmod(&file, mode).map_err(authority_error)?;
1439        for chunk in bytes.chunks(ARTIFACT_READ_CHUNK_BYTES) {
1440            control.poll()?;
1441            file.write_all(chunk)
1442                .map_err(|source| ParserSupervisorError::LinuxLaunchAuthority { role, source })?;
1443        }
1444        file.rewind()
1445            .map_err(|source| ParserSupervisorError::LinuxLaunchAuthority { role, source })?;
1446        let required = SealFlag::F_SEAL_WRITE
1447            | SealFlag::F_SEAL_GROW
1448            | SealFlag::F_SEAL_SHRINK
1449            | SealFlag::F_SEAL_SEAL;
1450        fcntl(&file, FcntlArg::F_ADD_SEALS(required)).map_err(authority_error)?;
1451        let observed = fcntl(&file, FcntlArg::F_GET_SEALS).map_err(authority_error)?;
1452        if observed & required.bits() != required.bits() {
1453            return Err(ParserSupervisorError::PayloadMismatch {
1454                path: PathBuf::from(name),
1455                reason: "Linux launch authority does not carry the complete seal set",
1456            });
1457        }
1458
1459        let read_only_path = PathBuf::from(format!("/proc/self/fd/{}", file.as_raw_fd()));
1460        let read_only = File::open(&read_only_path)
1461            .map_err(|source| ParserSupervisorError::LinuxLaunchAuthority { role, source })?;
1462        drop(file);
1463        Ok(Self { file: read_only })
1464    }
1465
1466    /// Return the process-local descriptor identity retained through spawn.
1467    fn raw_fd(&self) -> i32 {
1468        self.file.as_raw_fd()
1469    }
1470}
1471
1472/// Exact immutable authority consumed by one Linux resident launch.
1473#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1474#[derive(Debug)]
1475struct LinuxResidentLaunchAuthority {
1476    /// Executable parser worker.
1477    worker: SealedLinuxPayload,
1478    /// Exact artifact-manifest bytes.
1479    artifact_manifest: SealedLinuxPayload,
1480    /// Exact accepted-capability manifest bytes.
1481    accepted_manifest: SealedLinuxPayload,
1482    /// Exact native-import policy bytes.
1483    native_import_policy: SealedLinuxPayload,
1484    /// One grammar selected for this resident.
1485    grammar: SealedLinuxPayload,
1486}
1487
1488/// Complete private launch authority derived from one exact immutable artifact.
1489#[derive(Debug)]
1490struct VerifiedParserPackLaunch {
1491    /// Canonical artifact root.
1492    pack_root: PathBuf,
1493    /// Accepted target bound by the artifact manifest.
1494    platform: PackPlatform,
1495    /// Exact containment broker launched on Windows.
1496    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
1497    containment_broker: Option<PathBuf>,
1498    /// Sorted accepted language identities.
1499    accepted_grammars: Vec<String>,
1500    /// Exact artifact-manifest byte identity independently observed by Rust.
1501    artifact: ParserArtifactIdentity,
1502    /// Exact already verified artifact-manifest bytes retained for Linux handoff.
1503    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1504    artifact_manifest_bytes: Vec<u8>,
1505    /// Exact already verified accepted-capability bytes retained for Linux handoff.
1506    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1507    accepted_manifest_bytes: Vec<u8>,
1508    /// Exact already verified native-import policy bytes retained for Linux handoff.
1509    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1510    native_import_policy_bytes: Vec<u8>,
1511    /// Guarded artifact manifest captured before verification and rechecked afterward.
1512    artifact_manifest: FileObservation,
1513    /// Cheap metadata observations for every already hashed payload.
1514    payloads: Vec<PayloadObservation>,
1515    /// Test-only pause at the real currentness metadata boundary.
1516    #[cfg(test)]
1517    currentness_blocker: Option<std::sync::Arc<MetadataProbeBlocker>>,
1518}
1519
1520/// One complete owned change-epoch probe for a grammar-affined parse request.
1521struct ArtifactCurrentnessProbe {
1522    /// Artifact manifest and every payload that can affect the requested launch.
1523    files: Vec<FileCurrentnessProbe>,
1524}
1525
1526/// Number of path identities that can affect one grammar-affined launch:
1527/// artifact manifest, worker, platform authority (broker or native policy),
1528/// accepted manifest, and selected grammar.
1529const MAX_CURRENTNESS_PROBE_FILES: usize = 5;
1530
1531impl ArtifactCurrentnessProbe {
1532    /// Require every path to retain its verified constant-size identity.
1533    fn is_current(
1534        &self,
1535        control: Option<&ArtifactIoControl<'_>>,
1536    ) -> Result<bool, ParserSupervisorError> {
1537        for file in &self.files {
1538            if let Some(control) = control {
1539                control.poll()?;
1540            }
1541            if !file.is_current()? {
1542                return Ok(false);
1543            }
1544        }
1545        Ok(true)
1546    }
1547}
1548
1549impl VerifiedParserPackLaunch {
1550    /// Validate and canonicalize one exact artifact before process creation.
1551    fn load(pack_root: &Path) -> Result<Self, ParserSupervisorError> {
1552        Self::load_inner(pack_root, None)
1553    }
1554
1555    /// Reload a changed artifact while honoring the active parse request bounds.
1556    fn load_controlled(
1557        pack_root: &Path,
1558        language_id: &str,
1559        last_progress: Instant,
1560        absolute_deadline: Instant,
1561        no_progress_timeout: Duration,
1562        cancellation: &IndexCancellation,
1563    ) -> Result<Self, ParserSupervisorError> {
1564        let control = ArtifactIoControl {
1565            absolute_deadline,
1566            last_progress,
1567            no_progress_timeout,
1568            cancellation,
1569        };
1570        let pack_root = pack_root.to_path_buf();
1571        let language_id = language_id.to_owned();
1572        let worker_cancellation = cancellation.clone();
1573        run_bounded_artifact_io(
1574            move || {
1575                let worker_control = ArtifactIoControl {
1576                    absolute_deadline,
1577                    last_progress,
1578                    no_progress_timeout,
1579                    cancellation: &worker_cancellation,
1580                };
1581                let refreshed = Self::load_inner(&pack_root, Some(&worker_control))?;
1582                if !refreshed
1583                    .currentness_probe(&language_id)
1584                    .is_current(Some(&worker_control))?
1585                {
1586                    return Err(ParserSupervisorError::PayloadMismatch {
1587                        path: pack_root,
1588                        reason: "artifact changed during digest revalidation",
1589                    });
1590                }
1591                Ok(refreshed)
1592            },
1593            &control,
1594        )
1595    }
1596
1597    /// Validate one artifact with optional worker-side request bounds.
1598    fn load_inner(
1599        pack_root: &Path,
1600        control: Option<&ArtifactIoControl<'_>>,
1601    ) -> Result<Self, ParserSupervisorError> {
1602        if let Some(control) = control {
1603            control.poll()?;
1604        }
1605        let platform =
1606            host_pack_platform().ok_or(ParserSupervisorError::UnsupportedContainment {
1607                os: std::env::consts::OS,
1608                architecture: std::env::consts::ARCH,
1609            })?;
1610        let pack_root = canonical_directory(pack_root)?;
1611        let accepted_path = canonical_direct_file(&pack_root, ACCEPTED_MANIFEST_FILE_NAME)?;
1612        let artifact_path = canonical_direct_file(&pack_root, ARTIFACT_MANIFEST_FILE_NAME)?;
1613        let accepted_manifest_file = FileObservation::capture(accepted_path.clone())?;
1614        let artifact_manifest_file = FileObservation::capture(artifact_path.clone())?;
1615        let accepted_read = read_bounded_file(
1616            &accepted_path,
1617            accepted_manifest_file.epoch,
1618            u64::try_from(OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES).unwrap_or(u64::MAX),
1619            control,
1620        )?;
1621        let artifact_read = read_bounded_file(
1622            &artifact_path,
1623            artifact_manifest_file.epoch,
1624            u64::try_from(OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES).unwrap_or(u64::MAX),
1625            control,
1626        )?;
1627        let mut accepted_manifest = Some(accepted_manifest_file);
1628        let logical = OptionalParserPackManifest::from_json(&accepted_read.bytes)?;
1629        let artifact_manifest: OptionalParserPackArtifactManifest =
1630            serde_json::from_slice(&artifact_read.bytes)
1631                .map_err(|source| ParserSupervisorError::ArtifactManifestJson { source })?;
1632        artifact_manifest.validate(&logical)?;
1633        if artifact_manifest.platform != platform {
1634            return Err(ParserSupervisorError::PayloadMismatch {
1635                path: artifact_path,
1636                reason: "artifact target does not match the current host",
1637            });
1638        }
1639
1640        let mut worker = None;
1641        let mut containment_broker = None;
1642        let mut accepted_payload_sha256 = None;
1643        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1644        let mut native_import_policy_bytes = None;
1645        let mut payloads = Vec::with_capacity(artifact_manifest.files.len());
1646        for payload in &artifact_manifest.files {
1647            let path = canonical_payload_file(&pack_root, payload.path.as_str())?;
1648            let file = if matches!(payload.role, ParserPackPayloadRole::AcceptedManifest) {
1649                if path != accepted_path {
1650                    return Err(ParserSupervisorError::PayloadMismatch {
1651                        path,
1652                        reason: "accepted capability manifest is not at its defined artifact path",
1653                    });
1654                }
1655                accepted_manifest
1656                    .take()
1657                    .ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1658                        path: path.clone(),
1659                        reason: "artifact contains more than one accepted capability manifest",
1660                    })?
1661            } else {
1662                FileObservation::capture(path.clone())?
1663            };
1664            let payload_read = read_bounded_file(&path, file.epoch, payload.bytes, control)?;
1665            if u64::try_from(payload_read.bytes.len()).ok() != Some(payload.bytes) {
1666                return Err(ParserSupervisorError::PayloadMismatch {
1667                    path,
1668                    reason: "payload byte count differs from the artifact manifest",
1669                });
1670            }
1671            if payload_read.sha256 != payload.sha256.as_str() {
1672                return Err(ParserSupervisorError::PayloadMismatch {
1673                    path,
1674                    reason: "payload SHA-256 differs from the artifact manifest",
1675                });
1676            }
1677            payloads.push(PayloadObservation {
1678                file,
1679                role: payload.role.clone(),
1680                #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1681                bytes: payload.bytes,
1682                #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1683                sha256: payload.sha256.as_str().to_owned(),
1684            });
1685            match &payload.role {
1686                ParserPackPayloadRole::Worker => worker = Some(path),
1687                ParserPackPayloadRole::ContainmentBroker => containment_broker = Some(path),
1688                ParserPackPayloadRole::AcceptedManifest => {
1689                    accepted_payload_sha256 = Some(payload.sha256.as_str().to_owned());
1690                }
1691                #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1692                ParserPackPayloadRole::NativeImportPolicy => {
1693                    native_import_policy_bytes = Some(payload_read.bytes.clone());
1694                }
1695                ParserPackPayloadRole::FixtureCorpus
1696                | ParserPackPayloadRole::ProjectLicense
1697                | ParserPackPayloadRole::NativeAuditReport
1698                | ParserPackPayloadRole::GrammarLibrary { .. } => {}
1699                #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
1700                ParserPackPayloadRole::NativeImportPolicy => {}
1701            }
1702        }
1703
1704        let worker = worker.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1705            path: pack_root.join(platform.worker_file_name()),
1706            reason: "artifact does not contain its exact worker payload",
1707        })?;
1708        #[cfg(unix)]
1709        require_executable(&worker)?;
1710        let expected_worker = canonical_direct_file(&pack_root, platform.worker_file_name())?;
1711        if worker != expected_worker {
1712            return Err(ParserSupervisorError::PayloadMismatch {
1713                path: worker,
1714                reason: "worker is not at its platform-defined artifact path",
1715            });
1716        }
1717        let containment_broker = match platform.containment_broker_file_name() {
1718            Some(file_name) => {
1719                let broker =
1720                    containment_broker.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1721                        path: pack_root.join(file_name),
1722                        reason: "artifact does not contain its required containment broker",
1723                    })?;
1724                #[cfg(unix)]
1725                require_executable(&broker)?;
1726                let expected = canonical_direct_file(&pack_root, file_name)?;
1727                if broker != expected {
1728                    return Err(ParserSupervisorError::PayloadMismatch {
1729                        path: broker,
1730                        reason: "containment broker is not at its platform-defined artifact path",
1731                    });
1732                }
1733                Some(expected)
1734            }
1735            None if containment_broker.is_none() => None,
1736            None => {
1737                return Err(ParserSupervisorError::PayloadMismatch {
1738                    path: pack_root,
1739                    reason: "artifact contains an unsupported containment broker",
1740                });
1741            }
1742        };
1743        let accepted_manifest_sha256 =
1744            accepted_payload_sha256.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1745                path: accepted_path.clone(),
1746                reason: "artifact does not contain its accepted capability manifest",
1747            })?;
1748        if accepted_read.sha256 != accepted_manifest_sha256 {
1749            return Err(ParserSupervisorError::PayloadMismatch {
1750                path: accepted_path,
1751                reason: "accepted capability manifest does not match its artifact payload row",
1752            });
1753        }
1754        #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
1755        let _ = containment_broker;
1756        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1757        let native_import_policy_bytes =
1758            native_import_policy_bytes.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1759                path: pack_root.clone(),
1760                reason: "Linux artifact does not contain its native-import policy",
1761            })?;
1762
1763        Ok(Self {
1764            pack_root,
1765            platform,
1766            #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
1767            containment_broker,
1768            accepted_grammars: logical
1769                .grammars()
1770                .iter()
1771                .map(|grammar| grammar.language_id.clone())
1772                .collect(),
1773            artifact: ParserArtifactIdentity::for_bytes(&artifact_read.bytes),
1774            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1775            artifact_manifest_bytes: artifact_read.bytes,
1776            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1777            accepted_manifest_bytes: accepted_read.bytes,
1778            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1779            native_import_policy_bytes,
1780            artifact_manifest: artifact_manifest_file,
1781            payloads,
1782            #[cfg(test)]
1783            currentness_blocker: None,
1784        })
1785    }
1786
1787    /// Copy the constant-size identities needed for one bounded currentness probe.
1788    fn currentness_probe(&self, language_id: &str) -> ArtifactCurrentnessProbe {
1789        let mut files = Vec::with_capacity(MAX_CURRENTNESS_PROBE_FILES);
1790        files.push(self.artifact_manifest.currentness_probe());
1791        files.extend(
1792            self.payloads
1793                .iter()
1794                .filter(|payload| payload.contributes_to_launch(language_id))
1795                .map(|payload| payload.file.currentness_probe()),
1796        );
1797        #[cfg(test)]
1798        if let (Some(file), Some(blocker)) = (files.first_mut(), &self.currentness_blocker) {
1799            file.blocker = Some(std::sync::Arc::clone(blocker));
1800        }
1801        ArtifactCurrentnessProbe { files }
1802    }
1803
1804    /// Validate one requested grammar against the exact accepted manifest.
1805    fn require_grammar(
1806        &self,
1807        language_id: &str,
1808    ) -> Result<ParserLanguageIdentity, ParserSupervisorError> {
1809        let language = ParserLanguageIdentity::new(language_id)?;
1810        if self
1811            .accepted_grammars
1812            .binary_search_by(|candidate| candidate.as_str().cmp(language.as_str()))
1813            .is_err()
1814        {
1815            return Err(ParserSupervisorError::GrammarNotAccepted {
1816                language_id: language_id.to_owned(),
1817            });
1818        }
1819        Ok(language)
1820    }
1821
1822    /// Build immutable authority for one Linux grammar-affined resident.
1823    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1824    fn prepare_resident_launch_controlled(
1825        &self,
1826        language_id: &str,
1827        last_progress: Instant,
1828        absolute_deadline: Instant,
1829        no_progress_timeout: Duration,
1830        cancellation: &IndexCancellation,
1831    ) -> Result<LinuxResidentLaunchAuthority, ParserSupervisorError> {
1832        let mut workers = self
1833            .payloads
1834            .iter()
1835            .filter(|payload| matches!(payload.role, ParserPackPayloadRole::Worker));
1836        let worker = workers.next().map(PayloadObservation::linux_spec);
1837        if worker.is_none() || workers.next().is_some() {
1838            return Err(ParserSupervisorError::PayloadMismatch {
1839                path: self.pack_root.clone(),
1840                reason: "artifact must bind exactly one Linux worker payload",
1841            });
1842        }
1843        let mut grammars = self.payloads.iter().filter(|payload| {
1844            matches!(
1845                &payload.role,
1846                ParserPackPayloadRole::GrammarLibrary {
1847                    language_id: payload_language
1848                } if payload_language == language_id
1849            )
1850        });
1851        let grammar = grammars.next().map(PayloadObservation::linux_spec);
1852        if grammar.is_none() || grammars.next().is_some() {
1853            return Err(ParserSupervisorError::PayloadMismatch {
1854                path: self.pack_root.clone(),
1855                reason: "artifact must bind exactly one selected grammar payload",
1856            });
1857        }
1858
1859        let worker = worker.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1860            path: self.pack_root.clone(),
1861            reason: "artifact has no Linux worker payload",
1862        })?;
1863        let grammar = grammar.ok_or_else(|| ParserSupervisorError::PayloadMismatch {
1864            path: self.pack_root.clone(),
1865            reason: "artifact has no selected grammar payload",
1866        })?;
1867        let artifact_manifest = self.artifact_manifest_bytes.clone();
1868        let accepted_manifest = self.accepted_manifest_bytes.clone();
1869        let native_import_policy = self.native_import_policy_bytes.clone();
1870        let worker_cancellation = cancellation.clone();
1871        let control = ArtifactIoControl {
1872            absolute_deadline,
1873            last_progress,
1874            no_progress_timeout,
1875            cancellation,
1876        };
1877        run_bounded_artifact_io(
1878            move || {
1879                let worker_control = ArtifactIoControl {
1880                    absolute_deadline,
1881                    last_progress,
1882                    no_progress_timeout,
1883                    cancellation: &worker_cancellation,
1884                };
1885                let worker_bytes = read_verified_linux_payload(&worker, &worker_control)?;
1886                let grammar_bytes = read_verified_linux_payload(&grammar, &worker_control)?;
1887                Ok(LinuxResidentLaunchAuthority {
1888                    worker: SealedLinuxPayload::from_verified_bytes(
1889                        "worker",
1890                        "projectatlas-parser-worker",
1891                        &worker_bytes,
1892                        true,
1893                        &worker_control,
1894                    )?,
1895                    artifact_manifest: SealedLinuxPayload::from_verified_bytes(
1896                        "artifact manifest",
1897                        "projectatlas-artifact-manifest",
1898                        &artifact_manifest,
1899                        false,
1900                        &worker_control,
1901                    )?,
1902                    accepted_manifest: SealedLinuxPayload::from_verified_bytes(
1903                        "accepted capability manifest",
1904                        "projectatlas-accepted-manifest",
1905                        &accepted_manifest,
1906                        false,
1907                        &worker_control,
1908                    )?,
1909                    native_import_policy: SealedLinuxPayload::from_verified_bytes(
1910                        "native-import policy",
1911                        "projectatlas-native-policy",
1912                        &native_import_policy,
1913                        false,
1914                        &worker_control,
1915                    )?,
1916                    grammar: SealedLinuxPayload::from_verified_bytes(
1917                        "selected grammar",
1918                        "projectatlas-selected-grammar",
1919                        &grammar_bytes,
1920                        true,
1921                        &worker_control,
1922                    )?,
1923                })
1924            },
1925            &control,
1926        )
1927    }
1928}
1929
1930/// Re-read one manifest-owned Linux payload and require its exact bytes and digest.
1931#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
1932fn read_verified_linux_payload(
1933    spec: &VerifiedLinuxPayloadSpec,
1934    control: &ArtifactIoControl<'_>,
1935) -> Result<Vec<u8>, ParserSupervisorError> {
1936    let read = read_bounded_file(&spec.path, spec.epoch, spec.bytes, Some(control))?;
1937    if u64::try_from(read.bytes.len()).ok() != Some(spec.bytes) {
1938        return Err(ParserSupervisorError::PayloadMismatch {
1939            path: spec.path.clone(),
1940            reason: "launch payload byte count differs from the artifact manifest",
1941        });
1942    }
1943    if read.sha256 != spec.sha256 {
1944        return Err(ParserSupervisorError::PayloadMismatch {
1945            path: spec.path.clone(),
1946            reason: "launch payload SHA-256 differs from the artifact manifest",
1947        });
1948    }
1949    Ok(read.bytes)
1950}
1951
1952/// Return the accepted target for the current host or refuse before reading source.
1953fn host_pack_platform() -> Option<PackPlatform> {
1954    match (std::env::consts::OS, std::env::consts::ARCH) {
1955        ("linux", "x86_64") => Some(PackPlatform::LinuxX86_64),
1956        ("windows", "x86_64") => Some(PackPlatform::WindowsX86_64),
1957        _ => None,
1958    }
1959}
1960
1961/// Canonicalize one required artifact directory.
1962fn canonical_directory(path: &Path) -> Result<PathBuf, ParserSupervisorError> {
1963    let canonical = fs::canonicalize(path).map_err(|source| ParserSupervisorError::PackPath {
1964        path: path.to_path_buf(),
1965        source,
1966    })?;
1967    let metadata = file_metadata(&canonical)?;
1968    if !canonical.is_absolute() || !metadata.is_dir() {
1969        return Err(ParserSupervisorError::InvalidPackPath {
1970            path: canonical,
1971            reason: "expected an absolute regular directory",
1972        });
1973    }
1974    Ok(canonical)
1975}
1976
1977/// Canonicalize one exact file at the artifact root.
1978fn canonical_direct_file(
1979    pack_root: &Path,
1980    file_name: &str,
1981) -> Result<PathBuf, ParserSupervisorError> {
1982    let path = canonical_payload_file(pack_root, file_name)?;
1983    if path.parent() != Some(pack_root) {
1984        return Err(ParserSupervisorError::InvalidPackPath {
1985            path,
1986            reason: "expected a direct artifact-root file",
1987        });
1988    }
1989    Ok(path)
1990}
1991
1992/// Canonicalize one manifest-approved payload without following mutable indirection.
1993fn canonical_payload_file(
1994    pack_root: &Path,
1995    relative: &str,
1996) -> Result<PathBuf, ParserSupervisorError> {
1997    let relative_path = Path::new(relative);
1998    if relative_path.is_absolute()
1999        || relative_path
2000            .components()
2001            .any(|component| !matches!(component, std::path::Component::Normal(_)))
2002    {
2003        return Err(ParserSupervisorError::InvalidPackPath {
2004            path: relative_path.to_path_buf(),
2005            reason: "expected a normalized artifact-relative path",
2006        });
2007    }
2008    let requested = pack_root.join(relative_path);
2009    let mut component_path = pack_root.to_path_buf();
2010    for component in relative_path.components() {
2011        let std::path::Component::Normal(component) = component else {
2012            return Err(ParserSupervisorError::InvalidPackPath {
2013                path: requested,
2014                reason: "expected only normal relative components",
2015            });
2016        };
2017        component_path.push(component);
2018        let metadata = fs::symlink_metadata(&component_path).map_err(|source| {
2019            ParserSupervisorError::PackPath {
2020                path: component_path.clone(),
2021                source,
2022            }
2023        })?;
2024        if is_link_or_reparse_point(&metadata) {
2025            return Err(ParserSupervisorError::InvalidPackPath {
2026                path: component_path,
2027                reason: "symbolic links and reparse points are not accepted in immutable packs",
2028            });
2029        }
2030    }
2031    let canonical =
2032        fs::canonicalize(&requested).map_err(|source| ParserSupervisorError::PackPath {
2033            path: requested.clone(),
2034            source,
2035        })?;
2036    let metadata = file_metadata(&canonical)?;
2037    if !canonical.starts_with(pack_root) || !metadata.is_file() {
2038        return Err(ParserSupervisorError::InvalidPackPath {
2039            path: canonical,
2040            reason: "payload must be a regular file inside the canonical pack root",
2041        });
2042    }
2043    Ok(canonical)
2044}
2045
2046/// Return whether metadata represents mutable path indirection.
2047#[cfg(windows)]
2048fn is_link_or_reparse_point(metadata: &Metadata) -> bool {
2049    use std::os::windows::fs::MetadataExt;
2050
2051    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
2052    metadata.file_type().is_symlink()
2053        || metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
2054}
2055
2056/// Return whether metadata represents a symbolic link.
2057#[cfg(not(windows))]
2058fn is_link_or_reparse_point(metadata: &Metadata) -> bool {
2059    metadata.file_type().is_symlink()
2060}
2061
2062/// Read regular-file metadata with a typed path error.
2063fn file_metadata(path: &Path) -> Result<Metadata, ParserSupervisorError> {
2064    fs::metadata(path).map_err(|source| ParserSupervisorError::PackPath {
2065        path: path.to_path_buf(),
2066        source,
2067    })
2068}
2069
2070/// Read and hash one exact regular file without permitting growth beyond its bound.
2071fn read_bounded_file(
2072    path: &Path,
2073    expected_epoch: FileChangeEpoch,
2074    maximum: u64,
2075    control: Option<&ArtifactIoControl<'_>>,
2076) -> Result<BoundedArtifactRead, ParserSupervisorError> {
2077    let mut file = File::open(path).map_err(|source| ParserSupervisorError::ArtifactRead {
2078        path: path.to_path_buf(),
2079        source,
2080    })?;
2081    let metadata = file
2082        .metadata()
2083        .map_err(|source| ParserSupervisorError::ArtifactRead {
2084            path: path.to_path_buf(),
2085            source,
2086        })?;
2087    if !metadata.is_file() {
2088        return Err(ParserSupervisorError::InvalidPackPath {
2089            path: path.to_path_buf(),
2090            reason: "expected a regular artifact file",
2091        });
2092    }
2093    if FileChangeEpoch::from_metadata(&metadata) != expected_epoch {
2094        return Err(ParserSupervisorError::PayloadMismatch {
2095            path: path.to_path_buf(),
2096            reason: "artifact read handle does not match the captured file identity",
2097        });
2098    }
2099    if metadata.len() > maximum {
2100        return Err(ParserSupervisorError::ArtifactFileTooLarge {
2101            path: path.to_path_buf(),
2102            actual: metadata.len(),
2103            maximum,
2104        });
2105    }
2106    let capacity = usize::try_from(metadata.len()).unwrap_or(ARTIFACT_READ_CHUNK_BYTES);
2107    let mut bytes = Vec::with_capacity(capacity);
2108    let mut sha256 = Sha256::new();
2109    read_bounded_chunks(&mut file, path, maximum, &mut bytes, &mut sha256, control)?;
2110    if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > maximum {
2111        return Err(ParserSupervisorError::ArtifactFileTooLarge {
2112            path: path.to_path_buf(),
2113            actual: u64::try_from(bytes.len()).unwrap_or(u64::MAX),
2114            maximum,
2115        });
2116    }
2117    Ok(BoundedArtifactRead {
2118        bytes,
2119        sha256: encode_sha256(sha256.finalize()),
2120    })
2121}
2122
2123/// Read bounded chunks while polling active request stop conditions.
2124fn read_bounded_chunks(
2125    reader: &mut impl Read,
2126    path: &Path,
2127    maximum: u64,
2128    bytes: &mut Vec<u8>,
2129    sha256: &mut Sha256,
2130    control: Option<&ArtifactIoControl<'_>>,
2131) -> Result<(), ParserSupervisorError> {
2132    let mut chunk = vec![0_u8; ARTIFACT_READ_CHUNK_BYTES].into_boxed_slice();
2133    loop {
2134        if let Some(control) = control {
2135            control.poll()?;
2136        }
2137        let remaining = maximum
2138            .saturating_add(1)
2139            .saturating_sub(u64::try_from(bytes.len()).unwrap_or(u64::MAX));
2140        if remaining == 0 {
2141            break;
2142        }
2143        let limit = usize::try_from(remaining)
2144            .unwrap_or(ARTIFACT_READ_CHUNK_BYTES)
2145            .min(ARTIFACT_READ_CHUNK_BYTES);
2146        let read = match reader.read(&mut chunk[..limit]) {
2147            Ok(read) => read,
2148            Err(source) if source.kind() == io::ErrorKind::Interrupted => continue,
2149            Err(source) => {
2150                return Err(ParserSupervisorError::ArtifactRead {
2151                    path: path.to_path_buf(),
2152                    source,
2153                });
2154            }
2155        };
2156        if read == 0 {
2157            break;
2158        }
2159        sha256.update(&chunk[..read]);
2160        bytes.extend_from_slice(&chunk[..read]);
2161    }
2162    Ok(())
2163}
2164
2165/// Encode one SHA-256 digest as lowercase hexadecimal.
2166fn encode_sha256(digest: impl AsRef<[u8]>) -> String {
2167    const LOWER_HEX: &[u8; 16] = b"0123456789abcdef";
2168    let digest = digest.as_ref();
2169    let mut encoded = String::with_capacity(digest.len().saturating_mul(2));
2170    for byte in digest {
2171        encoded.push(char::from(LOWER_HEX[usize::from(*byte >> 4)]));
2172        encoded.push(char::from(LOWER_HEX[usize::from(*byte & 0x0f)]));
2173    }
2174    encoded
2175}
2176
2177/// Require an executable payload on hosts that expose Unix mode bits.
2178#[cfg(unix)]
2179fn require_executable(path: &Path) -> Result<(), ParserSupervisorError> {
2180    use std::os::unix::fs::PermissionsExt;
2181
2182    if file_metadata(path)?.permissions().mode() & 0o111 == 0 {
2183        return Err(ParserSupervisorError::PayloadMismatch {
2184            path: path.to_path_buf(),
2185            reason: "executable payload has no execute permission",
2186        });
2187    }
2188    Ok(())
2189}
2190
2191/// Failure produced inside one owned standard-stream thread.
2192#[derive(Debug, Error)]
2193enum ParserIoThreadError {
2194    /// A stream read or write failed.
2195    #[error("{operation}: {source}")]
2196    Stream {
2197        /// Stable stream operation.
2198        operation: &'static str,
2199        /// Standard I/O failure.
2200        #[source]
2201        source: io::Error,
2202    },
2203    /// A fixed frame header violated the closed protocol.
2204    #[error("frame header: {source}")]
2205    FrameHeader {
2206        /// Typed header failure.
2207        #[source]
2208        source: ParserProtocolError,
2209    },
2210    /// The Windows broker admission record differed from the fixed contract.
2211    #[error("Windows admission record mismatch")]
2212    AdmissionMismatch,
2213    /// A worker or broker wrote bytes outside the framed protocol.
2214    #[error("unexpected diagnostic bytes: {diagnostic}")]
2215    UnexpectedDiagnostic {
2216        /// Bounded lossy rendering of the first observed bytes.
2217        diagnostic: String,
2218    },
2219}
2220
2221/// One bounded stdout-reader event.
2222#[derive(Debug)]
2223enum FrameReaderEvent {
2224    /// One complete frame whose header was validated before allocation.
2225    Frame(Vec<u8>),
2226    /// Clean end of stream between frames.
2227    EndOfStream,
2228    /// Terminal bounded reader failure.
2229    Failure(ParserIoThreadError),
2230}
2231
2232/// One bounded stderr/admission-reader event.
2233#[derive(Debug)]
2234enum DiagnosticReaderEvent {
2235    /// Platform admission completed and protocol input may begin.
2236    AdmissionAccepted,
2237    /// The parent-authored fence after one complete stdout frame was observed.
2238    FenceObserved,
2239    /// Terminal bounded reader failure.
2240    Failure(ParserIoThreadError),
2241}
2242
2243/// One random parent-only record used to order independent standard pipes.
2244#[derive(Clone, Copy)]
2245struct DiagnosticFence([u8; PARSER_DIAGNOSTIC_FENCE_BYTES]);
2246
2247/// One exact write owned by the fixed worker-input thread.
2248struct WriterCommand {
2249    /// Complete bytes for one indivisible protocol send.
2250    bytes: Vec<u8>,
2251    /// One-shot write and flush result.
2252    acknowledgement: SyncSender<Result<(), ParserIoThreadError>>,
2253}
2254
2255/// Read one frame with fixed-header validation before payload allocation.
2256fn read_one_frame(input: &mut impl Read) -> Result<Option<Vec<u8>>, ParserIoThreadError> {
2257    let mut header_bytes = [0_u8; PARSER_FRAME_HEADER_BYTES];
2258    let mut header_read = 0_usize;
2259    while header_read < header_bytes.len() {
2260        match input.read(&mut header_bytes[header_read..]) {
2261            Ok(0) if header_read == 0 => return Ok(None),
2262            Ok(0) => {
2263                return Err(ParserIoThreadError::Stream {
2264                    operation: "read partial frame header",
2265                    source: io::Error::new(io::ErrorKind::UnexpectedEof, "partial frame header"),
2266                });
2267            }
2268            Ok(count) => header_read = header_read.saturating_add(count),
2269            Err(source) if source.kind() == io::ErrorKind::Interrupted => {}
2270            Err(source) => {
2271                return Err(ParserIoThreadError::Stream {
2272                    operation: "read frame header",
2273                    source,
2274                });
2275            }
2276        }
2277    }
2278    let header = ParserFrameHeader::decode(&header_bytes)
2279        .map_err(|source| ParserIoThreadError::FrameHeader { source })?;
2280    let payload_len = header.payload_len() as usize;
2281    let frame_len = PARSER_FRAME_HEADER_BYTES.saturating_add(payload_len);
2282    let mut frame = Vec::with_capacity(frame_len);
2283    frame.extend_from_slice(&header_bytes);
2284    frame.resize(frame_len, 0);
2285    input
2286        .read_exact(&mut frame[PARSER_FRAME_HEADER_BYTES..])
2287        .map_err(|source| ParserIoThreadError::Stream {
2288            operation: "read frame payload",
2289            source,
2290        })?;
2291    Ok(Some(frame))
2292}
2293
2294/// Own worker stdout and fence every complete or failed frame through the diagnostic pipe.
2295fn frame_reader_loop(
2296    mut stdout: ChildStdout,
2297    mut diagnostic_fence_writer: impl Write,
2298    diagnostic_fence: DiagnosticFence,
2299    events: &SyncSender<FrameReaderEvent>,
2300) {
2301    loop {
2302        let event = match read_one_frame(&mut stdout) {
2303            Ok(Some(frame)) => FrameReaderEvent::Frame(frame),
2304            Ok(None) => FrameReaderEvent::EndOfStream,
2305            Err(error) => FrameReaderEvent::Failure(error),
2306        };
2307        let event = if matches!(event, FrameReaderEvent::EndOfStream) {
2308            event
2309        } else {
2310            match diagnostic_fence_writer
2311                .write_all(&diagnostic_fence.0)
2312                .and_then(|()| diagnostic_fence_writer.flush())
2313            {
2314                Ok(()) => event,
2315                Err(source) => FrameReaderEvent::Failure(ParserIoThreadError::Stream {
2316                    operation: "write diagnostic fence",
2317                    source,
2318                }),
2319            }
2320        };
2321        let terminal = !matches!(event, FrameReaderEvent::Frame(_));
2322        if events.send(event).is_err() || terminal {
2323            return;
2324        }
2325    }
2326}
2327
2328/// Own worker or broker stderr, validating Windows admission before diagnostics.
2329fn diagnostic_reader_loop(
2330    mut stderr: impl Read,
2331    expect_windows_admission: bool,
2332    diagnostic_fence: DiagnosticFence,
2333    events: &SyncSender<DiagnosticReaderEvent>,
2334) -> Result<Vec<u8>, ParserIoThreadError> {
2335    if expect_windows_admission {
2336        let mut observed = [0_u8; PARSER_WINDOWS_BROKER_ADMISSION_RECORD.len()];
2337        if let Err(source) = stderr.read_exact(&mut observed) {
2338            let message = source.to_string();
2339            return if events
2340                .send(DiagnosticReaderEvent::Failure(
2341                    ParserIoThreadError::Stream {
2342                        operation: "read Windows admission record",
2343                        source,
2344                    },
2345                ))
2346                .is_ok()
2347            {
2348                Ok(Vec::new())
2349            } else {
2350                Err(ParserIoThreadError::Stream {
2351                    operation: "read Windows admission record",
2352                    source: io::Error::other(message),
2353                })
2354            };
2355        }
2356        if observed != PARSER_WINDOWS_BROKER_ADMISSION_RECORD {
2357            let error = ParserIoThreadError::AdmissionMismatch;
2358            return if events.send(DiagnosticReaderEvent::Failure(error)).is_ok() {
2359                Ok(Vec::new())
2360            } else {
2361                Err(ParserIoThreadError::AdmissionMismatch)
2362            };
2363        }
2364    }
2365    if events
2366        .send(DiagnosticReaderEvent::AdmissionAccepted)
2367        .is_err()
2368    {
2369        return Ok(Vec::new());
2370    }
2371
2372    loop {
2373        let mut observed = [0_u8; PARSER_DIAGNOSTIC_FENCE_BYTES];
2374        let mut observed_len = 0_usize;
2375        while observed_len < observed.len() {
2376            match stderr.read(&mut observed[observed_len..]) {
2377                Ok(0) if observed_len == 0 => return Ok(Vec::new()),
2378                Ok(0) => break,
2379                Ok(count) => observed_len = observed_len.saturating_add(count),
2380                Err(source) if source.kind() == io::ErrorKind::Interrupted => {}
2381                Err(source) => {
2382                    let message = source.to_string();
2383                    return if events
2384                        .send(DiagnosticReaderEvent::Failure(
2385                            ParserIoThreadError::Stream {
2386                                operation: "read diagnostic stream",
2387                                source,
2388                            },
2389                        ))
2390                        .is_ok()
2391                    {
2392                        Ok(observed[..observed_len].to_vec())
2393                    } else {
2394                        Err(ParserIoThreadError::Stream {
2395                            operation: "read diagnostic stream",
2396                            source: io::Error::other(message),
2397                        })
2398                    };
2399                }
2400            }
2401        }
2402        if observed_len == observed.len() && observed == diagnostic_fence.0 {
2403            if events.send(DiagnosticReaderEvent::FenceObserved).is_err() {
2404                return Ok(Vec::new());
2405            }
2406            continue;
2407        }
2408        let diagnostics = observed[..observed_len].to_vec();
2409        let diagnostic = bounded_diagnostic(&diagnostics);
2410        return if events
2411            .send(DiagnosticReaderEvent::Failure(
2412                ParserIoThreadError::UnexpectedDiagnostic {
2413                    diagnostic: diagnostic.clone(),
2414                },
2415            ))
2416            .is_ok()
2417        {
2418            Ok(diagnostics)
2419        } else {
2420            Err(ParserIoThreadError::UnexpectedDiagnostic { diagnostic })
2421        };
2422    }
2423}
2424
2425/// Own worker stdin and acknowledge each bounded write after flushing.
2426fn writer_loop(mut stdin: impl Write, commands: &Receiver<WriterCommand>) {
2427    while let Ok(command) = commands.recv() {
2428        let result = stdin
2429            .write_all(&command.bytes)
2430            .and_then(|()| stdin.flush())
2431            .map_err(|source| ParserIoThreadError::Stream {
2432                operation: "write protocol frame",
2433                source,
2434            });
2435        let failed = result.is_err();
2436        if command.acknowledgement.send(result).is_err() || failed {
2437            return;
2438        }
2439    }
2440}
2441
2442/// Owned stdout-reader thread and its capacity-one event channel.
2443struct FrameReader {
2444    /// Capacity-one framed output channel.
2445    events: Receiver<FrameReaderEvent>,
2446    /// Owned reader thread.
2447    handle: Option<JoinHandle<()>>,
2448}
2449
2450/// Owned stderr/admission-reader thread and its capacity-one event channel.
2451struct DiagnosticReader {
2452    /// Capacity-one admission and failure channel.
2453    events: Receiver<DiagnosticReaderEvent>,
2454    /// Owned diagnostic reader thread.
2455    handle: Option<JoinHandle<Result<Vec<u8>, ParserIoThreadError>>>,
2456}
2457
2458/// One observed Linux resident-memory breach.
2459#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2460struct LinuxMemoryBreach {
2461    /// Active accounting path.
2462    accounting: ParserMemoryAccountingKind,
2463    /// Last observed resident or cgroup memory bytes.
2464    observed_bytes: u64,
2465}
2466
2467/// Bounded resolution of a transient Linux process-exit/accounting transition.
2468#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2469enum LinuxMemoryObservation {
2470    /// Resident memory became readable again while the worker remained live.
2471    Memory(Option<LinuxMemoryBreach>),
2472    /// The direct child became waitable before memory accounting recovered.
2473    ChildExited {
2474        /// Platform exit code, when the process reported one.
2475        code: Option<i32>,
2476    },
2477}
2478
2479/// One observed direct-child exit stripped to the public diagnostic contract.
2480#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2481struct LinuxChildExit {
2482    /// Platform exit code, when the process reported one.
2483    code: Option<i32>,
2484}
2485
2486/// Result of one Linux process-group signal attempt.
2487#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2488enum LinuxProcessGroupTermination {
2489    /// `SIGKILL` was delivered to the process group.
2490    Signalled,
2491    /// The kernel reported that the process group was absent.
2492    Absent,
2493}
2494
2495/// Optional delegated cgroup-v2 state retained until the worker has been reaped.
2496#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2497struct LinuxCgroupMemory {
2498    /// Product-owned child cgroup inside an already delegated parent.
2499    directory: PathBuf,
2500    /// `memory.events:max` counter before the worker was attached.
2501    initial_max_events: u64,
2502}
2503
2504#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2505impl LinuxCgroupMemory {
2506    /// Create, configure, and attach only inside an existing writable delegation.
2507    fn try_attach(process_id: u32, maximum_bytes: u64) -> io::Result<Option<Self>> {
2508        for parent in delegated_cgroup_parents()? {
2509            let sequence = CGROUP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
2510            let directory = parent.join(format!(
2511                "projectatlas-parser-{}-{sequence}",
2512                std::process::id()
2513            ));
2514            if fs::create_dir(&directory).is_err() {
2515                continue;
2516            }
2517            let mut candidate = Self {
2518                directory,
2519                initial_max_events: 0,
2520            };
2521            if !prepare_delegated_memory_parent(&parent) {
2522                candidate.cleanup()?;
2523                continue;
2524            }
2525            if candidate.configure(maximum_bytes).is_err() {
2526                candidate.cleanup()?;
2527                continue;
2528            }
2529            let Ok(initial_max_events) = read_cgroup_max_events(&candidate.directory) else {
2530                candidate.cleanup()?;
2531                continue;
2532            };
2533            candidate.initial_max_events = initial_max_events;
2534            if fs::write(
2535                candidate.directory.join("cgroup.procs"),
2536                process_id.to_string(),
2537            )
2538            .is_err()
2539            {
2540                candidate.cleanup()?;
2541                continue;
2542            }
2543            return Ok(Some(candidate));
2544        }
2545        Ok(None)
2546    }
2547
2548    /// Install and read back the hard kernel memory ceiling.
2549    fn configure(&self, maximum_bytes: u64) -> io::Result<()> {
2550        let maximum = maximum_bytes.to_string();
2551        fs::write(self.directory.join("memory.max"), &maximum)?;
2552        if read_bounded_linux_text(
2553            &self.directory.join("memory.max"),
2554            LINUX_MEMORY_RECORD_MAX_BYTES,
2555        )?
2556        .trim()
2557            != maximum
2558        {
2559            return Err(io::Error::new(
2560                io::ErrorKind::InvalidData,
2561                "delegated cgroup memory.max did not retain the configured ceiling",
2562            ));
2563        }
2564        let oom_group = self.directory.join("memory.oom.group");
2565        if oom_group.is_file() {
2566            fs::write(oom_group, "1")?;
2567        }
2568        Ok(())
2569    }
2570
2571    /// Observe current kernel-accounted memory and allocation-limit events.
2572    fn observe(&self, maximum_bytes: u64) -> io::Result<Option<LinuxMemoryBreach>> {
2573        let observed_bytes = read_cgroup_current(&self.directory)?;
2574        let maximum_events = read_cgroup_max_events(&self.directory)?;
2575        if observed_bytes >= maximum_bytes || maximum_events > self.initial_max_events {
2576            Ok(Some(LinuxMemoryBreach {
2577                accounting: ParserMemoryAccountingKind::LinuxCgroupV2,
2578                observed_bytes,
2579            }))
2580        } else {
2581            Ok(None)
2582        }
2583    }
2584
2585    /// Remove the now-empty delegated child cgroup after the worker was reaped.
2586    fn cleanup(&mut self) -> io::Result<()> {
2587        if !self.directory.exists() {
2588            return Ok(());
2589        }
2590        fs::remove_dir(&self.directory)
2591    }
2592}
2593
2594#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2595impl Drop for LinuxCgroupMemory {
2596    fn drop(&mut self) {
2597        drop(self.cleanup());
2598    }
2599}
2600
2601/// Supervisor-owned Linux memory observer with a sampled-RSS fallback.
2602#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2603struct LinuxMemoryObserver {
2604    /// Optional kernel-hard cgroup accounting retained for cleanup.
2605    cgroup: Option<LinuxCgroupMemory>,
2606    /// Whether cgroup observation remains readable for this session.
2607    observe_cgroup: bool,
2608    /// Inclusive resident-memory ceiling.
2609    maximum_bytes: u64,
2610}
2611
2612#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2613impl LinuxMemoryObserver {
2614    /// Attach opportunistic cgroup accounting and always retain sampled-RSS fallback.
2615    fn attach(process_id: u32, maximum_bytes: u64) -> io::Result<Self> {
2616        let cgroup = LinuxCgroupMemory::try_attach(process_id, maximum_bytes)?;
2617        let observe_cgroup = cgroup.is_some();
2618        Ok(Self {
2619            cgroup,
2620            observe_cgroup,
2621            maximum_bytes,
2622        })
2623    }
2624
2625    /// Construct the ordinary sampled-RSS path after a failed optional-cgroup cleanup.
2626    fn sampled_rss(maximum_bytes: u64) -> Self {
2627        Self {
2628            cgroup: None,
2629            observe_cgroup: false,
2630            maximum_bytes,
2631        }
2632    }
2633
2634    /// Observe one current cgroup or sampled-RSS value.
2635    fn observe(&mut self, process_id: u32) -> io::Result<Option<LinuxMemoryBreach>> {
2636        if self.observe_cgroup
2637            && let Some(cgroup) = self.cgroup.as_ref()
2638        {
2639            match cgroup.observe(self.maximum_bytes) {
2640                Ok(observation) => return Ok(observation),
2641                Err(_source) => self.observe_cgroup = false,
2642            }
2643        }
2644        let observed_bytes = read_process_rss(process_id)?;
2645        if observed_bytes >= self.maximum_bytes {
2646            Ok(Some(LinuxMemoryBreach {
2647                accounting: ParserMemoryAccountingKind::LinuxProcStatus,
2648                observed_bytes,
2649            }))
2650        } else {
2651            Ok(None)
2652        }
2653    }
2654
2655    /// Remove delegated cgroup state after the direct child has been reaped.
2656    fn cleanup(&mut self) -> io::Result<()> {
2657        match self.cgroup.as_mut() {
2658            Some(cgroup) => cgroup.cleanup(),
2659            None => Ok(()),
2660        }
2661    }
2662
2663    /// Return the accounting path used by the next observation.
2664    fn accounting_kind(&self) -> ParserMemoryAccountingKind {
2665        if self.observe_cgroup {
2666            ParserMemoryAccountingKind::LinuxCgroupV2
2667        } else {
2668            ParserMemoryAccountingKind::LinuxProcStatus
2669        }
2670    }
2671}
2672
2673/// One terminal event emitted by the continuous Linux memory monitor.
2674#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2675enum LinuxMemoryMonitorEvent {
2676    /// The configured resident-memory ceiling was reached.
2677    Limit {
2678        /// Accounting mode that observed the breach.
2679        breach: LinuxMemoryBreach,
2680        /// Process-group termination failure, when the first kill attempt failed.
2681        termination_error: Option<String>,
2682    },
2683    /// Both cgroup accounting and its sampled-RSS fallback became unreadable.
2684    ObservationFailed {
2685        /// Accounting mode that became unreadable.
2686        accounting: ParserMemoryAccountingKind,
2687        /// Bounded observation failure.
2688        message: String,
2689    },
2690}
2691
2692/// Exactly one owned continuous Linux resident-memory monitor.
2693#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2694struct LinuxMemoryMonitor {
2695    /// Capacity-one stop signal.
2696    stop: SyncSender<()>,
2697    /// Capacity-one terminal event channel.
2698    events: Receiver<LinuxMemoryMonitorEvent>,
2699    /// Owned monitor thread joined before the child can be reaped.
2700    handle: Option<JoinHandle<()>>,
2701}
2702
2703#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2704impl LinuxMemoryMonitor {
2705    /// Start continuous sampling for one process group.
2706    fn start(process_id: u32, observer: Arc<Mutex<LinuxMemoryObserver>>) -> io::Result<Self> {
2707        let (stop, stop_receiver) = mpsc::sync_channel(1);
2708        let (event_sender, events) = mpsc::sync_channel(1);
2709        let handle = thread::Builder::new()
2710            .name("parser-supervisor-memory".to_owned())
2711            .spawn(move || {
2712                linux_memory_monitor_loop(process_id, &observer, &stop_receiver, &event_sender);
2713            })?;
2714        Ok(Self {
2715            stop,
2716            events,
2717            handle: Some(handle),
2718        })
2719    }
2720
2721    /// Stop and join exactly once, returning any terminal observation.
2722    fn stop(&mut self) -> Result<Option<LinuxMemoryMonitorEvent>, ParserSupervisorError> {
2723        let _stop_signal_result = self.stop.try_send(());
2724        if let Some(handle) = self.handle.take() {
2725            handle
2726                .join()
2727                .map_err(|_panic| ParserSupervisorError::Cleanup {
2728                    message: "Linux resident-memory monitor panicked".to_owned(),
2729                })?;
2730        }
2731        match self.events.try_recv() {
2732            Ok(event) => Ok(Some(event)),
2733            Err(TryRecvError::Empty | TryRecvError::Disconnected) => Ok(None),
2734        }
2735    }
2736}
2737
2738/// Sample continuously while the resident worker is alive, including idle periods.
2739#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2740fn linux_memory_monitor_loop(
2741    process_id: u32,
2742    observer: &Arc<Mutex<LinuxMemoryObserver>>,
2743    stop: &Receiver<()>,
2744    events: &SyncSender<LinuxMemoryMonitorEvent>,
2745) {
2746    let mut next_observation = Instant::now();
2747    loop {
2748        let observation = match observer.lock() {
2749            Ok(mut observer) => {
2750                let observation = observer.observe(process_id);
2751                let accounting = observer.accounting_kind();
2752                observation.map_err(|source| (accounting, source))
2753            }
2754            Err(_poisoned) => Err((
2755                ParserMemoryAccountingKind::LinuxProcStatus,
2756                io::Error::other("Linux memory observer lock was poisoned"),
2757            )),
2758        };
2759        let event = match observation {
2760            Ok(None) => None,
2761            Ok(Some(breach)) => Some(LinuxMemoryMonitorEvent::Limit {
2762                breach,
2763                termination_error: linux_monitor_termination_error(process_id),
2764            }),
2765            Err((accounting, source)) => Some(LinuxMemoryMonitorEvent::ObservationFailed {
2766                accounting,
2767                message: bounded_message(source.to_string()),
2768            }),
2769        };
2770        if let Some(event) = event {
2771            drop(events.try_send(event));
2772            return;
2773        }
2774        next_observation = next_observation
2775            .checked_add(PARSER_LINUX_RSS_OBSERVATION_INTERVAL)
2776            .unwrap_or_else(Instant::now);
2777        let wait = next_observation.saturating_duration_since(Instant::now());
2778        match stop.recv_timeout(wait) {
2779            Ok(()) | Err(RecvTimeoutError::Disconnected) => return,
2780            Err(RecvTimeoutError::Timeout) => {}
2781        }
2782    }
2783}
2784
2785/// Convert a group-signal miss into an event so the child owner performs direct fallback.
2786#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2787fn linux_monitor_termination_error(process_id: u32) -> Option<String> {
2788    match terminate_linux_process_group(process_id) {
2789        Ok(LinuxProcessGroupTermination::Signalled) => None,
2790        Ok(LinuxProcessGroupTermination::Absent) => {
2791            Some("worker process group was absent".to_owned())
2792        }
2793        Err(source) => Some(source),
2794    }
2795}
2796
2797/// Return bounded current-to-root cgroup-v2 candidates for delegation probing.
2798#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2799fn delegated_cgroup_parents() -> io::Result<Vec<PathBuf>> {
2800    let membership = read_bounded_linux_text(
2801        Path::new("/proc/self/cgroup"),
2802        LINUX_MEMORY_RECORD_MAX_BYTES,
2803    )?;
2804    let Some(relative_path) = parse_unified_cgroup_path(&membership)? else {
2805        return Ok(Vec::new());
2806    };
2807    let root = Path::new(CGROUP_V2_ROOT);
2808    let mut candidate = root.join(relative_path);
2809    let mut candidates = Vec::new();
2810    loop {
2811        if !candidate.starts_with(root) || candidates.len() >= MAX_CGROUP_ANCESTORS {
2812            return Err(io::Error::new(
2813                io::ErrorKind::InvalidData,
2814                "unified cgroup membership exceeds its ancestor bound",
2815            ));
2816        }
2817        candidates.push(candidate.clone());
2818        if candidate == root {
2819            break;
2820        }
2821        if !candidate.pop() {
2822            return Err(io::Error::new(
2823                io::ErrorKind::InvalidData,
2824                "unified cgroup membership escaped its mount",
2825            ));
2826        }
2827    }
2828    Ok(candidates)
2829}
2830
2831/// Parse exactly one safe unified-cgroup membership path.
2832#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2833fn parse_unified_cgroup_path(membership: &str) -> io::Result<Option<PathBuf>> {
2834    let mut unified = membership
2835        .lines()
2836        .filter_map(|line| line.strip_prefix("0::"));
2837    let Some(relative) = unified.next() else {
2838        return Ok(None);
2839    };
2840    if unified.next().is_some() {
2841        return Err(io::Error::new(
2842            io::ErrorKind::InvalidData,
2843            "multiple unified cgroup memberships were reported",
2844        ));
2845    }
2846    let relative = relative.trim_start_matches('/');
2847    let relative_path = Path::new(relative);
2848    let component_count = relative_path.components().count();
2849    if component_count > MAX_CGROUP_ANCESTORS.saturating_sub(1)
2850        || relative_path
2851            .components()
2852            .any(|component| !matches!(component, std::path::Component::Normal(_)))
2853    {
2854        return Err(io::Error::new(
2855            io::ErrorKind::InvalidData,
2856            "unified cgroup membership path is unsafe or too deep",
2857        ));
2858    }
2859    Ok(Some(relative_path.to_path_buf()))
2860}
2861
2862/// Require one candidate parent to expose a usable delegated memory controller.
2863#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2864fn prepare_delegated_memory_parent(parent: &Path) -> bool {
2865    let controllers = read_bounded_linux_text(
2866        &parent.join("cgroup.controllers"),
2867        LINUX_MEMORY_RECORD_MAX_BYTES,
2868    );
2869    let Ok(controllers) = controllers else {
2870        return false;
2871    };
2872    if !has_cgroup_token(&controllers, "memory") {
2873        return false;
2874    }
2875    let subtree_path = parent.join("cgroup.subtree_control");
2876    let Ok(mut subtree_control) =
2877        read_bounded_linux_text(&subtree_path, LINUX_MEMORY_RECORD_MAX_BYTES)
2878    else {
2879        return false;
2880    };
2881    if !has_cgroup_token(&subtree_control, "memory") {
2882        if fs::write(&subtree_path, "+memory").is_err() {
2883            return false;
2884        }
2885        let Ok(observed) = read_bounded_linux_text(&subtree_path, LINUX_MEMORY_RECORD_MAX_BYTES)
2886        else {
2887            return false;
2888        };
2889        subtree_control = observed;
2890        if !has_cgroup_token(&subtree_control, "memory") {
2891            return false;
2892        }
2893    }
2894    true
2895}
2896
2897/// Return whether one whitespace-delimited cgroup controller set contains an exact token.
2898#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2899fn has_cgroup_token(values: &str, expected: &str) -> bool {
2900    values.split_whitespace().any(|value| {
2901        value == expected
2902            || value
2903                .strip_prefix('+')
2904                .is_some_and(|value| value == expected)
2905    })
2906}
2907
2908/// Read one kernel-generated Linux accounting record within a fixed byte ceiling.
2909#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2910fn read_bounded_linux_text(path: &Path, maximum: u64) -> io::Result<String> {
2911    let mut bytes = Vec::new();
2912    File::open(path)?
2913        .take(maximum.saturating_add(1))
2914        .read_to_end(&mut bytes)?;
2915    if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > maximum {
2916        return Err(io::Error::new(
2917            io::ErrorKind::InvalidData,
2918            "Linux memory accounting record exceeded its byte ceiling",
2919        ));
2920    }
2921    String::from_utf8(bytes).map_err(|source| io::Error::new(io::ErrorKind::InvalidData, source))
2922}
2923
2924/// Read the worker's resident memory from one bounded procfs status record.
2925#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
2926fn read_process_rss(process_id: u32) -> io::Result<u64> {
2927    let status = read_bounded_linux_text(
2928        &PathBuf::from(format!("/proc/{process_id}/status")),
2929        LINUX_MEMORY_RECORD_MAX_BYTES,
2930    )?;
2931    parse_process_rss(&status)
2932}
2933
2934/// Resolve the short Linux interval between releasing a process address space and becoming
2935/// waitable without treating unreadable accounting as successful containment.
2936#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2937fn resolve_linux_memory_exit_transition(
2938    initial_error: io::Error,
2939    timeout: Duration,
2940    mut observe_memory: impl FnMut() -> io::Result<Option<LinuxMemoryBreach>>,
2941    mut observe_exit: impl FnMut() -> io::Result<Option<LinuxChildExit>>,
2942) -> io::Result<LinuxMemoryObservation> {
2943    let deadline = Instant::now()
2944        .checked_add(timeout)
2945        .unwrap_or_else(Instant::now);
2946    let mut memory_error = initial_error;
2947    loop {
2948        match observe_exit() {
2949            Ok(Some(exit)) => {
2950                return Ok(LinuxMemoryObservation::ChildExited { code: exit.code });
2951            }
2952            Ok(None) => {}
2953            Err(source) => {
2954                return Err(io::Error::other(format!(
2955                    "memory observation failed: {memory_error}; child-state observation also failed: {source}"
2956                )));
2957            }
2958        }
2959        match observe_memory() {
2960            Ok(observation) => return Ok(LinuxMemoryObservation::Memory(observation)),
2961            Err(source) => memory_error = source,
2962        }
2963        let now = Instant::now();
2964        if now >= deadline {
2965            return Err(memory_error);
2966        }
2967        thread::sleep(Duration::from_millis(1).min(deadline.saturating_duration_since(now)));
2968    }
2969}
2970
2971/// Parse one exact `VmRSS` value expressed by Linux in kibibytes.
2972#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
2973fn parse_process_rss(status: &str) -> io::Result<u64> {
2974    let value = status
2975        .lines()
2976        .find_map(|line| line.strip_prefix("VmRSS:"))
2977        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "VmRSS is absent"))?;
2978    let mut fields = value.split_whitespace();
2979    let kibibytes = fields
2980        .next()
2981        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "VmRSS value is absent"))?
2982        .parse::<u64>()
2983        .map_err(|source| io::Error::new(io::ErrorKind::InvalidData, source))?;
2984    if fields.next() != Some("kB") || fields.next().is_some() {
2985        return Err(io::Error::new(
2986            io::ErrorKind::InvalidData,
2987            "VmRSS does not use the exact Linux kB unit",
2988        ));
2989    }
2990    kibibytes.checked_mul(1024).ok_or_else(|| {
2991        io::Error::new(
2992            io::ErrorKind::InvalidData,
2993            "VmRSS byte conversion overflowed",
2994        )
2995    })
2996}
2997
2998/// Read one cgroup-v2 `memory.current` byte count.
2999#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3000fn read_cgroup_current(directory: &Path) -> io::Result<u64> {
3001    let current = read_bounded_linux_text(
3002        &directory.join("memory.current"),
3003        LINUX_MEMORY_RECORD_MAX_BYTES,
3004    )?;
3005    current
3006        .trim()
3007        .parse::<u64>()
3008        .map_err(|source| io::Error::new(io::ErrorKind::InvalidData, source))
3009}
3010
3011/// Read the cgroup-v2 count of allocations rejected by `memory.max`.
3012#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3013fn read_cgroup_max_events(directory: &Path) -> io::Result<u64> {
3014    let events = read_bounded_linux_text(
3015        &directory.join("memory.events"),
3016        LINUX_MEMORY_RECORD_MAX_BYTES,
3017    )?;
3018    parse_cgroup_event(&events, "max")
3019}
3020
3021/// Parse one exact cgroup-v2 event counter.
3022#[cfg(any(all(target_os = "linux", target_arch = "x86_64"), test))]
3023fn parse_cgroup_event(events: &str, name: &str) -> io::Result<u64> {
3024    let value = events
3025        .lines()
3026        .find_map(|line| {
3027            let mut fields = line.split_whitespace();
3028            (fields.next() == Some(name)).then(|| (fields.next(), fields.next()))
3029        })
3030        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "cgroup event is absent"))?;
3031    if value.1.is_some() {
3032        return Err(io::Error::new(
3033            io::ErrorKind::InvalidData,
3034            "cgroup event row has extra fields",
3035        ));
3036    }
3037    value
3038        .0
3039        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "cgroup event value is absent"))?
3040        .parse::<u64>()
3041        .map_err(|source| io::Error::new(io::ErrorKind::InvalidData, source))
3042}
3043
3044/// One admitted grammar-affined child session.
3045struct ResidentParserSession {
3046    /// Direct worker on Linux or direct containment broker on Windows.
3047    child: Child,
3048    /// Grammar identity accepted for this process lifetime.
3049    grammar: ParserLanguageIdentity,
3050    /// Fresh process-session identity echoed by every response.
3051    session: ParserSessionIdentity,
3052    /// Exact independently observed artifact identity.
3053    artifact: ParserArtifactIdentity,
3054    /// Next non-zero request identity.
3055    next_request_id: u64,
3056    /// Whether a resource observer already requested terminal process-tree cleanup.
3057    termination_requested: bool,
3058    /// Exact worker and process-tree ceilings used by this session.
3059    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3060    memory_limits: ParserMemoryLimits,
3061    /// Capacity-one input queue.
3062    writer: Option<SyncSender<WriterCommand>>,
3063    /// Owned fixed writer thread.
3064    writer_handle: Option<JoinHandle<()>>,
3065    /// Owned fixed-header stdout reader.
3066    frame_reader: FrameReader,
3067    /// Owned bounded diagnostic/admission reader.
3068    diagnostic_reader: DiagnosticReader,
3069    /// Parent-authored diagnostic fences already observed ahead of their frame event.
3070    pending_diagnostic_fences: usize,
3071    /// Bounded Linux resident-memory accounting retained through cleanup.
3072    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3073    memory_observer: Arc<Mutex<LinuxMemoryObserver>>,
3074    /// Continuous Linux resident-memory monitor retained through cleanup.
3075    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3076    memory_monitor: Option<LinuxMemoryMonitor>,
3077}
3078
3079impl ResidentParserSession {
3080    /// Launch, admit, open, and validate one exact worker session.
3081    fn launch(
3082        launch: &VerifiedParserPackLaunch,
3083        grammar: ParserLanguageIdentity,
3084        memory_limits: ParserMemoryLimits,
3085        last_progress: Instant,
3086        absolute_deadline: Instant,
3087        no_progress_timeout: Duration,
3088        cancellation: &IndexCancellation,
3089    ) -> Result<Self, ParserSupervisorError> {
3090        let memory_limits = memory_limits.checked()?;
3091        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3092        let command = {
3093            let authority = launch.prepare_resident_launch_controlled(
3094                grammar.as_str(),
3095                last_progress,
3096                absolute_deadline,
3097                no_progress_timeout,
3098                cancellation,
3099            )?;
3100            #[cfg(debug_assertions)]
3101            invoke_linux_launch_test_hook()?;
3102            platform_command(launch, authority, memory_limits)?
3103        };
3104        #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
3105        let command = platform_command(launch, memory_limits)?;
3106        Self::launch_command(
3107            launch,
3108            grammar,
3109            memory_limits,
3110            last_progress,
3111            absolute_deadline,
3112            no_progress_timeout,
3113            cancellation,
3114            command,
3115        )
3116    }
3117
3118    /// Launch one already closed command through the production process owner.
3119    fn launch_command(
3120        launch: &VerifiedParserPackLaunch,
3121        grammar: ParserLanguageIdentity,
3122        memory_limits: ParserMemoryLimits,
3123        last_progress: Instant,
3124        absolute_deadline: Instant,
3125        no_progress_timeout: Duration,
3126        cancellation: &IndexCancellation,
3127        mut command: Command,
3128    ) -> Result<Self, ParserSupervisorError> {
3129        let _ = memory_limits;
3130        poll_stop(
3131            PROCESS_LAUNCH_PHASE,
3132            absolute_deadline,
3133            last_progress,
3134            no_progress_timeout,
3135            cancellation,
3136        )?;
3137        let session = fresh_session_identity()?;
3138        let containment = containment_for_platform(launch.platform);
3139        let diagnostic_fence = fresh_diagnostic_fence()?;
3140        let (diagnostic_pipe, child_diagnostic_writer) =
3141            io::pipe().map_err(|source| ParserSupervisorError::IoThread {
3142                phase: "diagnostic pipe startup",
3143                message: source.to_string(),
3144            })?;
3145        let diagnostic_fence_writer = child_diagnostic_writer.try_clone().map_err(|source| {
3146            ParserSupervisorError::IoThread {
3147                phase: "diagnostic pipe startup",
3148                message: source.to_string(),
3149            }
3150        })?;
3151        command.stderr(Stdio::from(child_diagnostic_writer));
3152        #[cfg(debug_assertions)]
3153        invoke_pre_spawn_test_hook()?;
3154        let mut child = run_bounded_process_spawn(
3155            command,
3156            absolute_deadline,
3157            last_progress,
3158            no_progress_timeout,
3159            cancellation,
3160        )?;
3161        let stdin = child
3162            .stdin
3163            .take()
3164            .ok_or(ParserSupervisorError::MissingPipe { stream: "stdin" });
3165        let stdout = child
3166            .stdout
3167            .take()
3168            .ok_or(ParserSupervisorError::MissingPipe { stream: "stdout" });
3169        let (stdin, stdout) = match (stdin, stdout) {
3170            (Ok(stdin), Ok(stdout)) => (stdin, stdout),
3171            (stdin, stdout) => {
3172                let operation = stdin
3173                    .err()
3174                    .or_else(|| stdout.err())
3175                    .unwrap_or(ParserSupervisorError::MissingPipe { stream: "unknown" });
3176                return Err(attach_cleanup(
3177                    operation,
3178                    cleanup_partial_launch(&mut child, Vec::new(), None, None, None),
3179                ));
3180            }
3181        };
3182
3183        let (writer_sender, writer_receiver) = mpsc::sync_channel(1);
3184        let writer_handle = thread::Builder::new()
3185            .name("parser-supervisor-writer".to_owned())
3186            .spawn(move || writer_loop(stdin, &writer_receiver))
3187            .map_err(|source| ParserSupervisorError::IoThread {
3188                phase: "writer startup",
3189                message: source.to_string(),
3190            });
3191        let writer_handle = match writer_handle {
3192            Ok(handle) => handle,
3193            Err(error) => {
3194                return Err(attach_cleanup(
3195                    error,
3196                    cleanup_partial_launch(&mut child, Vec::new(), None, None, None),
3197                ));
3198            }
3199        };
3200        let (frame_sender, frame_events) = mpsc::sync_channel(1);
3201        let frame_handle = thread::Builder::new()
3202            .name("parser-supervisor-stdout".to_owned())
3203            .spawn(move || {
3204                frame_reader_loop(
3205                    stdout,
3206                    diagnostic_fence_writer,
3207                    diagnostic_fence,
3208                    &frame_sender,
3209                );
3210            })
3211            .map_err(|source| ParserSupervisorError::IoThread {
3212                phase: "stdout reader startup",
3213                message: source.to_string(),
3214            });
3215        let frame_handle = match frame_handle {
3216            Ok(handle) => handle,
3217            Err(error) => {
3218                drop(writer_sender);
3219                return Err(attach_cleanup(
3220                    error,
3221                    cleanup_partial_launch(&mut child, vec![writer_handle], None, None, None),
3222                ));
3223            }
3224        };
3225        let (diagnostic_sender, diagnostic_events) = mpsc::sync_channel(1);
3226        let expect_windows_admission = launch.platform == PackPlatform::WindowsX86_64;
3227        let diagnostic_handle = thread::Builder::new()
3228            .name("parser-supervisor-stderr".to_owned())
3229            .spawn(move || {
3230                diagnostic_reader_loop(
3231                    diagnostic_pipe,
3232                    expect_windows_admission,
3233                    diagnostic_fence,
3234                    &diagnostic_sender,
3235                )
3236            })
3237            .map_err(|source| ParserSupervisorError::IoThread {
3238                phase: "diagnostic reader startup",
3239                message: source.to_string(),
3240            });
3241        let diagnostic_handle = match diagnostic_handle {
3242            Ok(handle) => handle,
3243            Err(error) => {
3244                drop(writer_sender);
3245                return Err(attach_cleanup(
3246                    error,
3247                    cleanup_partial_launch(
3248                        &mut child,
3249                        vec![writer_handle, frame_handle],
3250                        None,
3251                        Some(frame_events),
3252                        None,
3253                    ),
3254                ));
3255            }
3256        };
3257        if let Err(operation) = poll_stop(
3258            PROCESS_LAUNCH_PHASE,
3259            absolute_deadline,
3260            last_progress,
3261            no_progress_timeout,
3262            cancellation,
3263        ) {
3264            drop(writer_sender);
3265            return Err(attach_cleanup(
3266                operation,
3267                cleanup_partial_launch(
3268                    &mut child,
3269                    vec![writer_handle, frame_handle],
3270                    Some(diagnostic_handle),
3271                    Some(frame_events),
3272                    Some(diagnostic_events),
3273                ),
3274            ));
3275        }
3276
3277        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3278        let (memory_observer, memory_attachment_error) =
3279            match LinuxMemoryObserver::attach(child.id(), memory_limits.process_bytes) {
3280                Ok(observer) => (observer, None),
3281                Err(source) => (
3282                    LinuxMemoryObserver::sampled_rss(memory_limits.process_bytes),
3283                    Some(source),
3284                ),
3285            };
3286        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3287        let memory_observer = Arc::new(Mutex::new(memory_observer));
3288        let mut resident = Self {
3289            child,
3290            grammar,
3291            session: session.clone(),
3292            artifact: launch.artifact.clone(),
3293            next_request_id: 1,
3294            termination_requested: false,
3295            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3296            memory_limits,
3297            writer: Some(writer_sender),
3298            writer_handle: Some(writer_handle),
3299            frame_reader: FrameReader {
3300                events: frame_events,
3301                handle: Some(frame_handle),
3302            },
3303            diagnostic_reader: DiagnosticReader {
3304                events: diagnostic_events,
3305                handle: Some(diagnostic_handle),
3306            },
3307            pending_diagnostic_fences: 0,
3308            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3309            memory_observer,
3310            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3311            memory_monitor: None,
3312        };
3313        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3314        if let Some(source) = memory_attachment_error {
3315            let operation = ParserSupervisorError::ResidentMemoryObservationFailed {
3316                phase: "delegated cgroup attachment cleanup",
3317                accounting: ParserMemoryAccountingKind::LinuxCgroupV2,
3318                message: bounded_message(source.to_string()),
3319            };
3320            return match resident.shutdown() {
3321                Ok(()) => Err(operation),
3322                Err(cleanup) => Err(ParserSupervisorError::OperationAndCleanup {
3323                    operation: Box::new(operation),
3324                    cleanup: Box::new(cleanup),
3325                }),
3326            };
3327        }
3328        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3329        match LinuxMemoryMonitor::start(resident.child.id(), Arc::clone(&resident.memory_observer))
3330        {
3331            Ok(monitor) => resident.memory_monitor = Some(monitor),
3332            Err(source) => {
3333                let operation = ParserSupervisorError::IoThread {
3334                    phase: "resident-memory monitor startup",
3335                    message: source.to_string(),
3336                };
3337                return match resident.shutdown() {
3338                    Ok(()) => Err(operation),
3339                    Err(cleanup) => Err(ParserSupervisorError::OperationAndCleanup {
3340                        operation: Box::new(operation),
3341                        cleanup: Box::new(cleanup),
3342                    }),
3343                };
3344            }
3345        }
3346        let opening: Result<(), ParserSupervisorError> = (|| {
3347            resident.wait_for_admission(
3348                absolute_deadline,
3349                last_progress,
3350                no_progress_timeout,
3351                cancellation,
3352            )?;
3353            let session_open = encode_parser_control(&ParserControl::SessionOpen(
3354                ParserSessionOpen::new(session.clone()),
3355            ))?;
3356            resident.send_bytes(
3357                session_open,
3358                "SessionOpen write",
3359                absolute_deadline,
3360                last_progress,
3361                no_progress_timeout,
3362                cancellation,
3363            )?;
3364            let ready_bytes = resident.wait_for_frame(
3365                "READY",
3366                absolute_deadline,
3367                last_progress,
3368                no_progress_timeout,
3369                cancellation,
3370            )?;
3371            let ready_frame = ParserFrame::decode_exact(&ready_bytes)?;
3372            decode_parser_ready_for_launch(ready_frame, &session, &launch.artifact, containment)?;
3373            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3374            resident.enforce_memory_bound("READY", true)?;
3375            Ok(())
3376        })();
3377        if let Err(operation) = opening {
3378            if operation.is_caller_stop() {
3379                resident.termination_requested = true;
3380            }
3381            return match resident.shutdown() {
3382                Ok(()) => Err(operation),
3383                Err(cleanup) => Err(ParserSupervisorError::OperationAndCleanup {
3384                    operation: Box::new(operation),
3385                    cleanup: Box::new(cleanup),
3386                }),
3387            };
3388        }
3389        Ok(resident)
3390    }
3391
3392    /// Send one request/source pair and validate all response identities.
3393    fn parse(
3394        &mut self,
3395        source: &[u8],
3396        source_identity: ParserSourceIdentity,
3397        limits: ParserRequestLimits,
3398        mut last_progress: Instant,
3399        absolute_deadline: Instant,
3400        no_progress_timeout: Duration,
3401        cancellation: &IndexCancellation,
3402    ) -> Result<ParserCompletionEvidence, ParserSupervisorError> {
3403        let request_id = ParserRequestIdentity::new(self.next_request_id)?;
3404        self.next_request_id = self
3405            .next_request_id
3406            .checked_add(1)
3407            .ok_or(ParserSupervisorError::RequestIdentityExhausted)?;
3408        let request = ParserRequest::new(
3409            self.session.clone(),
3410            request_id,
3411            self.artifact.clone(),
3412            self.grammar.clone(),
3413            source_identity,
3414            limits,
3415        );
3416        let mut request_bytes = encode_parser_control(&ParserControl::Request(request.clone()))?;
3417        let source_len = u32::try_from(source.len()).map_err(|_source| {
3418            ParserProtocolError::FramePayloadTooLarge {
3419                kind: ParserFrameKind::RawSource,
3420                actual: u32::MAX,
3421                maximum: ParserFrameKind::RawSource.maximum_payload_bytes(),
3422            }
3423        })?;
3424        let source_header = ParserFrameHeader::new(ParserFrameKind::RawSource, source_len)?;
3425        request_bytes.reserve(PARSER_FRAME_HEADER_BYTES.saturating_add(source.len()));
3426        request_bytes.extend_from_slice(&source_header.encode());
3427        request_bytes.extend_from_slice(source);
3428
3429        self.send_bytes(
3430            request_bytes,
3431            "request write",
3432            absolute_deadline,
3433            last_progress,
3434            no_progress_timeout,
3435            cancellation,
3436        )?;
3437        let mut previous_progress: Option<ParserProgress> = None;
3438        loop {
3439            let response_bytes = self.wait_for_frame(
3440                "request response",
3441                absolute_deadline,
3442                last_progress,
3443                no_progress_timeout,
3444                cancellation,
3445            )?;
3446            let frame = ParserFrame::decode_exact(&response_bytes)?;
3447            match frame.kind() {
3448                ParserFrameKind::Progress => {
3449                    let (progress, disposition) = decode_parser_progress_for_request(
3450                        frame,
3451                        &request,
3452                        previous_progress.as_ref(),
3453                    )?;
3454                    if disposition == ParserProgressDisposition::Advanced {
3455                        last_progress = Instant::now();
3456                    }
3457                    previous_progress = Some(progress);
3458                }
3459                ParserFrameKind::Completion => {
3460                    let completion = decode_parser_completion_for_request(frame, &request)?;
3461                    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3462                    self.enforce_memory_bound("request completion", true)?;
3463                    return Ok(completion.evidence().clone());
3464                }
3465                ParserFrameKind::Failure => {
3466                    let failure = decode_parser_failure_for_request(frame, &request)?;
3467                    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3468                    match self.enforce_memory_bound("request failure", true) {
3469                        Ok(()) | Err(ParserSupervisorError::ChildExited { code: Some(0), .. }) => {}
3470                        Err(error) => return Err(error),
3471                    }
3472                    return Err(ParserSupervisorError::WorkerFailure {
3473                        code: failure.code(),
3474                    });
3475                }
3476                kind => {
3477                    return Err(ParserProtocolError::UnexpectedFrameKind { kind }.into());
3478                }
3479            }
3480        }
3481    }
3482
3483    /// Wait until the platform adapter authorizes protocol input.
3484    fn wait_for_admission(
3485        &mut self,
3486        absolute_deadline: Instant,
3487        last_progress: Instant,
3488        no_progress_timeout: Duration,
3489        cancellation: &IndexCancellation,
3490    ) -> Result<(), ParserSupervisorError> {
3491        loop {
3492            poll_stop(
3493                "containment admission",
3494                absolute_deadline,
3495                last_progress,
3496                no_progress_timeout,
3497                cancellation,
3498            )?;
3499            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3500            self.enforce_memory_bound("containment admission", false)?;
3501            match self.diagnostic_reader.events.recv_timeout(next_poll_wait(
3502                absolute_deadline,
3503                last_progress,
3504                no_progress_timeout,
3505            )) {
3506                Ok(DiagnosticReaderEvent::AdmissionAccepted) => return Ok(()),
3507                Ok(DiagnosticReaderEvent::FenceObserved) => {
3508                    return Err(ParserSupervisorError::IoThread {
3509                        phase: "containment admission",
3510                        message: "diagnostic fence arrived before admission".to_owned(),
3511                    });
3512                }
3513                Ok(DiagnosticReaderEvent::Failure(ParserIoThreadError::AdmissionMismatch)) => {
3514                    return Err(ParserSupervisorError::InvalidAdmission);
3515                }
3516                Ok(DiagnosticReaderEvent::Failure(error)) => {
3517                    return Err(io_thread_error("containment admission", &error));
3518                }
3519                Err(RecvTimeoutError::Timeout) => self.require_child_running("admission")?,
3520                Err(RecvTimeoutError::Disconnected) => {
3521                    return Err(ParserSupervisorError::IoThread {
3522                        phase: "containment admission",
3523                        message: "diagnostic reader closed before admission".to_owned(),
3524                    });
3525                }
3526            }
3527        }
3528    }
3529
3530    /// Submit one bounded write without blocking the caller on the pipe itself.
3531    fn send_bytes(
3532        &mut self,
3533        bytes: Vec<u8>,
3534        phase: &'static str,
3535        absolute_deadline: Instant,
3536        last_progress: Instant,
3537        no_progress_timeout: Duration,
3538        cancellation: &IndexCancellation,
3539    ) -> Result<(), ParserSupervisorError> {
3540        let (acknowledgement, result) = mpsc::sync_channel(1);
3541        let mut command = WriterCommand {
3542            bytes,
3543            acknowledgement,
3544        };
3545        loop {
3546            poll_stop(
3547                phase,
3548                absolute_deadline,
3549                last_progress,
3550                no_progress_timeout,
3551                cancellation,
3552            )?;
3553            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3554            self.enforce_memory_bound(phase, false)?;
3555            let Some(writer) = self.writer.as_ref() else {
3556                return Err(ParserSupervisorError::IoThread {
3557                    phase,
3558                    message: "writer was already closed".to_owned(),
3559                });
3560            };
3561            match writer.try_send(command) {
3562                Ok(()) => break,
3563                Err(TrySendError::Full(returned)) => {
3564                    command = returned;
3565                    self.require_child_running(phase)?;
3566                    thread::sleep(next_poll_wait(
3567                        absolute_deadline,
3568                        last_progress,
3569                        no_progress_timeout,
3570                    ));
3571                }
3572                Err(TrySendError::Disconnected(_returned)) => {
3573                    return Err(ParserSupervisorError::IoThread {
3574                        phase,
3575                        message: "writer thread closed".to_owned(),
3576                    });
3577                }
3578            }
3579        }
3580        loop {
3581            poll_stop(
3582                phase,
3583                absolute_deadline,
3584                last_progress,
3585                no_progress_timeout,
3586                cancellation,
3587            )?;
3588            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3589            self.enforce_memory_bound(phase, false)?;
3590            match result.recv_timeout(next_poll_wait(
3591                absolute_deadline,
3592                last_progress,
3593                no_progress_timeout,
3594            )) {
3595                Ok(Ok(())) => return Ok(()),
3596                Ok(Err(error)) => return Err(io_thread_error(phase, &error)),
3597                Err(RecvTimeoutError::Timeout) => self.require_child_running(phase)?,
3598                Err(RecvTimeoutError::Disconnected) => {
3599                    return Err(ParserSupervisorError::IoThread {
3600                        phase,
3601                        message: "writer acknowledgement closed".to_owned(),
3602                    });
3603                }
3604            }
3605        }
3606    }
3607
3608    /// Wait for one framed response while polling every terminal condition.
3609    fn wait_for_frame(
3610        &mut self,
3611        phase: &'static str,
3612        absolute_deadline: Instant,
3613        last_progress: Instant,
3614        no_progress_timeout: Duration,
3615        cancellation: &IndexCancellation,
3616    ) -> Result<Vec<u8>, ParserSupervisorError> {
3617        loop {
3618            poll_stop(
3619                phase,
3620                absolute_deadline,
3621                last_progress,
3622                no_progress_timeout,
3623                cancellation,
3624            )?;
3625            if let Some(event) = try_frame_event(&self.frame_reader.events)? {
3626                self.synchronize_frame_event(
3627                    &event,
3628                    phase,
3629                    absolute_deadline,
3630                    last_progress,
3631                    no_progress_timeout,
3632                    cancellation,
3633                )?;
3634                return self.finish_frame_event(event, phase);
3635            }
3636            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3637            self.enforce_memory_bound(phase, false)?;
3638            self.check_diagnostic_reader(
3639                phase,
3640                absolute_deadline,
3641                last_progress,
3642                no_progress_timeout,
3643                cancellation,
3644            )?;
3645            match self.frame_reader.events.recv_timeout(next_poll_wait(
3646                absolute_deadline,
3647                last_progress,
3648                no_progress_timeout,
3649            )) {
3650                Ok(event) => {
3651                    self.synchronize_frame_event(
3652                        &event,
3653                        phase,
3654                        absolute_deadline,
3655                        last_progress,
3656                        no_progress_timeout,
3657                        cancellation,
3658                    )?;
3659                    return self.finish_frame_event(event, phase);
3660                }
3661                Err(RecvTimeoutError::Timeout) => {}
3662                Err(RecvTimeoutError::Disconnected) => {
3663                    return Err(ParserSupervisorError::IoThread {
3664                        phase,
3665                        message: "stdout reader closed".to_owned(),
3666                    });
3667                }
3668            }
3669        }
3670    }
3671
3672    /// Order one stdout event against every earlier diagnostic-pipe write.
3673    fn synchronize_frame_event(
3674        &mut self,
3675        event: &FrameReaderEvent,
3676        phase: &'static str,
3677        absolute_deadline: Instant,
3678        last_progress: Instant,
3679        no_progress_timeout: Duration,
3680        cancellation: &IndexCancellation,
3681    ) -> Result<(), ParserSupervisorError> {
3682        if matches!(event, FrameReaderEvent::EndOfStream) {
3683            self.wait_for_diagnostic_termination(
3684                phase,
3685                absolute_deadline,
3686                last_progress,
3687                no_progress_timeout,
3688                cancellation,
3689            )
3690        } else {
3691            self.wait_for_diagnostic_fence(
3692                phase,
3693                absolute_deadline,
3694                last_progress,
3695                no_progress_timeout,
3696                cancellation,
3697            )
3698        }
3699    }
3700
3701    /// Drain the diagnostic boundary before accepting clean stdout termination.
3702    fn wait_for_diagnostic_termination(
3703        &mut self,
3704        phase: &'static str,
3705        absolute_deadline: Instant,
3706        last_progress: Instant,
3707        no_progress_timeout: Duration,
3708        cancellation: &IndexCancellation,
3709    ) -> Result<(), ParserSupervisorError> {
3710        loop {
3711            poll_stop(
3712                phase,
3713                absolute_deadline,
3714                last_progress,
3715                no_progress_timeout,
3716                cancellation,
3717            )?;
3718            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3719            self.enforce_memory_bound(phase, false)?;
3720            self.check_diagnostic_reader(
3721                phase,
3722                absolute_deadline,
3723                last_progress,
3724                no_progress_timeout,
3725                cancellation,
3726            )?;
3727            if thread_finished(self.diagnostic_reader.handle.as_ref()) {
3728                self.check_diagnostic_reader(
3729                    phase,
3730                    absolute_deadline,
3731                    last_progress,
3732                    no_progress_timeout,
3733                    cancellation,
3734                )?;
3735                return Ok(());
3736            }
3737            thread::sleep(next_poll_wait(
3738                absolute_deadline,
3739                last_progress,
3740                no_progress_timeout,
3741            ));
3742        }
3743    }
3744
3745    /// Require the parent-authored stderr fence for one complete stdout frame.
3746    fn wait_for_diagnostic_fence(
3747        &mut self,
3748        phase: &'static str,
3749        absolute_deadline: Instant,
3750        last_progress: Instant,
3751        no_progress_timeout: Duration,
3752        cancellation: &IndexCancellation,
3753    ) -> Result<(), ParserSupervisorError> {
3754        if self.pending_diagnostic_fences > 0 {
3755            self.pending_diagnostic_fences = self.pending_diagnostic_fences.saturating_sub(1);
3756            return Ok(());
3757        }
3758        loop {
3759            poll_stop(
3760                phase,
3761                absolute_deadline,
3762                last_progress,
3763                no_progress_timeout,
3764                cancellation,
3765            )?;
3766            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3767            match self.enforce_memory_bound(phase, false) {
3768                Ok(()) | Err(ParserSupervisorError::ChildExited { code: Some(0), .. }) => {}
3769                Err(error) => return Err(error),
3770            }
3771            match self.diagnostic_reader.events.recv_timeout(next_poll_wait(
3772                absolute_deadline,
3773                last_progress,
3774                no_progress_timeout,
3775            )) {
3776                Ok(DiagnosticReaderEvent::FenceObserved) => return Ok(()),
3777                Ok(DiagnosticReaderEvent::Failure(error)) => {
3778                    self.termination_requested = true;
3779                    return Err(diagnostic_failure_after_exit_observation(
3780                        &mut self.child,
3781                        phase,
3782                        &error,
3783                        absolute_deadline,
3784                        last_progress,
3785                        no_progress_timeout,
3786                        cancellation,
3787                    ));
3788                }
3789                Ok(DiagnosticReaderEvent::AdmissionAccepted) | Err(RecvTimeoutError::Timeout) => {}
3790                Err(RecvTimeoutError::Disconnected) => {
3791                    return Err(ParserSupervisorError::IoThread {
3792                        phase,
3793                        message: "diagnostic reader closed before frame fence".to_owned(),
3794                    });
3795                }
3796            }
3797        }
3798    }
3799
3800    /// Convert one frame event and mark an OS-proved Windows memory exit as expected termination.
3801    fn finish_frame_event(
3802        &mut self,
3803        event: FrameReaderEvent,
3804        phase: &'static str,
3805    ) -> Result<Vec<u8>, ParserSupervisorError> {
3806        let result = frame_event_result(event, &mut self.child, phase);
3807        #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
3808        if matches!(
3809            &result,
3810            Err(ParserSupervisorError::WindowsJobMemoryLimitExceeded { .. })
3811        ) {
3812            self.termination_requested = true;
3813        }
3814        result
3815    }
3816
3817    /// Surface diagnostic bytes and retain frame fences observed ahead of stdout.
3818    fn check_diagnostic_reader(
3819        &mut self,
3820        phase: &'static str,
3821        absolute_deadline: Instant,
3822        last_progress: Instant,
3823        no_progress_timeout: Duration,
3824        cancellation: &IndexCancellation,
3825    ) -> Result<(), ParserSupervisorError> {
3826        loop {
3827            match self.diagnostic_reader.events.try_recv() {
3828                Ok(DiagnosticReaderEvent::Failure(error)) => {
3829                    self.termination_requested = true;
3830                    return Err(diagnostic_failure_after_exit_observation(
3831                        &mut self.child,
3832                        phase,
3833                        &error,
3834                        absolute_deadline,
3835                        last_progress,
3836                        no_progress_timeout,
3837                        cancellation,
3838                    ));
3839                }
3840                Ok(DiagnosticReaderEvent::FenceObserved) => {
3841                    self.pending_diagnostic_fences = self
3842                        .pending_diagnostic_fences
3843                        .checked_add(1)
3844                        .ok_or_else(|| ParserSupervisorError::IoThread {
3845                            phase,
3846                            message: "diagnostic fence count overflowed".to_owned(),
3847                        })?;
3848                }
3849                Ok(DiagnosticReaderEvent::AdmissionAccepted) => {}
3850                Err(TryRecvError::Empty | TryRecvError::Disconnected) => return Ok(()),
3851            }
3852        }
3853    }
3854
3855    /// Enforce the Linux resident-memory ceiling and terminate the worker group on failure.
3856    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3857    fn enforce_memory_bound(
3858        &mut self,
3859        phase: &'static str,
3860        force: bool,
3861    ) -> Result<(), ParserSupervisorError> {
3862        let monitor_event = self
3863            .memory_monitor
3864            .as_ref()
3865            .and_then(|monitor| monitor.events.try_recv().ok());
3866        if let Some(event) = monitor_event {
3867            return Err(self.memory_monitor_error(phase, event));
3868        }
3869        if !force {
3870            return Ok(());
3871        }
3872        let process_id = self.child.id();
3873        let observation = match self.memory_observer.lock() {
3874            Ok(mut observer) => observer.observe(process_id),
3875            Err(_poisoned) => Err(io::Error::other("Linux memory observer lock was poisoned")),
3876        };
3877        let observation = match observation {
3878            Ok(observation) => observation,
3879            Err(source) => {
3880                let observer = Arc::clone(&self.memory_observer);
3881                let child = &mut self.child;
3882                let transition = resolve_linux_memory_exit_transition(
3883                    source,
3884                    SUPERVISOR_POLL_INTERVAL,
3885                    || match observer.try_lock() {
3886                        Ok(mut observer) => observer.observe(process_id),
3887                        Err(std::sync::TryLockError::WouldBlock) => Err(io::Error::new(
3888                            io::ErrorKind::WouldBlock,
3889                            "Linux memory observer is busy",
3890                        )),
3891                        Err(std::sync::TryLockError::Poisoned(_poisoned)) => {
3892                            Err(io::Error::other("Linux memory observer lock was poisoned"))
3893                        }
3894                    },
3895                    || {
3896                        child.try_wait().map(|status| {
3897                            status.map(|status| LinuxChildExit {
3898                                code: status.code(),
3899                            })
3900                        })
3901                    },
3902                );
3903                match transition {
3904                    Ok(LinuxMemoryObservation::Memory(observation)) => observation,
3905                    Ok(LinuxMemoryObservation::ChildExited { code }) => {
3906                        return Err(ParserSupervisorError::ChildExited { phase, code });
3907                    }
3908                    Err(source) => {
3909                        self.termination_requested = true;
3910                        let operation = ParserSupervisorError::ResidentMemoryObservationFailed {
3911                            phase,
3912                            accounting: ParserMemoryAccountingKind::LinuxProcStatus,
3913                            message: bounded_message(source.to_string()),
3914                        };
3915                        return Err(attach_cleanup(
3916                            operation,
3917                            kill_direct_child(&mut self.child),
3918                        ));
3919                    }
3920                }
3921            }
3922        };
3923        let Some(breach) = observation else {
3924            return Ok(());
3925        };
3926        self.termination_requested = true;
3927        let observation_interval_millis =
3928            u64::try_from(PARSER_LINUX_RSS_OBSERVATION_INTERVAL.as_millis()).unwrap_or(u64::MAX);
3929        let operation = ParserSupervisorError::ResidentMemoryLimitExceeded {
3930            phase,
3931            accounting: breach.accounting,
3932            observed_bytes: breach.observed_bytes,
3933            maximum_bytes: self.memory_limits.process_bytes,
3934            observation_interval_millis,
3935        };
3936        Err(attach_cleanup(
3937            operation,
3938            kill_direct_child(&mut self.child),
3939        ))
3940    }
3941
3942    /// Convert one monitor-owned terminal event into the public typed failure.
3943    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
3944    fn memory_monitor_error(
3945        &mut self,
3946        phase: &'static str,
3947        event: LinuxMemoryMonitorEvent,
3948    ) -> ParserSupervisorError {
3949        let (operation, termination_error) = match event {
3950            LinuxMemoryMonitorEvent::Limit {
3951                breach,
3952                termination_error,
3953            } => {
3954                self.termination_requested = true;
3955                (
3956                    ParserSupervisorError::ResidentMemoryLimitExceeded {
3957                        phase,
3958                        accounting: breach.accounting,
3959                        observed_bytes: breach.observed_bytes,
3960                        maximum_bytes: self.memory_limits.process_bytes,
3961                        observation_interval_millis: u64::try_from(
3962                            PARSER_LINUX_RSS_OBSERVATION_INTERVAL.as_millis(),
3963                        )
3964                        .unwrap_or(u64::MAX),
3965                    },
3966                    termination_error,
3967                )
3968            }
3969            LinuxMemoryMonitorEvent::ObservationFailed {
3970                accounting,
3971                message,
3972            } => {
3973                let transition = resolve_linux_memory_exit_transition(
3974                    io::Error::other(message.clone()),
3975                    SUPERVISOR_POLL_INTERVAL,
3976                    || Err(io::Error::other(message.clone())),
3977                    || {
3978                        self.child.try_wait().map(|status| {
3979                            status.map(|status| LinuxChildExit {
3980                                code: status.code(),
3981                            })
3982                        })
3983                    },
3984                );
3985                if let Ok(LinuxMemoryObservation::ChildExited { code }) = transition {
3986                    return ParserSupervisorError::ChildExited { phase, code };
3987                }
3988                self.termination_requested = true;
3989                return attach_cleanup(
3990                    ParserSupervisorError::ResidentMemoryObservationFailed {
3991                        phase,
3992                        accounting,
3993                        message,
3994                    },
3995                    kill_direct_child(&mut self.child),
3996                );
3997            }
3998        };
3999        let Some(termination_error) = termination_error else {
4000            return operation;
4001        };
4002        let retry = kill_direct_child(&mut self.child);
4003        let initial = ParserSupervisorError::Cleanup {
4004            message: format!(
4005                "continuous memory monitor could not terminate the worker process group: {termination_error}"
4006            ),
4007        };
4008        let cleanup = match retry {
4009            Ok(()) => initial,
4010            Err(retry) => ParserSupervisorError::OperationAndCleanup {
4011                operation: Box::new(initial),
4012                cleanup: Box::new(retry),
4013            },
4014        };
4015        ParserSupervisorError::OperationAndCleanup {
4016            operation: Box::new(operation),
4017            cleanup: Box::new(cleanup),
4018        }
4019    }
4020
4021    /// Retain the first memory failure while shutdown continues mandatory cleanup.
4022    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4023    fn observe_shutdown_memory(&mut self, failure: &mut Option<ParserSupervisorError>) {
4024        if failure.is_some() {
4025            return;
4026        }
4027        match self.enforce_memory_bound("shutdown", true) {
4028            Ok(()) | Err(ParserSupervisorError::ChildExited { .. }) => {}
4029            Err(error) => *failure = Some(error),
4030        }
4031    }
4032
4033    /// Require that the direct child has not exited.
4034    fn require_child_running(&mut self, phase: &'static str) -> Result<(), ParserSupervisorError> {
4035        match self
4036            .child
4037            .try_wait()
4038            .map_err(|source| ParserSupervisorError::IoThread {
4039                phase,
4040                message: source.to_string(),
4041            })? {
4042            None => Ok(()),
4043            Some(status) => Err(ParserSupervisorError::ChildExited {
4044                phase,
4045                code: status.code(),
4046            }),
4047        }
4048    }
4049
4050    /// Close input, terminate if needed, reap, drain, and join exactly once.
4051    fn shutdown(mut self) -> Result<(), ParserSupervisorError> {
4052        let mut failures = Vec::new();
4053        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4054        let mut memory_failure = None;
4055        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4056        if let Some(mut monitor) = self.memory_monitor.take() {
4057            match monitor.stop() {
4058                Ok(Some(event)) => {
4059                    memory_failure = Some(self.memory_monitor_error("idle resident", event));
4060                }
4061                Ok(None) => {}
4062                Err(error) => failures.push(error.to_string()),
4063            }
4064        }
4065        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4066        self.observe_shutdown_memory(&mut memory_failure);
4067        self.writer.take();
4068        let graceful_deadline = Instant::now()
4069            .checked_add(SUPERVISOR_GRACEFUL_CLOSE)
4070            .unwrap_or_else(Instant::now);
4071        while self
4072            .writer_handle
4073            .as_ref()
4074            .is_some_and(|handle| !handle.is_finished())
4075            && Instant::now() < graceful_deadline
4076        {
4077            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4078            self.observe_shutdown_memory(&mut memory_failure);
4079            drain_reader_events(&self.frame_reader.events, &self.diagnostic_reader.events);
4080            thread::sleep(SUPERVISOR_POLL_INTERVAL);
4081        }
4082        let mut forced = self.termination_requested;
4083        let mut status = match self.child.try_wait() {
4084            Ok(status) => status,
4085            Err(source) => {
4086                failures.push(source.to_string());
4087                None
4088            }
4089        };
4090        while status.is_none() && Instant::now() < graceful_deadline {
4091            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4092            self.observe_shutdown_memory(&mut memory_failure);
4093            drain_reader_events(&self.frame_reader.events, &self.diagnostic_reader.events);
4094            thread::sleep(SUPERVISOR_POLL_INTERVAL);
4095            match self.child.try_wait() {
4096                Ok(observed) => status = observed,
4097                Err(source) => {
4098                    failures.push(source.to_string());
4099                    break;
4100                }
4101            }
4102        }
4103        if status.is_none() {
4104            forced = true;
4105            if let Err(error) = kill_direct_child(&mut self.child) {
4106                failures.push(error.to_string());
4107            }
4108        }
4109        let cleanup_deadline = Instant::now()
4110            .checked_add(SUPERVISOR_CLEANUP_TIMEOUT)
4111            .unwrap_or_else(Instant::now);
4112        while status.is_none() && Instant::now() < cleanup_deadline {
4113            drain_reader_events(&self.frame_reader.events, &self.diagnostic_reader.events);
4114            thread::sleep(SUPERVISOR_POLL_INTERVAL);
4115            match self.child.try_wait() {
4116                Ok(observed) => status = observed,
4117                Err(source) => {
4118                    failures.push(source.to_string());
4119                    break;
4120                }
4121            }
4122        }
4123        if status.is_none() {
4124            failures.push("direct child was not reaped within the cleanup deadline".to_owned());
4125        }
4126        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4127        if status.is_some() {
4128            let cleanup = match self.memory_observer.lock() {
4129                Ok(mut observer) => observer.cleanup(),
4130                Err(_poisoned) => Err(io::Error::other(
4131                    "Linux memory observer lock was poisoned during cleanup",
4132                )),
4133            };
4134            if let Err(source) = cleanup {
4135                failures.push(format!(
4136                    "delegated Linux memory-accounting cleanup failed: {source}"
4137                ));
4138            }
4139        }
4140
4141        while (!thread_finished(self.writer_handle.as_ref())
4142            || !thread_finished(self.frame_reader.handle.as_ref())
4143            || !thread_finished(self.diagnostic_reader.handle.as_ref()))
4144            && Instant::now() < cleanup_deadline
4145        {
4146            drain_reader_events(&self.frame_reader.events, &self.diagnostic_reader.events);
4147            thread::sleep(SUPERVISOR_POLL_INTERVAL);
4148        }
4149        if !thread_finished(self.writer_handle.as_ref())
4150            || !thread_finished(self.frame_reader.handle.as_ref())
4151            || !thread_finished(self.diagnostic_reader.handle.as_ref())
4152        {
4153            failures
4154                .push("protocol I/O threads did not drain within the cleanup deadline".to_owned());
4155        }
4156        if thread_finished(self.writer_handle.as_ref())
4157            && let Err(error) = join_unit_thread(self.writer_handle.take(), "writer")
4158        {
4159            failures.push(error.to_string());
4160        }
4161        if thread_finished(self.frame_reader.handle.as_ref())
4162            && let Err(error) = join_unit_thread(self.frame_reader.handle.take(), "stdout reader")
4163        {
4164            failures.push(error.to_string());
4165        }
4166        let diagnostics = if thread_finished(self.diagnostic_reader.handle.as_ref()) {
4167            match join_diagnostic_thread(self.diagnostic_reader.handle.take()) {
4168                Ok(diagnostics) => diagnostics,
4169                Err(error) => {
4170                    failures.push(error.to_string());
4171                    Vec::new()
4172                }
4173            }
4174        } else {
4175            Vec::new()
4176        };
4177        forced |= self.termination_requested;
4178        if !forced && status.as_ref().is_some_and(|status| !status.success()) {
4179            failures.push(format!(
4180                "direct child reported failed cleanup; diagnostic={}",
4181                bounded_diagnostic(&diagnostics)
4182            ));
4183        }
4184        if !forced && status.as_ref().is_some_and(ExitStatus::success) && !diagnostics.is_empty() {
4185            failures.push(format!(
4186                "direct child emitted unexpected diagnostic bytes: {}",
4187                bounded_diagnostic(&diagnostics)
4188            ));
4189        }
4190        let cleanup = (!failures.is_empty()).then(|| ParserSupervisorError::Cleanup {
4191            message: bounded_message(failures.join("; ")),
4192        });
4193        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4194        if let Some(operation) = memory_failure {
4195            return match cleanup {
4196                Some(cleanup) => Err(ParserSupervisorError::OperationAndCleanup {
4197                    operation: Box::new(operation),
4198                    cleanup: Box::new(cleanup),
4199                }),
4200                None => Err(operation),
4201            };
4202        }
4203        match cleanup {
4204            None => Ok(()),
4205            Some(cleanup) => Err(cleanup),
4206        }
4207    }
4208}
4209
4210/// Inherit only the sealed Linux authority descriptors needed after `exec`.
4211#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4212#[expect(
4213    unsafe_code,
4214    reason = "Command has no safe descriptor-sanitizing hook; this pre-exec closure performs only async-signal-safe Linux syscalls"
4215)]
4216fn inherit_linux_authority_on_exec(command: &mut Command, authority: LinuxResidentLaunchAuthority) {
4217    use nix::libc;
4218    use std::os::unix::process::CommandExt;
4219
4220    let inherited = [
4221        authority.artifact_manifest.raw_fd(),
4222        authority.accepted_manifest.raw_fd(),
4223        authority.native_import_policy.raw_fd(),
4224        authority.grammar.raw_fd(),
4225    ];
4226    // SAFETY: `pre_exec` runs after fork. The closure retains every source
4227    // descriptor and performs only allocation-free `close_range` and `fcntl`
4228    // syscalls. Parent descriptors remain CLOEXEC, so concurrent spawns cannot
4229    // inherit them. Rust's spawn-error pipe remains CLOEXEC until successful exec.
4230    unsafe {
4231        command.pre_exec(move || {
4232            let _authority_guard = &authority;
4233            let result = libc::syscall(
4234                libc::SYS_close_range,
4235                3_u32,
4236                u32::MAX,
4237                libc::CLOSE_RANGE_CLOEXEC | libc::CLOSE_RANGE_UNSHARE,
4238            );
4239            if result != 0 {
4240                return Err(io::Error::last_os_error());
4241            }
4242            for descriptor in inherited {
4243                if libc::fcntl(descriptor, libc::F_SETFD, 0) != 0 {
4244                    return Err(io::Error::last_os_error());
4245                }
4246            }
4247            Ok(())
4248        });
4249    }
4250}
4251
4252/// Build the one accepted platform command with closed arguments and environment.
4253#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4254fn platform_command(
4255    launch: &VerifiedParserPackLaunch,
4256    authority: LinuxResidentLaunchAuthority,
4257    _memory_limits: ParserMemoryLimits,
4258) -> Result<Command, ParserSupervisorError> {
4259    use std::os::unix::process::CommandExt;
4260
4261    if launch.platform != PackPlatform::LinuxX86_64 {
4262        return Err(ParserSupervisorError::PayloadMismatch {
4263            path: launch.pack_root.clone(),
4264            reason: "Linux supervisor received another platform artifact",
4265        });
4266    }
4267    let worker_fd = authority.worker.raw_fd();
4268    let artifact_fd = authority.artifact_manifest.raw_fd();
4269    let accepted_fd = authority.accepted_manifest.raw_fd();
4270    let policy_fd = authority.native_import_policy.raw_fd();
4271    let grammar_fd = authority.grammar.raw_fd();
4272    let mut command = Command::new(format!("/proc/self/fd/{worker_fd}"));
4273    command
4274        .arg(SERVE_ARGUMENT)
4275        .arg(ARTIFACT_FD_ARGUMENT)
4276        .arg(artifact_fd.to_string())
4277        .arg(ACCEPTED_FD_ARGUMENT)
4278        .arg(accepted_fd.to_string())
4279        .arg(POLICY_FD_ARGUMENT)
4280        .arg(policy_fd.to_string())
4281        .arg(GRAMMAR_FD_ARGUMENT)
4282        .arg(grammar_fd.to_string())
4283        .current_dir(&launch.pack_root)
4284        .env_clear()
4285        .stdin(Stdio::piped())
4286        .stdout(Stdio::piped())
4287        .stderr(Stdio::piped())
4288        .process_group(0);
4289    inherit_linux_authority_on_exec(&mut command, authority);
4290    Ok(command)
4291}
4292
4293/// Build the one accepted Windows broker command with closed arguments and environment.
4294#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
4295fn platform_command(
4296    launch: &VerifiedParserPackLaunch,
4297    memory_limits: ParserMemoryLimits,
4298) -> Result<Command, ParserSupervisorError> {
4299    use std::os::windows::process::CommandExt;
4300
4301    const CREATE_NO_WINDOW: u32 = 0x0800_0000;
4302    if launch.platform != PackPlatform::WindowsX86_64 {
4303        return Err(ParserSupervisorError::PayloadMismatch {
4304            path: launch.pack_root.clone(),
4305            reason: "Windows supervisor received another platform artifact",
4306        });
4307    }
4308    let broker = launch.containment_broker.as_ref().ok_or_else(|| {
4309        ParserSupervisorError::PayloadMismatch {
4310            path: launch.pack_root.clone(),
4311            reason: "Windows artifact has no containment broker",
4312        }
4313    })?;
4314    let mut command = Command::new(broker);
4315    command
4316        .arg(BROKER_SERVE_ARGUMENT)
4317        .arg("--parent-pid")
4318        .arg(std::process::id().to_string())
4319        .arg("--process-memory-bytes")
4320        .arg(memory_limits.process_bytes.to_string())
4321        .arg("--job-memory-bytes")
4322        .arg(memory_limits.process_tree_bytes.to_string())
4323        .current_dir(&launch.pack_root)
4324        .env_clear()
4325        .stdin(Stdio::piped())
4326        .stdout(Stdio::piped())
4327        .stderr(Stdio::piped())
4328        .creation_flags(CREATE_NO_WINDOW);
4329    Ok(command)
4330}
4331
4332/// Refuse command construction on every unaccepted optional-pack target.
4333#[cfg(not(any(
4334    all(target_os = "linux", target_arch = "x86_64"),
4335    all(target_os = "windows", target_arch = "x86_64")
4336)))]
4337fn platform_command(
4338    _launch: &VerifiedParserPackLaunch,
4339    _memory_limits: ParserMemoryLimits,
4340) -> Result<Command, ParserSupervisorError> {
4341    Err(ParserSupervisorError::UnsupportedContainment {
4342        os: std::env::consts::OS,
4343        architecture: std::env::consts::ARCH,
4344    })
4345}
4346
4347/// Return the READY containment identity for one closed artifact target.
4348const fn containment_for_platform(platform: PackPlatform) -> ParserContainmentKind {
4349    match platform {
4350        PackPlatform::LinuxX86_64 => ParserContainmentKind::LinuxLandlockSeccomp,
4351        PackPlatform::WindowsX86_64 => ParserContainmentKind::WindowsAppContainerJob,
4352    }
4353}
4354
4355/// Generate a fresh session identity from operating-system entropy.
4356fn fresh_session_identity() -> Result<ParserSessionIdentity, ParserSupervisorError> {
4357    let mut entropy = [0_u8; PARSER_SESSION_ENTROPY_BYTES];
4358    getrandom::fill(&mut entropy).map_err(|_source| ParserSupervisorError::EntropyUnavailable)?;
4359    Ok(ParserSessionIdentity::for_entropy(&entropy))
4360}
4361
4362/// Generate one parent-only marker that a worker cannot forge before a frame.
4363fn fresh_diagnostic_fence() -> Result<DiagnosticFence, ParserSupervisorError> {
4364    let mut entropy = [0_u8; PARSER_DIAGNOSTIC_FENCE_BYTES];
4365    getrandom::fill(&mut entropy).map_err(|_source| ParserSupervisorError::EntropyUnavailable)?;
4366    Ok(DiagnosticFence(entropy))
4367}
4368
4369/// Convert a private I/O thread failure at the public typed boundary.
4370fn io_thread_error(phase: &'static str, error: &ParserIoThreadError) -> ParserSupervisorError {
4371    ParserSupervisorError::IoThread {
4372        phase,
4373        message: bounded_message(error.to_string()),
4374    }
4375}
4376
4377/// Preserve fail-closed diagnostics while allowing the Windows broker to prove a memory exit.
4378fn diagnostic_failure_after_exit_observation(
4379    child: &mut Child,
4380    phase: &'static str,
4381    error: &ParserIoThreadError,
4382    absolute_deadline: Instant,
4383    last_progress: Instant,
4384    no_progress_timeout: Duration,
4385    cancellation: &IndexCancellation,
4386) -> ParserSupervisorError {
4387    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
4388    {
4389        let no_progress_deadline = last_progress
4390            .checked_add(no_progress_timeout)
4391            .unwrap_or(absolute_deadline);
4392        let observation_deadline = absolute_deadline.min(no_progress_deadline);
4393        loop {
4394            match child.try_wait() {
4395                Ok(Some(status))
4396                    if status.code() == Some(PARSER_WINDOWS_BROKER_MEMORY_LIMIT_EXIT_CODE) =>
4397                {
4398                    return ParserSupervisorError::WindowsJobMemoryLimitExceeded { phase };
4399                }
4400                Ok(Some(_)) | Err(_) => break,
4401                Ok(None) => {}
4402            }
4403            let now = Instant::now();
4404            if cancellation.is_cancelled() || now >= observation_deadline {
4405                break;
4406            }
4407            thread::sleep(SUPERVISOR_POLL_INTERVAL.min(observation_deadline.duration_since(now)));
4408        }
4409    }
4410    #[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
4411    let _ = (
4412        child,
4413        absolute_deadline,
4414        last_progress,
4415        no_progress_timeout,
4416        cancellation,
4417    );
4418    io_thread_error(phase, error)
4419}
4420
4421/// Bound one internal diagnostic without splitting UTF-8.
4422fn bounded_message(mut message: String) -> String {
4423    let mut end = message.len().min(PARSER_MAX_STDERR_BYTES);
4424    while !message.is_char_boundary(end) {
4425        end = end.saturating_sub(1);
4426    }
4427    message.truncate(end);
4428    message
4429}
4430
4431/// Render one already bounded diagnostic byte stream safely.
4432fn bounded_diagnostic(bytes: &[u8]) -> String {
4433    bounded_message(String::from_utf8_lossy(bytes).into_owned())
4434}
4435
4436/// Poll cancellation, the immutable absolute deadline, and meaningful progress age.
4437fn poll_stop(
4438    phase: &'static str,
4439    absolute_deadline: Instant,
4440    last_progress: Instant,
4441    no_progress_timeout: Duration,
4442    cancellation: &IndexCancellation,
4443) -> Result<(), ParserSupervisorError> {
4444    if cancellation.is_cancelled() {
4445        return Err(ParserSupervisorError::Cancelled { phase });
4446    }
4447    let now = Instant::now();
4448    if now >= absolute_deadline {
4449        return Err(ParserSupervisorError::DeadlineExceeded { phase });
4450    }
4451    if now.saturating_duration_since(last_progress) >= no_progress_timeout {
4452        return Err(ParserSupervisorError::NoProgress { phase });
4453    }
4454    Ok(())
4455}
4456
4457/// Return the next short wait that preserves every caller-owned bound.
4458fn next_poll_wait(
4459    absolute_deadline: Instant,
4460    last_progress: Instant,
4461    no_progress_timeout: Duration,
4462) -> Duration {
4463    let now = Instant::now();
4464    let deadline_wait = absolute_deadline.saturating_duration_since(now);
4465    let progress_wait =
4466        no_progress_timeout.saturating_sub(now.saturating_duration_since(last_progress));
4467    SUPERVISOR_POLL_INTERVAL
4468        .min(deadline_wait)
4469        .min(progress_wait)
4470}
4471
4472/// Take one already buffered frame event before observing child exit state.
4473fn try_frame_event(
4474    events: &Receiver<FrameReaderEvent>,
4475) -> Result<Option<FrameReaderEvent>, ParserSupervisorError> {
4476    match events.try_recv() {
4477        Ok(event) => Ok(Some(event)),
4478        Err(TryRecvError::Empty) => Ok(None),
4479        Err(TryRecvError::Disconnected) => Err(ParserSupervisorError::IoThread {
4480            phase: "stdout reader",
4481            message: "stdout reader closed without a terminal event".to_owned(),
4482        }),
4483    }
4484}
4485
4486/// Convert one owned frame-reader event at the synchronous request boundary.
4487fn frame_event_result(
4488    event: FrameReaderEvent,
4489    child: &mut Child,
4490    phase: &'static str,
4491) -> Result<Vec<u8>, ParserSupervisorError> {
4492    match event {
4493        FrameReaderEvent::Frame(frame) => Ok(frame),
4494        FrameReaderEvent::EndOfStream => {
4495            let status = wait_for_observed_exit(child, SUPERVISOR_POLL_INTERVAL)?;
4496            let code = status.and_then(|status| status.code());
4497            #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
4498            if code == Some(PARSER_WINDOWS_BROKER_MEMORY_LIMIT_EXIT_CODE) {
4499                return Err(ParserSupervisorError::WindowsJobMemoryLimitExceeded { phase });
4500            }
4501            Err(ParserSupervisorError::ChildExited { phase, code })
4502        }
4503        FrameReaderEvent::Failure(error) => Err(io_thread_error(phase, &error)),
4504    }
4505}
4506
4507/// Observe a direct-child exit for one short bounded interval.
4508fn wait_for_observed_exit(
4509    child: &mut Child,
4510    timeout: Duration,
4511) -> Result<Option<ExitStatus>, ParserSupervisorError> {
4512    let deadline = Instant::now()
4513        .checked_add(timeout)
4514        .unwrap_or_else(Instant::now);
4515    loop {
4516        if let Some(status) =
4517            child
4518                .try_wait()
4519                .map_err(|source| ParserSupervisorError::IoThread {
4520                    phase: "child exit observation",
4521                    message: source.to_string(),
4522                })?
4523        {
4524            return Ok(Some(status));
4525        }
4526        if Instant::now() >= deadline {
4527            return Ok(None);
4528        }
4529        thread::sleep(Duration::from_millis(1));
4530    }
4531}
4532
4533/// Terminate the complete Linux worker group.
4534#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4535fn terminate_linux_process_group(process_id: u32) -> Result<LinuxProcessGroupTermination, String> {
4536    use nix::errno::Errno;
4537    use nix::sys::signal::{Signal, killpg};
4538    use nix::unistd::Pid;
4539
4540    let process_group = i32::try_from(process_id)
4541        .map_err(|_source| "worker process-group identity exceeds i32".to_owned())?;
4542    match killpg(Pid::from_raw(process_group), Signal::SIGKILL) {
4543        Ok(()) => Ok(LinuxProcessGroupTermination::Signalled),
4544        Err(Errno::ESRCH) => Ok(LinuxProcessGroupTermination::Absent),
4545        Err(source) => Err(source.to_string()),
4546    }
4547}
4548
4549/// Terminate the complete Linux worker group and fall back to the direct child.
4550#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
4551fn kill_direct_child(child: &mut Child) -> Result<(), ParserSupervisorError> {
4552    if child
4553        .try_wait()
4554        .map_err(|source| ParserSupervisorError::Cleanup {
4555            message: source.to_string(),
4556        })?
4557        .is_some()
4558    {
4559        return Ok(());
4560    }
4561    match terminate_linux_process_group(child.id()) {
4562        Ok(LinuxProcessGroupTermination::Signalled) => Ok(()),
4563        Ok(LinuxProcessGroupTermination::Absent) => {
4564            if child
4565                .try_wait()
4566                .map_err(|source| ParserSupervisorError::Cleanup {
4567                    message: source.to_string(),
4568                })?
4569                .is_none()
4570            {
4571                child
4572                    .kill()
4573                    .map_err(|source| ParserSupervisorError::Cleanup {
4574                        message: format!(
4575                            "worker process group was absent and direct-child termination failed: {source}"
4576                        ),
4577                    })?;
4578            }
4579            Ok(())
4580        }
4581        Err(group_error) => {
4582            let direct_error = child.kill().err().map(|error| error.to_string());
4583            Err(ParserSupervisorError::Cleanup {
4584                message: direct_error.map_or_else(
4585                    || format!("process-group termination failed: {group_error}"),
4586                    |direct_error| {
4587                        format!(
4588                            "process-group termination failed: {group_error}; direct-child termination failed: {direct_error}"
4589                        )
4590                    },
4591                ),
4592            })
4593        }
4594    }
4595}
4596
4597/// Terminate the direct Windows broker or unsupported-host child.
4598#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
4599fn kill_direct_child(child: &mut Child) -> Result<(), ParserSupervisorError> {
4600    if child
4601        .try_wait()
4602        .map_err(|source| ParserSupervisorError::Cleanup {
4603            message: source.to_string(),
4604        })?
4605        .is_none()
4606    {
4607        child
4608            .kill()
4609            .map_err(|source| ParserSupervisorError::Cleanup {
4610                message: source.to_string(),
4611            })?;
4612    }
4613    Ok(())
4614}
4615
4616/// Reap an incompletely constructed launch without an unbounded process wait.
4617fn cleanup_partial_launch(
4618    child: &mut Child,
4619    handles: Vec<JoinHandle<()>>,
4620    diagnostic_handle: Option<JoinHandle<Result<Vec<u8>, ParserIoThreadError>>>,
4621    frame_events: Option<Receiver<FrameReaderEvent>>,
4622    diagnostic_events: Option<Receiver<DiagnosticReaderEvent>>,
4623) -> Result<(), ParserSupervisorError> {
4624    drop(frame_events);
4625    drop(diagnostic_events);
4626    let mut failures = Vec::new();
4627    if let Err(error) = kill_direct_child(child) {
4628        failures.push(error.to_string());
4629    }
4630    let deadline = Instant::now()
4631        .checked_add(SUPERVISOR_CLEANUP_TIMEOUT)
4632        .unwrap_or_else(Instant::now);
4633    let mut reaped = false;
4634    while Instant::now() < deadline {
4635        match child.try_wait() {
4636            Ok(Some(_status)) => reaped = true,
4637            Ok(None) => {}
4638            Err(source) => {
4639                failures.push(source.to_string());
4640                break;
4641            }
4642        }
4643        if reaped
4644            && handles.iter().all(JoinHandle::is_finished)
4645            && thread_finished(diagnostic_handle.as_ref())
4646        {
4647            break;
4648        }
4649        thread::sleep(SUPERVISOR_POLL_INTERVAL);
4650    }
4651    if !reaped {
4652        failures.push("incomplete direct child was not reaped".to_owned());
4653    }
4654    for handle in handles {
4655        if !handle.is_finished() {
4656            failures.push("incomplete launch thread did not terminate".to_owned());
4657        } else if handle.join().is_err() {
4658            failures.push("incomplete launch thread panicked".to_owned());
4659        }
4660    }
4661    if thread_finished(diagnostic_handle.as_ref()) {
4662        if let Err(error) = join_diagnostic_thread(diagnostic_handle) {
4663            failures.push(error.to_string());
4664        }
4665    } else if diagnostic_handle.is_some() {
4666        failures.push("incomplete diagnostic thread did not terminate".to_owned());
4667    }
4668    if failures.is_empty() {
4669        Ok(())
4670    } else {
4671        Err(ParserSupervisorError::Cleanup {
4672            message: bounded_message(failures.join("; ")),
4673        })
4674    }
4675}
4676
4677/// Preserve an operation failure together with any mandatory cleanup failure.
4678fn attach_cleanup(
4679    operation: ParserSupervisorError,
4680    cleanup: Result<(), ParserSupervisorError>,
4681) -> ParserSupervisorError {
4682    match cleanup {
4683        Ok(()) => operation,
4684        Err(cleanup) => ParserSupervisorError::OperationAndCleanup {
4685            operation: Box::new(operation),
4686            cleanup: Box::new(cleanup),
4687        },
4688    }
4689}
4690
4691/// Discard bounded reader events so capacity-one senders can finish during cleanup.
4692fn drain_reader_events(
4693    frames: &Receiver<FrameReaderEvent>,
4694    diagnostics: &Receiver<DiagnosticReaderEvent>,
4695) {
4696    while frames.try_recv().is_ok() {}
4697    while diagnostics.try_recv().is_ok() {}
4698}
4699
4700/// Return whether one optional owned thread has terminated.
4701fn thread_finished<T>(handle: Option<&JoinHandle<T>>) -> bool {
4702    handle.is_none_or(JoinHandle::is_finished)
4703}
4704
4705/// Join one unit-returning thread and reject a panic.
4706fn join_unit_thread(
4707    handle: Option<JoinHandle<()>>,
4708    name: &'static str,
4709) -> Result<(), ParserSupervisorError> {
4710    let Some(handle) = handle else {
4711        return Ok(());
4712    };
4713    handle
4714        .join()
4715        .map_err(|_panic| ParserSupervisorError::Cleanup {
4716            message: format!("{name} thread panicked"),
4717        })
4718}
4719
4720/// Join the diagnostic thread and retain its bounded bytes.
4721fn join_diagnostic_thread(
4722    handle: Option<JoinHandle<Result<Vec<u8>, ParserIoThreadError>>>,
4723) -> Result<Vec<u8>, ParserSupervisorError> {
4724    let Some(handle) = handle else {
4725        return Ok(Vec::new());
4726    };
4727    let result = handle
4728        .join()
4729        .map_err(|_panic| ParserSupervisorError::Cleanup {
4730            message: "diagnostic reader thread panicked".to_owned(),
4731        })?;
4732    result.map_err(|error| ParserSupervisorError::Cleanup {
4733        message: bounded_message(error.to_string()),
4734    })
4735}
4736
4737/// Synchronous owner of the one process-wide optional parser session.
4738pub struct OptionalParserSupervisor {
4739    /// Exact artifact root revalidated after observed mutation.
4740    pack_root: PathBuf,
4741    /// Current verified launch authority.
4742    launch: VerifiedParserPackLaunch,
4743    /// Worker and process-tree ceilings applied to every resident session.
4744    memory_limits: ParserMemoryLimits,
4745    /// Current grammar-affined child session, when healthy.
4746    resident: Option<ResidentParserSession>,
4747}
4748
4749impl OptionalParserSupervisor {
4750    /// Open and verify one installed immutable optional-parser artifact.
4751    ///
4752    /// This performs no process creation. Unsupported hosts fail before artifact
4753    /// acquisition by higher layers, worker launch, or source transfer.
4754    ///
4755    /// # Errors
4756    ///
4757    /// Returns a typed unsupported-host, path, manifest, payload, or digest error.
4758    pub fn open(pack_root: impl AsRef<Path>) -> Result<Self, ParserSupervisorError> {
4759        Self::open_with_memory_limits(pack_root, ParserMemoryLimits::PRODUCTION)
4760    }
4761
4762    /// Open one verified artifact with caller-owned release-probe ceilings.
4763    fn open_with_memory_limits(
4764        pack_root: impl AsRef<Path>,
4765        memory_limits: ParserMemoryLimits,
4766    ) -> Result<Self, ParserSupervisorError> {
4767        let pack_root = pack_root.as_ref().to_path_buf();
4768        let launch = VerifiedParserPackLaunch::load(&pack_root)?;
4769        Ok(Self {
4770            pack_root,
4771            launch,
4772            memory_limits: memory_limits.checked()?,
4773            resident: None,
4774        })
4775    }
4776
4777    /// Borrow the exact artifact-manifest identity verified during open.
4778    #[must_use]
4779    pub(crate) const fn artifact_identity(&self) -> &ParserArtifactIdentity {
4780        &self.launch.artifact
4781    }
4782
4783    /// Borrow the canonical root bound to this verified launch authority.
4784    #[must_use]
4785    pub(crate) fn pack_root(&self) -> &Path {
4786        &self.pack_root
4787    }
4788
4789    /// Return whether the verified artifact accepts one canonical language identity.
4790    #[must_use]
4791    pub(crate) fn accepts_language(&self, language_id: &str) -> bool {
4792        self.launch
4793            .accepted_grammars
4794            .binary_search_by(|candidate| candidate.as_str().cmp(language_id))
4795            .is_ok()
4796    }
4797
4798    /// Parse bounded raw source through one grammar-affined contained worker.
4799    ///
4800    /// `absolute_deadline` is never extended by progress. One pre-READY epoch
4801    /// covers currentness, reload, sealing, admission, and opening. A newly
4802    /// validated READY or later identity-validated advancing progress resets
4803    /// `no_progress_timeout`.
4804    /// `cancellation` is polled while waiting for admission, writes, and output.
4805    ///
4806    /// # Errors
4807    ///
4808    /// Returns a typed artifact, grammar, containment, protocol, worker,
4809    /// cancellation, timeout, I/O, or mandatory cleanup failure. Any failed
4810    /// operation destroys the resident session before returning.
4811    pub fn parse(
4812        &mut self,
4813        language_id: &str,
4814        source: &[u8],
4815        limits: ParserRequestLimits,
4816        absolute_deadline: Instant,
4817        no_progress_timeout: Duration,
4818        cancellation: &IndexCancellation,
4819    ) -> Result<ParserCompletionEvidence, ParserSupervisorError> {
4820        let last_progress = Instant::now();
4821        poll_stop(
4822            "request admission",
4823            absolute_deadline,
4824            last_progress,
4825            no_progress_timeout,
4826            cancellation,
4827        )?;
4828        let source_identity = ParserSourceIdentity::for_bytes(source)?;
4829        self.refresh_changed_artifact(
4830            language_id,
4831            last_progress,
4832            absolute_deadline,
4833            no_progress_timeout,
4834            cancellation,
4835        )?;
4836        if let Some(resident) = self.resident.as_ref() {
4837            let grammar_changed = self
4838                .launch
4839                .require_grammar(language_id)
4840                .map_or(true, |grammar| resident.grammar != grammar);
4841            if grammar_changed {
4842                self.shutdown_resident()?;
4843            }
4844        }
4845        let mut resident_opened = false;
4846        if self.resident.is_none() {
4847            let grammar = self.launch.require_grammar(language_id)?;
4848            self.resident = Some(ResidentParserSession::launch(
4849                &self.launch,
4850                grammar,
4851                self.memory_limits,
4852                last_progress,
4853                absolute_deadline,
4854                no_progress_timeout,
4855                cancellation,
4856            )?);
4857            resident_opened = true;
4858        }
4859        let request_last_progress = if resident_opened {
4860            Instant::now()
4861        } else {
4862            last_progress
4863        };
4864        let result = self
4865            .resident
4866            .as_mut()
4867            .ok_or_else(|| ParserSupervisorError::IoThread {
4868                phase: "request admission",
4869                message: "resident session was not retained".to_owned(),
4870            })?
4871            .parse(
4872                source,
4873                source_identity,
4874                limits,
4875                request_last_progress,
4876                absolute_deadline,
4877                no_progress_timeout,
4878                cancellation,
4879            );
4880        match result {
4881            Ok(evidence) => Ok(evidence),
4882            Err(operation) => {
4883                if operation.is_caller_stop()
4884                    && let Some(resident) = self.resident.as_mut()
4885                {
4886                    resident.termination_requested = true;
4887                }
4888                match self.take_and_shutdown_resident() {
4889                    Ok(()) => Err(operation),
4890                    Err(cleanup) => Err(ParserSupervisorError::OperationAndCleanup {
4891                        operation: Box::new(operation),
4892                        cleanup: Box::new(cleanup),
4893                    }),
4894                }
4895            }
4896        }
4897    }
4898
4899    /// Close, drain, and reap the resident session when one exists.
4900    ///
4901    /// # Errors
4902    ///
4903    /// Returns a typed cleanup failure when the direct child or an owned I/O
4904    /// thread cannot be verified as terminated within the cleanup deadline.
4905    pub fn shutdown(&mut self) -> Result<(), ParserSupervisorError> {
4906        self.shutdown_resident()
4907    }
4908
4909    /// Replace launch authority only after observed artifact mutation.
4910    fn refresh_changed_artifact(
4911        &mut self,
4912        language_id: &str,
4913        last_progress: Instant,
4914        absolute_deadline: Instant,
4915        no_progress_timeout: Duration,
4916        cancellation: &IndexCancellation,
4917    ) -> Result<(), ParserSupervisorError> {
4918        let control = ArtifactIoControl {
4919            absolute_deadline,
4920            last_progress,
4921            no_progress_timeout,
4922            cancellation,
4923        };
4924        let probe = self.launch.currentness_probe(language_id);
4925        let current = match run_bounded_artifact_currentness(probe, &control) {
4926            Ok(current) => current,
4927            Err(operation) => {
4928                return Err(attach_cleanup(operation, self.take_and_shutdown_resident()));
4929            }
4930        };
4931        if current {
4932            return Ok(());
4933        }
4934        self.shutdown_resident()?;
4935        let refreshed = VerifiedParserPackLaunch::load_controlled(
4936            &self.pack_root,
4937            language_id,
4938            last_progress,
4939            absolute_deadline,
4940            no_progress_timeout,
4941            cancellation,
4942        )?;
4943        self.replace_verified_launch(refreshed)
4944    }
4945
4946    /// Replace launch observations only when the content-addressed artifact identity is unchanged.
4947    fn replace_verified_launch(
4948        &mut self,
4949        refreshed: VerifiedParserPackLaunch,
4950    ) -> Result<(), ParserSupervisorError> {
4951        if refreshed.artifact != self.launch.artifact {
4952            return Err(ParserSupervisorError::PayloadMismatch {
4953                path: self.pack_root.join(ARTIFACT_MANIFEST_FILE_NAME),
4954                reason: "artifact identity changed inside its immutable slot",
4955            });
4956        }
4957        self.launch = refreshed;
4958        Ok(())
4959    }
4960
4961    /// Close the current resident session and clear its grammar affinity.
4962    fn shutdown_resident(&mut self) -> Result<(), ParserSupervisorError> {
4963        self.take_and_shutdown_resident()
4964    }
4965
4966    /// Take and terminate the current session exactly once.
4967    fn take_and_shutdown_resident(&mut self) -> Result<(), ParserSupervisorError> {
4968        match self.resident.take() {
4969            Some(resident) => resident.shutdown(),
4970            None => Ok(()),
4971        }
4972    }
4973}
4974
4975/// Exercise the exact packaged worker under a deliberately reduced memory ceiling.
4976///
4977/// Release verification first admits every grammar under the production ceilings. This
4978/// differential probe then reuses the same supervisor and platform adapter with a smaller
4979/// ceiling, requires the OS-specific memory failure, and accepts the result only when mandatory
4980/// process-tree cleanup also succeeds.
4981///
4982/// # Errors
4983///
4984/// Returns the first artifact, containment, protocol, cleanup, or unexpected probe outcome.
4985pub fn probe_optional_parser_memory_boundary(
4986    pack_root: impl AsRef<Path>,
4987    logical: &OptionalParserPackManifest,
4988) -> Result<ParserPackMemoryProbe, ParserSupervisorError> {
4989    let pack_root = pack_root.as_ref();
4990    logical.validate()?;
4991    let grammar =
4992        logical
4993            .grammars()
4994            .first()
4995            .ok_or_else(|| ParserSupervisorError::PayloadMismatch {
4996                path: pack_root.to_path_buf(),
4997                reason: "optional parser manifest has no grammar for the memory probe",
4998            })?;
4999    let platform = host_pack_platform().ok_or(ParserSupervisorError::UnsupportedContainment {
5000        os: std::env::consts::OS,
5001        architecture: std::env::consts::ARCH,
5002    })?;
5003    let process_bytes = match platform {
5004        PackPlatform::LinuxX86_64 => OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES,
5005        PackPlatform::WindowsX86_64 => OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES,
5006    };
5007    let memory_limits = ParserMemoryLimits {
5008        process_bytes,
5009        process_tree_bytes: process_bytes,
5010    }
5011    .checked()?;
5012    let probe_source = memory_probe_source(platform, grammar.fixtures.positive.source.as_bytes());
5013    let mut supervisor =
5014        OptionalParserSupervisor::open_with_memory_limits(pack_root, memory_limits)?;
5015    let limits = ParserRequestLimits::new(
5016        PARSER_MAX_OUTPUT_BYTES,
5017        PARSER_MAX_NODE_COUNT,
5018        PARSER_MAX_TREE_DEPTH,
5019    )?;
5020    let deadline = Instant::now()
5021        .checked_add(ARTIFACT_ADMISSION_TIMEOUT)
5022        .ok_or(ParserSupervisorError::DeadlineExceeded {
5023            phase: "memory probe deadline calculation",
5024        })?;
5025    let operation = supervisor.parse(
5026        &grammar.language_id,
5027        &probe_source,
5028        limits,
5029        deadline,
5030        ARTIFACT_ADMISSION_NO_PROGRESS_TIMEOUT,
5031        &IndexCancellation::new(),
5032    );
5033    let cleanup = supervisor.shutdown();
5034    let failure = match operation {
5035        Ok(_evidence) => match cleanup {
5036            Ok(()) => {
5037                return Err(ParserSupervisorError::MemoryProbeDidNotBreach { process_bytes });
5038            }
5039            Err(error) => error,
5040        },
5041        Err(error) => attach_cleanup(error, cleanup),
5042    };
5043
5044    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5045    if let ParserSupervisorError::ResidentMemoryLimitExceeded {
5046        accounting,
5047        observed_bytes,
5048        maximum_bytes,
5049        observation_interval_millis,
5050        ..
5051    } = &failure
5052    {
5053        let (control, interval, peak, overshoot) = match *accounting {
5054            ParserMemoryAccountingKind::LinuxCgroupV2 => {
5055                (ParserPackMemoryControl::LinuxCgroupV2, None, None, None)
5056            }
5057            ParserMemoryAccountingKind::LinuxProcStatus => (
5058                ParserPackMemoryControl::LinuxProcStatus,
5059                Some(*observation_interval_millis),
5060                Some(*observed_bytes),
5061                Some(observed_bytes.saturating_sub(*maximum_bytes)),
5062            ),
5063        };
5064        return Ok(ParserPackMemoryProbe {
5065            control,
5066            process_limit_bytes: *maximum_bytes,
5067            process_tree_limit_bytes: memory_limits.process_tree_bytes,
5068            observation_interval_millis: interval,
5069            peak_observed_bytes: peak,
5070            maximum_observed_overshoot_bytes: overshoot,
5071            limit_enforced: ParserPackVerifiedControl::Verified,
5072            process_tree_cleaned: ParserPackVerifiedControl::Verified,
5073        });
5074    }
5075
5076    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
5077    if matches!(
5078        &failure,
5079        ParserSupervisorError::WindowsJobMemoryLimitExceeded { .. }
5080    ) {
5081        return Ok(ParserPackMemoryProbe {
5082            control: ParserPackMemoryControl::WindowsJobObject,
5083            process_limit_bytes: memory_limits.process_bytes,
5084            process_tree_limit_bytes: memory_limits.process_tree_bytes,
5085            observation_interval_millis: None,
5086            peak_observed_bytes: None,
5087            maximum_observed_overshoot_bytes: None,
5088            limit_enforced: ParserPackVerifiedControl::Verified,
5089            process_tree_cleaned: ParserPackVerifiedControl::Verified,
5090        });
5091    }
5092
5093    Err(failure)
5094}
5095
5096/// Keep Linux's startup probe small while forcing post-admission allocation on Windows.
5097fn memory_probe_source(platform: PackPlatform, fixture: &[u8]) -> Vec<u8> {
5098    match platform {
5099        PackPlatform::LinuxX86_64 => fixture.to_vec(),
5100        PackPlatform::WindowsX86_64 => fixture
5101            .iter()
5102            .copied()
5103            .cycle()
5104            .take(WINDOWS_MEMORY_PROBE_SOURCE_BYTES.min(PARSER_MAX_SOURCE_BYTES as usize))
5105            .collect(),
5106    }
5107}
5108
5109/// Admit every accepted grammar through its exact positive and negative fixtures.
5110///
5111/// The supplied supervisor must have been opened from the same artifact represented by
5112/// `logical`. One grammar-affined session is reused for its fixture pair, and every session
5113/// is explicitly shut down before this function returns.
5114///
5115/// # Errors
5116///
5117/// Returns the first artifact, containment, protocol, fixture-expectation, worker, timeout,
5118/// cancellation, or cleanup failure. When fixture execution and mandatory shutdown both fail,
5119/// both typed failures are retained in [`ParserSupervisorError::OperationAndCleanup`].
5120pub fn admit_optional_parser_artifact(
5121    mut supervisor: OptionalParserSupervisor,
5122    logical: &OptionalParserPackManifest,
5123) -> Result<(), ParserSupervisorError> {
5124    let cancellation = IndexCancellation::new();
5125    let limits = ParserRequestLimits::new(
5126        PARSER_MAX_OUTPUT_BYTES,
5127        PARSER_MAX_NODE_COUNT,
5128        PARSER_MAX_TREE_DEPTH,
5129    )?;
5130    let aggregate_deadline = Instant::now()
5131        .checked_add(ARTIFACT_ADMISSION_AGGREGATE_TIMEOUT)
5132        .ok_or(ParserSupervisorError::DeadlineExceeded {
5133            phase: "artifact admission aggregate deadline calculation",
5134        })?;
5135    let operation = (|| {
5136        for grammar in logical.grammars() {
5137            let grammar_deadline = Instant::now()
5138                .checked_add(ARTIFACT_ADMISSION_TIMEOUT)
5139                .ok_or(ParserSupervisorError::DeadlineExceeded {
5140                    phase: "artifact admission deadline calculation",
5141                })?;
5142            let deadline = grammar_deadline.min(aggregate_deadline);
5143            for (fixture, expected) in [
5144                (&grammar.fixtures.positive, false),
5145                (&grammar.fixtures.negative, true),
5146            ] {
5147                let evidence = supervisor.parse(
5148                    &grammar.language_id,
5149                    fixture.source.as_bytes(),
5150                    limits,
5151                    deadline,
5152                    ARTIFACT_ADMISSION_NO_PROGRESS_TIMEOUT,
5153                    &cancellation,
5154                )?;
5155                let actual = evidence.root_has_error();
5156                if actual != expected {
5157                    return Err(ParserSupervisorError::FixtureExpectationMismatch {
5158                        language_id: grammar.language_id.clone(),
5159                        case_name: fixture.case_name.clone(),
5160                        actual,
5161                        expected,
5162                    });
5163                }
5164            }
5165        }
5166        Ok(())
5167    })();
5168    let cleanup = supervisor.shutdown();
5169    match operation {
5170        Ok(()) => cleanup,
5171        Err(operation) => Err(attach_cleanup(operation, cleanup)),
5172    }
5173}
5174
5175impl Drop for OptionalParserSupervisor {
5176    fn drop(&mut self) {
5177        drop(self.take_and_shutdown_resident());
5178    }
5179}
5180
5181/// Execute the process-owning supervisor against the test-only hostile protocol peer.
5182#[cfg(test)]
5183#[allow(dead_code)]
5184pub(crate) fn run_adversarial_process_suite(peer: &Path) -> io::Result<()> {
5185    #[derive(Clone, Copy)]
5186    enum ExpectedFailure {
5187        Cancelled,
5188        Deadline(&'static str),
5189        InvalidAdmission,
5190        Io,
5191        NoProgress,
5192        Progress(&'static str),
5193        Ready(&'static str),
5194        Response(&'static str),
5195        Limit(&'static str),
5196        InvalidControl(ParserFrameKind),
5197        Worker(ParserFailureCode),
5198    }
5199
5200    const RECOVERY_ALLOWANCE_PROBE_AGE: Duration = Duration::from_millis(550);
5201
5202    struct Case {
5203        scenario: &'static str,
5204        expected: ExpectedFailure,
5205        source_bytes: usize,
5206        cancel_before_launch: bool,
5207        cancellation_after_launch: Option<Duration>,
5208        deadline: Duration,
5209        deadline_after_launch: Option<Duration>,
5210        no_progress: Duration,
5211        limits: ParserRequestLimits,
5212    }
5213
5214    fn default_limits() -> io::Result<ParserRequestLimits> {
5215        ParserRequestLimits::new(4 * 1024, 16, 16)
5216            .map_err(|error| io::Error::other(error.to_string()))
5217    }
5218
5219    fn case(scenario: &'static str, expected: ExpectedFailure) -> io::Result<Case> {
5220        Ok(Case {
5221            scenario,
5222            expected,
5223            source_bytes: 32,
5224            cancel_before_launch: false,
5225            cancellation_after_launch: None,
5226            deadline: Duration::from_secs(2),
5227            deadline_after_launch: None,
5228            no_progress: Duration::from_millis(500),
5229            limits: default_limits()?,
5230        })
5231    }
5232
5233    fn error_matches(error: &ParserSupervisorError, expected: ExpectedFailure) -> bool {
5234        match (error, expected) {
5235            (ParserSupervisorError::Cancelled { .. }, ExpectedFailure::Cancelled)
5236            | (ParserSupervisorError::InvalidAdmission, ExpectedFailure::InvalidAdmission)
5237            | (ParserSupervisorError::IoThread { .. }, ExpectedFailure::Io)
5238            | (ParserSupervisorError::NoProgress { .. }, ExpectedFailure::NoProgress) => true,
5239            (
5240                ParserSupervisorError::DeadlineExceeded { .. },
5241                ExpectedFailure::Deadline(expected_phase),
5242            ) => adversarial_deadline_matches(error, expected_phase),
5243            (
5244                ParserSupervisorError::Protocol {
5245                    source: ParserProtocolError::ProgressRegression { field },
5246                },
5247                ExpectedFailure::Progress(expected),
5248            )
5249            | (
5250                ParserSupervisorError::Protocol {
5251                    source: ParserProtocolError::ReadyIdentityMismatch { field },
5252                },
5253                ExpectedFailure::Ready(expected),
5254            )
5255            | (
5256                ParserSupervisorError::Protocol {
5257                    source: ParserProtocolError::ResponseIdentityMismatch { field },
5258                },
5259                ExpectedFailure::Response(expected),
5260            )
5261            | (
5262                ParserSupervisorError::Protocol {
5263                    source: ParserProtocolError::RequestLimitExceeded { field, .. },
5264                },
5265                ExpectedFailure::Limit(expected),
5266            ) => field == &expected,
5267            (ParserSupervisorError::WorkerFailure { code }, ExpectedFailure::Worker(expected)) => {
5268                code == &expected
5269            }
5270            (
5271                ParserSupervisorError::Protocol {
5272                    source: ParserProtocolError::InvalidControlJson { kind, .. },
5273                },
5274                ExpectedFailure::InvalidControl(expected),
5275            ) => kind == &expected,
5276            _ => false,
5277        }
5278    }
5279
5280    fn command_for(peer: &Path, scenario: &str) -> io::Result<Command> {
5281        let current_dir = peer
5282            .parent()
5283            .ok_or_else(|| io::Error::other("hostile peer path has no parent"))?;
5284        let mut command = Command::new(peer);
5285        command
5286            .arg("--peer")
5287            .arg(scenario)
5288            .current_dir(current_dir)
5289            .env_clear()
5290            .stdin(Stdio::piped())
5291            .stdout(Stdio::piped())
5292            .stderr(Stdio::piped());
5293        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5294        {
5295            use std::os::unix::process::CommandExt;
5296
5297            command.process_group(0);
5298        }
5299        #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
5300        {
5301            use std::os::windows::process::CommandExt;
5302
5303            command.creation_flags(0x0800_0000);
5304        }
5305        Ok(command)
5306    }
5307
5308    fn test_launch(peer: &Path) -> io::Result<VerifiedParserPackLaunch> {
5309        let pack_root = peer
5310            .parent()
5311            .ok_or_else(|| io::Error::other("hostile peer path has no parent"))?
5312            .to_path_buf();
5313        let platform = host_pack_platform()
5314            .ok_or_else(|| io::Error::other("host has no optional-parser containment target"))?;
5315        Ok(VerifiedParserPackLaunch {
5316            pack_root: pack_root.clone(),
5317            platform,
5318            #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
5319            containment_broker: Some(peer.to_path_buf()),
5320            accepted_grammars: vec!["hostile".to_owned()],
5321            artifact: ParserArtifactIdentity::for_bytes(b"parser-supervisor-hostile-peer"),
5322            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5323            artifact_manifest_bytes: b"parser-supervisor-hostile-peer".to_vec(),
5324            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5325            accepted_manifest_bytes: Vec::new(),
5326            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5327            native_import_policy_bytes: Vec::new(),
5328            artifact_manifest: FileObservation::unavailable(
5329                pack_root.join(ARTIFACT_MANIFEST_FILE_NAME),
5330            ),
5331            payloads: Vec::new(),
5332            currentness_blocker: None,
5333        })
5334    }
5335
5336    fn require_reused_resident_payload_revalidation(peer: &Path) -> io::Result<()> {
5337        let temp = tempfile::tempdir()?;
5338        let accepted_bytes = b"accepted-manifest";
5339        fs::write(
5340            temp.path().join(ARTIFACT_MANIFEST_FILE_NAME),
5341            b"parser-supervisor-hostile-peer",
5342        )?;
5343        fs::write(
5344            temp.path().join(ACCEPTED_MANIFEST_FILE_NAME),
5345            accepted_bytes,
5346        )?;
5347        let payload_path = temp.path().join("hostile-grammar");
5348        fs::write(&payload_path, b"trusted")?;
5349        let modified = fs::metadata(&payload_path)?.modified()?;
5350
5351        let mut launch = test_launch(peer)?;
5352        launch.pack_root = temp.path().to_path_buf();
5353        launch.artifact_manifest =
5354            FileObservation::capture(temp.path().join(ARTIFACT_MANIFEST_FILE_NAME))
5355                .map_err(|error| io::Error::other(error.to_string()))?;
5356        launch.payloads = vec![PayloadObservation {
5357            file: FileObservation::capture(payload_path.clone())
5358                .map_err(|error| io::Error::other(error.to_string()))?,
5359            role: ParserPackPayloadRole::GrammarLibrary {
5360                language_id: "hostile".to_owned(),
5361            },
5362            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5363            bytes: 7,
5364            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5365            sha256: encode_sha256(Sha256::digest(b"trusted")),
5366        }];
5367        let grammar = ParserLanguageIdentity::new("hostile")
5368            .map_err(|error| io::Error::other(error.to_string()))?;
5369        let cancellation = IndexCancellation::new();
5370        let deadline = Instant::now()
5371            .checked_add(Duration::from_secs(2))
5372            .ok_or_else(|| io::Error::other("resident reuse deadline overflow"))?;
5373        let resident = ResidentParserSession::launch_command(
5374            &launch,
5375            grammar,
5376            ParserMemoryLimits::PRODUCTION,
5377            Instant::now(),
5378            deadline,
5379            ADVERSARIAL_NON_STALL_LAUNCH_NO_PROGRESS,
5380            &cancellation,
5381            command_for(peer, "idle-close")?,
5382        )
5383        .map_err(|error| {
5384            io::Error::other(format!(
5385                "resident reuse test launch failed before mutation: {error:?}"
5386            ))
5387        })?;
5388        let mut supervisor = OptionalParserSupervisor {
5389            pack_root: temp.path().to_path_buf(),
5390            launch,
5391            memory_limits: ParserMemoryLimits::PRODUCTION,
5392            resident: Some(resident),
5393        };
5394
5395        let (entered_sender, entered_receiver) = mpsc::sync_channel(1);
5396        let (release_sender, release_receiver) = mpsc::sync_channel(1);
5397        supervisor.launch.currentness_blocker = Some(std::sync::Arc::new(MetadataProbeBlocker {
5398            entered: entered_sender,
5399            release: std::sync::Mutex::new(release_receiver),
5400        }));
5401        let blocked_cancellation = IndexCancellation::new();
5402        let caller_cancellation = blocked_cancellation.clone();
5403        let blocked_deadline = Instant::now()
5404            .checked_add(Duration::from_secs(5))
5405            .ok_or_else(|| io::Error::other("blocked currentness deadline overflow"))?;
5406        let blocked_limits = default_limits()?;
5407        let (result_sender, result_receiver) = mpsc::sync_channel(1);
5408        let caller = thread::spawn(move || {
5409            let result = supervisor.parse(
5410                "hostile",
5411                &[b'x'; 32],
5412                blocked_limits,
5413                blocked_deadline,
5414                Duration::from_secs(5),
5415                &caller_cancellation,
5416            );
5417            let _send_result = result_sender.send((result, supervisor));
5418        });
5419        entered_receiver
5420            .recv_timeout(Duration::from_secs(1))
5421            .map_err(|source| io::Error::other(source.to_string()))?;
5422        blocked_cancellation.cancel();
5423        let (blocked_result, returned_supervisor) = result_receiver
5424            .recv_timeout(Duration::from_secs(1))
5425            .map_err(|source| io::Error::other(source.to_string()))?;
5426        if !matches!(
5427            blocked_result,
5428            Err(ParserSupervisorError::Cancelled {
5429                phase: ARTIFACT_IO_PHASE
5430            })
5431        ) {
5432            return Err(io::Error::other(format!(
5433                "blocked currentness returned the wrong result: {blocked_result:?}"
5434            )));
5435        }
5436        if returned_supervisor.resident.is_some() {
5437            return Err(io::Error::other(
5438                "blocked currentness retained the canceled resident",
5439            ));
5440        }
5441        if ArtifactIoLease::acquire().is_ok() {
5442            return Err(io::Error::other(
5443                "blocked currentness admitted a second artifact reader",
5444            ));
5445        }
5446        release_sender
5447            .send(())
5448            .map_err(|_closed| io::Error::other("metadata-probe release receiver closed"))?;
5449        caller
5450            .join()
5451            .map_err(|_panic| io::Error::other("blocked currentness caller panicked"))?;
5452        let permit_deadline = Instant::now() + Duration::from_secs(1);
5453        while ARTIFACT_IO_ACTIVE.load(Ordering::Acquire) && Instant::now() < permit_deadline {
5454            thread::yield_now();
5455        }
5456        if ARTIFACT_IO_ACTIVE.load(Ordering::Acquire) {
5457            return Err(io::Error::other(
5458                "blocked currentness did not release the artifact reader",
5459            ));
5460        }
5461
5462        supervisor = returned_supervisor;
5463        supervisor.launch.currentness_blocker = None;
5464        let cancellation = IndexCancellation::new();
5465        let deadline = Instant::now()
5466            .checked_add(Duration::from_secs(2))
5467            .ok_or_else(|| io::Error::other("resident reuse deadline overflow"))?;
5468        let grammar = ParserLanguageIdentity::new("hostile")
5469            .map_err(|error| io::Error::other(error.to_string()))?;
5470        supervisor.resident = Some(
5471            ResidentParserSession::launch_command(
5472                &supervisor.launch,
5473                grammar,
5474                ParserMemoryLimits::PRODUCTION,
5475                Instant::now(),
5476                deadline,
5477                ADVERSARIAL_NON_STALL_LAUNCH_NO_PROGRESS,
5478                &cancellation,
5479                command_for(peer, "idle-close")?,
5480            )
5481            .map_err(|error| {
5482                io::Error::other(format!(
5483                    "resident reuse test relaunch failed after blocked currentness: {error:?}"
5484                ))
5485            })?,
5486        );
5487
5488        let mutation = fs::write(&payload_path, b"mutated");
5489        #[cfg(windows)]
5490        if mutation.is_err() {
5491            if fs::read(&payload_path)? != b"trusted" {
5492                return Err(io::Error::other(
5493                    "Windows write guard reported failure after changing payload bytes",
5494                ));
5495            }
5496            supervisor
5497                .shutdown()
5498                .map_err(|error| io::Error::other(error.to_string()))?;
5499            return Ok(());
5500        }
5501        mutation?;
5502        File::options()
5503            .write(true)
5504            .open(&payload_path)?
5505            .set_times(fs::FileTimes::new().set_modified(modified))?;
5506        if fs::metadata(&payload_path)?.len() != 7
5507            || fs::metadata(&payload_path)?.modified()? != modified
5508        {
5509            return Err(io::Error::other(
5510                "resident reuse mutation did not preserve size and modification time",
5511            ));
5512        }
5513
5514        let source = vec![b'x'; 32];
5515        let operation = supervisor.parse(
5516            "hostile",
5517            &source,
5518            default_limits()?,
5519            deadline,
5520            Duration::from_millis(150),
5521            &cancellation,
5522        );
5523        let Err(error) = operation else {
5524            return Err(io::Error::other(
5525                "mutated launch payload was accepted by a reused resident",
5526            ));
5527        };
5528        if error.has_mandatory_cleanup_failure() {
5529            return Err(io::Error::other(format!(
5530                "mutated launch payload did not cleanly destroy the resident: {error:?}"
5531            )));
5532        }
5533        if supervisor.resident.is_some() {
5534            return Err(io::Error::other(
5535                "mutated launch payload retained the resident session",
5536            ));
5537        }
5538        Ok(())
5539    }
5540
5541    fn operate(
5542        peer: &Path,
5543        case: &Case,
5544    ) -> Result<ParserCompletionEvidence, ParserSupervisorError> {
5545        let launch = test_launch(peer).map_err(|source| ParserSupervisorError::IoThread {
5546            phase: "adversarial test launch",
5547            message: source.to_string(),
5548        })?;
5549        let grammar = ParserLanguageIdentity::new("hostile")?;
5550        let cancellation = IndexCancellation::new();
5551        if case.cancel_before_launch {
5552            cancellation.cancel();
5553        }
5554        let now = Instant::now();
5555        let deadline = now.checked_add(case.deadline).unwrap_or(now);
5556        let resident = ResidentParserSession::launch_command(
5557            &launch,
5558            grammar,
5559            ParserMemoryLimits::PRODUCTION,
5560            now,
5561            deadline,
5562            adversarial_launch_no_progress(case.scenario, case.no_progress),
5563            &cancellation,
5564            command_for(peer, case.scenario).map_err(|source| ParserSupervisorError::IoThread {
5565                phase: "adversarial test command",
5566                message: source.to_string(),
5567            })?,
5568        );
5569        let cancellation_thread;
5570        let result = match resident {
5571            Ok(mut resident) => {
5572                let source = vec![b'x'; case.source_bytes];
5573                let source_identity = ParserSourceIdentity::for_bytes(&source)?;
5574                let operation_started = Instant::now();
5575                let operation_deadline = case
5576                    .deadline_after_launch
5577                    .and_then(|duration| operation_started.checked_add(duration))
5578                    .unwrap_or(deadline);
5579                cancellation_thread = case.cancellation_after_launch.map(|delay| {
5580                    let cancellation = cancellation.clone();
5581                    thread::spawn(move || {
5582                        thread::sleep(delay);
5583                        cancellation.cancel();
5584                    })
5585                });
5586                let operation = resident.parse(
5587                    &source,
5588                    source_identity,
5589                    case.limits,
5590                    operation_started,
5591                    operation_deadline,
5592                    case.no_progress,
5593                    &cancellation,
5594                );
5595                match operation {
5596                    Ok(evidence) => resident.shutdown().map(|()| evidence),
5597                    Err(operation) => {
5598                        if operation.is_caller_stop() {
5599                            resident.termination_requested = true;
5600                        }
5601                        Err(attach_cleanup(operation, resident.shutdown()))
5602                    }
5603                }
5604            }
5605            Err(error) => {
5606                cancellation_thread = None;
5607                Err(error)
5608            }
5609        };
5610        if let Some(handle) = cancellation_thread {
5611            handle
5612                .join()
5613                .map_err(|_panic| ParserSupervisorError::Cleanup {
5614                    message: "adversarial cancellation thread panicked".to_owned(),
5615                })?;
5616        }
5617        result
5618    }
5619
5620    fn require_failure(peer: &Path, hostile: &Case) -> io::Result<()> {
5621        let Err(error) = operate(peer, hostile) else {
5622            return Err(io::Error::other(format!(
5623                "hostile scenario {} unexpectedly succeeded",
5624                hostile.scenario
5625            )));
5626        };
5627        if error.has_mandatory_cleanup_failure() {
5628            return Err(io::Error::other(format!(
5629                "hostile scenario {} did not reap and join cleanly: {error:?}",
5630                hostile.scenario
5631            )));
5632        }
5633        if !error_matches(&error, hostile.expected) {
5634            return Err(io::Error::other(format!(
5635                "hostile scenario {} returned the wrong typed failure: {error:?}",
5636                hostile.scenario
5637            )));
5638        }
5639
5640        let mut healthy = case("healthy", ExpectedFailure::Io)?;
5641        healthy.no_progress = healthy.deadline;
5642        if hostile.scenario == "pre-ready-stall"
5643            && !(hostile.no_progress < RECOVERY_ALLOWANCE_PROBE_AGE
5644                && RECOVERY_ALLOWANCE_PROBE_AGE < healthy.no_progress)
5645        {
5646            return Err(io::Error::other(format!(
5647                "controlled recovery age {:?} must exceed hostile allowance {:?} and remain below healthy allowance {:?}",
5648                RECOVERY_ALLOWANCE_PROBE_AGE, hostile.no_progress, healthy.no_progress
5649            )));
5650        }
5651        let cleanup_deadline = Instant::now() + healthy.deadline;
5652        while PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) && Instant::now() < cleanup_deadline {
5653            thread::yield_now();
5654        }
5655        if PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) {
5656            return Err(io::Error::other(format!(
5657                "hostile scenario {} did not finish late process cleanup",
5658                hostile.scenario
5659            )));
5660        }
5661        require_process_spawn_cleanup_health()
5662            .map_err(|error| io::Error::other(error.to_string()))?;
5663        let evidence = operate(peer, &healthy).map_err(|error| {
5664            io::Error::other(format!(
5665                "healthy restart after {} failed: {error:?}",
5666                hostile.scenario
5667            ))
5668        })?;
5669        if evidence.root_kind().as_str() != "source_file" {
5670            return Err(io::Error::other("healthy restart returned other evidence"));
5671        }
5672        Ok(())
5673    }
5674
5675    require_reused_resident_payload_revalidation(peer)?;
5676
5677    let mut cases = vec![
5678        {
5679            let mut opening_cancel = case("opening-cancel", ExpectedFailure::Cancelled)?;
5680            opening_cancel.cancel_before_launch = true;
5681            opening_cancel
5682        },
5683        case("pre-ready-stall", ExpectedFailure::NoProgress)?,
5684        case("ready-session", ExpectedFailure::Ready("session"))?,
5685        case("ready-artifact", ExpectedFailure::Ready("artifact"))?,
5686        case("ready-containment", ExpectedFailure::Ready("containment"))?,
5687        case(
5688            "ready-malformed",
5689            ExpectedFailure::InvalidControl(ParserFrameKind::Ready),
5690        )?,
5691        case("ready-truncated", ExpectedFailure::Io)?,
5692        case("ready-oversized", ExpectedFailure::Io)?,
5693        case("progress-session", ExpectedFailure::Response("session"))?,
5694        case("progress-request", ExpectedFailure::Response("request_id"))?,
5695        case("progress-duplicate", ExpectedFailure::Progress("sequence"))?,
5696        case("progress-gap", ExpectedFailure::Progress("sequence"))?,
5697        case(
5698            "progress-regression",
5699            ExpectedFailure::Progress("completed_work"),
5700        )?,
5701        case(
5702            "progress-endless",
5703            ExpectedFailure::Deadline("request response"),
5704        )?,
5705        case("progress-no-work", ExpectedFailure::NoProgress)?,
5706        case(
5707            "completion-malformed",
5708            ExpectedFailure::InvalidControl(ParserFrameKind::Completion),
5709        )?,
5710        case("completion-truncated", ExpectedFailure::Io)?,
5711        case("completion-oversized", ExpectedFailure::Io)?,
5712        case(
5713            "failure-exit",
5714            ExpectedFailure::Worker(ParserFailureCode::ParseRejected),
5715        )?,
5716        case("stderr-flood", ExpectedFailure::Io)?,
5717        case("stderr-completion", ExpectedFailure::Io)?,
5718        case(
5719            "limit-output",
5720            ExpectedFailure::Limit("completion.output_bytes"),
5721        )?,
5722        case(
5723            "limit-source",
5724            ExpectedFailure::Limit("evidence.root_end_byte"),
5725        )?,
5726        case(
5727            "limit-nodes",
5728            ExpectedFailure::Limit("evidence.named_node_count"),
5729        )?,
5730        case(
5731            "limit-depth",
5732            ExpectedFailure::Limit("evidence.maximum_depth"),
5733        )?,
5734    ];
5735    let mut blocked_cancel = case("blocked-write", ExpectedFailure::Cancelled)?;
5736    blocked_cancel.source_bytes = 4 * 1024 * 1024;
5737    blocked_cancel.cancellation_after_launch = Some(Duration::from_millis(75));
5738    blocked_cancel.no_progress = Duration::from_secs(1);
5739    cases.push(blocked_cancel);
5740    let mut blocked_deadline = case("blocked-write", ExpectedFailure::Deadline("request write"))?;
5741    blocked_deadline.source_bytes = 4 * 1024 * 1024;
5742    blocked_deadline.deadline_after_launch = Some(Duration::from_millis(125));
5743    blocked_deadline.no_progress = Duration::from_secs(1);
5744    cases.push(blocked_deadline);
5745    if let Some(progress_endless) = cases
5746        .iter_mut()
5747        .find(|candidate| candidate.scenario == "progress-endless")
5748    {
5749        progress_endless.deadline_after_launch = Some(Duration::from_millis(250));
5750        progress_endless.no_progress = Duration::from_secs(1);
5751    }
5752    if let Some(output_limit) = cases
5753        .iter_mut()
5754        .find(|candidate| candidate.scenario == "limit-output")
5755    {
5756        output_limit.limits = ParserRequestLimits::new(64, 16, 16)
5757            .map_err(|error| io::Error::other(error.to_string()))?;
5758    }
5759
5760    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
5761    cases.extend([
5762        case("admission-forged", ExpectedFailure::InvalidAdmission)?,
5763        case("admission-truncated", ExpectedFailure::Io)?,
5764        case("admission-stall", ExpectedFailure::NoProgress)?,
5765        case("admission-flood", ExpectedFailure::Io)?,
5766    ]);
5767
5768    for hostile in &cases {
5769        require_failure(peer, hostile)?;
5770    }
5771    Ok(())
5772}
5773
5774#[cfg(test)]
5775mod tests {
5776    //! Protect bounded framing, backpressure, stop polling, and response identity.
5777
5778    use std::io::Cursor;
5779    use std::sync::Arc;
5780
5781    use projectatlas_core::optional_parser_protocol::{
5782        PARSER_MAX_SOURCE_BYTES, PARSER_PROTOCOL_VERSION, PARSER_WINDOWS_BROKER_ADMISSION_RECORD,
5783        ParserCompletion, ParserContentDigest, ParserResponseIdentity, ParserSyntaxKind,
5784    };
5785
5786    use super::*;
5787
5788    /// Serializes tests that deliberately hold the process-wide artifact-I/O lease.
5789    static ARTIFACT_IO_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
5790    /// Serializes the one test that deliberately blocks process creation.
5791    static PROCESS_SPAWN_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
5792    /// Marks the subprocess branch of the blocked-spawn ownership test.
5793    const PROCESS_SPAWN_CHILD_ENV: &str = "PROJECTATLAS_PROCESS_SPAWN_CHILD";
5794    /// File written only if an abandoned child survives its mandatory cleanup.
5795    const PROCESS_SPAWN_CHILD_COMPLETED_ENV: &str = "PROJECTATLAS_PROCESS_SPAWN_CHILD_COMPLETED";
5796
5797    /// Restores process-wide spawn health after an injected sticky-failure test.
5798    struct ProcessSpawnCleanupFailureReset;
5799
5800    impl Drop for ProcessSpawnCleanupFailureReset {
5801        fn drop(&mut self) {
5802            if let Ok(mut slot) = PROCESS_SPAWN_CLEANUP_FAILURE.lock() {
5803                *slot = None;
5804            }
5805        }
5806    }
5807
5808    /// Clear any one-shot process-spawn race hooks left by an early test return.
5809    struct ProcessSpawnTestHookReset;
5810
5811    impl Drop for ProcessSpawnTestHookReset {
5812        fn drop(&mut self) {
5813            for slot in [
5814                &PROCESS_SPAWN_AFTER_RENDEZVOUS_TEST_HOOK,
5815                &PROCESS_SPAWN_AFTER_FINAL_CHECK_TEST_HOOK,
5816                &PROCESS_SPAWN_BEFORE_CLEANUP_TEST_HOOK,
5817            ] {
5818                if let Ok(mut hook) = slot.lock() {
5819                    *hook = None;
5820                }
5821            }
5822        }
5823    }
5824
5825    #[test]
5826    fn adversarial_launch_allowance_is_phase_specific_and_production_independent() {
5827        let operation_no_progress = Duration::from_millis(500);
5828        assert_eq!(
5829            adversarial_launch_no_progress("pre-ready-stall", operation_no_progress),
5830            operation_no_progress
5831        );
5832        assert_eq!(
5833            adversarial_launch_no_progress("admission-stall", operation_no_progress),
5834            operation_no_progress
5835        );
5836        for scenario in [
5837            "progress-no-work",
5838            "admission-forged",
5839            "completion-malformed",
5840        ] {
5841            assert_eq!(
5842                adversarial_launch_no_progress(scenario, operation_no_progress),
5843                ADVERSARIAL_NON_STALL_LAUNCH_NO_PROGRESS
5844            );
5845        }
5846        assert_eq!(ARTIFACT_ADMISSION_TIMEOUT, Duration::from_secs(15));
5847        assert_eq!(
5848            ARTIFACT_ADMISSION_AGGREGATE_TIMEOUT,
5849            Duration::from_secs(20 * 60)
5850        );
5851        assert_eq!(
5852            ARTIFACT_ADMISSION_NO_PROGRESS_TIMEOUT,
5853            Duration::from_secs(5)
5854        );
5855    }
5856
5857    #[test]
5858    fn adversarial_deadline_expectation_requires_the_exact_operation_phase() {
5859        let delayed_admission = ParserSupervisorError::DeadlineExceeded {
5860            phase: "containment admission",
5861        };
5862        assert!(!adversarial_deadline_matches(
5863            &delayed_admission,
5864            "request response"
5865        ));
5866
5867        let response_deadline = ParserSupervisorError::DeadlineExceeded {
5868            phase: "request response",
5869        };
5870        assert!(adversarial_deadline_matches(
5871            &response_deadline,
5872            "request response"
5873        ));
5874    }
5875
5876    #[test]
5877    fn blocked_process_spawn_child_fixture() {
5878        if std::env::var_os(PROCESS_SPAWN_CHILD_ENV).is_none() {
5879            return;
5880        }
5881        thread::sleep(Duration::from_secs(30));
5882        if let Some(path) = std::env::var_os(PROCESS_SPAWN_CHILD_COMPLETED_ENV) {
5883            let _write = fs::write(path, b"survived");
5884        }
5885    }
5886
5887    /// Reader that injects one transient interrupted read.
5888    struct InterruptOnceReader {
5889        /// Remaining deterministic input bytes.
5890        input: Cursor<Vec<u8>>,
5891        /// Whether one transient interrupted read has already been injected.
5892        did_interrupt: bool,
5893    }
5894
5895    impl Read for InterruptOnceReader {
5896        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
5897            if !self.did_interrupt {
5898                self.did_interrupt = true;
5899                return Err(io::ErrorKind::Interrupted.into());
5900            }
5901            self.input.read(buffer)
5902        }
5903    }
5904
5905    /// Reader that reports entry and blocks until the test releases it.
5906    struct BlockingReader {
5907        /// Signals that the worker entered the in-flight read.
5908        entered: SyncSender<()>,
5909        /// Releases the deliberately stalled read.
5910        release: Receiver<()>,
5911    }
5912
5913    impl Read for BlockingReader {
5914        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
5915            self.entered
5916                .send(())
5917                .map_err(|_closed| io::Error::other("blocking reader entry receiver closed"))?;
5918            self.release
5919                .recv()
5920                .map_err(|_closed| io::Error::other("blocking reader release sender closed"))?;
5921            buffer[0] = b'x';
5922            Ok(1)
5923        }
5924    }
5925
5926    /// Build process-free verified launch metadata for focused supervisor tests.
5927    fn metadata_only_launch() -> VerifiedParserPackLaunch {
5928        let pack_root = PathBuf::from("metadata-only-pack");
5929        VerifiedParserPackLaunch {
5930            pack_root: pack_root.clone(),
5931            platform: PackPlatform::LinuxX86_64,
5932            #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
5933            containment_broker: Some(pack_root.join("projectatlas-parser-containment.exe")),
5934            accepted_grammars: vec!["alpha".to_owned(), "zeta".to_owned()],
5935            artifact: ParserArtifactIdentity::for_bytes(b"artifact"),
5936            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5937            artifact_manifest_bytes: b"artifact".to_vec(),
5938            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5939            accepted_manifest_bytes: Vec::new(),
5940            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
5941            native_import_policy_bytes: Vec::new(),
5942            artifact_manifest: FileObservation::unavailable(
5943                pack_root.join(ARTIFACT_MANIFEST_FILE_NAME),
5944            ),
5945            payloads: Vec::new(),
5946            currentness_blocker: None,
5947        }
5948    }
5949
5950    /// Build a process-free supervisor value for public metadata delegation tests.
5951    fn metadata_only_supervisor() -> OptionalParserSupervisor {
5952        let launch = metadata_only_launch();
5953        OptionalParserSupervisor {
5954            pack_root: launch.pack_root.clone(),
5955            launch,
5956            memory_limits: ParserMemoryLimits::PRODUCTION,
5957            resident: None,
5958        }
5959    }
5960
5961    #[test]
5962    fn supervisor_exposes_only_verified_artifact_and_language_metadata() {
5963        let supervisor = metadata_only_supervisor();
5964        assert_eq!(
5965            supervisor.artifact_identity(),
5966            &ParserArtifactIdentity::for_bytes(b"artifact")
5967        );
5968        assert!(supervisor.accepts_language("alpha"));
5969        assert!(supervisor.accepts_language("zeta"));
5970        assert!(!supervisor.accepts_language("missing"));
5971        assert!(!supervisor.accepts_language("INVALID"));
5972    }
5973
5974    #[test]
5975    fn supervisor_rejects_artifact_identity_change_inside_selected_slot() {
5976        let mut supervisor = metadata_only_supervisor();
5977        let selected = supervisor.artifact_identity().clone();
5978        let mut replacement = metadata_only_launch();
5979        replacement.artifact = ParserArtifactIdentity::for_bytes(b"replacement-artifact");
5980
5981        assert!(matches!(
5982            supervisor.replace_verified_launch(replacement),
5983            Err(ParserSupervisorError::PayloadMismatch {
5984                reason: "artifact identity changed inside its immutable slot",
5985                ..
5986            })
5987        ));
5988        assert_eq!(supervisor.artifact_identity(), &selected);
5989    }
5990
5991    #[test]
5992    fn bounded_artifact_read_retries_interruption() -> Result<(), Box<dyn std::error::Error>> {
5993        let expected = vec![b'x'; ARTIFACT_READ_CHUNK_BYTES * 3];
5994        let expected_sha256 = encode_sha256(Sha256::digest(&expected));
5995        let mut reader = InterruptOnceReader {
5996            input: Cursor::new(expected.clone()),
5997            did_interrupt: false,
5998        };
5999        let mut bytes = Vec::new();
6000        let mut sha256 = Sha256::new();
6001        read_bounded_chunks(
6002            &mut reader,
6003            Path::new("changed-payload"),
6004            u64::try_from(ARTIFACT_READ_CHUNK_BYTES * 3)?,
6005            &mut bytes,
6006            &mut sha256,
6007            None,
6008        )?;
6009
6010        require_test(
6011            bytes == expected,
6012            "bounded artifact read changed bytes after an interrupted read",
6013        )?;
6014        require_test(
6015            encode_sha256(sha256.finalize()) == expected_sha256,
6016            "bounded artifact read changed its digest after an interrupted read",
6017        )
6018        .map_err(Into::into)
6019    }
6020
6021    #[test]
6022    fn changed_artifact_reload_returns_while_reader_is_blocked()
6023    -> Result<(), Box<dyn std::error::Error>> {
6024        let _test_guard = ARTIFACT_IO_TEST_LOCK
6025            .lock()
6026            .map_err(|_poisoned| io::Error::other("artifact I/O test lock was poisoned"))?;
6027        let cancellation = IndexCancellation::new();
6028        let started = Instant::now();
6029        let absolute_deadline = started + Duration::from_secs(5);
6030        let no_progress_timeout = Duration::from_secs(5);
6031        let (entered_sender, entered_receiver) = mpsc::sync_channel(1);
6032        let (release_sender, release_receiver) = mpsc::sync_channel(1);
6033        let (finished_sender, finished_receiver) = mpsc::sync_channel(1);
6034        let (result_sender, result_receiver) = mpsc::sync_channel(1);
6035        let caller_cancellation = cancellation.clone();
6036        let worker_cancellation = cancellation.clone();
6037        let caller = thread::spawn(move || {
6038            let control = ArtifactIoControl {
6039                absolute_deadline,
6040                last_progress: started,
6041                no_progress_timeout,
6042                cancellation: &caller_cancellation,
6043            };
6044            let result = run_bounded_artifact_io(
6045                move || {
6046                    let worker_control = ArtifactIoControl {
6047                        absolute_deadline,
6048                        last_progress: started,
6049                        no_progress_timeout,
6050                        cancellation: &worker_cancellation,
6051                    };
6052                    let mut reader = BlockingReader {
6053                        entered: entered_sender,
6054                        release: release_receiver,
6055                    };
6056                    let mut bytes = Vec::new();
6057                    let mut sha256 = Sha256::new();
6058                    let result = read_bounded_chunks(
6059                        &mut reader,
6060                        Path::new("blocked-payload"),
6061                        u64::try_from(ARTIFACT_READ_CHUNK_BYTES).unwrap_or(u64::MAX),
6062                        &mut bytes,
6063                        &mut sha256,
6064                        Some(&worker_control),
6065                    );
6066                    let worker_cancelled = matches!(
6067                        &result,
6068                        Err(ParserSupervisorError::Cancelled {
6069                            phase: ARTIFACT_IO_PHASE
6070                        })
6071                    );
6072                    let _finished_result = finished_sender.send(worker_cancelled);
6073                    result
6074                },
6075                &control,
6076            );
6077            let _result_send = result_sender.send(result);
6078        });
6079
6080        let entered = entered_receiver.recv_timeout(Duration::from_secs(1));
6081        cancellation.cancel();
6082        let result = result_receiver.recv_timeout(Duration::from_secs(1));
6083        let returned_cancelled = matches!(
6084            result.as_ref(),
6085            Ok(Err(ParserSupervisorError::Cancelled {
6086                phase: ARTIFACT_IO_PHASE
6087            }))
6088        );
6089        let refused_second_reader = matches!(
6090            ArtifactIoLease::acquire(),
6091            Err(ParserSupervisorError::IoThread {
6092                phase: ARTIFACT_IO_PHASE,
6093                ..
6094            })
6095        );
6096
6097        let release_result = release_sender.send(());
6098        caller
6099            .join()
6100            .map_err(|_panic| io::Error::other("artifact revalidation caller panicked"))?;
6101        let worker_cancelled = finished_receiver.recv_timeout(Duration::from_secs(1));
6102        let permit_deadline = Instant::now() + Duration::from_secs(1);
6103        while ARTIFACT_IO_ACTIVE.load(Ordering::Acquire) && Instant::now() < permit_deadline {
6104            thread::yield_now();
6105        }
6106
6107        entered.map_err(|source| io::Error::other(source.to_string()))?;
6108        let _returned = result.map_err(|source| io::Error::other(source.to_string()))?;
6109        release_result?;
6110        let worker_cancelled =
6111            worker_cancelled.map_err(|source| io::Error::other(source.to_string()))?;
6112        require_test(
6113            returned_cancelled,
6114            "blocked artifact read retained the canceled request",
6115        )?;
6116        require_test(
6117            refused_second_reader,
6118            "blocked artifact read permitted another reload worker",
6119        )?;
6120        require_test(
6121            worker_cancelled,
6122            "released artifact reader continued after request cancellation",
6123        )?;
6124        require_test(
6125            ArtifactIoLease::acquire().is_ok(),
6126            "artifact reader permit was not reusable after worker completion",
6127        )
6128        .map_err(Into::into)
6129    }
6130
6131    #[test]
6132    fn currentness_probe_returns_while_path_observer_is_blocked()
6133    -> Result<(), Box<dyn std::error::Error>> {
6134        let _test_guard = ARTIFACT_IO_TEST_LOCK
6135            .lock()
6136            .map_err(|_poisoned| io::Error::other("artifact I/O test lock was poisoned"))?;
6137        let cancellation = IndexCancellation::new();
6138        let started = Instant::now();
6139        let absolute_deadline = started + Duration::from_secs(5);
6140        let no_progress_timeout = Duration::from_secs(5);
6141        let (entered_sender, entered_receiver) = mpsc::sync_channel(1);
6142        let (release_sender, release_receiver) = mpsc::sync_channel(1);
6143        let (result_sender, result_receiver) = mpsc::sync_channel(1);
6144        let temp = tempfile::tempdir()?;
6145        let observed_path = temp.path().join("artifact.json");
6146        fs::write(&observed_path, b"verified")?;
6147        let observation = FileObservation::capture(observed_path)?;
6148        let mut file_probe = observation.currentness_probe();
6149        file_probe.blocker = Some(std::sync::Arc::new(MetadataProbeBlocker {
6150            entered: entered_sender,
6151            release: std::sync::Mutex::new(release_receiver),
6152        }));
6153        let probe = ArtifactCurrentnessProbe {
6154            files: vec![file_probe],
6155        };
6156        let caller_cancellation = cancellation.clone();
6157        let caller = thread::spawn(move || {
6158            let control = ArtifactIoControl {
6159                absolute_deadline,
6160                last_progress: started,
6161                no_progress_timeout,
6162                cancellation: &caller_cancellation,
6163            };
6164            let result = run_bounded_artifact_currentness(probe, &control);
6165            let _result_send = result_sender.send(result);
6166        });
6167
6168        entered_receiver
6169            .recv_timeout(Duration::from_secs(1))
6170            .map_err(|source| io::Error::other(source.to_string()))?;
6171        cancellation.cancel();
6172        let result = result_receiver
6173            .recv_timeout(Duration::from_secs(1))
6174            .map_err(|source| io::Error::other(source.to_string()))?;
6175        let returned_cancelled = matches!(
6176            result,
6177            Err(ParserSupervisorError::Cancelled {
6178                phase: ARTIFACT_IO_PHASE
6179            })
6180        );
6181        let refused_second_reader = matches!(
6182            ArtifactIoLease::acquire(),
6183            Err(ParserSupervisorError::IoThread {
6184                phase: ARTIFACT_IO_PHASE,
6185                ..
6186            })
6187        );
6188        release_sender.send(())?;
6189        caller
6190            .join()
6191            .map_err(|_panic| io::Error::other("currentness caller panicked"))?;
6192        let permit_deadline = Instant::now() + Duration::from_secs(1);
6193        while ARTIFACT_IO_ACTIVE.load(Ordering::Acquire) && Instant::now() < permit_deadline {
6194            thread::yield_now();
6195        }
6196
6197        require_test(
6198            returned_cancelled,
6199            "blocked pathname observation retained the canceled request",
6200        )?;
6201        require_test(
6202            refused_second_reader,
6203            "blocked pathname observation permitted another artifact reader",
6204        )?;
6205        require_test(
6206            ArtifactIoLease::acquire().is_ok(),
6207            "artifact reader permit was not reusable after currentness completion",
6208        )
6209        .map_err(Into::into)
6210    }
6211
6212    #[test]
6213    fn changed_artifact_refresh_propagates_request_cancellation() {
6214        let mut supervisor = metadata_only_supervisor();
6215        let cancellation = IndexCancellation::new();
6216        cancellation.cancel();
6217
6218        assert!(matches!(
6219            supervisor.refresh_changed_artifact(
6220                "alpha",
6221                Instant::now(),
6222                Instant::now() + Duration::from_secs(1),
6223                Duration::from_secs(1),
6224                &cancellation,
6225            ),
6226            Err(ParserSupervisorError::Cancelled {
6227                phase: ARTIFACT_IO_PHASE
6228            })
6229        ));
6230    }
6231
6232    #[test]
6233    fn controlled_artifact_currentness_polls_before_metadata() {
6234        let launch = metadata_only_launch();
6235        let cancellation = IndexCancellation::new();
6236        cancellation.cancel();
6237        let control = ArtifactIoControl {
6238            absolute_deadline: Instant::now() + Duration::from_secs(1),
6239            last_progress: Instant::now(),
6240            no_progress_timeout: Duration::from_secs(1),
6241            cancellation: &cancellation,
6242        };
6243
6244        assert!(matches!(
6245            launch.currentness_probe("alpha").is_current(Some(&control)),
6246            Err(ParserSupervisorError::Cancelled {
6247                phase: ARTIFACT_IO_PHASE
6248            })
6249        ));
6250    }
6251
6252    #[test]
6253    fn stopped_process_launch_never_invokes_spawn() -> Result<(), Box<dyn std::error::Error>> {
6254        let _guard = PROCESS_SPAWN_TEST_LOCK
6255            .lock()
6256            .map_err(|_poisoned| io::Error::other("process-spawn test lock is poisoned"))?;
6257        require_process_spawn_cleanup_health()?;
6258        let cancellation = IndexCancellation::new();
6259        cancellation.cancel();
6260        let invoked = Arc::new(AtomicBool::new(false));
6261        let spawn_invoked = Arc::clone(&invoked);
6262        let started = Instant::now();
6263        let result = run_bounded_process_spawn_with(
6264            Command::new(std::env::current_exe()?),
6265            started + Duration::from_secs(1),
6266            started,
6267            Duration::from_secs(1),
6268            &cancellation,
6269            move |_command| {
6270                spawn_invoked.store(true, Ordering::Release);
6271                Err(io::Error::other("stopped launch invoked spawn"))
6272            },
6273        );
6274        if !matches!(
6275            result,
6276            Err(ParserSupervisorError::Cancelled {
6277                phase: PROCESS_LAUNCH_PHASE
6278            })
6279        ) {
6280            return Err(io::Error::other(format!(
6281                "stopped process launch returned the wrong result: {result:?}"
6282            ))
6283            .into());
6284        }
6285        if invoked.load(Ordering::Acquire) {
6286            return Err(io::Error::other("stopped process launch invoked spawn").into());
6287        }
6288        Ok(())
6289    }
6290
6291    #[test]
6292    fn sticky_late_cleanup_failure_refuses_future_spawn() -> Result<(), Box<dyn std::error::Error>>
6293    {
6294        let _guard = PROCESS_SPAWN_TEST_LOCK
6295            .lock()
6296            .map_err(|_poisoned| io::Error::other("process-spawn test lock is poisoned"))?;
6297        require_process_spawn_cleanup_health()?;
6298        let _reset = ProcessSpawnCleanupFailureReset;
6299        record_process_spawn_cleanup_failure(&ParserSupervisorError::Cleanup {
6300            message: "injected cleanup failure".to_owned(),
6301        });
6302        let invoked = Arc::new(AtomicBool::new(false));
6303        let spawn_invoked = Arc::clone(&invoked);
6304        let cancellation = IndexCancellation::new();
6305        let started = Instant::now();
6306        let result = run_bounded_process_spawn_with(
6307            Command::new(std::env::current_exe()?),
6308            started + Duration::from_secs(1),
6309            started,
6310            Duration::from_secs(1),
6311            &cancellation,
6312            move |_command| {
6313                spawn_invoked.store(true, Ordering::Release);
6314                Err(io::Error::other("unhealthy launch invoked spawn"))
6315            },
6316        );
6317        let message = match result {
6318            Err(ParserSupervisorError::Cleanup { message }) => message,
6319            other => {
6320                return Err(io::Error::other(format!(
6321                    "sticky cleanup failure returned the wrong result: {other:?}"
6322                ))
6323                .into());
6324            }
6325        };
6326        if !message.contains("injected cleanup failure") {
6327            return Err(io::Error::other("sticky cleanup failure lost its diagnostic").into());
6328        }
6329        if invoked.load(Ordering::Acquire) {
6330            return Err(io::Error::other("unhealthy process launch invoked spawn").into());
6331        }
6332        Ok(())
6333    }
6334
6335    #[test]
6336    fn blocked_process_spawn_releases_caller_and_reaps_late_child()
6337    -> Result<(), Box<dyn std::error::Error>> {
6338        let _guard = PROCESS_SPAWN_TEST_LOCK
6339            .lock()
6340            .map_err(|_poisoned| io::Error::other("process-spawn test lock is poisoned"))?;
6341        require_process_spawn_cleanup_health()?;
6342        let temp = tempfile::tempdir()?;
6343        let completed = temp.path().join("child-completed");
6344        let mut command = Command::new(std::env::current_exe()?);
6345        command
6346            .arg("--exact")
6347            .arg("parser_supervisor::tests::blocked_process_spawn_child_fixture")
6348            .arg("--nocapture")
6349            .env(PROCESS_SPAWN_CHILD_ENV, "1")
6350            .env(PROCESS_SPAWN_CHILD_COMPLETED_ENV, &completed)
6351            .stdin(Stdio::null())
6352            .stdout(Stdio::null())
6353            .stderr(Stdio::null());
6354
6355        let (entered_sender, entered_receiver) = mpsc::sync_channel(1);
6356        let (release_sender, release_receiver) = mpsc::sync_channel(1);
6357        let (pid_sender, pid_receiver) = mpsc::sync_channel(1);
6358        let cancellation = IndexCancellation::new();
6359        let caller_cancellation = cancellation.clone();
6360        let (result_sender, result_receiver) = mpsc::sync_channel(1);
6361        let started = Instant::now();
6362        let caller = thread::spawn(move || {
6363            let result = run_bounded_process_spawn_with(
6364                command,
6365                started + Duration::from_secs(5),
6366                started,
6367                Duration::from_secs(5),
6368                &caller_cancellation,
6369                move |mut command| {
6370                    let child = command.spawn()?;
6371                    pid_sender
6372                        .send(child.id())
6373                        .map_err(|_closed| io::Error::other("spawn PID receiver closed"))?;
6374                    entered_sender
6375                        .send(())
6376                        .map_err(|_closed| io::Error::other("spawn blocker receiver closed"))?;
6377                    release_receiver
6378                        .recv()
6379                        .map_err(|_closed| io::Error::other("spawn release sender closed"))?;
6380                    Ok(child)
6381                },
6382            );
6383            let _send = result_sender.send(result);
6384        });
6385
6386        entered_receiver.recv_timeout(Duration::from_secs(5))?;
6387        let _late_child_pid = pid_receiver.recv_timeout(Duration::from_secs(1))?;
6388        cancellation.cancel();
6389        let result = result_receiver.recv_timeout(Duration::from_secs(1))?;
6390        match result {
6391            Err(ParserSupervisorError::Cancelled {
6392                phase: PROCESS_LAUNCH_PHASE,
6393            }) => {}
6394            other => {
6395                return Err(io::Error::other(format!(
6396                    "blocked process spawn returned the wrong result: {other:?}"
6397                ))
6398                .into());
6399            }
6400        }
6401        if ProcessSpawnLease::acquire().is_ok() {
6402            return Err(io::Error::other(
6403                "blocked process spawn released its process-wide lease early",
6404            )
6405            .into());
6406        }
6407        release_sender.send(())?;
6408        caller
6409            .join()
6410            .map_err(|_panic| io::Error::other("blocked-spawn caller panicked"))?;
6411
6412        let cleanup_deadline = Instant::now() + SUPERVISOR_CLEANUP_TIMEOUT;
6413        while PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) && Instant::now() < cleanup_deadline {
6414            thread::yield_now();
6415        }
6416        if PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) {
6417            return Err(io::Error::other(
6418                "late process spawn did not release its process-wide lease",
6419            )
6420            .into());
6421        }
6422        require_process_spawn_cleanup_health()?;
6423        if completed.exists() {
6424            return Err(io::Error::other("late child survived mandatory cleanup").into());
6425        }
6426        drop(ProcessSpawnLease::acquire()?);
6427        Ok(())
6428    }
6429
6430    #[test]
6431    fn completed_process_spawn_cancellation_detaches_after_rendezvous()
6432    -> Result<(), Box<dyn std::error::Error>> {
6433        let _guard = PROCESS_SPAWN_TEST_LOCK
6434            .lock()
6435            .map_err(|_poisoned| io::Error::other("process-spawn test lock is poisoned"))?;
6436        require_process_spawn_cleanup_health()?;
6437        let _hook_reset = ProcessSpawnTestHookReset;
6438        let temp = tempfile::tempdir()?;
6439        let completed = temp.path().join("completed-spawn-child-completed");
6440        let mut command = Command::new(std::env::current_exe()?);
6441        command
6442            .arg("--exact")
6443            .arg("parser_supervisor::tests::blocked_process_spawn_child_fixture")
6444            .arg("--nocapture")
6445            .env(PROCESS_SPAWN_CHILD_ENV, "1")
6446            .env(PROCESS_SPAWN_CHILD_COMPLETED_ENV, &completed)
6447            .stdin(Stdio::null())
6448            .stdout(Stdio::null())
6449            .stderr(Stdio::null());
6450
6451        let (rendezvous_entered_sender, rendezvous_entered_receiver) = mpsc::sync_channel(1);
6452        let (release_rendezvous_sender, release_rendezvous_receiver) = mpsc::sync_channel(1);
6453        *PROCESS_SPAWN_AFTER_RENDEZVOUS_TEST_HOOK
6454            .lock()
6455            .map_err(|_poisoned| io::Error::other("rendezvous test hook lock is poisoned"))? =
6456            Some(Box::new(move || {
6457                let _entered = rendezvous_entered_sender.send(());
6458                let _release = release_rendezvous_receiver.recv();
6459            }));
6460
6461        let (cleanup_entered_sender, cleanup_entered_receiver) = mpsc::sync_channel(1);
6462        let (release_cleanup_sender, release_cleanup_receiver) = mpsc::sync_channel(1);
6463        *PROCESS_SPAWN_BEFORE_CLEANUP_TEST_HOOK
6464            .lock()
6465            .map_err(|_poisoned| io::Error::other("cleanup test hook lock is poisoned"))? =
6466            Some(Box::new(move || {
6467                let thread_name = thread::current().name().unwrap_or("unnamed").to_owned();
6468                let _entered = cleanup_entered_sender.send(thread_name);
6469                let _release = release_cleanup_receiver.recv();
6470            }));
6471
6472        let (pid_sender, pid_receiver) = mpsc::sync_channel(1);
6473        let cancellation = IndexCancellation::new();
6474        let caller_cancellation = cancellation.clone();
6475        let (result_sender, result_receiver) = mpsc::sync_channel(1);
6476        let started = Instant::now();
6477        let caller = thread::spawn(move || {
6478            let result = run_bounded_process_spawn_with(
6479                command,
6480                started + Duration::from_secs(5),
6481                started,
6482                Duration::from_secs(5),
6483                &caller_cancellation,
6484                move |mut command| {
6485                    let child = command.spawn()?;
6486                    pid_sender
6487                        .send(child.id())
6488                        .map_err(|_closed| io::Error::other("spawn PID receiver closed"))?;
6489                    Ok(child)
6490                },
6491            );
6492            let _send = result_sender.send(result);
6493        });
6494
6495        rendezvous_entered_receiver.recv_timeout(Duration::from_secs(5))?;
6496        let _child_pid = pid_receiver.recv_timeout(Duration::from_secs(1))?;
6497        cancellation.cancel();
6498        release_rendezvous_sender.send(())?;
6499        let result = result_receiver.recv_timeout(Duration::from_secs(1))?;
6500        match result {
6501            Err(ParserSupervisorError::Cancelled {
6502                phase: PROCESS_LAUNCH_PHASE,
6503            }) => {}
6504            other => {
6505                return Err(io::Error::other(format!(
6506                    "completed process spawn returned the wrong result: {other:?}"
6507                ))
6508                .into());
6509            }
6510        }
6511        caller
6512            .join()
6513            .map_err(|_panic| io::Error::other("completed-spawn caller panicked"))?;
6514
6515        let cleanup_thread = cleanup_entered_receiver.recv_timeout(Duration::from_secs(1))?;
6516        if cleanup_thread != "projectatlas-process-spawn" {
6517            return Err(io::Error::other(format!(
6518                "unadmitted child cleanup ran on {cleanup_thread:?}"
6519            ))
6520            .into());
6521        }
6522        if !PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) || ProcessSpawnLease::acquire().is_ok() {
6523            return Err(io::Error::other(
6524                "unadmitted child cleanup released its process-wide lease early",
6525            )
6526            .into());
6527        }
6528        if completed.exists() {
6529            return Err(io::Error::other("unadmitted child survived before cleanup").into());
6530        }
6531
6532        release_cleanup_sender.send(())?;
6533        let cleanup_deadline = Instant::now() + SUPERVISOR_CLEANUP_TIMEOUT;
6534        while PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) && Instant::now() < cleanup_deadline {
6535            thread::yield_now();
6536        }
6537        if PROCESS_SPAWN_ACTIVE.load(Ordering::Acquire) {
6538            return Err(io::Error::other(
6539                "unadmitted child cleanup did not release its process-wide lease",
6540            )
6541            .into());
6542        }
6543        require_process_spawn_cleanup_health()?;
6544        if completed.exists() {
6545            return Err(io::Error::other("unadmitted child survived mandatory cleanup").into());
6546        }
6547        drop(ProcessSpawnLease::acquire()?);
6548        Ok(())
6549    }
6550
6551    #[test]
6552    fn launch_command_cancellation_after_process_spawn_commit_returns_after_child_cleanup()
6553    -> Result<(), Box<dyn std::error::Error>> {
6554        let _guard = PROCESS_SPAWN_TEST_LOCK
6555            .lock()
6556            .map_err(|_poisoned| io::Error::other("process-spawn test lock is poisoned"))?;
6557        require_process_spawn_cleanup_health()?;
6558        let _hook_reset = ProcessSpawnTestHookReset;
6559        let temp = tempfile::tempdir()?;
6560        let completed = temp.path().join("post-commit-child-completed");
6561        let mut command = Command::new(std::env::current_exe()?);
6562        command
6563            .arg("--exact")
6564            .arg("parser_supervisor::tests::blocked_process_spawn_child_fixture")
6565            .arg("--nocapture")
6566            .env(PROCESS_SPAWN_CHILD_ENV, "1")
6567            .env(PROCESS_SPAWN_CHILD_COMPLETED_ENV, &completed)
6568            .stdin(Stdio::piped())
6569            .stdout(Stdio::piped())
6570            .stderr(Stdio::null());
6571
6572        let started = Instant::now();
6573        let cancellation = IndexCancellation::new();
6574        let hook_cancellation = cancellation.clone();
6575        *PROCESS_SPAWN_AFTER_FINAL_CHECK_TEST_HOOK
6576            .lock()
6577            .map_err(|_poisoned| io::Error::other("final-check test hook lock is poisoned"))? =
6578            Some(Box::new(move || hook_cancellation.cancel()));
6579        let result = ResidentParserSession::launch_command(
6580            &metadata_only_launch(),
6581            ParserLanguageIdentity::new("alpha")?,
6582            ParserMemoryLimits::PRODUCTION,
6583            started,
6584            started + Duration::from_secs(5),
6585            Duration::from_secs(5),
6586            &cancellation,
6587            command,
6588        );
6589        if !cancellation.is_cancelled() {
6590            return Err(io::Error::other(
6591                "final-check test did not cancel after ownership commitment",
6592            )
6593            .into());
6594        }
6595        match result {
6596            Err(ParserSupervisorError::Cancelled {
6597                phase: PROCESS_LAUNCH_PHASE,
6598            }) => {}
6599            Err(other) => {
6600                return Err(io::Error::other(format!(
6601                    "post-commit launch cancellation returned the wrong error: {other:?}"
6602                ))
6603                .into());
6604            }
6605            Ok(resident) => {
6606                resident.shutdown()?;
6607                return Err(io::Error::other(
6608                    "post-commit launch cancellation returned a resident session",
6609                )
6610                .into());
6611            }
6612        }
6613        drop(ProcessSpawnLease::acquire()?);
6614        require_process_spawn_cleanup_health()?;
6615        if completed.exists() {
6616            return Err(io::Error::other("post-commit child survived launch cleanup").into());
6617        }
6618        Ok(())
6619    }
6620
6621    #[test]
6622    fn partial_launch_cleanup_releases_full_reader_channels()
6623    -> Result<(), Box<dyn std::error::Error>> {
6624        let temp = tempfile::tempdir()?;
6625        let completed = temp.path().join("partial-launch-child-completed");
6626        let mut child = Command::new(std::env::current_exe()?)
6627            .arg("--exact")
6628            .arg("parser_supervisor::tests::blocked_process_spawn_child_fixture")
6629            .arg("--nocapture")
6630            .env(PROCESS_SPAWN_CHILD_ENV, "1")
6631            .env(PROCESS_SPAWN_CHILD_COMPLETED_ENV, &completed)
6632            .stdin(Stdio::null())
6633            .stdout(Stdio::null())
6634            .stderr(Stdio::null())
6635            .spawn()?;
6636
6637        let (frame_sender, frame_events) = mpsc::sync_channel(1);
6638        let (frame_entered_sender, frame_entered_receiver) = mpsc::sync_channel(1);
6639        let frame_handle = thread::spawn(move || {
6640            let _first = frame_sender.send(FrameReaderEvent::Frame(vec![1]));
6641            let _entered = frame_entered_sender.send(());
6642            let _second = frame_sender.send(FrameReaderEvent::Frame(vec![2]));
6643        });
6644        let (diagnostic_sender, diagnostic_events) = mpsc::sync_channel(1);
6645        let (diagnostic_entered_sender, diagnostic_entered_receiver) = mpsc::sync_channel(1);
6646        let diagnostic_handle = thread::spawn(move || {
6647            let _first = diagnostic_sender.send(DiagnosticReaderEvent::AdmissionAccepted);
6648            let _entered = diagnostic_entered_sender.send(());
6649            let _second = diagnostic_sender.send(DiagnosticReaderEvent::AdmissionAccepted);
6650            Ok(Vec::new())
6651        });
6652        frame_entered_receiver.recv_timeout(Duration::from_secs(1))?;
6653        diagnostic_entered_receiver.recv_timeout(Duration::from_secs(1))?;
6654
6655        cleanup_partial_launch(
6656            &mut child,
6657            vec![frame_handle],
6658            Some(diagnostic_handle),
6659            Some(frame_events),
6660            Some(diagnostic_events),
6661        )?;
6662        if completed.exists() {
6663            return Err(io::Error::other("partial-launch child survived cleanup").into());
6664        }
6665        Ok(())
6666    }
6667
6668    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6669    #[test]
6670    fn memfd_creation_retries_only_unsupported_modern_flags() {
6671        use nix::errno::Errno;
6672        use nix::libc;
6673        use nix::sys::memfd::MFdFlags;
6674
6675        let base = MFdFlags::MFD_CLOEXEC | MFdFlags::MFD_ALLOW_SEALING;
6676        let requested = base | MFdFlags::from_bits_retain(libc::MFD_EXEC);
6677        let mut attempts = Vec::new();
6678        let descriptor = create_memfd_with_legacy_fallback(base, libc::MFD_EXEC, |flags| {
6679            attempts.push(flags);
6680            if attempts.len() == 1 {
6681                Err(Errno::EINVAL)
6682            } else {
6683                Ok(7)
6684            }
6685        });
6686        assert_eq!(descriptor, Ok(7));
6687        assert_eq!(attempts, vec![requested, base]);
6688
6689        let mut denied_attempts = Vec::new();
6690        let denied = create_memfd_with_legacy_fallback(base, libc::MFD_EXEC, |flags| {
6691            denied_attempts.push(flags);
6692            Err::<i32, _>(Errno::EPERM)
6693        });
6694        assert_eq!(denied, Err(Errno::EPERM));
6695        assert_eq!(denied_attempts, vec![requested]);
6696    }
6697
6698    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6699    #[test]
6700    fn sealed_linux_payload_is_read_only_complete_and_immutable()
6701    -> Result<(), Box<dyn std::error::Error>> {
6702        use std::os::unix::fs::PermissionsExt;
6703
6704        use nix::fcntl::{FcntlArg, OFlag, SealFlag, fcntl};
6705
6706        let cancellation = IndexCancellation::new();
6707        let control = ArtifactIoControl {
6708            absolute_deadline: Instant::now() + Duration::from_secs(1),
6709            last_progress: Instant::now(),
6710            no_progress_timeout: Duration::from_secs(1),
6711            cancellation: &cancellation,
6712        };
6713        let payload = SealedLinuxPayload::from_verified_bytes(
6714            "test payload",
6715            "projectatlas-sealed-payload-test",
6716            b"verified authority",
6717            false,
6718            &control,
6719        )?;
6720        require_test(
6721            payload.file.metadata()?.permissions().mode() & 0o777 == 0o400,
6722            "sealed document authority retained executable or writable mode bits",
6723        )?;
6724        let status = fcntl(&payload.file, FcntlArg::F_GETFL)?;
6725        require_test(
6726            status & OFlag::O_ACCMODE.bits() == OFlag::O_RDONLY.bits(),
6727            "sealed document authority was not reopened read-only",
6728        )?;
6729        let seals = fcntl(&payload.file, FcntlArg::F_GET_SEALS)?;
6730        let required = SealFlag::F_SEAL_WRITE
6731            | SealFlag::F_SEAL_GROW
6732            | SealFlag::F_SEAL_SHRINK
6733            | SealFlag::F_SEAL_SEAL;
6734        require_test(
6735            seals & required.bits() == required.bits(),
6736            "sealed document authority omitted a required seal",
6737        )?;
6738
6739        let mut reader = payload.file.try_clone()?;
6740        let mut bytes = Vec::new();
6741        reader.read_to_end(&mut bytes)?;
6742        require_test(
6743            bytes == b"verified authority",
6744            "sealed document authority changed after reopening",
6745        )?;
6746        require_test(
6747            payload.file.set_len(0).is_err(),
6748            "sealed document authority allowed truncation",
6749        )?;
6750        let mut writer = payload.file.try_clone()?;
6751        require_test(
6752            writer.write_all(b"attacker").is_err(),
6753            "sealed document authority allowed mutation",
6754        )?;
6755
6756        let executable = SealedLinuxPayload::from_verified_bytes(
6757            "test worker",
6758            "projectatlas-sealed-worker-test",
6759            b"verified executable authority",
6760            true,
6761            &control,
6762        )?;
6763        require_test(
6764            executable.file.metadata()?.permissions().mode() & 0o777 == 0o500,
6765            "sealed executable authority retained writable or unexpected mode bits",
6766        )?;
6767        Ok(())
6768    }
6769
6770    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6771    #[test]
6772    fn sealed_linux_payload_legacy_fallback_preserves_exact_modes()
6773    -> Result<(), Box<dyn std::error::Error>> {
6774        use std::os::unix::fs::PermissionsExt;
6775
6776        use nix::errno::Errno;
6777        use nix::libc;
6778        use nix::sys::memfd::{MFdFlags, memfd_create};
6779
6780        let cancellation = IndexCancellation::new();
6781        let control = ArtifactIoControl {
6782            absolute_deadline: Instant::now() + Duration::from_secs(1),
6783            last_progress: Instant::now(),
6784            no_progress_timeout: Duration::from_secs(1),
6785            cancellation: &cancellation,
6786        };
6787        let base = MFdFlags::MFD_CLOEXEC | MFdFlags::MFD_ALLOW_SEALING;
6788        for (name, executable, expected_mode) in [
6789            ("projectatlas-legacy-document-test", false, 0o400),
6790            ("projectatlas-legacy-executable-test", true, 0o500),
6791        ] {
6792            let mode_flag = if executable {
6793                libc::MFD_EXEC
6794            } else {
6795                libc::MFD_NOEXEC_SEAL
6796            };
6797            let mut attempts = Vec::new();
6798            let payload = SealedLinuxPayload::from_verified_bytes_with_create(
6799                "legacy payload",
6800                name,
6801                b"verified authority",
6802                executable,
6803                &control,
6804                |flags| {
6805                    attempts.push(flags);
6806                    if attempts.len() == 1 {
6807                        Err(Errno::EINVAL)
6808                    } else {
6809                        // A modern test kernel may make base-only memfds non-executable;
6810                        // model the legacy kernel's executable-by-default fallback inode.
6811                        memfd_create(name, flags | MFdFlags::from_bits_retain(libc::MFD_EXEC))
6812                    }
6813                },
6814            )?;
6815            require_test(
6816                attempts == vec![base | MFdFlags::from_bits_retain(mode_flag), base],
6817                "legacy fallback did not retry exactly once with base flags",
6818            )?;
6819            require_test(
6820                payload.file.metadata()?.permissions().mode() & 0o777 == expected_mode,
6821                "legacy fallback retained an unexpected payload mode",
6822            )?;
6823        }
6824        Ok(())
6825    }
6826
6827    #[test]
6828    fn payload_observation_detects_same_size_same_mtime_change_epoch()
6829    -> Result<(), Box<dyn std::error::Error>> {
6830        let temp = tempfile::tempdir()?;
6831        let path = temp.path().join("worker");
6832        fs::write(&path, b"trusted")?;
6833        let modified = fs::metadata(&path)?.modified()?;
6834        let observation = PayloadObservation {
6835            file: FileObservation::capture(path.clone())?,
6836            role: ParserPackPayloadRole::Worker,
6837            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6838            bytes: 7,
6839            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6840            sha256: encode_sha256(Sha256::digest(b"trusted")),
6841        };
6842        require_test(observation.is_current()?, "initial payload was rejected")?;
6843
6844        let mutation = fs::write(&path, b"mutated");
6845        #[cfg(windows)]
6846        if mutation.is_err() {
6847            require_test(
6848                observation.is_current()? && fs::read(&path)? == b"trusted",
6849                "Windows write guard reported failure after changing payload identity",
6850            )?;
6851            return Ok(());
6852        }
6853        mutation?;
6854        File::options()
6855            .write(true)
6856            .open(&path)?
6857            .set_times(fs::FileTimes::new().set_modified(modified))?;
6858        require_test(
6859            fs::metadata(&path)?.len() == observation.file.epoch.bytes
6860                && fs::metadata(&path)?.modified()? == modified,
6861            "test mutation did not preserve size and modification time",
6862        )?;
6863        require_test(
6864            !observation.is_current()?,
6865            "same-size same-mtime payload mutation retained launch authority",
6866        )?;
6867        Ok(())
6868    }
6869
6870    #[test]
6871    fn bounded_artifact_read_rejects_mismatched_captured_epoch()
6872    -> Result<(), Box<dyn std::error::Error>> {
6873        let temp = tempfile::tempdir()?;
6874        let path = temp.path().join("worker");
6875        fs::write(&path, b"trusted")?;
6876        let observation = FileObservation::capture(path.clone())?;
6877
6878        #[cfg(unix)]
6879        let expected_epoch = {
6880            let replacement = temp.path().join("replacement");
6881            let retained = temp.path().join("retained");
6882            fs::write(&replacement, b"mutated")?;
6883            fs::rename(&path, retained)?;
6884            fs::rename(replacement, &path)?;
6885            observation.epoch
6886        };
6887        #[cfg(not(unix))]
6888        let expected_epoch = {
6889            let _write_guard = &observation;
6890            FileChangeEpoch::default()
6891        };
6892
6893        let Err(error) = read_bounded_file(&path, expected_epoch, 7, None) else {
6894            return Err(io::Error::other(
6895                "replacement bytes were accepted under the captured epoch",
6896            )
6897            .into());
6898        };
6899        require_test(
6900            matches!(
6901                error,
6902                ParserSupervisorError::PayloadMismatch {
6903                    reason: "artifact read handle does not match the captured file identity",
6904                    ..
6905                }
6906            ),
6907            "replacement read did not fail on the captured file epoch",
6908        )?;
6909        Ok(())
6910    }
6911
6912    #[cfg(windows)]
6913    #[test]
6914    fn windows_file_observation_releases_delete_share_on_drop()
6915    -> Result<(), Box<dyn std::error::Error>> {
6916        let temp = tempfile::tempdir()?;
6917        let path = temp.path().join("guarded-payload");
6918        fs::write(&path, b"trusted")?;
6919        let observation = FileObservation::capture(path.clone())?;
6920
6921        require_test(
6922            fs::remove_file(&path).is_err() && path.is_file(),
6923            "Windows observation did not deny payload deletion",
6924        )?;
6925        drop(observation);
6926        fs::remove_file(&path)?;
6927        require_test(
6928            !path.exists(),
6929            "dropping Windows observation did not release payload deletion",
6930        )?;
6931        Ok(())
6932    }
6933
6934    #[test]
6935    fn payload_observation_revalidates_only_launch_inputs() {
6936        let observation = |role| PayloadObservation {
6937            file: FileObservation::unavailable(PathBuf::new()),
6938            role,
6939            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6940            bytes: 0,
6941            #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6942            sha256: String::new(),
6943        };
6944
6945        assert!(observation(ParserPackPayloadRole::Worker).contributes_to_launch("rust"));
6946        assert!(
6947            observation(ParserPackPayloadRole::ContainmentBroker).contributes_to_launch("rust")
6948        );
6949        assert!(observation(ParserPackPayloadRole::AcceptedManifest).contributes_to_launch("rust"));
6950        assert!(
6951            observation(ParserPackPayloadRole::GrammarLibrary {
6952                language_id: "rust".to_owned(),
6953            })
6954            .contributes_to_launch("rust")
6955        );
6956        assert!(
6957            !observation(ParserPackPayloadRole::GrammarLibrary {
6958                language_id: "python".to_owned(),
6959            })
6960            .contributes_to_launch("rust")
6961        );
6962        assert!(
6963            !observation(ParserPackPayloadRole::NativeAuditReport).contributes_to_launch("rust")
6964        );
6965        #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6966        assert!(
6967            observation(ParserPackPayloadRole::NativeImportPolicy).contributes_to_launch("rust")
6968        );
6969        #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
6970        assert!(
6971            !observation(ParserPackPayloadRole::NativeImportPolicy).contributes_to_launch("rust")
6972        );
6973    }
6974
6975    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
6976    #[test]
6977    fn linux_currentness_probe_detects_native_policy_drift()
6978    -> Result<(), Box<dyn std::error::Error>> {
6979        let temp = tempfile::tempdir()?;
6980        let artifact_path = temp.path().join(ARTIFACT_MANIFEST_FILE_NAME);
6981        let policy_path = temp.path().join("native-policy");
6982        fs::write(&artifact_path, b"artifact")?;
6983        fs::write(&policy_path, b"trusted")?;
6984        let modified = fs::metadata(&policy_path)?.modified()?;
6985
6986        let mut launch = metadata_only_launch();
6987        launch.pack_root = temp.path().to_path_buf();
6988        launch.artifact_manifest = FileObservation::capture(artifact_path)?;
6989        launch.payloads = vec![PayloadObservation {
6990            file: FileObservation::capture(policy_path.clone())?,
6991            role: ParserPackPayloadRole::NativeImportPolicy,
6992            bytes: 7,
6993            sha256: encode_sha256(Sha256::digest(b"trusted")),
6994        }];
6995        let probe = launch.currentness_probe("alpha");
6996        require_test(
6997            probe.is_current(None)?,
6998            "initial native policy currentness probe failed",
6999        )?;
7000
7001        fs::write(&policy_path, b"changed")?;
7002        File::options()
7003            .write(true)
7004            .open(&policy_path)?
7005            .set_times(fs::FileTimes::new().set_modified(modified))?;
7006        require_test(
7007            fs::metadata(&policy_path)?.len() == 7,
7008            "native policy mutation did not preserve size",
7009        )?;
7010        require_test(
7011            fs::metadata(&policy_path)?.modified()? == modified,
7012            "native policy mutation did not preserve modification time",
7013        )?;
7014        require_test(
7015            !probe.is_current(None)?,
7016            "same-size same-mtime native policy drift retained launch authority",
7017        )?;
7018        Ok(())
7019    }
7020
7021    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
7022    #[test]
7023    fn linux_worker_launch_closes_inherited_descriptors_on_exec()
7024    -> Result<(), Box<dyn std::error::Error>> {
7025        use std::os::fd::AsRawFd;
7026
7027        use nix::fcntl::{FcntlArg, FdFlag, fcntl};
7028
7029        let cancellation = IndexCancellation::new();
7030        let control = ArtifactIoControl {
7031            absolute_deadline: Instant::now() + Duration::from_secs(1),
7032            last_progress: Instant::now(),
7033            no_progress_timeout: Duration::from_secs(1),
7034            cancellation: &cancellation,
7035        };
7036        let payload = |name| {
7037            SealedLinuxPayload::from_verified_bytes(
7038                "test payload",
7039                name,
7040                b"verified authority",
7041                false,
7042                &control,
7043            )
7044        };
7045        let authority = LinuxResidentLaunchAuthority {
7046            worker: payload("projectatlas-test-worker")?,
7047            artifact_manifest: payload("projectatlas-test-artifact")?,
7048            accepted_manifest: payload("projectatlas-test-accepted")?,
7049            native_import_policy: payload("projectatlas-test-policy")?,
7050            grammar: payload("projectatlas-test-grammar")?,
7051        };
7052        let inherited = File::open("/dev/null")?;
7053        fcntl(&inherited, FcntlArg::F_SETFD(FdFlag::empty()))?;
7054        let descriptor = inherited.as_raw_fd();
7055        let mut command = Command::new("/bin/sh");
7056        command
7057            .args([
7058                "-c",
7059                "test ! -e \"/proc/self/fd/$1\"",
7060                "parser-worker-fd-check",
7061            ])
7062            .arg(descriptor.to_string());
7063        inherit_linux_authority_on_exec(&mut command, authority);
7064
7065        if command.status()?.success() {
7066            Ok(())
7067        } else {
7068            Err(io::Error::other("worker inherited an injected descriptor").into())
7069        }
7070    }
7071
7072    #[test]
7073    fn supervisor_memory_limits_reject_zero_reversed_and_runtime_excess() {
7074        for candidate in [
7075            ParserMemoryLimits {
7076                process_bytes: 0,
7077                process_tree_bytes: 1,
7078            },
7079            ParserMemoryLimits {
7080                process_bytes: 2,
7081                process_tree_bytes: 1,
7082            },
7083            ParserMemoryLimits {
7084                process_bytes: PARSER_WORKER_PROCESS_MEMORY_BYTES.saturating_add(1),
7085                process_tree_bytes: PARSER_WORKER_JOB_MEMORY_BYTES.saturating_add(1),
7086            },
7087        ] {
7088            assert!(matches!(
7089                candidate.checked(),
7090                Err(ParserSupervisorError::InvalidMemoryLimits { .. })
7091            ));
7092        }
7093        assert!(ParserMemoryLimits::PRODUCTION.checked().is_ok());
7094    }
7095
7096    #[test]
7097    fn memory_probe_source_preserves_linux_and_bounds_windows() {
7098        let fixture = b".\n";
7099        assert_eq!(
7100            memory_probe_source(PackPlatform::LinuxX86_64, fixture),
7101            fixture
7102        );
7103        let windows = memory_probe_source(PackPlatform::WindowsX86_64, fixture);
7104        assert_eq!(windows.len(), WINDOWS_MEMORY_PROBE_SOURCE_BYTES);
7105        assert_eq!(&windows[..4], b".\n.\n");
7106        assert!(windows.len() <= PARSER_MAX_SOURCE_BYTES as usize);
7107    }
7108
7109    #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
7110    #[test]
7111    fn windows_job_memory_exit_code_is_reserved_and_typed() -> Result<(), Box<dyn std::error::Error>>
7112    {
7113        let mut memory_command = Command::new("cmd.exe");
7114        memory_command
7115            .args(["/D", "/Q", "/C"])
7116            .arg(format!(
7117                "set /p _= & exit /B {PARSER_WINDOWS_BROKER_MEMORY_LIMIT_EXIT_CODE}"
7118            ))
7119            .stdin(Stdio::piped());
7120        let mut memory_exit = memory_command.spawn()?;
7121        let mut memory_input = memory_exit
7122            .stdin
7123            .take()
7124            .ok_or_else(|| io::Error::other("delayed broker memory-limit stdin is absent"))?;
7125        require_test(
7126            memory_exit.try_wait()?.is_none(),
7127            "delayed broker memory-limit process exited before observation",
7128        )?;
7129        let release_memory_exit = thread::spawn(move || {
7130            thread::sleep(Duration::from_millis(100));
7131            memory_input.write_all(b"\n")
7132        });
7133        let diagnostic = ParserIoThreadError::UnexpectedDiagnostic {
7134            diagnostic: "tree-sitter failed to allocate 8".to_owned(),
7135        };
7136        let observation_timeout = Duration::from_secs(5);
7137        let started = Instant::now();
7138        let memory_diagnostic_result = diagnostic_failure_after_exit_observation(
7139            &mut memory_exit,
7140            "request response",
7141            &diagnostic,
7142            started + observation_timeout,
7143            started,
7144            observation_timeout,
7145            &IndexCancellation::new(),
7146        );
7147        release_memory_exit
7148            .join()
7149            .map_err(|_panic| io::Error::other("delayed broker memory-limit release panicked"))??;
7150        if !matches!(
7151            memory_diagnostic_result,
7152            ParserSupervisorError::WindowsJobMemoryLimitExceeded {
7153                phase: "request response"
7154            }
7155        ) {
7156            return Err(std::io::Error::other(format!(
7157                "reserved broker memory-limit status did not override diagnostic bytes: {memory_diagnostic_result:?}"
7158            ))
7159            .into());
7160        }
7161        let memory_result =
7162            frame_event_result(FrameReaderEvent::EndOfStream, &mut memory_exit, "READY");
7163        if !matches!(
7164            memory_result,
7165            Err(ParserSupervisorError::WindowsJobMemoryLimitExceeded { phase: "READY" })
7166        ) {
7167            return Err(std::io::Error::other(format!(
7168                "reserved broker memory-limit status produced {memory_result:?}"
7169            ))
7170            .into());
7171        }
7172
7173        let mut ordinary_exit = Command::new("cmd.exe")
7174            .args(["/D", "/C", "exit", "125"])
7175            .spawn()?;
7176        ordinary_exit.wait()?;
7177        let ordinary_diagnostic_result = diagnostic_failure_after_exit_observation(
7178            &mut ordinary_exit,
7179            "request response",
7180            &diagnostic,
7181            started + Duration::from_secs(1),
7182            started,
7183            Duration::from_secs(1),
7184            &IndexCancellation::new(),
7185        );
7186        if !matches!(
7187            ordinary_diagnostic_result,
7188            ParserSupervisorError::IoThread {
7189                phase: "request response",
7190                ..
7191            }
7192        ) {
7193            return Err(std::io::Error::other(format!(
7194                "ordinary broker failure status replaced fail-closed diagnostics: {ordinary_diagnostic_result:?}"
7195            ))
7196            .into());
7197        }
7198        let ordinary_result =
7199            frame_event_result(FrameReaderEvent::EndOfStream, &mut ordinary_exit, "READY");
7200        if !matches!(
7201            ordinary_result,
7202            Err(ParserSupervisorError::ChildExited {
7203                phase: "READY",
7204                code: Some(125)
7205            })
7206        ) {
7207            return Err(std::io::Error::other(format!(
7208                "ordinary broker failure status produced {ordinary_result:?}"
7209            ))
7210            .into());
7211        }
7212        Ok(())
7213    }
7214
7215    #[test]
7216    fn cleanup_error_helper_includes_nested_combined_failures() {
7217        let nested = ParserSupervisorError::OperationAndCleanup {
7218            operation: Box::new(ParserSupervisorError::Cancelled { phase: "test" }),
7219            cleanup: Box::new(ParserSupervisorError::OperationAndCleanup {
7220                operation: Box::new(ParserSupervisorError::DeadlineExceeded { phase: "test" }),
7221                cleanup: Box::new(ParserSupervisorError::Cleanup {
7222                    message: "reap failed".to_owned(),
7223                }),
7224            }),
7225        };
7226        assert!(nested.has_mandatory_cleanup_failure());
7227        assert_eq!(
7228            nested.to_string(),
7229            "optional parser operation failed: optional parser operation was cancelled during test; cleanup also failed: optional parser operation failed: optional parser absolute deadline elapsed during test; cleanup also failed: optional parser cleanup failed: reap failed"
7230        );
7231        assert!(
7232            ParserSupervisorError::Cleanup {
7233                message: "drain failed".to_owned()
7234            }
7235            .has_mandatory_cleanup_failure()
7236        );
7237        assert!(
7238            !ParserSupervisorError::Cancelled { phase: "test" }.has_mandatory_cleanup_failure()
7239        );
7240    }
7241
7242    /// Writer that reports entry and blocks until the test releases one write.
7243    struct GateWriter {
7244        /// Reports each entered write call.
7245        entered: SyncSender<()>,
7246        /// Releases each entered write call.
7247        release: Receiver<()>,
7248    }
7249
7250    impl Write for GateWriter {
7251        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
7252            self.entered
7253                .send(())
7254                .map_err(|error| io::Error::other(error.to_string()))?;
7255            self.release
7256                .recv()
7257                .map_err(|error| io::Error::other(error.to_string()))?;
7258            Ok(bytes.len())
7259        }
7260
7261        fn flush(&mut self) -> io::Result<()> {
7262            Ok(())
7263        }
7264    }
7265
7266    /// Create one acknowledged writer command.
7267    fn writer_command(
7268        bytes: Vec<u8>,
7269    ) -> (WriterCommand, Receiver<Result<(), ParserIoThreadError>>) {
7270        let (acknowledgement, result) = mpsc::sync_channel(1);
7271        (
7272            WriterCommand {
7273                bytes,
7274                acknowledgement,
7275            },
7276            result,
7277        )
7278    }
7279
7280    /// Return a fallible test failure without panicking.
7281    fn require_test(condition: bool, message: &'static str) -> io::Result<()> {
7282        if condition {
7283            Ok(())
7284        } else {
7285            Err(io::Error::other(message))
7286        }
7287    }
7288
7289    /// Reject an oversized declaration after only the fixed header was read.
7290    #[test]
7291    fn frame_reader_bounds_header_before_payload_allocation() {
7292        let declared = PARSER_MAX_SOURCE_BYTES.saturating_add(1).to_be_bytes();
7293        let bytes = [
7294            b'P',
7295            b'A',
7296            PARSER_PROTOCOL_VERSION,
7297            ParserFrameKind::RawSource.as_u8(),
7298            declared[0],
7299            declared[1],
7300            declared[2],
7301            declared[3],
7302            b'x',
7303        ];
7304        let mut input = Cursor::new(bytes);
7305        assert!(matches!(
7306            read_one_frame(&mut input),
7307            Err(ParserIoThreadError::FrameHeader {
7308                source: ParserProtocolError::FramePayloadTooLarge { .. }
7309            })
7310        ));
7311        assert_eq!(input.position(), PARSER_FRAME_HEADER_BYTES as u64);
7312    }
7313
7314    /// Keep at most one pending large write behind the blocked writer thread.
7315    #[test]
7316    fn writer_queue_has_one_pending_slot() -> Result<(), Box<dyn std::error::Error>> {
7317        let (entered_sender, entered_receiver) = mpsc::sync_channel(1);
7318        let (release_sender, release_receiver) = mpsc::sync_channel(1);
7319        let (commands, receiver) = mpsc::sync_channel(1);
7320        let handle = thread::spawn(move || {
7321            writer_loop(
7322                GateWriter {
7323                    entered: entered_sender,
7324                    release: release_receiver,
7325                },
7326                &receiver,
7327            );
7328        });
7329
7330        let (first, first_result) = writer_command(vec![1]);
7331        commands.send(first)?;
7332        entered_receiver.recv_timeout(Duration::from_secs(1))?;
7333        let (second, second_result) = writer_command(vec![2]);
7334        commands.send(second)?;
7335        let (third, _third_result) = writer_command(vec![3]);
7336        require_test(
7337            matches!(commands.try_send(third), Err(TrySendError::Full(_))),
7338            "writer queue accepted more than one pending write",
7339        )?;
7340
7341        release_sender.send(())?;
7342        first_result.recv_timeout(Duration::from_secs(1))??;
7343        entered_receiver.recv_timeout(Duration::from_secs(1))?;
7344        release_sender.send(())?;
7345        second_result.recv_timeout(Duration::from_secs(1))??;
7346        drop(commands);
7347        handle
7348            .join()
7349            .map_err(|_panic| io::Error::other("writer test thread panicked"))?;
7350        Ok(())
7351    }
7352
7353    /// Poll cancellation, absolute deadline, and no-progress independently.
7354    #[test]
7355    fn stop_polling_preserves_independent_bounds() {
7356        let cancellation = IndexCancellation::new();
7357        let future = Instant::now()
7358            .checked_add(Duration::from_secs(1))
7359            .unwrap_or_else(Instant::now);
7360        assert!(
7361            poll_stop(
7362                "test",
7363                future,
7364                Instant::now(),
7365                Duration::from_secs(1),
7366                &cancellation,
7367            )
7368            .is_ok()
7369        );
7370        cancellation.cancel();
7371        assert!(matches!(
7372            poll_stop(
7373                "test",
7374                future,
7375                Instant::now(),
7376                Duration::from_secs(1),
7377                &cancellation,
7378            ),
7379            Err(ParserSupervisorError::Cancelled { .. })
7380        ));
7381        assert!(matches!(
7382            poll_stop(
7383                "test",
7384                Instant::now(),
7385                Instant::now(),
7386                Duration::from_secs(1),
7387                &IndexCancellation::new(),
7388            ),
7389            Err(ParserSupervisorError::DeadlineExceeded { .. })
7390        ));
7391        assert!(matches!(
7392            poll_stop(
7393                "test",
7394                future,
7395                Instant::now(),
7396                Duration::ZERO,
7397                &IndexCancellation::new(),
7398            ),
7399            Err(ParserSupervisorError::NoProgress { .. })
7400        ));
7401    }
7402
7403    /// Preserve `root_has_error` while rejecting a completion replayed to another session.
7404    #[test]
7405    fn completion_keeps_root_error_and_rejects_cross_session_replay()
7406    -> Result<(), Box<dyn std::error::Error>> {
7407        let source = b"broken";
7408        let session = ParserSessionIdentity::for_entropy(b"session-one");
7409        let artifact = ParserArtifactIdentity::new(ParserContentDigest::for_bytes(b"artifact"));
7410        let language = ParserLanguageIdentity::new("abl")?;
7411        let limits = ParserRequestLimits::new(1024, 100, 100)?;
7412        let request = ParserRequest::new(
7413            session,
7414            ParserRequestIdentity::new(1)?,
7415            artifact.clone(),
7416            language.clone(),
7417            ParserSourceIdentity::for_bytes(source)?,
7418            limits,
7419        );
7420        let evidence = ParserCompletionEvidence::new(
7421            ParserSyntaxKind::new("source_file")?,
7422            0,
7423            u32::try_from(source.len())?,
7424            true,
7425            1,
7426            1,
7427            0,
7428            1,
7429        )?;
7430        let encoded = encode_parser_control(&ParserControl::Completion(ParserCompletion::new(
7431            ParserResponseIdentity::for_request(&request),
7432            evidence,
7433        )))?;
7434        let decoded =
7435            decode_parser_completion_for_request(ParserFrame::decode_exact(&encoded)?, &request)?;
7436        require_test(
7437            decoded.evidence().root_has_error(),
7438            "completion lost root_has_error",
7439        )?;
7440
7441        let replay_target = ParserRequest::new(
7442            ParserSessionIdentity::for_entropy(b"session-two"),
7443            ParserRequestIdentity::new(1)?,
7444            artifact,
7445            language,
7446            ParserSourceIdentity::for_bytes(source)?,
7447            limits,
7448        );
7449        require_test(
7450            decode_parser_completion_for_request(
7451                ParserFrame::decode_exact(&encoded)?,
7452                &replay_target,
7453            )
7454            .is_err(),
7455            "cross-session completion replay was accepted",
7456        )?;
7457        Ok(())
7458    }
7459
7460    /// Accept only the exact Windows admission prefix before exposing diagnostics.
7461    #[test]
7462    fn diagnostic_reader_validates_admission_before_diagnostics()
7463    -> Result<(), Box<dyn std::error::Error>> {
7464        let mut bytes = PARSER_WINDOWS_BROKER_ADMISSION_RECORD.to_vec();
7465        bytes.extend_from_slice(b"bounded diagnostic");
7466        let (events, receiver) = mpsc::sync_channel(2);
7467        let diagnostics = diagnostic_reader_loop(
7468            Cursor::new(bytes),
7469            true,
7470            DiagnosticFence([0xA5; PARSER_DIAGNOSTIC_FENCE_BYTES]),
7471            &events,
7472        )?;
7473        require_test(
7474            matches!(
7475                receiver.recv_timeout(Duration::from_secs(1))?,
7476                DiagnosticReaderEvent::AdmissionAccepted
7477            ),
7478            "diagnostics became visible before admission",
7479        )?;
7480        require_test(
7481            matches!(
7482                receiver.recv_timeout(Duration::from_secs(1))?,
7483                DiagnosticReaderEvent::Failure(ParserIoThreadError::UnexpectedDiagnostic { .. })
7484            ),
7485            "bounded diagnostic bytes did not fail closed",
7486        )?;
7487        require_test(
7488            diagnostics == b"bounded diagnostic",
7489            "diagnostic bytes changed",
7490        )?;
7491        Ok(())
7492    }
7493
7494    /// Parse exact Linux RSS and cgroup counters without accepting unit or field drift.
7495    #[test]
7496    fn linux_memory_accounting_records_are_strict() {
7497        assert_eq!(
7498            parse_process_rss("Name:\tworker\nVmRSS:\t4096 kB\n").ok(),
7499            Some(4 * 1024 * 1024)
7500        );
7501        assert!(parse_process_rss("VmRSS:\t4096 MB\n").is_err());
7502        assert!(parse_process_rss("VmSize:\t4096 kB\n").is_err());
7503        assert_eq!(
7504            parse_cgroup_event("low 0\nhigh 1\nmax 7\noom 0\n", "max").ok(),
7505            Some(7)
7506        );
7507        assert!(parse_cgroup_event("max 7 extra\n", "max").is_err());
7508        assert!(has_cgroup_token("cpu io memory", "memory"));
7509        assert!(has_cgroup_token("+cpu +memory", "memory"));
7510        assert!(!has_cgroup_token("cpu memory.swap", "memory"));
7511        assert!(matches!(
7512            parse_unified_cgroup_path("0::/user.slice/projectatlas.scope/worker\n"),
7513            Ok(Some(path)) if path == Path::new("user.slice/projectatlas.scope/worker")
7514        ));
7515        assert!(matches!(
7516            parse_unified_cgroup_path("0::/\n"),
7517            Ok(Some(path)) if path.as_os_str().is_empty()
7518        ));
7519        assert!(parse_unified_cgroup_path("0::/safe/../escape\n").is_err());
7520        assert!(parse_unified_cgroup_path("0::/one\n0::/two\n").is_err());
7521        assert_eq!(
7522            PARSER_LINUX_RSS_OBSERVATION_INTERVAL,
7523            SUPERVISOR_POLL_INTERVAL
7524        );
7525    }
7526
7527    /// A worker that releases its address space before becoming waitable is classified as exited,
7528    /// not as a mandatory cleanup failure.
7529    #[test]
7530    fn linux_memory_exit_transition_observes_waitable_child() -> io::Result<()> {
7531        let exit_checks = std::cell::Cell::new(0_u8);
7532        let observation = resolve_linux_memory_exit_transition(
7533            io::Error::new(io::ErrorKind::InvalidData, "VmRSS is absent"),
7534            SUPERVISOR_POLL_INTERVAL,
7535            || {
7536                Err(io::Error::new(
7537                    io::ErrorKind::InvalidData,
7538                    "VmRSS is absent",
7539                ))
7540            },
7541            || {
7542                let checks = exit_checks.get();
7543                exit_checks.set(checks.saturating_add(1));
7544                Ok((checks > 0).then_some(LinuxChildExit { code: Some(17) }))
7545            },
7546        )?;
7547        require_test(
7548            matches!(
7549                observation,
7550                LinuxMemoryObservation::ChildExited { code: Some(17) }
7551            ),
7552            "exit transition did not become waitable",
7553        )
7554    }
7555
7556    /// Memory accounting that returns during the short transition remains authoritative.
7557    #[test]
7558    fn linux_memory_exit_transition_enforces_recovered_observation() -> io::Result<()> {
7559        let observation = resolve_linux_memory_exit_transition(
7560            io::Error::new(io::ErrorKind::InvalidData, "VmRSS is absent"),
7561            SUPERVISOR_POLL_INTERVAL,
7562            || {
7563                Ok(Some(LinuxMemoryBreach {
7564                    accounting: ParserMemoryAccountingKind::LinuxProcStatus,
7565                    observed_bytes: 4096,
7566                }))
7567            },
7568            || Ok(None),
7569        )?;
7570        require_test(
7571            matches!(
7572                observation,
7573                LinuxMemoryObservation::Memory(Some(LinuxMemoryBreach {
7574                    accounting: ParserMemoryAccountingKind::LinuxProcStatus,
7575                    observed_bytes: 4096,
7576                }))
7577            ),
7578            "recovered memory accounting was not retained",
7579        )
7580    }
7581
7582    /// A live non-waitable worker with unreadable accounting still fails closed.
7583    #[test]
7584    fn linux_memory_exit_transition_retains_unreadable_failure() -> io::Result<()> {
7585        let Err(error) = resolve_linux_memory_exit_transition(
7586            io::Error::new(io::ErrorKind::InvalidData, "VmRSS is absent"),
7587            Duration::ZERO,
7588            || {
7589                Err(io::Error::new(
7590                    io::ErrorKind::InvalidData,
7591                    "VmRSS remains absent",
7592                ))
7593            },
7594            || Ok(None),
7595        ) else {
7596            return Err(io::Error::other(
7597                "unreadable live accounting did not fail closed",
7598            ));
7599        };
7600        require_test(
7601            error.to_string() == "VmRSS remains absent",
7602            "unreadable memory failure changed",
7603        )
7604    }
7605}