projectatlas_core/
optional_parser_protocol.rs

1//! Closed, bounded wire contract between the parser supervisor and resident workers.
2
3use serde::{Deserialize, Deserializer, Serialize};
4use std::fmt;
5use std::num::NonZeroU64;
6use thiserror::Error;
7
8/// Current parser-worker protocol version.
9pub const PARSER_PROTOCOL_VERSION: u8 = 1;
10/// Fixed byte length of every frame header.
11pub const PARSER_FRAME_HEADER_BYTES: usize = 8;
12/// Largest raw source accepted by one parse request.
13pub const PARSER_MAX_SOURCE_BYTES: u32 = 16 * 1024 * 1024;
14/// Largest payload accepted by any single parser frame.
15pub const PARSER_MAX_FRAME_PAYLOAD_BYTES: u32 = PARSER_MAX_SOURCE_BYTES;
16/// Largest serialized control payload accepted by one frame.
17pub const PARSER_MAX_CONTROL_BYTES: u32 = 256 * 1024;
18/// Largest serialized worker result accepted for one request.
19pub const PARSER_MAX_OUTPUT_BYTES: u32 = 128 * 1024;
20/// Largest structural-node count accepted for one request.
21pub const PARSER_MAX_NODE_COUNT: u32 = 10_000_000;
22/// Largest structural depth accepted for one request.
23pub const PARSER_MAX_TREE_DEPTH: u32 = 16_384;
24/// Largest progress-work counter accepted for one request.
25pub const PARSER_MAX_WORK_UNITS: u32 = 10_000_000;
26/// Largest number of progress messages accepted for one request.
27pub const PARSER_MAX_PROGRESS_MESSAGES: u32 = 4_096;
28/// Largest UTF-8 language identity.
29pub const PARSER_MAX_LANGUAGE_ID_BYTES: usize = 128;
30/// Largest UTF-8 syntax-kind identity.
31pub const PARSER_MAX_SYNTAX_KIND_BYTES: usize = 256;
32/// Fresh unpredictable bytes hashed into one supervised worker-session identity.
33pub const PARSER_SESSION_ENTROPY_BYTES: usize = 32;
34/// Maximum diagnostic bytes drained from one supervised worker session.
35pub const PARSER_MAX_STDERR_BYTES: usize = 64 * 1024;
36/// Hard per-worker committed-memory/address-space ceiling on accepted targets.
37pub const PARSER_WORKER_PROCESS_MEMORY_BYTES: u64 = 1024 * 1024 * 1024;
38/// Hard aggregate worker Job Object memory ceiling on Windows.
39pub const PARSER_WORKER_JOB_MEMORY_BYTES: u64 = PARSER_WORKER_PROCESS_MEMORY_BYTES;
40/// Reserved Windows containment-broker exit code for an observed Job memory-limit message.
41pub const PARSER_WINDOWS_BROKER_MEMORY_LIMIT_EXIT_CODE: i32 = 124;
42/// Exact Windows broker admission record required before `SessionOpen`.
43pub const PARSER_WINDOWS_BROKER_ADMISSION_RECORD: [u8; 16] = [
44    0x50, 0x41, 0x54, 0x4c, 0x41, 0x53, 0x2d, 0x41, 0x44, 0x4d, 0x49, 0x54, 0x00, 0x00, 0x00, 0x01,
45];
46
47/// Fixed header marker for the parser-worker protocol.
48const FRAME_MAGIC: [u8; 2] = *b"PA";
49/// Canonical hexadecimal length of a BLAKE3 digest.
50const BLAKE3_HEX_BYTES: usize = 64;
51
52/// Failure while framing, decoding, or validating parser-worker traffic.
53#[derive(Debug, Error)]
54pub enum ParserProtocolError {
55    /// Fewer than the fixed header bytes were supplied.
56    #[error("parser frame header has {actual} bytes; expected {expected}")]
57    HeaderTooShort {
58        /// Observed bytes.
59        actual: usize,
60        /// Required fixed bytes.
61        expected: usize,
62    },
63    /// The fixed protocol marker did not match.
64    #[error("parser frame marker is invalid")]
65    InvalidFrameMarker,
66    /// The frame or control payload uses another protocol version.
67    #[error("parser protocol version {actual} is unsupported; expected {expected}")]
68    UnsupportedVersion {
69        /// Rejected version.
70        actual: u8,
71        /// Only accepted version.
72        expected: u8,
73    },
74    /// A frame kind is outside the closed protocol.
75    #[error("parser frame kind {actual} is unknown")]
76    UnknownFrameKind {
77        /// Rejected numeric kind.
78        actual: u8,
79    },
80    /// A declared payload exceeds its kind-specific ceiling.
81    #[error("parser {kind:?} payload declares {actual} bytes; maximum is {maximum}")]
82    FramePayloadTooLarge {
83        /// Closed frame kind.
84        kind: ParserFrameKind,
85        /// Declared payload bytes.
86        actual: u32,
87        /// Kind-specific ceiling.
88        maximum: u32,
89    },
90    /// The bytes stop before the declared payload ends.
91    #[error("parser frame declares {declared} payload bytes but only {available} are available")]
92    TruncatedFrame {
93        /// Declared payload bytes.
94        declared: u32,
95        /// Available payload bytes.
96        available: usize,
97    },
98    /// Bytes remain after the one declared frame.
99    #[error("parser frame has {actual} payload bytes; declared exactly {declared}")]
100    TrailingFrameBytes {
101        /// Declared payload bytes.
102        declared: u32,
103        /// Available payload bytes.
104        actual: usize,
105    },
106    /// A raw-source frame was used where a control frame was required.
107    #[error("parser frame kind {kind:?} is not valid for this operation")]
108    UnexpectedFrameKind {
109        /// Unexpected closed kind.
110        kind: ParserFrameKind,
111    },
112    /// Strict JSON decoding failed.
113    #[error("invalid parser {kind:?} control payload")]
114    InvalidControlJson {
115        /// Expected control kind.
116        kind: ParserFrameKind,
117        /// JSON or typed-deserialization failure.
118        #[source]
119        source: serde_json::Error,
120    },
121    /// Control serialization failed.
122    #[error("could not serialize parser {kind:?} control payload")]
123    ControlSerialization {
124        /// Serialized control kind.
125        kind: ParserFrameKind,
126        /// JSON serialization failure.
127        #[source]
128        source: serde_json::Error,
129    },
130    /// A typed field violated its local representation contract.
131    #[error("invalid parser protocol field {field}: {reason}")]
132    InvalidField {
133        /// Stable field identity.
134        field: &'static str,
135        /// Stable rejection reason.
136        reason: &'static str,
137    },
138    /// Raw source length differs from the authenticated request identity.
139    #[error("parser source has {actual} bytes; request declares {expected}")]
140    SourceLengthMismatch {
141        /// Authenticated length.
142        expected: u32,
143        /// Observed length.
144        actual: usize,
145    },
146    /// Raw source content differs from the authenticated request identity.
147    #[error("parser source BLAKE3 digest does not match the request")]
148    SourceDigestMismatch,
149    /// A containment-ready frame belongs to another launch or artifact.
150    #[error("parser ready {field} does not match the supervised launch")]
151    ReadyIdentityMismatch {
152        /// Mismatched launch identity component.
153        field: &'static str,
154    },
155    /// A request belongs to another supervised worker session or artifact.
156    #[error("parser request {field} does not match the ready worker session")]
157    RequestIdentityMismatch {
158        /// Mismatched worker-session identity component.
159        field: &'static str,
160    },
161    /// A response belongs to another request, artifact, language, or source.
162    #[error("parser response {field} does not match the request")]
163    ResponseIdentityMismatch {
164        /// Mismatched identity component.
165        field: &'static str,
166    },
167    /// A response exceeds a request-specific count or output limit.
168    #[error("parser {field} value {actual} exceeds request limit {maximum}")]
169    RequestLimitExceeded {
170        /// Limited field.
171        field: &'static str,
172        /// Observed value.
173        actual: u32,
174        /// Request ceiling.
175        maximum: u32,
176    },
177    /// Progress sequencing or monotonic state regressed.
178    #[error("parser progress field {field} is not monotonic")]
179    ProgressRegression {
180        /// Regressed progress field.
181        field: &'static str,
182    },
183}
184
185/// Closed frame kinds used on the resident worker's standard streams.
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187#[repr(u8)]
188pub enum ParserFrameKind {
189    /// Strict JSON parse request from supervisor to worker.
190    Request = 1,
191    /// Exact unencoded source bytes from supervisor to worker.
192    RawSource = 2,
193    /// Strict JSON progress observation from worker to supervisor.
194    Progress = 3,
195    /// Strict JSON successful completion from worker to supervisor.
196    Completion = 4,
197    /// Strict JSON closed failure from worker to supervisor.
198    Failure = 5,
199    /// Strict JSON containment-ready state from worker to supervisor.
200    Ready = 6,
201    /// Strict JSON session opening from supervisor to a contained worker.
202    SessionOpen = 7,
203}
204
205impl ParserFrameKind {
206    /// Return the stable numeric wire value.
207    #[must_use]
208    pub const fn as_u8(self) -> u8 {
209        self as u8
210    }
211
212    /// Return the maximum payload bytes for this frame kind.
213    #[must_use]
214    pub const fn maximum_payload_bytes(self) -> u32 {
215        match self {
216            Self::RawSource => PARSER_MAX_SOURCE_BYTES,
217            Self::Request
218            | Self::Progress
219            | Self::Completion
220            | Self::Failure
221            | Self::Ready
222            | Self::SessionOpen => PARSER_MAX_CONTROL_BYTES,
223        }
224    }
225
226    /// Return whether this kind carries strict JSON control data.
227    #[must_use]
228    pub const fn is_control(self) -> bool {
229        !matches!(self, Self::RawSource)
230    }
231}
232
233impl TryFrom<u8> for ParserFrameKind {
234    type Error = ParserProtocolError;
235
236    fn try_from(value: u8) -> Result<Self, Self::Error> {
237        match value {
238            1 => Ok(Self::Request),
239            2 => Ok(Self::RawSource),
240            3 => Ok(Self::Progress),
241            4 => Ok(Self::Completion),
242            5 => Ok(Self::Failure),
243            6 => Ok(Self::Ready),
244            7 => Ok(Self::SessionOpen),
245            actual => Err(ParserProtocolError::UnknownFrameKind { actual }),
246        }
247    }
248}
249
250/// Validated fixed parser frame header.
251#[derive(Clone, Copy, Debug, Eq, PartialEq)]
252pub struct ParserFrameHeader {
253    /// Closed payload kind.
254    kind: ParserFrameKind,
255    /// Declared payload byte length.
256    payload_len: u32,
257}
258
259impl ParserFrameHeader {
260    /// Construct and bound a fixed frame header before payload allocation.
261    ///
262    /// # Errors
263    ///
264    /// Returns an error when `payload_len` exceeds the selected kind's ceiling.
265    pub fn new(kind: ParserFrameKind, payload_len: u32) -> Result<Self, ParserProtocolError> {
266        let maximum = kind.maximum_payload_bytes();
267        if payload_len > maximum {
268            return Err(ParserProtocolError::FramePayloadTooLarge {
269                kind,
270                actual: payload_len,
271                maximum,
272            });
273        }
274        Ok(Self { kind, payload_len })
275    }
276
277    /// Decode and validate the fixed header before inspecting or allocating its payload.
278    ///
279    /// # Errors
280    ///
281    /// Returns an error for short input, an invalid marker, unsupported version,
282    /// unknown kind, or a kind-specific declared-length overflow.
283    pub fn decode(bytes: &[u8]) -> Result<Self, ParserProtocolError> {
284        if bytes.len() < PARSER_FRAME_HEADER_BYTES {
285            return Err(ParserProtocolError::HeaderTooShort {
286                actual: bytes.len(),
287                expected: PARSER_FRAME_HEADER_BYTES,
288            });
289        }
290        if bytes[..2] != FRAME_MAGIC {
291            return Err(ParserProtocolError::InvalidFrameMarker);
292        }
293        let version = bytes[2];
294        if version != PARSER_PROTOCOL_VERSION {
295            return Err(ParserProtocolError::UnsupportedVersion {
296                actual: version,
297                expected: PARSER_PROTOCOL_VERSION,
298            });
299        }
300        let kind = ParserFrameKind::try_from(bytes[3])?;
301        let payload_len = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
302        Self::new(kind, payload_len)
303    }
304
305    /// Encode the fixed header in canonical network byte order.
306    #[must_use]
307    pub fn encode(self) -> [u8; PARSER_FRAME_HEADER_BYTES] {
308        let payload_len = self.payload_len.to_be_bytes();
309        [
310            FRAME_MAGIC[0],
311            FRAME_MAGIC[1],
312            PARSER_PROTOCOL_VERSION,
313            self.kind.as_u8(),
314            payload_len[0],
315            payload_len[1],
316            payload_len[2],
317            payload_len[3],
318        ]
319    }
320
321    /// Return the closed payload kind.
322    #[must_use]
323    pub const fn kind(self) -> ParserFrameKind {
324        self.kind
325    }
326
327    /// Return the validated declared payload length.
328    #[must_use]
329    pub const fn payload_len(self) -> u32 {
330        self.payload_len
331    }
332}
333
334/// One exactly framed borrowed parser payload.
335#[derive(Clone, Copy, Debug, Eq, PartialEq)]
336pub struct ParserFrame<'a> {
337    /// Validated fixed header.
338    header: ParserFrameHeader,
339    /// Exact borrowed payload.
340    payload: &'a [u8],
341}
342
343impl<'a> ParserFrame<'a> {
344    /// Decode exactly one complete frame without allocating its payload.
345    ///
346    /// Header validation, including the declared bound, occurs before the
347    /// declared length is used to address the payload.
348    ///
349    /// # Errors
350    ///
351    /// Returns an error for an invalid header, truncation, or trailing bytes.
352    pub fn decode_exact(bytes: &'a [u8]) -> Result<Self, ParserProtocolError> {
353        let header = ParserFrameHeader::decode(bytes)?;
354        let declared = header.payload_len();
355        let available = bytes.len().saturating_sub(PARSER_FRAME_HEADER_BYTES);
356        let declared_usize = declared as usize;
357        if available < declared_usize {
358            return Err(ParserProtocolError::TruncatedFrame {
359                declared,
360                available,
361            });
362        }
363        if available > declared_usize {
364            return Err(ParserProtocolError::TrailingFrameBytes {
365                declared,
366                actual: available,
367            });
368        }
369        Ok(Self {
370            header,
371            payload: &bytes[PARSER_FRAME_HEADER_BYTES..],
372        })
373    }
374
375    /// Return the validated closed frame kind.
376    #[must_use]
377    pub const fn kind(self) -> ParserFrameKind {
378        self.header.kind()
379    }
380
381    /// Borrow the exact payload bytes.
382    #[must_use]
383    pub const fn payload(self) -> &'a [u8] {
384        self.payload
385    }
386}
387
388/// Encode one bounded parser frame.
389///
390/// Raw source bytes are copied exactly; no text or base64 transformation occurs.
391///
392/// # Errors
393///
394/// Returns an error when the payload exceeds `u32` or its kind-specific ceiling.
395pub fn encode_parser_frame(
396    kind: ParserFrameKind,
397    payload: &[u8],
398) -> Result<Vec<u8>, ParserProtocolError> {
399    let payload_len = u32::try_from(payload.len()).map_err(|_source| {
400        ParserProtocolError::FramePayloadTooLarge {
401            kind,
402            actual: u32::MAX,
403            maximum: kind.maximum_payload_bytes(),
404        }
405    })?;
406    let header = ParserFrameHeader::new(kind, payload_len)?;
407    let mut encoded = Vec::with_capacity(PARSER_FRAME_HEADER_BYTES + payload.len());
408    encoded.extend_from_slice(&header.encode());
409    encoded.extend_from_slice(payload);
410    Ok(encoded)
411}
412
413/// Validated current protocol version carried by every control payload.
414#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
415#[serde(transparent)]
416pub struct ParserProtocolVersion(u8);
417
418impl ParserProtocolVersion {
419    /// Return the only supported protocol version.
420    #[must_use]
421    pub const fn current() -> Self {
422        Self(PARSER_PROTOCOL_VERSION)
423    }
424
425    /// Return the numeric wire version.
426    #[must_use]
427    pub const fn get(self) -> u8 {
428        self.0
429    }
430}
431
432impl<'de> Deserialize<'de> for ParserProtocolVersion {
433    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
434    where
435        D: Deserializer<'de>,
436    {
437        let actual = u8::deserialize(deserializer)?;
438        if actual == PARSER_PROTOCOL_VERSION {
439            Ok(Self(actual))
440        } else {
441            Err(serde::de::Error::custom(
442                ParserProtocolError::UnsupportedVersion {
443                    actual,
444                    expected: PARSER_PROTOCOL_VERSION,
445                },
446            ))
447        }
448    }
449}
450
451/// Non-zero identity of one supervisor request within a worker session.
452#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
453#[serde(transparent)]
454pub struct ParserRequestIdentity(NonZeroU64);
455
456impl ParserRequestIdentity {
457    /// Construct a non-zero request identity.
458    ///
459    /// # Errors
460    ///
461    /// Returns an error when `value` is zero.
462    pub fn new(value: u64) -> Result<Self, ParserProtocolError> {
463        NonZeroU64::new(value)
464            .map(Self)
465            .ok_or(ParserProtocolError::InvalidField {
466                field: "request_id",
467                reason: "expected a non-zero integer",
468            })
469    }
470
471    /// Return the numeric session-local identity.
472    #[must_use]
473    pub const fn get(self) -> u64 {
474        self.0.get()
475    }
476}
477
478/// Canonical lowercase BLAKE3 digest used by protocol identities.
479#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
480#[serde(transparent)]
481pub struct ParserContentDigest(String);
482
483impl ParserContentDigest {
484    /// Validate a canonical lowercase BLAKE3 digest.
485    ///
486    /// # Errors
487    ///
488    /// Returns an error for a non-canonical digest.
489    pub fn new(value: impl Into<String>) -> Result<Self, ParserProtocolError> {
490        let value = value.into();
491        if value.len() != BLAKE3_HEX_BYTES
492            || !value
493                .bytes()
494                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
495        {
496            return Err(ParserProtocolError::InvalidField {
497                field: "blake3",
498                reason: "expected 64 lowercase hexadecimal characters",
499            });
500        }
501        Ok(Self(value))
502    }
503
504    /// Hash exact bytes into the canonical protocol representation.
505    #[must_use]
506    pub fn for_bytes(bytes: &[u8]) -> Self {
507        Self(blake3::hash(bytes).to_hex().to_string())
508    }
509
510    /// Borrow the canonical lowercase hexadecimal value.
511    #[must_use]
512    pub fn as_str(&self) -> &str {
513        &self.0
514    }
515}
516
517impl<'de> Deserialize<'de> for ParserContentDigest {
518    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
519    where
520        D: Deserializer<'de>,
521    {
522        let value = String::deserialize(deserializer)?;
523        Self::new(value).map_err(serde::de::Error::custom)
524    }
525}
526
527impl fmt::Display for ParserContentDigest {
528    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
529        formatter.write_str(self.as_str())
530    }
531}
532
533/// Authenticated identity of one complete verified platform-pack artifact.
534///
535/// This is the BLAKE3 of the exact immutable artifact-manifest bytes, which in
536/// turn bind the worker, logical capability manifest, and grammar payloads. It
537/// is not the identity of a grammar currently loaded into the worker.
538#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
539#[serde(transparent)]
540pub struct ParserArtifactIdentity(ParserContentDigest);
541
542impl ParserArtifactIdentity {
543    /// Construct a pack-artifact identity from its authenticated BLAKE3 digest.
544    #[must_use]
545    pub const fn new(digest: ParserContentDigest) -> Self {
546        Self(digest)
547    }
548
549    /// Hash exact immutable artifact-manifest bytes.
550    #[must_use]
551    pub fn for_bytes(bytes: &[u8]) -> Self {
552        Self(ParserContentDigest::for_bytes(bytes))
553    }
554
555    /// Borrow the canonical platform-pack artifact digest.
556    #[must_use]
557    pub const fn digest(&self) -> &ParserContentDigest {
558        &self.0
559    }
560}
561
562/// Unpredictable identity of one supervised worker process session.
563///
564/// The supervisor derives this value from operating-system entropy before it
565/// launches the worker. Passing it to the trusted worker without grammar or
566/// source input and requiring it in READY plus every later request prevents
567/// stale process traffic from being replayed across sessions.
568#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
569#[serde(transparent)]
570pub struct ParserSessionIdentity(ParserContentDigest);
571
572impl ParserSessionIdentity {
573    /// Construct a session identity from a caller-authenticated digest.
574    #[must_use]
575    pub const fn new(digest: ParserContentDigest) -> Self {
576        Self(digest)
577    }
578
579    /// Hash caller-provided session entropy into the canonical wire identity.
580    #[must_use]
581    pub fn for_entropy(entropy: &[u8]) -> Self {
582        Self(ParserContentDigest::for_bytes(entropy))
583    }
584
585    /// Borrow the canonical session digest.
586    #[must_use]
587    pub const fn digest(&self) -> &ParserContentDigest {
588        &self.0
589    }
590}
591
592/// Bounded supervisor opening that a worker reads only after containment.
593///
594/// This frame carries process-session freshness but deliberately contains no
595/// grammar identity, repository path, or source bytes. A Linux worker installs
596/// its own boundary before reading this frame; Windows may deliver it only
597/// after launcher admission and resume.
598#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
599#[serde(deny_unknown_fields)]
600pub struct ParserSessionOpen {
601    /// Closed protocol version.
602    protocol_version: ParserProtocolVersion,
603    /// Supervisor-generated unpredictable process-session identity.
604    session: ParserSessionIdentity,
605}
606
607impl ParserSessionOpen {
608    /// Construct the current protocol's session opening.
609    #[must_use]
610    pub const fn new(session: ParserSessionIdentity) -> Self {
611        Self {
612            protocol_version: ParserProtocolVersion::current(),
613            session,
614        }
615    }
616
617    /// Borrow the supervised process-session identity.
618    #[must_use]
619    pub const fn session(&self) -> &ParserSessionIdentity {
620        &self.session
621    }
622}
623
624/// Closed containment boundary admitted before the worker emits READY.
625#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
626#[serde(rename_all = "snake_case")]
627pub enum ParserContainmentKind {
628    /// Linux worker self-restriction through hard limits, Landlock, and seccomp.
629    LinuxLandlockSeccomp,
630    /// Windows restricted `AppContainer` child attached to a kill-on-close Job Object.
631    WindowsAppContainerJob,
632}
633
634/// Authenticated containment-ready state emitted before grammar or source input.
635#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
636#[serde(deny_unknown_fields)]
637pub struct ParserReady {
638    /// Closed protocol version.
639    protocol_version: ParserProtocolVersion,
640    /// Supervisor-provided unpredictable process-session identity.
641    session: ParserSessionIdentity,
642    /// Exact immutable platform-pack artifact observed by the worker.
643    artifact: ParserArtifactIdentity,
644    /// Platform containment boundary installed before this state was emitted.
645    containment: ParserContainmentKind,
646}
647
648impl ParserReady {
649    /// Construct the current protocol's containment-ready state.
650    #[must_use]
651    pub const fn new(
652        session: ParserSessionIdentity,
653        artifact: ParserArtifactIdentity,
654        containment: ParserContainmentKind,
655    ) -> Self {
656        Self {
657            protocol_version: ParserProtocolVersion::current(),
658            session,
659            artifact,
660            containment,
661        }
662    }
663
664    /// Validate READY against the exact supervised launch contract.
665    ///
666    /// # Errors
667    ///
668    /// Returns an error naming the first mismatched launch identity component.
669    pub fn validate_for(
670        &self,
671        session: &ParserSessionIdentity,
672        artifact: &ParserArtifactIdentity,
673        containment: ParserContainmentKind,
674    ) -> Result<(), ParserProtocolError> {
675        if self.protocol_version != ParserProtocolVersion::current() {
676            return Err(ready_identity_mismatch("protocol_version"));
677        }
678        if &self.session != session {
679            return Err(ready_identity_mismatch("session"));
680        }
681        if &self.artifact != artifact {
682            return Err(ready_identity_mismatch("artifact"));
683        }
684        if self.containment != containment {
685            return Err(ready_identity_mismatch("containment"));
686        }
687        Ok(())
688    }
689
690    /// Borrow the supervised process-session identity.
691    #[must_use]
692    pub const fn session(&self) -> &ParserSessionIdentity {
693        &self.session
694    }
695
696    /// Borrow the exact immutable platform-pack artifact identity.
697    #[must_use]
698    pub const fn artifact(&self) -> &ParserArtifactIdentity {
699        &self.artifact
700    }
701
702    /// Return the admitted containment boundary.
703    #[must_use]
704    pub const fn containment(&self) -> ParserContainmentKind {
705        self.containment
706    }
707}
708
709/// Bounded stable language identity for one grammar-affined worker.
710#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
711#[serde(transparent)]
712pub struct ParserLanguageIdentity(String);
713
714impl ParserLanguageIdentity {
715    /// Validate a stable lowercase ASCII language identity.
716    ///
717    /// # Errors
718    ///
719    /// Returns an error for an empty, oversized, or unsafe identity.
720    pub fn new(value: impl Into<String>) -> Result<Self, ParserProtocolError> {
721        let value = value.into();
722        let mut bytes = value.bytes();
723        let starts_alphanumeric = bytes
724            .next()
725            .is_some_and(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit());
726        if value.len() > PARSER_MAX_LANGUAGE_ID_BYTES
727            || !starts_alphanumeric
728            || !bytes.all(|byte| {
729                byte.is_ascii_lowercase()
730                    || byte.is_ascii_digit()
731                    || matches!(byte, b'-' | b'_' | b'.' | b'+' | b'#')
732            })
733        {
734            return Err(ParserProtocolError::InvalidField {
735                field: "language_id",
736                reason: "expected a bounded lowercase ASCII language identity",
737            });
738        }
739        Ok(Self(value))
740    }
741
742    /// Borrow the stable language identity.
743    #[must_use]
744    pub fn as_str(&self) -> &str {
745        &self.0
746    }
747}
748
749impl<'de> Deserialize<'de> for ParserLanguageIdentity {
750    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
751    where
752        D: Deserializer<'de>,
753    {
754        let value = String::deserialize(deserializer)?;
755        Self::new(value).map_err(serde::de::Error::custom)
756    }
757}
758
759impl fmt::Display for ParserLanguageIdentity {
760    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
761        formatter.write_str(self.as_str())
762    }
763}
764
765/// Authenticated identity of one exact raw-source frame.
766#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
767pub struct ParserSourceIdentity {
768    /// Exact unencoded source bytes.
769    byte_len: u32,
770    /// BLAKE3 of the exact unencoded source bytes.
771    blake3: ParserContentDigest,
772}
773
774impl ParserSourceIdentity {
775    /// Authenticate one bounded raw-source payload.
776    ///
777    /// # Errors
778    ///
779    /// Returns an error when the source exceeds the hard byte ceiling.
780    pub fn for_bytes(source: &[u8]) -> Result<Self, ParserProtocolError> {
781        let byte_len =
782            u32::try_from(source.len()).map_err(|_source| ParserProtocolError::InvalidField {
783                field: "source.byte_len",
784                reason: "source length exceeds u32",
785            })?;
786        Self::new(byte_len, ParserContentDigest::for_bytes(source))
787    }
788
789    /// Construct a bounded source identity from authenticated metadata.
790    ///
791    /// # Errors
792    ///
793    /// Returns an error when `byte_len` exceeds the hard source ceiling.
794    pub fn new(byte_len: u32, blake3: ParserContentDigest) -> Result<Self, ParserProtocolError> {
795        if byte_len > PARSER_MAX_SOURCE_BYTES {
796            return Err(ParserProtocolError::InvalidField {
797                field: "source.byte_len",
798                reason: "source exceeds the hard byte ceiling",
799            });
800        }
801        Ok(Self { byte_len, blake3 })
802    }
803
804    /// Validate exact source bytes against both authenticated length and digest.
805    ///
806    /// # Errors
807    ///
808    /// Returns an error for a length or BLAKE3 mismatch.
809    pub fn validate_bytes(&self, source: &[u8]) -> Result<(), ParserProtocolError> {
810        if source.len() != self.byte_len as usize {
811            return Err(ParserProtocolError::SourceLengthMismatch {
812                expected: self.byte_len,
813                actual: source.len(),
814            });
815        }
816        if ParserContentDigest::for_bytes(source) != self.blake3 {
817            return Err(ParserProtocolError::SourceDigestMismatch);
818        }
819        Ok(())
820    }
821
822    /// Return the authenticated source byte length.
823    #[must_use]
824    pub const fn byte_len(&self) -> u32 {
825        self.byte_len
826    }
827
828    /// Borrow the authenticated source digest.
829    #[must_use]
830    pub const fn blake3(&self) -> &ParserContentDigest {
831        &self.blake3
832    }
833}
834
835impl<'de> Deserialize<'de> for ParserSourceIdentity {
836    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
837    where
838        D: Deserializer<'de>,
839    {
840        let wire = ParserSourceIdentityWire::deserialize(deserializer)?;
841        Self::new(wire.byte_len, wire.blake3).map_err(serde::de::Error::custom)
842    }
843}
844
845/// Strict wire projection for a source identity.
846#[derive(Deserialize)]
847#[serde(deny_unknown_fields)]
848struct ParserSourceIdentityWire {
849    /// Declared exact raw-source bytes.
850    byte_len: u32,
851    /// Authenticated raw-source digest.
852    blake3: ParserContentDigest,
853}
854
855/// Per-request output and structural bounds.
856#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
857pub struct ParserRequestLimits {
858    /// Serialized completion-byte ceiling.
859    output_bytes: u32,
860    /// Structural-node ceiling.
861    node_count: u32,
862    /// Structural-depth ceiling.
863    tree_depth: u32,
864}
865
866impl ParserRequestLimits {
867    /// Construct request limits within the hard protocol ceilings.
868    ///
869    /// # Errors
870    ///
871    /// Returns an error when any limit is zero or exceeds its hard ceiling.
872    pub fn new(
873        output_bytes: u32,
874        node_count: u32,
875        tree_depth: u32,
876    ) -> Result<Self, ParserProtocolError> {
877        validate_nonzero_limit("limits.output_bytes", output_bytes, PARSER_MAX_OUTPUT_BYTES)?;
878        validate_nonzero_limit("limits.node_count", node_count, PARSER_MAX_NODE_COUNT)?;
879        validate_nonzero_limit("limits.tree_depth", tree_depth, PARSER_MAX_TREE_DEPTH)?;
880        Ok(Self {
881            output_bytes,
882            node_count,
883            tree_depth,
884        })
885    }
886
887    /// Return the serialized completion-byte ceiling.
888    #[must_use]
889    pub const fn output_bytes(self) -> u32 {
890        self.output_bytes
891    }
892
893    /// Return the structural-node ceiling.
894    #[must_use]
895    pub const fn node_count(self) -> u32 {
896        self.node_count
897    }
898
899    /// Return the structural-depth ceiling.
900    #[must_use]
901    pub const fn tree_depth(self) -> u32 {
902        self.tree_depth
903    }
904}
905
906impl<'de> Deserialize<'de> for ParserRequestLimits {
907    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
908    where
909        D: Deserializer<'de>,
910    {
911        let wire = ParserRequestLimitsWire::deserialize(deserializer)?;
912        Self::new(wire.output_bytes, wire.node_count, wire.tree_depth)
913            .map_err(serde::de::Error::custom)
914    }
915}
916
917/// Strict wire projection for per-request limits.
918#[derive(Deserialize)]
919#[serde(deny_unknown_fields)]
920struct ParserRequestLimitsWire {
921    /// Serialized completion-byte ceiling.
922    output_bytes: u32,
923    /// Structural-node ceiling.
924    node_count: u32,
925    /// Structural-depth ceiling.
926    tree_depth: u32,
927}
928
929/// Strict parse request sent before its matching raw-source frame.
930#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
931#[serde(deny_unknown_fields)]
932pub struct ParserRequest {
933    /// Closed protocol version.
934    protocol_version: ParserProtocolVersion,
935    /// Unpredictable supervised worker-session identity.
936    session: ParserSessionIdentity,
937    /// Session-local request identity.
938    request_id: ParserRequestIdentity,
939    /// Exact verified platform-pack artifact identity expected by the supervisor.
940    artifact: ParserArtifactIdentity,
941    /// Grammar-affined language identity expected by the supervisor.
942    language: ParserLanguageIdentity,
943    /// Authenticated identity of the following raw-source frame.
944    source: ParserSourceIdentity,
945    /// Request-specific bounds under the hard protocol ceilings.
946    limits: ParserRequestLimits,
947}
948
949impl ParserRequest {
950    /// Construct a request for the current closed protocol.
951    #[must_use]
952    pub const fn new(
953        session: ParserSessionIdentity,
954        request_id: ParserRequestIdentity,
955        artifact: ParserArtifactIdentity,
956        language: ParserLanguageIdentity,
957        source: ParserSourceIdentity,
958        limits: ParserRequestLimits,
959    ) -> Self {
960        Self {
961            protocol_version: ParserProtocolVersion::current(),
962            session,
963            request_id,
964            artifact,
965            language,
966            source,
967            limits,
968        }
969    }
970
971    /// Return the protocol version.
972    #[must_use]
973    pub const fn protocol_version(&self) -> ParserProtocolVersion {
974        self.protocol_version
975    }
976
977    /// Borrow the supervised worker-session identity.
978    #[must_use]
979    pub const fn session(&self) -> &ParserSessionIdentity {
980        &self.session
981    }
982
983    /// Return the request identity.
984    #[must_use]
985    pub const fn request_id(&self) -> ParserRequestIdentity {
986        self.request_id
987    }
988
989    /// Borrow the expected loaded-artifact identity.
990    #[must_use]
991    pub const fn artifact(&self) -> &ParserArtifactIdentity {
992        &self.artifact
993    }
994
995    /// Borrow the expected language identity.
996    #[must_use]
997    pub const fn language(&self) -> &ParserLanguageIdentity {
998        &self.language
999    }
1000
1001    /// Borrow the authenticated source identity.
1002    #[must_use]
1003    pub const fn source(&self) -> &ParserSourceIdentity {
1004        &self.source
1005    }
1006
1007    /// Return the request-specific limits.
1008    #[must_use]
1009    pub const fn limits(&self) -> ParserRequestLimits {
1010        self.limits
1011    }
1012
1013    /// Validate this request against the ready worker's process session and artifact.
1014    ///
1015    /// # Errors
1016    ///
1017    /// Returns an error naming the first mismatched worker-session identity component.
1018    pub fn validate_for_session(
1019        &self,
1020        session: &ParserSessionIdentity,
1021        artifact: &ParserArtifactIdentity,
1022    ) -> Result<(), ParserProtocolError> {
1023        if &self.session != session {
1024            return Err(request_identity_mismatch("session"));
1025        }
1026        if &self.artifact != artifact {
1027            return Err(request_identity_mismatch("artifact"));
1028        }
1029        Ok(())
1030    }
1031
1032    /// Validate the exact following raw-source frame.
1033    ///
1034    /// # Errors
1035    ///
1036    /// Returns an error for another frame kind or a source length/digest mismatch.
1037    pub fn validate_source_frame(&self, frame: ParserFrame<'_>) -> Result<(), ParserProtocolError> {
1038        if frame.kind() != ParserFrameKind::RawSource {
1039            return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1040        }
1041        self.source.validate_bytes(frame.payload())
1042    }
1043}
1044
1045/// Complete identity copied into every worker response.
1046#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1047#[serde(deny_unknown_fields)]
1048pub struct ParserResponseIdentity {
1049    /// Closed protocol version.
1050    protocol_version: ParserProtocolVersion,
1051    /// Unpredictable supervised worker-session identity.
1052    session: ParserSessionIdentity,
1053    /// Session-local request identity.
1054    request_id: ParserRequestIdentity,
1055    /// Exact verified platform-pack artifact identity.
1056    artifact: ParserArtifactIdentity,
1057    /// Grammar-affined language identity.
1058    language: ParserLanguageIdentity,
1059    /// Authenticated source identity.
1060    source: ParserSourceIdentity,
1061}
1062
1063impl ParserResponseIdentity {
1064    /// Copy the complete expected response identity from a request.
1065    #[must_use]
1066    pub fn for_request(request: &ParserRequest) -> Self {
1067        Self {
1068            protocol_version: request.protocol_version,
1069            session: request.session.clone(),
1070            request_id: request.request_id,
1071            artifact: request.artifact.clone(),
1072            language: request.language.clone(),
1073            source: request.source.clone(),
1074        }
1075    }
1076
1077    /// Validate all response identity components against a request.
1078    ///
1079    /// # Errors
1080    ///
1081    /// Returns an error naming the first mismatched identity component.
1082    pub fn validate_for(&self, request: &ParserRequest) -> Result<(), ParserProtocolError> {
1083        if self.protocol_version != request.protocol_version {
1084            return Err(response_identity_mismatch("protocol_version"));
1085        }
1086        if self.session != request.session {
1087            return Err(response_identity_mismatch("session"));
1088        }
1089        if self.request_id != request.request_id {
1090            return Err(response_identity_mismatch("request_id"));
1091        }
1092        if self.artifact != request.artifact {
1093            return Err(response_identity_mismatch("artifact"));
1094        }
1095        if self.language != request.language {
1096            return Err(response_identity_mismatch("language"));
1097        }
1098        if self.source != request.source {
1099            return Err(response_identity_mismatch("source"));
1100        }
1101        Ok(())
1102    }
1103
1104    /// Return the request identity.
1105    #[must_use]
1106    pub const fn request_id(&self) -> ParserRequestIdentity {
1107        self.request_id
1108    }
1109
1110    /// Borrow the supervised worker-session identity.
1111    #[must_use]
1112    pub const fn session(&self) -> &ParserSessionIdentity {
1113        &self.session
1114    }
1115}
1116
1117/// Closed worker progress stages in monotonic order.
1118#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1119#[serde(rename_all = "snake_case")]
1120pub enum ParserProgressStage {
1121    /// The request and source identities were accepted.
1122    Accepted,
1123    /// The grammar is parsing the authenticated source.
1124    Parsing,
1125    /// Bounded structural evidence is being collected.
1126    CollectingEvidence,
1127}
1128
1129impl ParserProgressStage {
1130    /// Return the monotonic stage rank.
1131    const fn rank(self) -> u8 {
1132        match self {
1133            Self::Accepted => 0,
1134            Self::Parsing => 1,
1135            Self::CollectingEvidence => 2,
1136        }
1137    }
1138}
1139
1140/// Whether a valid progress message advanced observable work.
1141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1142pub enum ParserProgressDisposition {
1143    /// The stage or completed-work count advanced.
1144    Advanced,
1145    /// Only the sequence advanced, so a no-progress watchdog must keep aging.
1146    NoProgress,
1147}
1148
1149/// One strictly ordered progress observation.
1150#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1151pub struct ParserProgress {
1152    /// Complete response identity.
1153    identity: ParserResponseIdentity,
1154    /// One-based contiguous message sequence.
1155    sequence: u32,
1156    /// Monotonic closed stage.
1157    stage: ParserProgressStage,
1158    /// Monotonic completed-work count.
1159    completed_work: u32,
1160    /// Stable total-work count when measurable.
1161    total_work: Option<u32>,
1162}
1163
1164impl ParserProgress {
1165    /// Construct one bounded progress observation.
1166    ///
1167    /// # Errors
1168    ///
1169    /// Returns an error for an invalid sequence or work count.
1170    pub fn new(
1171        identity: ParserResponseIdentity,
1172        sequence: u32,
1173        stage: ParserProgressStage,
1174        completed_work: u32,
1175        total_work: Option<u32>,
1176    ) -> Result<Self, ParserProtocolError> {
1177        validate_nonzero_limit("progress.sequence", sequence, PARSER_MAX_PROGRESS_MESSAGES)?;
1178        if completed_work > PARSER_MAX_WORK_UNITS {
1179            return Err(ParserProtocolError::InvalidField {
1180                field: "progress.completed_work",
1181                reason: "completed work exceeds the hard count ceiling",
1182            });
1183        }
1184        if let Some(total) = total_work {
1185            validate_nonzero_limit("progress.total_work", total, PARSER_MAX_WORK_UNITS)?;
1186            if completed_work > total {
1187                return Err(ParserProtocolError::InvalidField {
1188                    field: "progress.completed_work",
1189                    reason: "completed work exceeds total work",
1190                });
1191            }
1192        }
1193        Ok(Self {
1194            identity,
1195            sequence,
1196            stage,
1197            completed_work,
1198            total_work,
1199        })
1200    }
1201
1202    /// Validate request identity and monotonic progress semantics.
1203    ///
1204    /// A valid message that advances only its sequence returns
1205    /// [`ParserProgressDisposition::NoProgress`], allowing the supervisor to
1206    /// distinguish liveness traffic from meaningful forward progress.
1207    ///
1208    /// # Errors
1209    ///
1210    /// Returns an error for identity mismatch, non-contiguous sequence,
1211    /// stage/count regression, or a changing total-work claim.
1212    pub fn validate_for(
1213        &self,
1214        request: &ParserRequest,
1215        previous: Option<&Self>,
1216    ) -> Result<ParserProgressDisposition, ParserProtocolError> {
1217        self.identity.validate_for(request)?;
1218        let Some(previous) = previous else {
1219            if self.sequence != 1 {
1220                return Err(progress_regression("sequence"));
1221            }
1222            return Ok(ParserProgressDisposition::Advanced);
1223        };
1224        if self.identity != previous.identity {
1225            return Err(progress_regression("identity"));
1226        }
1227        if self.sequence != previous.sequence.saturating_add(1) {
1228            return Err(progress_regression("sequence"));
1229        }
1230        if self.stage.rank() < previous.stage.rank() {
1231            return Err(progress_regression("stage"));
1232        }
1233        if self.completed_work < previous.completed_work {
1234            return Err(progress_regression("completed_work"));
1235        }
1236        if self.total_work != previous.total_work {
1237            return Err(progress_regression("total_work"));
1238        }
1239        if self.stage == previous.stage && self.completed_work == previous.completed_work {
1240            Ok(ParserProgressDisposition::NoProgress)
1241        } else {
1242            Ok(ParserProgressDisposition::Advanced)
1243        }
1244    }
1245
1246    /// Return the contiguous progress sequence.
1247    #[must_use]
1248    pub const fn sequence(&self) -> u32 {
1249        self.sequence
1250    }
1251}
1252
1253impl<'de> Deserialize<'de> for ParserProgress {
1254    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1255    where
1256        D: Deserializer<'de>,
1257    {
1258        let wire = ParserProgressWire::deserialize(deserializer)?;
1259        Self::new(
1260            wire.identity,
1261            wire.sequence,
1262            wire.stage,
1263            wire.completed_work,
1264            wire.total_work,
1265        )
1266        .map_err(serde::de::Error::custom)
1267    }
1268}
1269
1270/// Strict wire projection for one progress observation.
1271#[derive(Deserialize)]
1272#[serde(deny_unknown_fields)]
1273struct ParserProgressWire {
1274    /// Complete response identity.
1275    identity: ParserResponseIdentity,
1276    /// One-based contiguous message sequence.
1277    sequence: u32,
1278    /// Monotonic closed stage.
1279    stage: ParserProgressStage,
1280    /// Monotonic completed-work count.
1281    completed_work: u32,
1282    /// Stable total-work count when measurable.
1283    total_work: Option<u32>,
1284}
1285
1286/// Bounded syntax-kind identity returned as structural evidence.
1287#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1288#[serde(transparent)]
1289pub struct ParserSyntaxKind(String);
1290
1291impl ParserSyntaxKind {
1292    /// Validate a non-empty bounded syntax-kind identity.
1293    ///
1294    /// # Errors
1295    ///
1296    /// Returns an error for empty, oversized, or control-character content.
1297    pub fn new(value: impl Into<String>) -> Result<Self, ParserProtocolError> {
1298        let value = value.into();
1299        if value.is_empty()
1300            || value.len() > PARSER_MAX_SYNTAX_KIND_BYTES
1301            || value.chars().any(char::is_control)
1302        {
1303            return Err(ParserProtocolError::InvalidField {
1304                field: "evidence.root_kind",
1305                reason: "expected bounded non-control UTF-8",
1306            });
1307        }
1308        Ok(Self(value))
1309    }
1310
1311    /// Borrow the exact syntax-kind identity.
1312    #[must_use]
1313    pub fn as_str(&self) -> &str {
1314        &self.0
1315    }
1316}
1317
1318impl<'de> Deserialize<'de> for ParserSyntaxKind {
1319    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1320    where
1321        D: Deserializer<'de>,
1322    {
1323        let value = String::deserialize(deserializer)?;
1324        Self::new(value).map_err(serde::de::Error::custom)
1325    }
1326}
1327
1328/// Small bounded structural evidence returned instead of a syntax tree.
1329#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1330pub struct ParserCompletionEvidence {
1331    /// Exact root syntax kind.
1332    root_kind: ParserSyntaxKind,
1333    /// Root start byte in the authenticated source.
1334    root_start_byte: u32,
1335    /// Root end byte in the authenticated source.
1336    root_end_byte: u32,
1337    /// Exact Tree-sitter root error state used by retained fixture contracts.
1338    root_has_error: bool,
1339    /// Named structural-node count.
1340    named_node_count: u32,
1341    /// Error-node count.
1342    error_node_count: u32,
1343    /// Missing-node count.
1344    missing_node_count: u32,
1345    /// Maximum observed structural depth.
1346    maximum_depth: u32,
1347}
1348
1349impl ParserCompletionEvidence {
1350    /// Construct bounded structural evidence.
1351    ///
1352    /// # Errors
1353    ///
1354    /// Returns an error for an inverted range or a hard count/depth overflow.
1355    pub fn new(
1356        root_kind: ParserSyntaxKind,
1357        root_start_byte: u32,
1358        root_end_byte: u32,
1359        root_has_error: bool,
1360        named_node_count: u32,
1361        error_node_count: u32,
1362        missing_node_count: u32,
1363        maximum_depth: u32,
1364    ) -> Result<Self, ParserProtocolError> {
1365        if root_start_byte > root_end_byte {
1366            return Err(ParserProtocolError::InvalidField {
1367                field: "evidence.root_range",
1368                reason: "root start exceeds root end",
1369            });
1370        }
1371        for (field, count) in [
1372            ("evidence.named_node_count", named_node_count),
1373            ("evidence.error_node_count", error_node_count),
1374            ("evidence.missing_node_count", missing_node_count),
1375        ] {
1376            if count > PARSER_MAX_NODE_COUNT {
1377                return Err(ParserProtocolError::InvalidField {
1378                    field,
1379                    reason: "count exceeds the hard node ceiling",
1380                });
1381            }
1382        }
1383        if maximum_depth > PARSER_MAX_TREE_DEPTH {
1384            return Err(ParserProtocolError::InvalidField {
1385                field: "evidence.maximum_depth",
1386                reason: "depth exceeds the hard tree ceiling",
1387            });
1388        }
1389        Ok(Self {
1390            root_kind,
1391            root_start_byte,
1392            root_end_byte,
1393            root_has_error,
1394            named_node_count,
1395            error_node_count,
1396            missing_node_count,
1397            maximum_depth,
1398        })
1399    }
1400
1401    /// Validate evidence against request-specific source and structural limits.
1402    ///
1403    /// # Errors
1404    ///
1405    /// Returns an error when the range, node count, or depth exceeds the request.
1406    pub fn validate_for(&self, request: &ParserRequest) -> Result<(), ParserProtocolError> {
1407        let source_len = request.source.byte_len();
1408        if self.root_end_byte > source_len {
1409            return Err(request_limit_exceeded(
1410                "evidence.root_end_byte",
1411                self.root_end_byte,
1412                source_len,
1413            ));
1414        }
1415        let limits = request.limits;
1416        for (field, count) in [
1417            ("evidence.named_node_count", self.named_node_count),
1418            ("evidence.error_node_count", self.error_node_count),
1419            ("evidence.missing_node_count", self.missing_node_count),
1420        ] {
1421            if count > limits.node_count {
1422                return Err(request_limit_exceeded(field, count, limits.node_count));
1423            }
1424        }
1425        if self.maximum_depth > limits.tree_depth {
1426            return Err(request_limit_exceeded(
1427                "evidence.maximum_depth",
1428                self.maximum_depth,
1429                limits.tree_depth,
1430            ));
1431        }
1432        Ok(())
1433    }
1434
1435    /// Borrow the exact root syntax kind.
1436    #[must_use]
1437    pub const fn root_kind(&self) -> &ParserSyntaxKind {
1438        &self.root_kind
1439    }
1440
1441    /// Return the root start byte.
1442    #[must_use]
1443    pub const fn root_start_byte(&self) -> u32 {
1444        self.root_start_byte
1445    }
1446
1447    /// Return the root end byte.
1448    #[must_use]
1449    pub const fn root_end_byte(&self) -> u32 {
1450        self.root_end_byte
1451    }
1452
1453    /// Return the exact Tree-sitter root error state.
1454    #[must_use]
1455    pub const fn root_has_error(&self) -> bool {
1456        self.root_has_error
1457    }
1458
1459    /// Return the named structural-node count.
1460    #[must_use]
1461    pub const fn named_node_count(&self) -> u32 {
1462        self.named_node_count
1463    }
1464
1465    /// Return the error-node count.
1466    #[must_use]
1467    pub const fn error_node_count(&self) -> u32 {
1468        self.error_node_count
1469    }
1470
1471    /// Return the missing-node count.
1472    #[must_use]
1473    pub const fn missing_node_count(&self) -> u32 {
1474        self.missing_node_count
1475    }
1476
1477    /// Return the maximum observed structural depth.
1478    #[must_use]
1479    pub const fn maximum_depth(&self) -> u32 {
1480        self.maximum_depth
1481    }
1482}
1483
1484impl<'de> Deserialize<'de> for ParserCompletionEvidence {
1485    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1486    where
1487        D: Deserializer<'de>,
1488    {
1489        let wire = ParserCompletionEvidenceWire::deserialize(deserializer)?;
1490        Self::new(
1491            wire.root_kind,
1492            wire.root_start_byte,
1493            wire.root_end_byte,
1494            wire.root_has_error,
1495            wire.named_node_count,
1496            wire.error_node_count,
1497            wire.missing_node_count,
1498            wire.maximum_depth,
1499        )
1500        .map_err(serde::de::Error::custom)
1501    }
1502}
1503
1504/// Strict wire projection for structural completion evidence.
1505#[derive(Deserialize)]
1506#[serde(deny_unknown_fields)]
1507struct ParserCompletionEvidenceWire {
1508    /// Exact root syntax kind.
1509    root_kind: ParserSyntaxKind,
1510    /// Root start byte.
1511    root_start_byte: u32,
1512    /// Root end byte.
1513    root_end_byte: u32,
1514    /// Exact Tree-sitter root error state.
1515    root_has_error: bool,
1516    /// Named structural-node count.
1517    named_node_count: u32,
1518    /// Error-node count.
1519    error_node_count: u32,
1520    /// Missing-node count.
1521    missing_node_count: u32,
1522    /// Maximum observed structural depth.
1523    maximum_depth: u32,
1524}
1525
1526/// Successful worker completion for one authenticated request.
1527#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1528#[serde(deny_unknown_fields)]
1529pub struct ParserCompletion {
1530    /// Complete response identity.
1531    identity: ParserResponseIdentity,
1532    /// Small bounded structural evidence.
1533    evidence: ParserCompletionEvidence,
1534}
1535
1536impl ParserCompletion {
1537    /// Construct a successful worker completion.
1538    #[must_use]
1539    pub const fn new(identity: ParserResponseIdentity, evidence: ParserCompletionEvidence) -> Self {
1540        Self { identity, evidence }
1541    }
1542
1543    /// Validate response identity and structural limits.
1544    ///
1545    /// # Errors
1546    ///
1547    /// Returns an error for an identity mismatch or structural request-limit overflow.
1548    fn validate_for(&self, request: &ParserRequest) -> Result<(), ParserProtocolError> {
1549        self.identity.validate_for(request)?;
1550        self.evidence.validate_for(request)
1551    }
1552
1553    /// Borrow the bounded structural evidence.
1554    #[must_use]
1555    pub const fn evidence(&self) -> &ParserCompletionEvidence {
1556        &self.evidence
1557    }
1558}
1559
1560/// Stable closed worker failure codes.
1561#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1562#[serde(rename_all = "snake_case")]
1563pub enum ParserFailureCode {
1564    /// Request control failed validation.
1565    InvalidRequest,
1566    /// Raw source failed authentication or validation.
1567    InvalidSource,
1568    /// The resident worker does not own the requested language.
1569    LanguageMismatch,
1570    /// The loaded parser artifact does not match the expected identity.
1571    ArtifactMismatch,
1572    /// The parser rejected the source without producing valid evidence.
1573    ParseRejected,
1574    /// A bounded resource limit was reached.
1575    LimitExceeded,
1576    /// The supervisor cancelled the request.
1577    Cancelled,
1578    /// The worker encountered a protocol invariant violation.
1579    ProtocolViolation,
1580    /// The worker failed without a more specific safe classification.
1581    InternalFailure,
1582}
1583
1584/// Closed worker failure for one authenticated request.
1585#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1586#[serde(deny_unknown_fields)]
1587pub struct ParserFailure {
1588    /// Complete response identity.
1589    identity: ParserResponseIdentity,
1590    /// Stable closed failure classification.
1591    code: ParserFailureCode,
1592}
1593
1594impl ParserFailure {
1595    /// Construct a closed worker failure.
1596    #[must_use]
1597    pub const fn new(identity: ParserResponseIdentity, code: ParserFailureCode) -> Self {
1598        Self { identity, code }
1599    }
1600
1601    /// Validate the complete response identity against a request.
1602    ///
1603    /// # Errors
1604    ///
1605    /// Returns an error naming the first mismatched identity component.
1606    pub fn validate_for(&self, request: &ParserRequest) -> Result<(), ParserProtocolError> {
1607        self.identity.validate_for(request)
1608    }
1609
1610    /// Return the stable failure code.
1611    #[must_use]
1612    pub const fn code(&self) -> ParserFailureCode {
1613        self.code
1614    }
1615}
1616
1617/// Closed strict-JSON control messages in the parser-worker protocol.
1618#[derive(Clone, Debug, Eq, PartialEq)]
1619pub enum ParserControl {
1620    /// Supervisor session opening before containment-ready acknowledgement.
1621    SessionOpen(ParserSessionOpen),
1622    /// Worker containment-ready state.
1623    Ready(ParserReady),
1624    /// Supervisor parse request.
1625    Request(ParserRequest),
1626    /// Worker progress observation.
1627    Progress(ParserProgress),
1628    /// Worker successful completion.
1629    Completion(ParserCompletion),
1630    /// Worker closed failure.
1631    Failure(ParserFailure),
1632}
1633
1634impl ParserControl {
1635    /// Return the closed frame kind for this control message.
1636    #[must_use]
1637    pub const fn frame_kind(&self) -> ParserFrameKind {
1638        match self {
1639            Self::SessionOpen(_) => ParserFrameKind::SessionOpen,
1640            Self::Ready(_) => ParserFrameKind::Ready,
1641            Self::Request(_) => ParserFrameKind::Request,
1642            Self::Progress(_) => ParserFrameKind::Progress,
1643            Self::Completion(_) => ParserFrameKind::Completion,
1644            Self::Failure(_) => ParserFrameKind::Failure,
1645        }
1646    }
1647}
1648
1649/// Deterministically serialize and frame one strict control message.
1650///
1651/// Struct field order and closed enums define the canonical JSON ordering; the
1652/// protocol deliberately contains no map-valued control fields.
1653///
1654/// # Errors
1655///
1656/// Returns an error for serialization failure or a control-frame byte overflow.
1657pub fn encode_parser_control(control: &ParserControl) -> Result<Vec<u8>, ParserProtocolError> {
1658    let kind = control.frame_kind();
1659    let payload = match control {
1660        ParserControl::SessionOpen(value) => serde_json::to_vec(value),
1661        ParserControl::Ready(value) => serde_json::to_vec(value),
1662        ParserControl::Request(value) => serde_json::to_vec(value),
1663        ParserControl::Progress(value) => serde_json::to_vec(value),
1664        ParserControl::Completion(value) => serde_json::to_vec(value),
1665        ParserControl::Failure(value) => serde_json::to_vec(value),
1666    }
1667    .map_err(|source| ParserProtocolError::ControlSerialization { kind, source })?;
1668    encode_parser_frame(kind, &payload)
1669}
1670
1671/// Decode one strict pre-READY supervisor session opening.
1672///
1673/// # Errors
1674///
1675/// Returns an error for another frame kind, unknown fields, malformed JSON, or
1676/// any typed session validation failure.
1677pub fn decode_parser_session_open(
1678    frame: ParserFrame<'_>,
1679) -> Result<ParserSessionOpen, ParserProtocolError> {
1680    if frame.kind() != ParserFrameKind::SessionOpen {
1681        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1682    }
1683    decode_control_json(frame.payload(), ParserFrameKind::SessionOpen)
1684}
1685
1686/// Decode and validate READY against the exact supervised launch contract.
1687///
1688/// # Errors
1689///
1690/// Returns an error for another frame kind, strict JSON failure, or launch-
1691/// identity mismatch.
1692pub fn decode_parser_ready_for_launch(
1693    frame: ParserFrame<'_>,
1694    session: &ParserSessionIdentity,
1695    artifact: &ParserArtifactIdentity,
1696    containment: ParserContainmentKind,
1697) -> Result<ParserReady, ParserProtocolError> {
1698    if frame.kind() != ParserFrameKind::Ready {
1699        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1700    }
1701    let ready: ParserReady = decode_control_json(frame.payload(), ParserFrameKind::Ready)?;
1702    ready.validate_for(session, artifact, containment)?;
1703    Ok(ready)
1704}
1705
1706/// Decode one strict request for the exact ready worker session.
1707///
1708/// # Errors
1709///
1710/// Returns an error for another frame kind, unknown fields, malformed JSON,
1711/// typed request validation failure, or worker-session identity mismatch.
1712pub fn decode_parser_request_for_session(
1713    frame: ParserFrame<'_>,
1714    session: &ParserSessionIdentity,
1715    artifact: &ParserArtifactIdentity,
1716) -> Result<ParserRequest, ParserProtocolError> {
1717    if frame.kind() != ParserFrameKind::Request {
1718        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1719    }
1720    let request: ParserRequest = decode_control_json(frame.payload(), ParserFrameKind::Request)?;
1721    request.validate_for_session(session, artifact)?;
1722    Ok(request)
1723}
1724
1725/// Decode and validate one progress frame against its request and prior progress.
1726///
1727/// # Errors
1728///
1729/// Returns an error for another frame kind, strict JSON failure, response-identity
1730/// mismatch, non-contiguous sequence, regressed work, or changed total work.
1731pub fn decode_parser_progress_for_request(
1732    frame: ParserFrame<'_>,
1733    request: &ParserRequest,
1734    previous: Option<&ParserProgress>,
1735) -> Result<(ParserProgress, ParserProgressDisposition), ParserProtocolError> {
1736    if frame.kind() != ParserFrameKind::Progress {
1737        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1738    }
1739    let progress: ParserProgress = decode_control_json(frame.payload(), ParserFrameKind::Progress)?;
1740    let disposition = progress.validate_for(request, previous)?;
1741    Ok((progress, disposition))
1742}
1743
1744/// Decode and validate one completion against the exact request and received bytes.
1745///
1746/// The request-specific output budget is checked against the original frame
1747/// payload before JSON decoding, so whitespace or alternate JSON escaping
1748/// cannot evade the byte ceiling through canonical re-serialization.
1749///
1750/// # Errors
1751///
1752/// Returns an error for another frame kind, an exact payload-byte overflow,
1753/// strict JSON failure, response-identity mismatch, or structural limit breach.
1754pub fn decode_parser_completion_for_request(
1755    frame: ParserFrame<'_>,
1756    request: &ParserRequest,
1757) -> Result<ParserCompletion, ParserProtocolError> {
1758    if frame.kind() != ParserFrameKind::Completion {
1759        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1760    }
1761    let output_bytes = u32::try_from(frame.payload().len()).map_err(|_source| {
1762        request_limit_exceeded(
1763            "completion.output_bytes",
1764            u32::MAX,
1765            request.limits.output_bytes,
1766        )
1767    })?;
1768    if output_bytes > request.limits.output_bytes {
1769        return Err(request_limit_exceeded(
1770            "completion.output_bytes",
1771            output_bytes,
1772            request.limits.output_bytes,
1773        ));
1774    }
1775    let completion: ParserCompletion =
1776        decode_control_json(frame.payload(), ParserFrameKind::Completion)?;
1777    completion.validate_for(request)?;
1778    Ok(completion)
1779}
1780
1781/// Decode and validate one failure frame against its exact request identity.
1782///
1783/// # Errors
1784///
1785/// Returns an error for another frame kind, strict JSON failure, or response-
1786/// identity mismatch.
1787pub fn decode_parser_failure_for_request(
1788    frame: ParserFrame<'_>,
1789    request: &ParserRequest,
1790) -> Result<ParserFailure, ParserProtocolError> {
1791    if frame.kind() != ParserFrameKind::Failure {
1792        return Err(ParserProtocolError::UnexpectedFrameKind { kind: frame.kind() });
1793    }
1794    let failure: ParserFailure = decode_control_json(frame.payload(), ParserFrameKind::Failure)?;
1795    failure.validate_for(request)?;
1796    Ok(failure)
1797}
1798
1799/// Decode one concrete strict control payload.
1800fn decode_control_json<T>(payload: &[u8], kind: ParserFrameKind) -> Result<T, ParserProtocolError>
1801where
1802    T: for<'de> Deserialize<'de>,
1803{
1804    serde_json::from_slice(payload)
1805        .map_err(|source| ParserProtocolError::InvalidControlJson { kind, source })
1806}
1807
1808/// Validate one non-zero request limit against its hard protocol ceiling.
1809fn validate_nonzero_limit(
1810    field: &'static str,
1811    value: u32,
1812    maximum: u32,
1813) -> Result<(), ParserProtocolError> {
1814    if value == 0 || value > maximum {
1815        return Err(ParserProtocolError::InvalidField {
1816            field,
1817            reason: "expected a non-zero value within the hard ceiling",
1818        });
1819    }
1820    Ok(())
1821}
1822
1823/// Construct a stable response-identity mismatch.
1824const fn response_identity_mismatch(field: &'static str) -> ParserProtocolError {
1825    ParserProtocolError::ResponseIdentityMismatch { field }
1826}
1827
1828/// Construct a stable containment-ready identity mismatch.
1829const fn ready_identity_mismatch(field: &'static str) -> ParserProtocolError {
1830    ParserProtocolError::ReadyIdentityMismatch { field }
1831}
1832
1833/// Construct a stable request-to-session identity mismatch.
1834const fn request_identity_mismatch(field: &'static str) -> ParserProtocolError {
1835    ParserProtocolError::RequestIdentityMismatch { field }
1836}
1837
1838/// Construct a stable request-limit overflow.
1839const fn request_limit_exceeded(
1840    field: &'static str,
1841    actual: u32,
1842    maximum: u32,
1843) -> ParserProtocolError {
1844    ParserProtocolError::RequestLimitExceeded {
1845        field,
1846        actual,
1847        maximum,
1848    }
1849}
1850
1851/// Construct a stable progress-regression error.
1852const fn progress_regression(field: &'static str) -> ParserProtocolError {
1853    ParserProtocolError::ProgressRegression { field }
1854}
1855
1856#[cfg(test)]
1857mod tests {
1858    use super::*;
1859    use std::fmt::Debug;
1860    use std::io;
1861
1862    #[test]
1863    fn control_and_raw_source_frames_round_trip_deterministically()
1864    -> Result<(), Box<dyn std::error::Error>> {
1865        let (request, source) = request_fixture(64 * 1024)?;
1866        let controls = [
1867            ParserControl::SessionOpen(ParserSessionOpen::new(request.session.clone())),
1868            ParserControl::Ready(ParserReady::new(
1869                request.session.clone(),
1870                request.artifact.clone(),
1871                ParserContainmentKind::LinuxLandlockSeccomp,
1872            )),
1873            ParserControl::Request(request.clone()),
1874            ParserControl::Progress(ParserProgress::new(
1875                ParserResponseIdentity::for_request(&request),
1876                1,
1877                ParserProgressStage::Accepted,
1878                0,
1879                Some(10),
1880            )?),
1881            ParserControl::Completion(ParserCompletion::new(
1882                ParserResponseIdentity::for_request(&request),
1883                evidence_fixture()?,
1884            )),
1885            ParserControl::Failure(ParserFailure::new(
1886                ParserResponseIdentity::for_request(&request),
1887                ParserFailureCode::ParseRejected,
1888            )),
1889        ];
1890        for control in controls {
1891            let first = encode_parser_control(&control)?;
1892            let second = encode_parser_control(&control)?;
1893            require_eq(&first, &second, "control serialization")?;
1894            let frame = ParserFrame::decode_exact(&first)?;
1895            match &control {
1896                ParserControl::SessionOpen(expected) => {
1897                    require_eq(
1898                        &decode_parser_session_open(frame)?,
1899                        expected,
1900                        "session-open round trip",
1901                    )?;
1902                }
1903                ParserControl::Ready(expected) => {
1904                    require_eq(
1905                        &decode_parser_ready_for_launch(
1906                            frame,
1907                            request.session(),
1908                            request.artifact(),
1909                            ParserContainmentKind::LinuxLandlockSeccomp,
1910                        )?,
1911                        expected,
1912                        "ready round trip",
1913                    )?;
1914                }
1915                ParserControl::Request(expected) => {
1916                    require_eq(
1917                        &decode_parser_request_for_session(
1918                            frame,
1919                            request.session(),
1920                            request.artifact(),
1921                        )?,
1922                        expected,
1923                        "request round trip",
1924                    )?;
1925                }
1926                ParserControl::Progress(expected) => {
1927                    let (actual, disposition) =
1928                        decode_parser_progress_for_request(frame, &request, None)?;
1929                    require_eq(&actual, expected, "progress round trip")?;
1930                    require_eq(
1931                        &disposition,
1932                        &ParserProgressDisposition::Advanced,
1933                        "progress disposition",
1934                    )?;
1935                }
1936                ParserControl::Completion(expected) => {
1937                    require_eq(
1938                        &decode_parser_completion_for_request(frame, &request)?,
1939                        expected,
1940                        "completion round trip",
1941                    )?;
1942                }
1943                ParserControl::Failure(expected) => {
1944                    require_eq(
1945                        &decode_parser_failure_for_request(frame, &request)?,
1946                        expected,
1947                        "failure round trip",
1948                    )?;
1949                }
1950            }
1951        }
1952
1953        let encoded_source = encode_parser_frame(ParserFrameKind::RawSource, source)?;
1954        let source_frame = ParserFrame::decode_exact(&encoded_source)?;
1955        require_eq(&source_frame.payload(), &source, "raw-source round trip")?;
1956        request.validate_source_frame(source_frame)?;
1957        Ok(())
1958    }
1959
1960    #[test]
1961    fn strict_controls_reject_unknown_fields_and_frame_kinds()
1962    -> Result<(), Box<dyn std::error::Error>> {
1963        let (request, _source) = request_fixture(64 * 1024)?;
1964        let mut value = serde_json::to_value(&request)?;
1965        let Some(object) = value.as_object_mut() else {
1966            return Err("request did not serialize as an object".into());
1967        };
1968        object.insert(
1969            "repository_path".to_string(),
1970            serde_json::json!("src/lib.rs"),
1971        );
1972        let frame_bytes =
1973            encode_parser_frame(ParserFrameKind::Request, &serde_json::to_vec(&value)?)?;
1974        let error = decode_parser_request_for_session(
1975            ParserFrame::decode_exact(&frame_bytes)?,
1976            request.session(),
1977            request.artifact(),
1978        );
1979        require(
1980            matches!(error, Err(ParserProtocolError::InvalidControlJson { .. })),
1981            "unknown request field was accepted",
1982        )?;
1983
1984        let mut unknown = ParserFrameHeader::new(ParserFrameKind::Request, 0)?.encode();
1985        unknown[3] = 99;
1986        require(
1987            matches!(
1988                ParserFrameHeader::decode(&unknown),
1989                Err(ParserProtocolError::UnknownFrameKind { actual: 99 })
1990            ),
1991            "unknown frame kind was accepted",
1992        )?;
1993        Ok(())
1994    }
1995
1996    #[test]
1997    fn protocol_version_and_response_identity_mismatches_are_rejected()
1998    -> Result<(), Box<dyn std::error::Error>> {
1999        let (request, _source) = request_fixture(64 * 1024)?;
2000        let mut value = serde_json::to_value(&request)?;
2001        value["protocol_version"] = serde_json::json!(PARSER_PROTOCOL_VERSION + 1);
2002        let frame_bytes =
2003            encode_parser_frame(ParserFrameKind::Request, &serde_json::to_vec(&value)?)?;
2004        require(
2005            matches!(
2006                decode_parser_request_for_session(
2007                    ParserFrame::decode_exact(&frame_bytes)?,
2008                    request.session(),
2009                    request.artifact(),
2010                ),
2011                Err(ParserProtocolError::InvalidControlJson { .. })
2012            ),
2013            "unsupported control version was accepted",
2014        )?;
2015
2016        let (other_request, _other_source) = request_fixture(64 * 1024)?;
2017        let mismatched = ParserCompletion::new(
2018            ParserResponseIdentity {
2019                request_id: ParserRequestIdentity::new(2)?,
2020                ..ParserResponseIdentity::for_request(&other_request)
2021            },
2022            evidence_fixture()?,
2023        );
2024        let mismatched_frame = encode_parser_control(&ParserControl::Completion(mismatched))?;
2025        require(
2026            matches!(
2027                decode_parser_completion_for_request(
2028                    ParserFrame::decode_exact(&mismatched_frame)?,
2029                    &request,
2030                ),
2031                Err(ParserProtocolError::ResponseIdentityMismatch {
2032                    field: "request_id"
2033                })
2034            ),
2035            "mismatched response request identity was accepted",
2036        )?;
2037        Ok(())
2038    }
2039
2040    #[test]
2041    fn ready_is_bound_to_the_exact_session_artifact_and_containment()
2042    -> Result<(), Box<dyn std::error::Error>> {
2043        let (request, _source) = request_fixture(64 * 1024)?;
2044        let ready = ParserReady::new(
2045            request.session.clone(),
2046            request.artifact.clone(),
2047            ParserContainmentKind::LinuxLandlockSeccomp,
2048        );
2049        let encoded = encode_parser_control(&ParserControl::Ready(ready))?;
2050
2051        for (session, artifact, containment, field) in [
2052            (
2053                ParserSessionIdentity::for_entropy(b"another-session"),
2054                request.artifact.clone(),
2055                ParserContainmentKind::LinuxLandlockSeccomp,
2056                "session",
2057            ),
2058            (
2059                request.session.clone(),
2060                ParserArtifactIdentity::for_bytes(b"another-artifact"),
2061                ParserContainmentKind::LinuxLandlockSeccomp,
2062                "artifact",
2063            ),
2064            (
2065                request.session,
2066                request.artifact,
2067                ParserContainmentKind::WindowsAppContainerJob,
2068                "containment",
2069            ),
2070        ] {
2071            require(
2072                matches!(
2073                    decode_parser_ready_for_launch(
2074                        ParserFrame::decode_exact(&encoded)?,
2075                        &session,
2076                        &artifact,
2077                        containment,
2078                    ),
2079                    Err(ParserProtocolError::ReadyIdentityMismatch { field: actual })
2080                        if actual == field
2081                ),
2082                "mismatched containment-ready identity was accepted",
2083            )?;
2084        }
2085        Ok(())
2086    }
2087
2088    #[test]
2089    fn session_open_carries_only_protocol_and_process_freshness()
2090    -> Result<(), Box<dyn std::error::Error>> {
2091        let open = ParserSessionOpen::new(ParserSessionIdentity::for_entropy(b"session"));
2092        let mut value = serde_json::to_value(&open)?;
2093        let Some(object) = value.as_object_mut() else {
2094            return Err("session opening did not serialize as an object".into());
2095        };
2096        require(
2097            object.len() == 2
2098                && object.contains_key("protocol_version")
2099                && object.contains_key("session"),
2100            "session opening exposed more than protocol and process freshness",
2101        )?;
2102        object.insert("grammar_id".to_owned(), serde_json::json!("rust"));
2103        let encoded =
2104            encode_parser_frame(ParserFrameKind::SessionOpen, &serde_json::to_vec(&value)?)?;
2105        require(
2106            matches!(
2107                decode_parser_session_open(ParserFrame::decode_exact(&encoded)?),
2108                Err(ParserProtocolError::InvalidControlJson { .. })
2109            ),
2110            "session opening accepted grammar input before READY",
2111        )?;
2112        Ok(())
2113    }
2114
2115    #[test]
2116    fn requests_and_responses_cannot_be_replayed_across_sessions()
2117    -> Result<(), Box<dyn std::error::Error>> {
2118        let (request, _source) = request_fixture(64 * 1024)?;
2119        let other_session = ParserSessionIdentity::for_entropy(b"another-session");
2120        let encoded_request = encode_parser_control(&ParserControl::Request(request.clone()))?;
2121        require(
2122            matches!(
2123                decode_parser_request_for_session(
2124                    ParserFrame::decode_exact(&encoded_request)?,
2125                    &other_session,
2126                    request.artifact(),
2127                ),
2128                Err(ParserProtocolError::RequestIdentityMismatch { field: "session" })
2129            ),
2130            "request from another worker session was accepted",
2131        )?;
2132        require(
2133            matches!(
2134                decode_parser_request_for_session(
2135                    ParserFrame::decode_exact(&encoded_request)?,
2136                    request.session(),
2137                    &ParserArtifactIdentity::for_bytes(b"another-artifact"),
2138                ),
2139                Err(ParserProtocolError::RequestIdentityMismatch { field: "artifact" })
2140            ),
2141            "request for another pack artifact was accepted",
2142        )?;
2143
2144        let other_request = ParserRequest::new(
2145            other_session,
2146            request.request_id,
2147            request.artifact.clone(),
2148            request.language.clone(),
2149            request.source.clone(),
2150            request.limits,
2151        );
2152        let progress = ParserProgress::new(
2153            ParserResponseIdentity::for_request(&request),
2154            1,
2155            ParserProgressStage::Accepted,
2156            0,
2157            Some(1),
2158        )?;
2159        let encoded_progress = encode_parser_control(&ParserControl::Progress(progress))?;
2160        require(
2161            matches!(
2162                decode_parser_progress_for_request(
2163                    ParserFrame::decode_exact(&encoded_progress)?,
2164                    &other_request,
2165                    None,
2166                ),
2167                Err(ParserProtocolError::ResponseIdentityMismatch { field: "session" })
2168            ),
2169            "progress from another worker session was accepted",
2170        )?;
2171
2172        let completion = ParserCompletion::new(
2173            ParserResponseIdentity::for_request(&request),
2174            evidence_fixture()?,
2175        );
2176        let encoded_completion = encode_parser_control(&ParserControl::Completion(completion))?;
2177        require(
2178            matches!(
2179                decode_parser_completion_for_request(
2180                    ParserFrame::decode_exact(&encoded_completion)?,
2181                    &other_request,
2182                ),
2183                Err(ParserProtocolError::ResponseIdentityMismatch { field: "session" })
2184            ),
2185            "response from another worker session was accepted",
2186        )?;
2187
2188        let failure = ParserFailure::new(
2189            ParserResponseIdentity::for_request(&request),
2190            ParserFailureCode::ParseRejected,
2191        );
2192        let encoded_failure = encode_parser_control(&ParserControl::Failure(failure))?;
2193        require(
2194            matches!(
2195                decode_parser_failure_for_request(
2196                    ParserFrame::decode_exact(&encoded_failure)?,
2197                    &other_request,
2198                ),
2199                Err(ParserProtocolError::ResponseIdentityMismatch { field: "session" })
2200            ),
2201            "failure from another worker session was accepted",
2202        )?;
2203        Ok(())
2204    }
2205
2206    #[test]
2207    fn declared_source_overflow_is_rejected_from_header_alone()
2208    -> Result<(), Box<dyn std::error::Error>> {
2209        let mut header = ParserFrameHeader::new(ParserFrameKind::RawSource, 0)?.encode();
2210        header[4..8].copy_from_slice(&(PARSER_MAX_SOURCE_BYTES + 1).to_be_bytes());
2211        require(
2212            matches!(
2213                ParserFrameHeader::decode(&header),
2214                Err(ParserProtocolError::FramePayloadTooLarge {
2215                    kind: ParserFrameKind::RawSource,
2216                    actual,
2217                    maximum: PARSER_MAX_SOURCE_BYTES,
2218                }) if actual == PARSER_MAX_SOURCE_BYTES + 1
2219            ),
2220            "oversized declared source was not rejected from the header",
2221        )?;
2222        let mut ready_header = ParserFrameHeader::new(ParserFrameKind::Ready, 0)?.encode();
2223        ready_header[4..8].copy_from_slice(&(PARSER_MAX_CONTROL_BYTES + 1).to_be_bytes());
2224        require(
2225            matches!(
2226                ParserFrameHeader::decode(&ready_header),
2227                Err(ParserProtocolError::FramePayloadTooLarge {
2228                    kind: ParserFrameKind::Ready,
2229                    actual,
2230                    maximum: PARSER_MAX_CONTROL_BYTES,
2231                }) if actual == PARSER_MAX_CONTROL_BYTES + 1
2232            ),
2233            "oversized declared READY was not rejected from the header",
2234        )?;
2235        Ok(())
2236    }
2237
2238    #[test]
2239    fn source_length_and_digest_are_both_authenticated() -> Result<(), Box<dyn std::error::Error>> {
2240        let identity = ParserSourceIdentity::for_bytes(b"abc")?;
2241        require(
2242            matches!(
2243                identity.validate_bytes(b"ab"),
2244                Err(ParserProtocolError::SourceLengthMismatch { .. })
2245            ),
2246            "source length mismatch was accepted",
2247        )?;
2248        require(
2249            matches!(
2250                identity.validate_bytes(b"abd"),
2251                Err(ParserProtocolError::SourceDigestMismatch)
2252            ),
2253            "source digest mismatch was accepted",
2254        )?;
2255        identity.validate_bytes(b"abc")?;
2256        Ok(())
2257    }
2258
2259    #[test]
2260    fn progress_is_contiguous_monotonic_and_exposes_no_progress()
2261    -> Result<(), Box<dyn std::error::Error>> {
2262        let (request, _source) = request_fixture(64 * 1024)?;
2263        let identity = ParserResponseIdentity::for_request(&request);
2264        let first = ParserProgress::new(
2265            identity.clone(),
2266            1,
2267            ParserProgressStage::Accepted,
2268            0,
2269            Some(10),
2270        )?;
2271        require_eq(
2272            &first.validate_for(&request, None)?,
2273            &ParserProgressDisposition::Advanced,
2274            "initial progress",
2275        )?;
2276        let heartbeat = ParserProgress::new(
2277            identity.clone(),
2278            2,
2279            ParserProgressStage::Accepted,
2280            0,
2281            Some(10),
2282        )?;
2283        require_eq(
2284            &heartbeat.validate_for(&request, Some(&first))?,
2285            &ParserProgressDisposition::NoProgress,
2286            "no-progress heartbeat",
2287        )?;
2288        let advanced = ParserProgress::new(
2289            identity.clone(),
2290            3,
2291            ParserProgressStage::Parsing,
2292            4,
2293            Some(10),
2294        )?;
2295        require_eq(
2296            &advanced.validate_for(&request, Some(&heartbeat))?,
2297            &ParserProgressDisposition::Advanced,
2298            "advanced progress",
2299        )?;
2300        let regressed =
2301            ParserProgress::new(identity, 4, ParserProgressStage::Accepted, 4, Some(10))?;
2302        require(
2303            matches!(
2304                regressed.validate_for(&request, Some(&advanced)),
2305                Err(ParserProtocolError::ProgressRegression { field: "stage" })
2306            ),
2307            "progress stage regression was accepted",
2308        )?;
2309        Ok(())
2310    }
2311
2312    #[test]
2313    fn exact_frame_decode_rejects_trailing_bytes() -> Result<(), Box<dyn std::error::Error>> {
2314        let mut encoded = encode_parser_frame(ParserFrameKind::RawSource, b"abc")?;
2315        encoded.push(0);
2316        require(
2317            matches!(
2318                ParserFrame::decode_exact(&encoded),
2319                Err(ParserProtocolError::TrailingFrameBytes {
2320                    declared: 3,
2321                    actual: 4,
2322                })
2323            ),
2324            "trailing frame bytes were accepted",
2325        )?;
2326        Ok(())
2327    }
2328
2329    #[test]
2330    fn request_and_completion_limits_are_enforced() -> Result<(), Box<dyn std::error::Error>> {
2331        require(
2332            ParserRequestLimits::new(PARSER_MAX_OUTPUT_BYTES + 1, 1, 1).is_err(),
2333            "oversized output limit was accepted",
2334        )?;
2335        require(
2336            ParserRequestLimits::new(1, 0, 1).is_err(),
2337            "zero node limit was accepted",
2338        )?;
2339        require(
2340            ParserRequestLimits::new(1, 1, PARSER_MAX_TREE_DEPTH + 1).is_err(),
2341            "oversized depth limit was accepted",
2342        )?;
2343
2344        let (unbounded_request, _source) = request_fixture(PARSER_MAX_OUTPUT_BYTES)?;
2345        let completion = ParserCompletion::new(
2346            ParserResponseIdentity::for_request(&unbounded_request),
2347            evidence_fixture()?,
2348        );
2349        require(
2350            !completion.evidence().root_has_error(),
2351            "completion root error state did not round trip",
2352        )?;
2353        let mut missing_root_error = serde_json::to_value(&completion)?;
2354        missing_root_error
2355            .get_mut("evidence")
2356            .and_then(serde_json::Value::as_object_mut)
2357            .and_then(|evidence| evidence.remove("root_has_error"))
2358            .ok_or_else(|| io::Error::other("completion fixture lacked root_has_error"))?;
2359        let missing_root_error_frame = encode_parser_frame(
2360            ParserFrameKind::Completion,
2361            &serde_json::to_vec(&missing_root_error)?,
2362        )?;
2363        require(
2364            matches!(
2365                decode_parser_completion_for_request(
2366                    ParserFrame::decode_exact(&missing_root_error_frame)?,
2367                    &unbounded_request,
2368                ),
2369                Err(ParserProtocolError::InvalidControlJson {
2370                    kind: ParserFrameKind::Completion,
2371                    ..
2372                })
2373            ),
2374            "completion accepted missing root_has_error evidence",
2375        )?;
2376        let canonical_payload = serde_json::to_vec(&completion)?;
2377        let canonical_bytes = u32::try_from(canonical_payload.len())?;
2378        let request = ParserRequest::new(
2379            unbounded_request.session,
2380            unbounded_request.request_id,
2381            unbounded_request.artifact,
2382            unbounded_request.language,
2383            unbounded_request.source,
2384            ParserRequestLimits::new(canonical_bytes, 100, 16)?,
2385        );
2386        let canonical_frame = encode_parser_frame(ParserFrameKind::Completion, &canonical_payload)?;
2387        decode_parser_completion_for_request(
2388            ParserFrame::decode_exact(&canonical_frame)?,
2389            &request,
2390        )?;
2391
2392        let mut padded_payload = canonical_payload;
2393        padded_payload.push(b' ');
2394        let padded_frame = encode_parser_frame(ParserFrameKind::Completion, &padded_payload)?;
2395        require(
2396            matches!(
2397                decode_parser_completion_for_request(
2398                    ParserFrame::decode_exact(&padded_frame)?,
2399                    &request,
2400                ),
2401                Err(ParserProtocolError::RequestLimitExceeded {
2402                    field: "completion.output_bytes",
2403                    actual,
2404                    maximum,
2405                })
2406                    if actual == canonical_bytes + 1 && maximum == canonical_bytes
2407            ),
2408            "padded completion exceeded exact request bytes without rejection",
2409        )?;
2410        Ok(())
2411    }
2412
2413    /// Build one complete valid request and its raw source.
2414    fn request_fixture(
2415        output_bytes: u32,
2416    ) -> Result<(ParserRequest, &'static [u8]), Box<dyn std::error::Error>> {
2417        let source = b"fn main() {}";
2418        Ok((
2419            ParserRequest::new(
2420                ParserSessionIdentity::for_entropy(b"supervised-session"),
2421                ParserRequestIdentity::new(1)?,
2422                ParserArtifactIdentity::for_bytes(b"artifact"),
2423                ParserLanguageIdentity::new("rust")?,
2424                ParserSourceIdentity::for_bytes(source)?,
2425                ParserRequestLimits::new(output_bytes, 100, 16)?,
2426            ),
2427            source,
2428        ))
2429    }
2430
2431    /// Build small valid completion evidence.
2432    fn evidence_fixture() -> Result<ParserCompletionEvidence, ParserProtocolError> {
2433        ParserCompletionEvidence::new(
2434            ParserSyntaxKind::new("source_file")?,
2435            0,
2436            12,
2437            false,
2438            5,
2439            0,
2440            0,
2441            3,
2442        )
2443    }
2444
2445    /// Return a test error when `condition` is false.
2446    fn require(condition: bool, message: &'static str) -> Result<(), Box<dyn std::error::Error>> {
2447        if condition {
2448            Ok(())
2449        } else {
2450            Err(io::Error::other(message).into())
2451        }
2452    }
2453
2454    /// Return a test error when two values differ.
2455    fn require_eq<T>(
2456        actual: &T,
2457        expected: &T,
2458        context: &'static str,
2459    ) -> Result<(), Box<dyn std::error::Error>>
2460    where
2461        T: Debug + PartialEq,
2462    {
2463        if actual == expected {
2464            Ok(())
2465        } else {
2466            Err(io::Error::other(format!(
2467                "{context}: expected {expected:?}, found {actual:?}"
2468            ))
2469            .into())
2470        }
2471    }
2472}