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