Skip to main content

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