Skip to main content

projectatlas_core/
optional_parser_pack.rs

1//! Validated accepted-capability manifest for the optional native parser pack.
2
3use crate::language::{
4    ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION, BROAD_PARSER_PACK_ID,
5    LANGUAGE_CAPABILITY_REGISTRY_VERSION, OPTIONAL_GRAMMAR_CATALOG,
6    OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION, OPTIONAL_GRAMMAR_CATALOG_VERSION,
7    OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS, accepted_language_capability_digest,
8    language_capability, language_registry_digest,
9};
10use crate::optional_parser_protocol::{
11    PARSER_WORKER_JOB_MEMORY_BYTES, PARSER_WORKER_PROCESS_MEMORY_BYTES,
12};
13use blake3::Hasher;
14use serde::{Deserialize, Deserializer, Serialize};
15use std::collections::{BTreeMap, BTreeSet};
16use std::fmt;
17use thiserror::Error;
18
19/// Schema version of the logical optional parser-pack manifest.
20pub const OPTIONAL_PARSER_PACK_MANIFEST_SCHEMA_VERSION: u32 = 2;
21/// Compatibility version of the accepted optional parser capabilities.
22pub const OPTIONAL_PARSER_PACK_CAPABILITY_SET_VERSION: u32 = 2;
23/// Stable identity of the one logical optional native parser pack.
24pub const OPTIONAL_PARSER_PACK_ID: &str = BROAD_PARSER_PACK_ID;
25/// SHA-256 of the exactly pinned broad-grammar Cargo archive.
26pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_SHA256: &str =
27    "44dc94ef7a5f7f4247d88d5acdd26d842c8fc6f5eaf491a970c8e3d8fc9c9287";
28/// Full VCS revision embedded in the exactly pinned Cargo archive.
29pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_REVISION: &str =
30    "ce9e9c0974731d25b4b9426711a62d544d993368";
31/// Cargo archive path recorded by its embedded VCS metadata.
32pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_PATH_IN_VCS: &str = "crates/ts-pack-core";
33/// Exact upstream tag that owns the parser-source and native release assets.
34pub const OPTIONAL_GRAMMAR_CATALOG_RELEASE_TAG: &str = "v1.13.2";
35/// SHA-256 of the exactly pinned broad-grammar parser-source bundle.
36pub const OPTIONAL_GRAMMAR_CATALOG_SOURCE_BUNDLE_SHA256: &str =
37    "d684799dc664553c9c746d5fe676a5b599f9efcec4cad5450bec7ec5a29574a9";
38/// Exact `ProjectAtlas` release selected to consume the pack.
39pub const OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION: &str = "0.5.0-rc1";
40/// Exact Tree-sitter runtime selected by the consuming parser worker.
41pub const OPTIONAL_PARSER_PACK_TREE_SITTER_VERSION: &str = "0.26.13";
42/// Oldest grammar ABI accepted by the selected Tree-sitter runtime.
43pub const OPTIONAL_PARSER_PACK_MINIMUM_ABI: u32 = 13;
44/// Newest grammar ABI accepted by the selected Tree-sitter runtime.
45pub const OPTIONAL_PARSER_PACK_MAXIMUM_ABI: u32 = 15;
46/// Largest accepted serialized logical manifest.
47pub const OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES: usize = 32 * 1024 * 1024;
48/// Schema version of one immutable platform artifact manifest.
49pub const OPTIONAL_PARSER_PACK_ARTIFACT_SCHEMA_VERSION: u32 = 2;
50/// Schema version of the normalized native-audit report inside each platform artifact.
51pub const OPTIONAL_PARSER_PACK_NATIVE_AUDIT_SCHEMA_VERSION: u32 = 3;
52/// Schema version of the immutable native-import policy packaged with each artifact.
53pub const OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_SCHEMA_VERSION: u32 = 3;
54/// Exact ELF interpreter admitted for the Linux x86-64 parser worker.
55pub const OPTIONAL_PARSER_PACK_LINUX_RUNTIME_LOADER_BASENAME: &str = "ld-linux-x86-64.so.2";
56/// External runtime family required by the artifact-bound Windows containment broker.
57pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_RUNTIME_FAMILY: &str = "windows-net-framework-clr-v4";
58/// Native entry point of the managed Windows containment broker.
59pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_NATIVE_ENTRY_POINT: &str = "0x0000000000000000";
60/// Exact size of the CLR 2.0 runtime header carried by the managed broker.
61pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_CLR_RUNTIME_HEADER_SIZE: u32 = 72;
62/// Complete ordinary and delay-loaded PE dependency set for the managed broker.
63pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_PE_LOADER_LIBRARIES: &[&str] = &[];
64/// Complete managed P/Invoke module set for the shipped Windows containment broker.
65pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_MANAGED_MODULES: &[&str] =
66    &["advapi32.dll", "kernel32.dll", "userenv.dll"];
67/// Schema version of one fresh-runner platform proof.
68pub const OPTIONAL_PARSER_PACK_PLATFORM_PROOF_SCHEMA_VERSION: u32 = 2;
69/// Schema version of the exact supported-platform aggregate proof.
70pub const OPTIONAL_PARSER_PACK_PROOF_AGGREGATE_SCHEMA_VERSION: u32 = 2;
71/// Deliberately reduced Linux ceiling used by exact-artifact release verification.
72pub const OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES: u64 = 1024 * 1024;
73/// Smallest committed-memory ceiling accepted by the shipped Windows containment broker.
74pub const OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES: u64 = 16 * 1024 * 1024;
75/// Compressed-byte ceiling for one platform pack archive.
76pub const OPTIONAL_PARSER_PACK_MAX_ARCHIVE_BYTES: u64 = 64 * 1024 * 1024;
77/// Expanded-byte ceiling for one platform pack artifact.
78pub const OPTIONAL_PARSER_PACK_MAX_EXPANDED_BYTES: u64 = 512 * 1024 * 1024;
79/// Byte ceiling for one payload file.
80pub const OPTIONAL_PARSER_PACK_MAX_FILE_BYTES: u64 = 128 * 1024 * 1024;
81/// Byte ceiling for the immutable native-import policy consumed before containment.
82pub const OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_MAX_BYTES: u64 = 1024 * 1024;
83/// UTF-8 byte ceiling for one artifact-relative path.
84pub const OPTIONAL_PARSER_PACK_MAX_PATH_BYTES: usize = 256;
85/// File-entry ceiling for one platform pack artifact.
86pub const OPTIONAL_PARSER_PACK_MAX_FILE_ENTRIES: usize = 512;
87
88/// Absolute ceiling for accepted grammar rows in one logical pack.
89const MAX_ACCEPTED_GRAMMARS: usize = 512;
90/// Absolute ceiling for deduplicated exact license records.
91const MAX_LICENSE_RECORDS: usize = 1_024;
92/// Largest bounded identity or provenance field.
93const MAX_IDENTITY_BYTES: usize = 4_096;
94/// Largest exact license text retained in the logical manifest.
95const MAX_LICENSE_TEXT_BYTES: usize = 256 * 1024;
96/// Largest exact UTF-8 fixture source retained per polarity.
97const MAX_FIXTURE_SOURCE_BYTES: usize = 64 * 1024;
98/// Fixed non-grammar payload files common to every platform artifact.
99const OPTIONAL_PARSER_PACK_COMMON_PAYLOAD_FILES: usize = 6;
100/// Domain separator for one accepted grammar capability digest.
101const CAPABILITY_DIGEST_DOMAIN: &str = "projectatlas.optional-parser-capability.v1";
102/// Domain separator for the complete logical-manifest digest.
103const MANIFEST_DIGEST_DOMAIN: &str = "projectatlas.optional-parser-pack-manifest.v2";
104
105/// Failure while parsing or validating an optional parser-pack manifest.
106#[derive(Debug, Error)]
107pub enum OptionalParserPackManifestError {
108    /// Serialized input exceeded the bounded logical-manifest limit.
109    #[error("optional parser-pack manifest is {actual} bytes; maximum is {maximum}")]
110    ManifestTooLarge {
111        /// Observed serialized bytes.
112        actual: usize,
113        /// Accepted serialized-byte ceiling.
114        maximum: usize,
115    },
116    /// JSON decoding failed before domain validation.
117    #[error("invalid optional parser-pack manifest JSON")]
118    InvalidJson {
119        /// JSON parser failure.
120        #[source]
121        source: serde_json::Error,
122    },
123    /// One field violated its local representation contract.
124    #[error("invalid {field} for {owner}: {reason}")]
125    InvalidField {
126        /// Record or manifest identity that owns the field.
127        owner: String,
128        /// Stable field name.
129        field: &'static str,
130        /// Stable validation reason.
131        reason: &'static str,
132    },
133    /// Manifest metadata does not match the selected source or registry authority.
134    #[error("optional parser-pack binding {field} is {actual:?}; expected {expected:?}")]
135    BindingMismatch {
136        /// Mismatched binding field.
137        field: &'static str,
138        /// Required value.
139        expected: String,
140        /// Manifest value.
141        actual: String,
142    },
143    /// A deterministic sequence was not strictly sorted and unique.
144    #[error("optional parser-pack {field} must be strictly sorted and unique")]
145    NotSortedUnique {
146        /// Sequence that violated canonical ordering.
147        field: &'static str,
148    },
149    /// Accepted grammar or license membership violated its hard bound.
150    #[error("optional parser-pack {field} count {actual} is outside {minimum}..={maximum}")]
151    CountOutOfBounds {
152        /// Bounded collection.
153        field: &'static str,
154        /// Observed rows.
155        actual: usize,
156        /// Required minimum.
157        minimum: usize,
158        /// Absolute maximum.
159        maximum: usize,
160    },
161    /// A grammar identity is not a canonical optional registry row.
162    #[error("grammar {language_id:?} is not a canonical optional language capability")]
163    UnknownOptionalLanguage {
164        /// Rejected language identity.
165        language_id: String,
166    },
167    /// An optional row attempted to overlap a default-core capability owner.
168    #[error("grammar {language_id:?} overlaps a default-core language capability")]
169    BuiltInOverlap {
170        /// Default-core language identity.
171        language_id: String,
172    },
173    /// A grammar references a missing license record.
174    #[error("grammar {language_id:?} references unknown license record {license_id:?}")]
175    UnknownLicense {
176        /// Grammar language identity.
177        language_id: String,
178        /// Missing license record identity.
179        license_id: String,
180    },
181    /// A referenced license did not come from the grammar's pinned source revision.
182    #[error(
183        "grammar {language_id:?} license {license_id:?} does not match its source repository and revision"
184    )]
185    LicenseSourceMismatch {
186        /// Grammar language identity.
187        language_id: String,
188        /// Mismatched license record.
189        license_id: String,
190    },
191    /// Two accepted grammars claimed one runtime-loading identity.
192    #[error("optional parser-pack {field} {value:?} is owned by more than one grammar")]
193    DuplicateRuntimeIdentity {
194        /// Colliding identity class.
195        field: &'static str,
196        /// Colliding identity value.
197        value: String,
198    },
199    /// A grammar ABI claim does not fit the consuming runtime.
200    #[error("grammar {language_id:?} ABI claim is outside the consuming runtime window")]
201    AbiMismatch {
202        /// Grammar language identity.
203        language_id: String,
204    },
205    /// Embedded content or a canonical capability projection was changed without its digest.
206    #[error("{field} digest mismatch for {owner}")]
207    DigestMismatch {
208        /// Record whose content drifted.
209        owner: String,
210        /// Digest-bearing field.
211        field: &'static str,
212    },
213}
214
215/// Validated lowercase SHA-256 digest used for fetched or compiled artifacts.
216#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
217#[serde(transparent)]
218pub struct Sha256Digest(String);
219
220impl Sha256Digest {
221    /// Validate a lowercase 64-character SHA-256 hexadecimal digest.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error when `value` is not canonical lowercase hexadecimal.
226    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
227        let value = value.into();
228        validate_hex_digest(&value, "sha256")?;
229        Ok(Self(value))
230    }
231
232    /// Borrow the canonical hexadecimal digest.
233    #[must_use]
234    pub fn as_str(&self) -> &str {
235        &self.0
236    }
237}
238
239impl<'de> Deserialize<'de> for Sha256Digest {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: Deserializer<'de>,
243    {
244        let value = String::deserialize(deserializer)?;
245        Self::new(value).map_err(serde::de::Error::custom)
246    }
247}
248
249impl fmt::Display for Sha256Digest {
250    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
251        formatter.write_str(self.as_str())
252    }
253}
254
255/// Validated lowercase BLAKE3 digest used for embedded and canonical content.
256#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
257#[serde(transparent)]
258pub struct Blake3Digest(String);
259
260impl Blake3Digest {
261    /// Validate a lowercase 64-character BLAKE3 hexadecimal digest.
262    ///
263    /// # Errors
264    ///
265    /// Returns an error when `value` is not canonical lowercase hexadecimal.
266    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
267        let value = value.into();
268        validate_hex_digest(&value, "blake3")?;
269        Ok(Self(value))
270    }
271
272    /// Hash exact bytes through the manifest's canonical content algorithm.
273    #[must_use]
274    pub fn for_bytes(bytes: &[u8]) -> Self {
275        Self(blake3::hash(bytes).to_hex().to_string())
276    }
277
278    /// Borrow the canonical hexadecimal digest.
279    #[must_use]
280    pub fn as_str(&self) -> &str {
281        &self.0
282    }
283}
284
285impl<'de> Deserialize<'de> for Blake3Digest {
286    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
287    where
288        D: Deserializer<'de>,
289    {
290        let value = String::deserialize(deserializer)?;
291        Self::new(value).map_err(serde::de::Error::custom)
292    }
293}
294
295impl fmt::Display for Blake3Digest {
296    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
297        formatter.write_str(self.as_str())
298    }
299}
300
301/// Exact full Git revision used for source and license provenance.
302#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
303#[serde(transparent)]
304pub struct SourceRevision(String);
305
306impl SourceRevision {
307    /// Validate a full lowercase 40-character Git revision.
308    ///
309    /// # Errors
310    ///
311    /// Returns an error for abbreviated or non-canonical revisions.
312    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
313        let value = value.into();
314        if value.len() != 40
315            || !value
316                .bytes()
317                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
318        {
319            return Err(invalid_field(
320                "source revision",
321                "revision",
322                "expected 40 lowercase hexadecimal characters",
323            ));
324        }
325        Ok(Self(value))
326    }
327
328    /// Borrow the full revision.
329    #[must_use]
330    pub fn as_str(&self) -> &str {
331        &self.0
332    }
333}
334
335impl<'de> Deserialize<'de> for SourceRevision {
336    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
337    where
338        D: Deserializer<'de>,
339    {
340        let value = String::deserialize(deserializer)?;
341        Self::new(value).map_err(serde::de::Error::custom)
342    }
343}
344
345impl fmt::Display for SourceRevision {
346    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
347        formatter.write_str(self.as_str())
348    }
349}
350
351/// Safe C-compatible grammar export symbol.
352#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
353#[serde(transparent)]
354pub struct GrammarExportSymbol(String);
355
356impl GrammarExportSymbol {
357    /// Validate a C identifier without any path or loader syntax.
358    ///
359    /// # Errors
360    ///
361    /// Returns an error for an empty, oversized, or non-identifier value.
362    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
363        let value = value.into();
364        let mut bytes = value.bytes();
365        let valid_first = bytes
366            .next()
367            .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_');
368        if value.len() > 256
369            || !valid_first
370            || !bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
371        {
372            return Err(invalid_field(
373                &value,
374                "export_symbol",
375                "expected a C identifier of at most 256 ASCII bytes",
376            ));
377        }
378        Ok(Self(value))
379    }
380
381    /// Borrow the validated export symbol.
382    #[must_use]
383    pub fn as_str(&self) -> &str {
384        &self.0
385    }
386}
387
388impl<'de> Deserialize<'de> for GrammarExportSymbol {
389    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
390    where
391        D: Deserializer<'de>,
392    {
393        let value = String::deserialize(deserializer)?;
394        Self::new(value).map_err(serde::de::Error::custom)
395    }
396}
397
398/// Safe platform-neutral grammar library stem.
399#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
400#[serde(transparent)]
401pub struct GrammarLibraryStem(String);
402
403impl GrammarLibraryStem {
404    /// Validate a lowercase basename without directory or extension syntax.
405    ///
406    /// # Errors
407    ///
408    /// Returns an error for unsafe loader or path characters.
409    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
410        let value = value.into();
411        if value.is_empty()
412            || value.len() > 256
413            || !value.bytes().all(|byte| {
414                byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'_' | b'-')
415            })
416            || !value
417                .as_bytes()
418                .first()
419                .is_some_and(u8::is_ascii_alphanumeric)
420        {
421            return Err(invalid_field(
422                &value,
423                "library_stem",
424                "expected a lowercase ASCII basename of at most 256 bytes",
425            ));
426        }
427        Ok(Self(value))
428    }
429
430    /// Borrow the validated library stem.
431    #[must_use]
432    pub fn as_str(&self) -> &str {
433        &self.0
434    }
435}
436
437impl<'de> Deserialize<'de> for GrammarLibraryStem {
438    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
439    where
440        D: Deserializer<'de>,
441    {
442        let value = String::deserialize(deserializer)?;
443        Self::new(value).map_err(serde::de::Error::custom)
444    }
445}
446
447/// Native target with an accepted optional parser-pack artifact contract.
448#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
449pub enum PackPlatform {
450    /// Linux x86-64 GNU target.
451    #[serde(rename = "x86_64-unknown-linux-gnu")]
452    LinuxX86_64,
453    /// Windows x86-64 MSVC target.
454    #[serde(rename = "x86_64-pc-windows-msvc")]
455    WindowsX86_64,
456}
457
458/// Closed runtime capability for the optional parser-pack boundary.
459#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
460#[serde(rename_all = "snake_case", tag = "mode")]
461pub enum OptionalParserCapability {
462    /// The current tuple has an accepted pack and containment adapter.
463    Pack {
464        /// Accepted native target for the optional parser pack.
465        platform: PackPlatform,
466    },
467    /// The tuple has no optional pack, while built-in parsing remains available.
468    BuiltInOnly,
469}
470
471impl OptionalParserCapability {
472    /// Resolve one host tuple through the closed optional-parser authority.
473    #[must_use]
474    pub fn for_target(os: &str, architecture: &str) -> Self {
475        match (os, architecture) {
476            ("linux", "x86_64") => Self::Pack {
477                platform: PackPlatform::LinuxX86_64,
478            },
479            ("windows", "x86_64") => Self::Pack {
480                platform: PackPlatform::WindowsX86_64,
481            },
482            _ => Self::BuiltInOnly,
483        }
484    }
485
486    /// Resolve the capability for the process host tuple.
487    #[must_use]
488    pub fn current() -> Self {
489        Self::for_target(std::env::consts::OS, std::env::consts::ARCH)
490    }
491
492    /// Return the accepted pack target, or `None` for built-in-only hosts.
493    #[must_use]
494    pub const fn pack_platform(self) -> Option<PackPlatform> {
495        match self {
496            Self::Pack { platform } => Some(platform),
497            Self::BuiltInOnly => None,
498        }
499    }
500
501    /// Return whether built-in parser coverage remains available.
502    #[must_use]
503    pub const fn built_in_parsing_available(self) -> bool {
504        true
505    }
506}
507
508impl PackPlatform {
509    /// Complete optional-pack artifact target set in canonical order.
510    pub const ALL: &'static [Self] = &[Self::LinuxX86_64, Self::WindowsX86_64];
511
512    /// Return the canonical Rust target triple used by manifest digests.
513    pub const fn as_str(self) -> &'static str {
514        match self {
515            Self::LinuxX86_64 => "x86_64-unknown-linux-gnu",
516            Self::WindowsX86_64 => "x86_64-pc-windows-msvc",
517        }
518    }
519
520    /// Return the executable name shipped at the pack root.
521    #[must_use]
522    pub const fn worker_file_name(self) -> &'static str {
523        match self {
524            Self::WindowsX86_64 => "projectatlas-parser-worker.exe",
525            Self::LinuxX86_64 => "projectatlas-parser-worker",
526        }
527    }
528
529    /// Return the artifact-bound runtime-containment broker when the platform requires one.
530    #[must_use]
531    pub const fn containment_broker_file_name(self) -> Option<&'static str> {
532        match self {
533            Self::LinuxX86_64 => None,
534            Self::WindowsX86_64 => Some("projectatlas-parser-containment.exe"),
535        }
536    }
537
538    /// Return the platform-native filename for a validated grammar-library stem.
539    #[must_use]
540    pub fn grammar_library_file_name(self, stem: &GrammarLibraryStem) -> String {
541        match self {
542            Self::LinuxX86_64 => format!("lib{}.so", stem.as_str()),
543            Self::WindowsX86_64 => format!("{}.dll", stem.as_str()),
544        }
545    }
546}
547
548/// Canonical artifact-relative UTF-8 path with no traversal or platform syntax.
549#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
550#[serde(transparent)]
551pub struct PackRelativePath(String);
552
553impl PackRelativePath {
554    /// Validate one slash-separated artifact-relative path.
555    ///
556    /// # Errors
557    ///
558    /// Returns an error for empty, absolute, traversing, non-ASCII, or oversized paths.
559    pub fn new(value: impl Into<String>) -> Result<Self, OptionalParserPackManifestError> {
560        let value = value.into();
561        let valid_bytes = value
562            .bytes()
563            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'.' | b'_' | b'-'));
564        let valid_components = !value.is_empty()
565            && !value.starts_with('/')
566            && !value.ends_with('/')
567            && value
568                .split('/')
569                .all(|component| !component.is_empty() && component != "." && component != "..");
570        if value.len() > OPTIONAL_PARSER_PACK_MAX_PATH_BYTES || !valid_bytes || !valid_components {
571            return Err(invalid_field(
572                &value,
573                "relative_path",
574                "expected a safe slash-separated ASCII path within the pack path bound",
575            ));
576        }
577        Ok(Self(value))
578    }
579
580    /// Borrow the canonical slash-separated relative path.
581    #[must_use]
582    pub fn as_str(&self) -> &str {
583        &self.0
584    }
585}
586
587impl<'de> Deserialize<'de> for PackRelativePath {
588    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
589    where
590        D: Deserializer<'de>,
591    {
592        let value = String::deserialize(deserializer)?;
593        Self::new(value).map_err(serde::de::Error::custom)
594    }
595}
596
597/// Candidate source-state classification bound into release-only pack evidence.
598#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
599#[serde(rename_all = "kebab-case")]
600pub enum ParserPackCandidateSourceState {
601    /// Candidate commit and tracked worktree are exact and clean.
602    Clean,
603    /// Local development artifact was produced from uncommitted source.
604    Dirty,
605}
606
607/// Exact source and toolchain identity used to build a parser-pack worker.
608#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
609#[serde(deny_unknown_fields)]
610pub struct ParserPackCandidateIdentity {
611    /// Exact `ProjectAtlas` source revision.
612    pub projectatlas_revision: SourceRevision,
613    /// Cargo package version compiled into the worker.
614    pub cargo_package_version: String,
615    /// Intended `ProjectAtlas` release line for the capability manifest.
616    pub intended_release_version: String,
617    /// SHA-256 of the exact workspace lockfile.
618    pub cargo_lock_sha256: Sha256Digest,
619    /// Rust compiler release version without host-specific prose.
620    pub rustc_release: String,
621    /// Rust compiler commit hash.
622    pub rustc_commit_hash: String,
623    /// Whether tracked candidate source was clean when constructed.
624    pub source_state: ParserPackCandidateSourceState,
625}
626
627/// Closed physical egress-denial mechanism for artifact construction and verification.
628#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
629#[serde(rename_all = "kebab-case")]
630pub enum ParserPackNetworkIsolation {
631    /// Linux network namespace or container with no network device route.
632    LinuxNetworkNamespace,
633    /// Windows Firewall denial scoped to a disposable construction principal.
634    WindowsPrincipalFirewall,
635    /// Windows zero-capability `AppContainer` used by fresh verification and runtime containment.
636    WindowsAppContainer,
637}
638
639impl ParserPackNetworkIsolation {
640    /// Return the accepted offline-construction mechanism for a required target.
641    const fn for_construction(platform: PackPlatform) -> Self {
642        match platform {
643            PackPlatform::LinuxX86_64 => Self::LinuxNetworkNamespace,
644            PackPlatform::WindowsX86_64 => Self::WindowsPrincipalFirewall,
645        }
646    }
647
648    /// Return the accepted fresh-verification mechanism for a required target.
649    const fn for_fresh_runner(platform: PackPlatform) -> Self {
650        match platform {
651            PackPlatform::LinuxX86_64 => Self::LinuxNetworkNamespace,
652            PackPlatform::WindowsX86_64 => Self::WindowsAppContainer,
653        }
654    }
655}
656
657/// Required three-path egress canary result under physical network denial.
658#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
659#[serde(deny_unknown_fields)]
660pub struct ParserPackNetworkDenial {
661    /// Platform-specific physical containment mechanism.
662    pub mechanism: ParserPackNetworkIsolation,
663    /// DNS resolution or query attempt was denied.
664    pub dns_denied: bool,
665    /// Direct TCP connection attempt was denied.
666    pub direct_tcp_denied: bool,
667    /// HTTPS connection attempt was denied.
668    pub https_denied: bool,
669}
670
671/// Typed success state for one required construction or fresh-runner control.
672#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
673#[serde(rename_all = "kebab-case")]
674pub enum ParserPackVerifiedControl {
675    /// The owning workflow enforced and verified the control.
676    Verified,
677}
678
679/// Offline-construction controls recorded with an immutable platform artifact.
680#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
681#[serde(deny_unknown_fields)]
682pub struct ParserPackOfflineConstruction {
683    /// Cargo ran in frozen mode against the exact lockfile.
684    pub cargo_frozen: ParserPackVerifiedControl,
685    /// Cargo ran in offline mode after the bounded acquisition stage.
686    pub cargo_offline: ParserPackVerifiedControl,
687    /// The grammar dependency's own offline mode was forced.
688    pub dependency_offline: ParserPackVerifiedControl,
689    /// The `ProjectAtlas` worker embedded no grammar libraries.
690    pub zero_embedded_grammars: ParserPackVerifiedControl,
691    /// The dependency's broad language-selection variable was absent.
692    pub language_selector_absent: ParserPackVerifiedControl,
693    /// The dependency's failed-grammar override was absent.
694    pub failed_grammar_override_absent: ParserPackVerifiedControl,
695    /// Physical egress denial and canary outcome.
696    pub network_denial: ParserPackNetworkDenial,
697}
698
699/// Exact pinned upstream native asset consumed by one platform realization.
700#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
701#[serde(deny_unknown_fields)]
702pub struct ParserPackSourceAsset {
703    /// Exact release tag that owns the asset.
704    pub release_tag: String,
705    /// Exact release revision that owns the asset.
706    pub release_revision: SourceRevision,
707    /// Safe release-asset basename.
708    pub name: String,
709    /// SHA-256 of the complete downloaded asset.
710    pub sha256: Sha256Digest,
711    /// Exact compressed asset bytes.
712    pub bytes: u64,
713    /// SHA-256 of the upstream asset-inventory manifest.
714    pub parsers_manifest_sha256: Sha256Digest,
715}
716
717/// Closed payload-file role in one platform artifact.
718#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
719#[serde(deny_unknown_fields, tag = "kind", rename_all = "kebab-case")]
720pub enum ParserPackPayloadRole {
721    /// Grammar-affined `ProjectAtlas` parser worker.
722    Worker,
723    /// Artifact-bound platform admission and containment broker.
724    ContainmentBroker,
725    /// Byte-identical accepted logical capability manifest.
726    AcceptedManifest,
727    /// Byte-identical retained positive/negative fixture corpus.
728    FixtureCorpus,
729    /// `ProjectAtlas` distribution license.
730    ProjectLicense,
731    /// Closed native import/export/dependency policy.
732    NativeImportPolicy,
733    /// Normalized per-library native audit evidence.
734    NativeAuditReport,
735    /// One exact accepted native grammar library.
736    GrammarLibrary {
737        /// Canonical accepted language identity.
738        language_id: String,
739    },
740}
741
742/// One exact payload file covered by an artifact manifest.
743#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
744#[serde(deny_unknown_fields)]
745pub struct ParserPackPayloadFile {
746    /// Canonical artifact-relative path.
747    pub path: PackRelativePath,
748    /// Closed payload responsibility.
749    pub role: ParserPackPayloadRole,
750    /// Exact payload bytes.
751    pub bytes: u64,
752    /// SHA-256 of the exact payload bytes.
753    pub sha256: Sha256Digest,
754}
755
756/// Derived bounded measurements for artifact payload files.
757#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
758#[serde(deny_unknown_fields)]
759pub struct ParserPackPayloadMeasurements {
760    /// Number of manifest-listed payload files.
761    pub files: u32,
762    /// Number of accepted native grammar libraries.
763    pub grammar_libraries: u32,
764    /// Sum of manifest-listed payload bytes, excluding the manifest itself.
765    pub payload_bytes: u64,
766    /// Largest manifest-listed payload file.
767    pub largest_file_bytes: u64,
768    /// Longest canonical relative path in UTF-8 bytes.
769    pub longest_path_bytes: u32,
770}
771
772/// Closed native binary-audit summary for one platform realization.
773#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
774#[serde(deny_unknown_fields)]
775pub struct ParserPackNativeAudit {
776    /// SHA-256 of the closed checked-in native audit policy.
777    pub policy_sha256: Sha256Digest,
778    /// SHA-256 of the normalized per-library audit report.
779    pub report_sha256: Sha256Digest,
780    /// Number of accepted grammar libraries audited.
781    pub audited_libraries: u32,
782    /// Forbidden imported symbols found across accepted libraries.
783    pub forbidden_imports: u32,
784    /// Dependencies outside the platform allowlist.
785    pub unexpected_dependencies: u32,
786    /// Required grammar constructors missing from accepted libraries.
787    pub missing_exports: u32,
788    /// Unexpected Tree-sitter constructor/helper exports.
789    pub unexpected_exports: u32,
790}
791
792/// Immutable payload and construction manifest for one platform pack artifact.
793#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
794#[serde(deny_unknown_fields)]
795pub struct OptionalParserPackArtifactManifest {
796    /// Artifact-manifest schema version.
797    pub schema_version: u32,
798    /// Stable logical pack identity.
799    pub pack_id: String,
800    /// Intended `ProjectAtlas` release line.
801    pub projectatlas_version: String,
802    /// Required native target.
803    pub platform: PackPlatform,
804    /// Exact source/toolchain identity for the packaged worker.
805    pub candidate: ParserPackCandidateIdentity,
806    /// SHA-256 of the exact accepted logical manifest bytes.
807    pub accepted_manifest_sha256: Sha256Digest,
808    /// Logical accepted capability digest.
809    pub capability_set_digest: Blake3Digest,
810    /// SHA-256 of the exact retained fixture corpus bytes.
811    pub fixture_corpus_sha256: Sha256Digest,
812    /// Pinned upstream native asset identity.
813    pub source_asset: ParserPackSourceAsset,
814    /// Network-disabled and dependency-offline construction state.
815    pub construction: ParserPackOfflineConstruction,
816    /// Closed native audit result.
817    pub native_audit: ParserPackNativeAudit,
818    /// Derived payload measurements.
819    pub measurements: ParserPackPayloadMeasurements,
820    /// Strictly path-sorted payload inventory, excluding this manifest.
821    pub files: Vec<ParserPackPayloadFile>,
822}
823
824/// Fresh-runner isolation state recorded after extracting a completed artifact.
825#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
826#[serde(deny_unknown_fields)]
827pub struct ParserPackFreshRunner {
828    /// Verification ran in a newly allocated host job or machine image.
829    pub fresh_host: ParserPackVerifiedControl,
830    /// No repository source or build output was available to the verifier.
831    pub repository_inputs_absent: ParserPackVerifiedControl,
832    /// Verification invoked neither Cargo nor a compiler.
833    pub build_tools_not_invoked: ParserPackVerifiedControl,
834    /// Verification current directory was outside the extracted pack.
835    pub working_directory_outside_pack: ParserPackVerifiedControl,
836    /// Ambient dynamic-library search paths were cleared.
837    pub ambient_library_paths_cleared: ParserPackVerifiedControl,
838    /// Physical egress denial and canary outcome during packaged loading.
839    pub network_denial: ParserPackNetworkDenial,
840}
841
842impl ParserPackFreshRunner {
843    /// Validate clean-runner and physical-isolation controls for one accepted target.
844    ///
845    /// # Errors
846    ///
847    /// Returns the first missing, mismatched, or unverified runner control.
848    pub fn validate(&self, platform: PackPlatform) -> Result<(), OptionalParserPackManifestError> {
849        validate_fresh_runner(self, platform)
850    }
851}
852
853/// One accepted grammar's packaged worker probe on a fresh runner.
854#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
855#[serde(deny_unknown_fields)]
856pub struct ParserPackGrammarProbe {
857    /// Canonical accepted language identity supplied to the worker.
858    pub language_id: String,
859    /// Worker loaded the manifest-approved library, matched ABI, and proved both fixtures.
860    pub worker_probe_passed: bool,
861}
862
863/// Platform memory-control path exercised by the exact packaged worker probe.
864#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
865#[serde(rename_all = "kebab-case")]
866pub enum ParserPackMemoryControl {
867    /// A delegated Linux cgroup-v2 `memory.max` ceiling enforced the limit.
868    LinuxCgroupV2,
869    /// The Linux supervisor sampled `/proc/<pid>/status` and killed the process group.
870    LinuxProcStatus,
871    /// A Windows no-breakaway Job Object enforced committed-memory ceilings.
872    WindowsJobObject,
873}
874
875/// Hosted memory-limit and process-cleanup proof for one exact packaged worker.
876#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
877#[serde(deny_unknown_fields)]
878pub struct ParserPackMemoryProbe {
879    /// Platform control that observed or enforced the deliberately reduced probe limit.
880    pub control: ParserPackMemoryControl,
881    /// Deliberately reduced per-process ceiling used only by the hosted probe.
882    pub process_limit_bytes: u64,
883    /// Deliberately reduced process-tree or Job ceiling used by the hosted probe.
884    pub process_tree_limit_bytes: u64,
885    /// Declared maximum sampling interval for the `/proc` fallback, when applicable.
886    pub observation_interval_millis: Option<u64>,
887    /// Highest sampled resident bytes at the first confirmed `/proc` breach.
888    pub peak_observed_bytes: Option<u64>,
889    /// Hosted-measured maximum bytes above the sampled `/proc` ceiling.
890    pub maximum_observed_overshoot_bytes: Option<u64>,
891    /// The configured limit terminated or rejected the exact worker process tree.
892    pub limit_enforced: ParserPackVerifiedControl,
893    /// The supervisor or broker confirmed bounded worker/process-tree cleanup.
894    pub process_tree_cleaned: ParserPackVerifiedControl,
895}
896
897/// Fresh-runner receipt for one exact completed platform archive.
898#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
899#[serde(deny_unknown_fields)]
900pub struct OptionalParserPackPlatformProof {
901    /// Platform-proof schema version.
902    pub schema_version: u32,
903    /// Stable logical pack identity.
904    pub pack_id: String,
905    /// Required native target.
906    pub platform: PackPlatform,
907    /// Exact candidate identity repeated for cross-platform comparison.
908    pub candidate: ParserPackCandidateIdentity,
909    /// Safe completed archive basename.
910    pub archive_name: String,
911    /// SHA-256 of the complete archive, including its artifact manifest.
912    pub archive_sha256: Sha256Digest,
913    /// Exact completed archive bytes.
914    pub archive_bytes: u64,
915    /// Expanded bytes including the artifact manifest.
916    pub expanded_bytes: u64,
917    /// SHA-256 of the immutable artifact manifest inside the archive.
918    pub artifact_manifest_sha256: Sha256Digest,
919    /// SHA-256 of the byte-identical accepted logical manifest.
920    pub accepted_manifest_sha256: Sha256Digest,
921    /// Byte-identical logical accepted capability digest.
922    pub capability_set_digest: Blake3Digest,
923    /// SHA-256 of the byte-identical retained fixture corpus.
924    pub fixture_corpus_sha256: Sha256Digest,
925    /// SHA-256 of the normalized native audit report bound by the artifact manifest.
926    pub native_audit_report_sha256: Sha256Digest,
927    /// Clean-machine and physical-isolation state.
928    pub runner: ParserPackFreshRunner,
929    /// Strictly language-sorted accepted grammar worker probes.
930    pub grammars: Vec<ParserPackGrammarProbe>,
931    /// Exact-host memory-boundary and cleanup probe.
932    pub memory: ParserPackMemoryProbe,
933}
934
935/// Exact supported-platform optional parser-pack proof aggregate.
936#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
937#[serde(deny_unknown_fields)]
938pub struct OptionalParserPackProofAggregate {
939    /// Aggregate schema version.
940    pub schema_version: u32,
941    /// Stable logical pack identity.
942    pub pack_id: String,
943    /// Intended `ProjectAtlas` release line.
944    pub projectatlas_version: String,
945    /// SHA-256 of the shared accepted logical manifest bytes.
946    pub accepted_manifest_sha256: Sha256Digest,
947    /// Shared logical accepted capability digest.
948    pub capability_set_digest: Blake3Digest,
949    /// SHA-256 of the shared retained fixture corpus bytes.
950    pub fixture_corpus_sha256: Sha256Digest,
951    /// Required platform receipts in canonical platform order.
952    pub platforms: Vec<OptionalParserPackPlatformProof>,
953}
954
955/// Closed `ProjectAtlas` consumer selected for native optional grammars.
956#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
957#[serde(rename_all = "kebab-case")]
958pub enum ParserPackConsumer {
959    /// Separately packaged grammar-affined `ProjectAtlas` parser worker.
960    #[serde(rename = "projectatlas-parser-worker")]
961    ProjectAtlasParserWorker,
962}
963
964impl ParserPackConsumer {
965    /// Return the stable consuming executable name.
966    const fn canonical_name(self) -> &'static str {
967        match self {
968            Self::ProjectAtlasParserWorker => "projectatlas-parser-worker",
969        }
970    }
971}
972
973/// Required behavior when an optional grammar overlaps default-core ownership.
974#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
975#[serde(rename_all = "kebab-case")]
976pub enum BuiltInParserPrecedence {
977    /// The default-core owner is authoritative and overlap is rejected.
978    BuiltInAuthoritative,
979}
980
981impl BuiltInParserPrecedence {
982    /// Return the stable precedence-policy name.
983    const fn canonical_name(self) -> &'static str {
984        match self {
985            Self::BuiltInAuthoritative => "built-in-authoritative",
986        }
987    }
988}
989
990/// Exact broad-grammar source package selected for the logical pack.
991#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
992#[serde(deny_unknown_fields)]
993pub struct OptionalParserPackSource {
994    /// Cargo source package.
995    pub package: String,
996    /// Exact published package version.
997    pub version: String,
998    /// Exact published Cargo archive identity.
999    pub cargo_archive: OptionalParserCargoArchive,
1000    /// Exact upstream release identity that owns native assets.
1001    pub native_release: OptionalParserNativeRelease,
1002}
1003
1004/// Published Cargo archive provenance for the consuming API/runtime package.
1005#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1006#[serde(deny_unknown_fields)]
1007pub struct OptionalParserCargoArchive {
1008    /// SHA-256 of the published crate archive.
1009    pub sha256: Sha256Digest,
1010    /// Full VCS revision embedded in `.cargo_vcs_info.json`.
1011    pub vcs_revision: SourceRevision,
1012    /// Monorepo-relative crate path embedded in `.cargo_vcs_info.json`.
1013    pub path_in_vcs: String,
1014}
1015
1016/// Upstream release provenance for parser-source and native assets.
1017#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1018#[serde(deny_unknown_fields)]
1019pub struct OptionalParserNativeRelease {
1020    /// Exact version tag that owns the release assets.
1021    pub tag: String,
1022    /// Full Git revision named by the release tag.
1023    pub revision: SourceRevision,
1024    /// SHA-256 of the pinned parser-source bundle.
1025    pub source_bundle_sha256: Sha256Digest,
1026}
1027
1028/// Exact `ProjectAtlas` consumer and ABI window for the logical pack.
1029#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030#[serde(deny_unknown_fields)]
1031pub struct OptionalParserPackRuntime {
1032    /// Closed consuming executable identity.
1033    pub consumer: ParserPackConsumer,
1034    /// Exact `ProjectAtlas` runtime version used to consume the manifest.
1035    pub projectatlas_version: String,
1036    /// Exact Tree-sitter runtime package version.
1037    pub tree_sitter_version: String,
1038    /// Oldest accepted Tree-sitter grammar ABI.
1039    pub minimum_abi: u32,
1040    /// Newest accepted Tree-sitter grammar ABI.
1041    pub maximum_abi: u32,
1042}
1043
1044/// Exact binding to the language registry that admitted optional candidates.
1045#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1046#[serde(deny_unknown_fields)]
1047pub struct OptionalParserPackRegistryBinding {
1048    /// Language capability registry schema version.
1049    pub registry_version: u32,
1050    /// Digest of achieved registry truth and detector rules.
1051    pub registry_digest: Blake3Digest,
1052    /// Accepted language capability-set version.
1053    pub accepted_set_version: u32,
1054    /// Digest of accepted language minimums.
1055    pub accepted_set_digest: Blake3Digest,
1056}
1057
1058impl OptionalParserPackRegistryBinding {
1059    /// Capture the current authoritative language-registry identity.
1060    ///
1061    /// # Errors
1062    ///
1063    /// Returns an error only if an internal registry digest is not canonical.
1064    pub fn current() -> Result<Self, OptionalParserPackManifestError> {
1065        Ok(Self {
1066            registry_version: LANGUAGE_CAPABILITY_REGISTRY_VERSION,
1067            registry_digest: Blake3Digest::new(language_registry_digest())?,
1068            accepted_set_version: ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION,
1069            accepted_set_digest: Blake3Digest::new(accepted_language_capability_digest())?,
1070        })
1071    }
1072}
1073
1074/// Pinned source subtree and deterministic compile-input identity for one grammar.
1075#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1076#[serde(deny_unknown_fields)]
1077pub struct GrammarSourceProvenance {
1078    /// HTTPS source repository.
1079    pub repository_url: String,
1080    /// Full pinned repository revision.
1081    pub revision: SourceRevision,
1082    /// Repository-relative grammar subtree, or `.` for repository root.
1083    pub subdirectory: String,
1084    /// SHA-256 of every admitted grammar compile input in canonical order.
1085    pub compile_input_sha256: Sha256Digest,
1086}
1087
1088/// Exact applicable license text retained once and referenced by grammar rows.
1089#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1090#[serde(deny_unknown_fields)]
1091pub struct GrammarLicense {
1092    /// Stable manifest-local license record identity.
1093    pub id: String,
1094    /// HTTPS repository containing the exact text.
1095    pub repository_url: String,
1096    /// Repository-relative license source path.
1097    pub source_path: String,
1098    /// Full repository revision at which the text was read.
1099    pub revision: SourceRevision,
1100    /// Exact applicable license text.
1101    pub text: String,
1102    /// BLAKE3 of the exact UTF-8 license text.
1103    pub text_blake3: Blake3Digest,
1104    /// Optional declarative SPDX expression; exact text remains authoritative.
1105    pub spdx_expression: Option<String>,
1106}
1107
1108impl GrammarLicense {
1109    /// Construct a license record and bind its exact text digest.
1110    #[must_use]
1111    pub fn new(
1112        id: impl Into<String>,
1113        repository_url: impl Into<String>,
1114        source_path: impl Into<String>,
1115        revision: SourceRevision,
1116        text: impl Into<String>,
1117        spdx_expression: Option<String>,
1118    ) -> Self {
1119        let text = text.into();
1120        Self {
1121            id: id.into(),
1122            repository_url: repository_url.into(),
1123            source_path: source_path.into(),
1124            revision,
1125            text_blake3: Blake3Digest::for_bytes(text.as_bytes()),
1126            text,
1127            spdx_expression,
1128        }
1129    }
1130}
1131
1132/// ABI and loader identities for one compiled grammar.
1133#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1134#[serde(deny_unknown_fields)]
1135pub struct GrammarAbiExport {
1136    /// Oldest ABI accepted for this grammar.
1137    pub minimum_abi: u32,
1138    /// Newest ABI accepted for this grammar.
1139    pub maximum_abi: u32,
1140    /// ABI reported by the compiled grammar.
1141    pub expected_abi: u32,
1142    /// Exact exported language function.
1143    pub export_symbol: GrammarExportSymbol,
1144    /// Platform-neutral dynamic-library stem.
1145    pub library_stem: GrammarLibraryStem,
1146}
1147
1148/// Closed provenance and transformation classes for accepted fixtures.
1149#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1150pub enum GrammarFixtureOrigin {
1151    /// Natural case from an upstream Tree-sitter corpus.
1152    #[serde(rename = "upstream-tree-sitter-corpus")]
1153    UpstreamTreeSitterCorpus,
1154    /// Natural example from an upstream language repository.
1155    #[serde(rename = "upstream-language-example")]
1156    UpstreamLanguageExample,
1157    /// Malformed or rejected case authored in an upstream corpus.
1158    #[serde(rename = "upstream-corpus-error-case")]
1159    UpstreamCorpusErrorCase,
1160    /// Incomplete editor-state case derived from an upstream corpus case.
1161    #[serde(rename = "projectatlas-incomplete-upstream-case")]
1162    ProjectAtlasIncompleteUpstreamCase,
1163    /// Incomplete editor-state case derived from an upstream language example.
1164    #[serde(rename = "projectatlas-incomplete-upstream-example")]
1165    ProjectAtlasIncompleteUpstreamExample,
1166}
1167
1168impl GrammarFixtureOrigin {
1169    /// Return the stable serialized fixture-origin identity.
1170    const fn canonical_name(self) -> &'static str {
1171        match self {
1172            Self::UpstreamTreeSitterCorpus => "upstream-tree-sitter-corpus",
1173            Self::UpstreamLanguageExample => "upstream-language-example",
1174            Self::UpstreamCorpusErrorCase => "upstream-corpus-error-case",
1175            Self::ProjectAtlasIncompleteUpstreamCase => "projectatlas-incomplete-upstream-case",
1176            Self::ProjectAtlasIncompleteUpstreamExample => {
1177                "projectatlas-incomplete-upstream-example"
1178            }
1179        }
1180    }
1181
1182    /// Return whether the origin is valid for a natural positive case.
1183    const fn is_positive(self) -> bool {
1184        matches!(
1185            self,
1186            Self::UpstreamTreeSitterCorpus | Self::UpstreamLanguageExample
1187        )
1188    }
1189
1190    /// Return whether the origin is valid for a non-vacuous negative case.
1191    const fn is_negative(self) -> bool {
1192        matches!(
1193            self,
1194            Self::UpstreamCorpusErrorCase
1195                | Self::ProjectAtlasIncompleteUpstreamCase
1196                | Self::ProjectAtlasIncompleteUpstreamExample
1197        )
1198    }
1199}
1200
1201/// Exact source fixture with retained origin, case, path, and digest.
1202#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1203#[serde(deny_unknown_fields)]
1204pub struct GrammarFixture {
1205    /// Stable fixture provenance or transformation class.
1206    pub origin: GrammarFixtureOrigin,
1207    /// Exact upstream repository-relative fixture path.
1208    pub path: String,
1209    /// Exact upstream case name or stable example label.
1210    pub case_name: String,
1211    /// Exact natural source bytes represented as UTF-8.
1212    pub source: String,
1213    /// BLAKE3 of the exact fixture source.
1214    pub source_blake3: Blake3Digest,
1215}
1216
1217impl GrammarFixture {
1218    /// Construct a fixture and bind its provenance plus exact source digest.
1219    #[must_use]
1220    pub fn new(
1221        origin: GrammarFixtureOrigin,
1222        path: impl Into<String>,
1223        case_name: impl Into<String>,
1224        source: impl Into<String>,
1225    ) -> Self {
1226        let source = source.into();
1227        Self {
1228            origin,
1229            path: path.into(),
1230            case_name: case_name.into(),
1231            source_blake3: Blake3Digest::for_bytes(source.as_bytes()),
1232            source,
1233        }
1234    }
1235}
1236
1237/// Natural positive and non-vacuous negative grammar fixtures.
1238#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1239#[serde(deny_unknown_fields)]
1240pub struct GrammarFixtures {
1241    /// Source expected to load and parse through the selected grammar.
1242    pub positive: GrammarFixture,
1243    /// Distinct source that protects rejection, error, or non-match behavior.
1244    pub negative: GrammarFixture,
1245}
1246
1247/// One accepted non-built-in grammar capability.
1248#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1249#[serde(deny_unknown_fields)]
1250pub struct AcceptedGrammar {
1251    /// Canonical language-registry identity.
1252    pub language_id: String,
1253    /// Exact grammar source and compile inputs.
1254    pub source: GrammarSourceProvenance,
1255    /// Sorted non-empty applicable license record identities.
1256    pub license_record_ids: Vec<String>,
1257    /// ABI, export, and library identity.
1258    pub abi_export: GrammarAbiExport,
1259    /// Exact positive and negative fixtures.
1260    pub fixtures: GrammarFixtures,
1261    /// Required platforms on which this row must be realized.
1262    pub required_platforms: Vec<PackPlatform>,
1263    /// Default-core overlap policy.
1264    pub built_in_precedence: BuiltInParserPrecedence,
1265    /// BLAKE3 of this canonical grammar capability row.
1266    pub capability_digest: Blake3Digest,
1267}
1268
1269impl AcceptedGrammar {
1270    /// Construct one row with required platform and precedence policy and seal its digest.
1271    #[must_use]
1272    pub fn new(
1273        language_id: impl Into<String>,
1274        source: GrammarSourceProvenance,
1275        license_record_ids: Vec<String>,
1276        abi_export: GrammarAbiExport,
1277        fixtures: GrammarFixtures,
1278    ) -> Self {
1279        let mut grammar = Self {
1280            language_id: language_id.into(),
1281            source,
1282            license_record_ids,
1283            abi_export,
1284            fixtures,
1285            required_platforms: PackPlatform::ALL.to_vec(),
1286            built_in_precedence: BuiltInParserPrecedence::BuiltInAuthoritative,
1287            capability_digest: Blake3Digest::for_bytes(&[]),
1288        };
1289        grammar.capability_digest = grammar.computed_capability_digest();
1290        grammar
1291    }
1292
1293    /// Compute the canonical capability digest from every row-owned field.
1294    #[must_use]
1295    pub fn computed_capability_digest(&self) -> Blake3Digest {
1296        let mut hasher = Hasher::new();
1297        hash_value(&mut hasher, CAPABILITY_DIGEST_DOMAIN);
1298        hash_value(&mut hasher, &self.language_id);
1299        hash_source_provenance(&mut hasher, &self.source);
1300        for license_id in &self.license_record_ids {
1301            hash_value(&mut hasher, license_id);
1302        }
1303        hasher.update(&self.abi_export.minimum_abi.to_le_bytes());
1304        hasher.update(&self.abi_export.maximum_abi.to_le_bytes());
1305        hasher.update(&self.abi_export.expected_abi.to_le_bytes());
1306        hash_value(&mut hasher, self.abi_export.export_symbol.as_str());
1307        hash_value(&mut hasher, self.abi_export.library_stem.as_str());
1308        hash_fixture(&mut hasher, &self.fixtures.positive);
1309        hash_fixture(&mut hasher, &self.fixtures.negative);
1310        for platform in &self.required_platforms {
1311            hash_value(&mut hasher, platform.as_str());
1312        }
1313        hash_value(&mut hasher, self.built_in_precedence.canonical_name());
1314        Blake3Digest(hasher.finalize().to_hex().to_string())
1315    }
1316}
1317
1318/// One validated logical optional parser-pack accepted-capability manifest.
1319#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1320pub struct OptionalParserPackManifest {
1321    /// Logical manifest schema version.
1322    schema_version: u32,
1323    /// Stable single-pack identity.
1324    pack_id: String,
1325    /// Accepted capability-set compatibility version.
1326    capability_set_version: u32,
1327    /// Exact broad-grammar source package.
1328    source: OptionalParserPackSource,
1329    /// Exact `ProjectAtlas` consuming runtime.
1330    runtime: OptionalParserPackRuntime,
1331    /// Language registry authority that admitted these rows.
1332    registry: OptionalParserPackRegistryBinding,
1333    /// Complete required platform set.
1334    required_platforms: Vec<PackPlatform>,
1335    /// Sorted unique exact license inventory.
1336    licenses: Vec<GrammarLicense>,
1337    /// Sorted unique accepted non-built-in grammar rows.
1338    grammars: Vec<AcceptedGrammar>,
1339    /// BLAKE3 of the complete canonical logical manifest.
1340    capability_set_digest: Blake3Digest,
1341}
1342
1343#[derive(Deserialize)]
1344#[serde(deny_unknown_fields)]
1345/// Raw serde projection validated before it becomes a public manifest.
1346struct OptionalParserPackManifestWire {
1347    /// Logical schema version.
1348    schema_version: u32,
1349    /// Stable pack identity.
1350    pack_id: String,
1351    /// Accepted capability-set version.
1352    capability_set_version: u32,
1353    /// Exact broad-grammar source package.
1354    source: OptionalParserPackSource,
1355    /// Exact consuming runtime.
1356    runtime: OptionalParserPackRuntime,
1357    /// Current language-registry binding.
1358    registry: OptionalParserPackRegistryBinding,
1359    /// Complete required platform set.
1360    required_platforms: Vec<PackPlatform>,
1361    /// Deduplicated exact license records.
1362    licenses: Vec<GrammarLicense>,
1363    /// Accepted non-built-in grammar rows.
1364    grammars: Vec<AcceptedGrammar>,
1365    /// Complete logical-manifest digest.
1366    capability_set_digest: Blake3Digest,
1367}
1368
1369impl OptionalParserPackManifest {
1370    /// Construct and seal one logical manifest from sorted accepted records.
1371    ///
1372    /// # Errors
1373    ///
1374    /// Returns an error when the records violate any accepted-capability invariant.
1375    pub fn new(
1376        source: OptionalParserPackSource,
1377        runtime: OptionalParserPackRuntime,
1378        licenses: Vec<GrammarLicense>,
1379        mut grammars: Vec<AcceptedGrammar>,
1380    ) -> Result<Self, OptionalParserPackManifestError> {
1381        for grammar in &mut grammars {
1382            grammar.capability_digest = grammar.computed_capability_digest();
1383        }
1384        let mut manifest = Self {
1385            schema_version: OPTIONAL_PARSER_PACK_MANIFEST_SCHEMA_VERSION,
1386            pack_id: OPTIONAL_PARSER_PACK_ID.to_string(),
1387            capability_set_version: OPTIONAL_PARSER_PACK_CAPABILITY_SET_VERSION,
1388            source,
1389            runtime,
1390            registry: OptionalParserPackRegistryBinding::current()?,
1391            required_platforms: PackPlatform::ALL.to_vec(),
1392            licenses,
1393            grammars,
1394            capability_set_digest: Blake3Digest::for_bytes(&[]),
1395        };
1396        manifest.capability_set_digest = manifest.computed_capability_set_digest();
1397        manifest.validate()?;
1398        Ok(manifest)
1399    }
1400
1401    /// Return the logical manifest schema version.
1402    #[must_use]
1403    pub const fn schema_version(&self) -> u32 {
1404        self.schema_version
1405    }
1406
1407    /// Borrow the stable logical pack identity.
1408    #[must_use]
1409    pub fn pack_id(&self) -> &str {
1410        &self.pack_id
1411    }
1412
1413    /// Return the accepted capability-set version.
1414    #[must_use]
1415    pub const fn capability_set_version(&self) -> u32 {
1416        self.capability_set_version
1417    }
1418
1419    /// Borrow the exact broad-grammar source pin.
1420    #[must_use]
1421    pub const fn source(&self) -> &OptionalParserPackSource {
1422        &self.source
1423    }
1424
1425    /// Borrow the exact consuming runtime binding.
1426    #[must_use]
1427    pub const fn runtime(&self) -> &OptionalParserPackRuntime {
1428        &self.runtime
1429    }
1430
1431    /// Borrow the accepted language-registry binding.
1432    #[must_use]
1433    pub const fn registry(&self) -> &OptionalParserPackRegistryBinding {
1434        &self.registry
1435    }
1436
1437    /// Borrow the complete optional-pack artifact target set.
1438    #[must_use]
1439    pub fn required_platforms(&self) -> &[PackPlatform] {
1440        &self.required_platforms
1441    }
1442
1443    /// Borrow the sorted exact license inventory.
1444    #[must_use]
1445    pub fn licenses(&self) -> &[GrammarLicense] {
1446        &self.licenses
1447    }
1448
1449    /// Borrow the sorted accepted non-built-in grammar rows.
1450    #[must_use]
1451    pub fn grammars(&self) -> &[AcceptedGrammar] {
1452        &self.grammars
1453    }
1454
1455    /// Borrow the complete canonical capability-set digest.
1456    #[must_use]
1457    pub const fn capability_set_digest(&self) -> &Blake3Digest {
1458        &self.capability_set_digest
1459    }
1460
1461    /// Parse and validate one bounded JSON manifest.
1462    ///
1463    /// # Errors
1464    ///
1465    /// Returns a typed size, JSON, or domain-validation error.
1466    pub fn from_json(bytes: &[u8]) -> Result<Self, OptionalParserPackManifestError> {
1467        if bytes.len() > OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES {
1468            return Err(OptionalParserPackManifestError::ManifestTooLarge {
1469                actual: bytes.len(),
1470                maximum: OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES,
1471            });
1472        }
1473        let wire: OptionalParserPackManifestWire = serde_json::from_slice(bytes)
1474            .map_err(|source| OptionalParserPackManifestError::InvalidJson { source })?;
1475        Self::try_from(wire)
1476    }
1477
1478    /// Validate all local, cross-record, registry, and deterministic digest invariants.
1479    ///
1480    /// # Errors
1481    ///
1482    /// Returns the first deterministic invalid field, binding, row, or digest.
1483    pub fn validate(&self) -> Result<(), OptionalParserPackManifestError> {
1484        validate_binding(
1485            "schema_version",
1486            &OPTIONAL_PARSER_PACK_MANIFEST_SCHEMA_VERSION,
1487            &self.schema_version,
1488        )?;
1489        validate_binding("pack_id", OPTIONAL_PARSER_PACK_ID, self.pack_id.as_str())?;
1490        validate_binding(
1491            "capability_set_version",
1492            &OPTIONAL_PARSER_PACK_CAPABILITY_SET_VERSION,
1493            &self.capability_set_version,
1494        )?;
1495        validate_source(&self.source)?;
1496        validate_runtime(&self.runtime)?;
1497        validate_registry_binding(&self.registry)?;
1498        validate_required_platforms("manifest", &self.required_platforms)?;
1499        validate_count("licenses", self.licenses.len(), 1, MAX_LICENSE_RECORDS)?;
1500        validate_count(
1501            "grammars",
1502            self.grammars.len(),
1503            OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
1504            MAX_ACCEPTED_GRAMMARS,
1505        )?;
1506        if !strictly_sorted_by(&self.licenses, |license| license.id.as_str()) {
1507            return Err(OptionalParserPackManifestError::NotSortedUnique { field: "licenses" });
1508        }
1509        if !strictly_sorted_by(&self.grammars, |grammar| grammar.language_id.as_str()) {
1510            return Err(OptionalParserPackManifestError::NotSortedUnique { field: "grammars" });
1511        }
1512
1513        let mut license_by_id = BTreeMap::new();
1514        for license in &self.licenses {
1515            validate_license(license)?;
1516            license_by_id.insert(license.id.as_str(), license);
1517        }
1518
1519        let mut source_owners = BTreeSet::new();
1520        let mut export_symbols = BTreeSet::new();
1521        let mut library_stems = BTreeSet::new();
1522        for grammar in &self.grammars {
1523            validate_grammar(
1524                grammar,
1525                &self.runtime,
1526                &self.required_platforms,
1527                &license_by_id,
1528            )?;
1529            let source_identity = (
1530                grammar.source.repository_url.as_str(),
1531                grammar.source.revision.as_str(),
1532                grammar.source.subdirectory.as_str(),
1533            );
1534            if !source_owners.insert(source_identity) {
1535                return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
1536                    field: "grammar_source",
1537                    value: format!(
1538                        "{}@{}:{}",
1539                        source_identity.0, source_identity.1, source_identity.2
1540                    ),
1541                });
1542            }
1543            if !export_symbols.insert(grammar.abi_export.export_symbol.as_str()) {
1544                return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
1545                    field: "export_symbol",
1546                    value: grammar.abi_export.export_symbol.as_str().to_string(),
1547                });
1548            }
1549            if !library_stems.insert(grammar.abi_export.library_stem.as_str()) {
1550                return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
1551                    field: "library_stem",
1552                    value: grammar.abi_export.library_stem.as_str().to_string(),
1553                });
1554            }
1555        }
1556
1557        if self.computed_capability_set_digest() != self.capability_set_digest {
1558            return Err(OptionalParserPackManifestError::DigestMismatch {
1559                owner: self.pack_id.clone(),
1560                field: "capability_set_digest",
1561            });
1562        }
1563        Ok(())
1564    }
1565
1566    /// Compute the deterministic digest of the complete logical manifest.
1567    #[must_use]
1568    pub fn computed_capability_set_digest(&self) -> Blake3Digest {
1569        let mut hasher = Hasher::new();
1570        hash_value(&mut hasher, MANIFEST_DIGEST_DOMAIN);
1571        hasher.update(&self.schema_version.to_le_bytes());
1572        hash_value(&mut hasher, &self.pack_id);
1573        hasher.update(&self.capability_set_version.to_le_bytes());
1574        hash_value(&mut hasher, &self.source.package);
1575        hash_value(&mut hasher, &self.source.version);
1576        hash_value(&mut hasher, self.source.cargo_archive.sha256.as_str());
1577        hash_value(&mut hasher, self.source.cargo_archive.vcs_revision.as_str());
1578        hash_value(&mut hasher, &self.source.cargo_archive.path_in_vcs);
1579        hash_value(&mut hasher, &self.source.native_release.tag);
1580        hash_value(&mut hasher, self.source.native_release.revision.as_str());
1581        hash_value(
1582            &mut hasher,
1583            self.source.native_release.source_bundle_sha256.as_str(),
1584        );
1585        hash_value(&mut hasher, self.runtime.consumer.canonical_name());
1586        hash_value(&mut hasher, &self.runtime.projectatlas_version);
1587        hash_value(&mut hasher, &self.runtime.tree_sitter_version);
1588        hasher.update(&self.runtime.minimum_abi.to_le_bytes());
1589        hasher.update(&self.runtime.maximum_abi.to_le_bytes());
1590        hasher.update(&self.registry.registry_version.to_le_bytes());
1591        hash_value(&mut hasher, self.registry.registry_digest.as_str());
1592        hasher.update(&self.registry.accepted_set_version.to_le_bytes());
1593        hash_value(&mut hasher, self.registry.accepted_set_digest.as_str());
1594        for platform in &self.required_platforms {
1595            hash_value(&mut hasher, platform.as_str());
1596        }
1597        for license in &self.licenses {
1598            hash_value(&mut hasher, &license.id);
1599            hash_value(&mut hasher, &license.repository_url);
1600            hash_value(&mut hasher, &license.source_path);
1601            hash_value(&mut hasher, license.revision.as_str());
1602            hash_value(&mut hasher, license.text_blake3.as_str());
1603            hash_value(
1604                &mut hasher,
1605                license.spdx_expression.as_deref().unwrap_or(""),
1606            );
1607        }
1608        for grammar in &self.grammars {
1609            hash_value(&mut hasher, &grammar.language_id);
1610            hash_value(&mut hasher, grammar.capability_digest.as_str());
1611        }
1612        Blake3Digest(hasher.finalize().to_hex().to_string())
1613    }
1614}
1615
1616impl TryFrom<OptionalParserPackManifestWire> for OptionalParserPackManifest {
1617    type Error = OptionalParserPackManifestError;
1618
1619    fn try_from(wire: OptionalParserPackManifestWire) -> Result<Self, Self::Error> {
1620        let manifest = Self {
1621            schema_version: wire.schema_version,
1622            pack_id: wire.pack_id,
1623            capability_set_version: wire.capability_set_version,
1624            source: wire.source,
1625            runtime: wire.runtime,
1626            registry: wire.registry,
1627            required_platforms: wire.required_platforms,
1628            licenses: wire.licenses,
1629            grammars: wire.grammars,
1630            capability_set_digest: wire.capability_set_digest,
1631        };
1632        manifest.validate()?;
1633        Ok(manifest)
1634    }
1635}
1636
1637impl ParserPackPayloadMeasurements {
1638    /// Derive bounded measurements from an exact manifest-listed payload inventory.
1639    ///
1640    /// # Errors
1641    ///
1642    /// Returns an error when a file or aggregate measurement exceeds a pack ceiling.
1643    pub fn from_files(
1644        files: &[ParserPackPayloadFile],
1645    ) -> Result<Self, OptionalParserPackManifestError> {
1646        validate_count(
1647            "artifact payload files",
1648            files.len(),
1649            1,
1650            OPTIONAL_PARSER_PACK_MAX_FILE_ENTRIES,
1651        )?;
1652        let mut payload_bytes = 0_u64;
1653        let mut largest_file_bytes = 0_u64;
1654        let mut longest_path_bytes = 0_usize;
1655        let mut grammar_libraries = 0_usize;
1656        for file in files {
1657            if file.bytes == 0 || file.bytes > OPTIONAL_PARSER_PACK_MAX_FILE_BYTES {
1658                return Err(invalid_field(
1659                    file.path.as_str(),
1660                    "bytes",
1661                    "expected a non-empty payload file within the per-file byte ceiling",
1662                ));
1663            }
1664            payload_bytes = payload_bytes.checked_add(file.bytes).ok_or_else(|| {
1665                invalid_field(
1666                    OPTIONAL_PARSER_PACK_ID,
1667                    "payload_bytes",
1668                    "payload byte sum overflowed",
1669                )
1670            })?;
1671            if payload_bytes > OPTIONAL_PARSER_PACK_MAX_EXPANDED_BYTES {
1672                return Err(invalid_field(
1673                    OPTIONAL_PARSER_PACK_ID,
1674                    "payload_bytes",
1675                    "payload byte sum exceeds the expanded artifact ceiling",
1676                ));
1677            }
1678            largest_file_bytes = largest_file_bytes.max(file.bytes);
1679            longest_path_bytes = longest_path_bytes.max(file.path.as_str().len());
1680            if matches!(&file.role, ParserPackPayloadRole::GrammarLibrary { .. }) {
1681                grammar_libraries = grammar_libraries.checked_add(1).ok_or_else(|| {
1682                    invalid_field(
1683                        OPTIONAL_PARSER_PACK_ID,
1684                        "grammar_libraries",
1685                        "grammar-library count overflowed",
1686                    )
1687                })?;
1688            }
1689        }
1690        Ok(Self {
1691            files: u32::try_from(files.len()).map_err(|_error| {
1692                invalid_field(
1693                    OPTIONAL_PARSER_PACK_ID,
1694                    "files",
1695                    "payload-file count cannot be represented",
1696                )
1697            })?,
1698            grammar_libraries: u32::try_from(grammar_libraries).map_err(|_error| {
1699                invalid_field(
1700                    OPTIONAL_PARSER_PACK_ID,
1701                    "grammar_libraries",
1702                    "grammar-library count cannot be represented",
1703                )
1704            })?,
1705            payload_bytes,
1706            largest_file_bytes,
1707            longest_path_bytes: u32::try_from(longest_path_bytes).map_err(|_error| {
1708                invalid_field(
1709                    OPTIONAL_PARSER_PACK_ID,
1710                    "longest_path_bytes",
1711                    "relative-path length cannot be represented",
1712                )
1713            })?,
1714        })
1715    }
1716}
1717
1718impl OptionalParserPackArtifactManifest {
1719    /// Validate one immutable platform artifact against the accepted logical manifest.
1720    ///
1721    /// The artifact manifest intentionally excludes its own file from `files`; the complete
1722    /// archive digest in the later platform proof binds that manifest together with its payload.
1723    ///
1724    /// # Errors
1725    ///
1726    /// Returns the first binding, construction, native-audit, inventory, or bound violation.
1727    pub fn validate(
1728        &self,
1729        logical: &OptionalParserPackManifest,
1730    ) -> Result<(), OptionalParserPackManifestError> {
1731        logical.validate()?;
1732        validate_binding(
1733            "artifact.schema_version",
1734            &OPTIONAL_PARSER_PACK_ARTIFACT_SCHEMA_VERSION,
1735            &self.schema_version,
1736        )?;
1737        validate_binding("artifact.pack_id", logical.pack_id(), self.pack_id.as_str())?;
1738        validate_binding(
1739            "artifact.projectatlas_version",
1740            logical.runtime().projectatlas_version.as_str(),
1741            self.projectatlas_version.as_str(),
1742        )?;
1743        validate_required_platform("artifact.platform", self.platform)?;
1744        validate_candidate_identity(&self.candidate, false, logical)?;
1745        validate_binding(
1746            "artifact.capability_set_digest",
1747            logical.capability_set_digest(),
1748            &self.capability_set_digest,
1749        )?;
1750        validate_source_asset(&self.source_asset, logical)?;
1751        validate_offline_construction(&self.construction, self.platform)?;
1752        validate_native_audit(&self.native_audit, logical.grammars().len())?;
1753        validate_payload_files(self, logical)?;
1754        let measured = ParserPackPayloadMeasurements::from_files(&self.files)?;
1755        if measured != self.measurements {
1756            return Err(invalid_field(
1757                self.pack_id.as_str(),
1758                "measurements",
1759                "stored artifact measurements differ from the payload inventory",
1760            ));
1761        }
1762        Ok(())
1763    }
1764}
1765
1766impl OptionalParserPackPlatformProof {
1767    /// Validate one fresh-runner receipt against the accepted logical manifest.
1768    ///
1769    /// # Errors
1770    ///
1771    /// Returns the first candidate, archive, isolation, or grammar-probe violation.
1772    pub fn validate(
1773        &self,
1774        logical: &OptionalParserPackManifest,
1775    ) -> Result<(), OptionalParserPackManifestError> {
1776        logical.validate()?;
1777        validate_binding(
1778            "platform_proof.schema_version",
1779            &OPTIONAL_PARSER_PACK_PLATFORM_PROOF_SCHEMA_VERSION,
1780            &self.schema_version,
1781        )?;
1782        validate_binding(
1783            "platform_proof.pack_id",
1784            logical.pack_id(),
1785            self.pack_id.as_str(),
1786        )?;
1787        validate_required_platform("platform_proof.platform", self.platform)?;
1788        validate_candidate_identity(&self.candidate, true, logical)?;
1789        validate_safe_basename("platform proof", "archive_name", &self.archive_name)?;
1790        if self.archive_bytes == 0 || self.archive_bytes > OPTIONAL_PARSER_PACK_MAX_ARCHIVE_BYTES {
1791            return Err(invalid_field(
1792                self.archive_name.as_str(),
1793                "archive_bytes",
1794                "completed archive is empty or exceeds the compressed-byte ceiling",
1795            ));
1796        }
1797        if self.expanded_bytes == 0 || self.expanded_bytes > OPTIONAL_PARSER_PACK_MAX_EXPANDED_BYTES
1798        {
1799            return Err(invalid_field(
1800                self.archive_name.as_str(),
1801                "expanded_bytes",
1802                "expanded artifact is empty or exceeds the expanded-byte ceiling",
1803            ));
1804        }
1805        validate_binding(
1806            "platform_proof.capability_set_digest",
1807            logical.capability_set_digest(),
1808            &self.capability_set_digest,
1809        )?;
1810        validate_fresh_runner(&self.runner, self.platform)?;
1811        validate_grammar_probes(&self.grammars, logical)?;
1812        validate_memory_probe(&self.memory, self.platform)?;
1813        Ok(())
1814    }
1815}
1816
1817impl OptionalParserPackProofAggregate {
1818    /// Validate the exact required platform set and shared logical proof identity.
1819    ///
1820    /// # Errors
1821    ///
1822    /// Returns an error for a missing, duplicate, failed, dirty, or logically divergent proof.
1823    pub fn validate(
1824        &self,
1825        logical: &OptionalParserPackManifest,
1826    ) -> Result<(), OptionalParserPackManifestError> {
1827        logical.validate()?;
1828        validate_binding(
1829            "proof_aggregate.schema_version",
1830            &OPTIONAL_PARSER_PACK_PROOF_AGGREGATE_SCHEMA_VERSION,
1831            &self.schema_version,
1832        )?;
1833        validate_binding(
1834            "proof_aggregate.pack_id",
1835            logical.pack_id(),
1836            self.pack_id.as_str(),
1837        )?;
1838        validate_binding(
1839            "proof_aggregate.projectatlas_version",
1840            logical.runtime().projectatlas_version.as_str(),
1841            self.projectatlas_version.as_str(),
1842        )?;
1843        validate_binding(
1844            "proof_aggregate.capability_set_digest",
1845            logical.capability_set_digest(),
1846            &self.capability_set_digest,
1847        )?;
1848        let platforms = self
1849            .platforms
1850            .iter()
1851            .map(|proof| proof.platform)
1852            .collect::<Vec<_>>();
1853        validate_required_platforms("proof aggregate", &platforms)?;
1854        let mut archive_names = BTreeSet::new();
1855        let mut archive_digests = BTreeSet::new();
1856        let first_candidate = self.platforms.first().map(|proof| &proof.candidate);
1857        for proof in &self.platforms {
1858            proof.validate(logical)?;
1859            validate_binding(
1860                "proof_aggregate.accepted_manifest_sha256",
1861                &self.accepted_manifest_sha256,
1862                &proof.accepted_manifest_sha256,
1863            )?;
1864            validate_binding(
1865                "proof_aggregate.capability_set_digest",
1866                &self.capability_set_digest,
1867                &proof.capability_set_digest,
1868            )?;
1869            validate_binding(
1870                "proof_aggregate.fixture_corpus_sha256",
1871                &self.fixture_corpus_sha256,
1872                &proof.fixture_corpus_sha256,
1873            )?;
1874            if first_candidate.is_some_and(|candidate| candidate != &proof.candidate) {
1875                return Err(invalid_field(
1876                    self.pack_id.as_str(),
1877                    "candidate",
1878                    "platform proofs were not built from one exact candidate identity",
1879                ));
1880            }
1881            if !archive_names.insert(proof.archive_name.as_str()) {
1882                return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
1883                    field: "archive_name",
1884                    value: proof.archive_name.clone(),
1885                });
1886            }
1887            if !archive_digests.insert(proof.archive_sha256.as_str()) {
1888                return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
1889                    field: "archive_sha256",
1890                    value: proof.archive_sha256.as_str().to_string(),
1891                });
1892            }
1893        }
1894        Ok(())
1895    }
1896}
1897
1898/// Validate one exact `ProjectAtlas` candidate identity.
1899fn validate_candidate_identity(
1900    candidate: &ParserPackCandidateIdentity,
1901    require_release_candidate: bool,
1902    logical: &OptionalParserPackManifest,
1903) -> Result<(), OptionalParserPackManifestError> {
1904    validate_identity(
1905        "candidate",
1906        "cargo_package_version",
1907        &candidate.cargo_package_version,
1908    )?;
1909    validate_binding(
1910        "candidate.intended_release_version",
1911        logical.runtime().projectatlas_version.as_str(),
1912        candidate.intended_release_version.as_str(),
1913    )?;
1914    validate_identity("candidate", "rustc_release", &candidate.rustc_release)?;
1915    validate_hex_value(
1916        "candidate rustc commit hash",
1917        "rustc_commit_hash",
1918        &candidate.rustc_commit_hash,
1919        40,
1920    )?;
1921    if require_release_candidate {
1922        validate_binding(
1923            "candidate.cargo_package_version",
1924            logical.runtime().projectatlas_version.as_str(),
1925            candidate.cargo_package_version.as_str(),
1926        )?;
1927        if candidate.source_state != ParserPackCandidateSourceState::Clean {
1928            return Err(invalid_field(
1929                candidate.projectatlas_revision.as_str(),
1930                "source_state",
1931                "fresh-runner proof requires one exact clean candidate commit",
1932            ));
1933        }
1934    }
1935    Ok(())
1936}
1937
1938/// Validate one pinned upstream asset binding without duplicating platform hashes in Rust.
1939fn validate_source_asset(
1940    asset: &ParserPackSourceAsset,
1941    logical: &OptionalParserPackManifest,
1942) -> Result<(), OptionalParserPackManifestError> {
1943    validate_binding(
1944        "source_asset.release_tag",
1945        logical.source().native_release.tag.as_str(),
1946        asset.release_tag.as_str(),
1947    )?;
1948    validate_binding(
1949        "source_asset.release_revision",
1950        &logical.source().native_release.revision,
1951        &asset.release_revision,
1952    )?;
1953    validate_safe_basename("source asset", "name", &asset.name)?;
1954    if asset.bytes == 0 || asset.bytes > OPTIONAL_PARSER_PACK_MAX_ARCHIVE_BYTES {
1955        return Err(invalid_field(
1956            asset.name.as_str(),
1957            "bytes",
1958            "source asset is empty or exceeds the acquisition ceiling",
1959        ));
1960    }
1961    Ok(())
1962}
1963
1964/// Validate all offline and physical egress-denial controls.
1965fn validate_offline_construction(
1966    construction: &ParserPackOfflineConstruction,
1967    platform: PackPlatform,
1968) -> Result<(), OptionalParserPackManifestError> {
1969    validate_network_denial(
1970        &construction.network_denial,
1971        platform,
1972        ParserPackNetworkIsolation::for_construction(platform),
1973    )
1974}
1975
1976/// Validate physical isolation method and all three egress canaries.
1977fn validate_network_denial(
1978    denial: &ParserPackNetworkDenial,
1979    platform: PackPlatform,
1980    expected: ParserPackNetworkIsolation,
1981) -> Result<(), OptionalParserPackManifestError> {
1982    if denial.mechanism != expected {
1983        return Err(invalid_field(
1984            platform.as_str(),
1985            "network_isolation",
1986            "physical network-isolation mechanism does not match the platform",
1987        ));
1988    }
1989    if !denial.dns_denied || !denial.direct_tcp_denied || !denial.https_denied {
1990        return Err(invalid_field(
1991            platform.as_str(),
1992            "network_denial",
1993            "DNS, direct TCP, and HTTPS canaries must all be denied",
1994        ));
1995    }
1996    Ok(())
1997}
1998
1999/// Validate the closed native audit result.
2000fn validate_native_audit(
2001    audit: &ParserPackNativeAudit,
2002    expected_grammars: usize,
2003) -> Result<(), OptionalParserPackManifestError> {
2004    let audited = usize::try_from(audit.audited_libraries).map_err(|_error| {
2005        invalid_field(
2006            OPTIONAL_PARSER_PACK_ID,
2007            "audited_libraries",
2008            "native audit count cannot be represented",
2009        )
2010    })?;
2011    validate_binding(
2012        "native_audit.audited_libraries",
2013        &expected_grammars,
2014        &audited,
2015    )?;
2016    if audit.forbidden_imports != 0
2017        || audit.unexpected_dependencies != 0
2018        || audit.missing_exports != 0
2019        || audit.unexpected_exports != 0
2020    {
2021        return Err(invalid_field(
2022            OPTIONAL_PARSER_PACK_ID,
2023            "native_audit",
2024            "closed import, dependency, and export audit must have zero violations",
2025        ));
2026    }
2027    Ok(())
2028}
2029
2030/// Validate the complete exact payload inventory and role-to-path mapping.
2031fn validate_payload_files(
2032    artifact: &OptionalParserPackArtifactManifest,
2033    logical: &OptionalParserPackManifest,
2034) -> Result<(), OptionalParserPackManifestError> {
2035    let platform_fixed_files = OPTIONAL_PARSER_PACK_COMMON_PAYLOAD_FILES
2036        + usize::from(artifact.platform.containment_broker_file_name().is_some());
2037    let expected_files = logical
2038        .grammars()
2039        .len()
2040        .checked_add(platform_fixed_files)
2041        .ok_or_else(|| {
2042            invalid_field(
2043                logical.pack_id(),
2044                "files",
2045                "expected payload-file count overflowed",
2046            )
2047        })?;
2048    validate_count(
2049        "artifact payload files",
2050        artifact.files.len(),
2051        expected_files,
2052        expected_files,
2053    )?;
2054    if !strictly_sorted_by(&artifact.files, |file| file.path.as_str()) {
2055        return Err(OptionalParserPackManifestError::NotSortedUnique {
2056            field: "artifact payload paths",
2057        });
2058    }
2059    let grammar_by_id = logical
2060        .grammars()
2061        .iter()
2062        .map(|grammar| (grammar.language_id.as_str(), grammar))
2063        .collect::<BTreeMap<_, _>>();
2064    let mut fixed_roles = BTreeSet::new();
2065    let mut grammar_ids = BTreeSet::new();
2066    for file in &artifact.files {
2067        if file.path.as_str() == "artifact-manifest.json" {
2068            return Err(invalid_field(
2069                file.path.as_str(),
2070                "files",
2071                "artifact manifest must not recursively list itself",
2072            ));
2073        }
2074        let expected_path = match &file.role {
2075            ParserPackPayloadRole::Worker => {
2076                fixed_roles.insert("worker");
2077                artifact.platform.worker_file_name().to_string()
2078            }
2079            ParserPackPayloadRole::ContainmentBroker => {
2080                fixed_roles.insert("containment-broker");
2081                artifact
2082                    .platform
2083                    .containment_broker_file_name()
2084                    .ok_or_else(|| {
2085                        invalid_field(
2086                            artifact.platform.as_str(),
2087                            "files",
2088                            "platform does not admit a runtime-containment broker",
2089                        )
2090                    })?
2091                    .to_string()
2092            }
2093            ParserPackPayloadRole::AcceptedManifest => {
2094                fixed_roles.insert("accepted-manifest");
2095                validate_binding(
2096                    "artifact.accepted_manifest_sha256",
2097                    &artifact.accepted_manifest_sha256,
2098                    &file.sha256,
2099                )?;
2100                "accepted-capabilities.json".to_string()
2101            }
2102            ParserPackPayloadRole::FixtureCorpus => {
2103                fixed_roles.insert("fixture-corpus");
2104                validate_binding(
2105                    "artifact.fixture_corpus_sha256",
2106                    &artifact.fixture_corpus_sha256,
2107                    &file.sha256,
2108                )?;
2109                "optional-parser-pack-corpus.json".to_string()
2110            }
2111            ParserPackPayloadRole::ProjectLicense => {
2112                fixed_roles.insert("project-license");
2113                "LICENSE".to_string()
2114            }
2115            ParserPackPayloadRole::NativeImportPolicy => {
2116                if file.bytes > OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_MAX_BYTES {
2117                    return Err(invalid_field(
2118                        file.path.as_str(),
2119                        "bytes",
2120                        "native-import policy exceeds its pre-containment byte ceiling",
2121                    ));
2122                }
2123                fixed_roles.insert("native-import-policy");
2124                validate_binding(
2125                    "artifact.native_audit.policy_sha256",
2126                    &artifact.native_audit.policy_sha256,
2127                    &file.sha256,
2128                )?;
2129                "native-import-policy.json".to_string()
2130            }
2131            ParserPackPayloadRole::NativeAuditReport => {
2132                fixed_roles.insert("native-audit-report");
2133                validate_binding(
2134                    "artifact.native_audit.report_sha256",
2135                    &artifact.native_audit.report_sha256,
2136                    &file.sha256,
2137                )?;
2138                "native-audit-report.json".to_string()
2139            }
2140            ParserPackPayloadRole::GrammarLibrary { language_id } => {
2141                validate_record_id("artifact grammar", language_id)?;
2142                if !grammar_ids.insert(language_id.as_str()) {
2143                    return Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
2144                        field: "artifact grammar",
2145                        value: language_id.clone(),
2146                    });
2147                }
2148                let grammar = grammar_by_id.get(language_id.as_str()).ok_or_else(|| {
2149                    OptionalParserPackManifestError::UnknownOptionalLanguage {
2150                        language_id: language_id.clone(),
2151                    }
2152                })?;
2153                format!(
2154                    "lib/{}",
2155                    artifact
2156                        .platform
2157                        .grammar_library_file_name(&grammar.abi_export.library_stem)
2158                )
2159            }
2160        };
2161        validate_binding(
2162            "artifact payload path",
2163            expected_path.as_str(),
2164            file.path.as_str(),
2165        )?;
2166    }
2167    validate_count(
2168        "artifact fixed payload roles",
2169        fixed_roles.len(),
2170        platform_fixed_files,
2171        platform_fixed_files,
2172    )?;
2173    if grammar_by_id
2174        .keys()
2175        .copied()
2176        .ne(grammar_ids.iter().copied())
2177    {
2178        return Err(invalid_field(
2179            artifact.pack_id.as_str(),
2180            "grammar_set",
2181            "artifact grammar identities differ from the accepted logical manifest",
2182        ));
2183    }
2184    Ok(())
2185}
2186
2187/// Validate one fresh-runner environment contract.
2188fn validate_fresh_runner(
2189    runner: &ParserPackFreshRunner,
2190    platform: PackPlatform,
2191) -> Result<(), OptionalParserPackManifestError> {
2192    validate_network_denial(
2193        &runner.network_denial,
2194        platform,
2195        ParserPackNetworkIsolation::for_fresh_runner(platform),
2196    )
2197}
2198
2199/// Validate exact sorted grammar probes and their declared success.
2200fn validate_grammar_probes(
2201    probes: &[ParserPackGrammarProbe],
2202    logical: &OptionalParserPackManifest,
2203) -> Result<(), OptionalParserPackManifestError> {
2204    validate_count(
2205        "platform proof grammars",
2206        probes.len(),
2207        logical.grammars().len(),
2208        logical.grammars().len(),
2209    )?;
2210    if !strictly_sorted_by(probes, |probe| probe.language_id.as_str()) {
2211        return Err(OptionalParserPackManifestError::NotSortedUnique {
2212            field: "platform proof grammars",
2213        });
2214    }
2215    for (probe, grammar) in probes.iter().zip(logical.grammars()) {
2216        validate_binding(
2217            "platform proof language_id",
2218            grammar.language_id.as_str(),
2219            probe.language_id.as_str(),
2220        )?;
2221        if !probe.worker_probe_passed {
2222            return Err(invalid_field(
2223                probe.language_id.as_str(),
2224                "worker_probe_passed",
2225                "manifest approval, native loading, ABI, positive, and negative fixtures must pass",
2226            ));
2227        }
2228    }
2229    Ok(())
2230}
2231
2232/// Validate one exact-host worker memory-limit and cleanup receipt.
2233fn validate_memory_probe(
2234    probe: &ParserPackMemoryProbe,
2235    platform: PackPlatform,
2236) -> Result<(), OptionalParserPackManifestError> {
2237    if probe.process_limit_bytes == 0
2238        || probe.process_limit_bytes > PARSER_WORKER_PROCESS_MEMORY_BYTES
2239        || probe.process_tree_limit_bytes < probe.process_limit_bytes
2240        || probe.process_tree_limit_bytes > PARSER_WORKER_JOB_MEMORY_BYTES
2241    {
2242        return Err(invalid_field(
2243            platform.as_str(),
2244            "memory_limits",
2245            "probe limits must be non-zero, ordered, and no stronger than the runtime ceilings",
2246        ));
2247    }
2248
2249    let sampled = match (platform, probe.control) {
2250        (PackPlatform::LinuxX86_64, ParserPackMemoryControl::LinuxProcStatus) => true,
2251        (PackPlatform::LinuxX86_64, ParserPackMemoryControl::LinuxCgroupV2)
2252        | (PackPlatform::WindowsX86_64, ParserPackMemoryControl::WindowsJobObject) => false,
2253        _ => {
2254            return Err(invalid_field(
2255                platform.as_str(),
2256                "memory.control",
2257                "memory control does not match the platform proof",
2258            ));
2259        }
2260    };
2261
2262    let expected_process_limit = match platform {
2263        PackPlatform::LinuxX86_64 => OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES,
2264        PackPlatform::WindowsX86_64 => OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES,
2265    };
2266    if probe.process_limit_bytes != expected_process_limit
2267        || probe.process_tree_limit_bytes != expected_process_limit
2268    {
2269        return Err(invalid_field(
2270            platform.as_str(),
2271            "memory_limits",
2272            "memory probe must use the exact closed release-verification ceiling",
2273        ));
2274    }
2275
2276    if sampled {
2277        let Some(interval) = probe.observation_interval_millis else {
2278            return Err(invalid_field(
2279                platform.as_str(),
2280                "memory.observation_interval_millis",
2281                "sampled Linux RSS proof requires the declared observation interval",
2282            ));
2283        };
2284        let Some(peak) = probe.peak_observed_bytes else {
2285            return Err(invalid_field(
2286                platform.as_str(),
2287                "memory.peak_observed_bytes",
2288                "sampled Linux RSS proof requires the hosted peak observation",
2289            ));
2290        };
2291        let Some(overshoot) = probe.maximum_observed_overshoot_bytes else {
2292            return Err(invalid_field(
2293                platform.as_str(),
2294                "memory.maximum_observed_overshoot_bytes",
2295                "sampled Linux RSS proof requires the hosted maximum overshoot",
2296            ));
2297        };
2298        if interval == 0
2299            || peak < probe.process_limit_bytes
2300            || overshoot != peak.saturating_sub(probe.process_limit_bytes)
2301        {
2302            return Err(invalid_field(
2303                platform.as_str(),
2304                "memory.sampled_measurement",
2305                "sampled Linux RSS interval, peak, and overshoot are inconsistent",
2306            ));
2307        }
2308    } else if probe.observation_interval_millis.is_some()
2309        || probe.peak_observed_bytes.is_some()
2310        || probe.maximum_observed_overshoot_bytes.is_some()
2311    {
2312        return Err(invalid_field(
2313            platform.as_str(),
2314            "memory.hard_limit_measurement",
2315            "kernel-hard controls must not claim sampled RSS interval or overshoot values",
2316        ));
2317    }
2318    Ok(())
2319}
2320
2321/// Validate a required platform without accepting future or duplicate values.
2322fn validate_required_platform(
2323    owner: &'static str,
2324    platform: PackPlatform,
2325) -> Result<(), OptionalParserPackManifestError> {
2326    if !PackPlatform::ALL.contains(&platform) {
2327        return Err(invalid_field(
2328            owner,
2329            "platform",
2330            "platform is not part of the optional-pack artifact target set",
2331        ));
2332    }
2333    Ok(())
2334}
2335
2336/// Validate a release archive or asset basename without path syntax.
2337fn validate_safe_basename(
2338    owner: &str,
2339    field: &'static str,
2340    value: &str,
2341) -> Result<(), OptionalParserPackManifestError> {
2342    if value.is_empty()
2343        || value.len() > OPTIONAL_PARSER_PACK_MAX_PATH_BYTES
2344        || !value
2345            .bytes()
2346            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
2347    {
2348        return Err(invalid_field(
2349            owner,
2350            field,
2351            "expected a safe ASCII basename within the pack path bound",
2352        ));
2353    }
2354    Ok(())
2355}
2356
2357/// Validate an exact lowercase hexadecimal value with a caller-owned field name.
2358fn validate_hex_value(
2359    owner: &str,
2360    field: &'static str,
2361    value: &str,
2362    expected_len: usize,
2363) -> Result<(), OptionalParserPackManifestError> {
2364    if value.len() != expected_len
2365        || !value
2366            .bytes()
2367            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2368    {
2369        return Err(invalid_field(
2370            owner,
2371            field,
2372            "expected canonical lowercase hexadecimal",
2373        ));
2374    }
2375    Ok(())
2376}
2377
2378/// Validate the selected source package against the language-registry pin.
2379fn validate_source(
2380    source: &OptionalParserPackSource,
2381) -> Result<(), OptionalParserPackManifestError> {
2382    validate_binding(
2383        "source.package",
2384        OPTIONAL_GRAMMAR_CATALOG,
2385        source.package.as_str(),
2386    )?;
2387    validate_binding(
2388        "source.version",
2389        OPTIONAL_GRAMMAR_CATALOG_VERSION,
2390        source.version.as_str(),
2391    )?;
2392    validate_binding(
2393        "source.cargo_archive.sha256",
2394        OPTIONAL_GRAMMAR_CATALOG_CRATE_SHA256,
2395        source.cargo_archive.sha256.as_str(),
2396    )?;
2397    validate_binding(
2398        "source.cargo_archive.vcs_revision",
2399        OPTIONAL_GRAMMAR_CATALOG_CRATE_REVISION,
2400        source.cargo_archive.vcs_revision.as_str(),
2401    )?;
2402    validate_binding(
2403        "source.cargo_archive.path_in_vcs",
2404        OPTIONAL_GRAMMAR_CATALOG_CRATE_PATH_IN_VCS,
2405        source.cargo_archive.path_in_vcs.as_str(),
2406    )?;
2407    validate_binding(
2408        "source.native_release.tag",
2409        OPTIONAL_GRAMMAR_CATALOG_RELEASE_TAG,
2410        source.native_release.tag.as_str(),
2411    )?;
2412    validate_binding(
2413        "source.native_release.revision",
2414        OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
2415        source.native_release.revision.as_str(),
2416    )?;
2417    validate_binding(
2418        "source.native_release.source_bundle_sha256",
2419        OPTIONAL_GRAMMAR_CATALOG_SOURCE_BUNDLE_SHA256,
2420        source.native_release.source_bundle_sha256.as_str(),
2421    )
2422}
2423
2424/// Validate the concrete consumer version and supported ABI window.
2425fn validate_runtime(
2426    runtime: &OptionalParserPackRuntime,
2427) -> Result<(), OptionalParserPackManifestError> {
2428    validate_binding(
2429        "runtime.projectatlas_version",
2430        OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION,
2431        runtime.projectatlas_version.as_str(),
2432    )?;
2433    validate_binding(
2434        "runtime.tree_sitter_version",
2435        OPTIONAL_PARSER_PACK_TREE_SITTER_VERSION,
2436        runtime.tree_sitter_version.as_str(),
2437    )?;
2438    validate_binding(
2439        "runtime.minimum_abi",
2440        &OPTIONAL_PARSER_PACK_MINIMUM_ABI,
2441        &runtime.minimum_abi,
2442    )?;
2443    validate_binding(
2444        "runtime.maximum_abi",
2445        &OPTIONAL_PARSER_PACK_MAXIMUM_ABI,
2446        &runtime.maximum_abi,
2447    )
2448}
2449
2450/// Validate that the manifest is bound to current accepted registry truth.
2451fn validate_registry_binding(
2452    binding: &OptionalParserPackRegistryBinding,
2453) -> Result<(), OptionalParserPackManifestError> {
2454    let current = OptionalParserPackRegistryBinding::current()?;
2455    validate_binding(
2456        "registry.registry_version",
2457        &current.registry_version,
2458        &binding.registry_version,
2459    )?;
2460    validate_binding(
2461        "registry.registry_digest",
2462        current.registry_digest.as_str(),
2463        binding.registry_digest.as_str(),
2464    )?;
2465    validate_binding(
2466        "registry.accepted_set_version",
2467        &current.accepted_set_version,
2468        &binding.accepted_set_version,
2469    )?;
2470    validate_binding(
2471        "registry.accepted_set_digest",
2472        current.accepted_set_digest.as_str(),
2473        binding.accepted_set_digest.as_str(),
2474    )
2475}
2476
2477/// Validate one exact deduplicated license record.
2478fn validate_license(license: &GrammarLicense) -> Result<(), OptionalParserPackManifestError> {
2479    validate_record_id("license", &license.id)?;
2480    validate_https_url(&license.id, &license.repository_url)?;
2481    validate_relative_path(&license.id, "source_path", &license.source_path, false)?;
2482    if license.text.is_empty() || license.text.len() > MAX_LICENSE_TEXT_BYTES {
2483        return Err(invalid_field(
2484            &license.id,
2485            "text",
2486            "expected 1..=262144 exact UTF-8 bytes",
2487        ));
2488    }
2489    if Blake3Digest::for_bytes(license.text.as_bytes()) != license.text_blake3 {
2490        return Err(OptionalParserPackManifestError::DigestMismatch {
2491            owner: license.id.clone(),
2492            field: "text_blake3",
2493        });
2494    }
2495    if let Some(expression) = &license.spdx_expression {
2496        validate_identity(&license.id, "spdx_expression", expression)?;
2497    }
2498    Ok(())
2499}
2500
2501/// Validate one accepted grammar against registry, license, ABI, and platform authority.
2502fn validate_grammar(
2503    grammar: &AcceptedGrammar,
2504    runtime: &OptionalParserPackRuntime,
2505    required_platforms: &[PackPlatform],
2506    license_by_id: &BTreeMap<&str, &GrammarLicense>,
2507) -> Result<(), OptionalParserPackManifestError> {
2508    let Some(capability) = language_capability(&grammar.language_id) else {
2509        return Err(OptionalParserPackManifestError::UnknownOptionalLanguage {
2510            language_id: grammar.language_id.clone(),
2511        });
2512    };
2513    if capability.id != grammar.language_id {
2514        return Err(OptionalParserPackManifestError::UnknownOptionalLanguage {
2515            language_id: grammar.language_id.clone(),
2516        });
2517    }
2518    if capability.optional_pack != Some(OPTIONAL_PARSER_PACK_ID) {
2519        return Err(OptionalParserPackManifestError::BuiltInOverlap {
2520            language_id: grammar.language_id.clone(),
2521        });
2522    }
2523    validate_https_url(&grammar.language_id, &grammar.source.repository_url)?;
2524    validate_relative_path(
2525        &grammar.language_id,
2526        "subdirectory",
2527        &grammar.source.subdirectory,
2528        true,
2529    )?;
2530    if grammar.license_record_ids.is_empty() {
2531        return Err(invalid_field(
2532            &grammar.language_id,
2533            "license_record_ids",
2534            "expected at least one applicable exact license record",
2535        ));
2536    }
2537    if !strictly_sorted_by(&grammar.license_record_ids, String::as_str) {
2538        return Err(OptionalParserPackManifestError::NotSortedUnique {
2539            field: "grammar.license_record_ids",
2540        });
2541    }
2542    for license_id in &grammar.license_record_ids {
2543        let Some(license) = license_by_id.get(license_id.as_str()) else {
2544            return Err(OptionalParserPackManifestError::UnknownLicense {
2545                language_id: grammar.language_id.clone(),
2546                license_id: license_id.clone(),
2547            });
2548        };
2549        if license.repository_url != grammar.source.repository_url
2550            || license.revision != grammar.source.revision
2551        {
2552            return Err(OptionalParserPackManifestError::LicenseSourceMismatch {
2553                language_id: grammar.language_id.clone(),
2554                license_id: license_id.clone(),
2555            });
2556        }
2557    }
2558    if grammar.abi_export.minimum_abi == 0
2559        || grammar.abi_export.maximum_abi < grammar.abi_export.minimum_abi
2560        || grammar.abi_export.expected_abi < grammar.abi_export.minimum_abi
2561        || grammar.abi_export.expected_abi > grammar.abi_export.maximum_abi
2562        || grammar.abi_export.minimum_abi != runtime.minimum_abi
2563        || grammar.abi_export.maximum_abi != runtime.maximum_abi
2564    {
2565        return Err(OptionalParserPackManifestError::AbiMismatch {
2566            language_id: grammar.language_id.clone(),
2567        });
2568    }
2569    validate_fixture(&grammar.language_id, "positive", &grammar.fixtures.positive)?;
2570    validate_fixture(&grammar.language_id, "negative", &grammar.fixtures.negative)?;
2571    if !grammar.fixtures.positive.origin.is_positive()
2572        || !grammar.fixtures.negative.origin.is_negative()
2573    {
2574        return Err(invalid_field(
2575            &grammar.language_id,
2576            "fixtures.origin",
2577            "expected a natural upstream positive and an upstream-error or incomplete-editor-state negative",
2578        ));
2579    }
2580    if grammar.fixtures.positive.source_blake3 == grammar.fixtures.negative.source_blake3 {
2581        return Err(invalid_field(
2582            &grammar.language_id,
2583            "fixtures",
2584            "positive and negative fixture source must be distinct",
2585        ));
2586    }
2587    validate_required_platforms(&grammar.language_id, &grammar.required_platforms)?;
2588    if grammar.required_platforms != required_platforms {
2589        return Err(invalid_field(
2590            &grammar.language_id,
2591            "required_platforms",
2592            "grammar and manifest platform sets differ",
2593        ));
2594    }
2595    if grammar.built_in_precedence != BuiltInParserPrecedence::BuiltInAuthoritative {
2596        return Err(invalid_field(
2597            &grammar.language_id,
2598            "built_in_precedence",
2599            "default-core ownership must remain authoritative",
2600        ));
2601    }
2602    if grammar.computed_capability_digest() != grammar.capability_digest {
2603        return Err(OptionalParserPackManifestError::DigestMismatch {
2604            owner: grammar.language_id.clone(),
2605            field: "capability_digest",
2606        });
2607    }
2608    Ok(())
2609}
2610
2611/// Validate one exact bounded provenance-bearing fixture.
2612fn validate_fixture(
2613    owner: &str,
2614    role: &'static str,
2615    fixture: &GrammarFixture,
2616) -> Result<(), OptionalParserPackManifestError> {
2617    validate_relative_path(owner, "fixture.path", &fixture.path, false)?;
2618    validate_identity(owner, "fixture.case_name", &fixture.case_name)?;
2619    if fixture.source.is_empty() || fixture.source.len() > MAX_FIXTURE_SOURCE_BYTES {
2620        return Err(invalid_field(
2621            owner,
2622            role,
2623            "expected 1..=65536 exact UTF-8 source bytes",
2624        ));
2625    }
2626    if Blake3Digest::for_bytes(fixture.source.as_bytes()) != fixture.source_blake3 {
2627        return Err(OptionalParserPackManifestError::DigestMismatch {
2628            owner: owner.to_string(),
2629            field: "fixture.source_blake3",
2630        });
2631    }
2632    Ok(())
2633}
2634
2635/// Require the complete closed optional-pack artifact target set in canonical order.
2636fn validate_required_platforms(
2637    owner: &str,
2638    platforms: &[PackPlatform],
2639) -> Result<(), OptionalParserPackManifestError> {
2640    if platforms != PackPlatform::ALL {
2641        return Err(invalid_field(
2642            owner,
2643            "required_platforms",
2644            "expected the complete canonical optional-pack artifact target set",
2645        ));
2646    }
2647    Ok(())
2648}
2649
2650/// Validate one bounded manifest collection size.
2651fn validate_count(
2652    field: &'static str,
2653    actual: usize,
2654    minimum: usize,
2655    maximum: usize,
2656) -> Result<(), OptionalParserPackManifestError> {
2657    if !(minimum..=maximum).contains(&actual) {
2658        return Err(OptionalParserPackManifestError::CountOutOfBounds {
2659            field,
2660            actual,
2661            minimum,
2662            maximum,
2663        });
2664    }
2665    Ok(())
2666}
2667
2668/// Compare one manifest binding with its selected authority.
2669fn validate_binding<T>(
2670    field: &'static str,
2671    expected: &T,
2672    actual: &T,
2673) -> Result<(), OptionalParserPackManifestError>
2674where
2675    T: Eq + fmt::Display + ?Sized,
2676{
2677    if expected != actual {
2678        return Err(OptionalParserPackManifestError::BindingMismatch {
2679            field,
2680            expected: expected.to_string(),
2681            actual: actual.to_string(),
2682        });
2683    }
2684    Ok(())
2685}
2686
2687/// Validate one canonical 256-bit lowercase hexadecimal digest.
2688fn validate_hex_digest(
2689    value: &str,
2690    field: &'static str,
2691) -> Result<(), OptionalParserPackManifestError> {
2692    if value.len() != 64
2693        || !value
2694            .bytes()
2695            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2696    {
2697        return Err(invalid_field(
2698            "digest",
2699            field,
2700            "expected 64 lowercase hexadecimal characters",
2701        ));
2702    }
2703    Ok(())
2704}
2705
2706/// Validate one bounded unpadded control-free identity field.
2707fn validate_identity(
2708    owner: &str,
2709    field: &'static str,
2710    value: &str,
2711) -> Result<(), OptionalParserPackManifestError> {
2712    if value.is_empty()
2713        || value.len() > MAX_IDENTITY_BYTES
2714        || value.trim() != value
2715        || value.chars().any(char::is_control)
2716    {
2717        return Err(invalid_field(
2718            owner,
2719            field,
2720            "expected non-empty, unpadded, control-free bounded text",
2721        ));
2722    }
2723    Ok(())
2724}
2725
2726/// Validate one stable manifest-local record identifier.
2727fn validate_record_id(owner: &str, value: &str) -> Result<(), OptionalParserPackManifestError> {
2728    validate_identity(owner, "id", value)?;
2729    if !value.bytes().all(|byte| {
2730        byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
2731    }) || !value
2732        .as_bytes()
2733        .first()
2734        .is_some_and(u8::is_ascii_alphanumeric)
2735    {
2736        return Err(invalid_field(
2737            owner,
2738            "id",
2739            "expected a lowercase ASCII record identifier",
2740        ));
2741    }
2742    Ok(())
2743}
2744
2745/// Validate one non-parameterized HTTPS provenance URL.
2746fn validate_https_url(owner: &str, value: &str) -> Result<(), OptionalParserPackManifestError> {
2747    validate_identity(owner, "repository_url", value)?;
2748    if !value.starts_with("https://") || value.contains(['?', '#']) {
2749        return Err(invalid_field(
2750            owner,
2751            "repository_url",
2752            "expected an HTTPS repository URL without query or fragment",
2753        ));
2754    }
2755    Ok(())
2756}
2757
2758/// Validate one normalized slash-separated repository-relative path.
2759fn validate_relative_path(
2760    owner: &str,
2761    field: &'static str,
2762    value: &str,
2763    allow_root: bool,
2764) -> Result<(), OptionalParserPackManifestError> {
2765    validate_identity(owner, field, value)?;
2766    if allow_root && value == "." {
2767        return Ok(());
2768    }
2769    if value.starts_with('/')
2770        || value.ends_with('/')
2771        || value.contains('\\')
2772        || value
2773            .split('/')
2774            .any(|part| part.is_empty() || matches!(part, "." | ".."))
2775    {
2776        return Err(invalid_field(
2777            owner,
2778            field,
2779            "expected a normalized repository-relative slash path",
2780        ));
2781    }
2782    Ok(())
2783}
2784
2785/// Build one deterministic invalid-field diagnostic.
2786fn invalid_field(
2787    owner: &str,
2788    field: &'static str,
2789    reason: &'static str,
2790) -> OptionalParserPackManifestError {
2791    OptionalParserPackManifestError::InvalidField {
2792        owner: owner.to_string(),
2793        field,
2794        reason,
2795    }
2796}
2797
2798/// Return whether selected string keys are strictly sorted and unique.
2799fn strictly_sorted_by<T>(values: &[T], key: impl Fn(&T) -> &str) -> bool {
2800    values.windows(2).all(|pair| key(&pair[0]) < key(&pair[1]))
2801}
2802
2803/// Add one grammar's pinned source projection to a canonical digest.
2804fn hash_source_provenance(hasher: &mut Hasher, source: &GrammarSourceProvenance) {
2805    hash_value(hasher, &source.repository_url);
2806    hash_value(hasher, source.revision.as_str());
2807    hash_value(hasher, &source.subdirectory);
2808    hash_value(hasher, source.compile_input_sha256.as_str());
2809}
2810
2811/// Add one exact fixture identity to a canonical digest.
2812fn hash_fixture(hasher: &mut Hasher, fixture: &GrammarFixture) {
2813    hash_value(hasher, fixture.origin.canonical_name());
2814    hash_value(hasher, &fixture.path);
2815    hash_value(hasher, &fixture.case_name);
2816    hash_value(hasher, fixture.source_blake3.as_str());
2817}
2818
2819/// Add one length-delimited string to a canonical digest.
2820fn hash_value(hasher: &mut Hasher, value: &str) {
2821    hasher.update(&(value.len() as u64).to_le_bytes());
2822    hasher.update(value.as_bytes());
2823}
2824
2825#[cfg(test)]
2826mod tests {
2827    use super::*;
2828    use crate::language::language_documentation_rows;
2829    use std::error::Error;
2830    use std::io;
2831
2832    const RELEASE_REVISION: &str = "6258abac30304283763a0d2dc8a48cb87fbcf438";
2833    const CRATE_VCS_REVISION: &str = "ce9e9c0974731d25b4b9426711a62d544d993368";
2834    const CRATE_SHA256: &str = "44dc94ef7a5f7f4247d88d5acdd26d842c8fc6f5eaf491a970c8e3d8fc9c9287";
2835    const SOURCE_BUNDLE_SHA256: &str =
2836        "d684799dc664553c9c746d5fe676a5b599f9efcec4cad5450bec7ec5a29574a9";
2837    const REPOSITORY_URL: &str = "https://example.invalid/optional-grammars.git";
2838
2839    /// Convert a behavior assertion into the crate's non-panicking result-test style.
2840    fn require(condition: bool, message: &'static str) -> Result<(), Box<dyn Error>> {
2841        if condition {
2842            Ok(())
2843        } else {
2844            Err(io::Error::other(message).into())
2845        }
2846    }
2847
2848    #[test]
2849    fn capability_authority_keeps_macos_arm64_builtin_only() -> Result<(), Box<dyn Error>> {
2850        let macos_arm64 = OptionalParserCapability::for_target("macos", "aarch64");
2851        require(
2852            macos_arm64 == OptionalParserCapability::BuiltInOnly
2853                && macos_arm64.pack_platform().is_none()
2854                && macos_arm64.built_in_parsing_available(),
2855            "macOS arm64 optional-parser capability drifted from built-in-only",
2856        )?;
2857        require(
2858            OptionalParserCapability::for_target("linux", "x86_64")
2859                == OptionalParserCapability::Pack {
2860                    platform: PackPlatform::LinuxX86_64,
2861                }
2862                && OptionalParserCapability::for_target("windows", "x86_64")
2863                    == OptionalParserCapability::Pack {
2864                        platform: PackPlatform::WindowsX86_64,
2865                    },
2866            "supported optional-parser platform capability drifted",
2867        )
2868    }
2869
2870    fn test_manifest() -> Result<OptionalParserPackManifest, Box<dyn Error>> {
2871        let revision = SourceRevision::new(RELEASE_REVISION)?;
2872        let licenses = vec![
2873            GrammarLicense::new(
2874                "apache-root",
2875                REPOSITORY_URL,
2876                "LICENSE-APACHE",
2877                revision.clone(),
2878                "Apache License\nVersion 2.0 fixture text.",
2879                Some("Apache-2.0".to_string()),
2880            ),
2881            GrammarLicense::new(
2882                "mit-root",
2883                REPOSITORY_URL,
2884                "LICENSE-MIT",
2885                revision.clone(),
2886                "MIT License\n\nPermission is hereby granted for the fixture.",
2887                Some("MIT".to_string()),
2888            ),
2889        ];
2890        let mut grammars = language_documentation_rows()
2891            .iter()
2892            .filter(|capability| capability.optional_pack.is_some())
2893            .take(OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS)
2894            .enumerate()
2895            .map(|(index, capability)| {
2896                let ordinal = index + 1;
2897                let license_record_ids = if index == 0 {
2898                    vec!["apache-root".to_string(), "mit-root".to_string()]
2899                } else {
2900                    vec!["mit-root".to_string()]
2901                };
2902                Ok(AcceptedGrammar::new(
2903                    capability.id,
2904                    GrammarSourceProvenance {
2905                        repository_url: REPOSITORY_URL.to_string(),
2906                        revision: revision.clone(),
2907                        subdirectory: format!("grammars/{ordinal}"),
2908                        compile_input_sha256: Sha256Digest::new(format!("{ordinal:064x}"))?,
2909                    },
2910                    license_record_ids,
2911                    GrammarAbiExport {
2912                        minimum_abi: 13,
2913                        maximum_abi: 15,
2914                        expected_abi: 15,
2915                        export_symbol: GrammarExportSymbol::new(format!(
2916                            "tree_sitter_optional_{ordinal}"
2917                        ))?,
2918                        library_stem: GrammarLibraryStem::new(format!(
2919                            "tree-sitter-optional-{ordinal}"
2920                        ))?,
2921                    },
2922                    GrammarFixtures {
2923                        positive: GrammarFixture::new(
2924                            GrammarFixtureOrigin::UpstreamTreeSitterCorpus,
2925                            format!("fixtures/{ordinal}/positive.txt"),
2926                            format!("natural positive {}", capability.id),
2927                            format!("natural positive {} source\n", capability.id),
2928                        ),
2929                        negative: GrammarFixture::new(
2930                            GrammarFixtureOrigin::ProjectAtlasIncompleteUpstreamCase,
2931                            format!("fixtures/{ordinal}/negative.txt"),
2932                            format!("natural negative {} incomplete", capability.id),
2933                            format!("natural negative {} source ?\n", capability.id),
2934                        ),
2935                    },
2936                ))
2937            })
2938            .collect::<Result<Vec<_>, OptionalParserPackManifestError>>()?;
2939        grammars.sort_by(|left, right| left.language_id.cmp(&right.language_id));
2940        OptionalParserPackManifest::new(
2941            OptionalParserPackSource {
2942                package: OPTIONAL_GRAMMAR_CATALOG.to_string(),
2943                version: OPTIONAL_GRAMMAR_CATALOG_VERSION.to_string(),
2944                cargo_archive: OptionalParserCargoArchive {
2945                    sha256: Sha256Digest::new(CRATE_SHA256)?,
2946                    vcs_revision: SourceRevision::new(CRATE_VCS_REVISION)?,
2947                    path_in_vcs: OPTIONAL_GRAMMAR_CATALOG_CRATE_PATH_IN_VCS.to_string(),
2948                },
2949                native_release: OptionalParserNativeRelease {
2950                    tag: OPTIONAL_GRAMMAR_CATALOG_RELEASE_TAG.to_string(),
2951                    revision,
2952                    source_bundle_sha256: Sha256Digest::new(SOURCE_BUNDLE_SHA256)?,
2953                },
2954            },
2955            OptionalParserPackRuntime {
2956                consumer: ParserPackConsumer::ProjectAtlasParserWorker,
2957                projectatlas_version: OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION.to_string(),
2958                tree_sitter_version: OPTIONAL_PARSER_PACK_TREE_SITTER_VERSION.to_string(),
2959                minimum_abi: 13,
2960                maximum_abi: 15,
2961            },
2962            licenses,
2963            grammars,
2964        )
2965        .map_err(Into::into)
2966    }
2967
2968    fn test_construction_network_denial(platform: PackPlatform) -> ParserPackNetworkDenial {
2969        ParserPackNetworkDenial {
2970            mechanism: ParserPackNetworkIsolation::for_construction(platform),
2971            dns_denied: true,
2972            direct_tcp_denied: true,
2973            https_denied: true,
2974        }
2975    }
2976
2977    fn test_fresh_runner_network_denial(platform: PackPlatform) -> ParserPackNetworkDenial {
2978        ParserPackNetworkDenial {
2979            mechanism: ParserPackNetworkIsolation::for_fresh_runner(platform),
2980            dns_denied: true,
2981            direct_tcp_denied: true,
2982            https_denied: true,
2983        }
2984    }
2985
2986    fn test_candidate(
2987        state: ParserPackCandidateSourceState,
2988        cargo_package_version: &str,
2989    ) -> Result<ParserPackCandidateIdentity, OptionalParserPackManifestError> {
2990        Ok(ParserPackCandidateIdentity {
2991            projectatlas_revision: SourceRevision::new(CRATE_VCS_REVISION)?,
2992            cargo_package_version: cargo_package_version.to_string(),
2993            intended_release_version: OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION.to_string(),
2994            cargo_lock_sha256: Sha256Digest::new(format!("{:064x}", 31))?,
2995            rustc_release: "1.88.0".to_string(),
2996            rustc_commit_hash: "01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf".to_string(),
2997            source_state: state,
2998        })
2999    }
3000
3001    fn test_artifact(
3002        logical: &OptionalParserPackManifest,
3003        platform: PackPlatform,
3004    ) -> Result<OptionalParserPackArtifactManifest, Box<dyn Error>> {
3005        let mut files = vec![
3006            ParserPackPayloadFile {
3007                path: PackRelativePath::new(platform.worker_file_name())?,
3008                role: ParserPackPayloadRole::Worker,
3009                bytes: 10,
3010                sha256: Sha256Digest::new(format!("{:064x}", 1))?,
3011            },
3012            ParserPackPayloadFile {
3013                path: PackRelativePath::new("accepted-capabilities.json")?,
3014                role: ParserPackPayloadRole::AcceptedManifest,
3015                bytes: 11,
3016                sha256: Sha256Digest::new(format!("{:064x}", 21))?,
3017            },
3018            ParserPackPayloadFile {
3019                path: PackRelativePath::new("optional-parser-pack-corpus.json")?,
3020                role: ParserPackPayloadRole::FixtureCorpus,
3021                bytes: 12,
3022                sha256: Sha256Digest::new(format!("{:064x}", 22))?,
3023            },
3024            ParserPackPayloadFile {
3025                path: PackRelativePath::new("LICENSE")?,
3026                role: ParserPackPayloadRole::ProjectLicense,
3027                bytes: 13,
3028                sha256: Sha256Digest::new(format!("{:064x}", 4))?,
3029            },
3030            ParserPackPayloadFile {
3031                path: PackRelativePath::new("native-import-policy.json")?,
3032                role: ParserPackPayloadRole::NativeImportPolicy,
3033                bytes: 14,
3034                sha256: Sha256Digest::new(format!("{:064x}", 25))?,
3035            },
3036            ParserPackPayloadFile {
3037                path: PackRelativePath::new("native-audit-report.json")?,
3038                role: ParserPackPayloadRole::NativeAuditReport,
3039                bytes: 15,
3040                sha256: Sha256Digest::new(format!("{:064x}", 26))?,
3041            },
3042        ];
3043        if let Some(broker_name) = platform.containment_broker_file_name() {
3044            files.push(ParserPackPayloadFile {
3045                path: PackRelativePath::new(broker_name)?,
3046                role: ParserPackPayloadRole::ContainmentBroker,
3047                bytes: 16,
3048                sha256: Sha256Digest::new(format!("{:064x}", 28))?,
3049            });
3050        }
3051        for (index, grammar) in logical.grammars().iter().enumerate() {
3052            files.push(ParserPackPayloadFile {
3053                path: PackRelativePath::new(format!(
3054                    "lib/{}",
3055                    platform.grammar_library_file_name(&grammar.abi_export.library_stem)
3056                ))?,
3057                role: ParserPackPayloadRole::GrammarLibrary {
3058                    language_id: grammar.language_id.clone(),
3059                },
3060                bytes: u64::try_from(index)?.saturating_add(100),
3061                sha256: Sha256Digest::new(format!("{:064x}", index + 100))?,
3062            });
3063        }
3064        files.sort_by(|left, right| left.path.cmp(&right.path));
3065        let measurements = ParserPackPayloadMeasurements::from_files(&files)?;
3066        Ok(OptionalParserPackArtifactManifest {
3067            schema_version: OPTIONAL_PARSER_PACK_ARTIFACT_SCHEMA_VERSION,
3068            pack_id: logical.pack_id().to_string(),
3069            projectatlas_version: logical.runtime().projectatlas_version.clone(),
3070            platform,
3071            candidate: test_candidate(ParserPackCandidateSourceState::Dirty, "0.3.26")?,
3072            accepted_manifest_sha256: Sha256Digest::new(format!("{:064x}", 21))?,
3073            capability_set_digest: logical.capability_set_digest().clone(),
3074            fixture_corpus_sha256: Sha256Digest::new(format!("{:064x}", 22))?,
3075            source_asset: ParserPackSourceAsset {
3076                release_tag: logical.source().native_release.tag.clone(),
3077                release_revision: logical.source().native_release.revision.clone(),
3078                name: format!("parsers-{}.tar.zst", platform.as_str()),
3079                sha256: Sha256Digest::new(format!("{:064x}", 23))?,
3080                bytes: 1_024,
3081                parsers_manifest_sha256: Sha256Digest::new(format!("{:064x}", 24))?,
3082            },
3083            construction: ParserPackOfflineConstruction {
3084                cargo_frozen: ParserPackVerifiedControl::Verified,
3085                cargo_offline: ParserPackVerifiedControl::Verified,
3086                dependency_offline: ParserPackVerifiedControl::Verified,
3087                zero_embedded_grammars: ParserPackVerifiedControl::Verified,
3088                language_selector_absent: ParserPackVerifiedControl::Verified,
3089                failed_grammar_override_absent: ParserPackVerifiedControl::Verified,
3090                network_denial: test_construction_network_denial(platform),
3091            },
3092            native_audit: ParserPackNativeAudit {
3093                policy_sha256: Sha256Digest::new(format!("{:064x}", 25))?,
3094                report_sha256: Sha256Digest::new(format!("{:064x}", 26))?,
3095                audited_libraries: u32::try_from(logical.grammars().len())?,
3096                forbidden_imports: 0,
3097                unexpected_dependencies: 0,
3098                missing_exports: 0,
3099                unexpected_exports: 0,
3100            },
3101            measurements,
3102            files,
3103        })
3104    }
3105
3106    fn test_platform_proof(
3107        logical: &OptionalParserPackManifest,
3108        platform: PackPlatform,
3109        ordinal: usize,
3110    ) -> Result<OptionalParserPackPlatformProof, Box<dyn Error>> {
3111        Ok(OptionalParserPackPlatformProof {
3112            schema_version: OPTIONAL_PARSER_PACK_PLATFORM_PROOF_SCHEMA_VERSION,
3113            pack_id: logical.pack_id().to_string(),
3114            platform,
3115            candidate: test_candidate(
3116                ParserPackCandidateSourceState::Clean,
3117                OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION,
3118            )?,
3119            archive_name: format!("projectatlas-broad-parser-{}.tar.zst", platform.as_str()),
3120            archive_sha256: Sha256Digest::new(format!("{:064x}", ordinal + 40))?,
3121            archive_bytes: 1_024,
3122            expanded_bytes: 4_096,
3123            artifact_manifest_sha256: Sha256Digest::new(format!("{:064x}", ordinal + 50))?,
3124            accepted_manifest_sha256: Sha256Digest::new(format!("{:064x}", 21))?,
3125            capability_set_digest: logical.capability_set_digest().clone(),
3126            fixture_corpus_sha256: Sha256Digest::new(format!("{:064x}", 22))?,
3127            native_audit_report_sha256: Sha256Digest::new(format!("{:064x}", ordinal + 60))?,
3128            runner: ParserPackFreshRunner {
3129                fresh_host: ParserPackVerifiedControl::Verified,
3130                repository_inputs_absent: ParserPackVerifiedControl::Verified,
3131                build_tools_not_invoked: ParserPackVerifiedControl::Verified,
3132                working_directory_outside_pack: ParserPackVerifiedControl::Verified,
3133                ambient_library_paths_cleared: ParserPackVerifiedControl::Verified,
3134                network_denial: test_fresh_runner_network_denial(platform),
3135            },
3136            grammars: logical
3137                .grammars()
3138                .iter()
3139                .map(|grammar| ParserPackGrammarProbe {
3140                    language_id: grammar.language_id.clone(),
3141                    worker_probe_passed: true,
3142                })
3143                .collect(),
3144            memory: match platform {
3145                PackPlatform::LinuxX86_64 => ParserPackMemoryProbe {
3146                    control: ParserPackMemoryControl::LinuxProcStatus,
3147                    process_limit_bytes: OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES,
3148                    process_tree_limit_bytes: OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES,
3149                    observation_interval_millis: Some(20),
3150                    peak_observed_bytes: Some(1024 * 1024 + 4096),
3151                    maximum_observed_overshoot_bytes: Some(4096),
3152                    limit_enforced: ParserPackVerifiedControl::Verified,
3153                    process_tree_cleaned: ParserPackVerifiedControl::Verified,
3154                },
3155                PackPlatform::WindowsX86_64 => ParserPackMemoryProbe {
3156                    control: ParserPackMemoryControl::WindowsJobObject,
3157                    process_limit_bytes: OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES,
3158                    process_tree_limit_bytes:
3159                        OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES,
3160                    observation_interval_millis: None,
3161                    peak_observed_bytes: None,
3162                    maximum_observed_overshoot_bytes: None,
3163                    limit_enforced: ParserPackVerifiedControl::Verified,
3164                    process_tree_cleaned: ParserPackVerifiedControl::Verified,
3165                },
3166            },
3167        })
3168    }
3169
3170    #[test]
3171    fn valid_manifest_round_trips_with_complete_optional_floor() -> Result<(), Box<dyn Error>> {
3172        let manifest = test_manifest()?;
3173        manifest.validate()?;
3174        require(
3175            manifest.grammars.len() >= OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
3176            "test manifest did not cover the accepted optional grammar floor",
3177        )?;
3178        require(
3179            manifest
3180                .grammars
3181                .iter()
3182                .any(|grammar| grammar.license_record_ids.len() == 2),
3183            "valid dual-licensed grammar was not retained as a license-record set",
3184        )?;
3185        let encoded = serde_json::to_vec(&manifest)?;
3186        let decoded = OptionalParserPackManifest::from_json(&encoded)?;
3187        require(
3188            decoded == manifest,
3189            "validated manifest JSON did not round-trip",
3190        )?;
3191        Ok(())
3192    }
3193
3194    #[test]
3195    fn external_release_json_rejects_unknown_fields() -> Result<(), Box<dyn Error>> {
3196        let manifest = test_manifest()?;
3197
3198        let mut logical_root = serde_json::to_value(&manifest)?;
3199        logical_root
3200            .as_object_mut()
3201            .ok_or_else(|| io::Error::other("logical manifest is not an object"))?
3202            .insert("unmodeled_claim".to_string(), serde_json::Value::Bool(true));
3203        require(
3204            OptionalParserPackManifest::from_json(&serde_json::to_vec(&logical_root)?).is_err(),
3205            "logical manifest accepted an unknown root field",
3206        )?;
3207
3208        let mut logical_nested = serde_json::to_value(&manifest)?;
3209        logical_nested["grammars"][0]["fixtures"]["positive"]
3210            .as_object_mut()
3211            .ok_or_else(|| io::Error::other("positive fixture is not an object"))?
3212            .insert("unmodeled_claim".to_string(), serde_json::Value::Bool(true));
3213        require(
3214            OptionalParserPackManifest::from_json(&serde_json::to_vec(&logical_nested)?).is_err(),
3215            "logical manifest accepted an unknown nested field",
3216        )?;
3217
3218        let artifact = test_artifact(&manifest, PackPlatform::LinuxX86_64)?;
3219        let mut artifact_json = serde_json::to_value(&artifact)?;
3220        artifact_json["candidate"]
3221            .as_object_mut()
3222            .ok_or_else(|| io::Error::other("artifact candidate is not an object"))?
3223            .insert("unmodeled_claim".to_string(), serde_json::Value::Bool(true));
3224        require(
3225            serde_json::from_value::<OptionalParserPackArtifactManifest>(artifact_json).is_err(),
3226            "artifact manifest accepted an unknown nested field",
3227        )?;
3228
3229        let platform_proof = test_platform_proof(&manifest, PackPlatform::LinuxX86_64, 0)?;
3230        let mut proof_json = serde_json::to_value(&platform_proof)?;
3231        proof_json
3232            .as_object_mut()
3233            .ok_or_else(|| io::Error::other("platform proof is not an object"))?
3234            .insert("unmodeled_claim".to_string(), serde_json::Value::Bool(true));
3235        require(
3236            serde_json::from_value::<OptionalParserPackPlatformProof>(proof_json).is_err(),
3237            "platform proof accepted an unknown root field",
3238        )?;
3239
3240        let aggregate = OptionalParserPackProofAggregate {
3241            schema_version: OPTIONAL_PARSER_PACK_PROOF_AGGREGATE_SCHEMA_VERSION,
3242            pack_id: manifest.pack_id().to_string(),
3243            projectatlas_version: manifest.runtime().projectatlas_version.clone(),
3244            accepted_manifest_sha256: platform_proof.accepted_manifest_sha256.clone(),
3245            capability_set_digest: manifest.capability_set_digest().clone(),
3246            fixture_corpus_sha256: platform_proof.fixture_corpus_sha256.clone(),
3247            platforms: vec![platform_proof],
3248        };
3249        let mut aggregate_json = serde_json::to_value(aggregate)?;
3250        aggregate_json
3251            .as_object_mut()
3252            .ok_or_else(|| io::Error::other("proof aggregate is not an object"))?
3253            .insert("unmodeled_claim".to_string(), serde_json::Value::Bool(true));
3254        require(
3255            serde_json::from_value::<OptionalParserPackProofAggregate>(aggregate_json).is_err(),
3256            "proof aggregate accepted an unknown root field",
3257        )?;
3258        Ok(())
3259    }
3260
3261    #[test]
3262    fn manifest_rejects_built_in_overlap_and_capability_shrinkage() -> Result<(), Box<dyn Error>> {
3263        let mut overlap = test_manifest()?;
3264        overlap.grammars[0].language_id = "rust".to_string();
3265        overlap
3266            .grammars
3267            .sort_by(|left, right| left.language_id.cmp(&right.language_id));
3268        require(
3269            matches!(
3270                overlap.validate(),
3271                Err(OptionalParserPackManifestError::BuiltInOverlap { .. })
3272            ),
3273            "default-core grammar overlap was accepted",
3274        )?;
3275
3276        let mut too_small = test_manifest()?;
3277        too_small
3278            .grammars
3279            .truncate(OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS - 1);
3280        require(
3281            matches!(
3282                too_small.validate(),
3283                Err(OptionalParserPackManifestError::CountOutOfBounds {
3284                    field: "grammars",
3285                    ..
3286                })
3287            ),
3288            "accepted grammar floor shrinkage was accepted",
3289        )?;
3290        Ok(())
3291    }
3292
3293    #[test]
3294    fn manifest_rejects_nondeterminism_unknown_licenses_and_runtime_collisions()
3295    -> Result<(), Box<dyn Error>> {
3296        let mut unsorted = test_manifest()?;
3297        unsorted.grammars.swap(0, 1);
3298        require(
3299            matches!(
3300                unsorted.validate(),
3301                Err(OptionalParserPackManifestError::NotSortedUnique { field: "grammars" })
3302            ),
3303            "unsorted grammar rows were accepted",
3304        )?;
3305
3306        let mut missing_license = test_manifest()?;
3307        missing_license.grammars[0].license_record_ids = vec!["missing".to_string()];
3308        require(
3309            matches!(
3310                missing_license.validate(),
3311                Err(OptionalParserPackManifestError::UnknownLicense { .. })
3312            ),
3313            "unknown license reference was accepted",
3314        )?;
3315
3316        let mut duplicate_symbol = test_manifest()?;
3317        duplicate_symbol.grammars[1].abi_export.export_symbol = duplicate_symbol.grammars[0]
3318            .abi_export
3319            .export_symbol
3320            .clone();
3321        duplicate_symbol.grammars[1].capability_digest =
3322            duplicate_symbol.grammars[1].computed_capability_digest();
3323        require(
3324            matches!(
3325                duplicate_symbol.validate(),
3326                Err(OptionalParserPackManifestError::DuplicateRuntimeIdentity {
3327                    field: "export_symbol",
3328                    ..
3329                })
3330            ),
3331            "duplicate grammar export symbol was accepted",
3332        )?;
3333        Ok(())
3334    }
3335
3336    #[test]
3337    fn manifest_rejects_tampered_content_abi_and_registry_binding() -> Result<(), Box<dyn Error>> {
3338        let mut tampered_license = test_manifest()?;
3339        tampered_license.licenses[0].text.push_str("\ntampered");
3340        require(
3341            matches!(
3342                tampered_license.validate(),
3343                Err(OptionalParserPackManifestError::DigestMismatch {
3344                    field: "text_blake3",
3345                    ..
3346                })
3347            ),
3348            "tampered exact license text was accepted",
3349        )?;
3350
3351        let mut invalid_abi = test_manifest()?;
3352        invalid_abi.grammars[0].abi_export.expected_abi = 16;
3353        invalid_abi.grammars[0].capability_digest =
3354            invalid_abi.grammars[0].computed_capability_digest();
3355        require(
3356            matches!(
3357                invalid_abi.validate(),
3358                Err(OptionalParserPackManifestError::AbiMismatch { .. })
3359            ),
3360            "grammar ABI outside the runtime window was accepted",
3361        )?;
3362
3363        let mut wrong_fixture_role = test_manifest()?;
3364        wrong_fixture_role.grammars[0].fixtures.negative.origin =
3365            GrammarFixtureOrigin::UpstreamTreeSitterCorpus;
3366        wrong_fixture_role.grammars[0].capability_digest =
3367            wrong_fixture_role.grammars[0].computed_capability_digest();
3368        require(
3369            matches!(
3370                wrong_fixture_role.validate(),
3371                Err(OptionalParserPackManifestError::InvalidField {
3372                    field: "fixtures.origin",
3373                    ..
3374                })
3375            ),
3376            "a natural-positive origin was accepted for a negative fixture",
3377        )?;
3378
3379        let mut stale_registry = test_manifest()?;
3380        stale_registry.registry.registry_version += 1;
3381        require(
3382            matches!(
3383                stale_registry.validate(),
3384                Err(OptionalParserPackManifestError::BindingMismatch {
3385                    field: "registry.registry_version",
3386                    ..
3387                })
3388            ),
3389            "stale language-registry binding was accepted",
3390        )?;
3391        Ok(())
3392    }
3393
3394    #[test]
3395    fn validated_loader_identities_reject_abbreviated_or_unsafe_values() {
3396        assert!(SourceRevision::new("6258abac").is_err());
3397        assert!(Sha256Digest::new("ABCDEF").is_err());
3398        assert!(GrammarExportSymbol::new("../tree_sitter_bad").is_err());
3399        assert!(GrammarLibraryStem::new("tree-sitter.dll").is_err());
3400        assert!(PackRelativePath::new("../artifact-manifest.json").is_err());
3401        assert!(PackRelativePath::new("lib\\tree_sitter_bad.dll").is_err());
3402        assert!(PackRelativePath::new("/absolute/path").is_err());
3403    }
3404
3405    #[test]
3406    fn artifact_manifest_requires_exact_payload_and_closed_audit() -> Result<(), Box<dyn Error>> {
3407        let logical = test_manifest()?;
3408        let artifact = test_artifact(&logical, PackPlatform::WindowsX86_64)?;
3409        artifact.validate(&logical)?;
3410        require(
3411            artifact.files.iter().any(|file| {
3412                matches!(&file.role, ParserPackPayloadRole::ContainmentBroker)
3413                    && file.path.as_str() == "projectatlas-parser-containment.exe"
3414            }),
3415            "Windows artifact did not retain its runtime-containment broker",
3416        )?;
3417        require(
3418            usize::try_from(artifact.measurements.grammar_libraries)? == logical.grammars().len(),
3419            "artifact did not retain the exact accepted grammar count",
3420        )?;
3421
3422        let mut wrong_construction_isolation = artifact.clone();
3423        wrong_construction_isolation
3424            .construction
3425            .network_denial
3426            .mechanism = ParserPackNetworkIsolation::WindowsAppContainer;
3427        require(
3428            wrong_construction_isolation.validate(&logical).is_err(),
3429            "Windows construction accepted its fresh-verification isolation mechanism",
3430        )?;
3431        let mut cross_platform_construction_isolation = artifact.clone();
3432        cross_platform_construction_isolation
3433            .construction
3434            .network_denial
3435            .mechanism = ParserPackNetworkIsolation::LinuxNetworkNamespace;
3436        require(
3437            cross_platform_construction_isolation
3438                .validate(&logical)
3439                .is_err(),
3440            "Windows construction accepted a Linux network namespace",
3441        )?;
3442
3443        let mut recursive_manifest = artifact.clone();
3444        let worker = recursive_manifest
3445            .files
3446            .iter_mut()
3447            .find(|file| matches!(&file.role, ParserPackPayloadRole::Worker))
3448            .ok_or_else(|| io::Error::other("worker payload missing"))?;
3449        worker.path = PackRelativePath::new("artifact-manifest.json")?;
3450        recursive_manifest
3451            .files
3452            .sort_by(|left, right| left.path.cmp(&right.path));
3453        require(
3454            recursive_manifest.validate(&logical).is_err(),
3455            "artifact manifest was allowed to list itself",
3456        )?;
3457
3458        let mut missing_broker = artifact.clone();
3459        missing_broker
3460            .files
3461            .retain(|file| !matches!(&file.role, ParserPackPayloadRole::ContainmentBroker));
3462        missing_broker.measurements =
3463            ParserPackPayloadMeasurements::from_files(&missing_broker.files)?;
3464        require(
3465            missing_broker.validate(&logical).is_err(),
3466            "Windows artifact without its runtime-containment broker was accepted",
3467        )?;
3468
3469        let mut forbidden_import = artifact.clone();
3470        forbidden_import.native_audit.forbidden_imports = 1;
3471        require(
3472            forbidden_import.validate(&logical).is_err(),
3473            "artifact with a forbidden native import was accepted",
3474        )?;
3475
3476        let mut detached_audit_report = artifact.clone();
3477        let report = detached_audit_report
3478            .files
3479            .iter_mut()
3480            .find(|file| matches!(&file.role, ParserPackPayloadRole::NativeAuditReport))
3481            .ok_or_else(|| io::Error::other("native audit report payload missing"))?;
3482        report.sha256 = Sha256Digest::new(format!("{:064x}", 27))?;
3483        require(
3484            detached_audit_report.validate(&logical).is_err(),
3485            "artifact audit claim was allowed to detach from its packaged report",
3486        )?;
3487
3488        let mut oversized_policy = artifact.clone();
3489        let policy = oversized_policy
3490            .files
3491            .iter_mut()
3492            .find(|file| matches!(&file.role, ParserPackPayloadRole::NativeImportPolicy))
3493            .ok_or_else(|| io::Error::other("native-import policy payload missing"))?;
3494        policy.bytes = OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_MAX_BYTES + 1;
3495        oversized_policy.measurements =
3496            ParserPackPayloadMeasurements::from_files(&oversized_policy.files)?;
3497        require(
3498            matches!(
3499                oversized_policy.validate(&logical),
3500                Err(OptionalParserPackManifestError::InvalidField { field: "bytes", .. })
3501            ),
3502            "oversized native-import policy was accepted",
3503        )?;
3504
3505        let mut network_available = artifact;
3506        network_available.construction.network_denial.https_denied = false;
3507        require(
3508            network_available.validate(&logical).is_err(),
3509            "artifact constructed with reachable HTTPS was accepted",
3510        )?;
3511        Ok(())
3512    }
3513
3514    #[test]
3515    fn linux_artifact_rejects_a_windows_containment_broker() -> Result<(), Box<dyn Error>> {
3516        let logical = test_manifest()?;
3517        let mut artifact = test_artifact(&logical, PackPlatform::LinuxX86_64)?;
3518        for mechanism in [
3519            ParserPackNetworkIsolation::WindowsPrincipalFirewall,
3520            ParserPackNetworkIsolation::WindowsAppContainer,
3521        ] {
3522            let mut wrong_isolation = artifact.clone();
3523            wrong_isolation.construction.network_denial.mechanism = mechanism;
3524            require(
3525                wrong_isolation.validate(&logical).is_err(),
3526                "Linux construction accepted a Windows isolation mechanism",
3527            )?;
3528        }
3529        require(
3530            artifact
3531                .files
3532                .iter()
3533                .all(|file| !matches!(&file.role, ParserPackPayloadRole::ContainmentBroker)),
3534            "Linux test artifact unexpectedly contained a broker",
3535        )?;
3536        artifact.files.push(ParserPackPayloadFile {
3537            path: PackRelativePath::new("projectatlas-parser-containment.exe")?,
3538            role: ParserPackPayloadRole::ContainmentBroker,
3539            bytes: 16,
3540            sha256: Sha256Digest::new(format!("{:064x}", 28))?,
3541        });
3542        artifact
3543            .files
3544            .sort_by(|left, right| left.path.cmp(&right.path));
3545        artifact.measurements = ParserPackPayloadMeasurements::from_files(&artifact.files)?;
3546        require(
3547            artifact.validate(&logical).is_err(),
3548            "Linux artifact accepted a Windows runtime-containment broker",
3549        )?;
3550        Ok(())
3551    }
3552
3553    #[test]
3554    fn proof_aggregate_requires_one_clean_identical_platform_set() -> Result<(), Box<dyn Error>> {
3555        let logical = test_manifest()?;
3556        let platforms = PackPlatform::ALL
3557            .iter()
3558            .copied()
3559            .enumerate()
3560            .map(|(ordinal, platform)| test_platform_proof(&logical, platform, ordinal))
3561            .collect::<Result<Vec<_>, _>>()?;
3562        let aggregate = OptionalParserPackProofAggregate {
3563            schema_version: OPTIONAL_PARSER_PACK_PROOF_AGGREGATE_SCHEMA_VERSION,
3564            pack_id: logical.pack_id().to_string(),
3565            projectatlas_version: logical.runtime().projectatlas_version.clone(),
3566            accepted_manifest_sha256: platforms[0].accepted_manifest_sha256.clone(),
3567            capability_set_digest: logical.capability_set_digest().clone(),
3568            fixture_corpus_sha256: platforms[0].fixture_corpus_sha256.clone(),
3569            platforms,
3570        };
3571        aggregate.validate(&logical)?;
3572
3573        let mut wrong_fresh_isolation = aggregate.clone();
3574        wrong_fresh_isolation.platforms[1]
3575            .runner
3576            .network_denial
3577            .mechanism = ParserPackNetworkIsolation::WindowsPrincipalFirewall;
3578        require(
3579            wrong_fresh_isolation.validate(&logical).is_err(),
3580            "Windows fresh verification accepted its construction isolation mechanism",
3581        )?;
3582        let mut cross_platform_fresh_isolation = aggregate.clone();
3583        cross_platform_fresh_isolation.platforms[1]
3584            .runner
3585            .network_denial
3586            .mechanism = ParserPackNetworkIsolation::LinuxNetworkNamespace;
3587        require(
3588            cross_platform_fresh_isolation.validate(&logical).is_err(),
3589            "Windows fresh verification accepted a Linux network namespace",
3590        )?;
3591        for mechanism in [
3592            ParserPackNetworkIsolation::WindowsPrincipalFirewall,
3593            ParserPackNetworkIsolation::WindowsAppContainer,
3594        ] {
3595            let mut linux_wrong_isolation = aggregate.clone();
3596            linux_wrong_isolation.platforms[0]
3597                .runner
3598                .network_denial
3599                .mechanism = mechanism;
3600            require(
3601                linux_wrong_isolation.validate(&logical).is_err(),
3602                "Linux fresh verification accepted a Windows isolation mechanism",
3603            )?;
3604        }
3605
3606        let mut failed_probe = aggregate.clone();
3607        failed_probe.platforms[0].grammars[0].worker_probe_passed = false;
3608        require(
3609            failed_probe.validate(&logical).is_err(),
3610            "aggregate accepted one failed grammar/platform probe",
3611        )?;
3612
3613        let mut dirty_candidate = aggregate.clone();
3614        dirty_candidate.platforms[0].candidate.source_state = ParserPackCandidateSourceState::Dirty;
3615        require(
3616            dirty_candidate.validate(&logical).is_err(),
3617            "aggregate accepted a dirty candidate proof",
3618        )?;
3619
3620        let mut false_overshoot = aggregate.clone();
3621        false_overshoot.platforms[0]
3622            .memory
3623            .maximum_observed_overshoot_bytes = Some(0);
3624        require(
3625            false_overshoot.validate(&logical).is_err(),
3626            "aggregate accepted an inconsistent sampled RSS overshoot",
3627        )?;
3628
3629        let mut mismatched_memory_control = aggregate.clone();
3630        mismatched_memory_control.platforms[1].memory.control =
3631            ParserPackMemoryControl::LinuxCgroupV2;
3632        require(
3633            mismatched_memory_control.validate(&logical).is_err(),
3634            "aggregate accepted a memory control from another platform",
3635        )?;
3636
3637        let mut impossible_windows_probe = aggregate.clone();
3638        impossible_windows_probe.platforms[1]
3639            .memory
3640            .process_limit_bytes =
3641            OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES.saturating_sub(1);
3642        require(
3643            impossible_windows_probe.validate(&logical).is_err(),
3644            "aggregate accepted a Windows probe below the broker's configured floor",
3645        )?;
3646
3647        let mut missing_platform = aggregate;
3648        missing_platform.platforms.pop();
3649        require(
3650            missing_platform.validate(&logical).is_err(),
3651            "aggregate accepted a missing required platform",
3652        )?;
3653        Ok(())
3654    }
3655}