1use 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
19pub const OPTIONAL_PARSER_PACK_MANIFEST_SCHEMA_VERSION: u32 = 2;
21pub const OPTIONAL_PARSER_PACK_CAPABILITY_SET_VERSION: u32 = 2;
23pub const OPTIONAL_PARSER_PACK_ID: &str = BROAD_PARSER_PACK_ID;
25pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_SHA256: &str =
27 "44dc94ef7a5f7f4247d88d5acdd26d842c8fc6f5eaf491a970c8e3d8fc9c9287";
28pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_REVISION: &str =
30 "ce9e9c0974731d25b4b9426711a62d544d993368";
31pub const OPTIONAL_GRAMMAR_CATALOG_CRATE_PATH_IN_VCS: &str = "crates/ts-pack-core";
33pub const OPTIONAL_GRAMMAR_CATALOG_RELEASE_TAG: &str = "v1.13.2";
35pub const OPTIONAL_GRAMMAR_CATALOG_SOURCE_BUNDLE_SHA256: &str =
37 "d684799dc664553c9c746d5fe676a5b599f9efcec4cad5450bec7ec5a29574a9";
38pub const OPTIONAL_PARSER_PACK_PROJECTATLAS_VERSION: &str = "0.4.2";
40pub const OPTIONAL_PARSER_PACK_TREE_SITTER_VERSION: &str = "0.26.9";
42pub const OPTIONAL_PARSER_PACK_MINIMUM_ABI: u32 = 13;
44pub const OPTIONAL_PARSER_PACK_MAXIMUM_ABI: u32 = 15;
46pub const OPTIONAL_PARSER_PACK_MANIFEST_MAX_BYTES: usize = 32 * 1024 * 1024;
48pub const OPTIONAL_PARSER_PACK_ARTIFACT_SCHEMA_VERSION: u32 = 2;
50pub const OPTIONAL_PARSER_PACK_NATIVE_AUDIT_SCHEMA_VERSION: u32 = 3;
52pub const OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_SCHEMA_VERSION: u32 = 3;
54pub const OPTIONAL_PARSER_PACK_LINUX_RUNTIME_LOADER_BASENAME: &str = "ld-linux-x86-64.so.2";
56pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_RUNTIME_FAMILY: &str = "windows-net-framework-clr-v4";
58pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_NATIVE_ENTRY_POINT: &str = "0x0000000000000000";
60pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_CLR_RUNTIME_HEADER_SIZE: u32 = 72;
62pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_PE_LOADER_LIBRARIES: &[&str] = &[];
64pub const OPTIONAL_PARSER_PACK_WINDOWS_BROKER_MANAGED_MODULES: &[&str] =
66 &["advapi32.dll", "kernel32.dll", "userenv.dll"];
67pub const OPTIONAL_PARSER_PACK_PLATFORM_PROOF_SCHEMA_VERSION: u32 = 2;
69pub const OPTIONAL_PARSER_PACK_PROOF_AGGREGATE_SCHEMA_VERSION: u32 = 2;
71pub const OPTIONAL_PARSER_PACK_LINUX_MEMORY_PROBE_BYTES: u64 = 1024 * 1024;
73pub const OPTIONAL_PARSER_PACK_WINDOWS_MINIMUM_MEMORY_PROBE_BYTES: u64 = 16 * 1024 * 1024;
75pub const OPTIONAL_PARSER_PACK_MAX_ARCHIVE_BYTES: u64 = 64 * 1024 * 1024;
77pub const OPTIONAL_PARSER_PACK_MAX_EXPANDED_BYTES: u64 = 512 * 1024 * 1024;
79pub const OPTIONAL_PARSER_PACK_MAX_FILE_BYTES: u64 = 128 * 1024 * 1024;
81pub const OPTIONAL_PARSER_PACK_NATIVE_IMPORT_POLICY_MAX_BYTES: u64 = 1024 * 1024;
83pub const OPTIONAL_PARSER_PACK_MAX_PATH_BYTES: usize = 256;
85pub const OPTIONAL_PARSER_PACK_MAX_FILE_ENTRIES: usize = 512;
87
88const MAX_ACCEPTED_GRAMMARS: usize = 512;
90const MAX_LICENSE_RECORDS: usize = 1_024;
92const MAX_IDENTITY_BYTES: usize = 4_096;
94const MAX_LICENSE_TEXT_BYTES: usize = 256 * 1024;
96const MAX_FIXTURE_SOURCE_BYTES: usize = 64 * 1024;
98const OPTIONAL_PARSER_PACK_COMMON_PAYLOAD_FILES: usize = 6;
100const CAPABILITY_DIGEST_DOMAIN: &str = "projectatlas.optional-parser-capability.v1";
102const MANIFEST_DIGEST_DOMAIN: &str = "projectatlas.optional-parser-pack-manifest.v2";
104
105#[derive(Debug, Error)]
107pub enum OptionalParserPackManifestError {
108 #[error("optional parser-pack manifest is {actual} bytes; maximum is {maximum}")]
110 ManifestTooLarge {
111 actual: usize,
113 maximum: usize,
115 },
116 #[error("invalid optional parser-pack manifest JSON")]
118 InvalidJson {
119 #[source]
121 source: serde_json::Error,
122 },
123 #[error("invalid {field} for {owner}: {reason}")]
125 InvalidField {
126 owner: String,
128 field: &'static str,
130 reason: &'static str,
132 },
133 #[error("optional parser-pack binding {field} is {actual:?}; expected {expected:?}")]
135 BindingMismatch {
136 field: &'static str,
138 expected: String,
140 actual: String,
142 },
143 #[error("optional parser-pack {field} must be strictly sorted and unique")]
145 NotSortedUnique {
146 field: &'static str,
148 },
149 #[error("optional parser-pack {field} count {actual} is outside {minimum}..={maximum}")]
151 CountOutOfBounds {
152 field: &'static str,
154 actual: usize,
156 minimum: usize,
158 maximum: usize,
160 },
161 #[error("grammar {language_id:?} is not a canonical optional language capability")]
163 UnknownOptionalLanguage {
164 language_id: String,
166 },
167 #[error("grammar {language_id:?} overlaps a default-core language capability")]
169 BuiltInOverlap {
170 language_id: String,
172 },
173 #[error("grammar {language_id:?} references unknown license record {license_id:?}")]
175 UnknownLicense {
176 language_id: String,
178 license_id: String,
180 },
181 #[error(
183 "grammar {language_id:?} license {license_id:?} does not match its source repository and revision"
184 )]
185 LicenseSourceMismatch {
186 language_id: String,
188 license_id: String,
190 },
191 #[error("optional parser-pack {field} {value:?} is owned by more than one grammar")]
193 DuplicateRuntimeIdentity {
194 field: &'static str,
196 value: String,
198 },
199 #[error("grammar {language_id:?} ABI claim is outside the consuming runtime window")]
201 AbiMismatch {
202 language_id: String,
204 },
205 #[error("{field} digest mismatch for {owner}")]
207 DigestMismatch {
208 owner: String,
210 field: &'static str,
212 },
213}
214
215#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
217#[serde(transparent)]
218pub struct Sha256Digest(String);
219
220impl Sha256Digest {
221 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 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
257#[serde(transparent)]
258pub struct Blake3Digest(String);
259
260impl Blake3Digest {
261 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 #[must_use]
274 pub fn for_bytes(bytes: &[u8]) -> Self {
275 Self(blake3::hash(bytes).to_hex().to_string())
276 }
277
278 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
303#[serde(transparent)]
304pub struct SourceRevision(String);
305
306impl SourceRevision {
307 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 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
353#[serde(transparent)]
354pub struct GrammarExportSymbol(String);
355
356impl GrammarExportSymbol {
357 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 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
400#[serde(transparent)]
401pub struct GrammarLibraryStem(String);
402
403impl GrammarLibraryStem {
404 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 #[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#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
449pub enum PackPlatform {
450 #[serde(rename = "x86_64-unknown-linux-gnu")]
452 LinuxX86_64,
453 #[serde(rename = "x86_64-pc-windows-msvc")]
455 WindowsX86_64,
456}
457
458impl PackPlatform {
459 pub const ALL: &'static [Self] = &[Self::LinuxX86_64, Self::WindowsX86_64];
461
462 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 #[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 #[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 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
500#[serde(transparent)]
501pub struct PackRelativePath(String);
502
503impl PackRelativePath {
504 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 #[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#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
549#[serde(rename_all = "kebab-case")]
550pub enum ParserPackCandidateSourceState {
551 Clean,
553 Dirty,
555}
556
557#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
559#[serde(deny_unknown_fields)]
560pub struct ParserPackCandidateIdentity {
561 pub projectatlas_revision: SourceRevision,
563 pub cargo_package_version: String,
565 pub intended_release_version: String,
567 pub cargo_lock_sha256: Sha256Digest,
569 pub rustc_release: String,
571 pub rustc_commit_hash: String,
573 pub source_state: ParserPackCandidateSourceState,
575}
576
577#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
579#[serde(rename_all = "kebab-case")]
580pub enum ParserPackNetworkIsolation {
581 LinuxNetworkNamespace,
583 WindowsPrincipalFirewall,
585 WindowsAppContainer,
587}
588
589impl ParserPackNetworkIsolation {
590 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 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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
609#[serde(deny_unknown_fields)]
610pub struct ParserPackNetworkDenial {
611 pub mechanism: ParserPackNetworkIsolation,
613 pub dns_denied: bool,
615 pub direct_tcp_denied: bool,
617 pub https_denied: bool,
619}
620
621#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
623#[serde(rename_all = "kebab-case")]
624pub enum ParserPackVerifiedControl {
625 Verified,
627}
628
629#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
631#[serde(deny_unknown_fields)]
632pub struct ParserPackOfflineConstruction {
633 pub cargo_frozen: ParserPackVerifiedControl,
635 pub cargo_offline: ParserPackVerifiedControl,
637 pub dependency_offline: ParserPackVerifiedControl,
639 pub zero_embedded_grammars: ParserPackVerifiedControl,
641 pub language_selector_absent: ParserPackVerifiedControl,
643 pub failed_grammar_override_absent: ParserPackVerifiedControl,
645 pub network_denial: ParserPackNetworkDenial,
647}
648
649#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
651#[serde(deny_unknown_fields)]
652pub struct ParserPackSourceAsset {
653 pub release_tag: String,
655 pub release_revision: SourceRevision,
657 pub name: String,
659 pub sha256: Sha256Digest,
661 pub bytes: u64,
663 pub parsers_manifest_sha256: Sha256Digest,
665}
666
667#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
669#[serde(deny_unknown_fields, tag = "kind", rename_all = "kebab-case")]
670pub enum ParserPackPayloadRole {
671 Worker,
673 ContainmentBroker,
675 AcceptedManifest,
677 FixtureCorpus,
679 ProjectLicense,
681 NativeImportPolicy,
683 NativeAuditReport,
685 GrammarLibrary {
687 language_id: String,
689 },
690}
691
692#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
694#[serde(deny_unknown_fields)]
695pub struct ParserPackPayloadFile {
696 pub path: PackRelativePath,
698 pub role: ParserPackPayloadRole,
700 pub bytes: u64,
702 pub sha256: Sha256Digest,
704}
705
706#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
708#[serde(deny_unknown_fields)]
709pub struct ParserPackPayloadMeasurements {
710 pub files: u32,
712 pub grammar_libraries: u32,
714 pub payload_bytes: u64,
716 pub largest_file_bytes: u64,
718 pub longest_path_bytes: u32,
720}
721
722#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
724#[serde(deny_unknown_fields)]
725pub struct ParserPackNativeAudit {
726 pub policy_sha256: Sha256Digest,
728 pub report_sha256: Sha256Digest,
730 pub audited_libraries: u32,
732 pub forbidden_imports: u32,
734 pub unexpected_dependencies: u32,
736 pub missing_exports: u32,
738 pub unexpected_exports: u32,
740}
741
742#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
744#[serde(deny_unknown_fields)]
745pub struct OptionalParserPackArtifactManifest {
746 pub schema_version: u32,
748 pub pack_id: String,
750 pub projectatlas_version: String,
752 pub platform: PackPlatform,
754 pub candidate: ParserPackCandidateIdentity,
756 pub accepted_manifest_sha256: Sha256Digest,
758 pub capability_set_digest: Blake3Digest,
760 pub fixture_corpus_sha256: Sha256Digest,
762 pub source_asset: ParserPackSourceAsset,
764 pub construction: ParserPackOfflineConstruction,
766 pub native_audit: ParserPackNativeAudit,
768 pub measurements: ParserPackPayloadMeasurements,
770 pub files: Vec<ParserPackPayloadFile>,
772}
773
774#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
776#[serde(deny_unknown_fields)]
777pub struct ParserPackFreshRunner {
778 pub fresh_host: ParserPackVerifiedControl,
780 pub repository_inputs_absent: ParserPackVerifiedControl,
782 pub build_tools_not_invoked: ParserPackVerifiedControl,
784 pub working_directory_outside_pack: ParserPackVerifiedControl,
786 pub ambient_library_paths_cleared: ParserPackVerifiedControl,
788 pub network_denial: ParserPackNetworkDenial,
790}
791
792impl ParserPackFreshRunner {
793 pub fn validate(&self, platform: PackPlatform) -> Result<(), OptionalParserPackManifestError> {
799 validate_fresh_runner(self, platform)
800 }
801}
802
803#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
805#[serde(deny_unknown_fields)]
806pub struct ParserPackGrammarProbe {
807 pub language_id: String,
809 pub worker_probe_passed: bool,
811}
812
813#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
815#[serde(rename_all = "kebab-case")]
816pub enum ParserPackMemoryControl {
817 LinuxCgroupV2,
819 LinuxProcStatus,
821 WindowsJobObject,
823}
824
825#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
827#[serde(deny_unknown_fields)]
828pub struct ParserPackMemoryProbe {
829 pub control: ParserPackMemoryControl,
831 pub process_limit_bytes: u64,
833 pub process_tree_limit_bytes: u64,
835 pub observation_interval_millis: Option<u64>,
837 pub peak_observed_bytes: Option<u64>,
839 pub maximum_observed_overshoot_bytes: Option<u64>,
841 pub limit_enforced: ParserPackVerifiedControl,
843 pub process_tree_cleaned: ParserPackVerifiedControl,
845}
846
847#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
849#[serde(deny_unknown_fields)]
850pub struct OptionalParserPackPlatformProof {
851 pub schema_version: u32,
853 pub pack_id: String,
855 pub platform: PackPlatform,
857 pub candidate: ParserPackCandidateIdentity,
859 pub archive_name: String,
861 pub archive_sha256: Sha256Digest,
863 pub archive_bytes: u64,
865 pub expanded_bytes: u64,
867 pub artifact_manifest_sha256: Sha256Digest,
869 pub accepted_manifest_sha256: Sha256Digest,
871 pub capability_set_digest: Blake3Digest,
873 pub fixture_corpus_sha256: Sha256Digest,
875 pub native_audit_report_sha256: Sha256Digest,
877 pub runner: ParserPackFreshRunner,
879 pub grammars: Vec<ParserPackGrammarProbe>,
881 pub memory: ParserPackMemoryProbe,
883}
884
885#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
887#[serde(deny_unknown_fields)]
888pub struct OptionalParserPackProofAggregate {
889 pub schema_version: u32,
891 pub pack_id: String,
893 pub projectatlas_version: String,
895 pub accepted_manifest_sha256: Sha256Digest,
897 pub capability_set_digest: Blake3Digest,
899 pub fixture_corpus_sha256: Sha256Digest,
901 pub platforms: Vec<OptionalParserPackPlatformProof>,
903}
904
905#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
907#[serde(rename_all = "kebab-case")]
908pub enum ParserPackConsumer {
909 #[serde(rename = "projectatlas-parser-worker")]
911 ProjectAtlasParserWorker,
912}
913
914impl ParserPackConsumer {
915 const fn canonical_name(self) -> &'static str {
917 match self {
918 Self::ProjectAtlasParserWorker => "projectatlas-parser-worker",
919 }
920 }
921}
922
923#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
925#[serde(rename_all = "kebab-case")]
926pub enum BuiltInParserPrecedence {
927 BuiltInAuthoritative,
929}
930
931impl BuiltInParserPrecedence {
932 const fn canonical_name(self) -> &'static str {
934 match self {
935 Self::BuiltInAuthoritative => "built-in-authoritative",
936 }
937 }
938}
939
940#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
942#[serde(deny_unknown_fields)]
943pub struct OptionalParserPackSource {
944 pub package: String,
946 pub version: String,
948 pub cargo_archive: OptionalParserCargoArchive,
950 pub native_release: OptionalParserNativeRelease,
952}
953
954#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
956#[serde(deny_unknown_fields)]
957pub struct OptionalParserCargoArchive {
958 pub sha256: Sha256Digest,
960 pub vcs_revision: SourceRevision,
962 pub path_in_vcs: String,
964}
965
966#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
968#[serde(deny_unknown_fields)]
969pub struct OptionalParserNativeRelease {
970 pub tag: String,
972 pub revision: SourceRevision,
974 pub source_bundle_sha256: Sha256Digest,
976}
977
978#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
980#[serde(deny_unknown_fields)]
981pub struct OptionalParserPackRuntime {
982 pub consumer: ParserPackConsumer,
984 pub projectatlas_version: String,
986 pub tree_sitter_version: String,
988 pub minimum_abi: u32,
990 pub maximum_abi: u32,
992}
993
994#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
996#[serde(deny_unknown_fields)]
997pub struct OptionalParserPackRegistryBinding {
998 pub registry_version: u32,
1000 pub registry_digest: Blake3Digest,
1002 pub accepted_set_version: u32,
1004 pub accepted_set_digest: Blake3Digest,
1006}
1007
1008impl OptionalParserPackRegistryBinding {
1009 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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1026#[serde(deny_unknown_fields)]
1027pub struct GrammarSourceProvenance {
1028 pub repository_url: String,
1030 pub revision: SourceRevision,
1032 pub subdirectory: String,
1034 pub compile_input_sha256: Sha256Digest,
1036}
1037
1038#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1040#[serde(deny_unknown_fields)]
1041pub struct GrammarLicense {
1042 pub id: String,
1044 pub repository_url: String,
1046 pub source_path: String,
1048 pub revision: SourceRevision,
1050 pub text: String,
1052 pub text_blake3: Blake3Digest,
1054 pub spdx_expression: Option<String>,
1056}
1057
1058impl GrammarLicense {
1059 #[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1084#[serde(deny_unknown_fields)]
1085pub struct GrammarAbiExport {
1086 pub minimum_abi: u32,
1088 pub maximum_abi: u32,
1090 pub expected_abi: u32,
1092 pub export_symbol: GrammarExportSymbol,
1094 pub library_stem: GrammarLibraryStem,
1096}
1097
1098#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1100pub enum GrammarFixtureOrigin {
1101 #[serde(rename = "upstream-tree-sitter-corpus")]
1103 UpstreamTreeSitterCorpus,
1104 #[serde(rename = "upstream-language-example")]
1106 UpstreamLanguageExample,
1107 #[serde(rename = "upstream-corpus-error-case")]
1109 UpstreamCorpusErrorCase,
1110 #[serde(rename = "projectatlas-incomplete-upstream-case")]
1112 ProjectAtlasIncompleteUpstreamCase,
1113 #[serde(rename = "projectatlas-incomplete-upstream-example")]
1115 ProjectAtlasIncompleteUpstreamExample,
1116}
1117
1118impl GrammarFixtureOrigin {
1119 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 const fn is_positive(self) -> bool {
1134 matches!(
1135 self,
1136 Self::UpstreamTreeSitterCorpus | Self::UpstreamLanguageExample
1137 )
1138 }
1139
1140 const fn is_negative(self) -> bool {
1142 matches!(
1143 self,
1144 Self::UpstreamCorpusErrorCase
1145 | Self::ProjectAtlasIncompleteUpstreamCase
1146 | Self::ProjectAtlasIncompleteUpstreamExample
1147 )
1148 }
1149}
1150
1151#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1153#[serde(deny_unknown_fields)]
1154pub struct GrammarFixture {
1155 pub origin: GrammarFixtureOrigin,
1157 pub path: String,
1159 pub case_name: String,
1161 pub source: String,
1163 pub source_blake3: Blake3Digest,
1165}
1166
1167impl GrammarFixture {
1168 #[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1189#[serde(deny_unknown_fields)]
1190pub struct GrammarFixtures {
1191 pub positive: GrammarFixture,
1193 pub negative: GrammarFixture,
1195}
1196
1197#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1199#[serde(deny_unknown_fields)]
1200pub struct AcceptedGrammar {
1201 pub language_id: String,
1203 pub source: GrammarSourceProvenance,
1205 pub license_record_ids: Vec<String>,
1207 pub abi_export: GrammarAbiExport,
1209 pub fixtures: GrammarFixtures,
1211 pub required_platforms: Vec<PackPlatform>,
1213 pub built_in_precedence: BuiltInParserPrecedence,
1215 pub capability_digest: Blake3Digest,
1217}
1218
1219impl AcceptedGrammar {
1220 #[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 #[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#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1270pub struct OptionalParserPackManifest {
1271 schema_version: u32,
1273 pack_id: String,
1275 capability_set_version: u32,
1277 source: OptionalParserPackSource,
1279 runtime: OptionalParserPackRuntime,
1281 registry: OptionalParserPackRegistryBinding,
1283 required_platforms: Vec<PackPlatform>,
1285 licenses: Vec<GrammarLicense>,
1287 grammars: Vec<AcceptedGrammar>,
1289 capability_set_digest: Blake3Digest,
1291}
1292
1293#[derive(Deserialize)]
1294#[serde(deny_unknown_fields)]
1295struct OptionalParserPackManifestWire {
1297 schema_version: u32,
1299 pack_id: String,
1301 capability_set_version: u32,
1303 source: OptionalParserPackSource,
1305 runtime: OptionalParserPackRuntime,
1307 registry: OptionalParserPackRegistryBinding,
1309 required_platforms: Vec<PackPlatform>,
1311 licenses: Vec<GrammarLicense>,
1313 grammars: Vec<AcceptedGrammar>,
1315 capability_set_digest: Blake3Digest,
1317}
1318
1319impl OptionalParserPackManifest {
1320 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 #[must_use]
1353 pub const fn schema_version(&self) -> u32 {
1354 self.schema_version
1355 }
1356
1357 #[must_use]
1359 pub fn pack_id(&self) -> &str {
1360 &self.pack_id
1361 }
1362
1363 #[must_use]
1365 pub const fn capability_set_version(&self) -> u32 {
1366 self.capability_set_version
1367 }
1368
1369 #[must_use]
1371 pub const fn source(&self) -> &OptionalParserPackSource {
1372 &self.source
1373 }
1374
1375 #[must_use]
1377 pub const fn runtime(&self) -> &OptionalParserPackRuntime {
1378 &self.runtime
1379 }
1380
1381 #[must_use]
1383 pub const fn registry(&self) -> &OptionalParserPackRegistryBinding {
1384 &self.registry
1385 }
1386
1387 #[must_use]
1389 pub fn required_platforms(&self) -> &[PackPlatform] {
1390 &self.required_platforms
1391 }
1392
1393 #[must_use]
1395 pub fn licenses(&self) -> &[GrammarLicense] {
1396 &self.licenses
1397 }
1398
1399 #[must_use]
1401 pub fn grammars(&self) -> &[AcceptedGrammar] {
1402 &self.grammars
1403 }
1404
1405 #[must_use]
1407 pub const fn capability_set_digest(&self) -> &Blake3Digest {
1408 &self.capability_set_digest
1409 }
1410
1411 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 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 #[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 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 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 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 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
1848fn 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
1888fn 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
1914fn 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
1926fn 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
1949fn 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
1980fn 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
2137fn 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
2149fn 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
2182fn 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
2271fn 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
2286fn 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
2307fn 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
2328fn 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
2374fn 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
2400fn validate_registry_binding(
2402 binding: &OptionalParserPackRegistryBinding,
2403) -> Result<(), OptionalParserPackManifestError> {
2404 let current = OptionalParserPackRegistryBinding::current()?;
2405 validate_binding(
2406 "registry.registry_version",
2407 ¤t.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 ¤t.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
2427fn 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
2451fn 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
2561fn 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
2585fn 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
2600fn 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
2618fn 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
2637fn 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
2656fn 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
2676fn 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
2695fn 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
2708fn 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
2735fn 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
2748fn 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
2753fn 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
2761fn 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
2769fn 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 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}