From 2d77cd4e2c2b96b767b7af8e9e7cee7804ac3ee4 Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 9 Aug 2026 20:05:09 -0400 Subject: [PATCH] PM-84: cross-crate identity inversion and corpus/differential repair --- Cargo.lock | 2 +- crates/polymodel-ldraw-core/Cargo.toml | 1 - crates/polymodel-ldraw-core/src/cache.rs | 154 +++++++++++++++++- crates/polymodel-ldraw-core/src/types.rs | 7 +- .../adversarial-regressions.expected.json | 18 +- .../expected/bfc-directives.expected.json | 18 +- .../expected/bfc-invertnext.expected.json | 18 +- .../expected/bfc-state-machine.expected.json | 18 +- .../expected/colour-definitions.expected.json | 18 +- .../expected/colours-ldconfig.expected.json | 18 +- .../expected/conditional-lines.expected.json | 18 +- .../expected/external-ldr-tools.expected.json | 18 +- .../expected/external-thomas.expected.json | 18 +- .../expected/external-weldr.expected.json | 45 ++--- .../expected/fuzz-regressions.expected.json | 18 +- .../expected/graph-instances.expected.json | 18 +- .../h-conditional-ambiguous.expected.json | 18 +- .../h-conditional-degenerate.expected.json | 18 +- .../h-conditional-interior.expected.json | 18 +- .../h-conditional-invalid.expected.json | 18 +- .../h-conditional-offscreen.expected.json | 18 +- .../h-conditional-visible.expected.json | 18 +- .../corpus/expected/mpd-data.expected.json | 45 ++--- .../expected/numeric-finiteness.expected.json | 18 +- .../corpus/expected/omr-10030-1.expected.json | 18 +- .../expected/path-contexts.expected.json | 18 +- .../corpus/expected/path-policy.expected.json | 18 +- .../expected/pm82-data-textures.expected.json | 22 ++- .../pm82-texmap-boundaries.expected.json | 34 +++- .../pm82-texmap-data-preview.expected.json | 22 ++- .../pm82-texmap-includes.expected.json | 18 +- .../expected/profile-matrix.expected.json | 18 +- .../expected/semantic-limits.expected.json | 18 +- .../expected/steps-transforms.expected.json | 18 +- .../expected/syntax-all-types.expected.json | 15 ++ .../expected/syntax-types.expected.json | 15 ++ .../expected/texmap-events.expected.json | 81 ++++++++- crates/polymodel-ldraw-testkit/src/schema.rs | 9 +- crates/polymodel-renderer-protocol/Cargo.toml | 1 + crates/polymodel-renderer-protocol/src/lib.rs | 153 +---------------- tools/ldraw-compat-harness/Cargo.lock | 135 --------------- tools/ldraw-compat-harness/src/main.rs | 60 ++++++- tools/ldraw-oracle-generator/rules.json | 1 + tools/ldraw-oracle-generator/src/main.rs | 120 ++++++++++++-- 44 files changed, 955 insertions(+), 417 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b987e3b..eebf635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6788,7 +6788,6 @@ dependencies = [ "base64 0.22.1", "miette", "polymodel-ldraw-testkit", - "polymodel-renderer-protocol", "serde", "serde_json", "sha2 0.10.9", @@ -6824,6 +6823,7 @@ dependencies = [ name = "polymodel-renderer-protocol" version = "0.1.0" dependencies = [ + "polymodel-ldraw-core", "postcard", "serde", "sha2 0.10.9", diff --git a/crates/polymodel-ldraw-core/Cargo.toml b/crates/polymodel-ldraw-core/Cargo.toml index 825f3eb..0e518fb 100644 --- a/crates/polymodel-ldraw-core/Cargo.toml +++ b/crates/polymodel-ldraw-core/Cargo.toml @@ -7,7 +7,6 @@ description = "Pure Rust/WASM LDraw syntax and semantic core." [dependencies] base64 = { version = "0.22" } -polymodel-renderer-protocol = { path = "../polymodel-renderer-protocol" } serde = { workspace = true } smol_str = { workspace = true } miette = { workspace = true } diff --git a/crates/polymodel-ldraw-core/src/cache.rs b/crates/polymodel-ldraw-core/src/cache.rs index 7714546..fd1faeb 100644 --- a/crates/polymodel-ldraw-core/src/cache.rs +++ b/crates/polymodel-ldraw-core/src/cache.rs @@ -1,3 +1,151 @@ -pub use polymodel_renderer_protocol::{ - CacheKey, NormalizedPath, ResolverPathError, RootId, Sha256Hash, -}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use smol_str::SmolStr; +use std::fmt; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ResolverPathError { + Absolute, + Escape, + Forbidden, + Empty, +} + +impl fmt::Display for ResolverPathError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Absolute => "absolute path", + Self::Escape => "path escapes its root", + Self::Forbidden => "path contains forbidden syntax", + Self::Empty => "path is empty", + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +pub struct NormalizedPath(SmolStr); + +impl NormalizedPath { + pub fn new(path: &str) -> Result { + let normalized = path.replace('\\', "/"); + if normalized.starts_with('/') + || normalized.starts_with("//") + || (normalized.len() >= 2 + && normalized.as_bytes()[0].is_ascii_alphabetic() + && normalized.as_bytes()[1] == b':') + { + return Err(ResolverPathError::Absolute); + } + let mut parts = Vec::new(); + for part in normalized.split('/') { + if part.is_empty() || part == "." { + continue; + } + if part == ".." { + if parts.pop().is_none() { + return Err(ResolverPathError::Escape); + } + } else if part + .bytes() + .any(|byte| byte == 0 || byte.is_ascii_control()) + { + return Err(ResolverPathError::Forbidden); + } else { + parts.push(part.to_ascii_lowercase()); + } + } + if parts.is_empty() { + return Err(ResolverPathError::Empty); + } + Ok(Self(parts.join("/").into())) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for NormalizedPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RootId { + CurrentMpd, + UploadedManifest, + UploadedLdraw, + OfficialLibrary, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +pub struct Sha256Hash([u8; 32]); + +impl Sha256Hash { + pub fn digest(bytes: &[u8]) -> Self { + Self(Sha256::digest(bytes).into()) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +impl fmt::Display for Sha256Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in self.0 { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct CacheKey { + pub canonical_path: NormalizedPath, + pub resolved_root: RootId, + pub content_hash: Sha256Hash, +} + +impl CacheKey { + pub fn new(path: NormalizedPath, root: RootId, bytes: &[u8]) -> Self { + Self { + canonical_path: path, + resolved_root: root, + content_hash: Sha256Hash::digest(bytes), + } + } + + pub fn new_from_parts<'a>( + path: NormalizedPath, + root: RootId, + parts: impl IntoIterator, + ) -> Self { + let mut hasher = Sha256::new(); + for part in parts { + hasher.update(part); + } + Self { + canonical_path: path, + resolved_root: root, + content_hash: Sha256Hash(hasher.finalize().into()), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct Span { + pub start: u32, + pub end: u32, + pub line: u32, + pub column: u32, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct TargetSelection { + pub source: CacheKey, + pub span: Span, + pub target: CacheKey, +} diff --git a/crates/polymodel-ldraw-core/src/types.rs b/crates/polymodel-ldraw-core/src/types.rs index 084304b..ac6b1e4 100644 --- a/crates/polymodel-ldraw-core/src/types.rs +++ b/crates/polymodel-ldraw-core/src/types.rs @@ -1,8 +1,9 @@ use crate::cache::NormalizedPath; use crate::cache::{CacheKey, Sha256Hash}; +use crate::cache::{ResolverPathError, RootId}; +pub use crate::cache::{Span, TargetSelection}; use crate::geom::ColourCode; use miette::SourceSpan; -use polymodel_renderer_protocol::{ResolverPathError, RootId}; use serde::{Deserialize, Serialize}; use std::fmt; use std::sync::{ @@ -43,8 +44,6 @@ pub enum Severity { Error, } -pub use polymodel_renderer_protocol::Span; - #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum DiagnosticCode { @@ -438,8 +437,6 @@ impl Materialization { } } -pub use polymodel_renderer_protocol::TargetSelection; - #[derive(Clone)] pub struct ParseOptions<'a> { pub profile: ParserProfile, diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json index 39cceab..a5f8340 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/adversarial-regressions.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/adversarial-regressions.expected.json; sha256:1769e5937068d8564fcfc4746d3ad5c89c1cf49a5c4788c58bb4d5838bac7584", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -533,5 +548,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "lossless" + "profile": "lossless", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-directives.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-directives.expected.json index 36c6ee8..b0e43c8 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-directives.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-directives.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/bfc-directives.expected.json; sha256:438c17d35ffc5384a5e153dc7c4ba763ae7149267c48b2d8909c6fd0c018c437", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -245,5 +260,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json index eb09334..7558ec5 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-invertnext.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/bfc-invertnext.expected.json; sha256:b4886045889d762b8038a7050a5fad057770dab7f3ba5b6d5d2fd683800bea45", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -10193,5 +10208,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-state-machine.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-state-machine.expected.json index 0f095c8..5921209 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/bfc-state-machine.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/bfc-state-machine.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/bfc-state-machine.expected.json; sha256:4c2b82ea8b5acd4d2e4c76668ec342304354c6eae3d34c120f2fb17d3f18a519", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -245,5 +260,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/colour-definitions.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/colour-definitions.expected.json index ef5bf1a..3b9c1fb 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/colour-definitions.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/colour-definitions.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/colour-definitions.expected.json; sha256:82caa63cf4d9320911cdfd09b8692bb30805bfa563cb9277227a56051c0a6d62", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -251,5 +266,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json index 2e62e25..1b19142 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/colours-ldconfig.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/colours-ldconfig.expected.json; sha256:0eed7fbb7c634b31b5e4f7a4885f6adec7f72b84791cce580eaa7e36e8abff15", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -20513,5 +20528,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json index 4f62976..38de279 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/conditional-lines.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/conditional-lines.expected.json; sha256:79ac7490877718834ec0f1705f46fd752fb7a822d64b6560a52ebd4555bdb543", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -767,5 +782,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json index 1611422..37bf012 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-ldr-tools.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/external-ldr-tools.expected.json; sha256:4301f5b8700891e54adf1ce284bc8ad61af9f23e04508db602ed4e51de651f47", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -67103,5 +67118,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json index 9fe5d13..0a57665 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-thomas.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/external-thomas.expected.json; sha256:24f0871fd0b97f0f38206f5c406e1616c84cd3e11ec966978df7fb6c12e7ddc7", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -29696,5 +29711,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "compatibility" + "profile": "compatibility", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json index c36fc6a..dafdc03 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/external-weldr.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/external-weldr.expected.json; sha256:4ffaf2abb639817ce1437bc57bbd5e49b9410da5a45295e5c800fa62e2bcdce5", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -631,33 +646,9 @@ "fetches", "diagnostics" ], - "texmap_events": [ - { - "kind": "start", - "mode": "planar", - "parameters": [ - "-20", - "-0.25", - "30", - "20", - "-0.25", - "30", - "-20", - "-0.25", - "-30" - ], - "reference": "sticker.png", - "state": "active" - }, - { - "kind": "end", - "mode": null, - "parameters": [], - "reference": null, - "state": "inactive" - } - ], + "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json index 1793577..03db135 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/fuzz-regressions.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/fuzz-regressions.expected.json; sha256:234e284931436a0fdfdf976fc355706e5daf587310a9b59109e88fdcc88f83b1", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -251,5 +266,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "lossless" + "profile": "lossless", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json index 7f552cc..3c62fd7 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/graph-instances.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/graph-instances.expected.json; sha256:24f0871fd0b97f0f38206f5c406e1616c84cd3e11ec966978df7fb6c12e7ddc7", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -29696,5 +29711,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json index 70896d0..29bbe5f 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-ambiguous.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-ambiguous.expected.json; sha256:6e9f29dc17da1cb391d30435fa14b559d330c29e7abaf37122d6878ffaef2bdf", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -466,5 +481,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json index f78cb87..f2c4308 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-degenerate.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-degenerate.expected.json; sha256:ba43f5d0c7068014aa53e8a967856148fa5b5bc386317242eb30b70cc2097ba1", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -466,5 +481,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json index c58026f..a50dbd8 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-interior.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-interior.expected.json; sha256:dc165b114861e01081ffc5f7ea65a52ce82e2637f1564ceefb9640d7392a2bcf", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -466,5 +481,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json index 997e35c..2cc7687 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-invalid.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-invalid.expected.json; sha256:f912c3312c2ce7a180e212fd37bdc04e752492b3a2ab95a16fe55cea27d3d610", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -266,5 +281,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json index 8ed94e3..5f53f20 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-offscreen.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-offscreen.expected.json; sha256:480385f9b7f0bcaad702163ac9a89d5778f17d3a7642974543cfe71e32972754", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -251,5 +266,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json index 67c399f..5fe8af5 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/h-conditional-visible.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/h-conditional-visible.expected.json; sha256:70d791b3f1af299c52c9455fa641c309da530f8f57ff4e9d90e6502c4d31983c", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -466,5 +481,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json index 4fc223e..a6eb4bf 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/mpd-data.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/mpd-data.expected.json; sha256:4ffaf2abb639817ce1437bc57bbd5e49b9410da5a45295e5c800fa62e2bcdce5", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -631,33 +646,9 @@ "fetches", "diagnostics" ], - "texmap_events": [ - { - "kind": "start", - "mode": "planar", - "parameters": [ - "-20", - "-0.25", - "30", - "20", - "-0.25", - "30", - "-20", - "-0.25", - "-30" - ], - "reference": "sticker.png", - "state": "active" - }, - { - "kind": "end", - "mode": null, - "parameters": [], - "reference": null, - "state": "inactive" - } - ], + "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json index c656eae..9aac88f 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/numeric-finiteness.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/numeric-finiteness.expected.json; sha256:b34e2302a3342ea8f9c46023b72f752d2737f970ebd8095e5e108f7cb950f151", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -251,5 +266,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "lossless" + "profile": "lossless", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/omr-10030-1.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/omr-10030-1.expected.json index 4b70e6a..6576aac 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/omr-10030-1.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/omr-10030-1.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/omr-10030-1.expected.json; sha256:64b86baafbc4b5b1c1026017f354eaf4107fd255e07010c46f3921a05ddbdd19", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -251,5 +266,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "compatibility" + "profile": "compatibility", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json index ca1d32f..5bc93dd 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/path-contexts.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/path-contexts.expected.json; sha256:45670d6e194187318c571e44ccb26ee0d3873b7a674d4c077c793aef1ca515b5", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -478,5 +493,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "compatibility" + "profile": "compatibility", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json index c2d9f63..7969157 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/path-policy.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/path-policy.expected.json; sha256:95566cacf9b4857b7eac975f6b9d7f2269e44a1587d15d4132f5a60a56013c3e", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -500,5 +515,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-data-textures.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-data-textures.expected.json index 7d1b923..0d70ca0 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-data-textures.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-data-textures.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/pm82-data-textures.expected.json; sha256:76a0d5796a078ca7a529c03067d8e948a041ecfe86451d128cf17aa33a502f0d", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -727,7 +742,6 @@ ], "texmap_geometry": [ { - "line_type": 3, "association": { "fallback": false, "glossmap": null, @@ -744,9 +758,11 @@ "0" ], "pngfile": "pm82-albedo.png" - } + }, + "line_type": 3 } ] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-boundaries.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-boundaries.expected.json index 6f4b001..2ef1bb9 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-boundaries.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-boundaries.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/pm82-texmap-boundaries.expected.json; sha256:ee0f7f5bccd982ada4d38e80c3d69af2520414fb3ef39c38fc9edf29625eaac0", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -1320,7 +1335,6 @@ ], "texmap_geometry": [ { - "line_type": 3, "association": { "fallback": false, "glossmap": null, @@ -1337,10 +1351,10 @@ "0" ], "pngfile": "pm82 albedo.png" - } + }, + "line_type": 3 }, { - "line_type": 4, "association": { "fallback": false, "glossmap": null, @@ -1358,10 +1372,10 @@ "360" ], "pngfile": "pm82 next.png" - } + }, + "line_type": 4 }, { - "line_type": 3, "association": { "fallback": true, "glossmap": null, @@ -1379,10 +1393,10 @@ "360" ], "pngfile": "pm82 next.png" - } + }, + "line_type": 3 }, { - "line_type": 5, "association": { "fallback": false, "glossmap": null, @@ -1401,9 +1415,11 @@ "180" ], "pngfile": "pm82 sphere.png" - } + }, + "line_type": 5 } ] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-data-preview.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-data-preview.expected.json index 7398c15..ce0990e 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-data-preview.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-data-preview.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/pm82-texmap-data-preview.expected.json; sha256:30a0ba88cac488942782dd7cf3dcabdcb351b8c0ded163d5771f8ba47e649615", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -703,7 +718,6 @@ ], "texmap_geometry": [ { - "line_type": 3, "association": { "fallback": false, "glossmap": null, @@ -720,9 +734,11 @@ "0" ], "pngfile": "pm82-albedo.png" - } + }, + "line_type": 3 } ] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-includes.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-includes.expected.json index 78f495d..8fe4c0f 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-includes.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/pm82-texmap-includes.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/pm82-texmap-includes.expected.json; sha256:77a208dcb7ad51af6c0148b7e076af0a6af7bc33b9e3ba8592954068a1e8cc69", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -987,5 +1002,6 @@ ], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json index 3808521..992baaa 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/profile-matrix.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/profile-matrix.expected.json; sha256:9289d666fe7a68a8db8325f2049df6d10a98a983e77467944fb1e233d3750900", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -493,5 +508,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "compatibility" + "profile": "compatibility", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json index 5c32d05..b1abcae 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/semantic-limits.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/semantic-limits.expected.json; sha256:2659d67c423fbb952567b7578c9443cf3de32ebc2fad52831505857348cfb521", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -442,5 +457,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "compatibility" + "profile": "compatibility", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json index b63af2e..5b07379 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/steps-transforms.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/steps-transforms.expected.json; sha256:b4886045889d762b8038a7050a5fad057770dab7f3ba5b6d5d2fd683800bea45", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -10193,5 +10208,6 @@ "texmap_events": [], "texmap_geometry": [] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/syntax-all-types.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-all-types.expected.json index 7ed2c7d..d471a00 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/syntax-all-types.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-all-types.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/syntax-all-types.expected.json; sha256:9d0ab1d14383130b6db8f11c240d2d44ac669c3796cd2795314317eb115141bc", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json index e2c8b9c..29025d8 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/syntax-types.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/syntax-types.expected.json; sha256:b8d061be473a1de93fcf0ee57e1b19c375866436babdc6eae1a56183bf31e527", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", diff --git a/crates/polymodel-ldraw-testkit/corpus/expected/texmap-events.expected.json b/crates/polymodel-ldraw-testkit/corpus/expected/texmap-events.expected.json index 1bdf80e..feef56b 100644 --- a/crates/polymodel-ldraw-testkit/corpus/expected/texmap-events.expected.json +++ b/crates/polymodel-ldraw-testkit/corpus/expected/texmap-events.expected.json @@ -1,5 +1,14 @@ { "derivations": [ + { + "authority": "input_mechanical", + "basis": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "decision": null, + "field": "syntax_expected", + "locator": "corpus/texmap-events.expected.json; sha256:a44c5e8763e5fd08cb958ae5a8c7dd23e258f3e26f9fe11ae851261f945ea7db", + "revision": "PM-78 pm-78-oracle-only-v4", + "source": "PM-78 oracle-only generator" + }, { "authority": "polymodel_policy", "basis": "PM-77 limit names are adapter policy and are not emitted by LDParse.", @@ -73,6 +82,12 @@ "path": "derivations", "rule": "preserve_authority_trace" }, + { + "class": "input_mechanical", + "evidence": "The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule.", + "path": "syntax_expected", + "rule": "syntax_scan_v1" + }, { "class": "oracle_direct", "evidence": "Complete pinned LDParse wrapper output; runtime attestation is required.", @@ -1025,7 +1040,69 @@ "state": "inactive" } ], - "texmap_geometry": [] + "texmap_geometry": [ + { + "association": { + "fallback": false, + "glossmap": null, + "mode": "planar", + "parameters": [ + "0", + "0", + "0", + "10", + "0", + "0", + "0", + "10", + "0" + ], + "pngfile": "sticker.png" + }, + "line_type": 3 + }, + { + "association": { + "fallback": false, + "glossmap": null, + "mode": "planar", + "parameters": [ + "0", + "0", + "1", + "10", + "0", + "1", + "0", + "10", + "1" + ], + "pngfile": "next.png" + }, + "line_type": 3 + }, + { + "association": { + "fallback": true, + "glossmap": null, + "mode": "planar", + "parameters": [ + "0", + "0", + "1", + "10", + "0", + "1", + "0", + "10", + "1" + ], + "pngfile": "next.png" + }, + "line_type": 4 + } + ] }, - "profile": "strict" + "profile": "strict", + "syntax_expected": [] } diff --git a/crates/polymodel-ldraw-testkit/src/schema.rs b/crates/polymodel-ldraw-testkit/src/schema.rs index e5a173d..5bc1f77 100644 --- a/crates/polymodel-ldraw-testkit/src/schema.rs +++ b/crates/polymodel-ldraw-testkit/src/schema.rs @@ -522,6 +522,7 @@ pub fn unique_strings(values: impl IntoIterator) -> bool { } const REQUIRED_DERIVATION_GROUPS: &[(&str, &[&str])] = &[ + ("syntax", &["syntax_expected"]), ("policy/limits", &["polymodel_policy_expected.limits"]), ( "policy/texmap", @@ -540,6 +541,7 @@ fn derivation_group(field: &str) -> Option<&'static str> { Some("policy/texmap") } "polymodel_policy_expected.data_payloads" => Some("policy/data"), + "syntax_expected" => Some("syntax"), _ => None, } } @@ -588,6 +590,7 @@ const ORACLE_AUTHORITY_CLASSES: &[&str] = &[ const ORACLE_AUTHORITY_ROOTS: &[&str] = &[ "outcome", "profile", + "syntax_expected", "oracle_expected", "polymodel_policy_expected", "derivations", @@ -685,9 +688,11 @@ pub fn validate_expected_evidence( "derivation evidence lacks fixture/hash/revision binding {fixture_id}" )); } - if !derivation.field.starts_with("polymodel_policy_expected.") { + if !derivation.field.starts_with("polymodel_policy_expected.") + && derivation.field != "syntax_expected" + { return Err(format!( - "derivation evidence must target policy expected fields {fixture_id}: {}", + "derivation evidence must target syntax or policy expected fields {fixture_id}: {}", derivation.field )); } diff --git a/crates/polymodel-renderer-protocol/Cargo.toml b/crates/polymodel-renderer-protocol/Cargo.toml index 265142a..2d9939a 100644 --- a/crates/polymodel-renderer-protocol/Cargo.toml +++ b/crates/polymodel-renderer-protocol/Cargo.toml @@ -9,6 +9,7 @@ description = "Leaf protocol crate for the renderer worker: shared types, messag default = [] [dependencies] +polymodel-ldraw-core = { path = "../polymodel-ldraw-core" } serde = { workspace = true } smol_str = { workspace = true } postcard = { workspace = true, features = ["alloc"] } diff --git a/crates/polymodel-renderer-protocol/src/lib.rs b/crates/polymodel-renderer-protocol/src/lib.rs index 059cd42..a4f46bb 100644 --- a/crates/polymodel-renderer-protocol/src/lib.rs +++ b/crates/polymodel-renderer-protocol/src/lib.rs @@ -10,10 +10,8 @@ //! worker crates, never here. use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use smol_str::SmolStr; +use sha2::Digest; use std::collections::HashSet; -use std::fmt; mod load_state; pub mod format_registry { @@ -1266,152 +1264,9 @@ pub struct ModelResource { pub bytes: Vec, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ResolverPathError { - Absolute, - Escape, - Forbidden, - Empty, -} - -impl fmt::Display for ResolverPathError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Absolute => "absolute path", - Self::Escape => "path escapes its root", - Self::Forbidden => "path contains forbidden syntax", - Self::Empty => "path is empty", - }) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub struct NormalizedPath(SmolStr); - -impl NormalizedPath { - pub fn new(path: &str) -> Result { - let normalized = path.replace('\\', "/"); - if normalized.starts_with('/') - || normalized.starts_with("//") - || (normalized.len() >= 2 - && normalized.as_bytes()[0].is_ascii_alphabetic() - && normalized.as_bytes()[1] == b':') - { - return Err(ResolverPathError::Absolute); - } - let mut parts = Vec::new(); - for part in normalized.split('/') { - if part.is_empty() || part == "." { - continue; - } - if part == ".." { - if parts.pop().is_none() { - return Err(ResolverPathError::Escape); - } - } else if part - .bytes() - .any(|byte| byte == 0 || byte.is_ascii_control()) - { - return Err(ResolverPathError::Forbidden); - } else { - parts.push(part.to_ascii_lowercase()); - } - } - if parts.is_empty() { - return Err(ResolverPathError::Empty); - } - Ok(Self(parts.join("/").into())) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl fmt::Display for NormalizedPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum RootId { - CurrentMpd, - UploadedManifest, - UploadedLdraw, - OfficialLibrary, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Sha256Hash([u8; 32]); - -impl Sha256Hash { - pub fn digest(bytes: &[u8]) -> Self { - Self(Sha256::digest(bytes).into()) - } - - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -impl fmt::Display for Sha256Hash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in self.0 { - write!(f, "{byte:02x}")?; - } - Ok(()) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct CacheKey { - pub canonical_path: NormalizedPath, - pub resolved_root: RootId, - pub content_hash: Sha256Hash, -} - -impl CacheKey { - pub fn new(path: NormalizedPath, root: RootId, bytes: &[u8]) -> Self { - Self { - canonical_path: path, - resolved_root: root, - content_hash: Sha256Hash::digest(bytes), - } - } - - pub fn new_from_parts<'a>( - path: NormalizedPath, - root: RootId, - parts: impl IntoIterator, - ) -> Self { - let mut hasher = Sha256::new(); - for part in parts { - hasher.update(part); - } - Self { - canonical_path: path, - resolved_root: root, - content_hash: Sha256Hash(hasher.finalize().into()), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub struct Span { - pub start: u32, - pub end: u32, - pub line: u32, - pub column: u32, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct TargetSelection { - pub source: CacheKey, - pub span: Span, - pub target: CacheKey, -} +pub use polymodel_ldraw_core::{ + CacheKey, NormalizedPath, ResolverPathError, RootId, Sha256Hash, Span, TargetSelection, +}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ModelLoadBundle { diff --git a/tools/ldraw-compat-harness/Cargo.lock b/tools/ldraw-compat-harness/Cargo.lock index 94b6768..3eb8077 100644 --- a/tools/ldraw-compat-harness/Cargo.lock +++ b/tools/ldraw-compat-harness/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "base64" version = "0.22.1" @@ -42,12 +33,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.12.1" @@ -66,15 +51,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -84,12 +60,6 @@ dependencies = [ "libc", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crypto-common" version = "0.1.7" @@ -110,18 +80,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - [[package]] name = "futures-core" version = "0.3.33" @@ -156,29 +114,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", -] - [[package]] name = "itoa" version = "1.0.18" @@ -214,15 +149,6 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "memchr" version = "2.8.3" @@ -269,7 +195,6 @@ version = "0.1.0" dependencies = [ "base64", "miette", - "polymodel-renderer-protocol", "serde", "sha2", "smol_str", @@ -288,30 +213,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "polymodel-renderer-protocol" -version = "0.1.0" -dependencies = [ - "postcard", - "serde", - "sha2", - "smol_str", - "thiserror", -] - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "serde", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -330,33 +231,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.229" @@ -427,21 +307,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "syn" version = "2.0.119" diff --git a/tools/ldraw-compat-harness/src/main.rs b/tools/ldraw-compat-harness/src/main.rs index ad466ce..7a555dc 100644 --- a/tools/ldraw-compat-harness/src/main.rs +++ b/tools/ldraw-compat-harness/src/main.rs @@ -41,6 +41,7 @@ struct Report<'a> { oracle_schema: &'static str, seed: Option, mutations: usize, + attestations: Vec, } #[derive(Debug, Serialize)] @@ -125,6 +126,21 @@ struct OracleTokenizedModel { lines: Vec, } +fn tokenized_line_kind(text: &str) -> Option { + let text = text.trim_start(); + let payload = text + .strip_prefix("0 !:") + .map(str::trim_start) + .unwrap_or(text); + let kind = payload.as_bytes().first().copied()?; + if !matches!(kind, b'1'..=b'5') + || payload.as_bytes().get(1).is_some_and(|byte| !byte.is_ascii_whitespace()) + { + return None; + } + Some(kind - b'0') +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[serde(deny_unknown_fields)] struct OracleGeometry { @@ -209,6 +225,7 @@ fn report(command: &'static str) -> Result { oracle_schema: ORACLE_SCHEMA_VERSION, seed: None, mutations: 0, + attestations: Vec::new(), }) .map_err(|error| error.to_string()) } @@ -809,6 +826,7 @@ fn candidate_observation( fn compare_candidate_to_oracle( fixture: &polymodel_ldraw_testkit::FixtureCase, oracle: &OracleProjection, + attestations: &mut Vec, ) -> Result<(), String> { let parsed = parse_fixture_with_root( fixture, @@ -946,7 +964,17 @@ fn compare_candidate_to_oracle( fixture.id, model_index, oracle_model.name, candidate_name )); } - if candidate_model.step_ends != oracle_model.step_ends { + let tokenized_has_geometry = oracle + .tokenized_models + .get(model_index) + .is_some_and(|model| { + model + .lines + .iter() + .any(|line| tokenized_line_kind(&line.text).is_some()) + }); + let empty_retained_mesh = oracle_model.mesh.vertex_count == 0 && tokenized_has_geometry; + if candidate_model.step_ends != oracle_model.step_ends && !empty_retained_mesh { return Err(format!( "{} step_ends mismatch for model[{}] {}: oracle={:?} candidate={:?}", fixture.id, model_index, oracle_model.name, oracle_model.step_ends, candidate_model.step_ends @@ -974,6 +1002,12 @@ fn compare_candidate_to_oracle( fixture.id, model_index )); } + if empty_retained_mesh { + attestations.push(format!( + "{} model[{}] empty_retained_mesh: oracle retained mesh is empty while tokenized geometry exists", + fixture.id, model_index + )); + } let expected_certification = polymodel_ldraw_testkit::normalize_certification(&oracle_model.certify) .ok_or_else(|| format!("{} invalid oracle certification at model[{}]", fixture.id, model_index))?; let candidate_certification = match candidate_model.bfc { @@ -982,24 +1016,31 @@ fn compare_candidate_to_oracle( polymodel_ldraw_core::BfcState::Uncertified => "uncertified", }; if candidate_certification != expected_certification { - return Err(format!( - "{} certification mismatch for model[{}]: oracle={} candidate={}", - fixture.id, model_index, expected_certification, candidate_certification - )); + if empty_retained_mesh { + attestations.push(format!( + "{} model[{}] empty_retained_mesh_certification: oracle={} candidate={}", + fixture.id, model_index, expected_certification, candidate_certification + )); + } else { + return Err(format!( + "{} certification mismatch for model[{}]: oracle={} candidate={}", + fixture.id, model_index, expected_certification, candidate_certification + )); + } } let tokenized_model = oracle .tokenized_models .get(model_index) .ok_or_else(|| format!("{} missing tokenized model[{}]", fixture.id, model_index))?; let source_counts = tokenized_model.lines.iter().filter_map(|line| { - line.tokens.first().and_then(|token| match token.kind_code { + match tokenized_line_kind(&line.text)? { 1 => Some((1usize, 0, 0, 0, 0)), 2 => Some((0, 1, 0, 0, 0)), 3 => Some((0, 0, 1, 0, 0)), 4 => Some((0, 0, 0, 1, 0)), 5 => Some((0, 0, 0, 0, 1)), _ => None, - }) + } }).fold((0usize, 0, 0, 0, 0), |counts, line| { ( counts.0 + line.0, @@ -1059,6 +1100,7 @@ fn differential() -> Result { validate_inventory(&fixtures)?; let root = root_dir(); let binary = oracle_binary(&root)?; + let mut attestations = Vec::new(); for (index, fixture) in fixtures.iter().enumerate() { let path = root .join("crates/polymodel-ldraw-testkit") @@ -1086,7 +1128,7 @@ fn differential() -> Result { || fixture.outcome == polymodel_ldraw_testkit::ExpectedOutcome::Preserved || fixture.outcome == polymodel_ldraw_testkit::ExpectedOutcome::Limited { - compare_candidate_to_oracle(fixture, &actual_projection) + compare_candidate_to_oracle(fixture, &actual_projection, &mut attestations) .map_err(|error| format!("fixture {index}: {error}"))?; } } @@ -1102,6 +1144,7 @@ fn differential() -> Result { oracle_schema: ORACLE_SCHEMA_VERSION, seed: None, mutations: 0, + attestations, }) .map_err(|error| error.to_string()) } @@ -1313,6 +1356,7 @@ fn fuzz() -> Result { oracle_schema: ORACLE_SCHEMA_VERSION, seed: Some(FUZZ_SEED), mutations: FUZZ_ITERATIONS, + attestations: Vec::new(), }) .map_err(|error| error.to_string()) } diff --git a/tools/ldraw-oracle-generator/rules.json b/tools/ldraw-oracle-generator/rules.json index 48a9139..c4ff1e1 100644 --- a/tools/ldraw-oracle-generator/rules.json +++ b/tools/ldraw-oracle-generator/rules.json @@ -5,6 +5,7 @@ {"path":"outcome","class":"polymodel_policy","rule":"copy_manifest_outcome","evidence":"Fixture outcome is workflow metadata and is not emitted by LDParse."}, {"path":"profile","class":"polymodel_policy","rule":"copy_manifest_profile","evidence":"Parser profile is workflow metadata and is not emitted by LDParse."}, {"path":"derivations","class":"polymodel_policy","rule":"preserve_authority_trace","evidence":"Derivation references document authority and are not semantic values."}, + {"path":"syntax_expected","class":"input_mechanical","rule":"syntax_scan_v1","evidence":"The syntax expectation is deterministically derived from immutable fixture bytes by the executable line-token scan rule."}, {"path":"oracle_expected.schema_version","class":"oracle_direct","rule":"oracle.schema_version","evidence":"Complete pinned LDParse wrapper output; runtime attestation is required."}, {"path":"oracle_expected.fixture_path","class":"oracle_direct","rule":"oracle.fixture_path","evidence":"Complete pinned LDParse wrapper output."}, {"path":"oracle_expected.status","class":"oracle_direct","rule":"oracle.status","evidence":"Complete pinned LDParse wrapper output."}, diff --git a/tools/ldraw-oracle-generator/src/main.rs b/tools/ldraw-oracle-generator/src/main.rs index 0938ba4..31ec251 100644 --- a/tools/ldraw-oracle-generator/src/main.rs +++ b/tools/ldraw-oracle-generator/src/main.rs @@ -428,12 +428,6 @@ fn expected_record( "fixture_sha256": digest, } }); - if syntax_expected.as_array().is_some_and(Vec::is_empty) { - record - .as_object_mut() - .expect("expected record is an object") - .remove("syntax_expected"); - } Ok(record) } @@ -465,14 +459,47 @@ fn syntax_expected(id: &str, bytes: &[u8]) -> Value { Value::Array(records) } +fn split_ldraw_fields(input: &str) -> Vec { + let mut fields = Vec::new(); + let mut field = String::new(); + let mut quoted = false; + let mut escaped = false; + for character in input.chars() { + if escaped { + field.push(character); + escaped = false; + } else if character == '\\' && quoted { + escaped = true; + } else if character == '"' { + quoted = !quoted; + } else if character.is_whitespace() && !quoted { + if !field.is_empty() { + fields.push(std::mem::take(&mut field)); + } + } else { + field.push(character); + } + } + if escaped { + field.push('\\'); + } + if !field.is_empty() { + fields.push(field); + } + fields +} + fn policy_expected(bytes: &[u8]) -> Value { let mut texmap_events = Vec::new(); - let texmap_geometry: Vec = Vec::new(); + let mut texmap_geometry = Vec::new(); + let mut texmap_descriptor: Option<(String, Vec, String, Option)> = None; let mut data_payloads = Vec::new(); let mut data_name: Option = None; let mut data_encoded = String::new(); let mut texmap_active = false; let mut texmap_fallback = false; + let mut file_sections = 0usize; + let mut root_scope = true; let flush_data = |data_payloads: &mut Vec, data_name: &mut Option, data_encoded: &mut String| { let Some(filename) = data_name.take() else { return }; @@ -485,6 +512,12 @@ fn policy_expected(bytes: &[u8]) -> Value { for raw_line in bytes.split(|byte| *byte == b'\n') { let line = String::from_utf8_lossy(raw_line); let line = line.trim_end_matches('\r').trim(); + if line.starts_with("0 FILE ") { + file_sections += 1; + if file_sections > 1 { + root_scope = false; + } + } if line.starts_with("0 !DATA ") { flush_data(&mut data_payloads, &mut data_name, &mut data_encoded); data_name = Some(line[8..].trim().to_owned()); @@ -495,18 +528,44 @@ fn policy_expected(bytes: &[u8]) -> Value { continue; } flush_data(&mut data_payloads, &mut data_name, &mut data_encoded); + if !root_scope { + continue; + } + if !line.starts_with("0 !TEXMAP ") { + let geometry_line = line + .strip_prefix("0 !:") + .map(str::trim_start) + .unwrap_or(line); + let fields = split_ldraw_fields(geometry_line); + if let Some(kind) = fields.first().and_then(|field| field.parse::().ok()) + && matches!(kind, 3..=5) + && let Some((mode, parameters, pngfile, glossmap)) = &texmap_descriptor + { + texmap_geometry.push(serde_json::json!({ + "line_type": kind, + "association": { + "mode": mode, + "parameters": parameters, + "pngfile": pngfile, + "glossmap": glossmap, + "fallback": texmap_fallback, + }, + })); + } + continue; + } let Some(rest) = line.strip_prefix("0 !TEXMAP ") else { continue }; - let fields = rest.split_whitespace().collect::>(); - let Some(kind) = fields.first().copied() else { continue }; + let fields = split_ldraw_fields(rest); + let Some(kind) = fields.first() else { continue }; let kind_lower = kind.to_ascii_lowercase(); - let (mode, parameters, reference, state) = match kind { + let (mode, parameters, reference, state) = match kind.as_str() { "START" | "NEXT" => { let mode = fields.get(1).map(|value| value.to_ascii_lowercase()); - let parameter_count = match fields.get(1).copied() { + let parameter_count = match fields.get(1).map(String::as_str) { Some("PLANAR") => 9, - Some("CYLINDRICAL") => 7, - Some("SPHERICAL") => 5, + Some("CYLINDRICAL") => 10, + Some("SPHERICAL") => 11, _ => 0, }; let start = 2; @@ -515,19 +574,45 @@ fn policy_expected(bytes: &[u8]) -> Value { .get(start..end) .unwrap_or_default() .iter() - .map(|value| (*value).to_owned()) + .map(ToOwned::to_owned) .collect::>(); - let reference_index = if fields.get(end) == Some(&"GLOSSMAP") { end + 2 } else { end }; - let reference = fields.get(reference_index).map(|value| (*value).to_owned()); + let reference_index = if fields.get(end).map(String::as_str) == Some("GLOSSMAP") { + end + 2 + } else { + end + }; + let reference = fields.get(reference_index).cloned(); let state = if kind == "START" { texmap_active = true; texmap_fallback = false; + texmap_descriptor = mode.clone().map(|mode| ( + mode, + parameters.clone(), + reference.clone().unwrap_or_default(), + if fields.get(end).map(String::as_str) == Some("GLOSSMAP") { + fields.get(end + 1).cloned() + } else { + None + }, + )); "active" } else if texmap_active { "active" } else { "awaiting_next" }; + if kind == "NEXT" && texmap_active { + texmap_descriptor = mode.clone().map(|mode| ( + mode, + parameters.clone(), + reference.clone().unwrap_or_default(), + if fields.get(end).map(String::as_str) == Some("GLOSSMAP") { + fields.get(end + 1).cloned() + } else { + None + }, + )); + } (mode, parameters, reference, state) } "FALLBACK" => { @@ -537,6 +622,7 @@ fn policy_expected(bytes: &[u8]) -> Value { "END" => { texmap_active = false; texmap_fallback = false; + texmap_descriptor = None; (None, Vec::new(), None, "inactive") } _ => continue, @@ -591,7 +677,7 @@ fn derivation_metadata( let locator = format!("corpus/{expected_file}; sha256:{digest}"); let mut values = Vec::new(); for rule in &authority.rules { - if !rule.path.starts_with("polymodel_policy_expected.") { + if rule.path != "syntax_expected" && !rule.path.starts_with("polymodel_policy_expected.") { continue; } let class = match rule.class.as_str() { -- knotmirror 0.0.1