Skip to main content

workshop_rs/
real_projects.rs

1//! Owner-controlled expectations for the pinned real-project Workshop corpus.
2//!
3//! This is a test-support contract, not a conformance result or evidence
4//! report. It contains only the current owner-defined input identities and
5//! admitted Workshop semantic gaps. Consumers can use it to select and
6//! validate the same inputs without maintaining a second expectation list.
7
8use crate::error::WorkshopError;
9use crate::semantic::{IncompletenessKind, ResidualClassification, SemanticIssue};
10
11/// The schema version of [`REAL_PROJECT_EXPECTATION`].
12pub const REAL_PROJECT_EXPECTATION_SCHEMA_VERSION: u32 = 1;
13
14/// The stable identity of the pinned real-project source corpus.
15pub const REAL_PROJECT_CORPUS_ID: &str = "raw-workshop-real-projects/v1";
16
17/// The stage at which an admitted real-project gap is observed.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RealProjectStage {
20    /// Canonical builtin references are validated after parsing.
21    CanonicalValidation,
22    /// Canonical WIR is emitted back to Workshop text.
23    Emission,
24    /// Source text is converted to the other supported locale.
25    LocaleConversion,
26}
27
28impl RealProjectStage {
29    /// Return the stable machine-readable stage name.
30    pub const fn as_str(self) -> &'static str {
31        match self {
32            Self::CanonicalValidation => "canonical-validation",
33            Self::Emission => "emission",
34            Self::LocaleConversion => "locale-conversion",
35        }
36    }
37}
38
39/// The Workshop error identity admitted for a real-project stage gap.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum RealProjectGapKind {
42    /// An action spelling is not present in the canonical Workshop catalog.
43    UnknownAction,
44}
45
46impl RealProjectGapKind {
47    /// Return the stable machine-readable error kind name.
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Self::UnknownAction => "action",
51        }
52    }
53}
54
55/// An admitted semantic residual for one real-project source case.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct RealProjectResidualExpectation {
58    /// The semantic issue kind reported by [`crate::semantic::inspect`].
59    pub kind: IncompletenessKind,
60    /// The locale-independent Workshop identity of the residual.
61    pub identity: &'static str,
62    /// The owner-defined classification of the residual.
63    pub classification: ResidualClassification,
64}
65
66/// An admitted Workshop error for one real-project processing stage.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct RealProjectGapExpectation {
69    /// The processing stage where the error is admitted.
70    pub stage: RealProjectStage,
71    /// The structured Workshop error kind.
72    pub kind: RealProjectGapKind,
73    /// The localized spelling or identity carried by the error.
74    pub identity: &'static str,
75    /// The owner-defined classification corresponding to this gap.
76    pub classification: ResidualClassification,
77}
78
79/// The pinned source identity and owner-defined expectation for one real-project case.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct RealProjectCaseExpectation {
82    /// Stable case identity.
83    pub id: &'static str,
84    /// Source locale of the pinned Workshop input.
85    pub locale: &'static str,
86    /// Crate-relative path to the pinned source input.
87    pub source_fixture: &'static str,
88    /// SHA-256 digest of [`Self::source_fixture`].
89    pub source_sha256: &'static str,
90    /// Semantic residuals admitted for this case at every inspection stage.
91    pub residuals: &'static [RealProjectResidualExpectation],
92    /// Stage-specific Workshop errors admitted for this case.
93    pub gaps: &'static [RealProjectGapExpectation],
94}
95
96impl RealProjectCaseExpectation {
97    /// Whether the inspected semantic issue is admitted for this case.
98    pub fn admits_residual(&self, issue: &SemanticIssue) -> bool {
99        self.residuals.iter().any(|expected| {
100            expected.kind == issue.kind
101                && expected.identity == issue.name
102                && expected.classification == issue.classification
103        })
104    }
105
106    /// Whether the error is an owner-admitted gap at the given stage.
107    pub fn admits_gap(&self, stage: RealProjectStage, error: &WorkshopError) -> bool {
108        self.gaps.iter().any(|expected| {
109            expected.stage == stage
110                && match (expected.kind, error) {
111                    (
112                        RealProjectGapKind::UnknownAction,
113                        WorkshopError::Unknown { kind, spelling, .. },
114                    ) => *kind == expected.kind.as_str() && spelling == expected.identity,
115                    _ => false,
116                }
117        })
118    }
119}
120
121/// The owner-controlled real-project expectation contract consumed by the
122/// harness and downstream conformance tests.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct RealProjectExpectation {
125    /// Contract schema version.
126    pub schema_version: u32,
127    /// Stable identity of the source corpus.
128    pub corpus_id: &'static str,
129    /// The complete current real-project case inventory and expectations.
130    pub cases: &'static [RealProjectCaseExpectation],
131}
132
133const NO_RESIDUALS: &[RealProjectResidualExpectation] = &[];
134const NO_GAPS: &[RealProjectGapExpectation] = &[];
135const DEFEND_RESIDUALS: &[RealProjectResidualExpectation] = &[RealProjectResidualExpectation {
136    kind: IncompletenessKind::OpaqueAction,
137    identity: "rawWorkshopAction",
138    classification: ResidualClassification::LegacyOpaque,
139}];
140const DEFEND_GAPS: &[RealProjectGapExpectation] = &[
141    RealProjectGapExpectation {
142        stage: RealProjectStage::CanonicalValidation,
143        kind: RealProjectGapKind::UnknownAction,
144        identity: "rawWorkshopAction",
145        classification: ResidualClassification::LegacyOpaque,
146    },
147    RealProjectGapExpectation {
148        stage: RealProjectStage::Emission,
149        kind: RealProjectGapKind::UnknownAction,
150        identity: "rawWorkshopAction",
151        classification: ResidualClassification::LegacyOpaque,
152    },
153    RealProjectGapExpectation {
154        stage: RealProjectStage::LocaleConversion,
155        kind: RealProjectGapKind::UnknownAction,
156        identity: "rawWorkshopAction",
157        classification: ResidualClassification::LegacyOpaque,
158    },
159];
160
161/// The single authoritative real-project case and expectation definition.
162pub const REAL_PROJECT_EXPECTATION: RealProjectExpectation = RealProjectExpectation {
163    schema_version: REAL_PROJECT_EXPECTATION_SCHEMA_VERSION,
164    corpus_id: REAL_PROJECT_CORPUS_ID,
165    cases: &[
166        RealProjectCaseExpectation {
167            id: "ai-pve",
168            locale: "zh-CN",
169            source_fixture: "tests/fixtures/real-projects/ai-pve.ow",
170            source_sha256: "d9c6460ca550e40083efcc2b57de16360088631970824599a22c0aa2cb7f11f9",
171            residuals: NO_RESIDUALS,
172            gaps: NO_GAPS,
173        },
174        RealProjectCaseExpectation {
175            id: "bastion",
176            locale: "en-US",
177            source_fixture: "tests/fixtures/real-projects/bastion.ow",
178            source_sha256: "44e453ddf7f373be65aea82d019abd45dd60f5ecb57c8d1607d3576a8bc60259",
179            residuals: NO_RESIDUALS,
180            gaps: NO_GAPS,
181        },
182        RealProjectCaseExpectation {
183            id: "defend",
184            locale: "en-US",
185            source_fixture: "tests/fixtures/real-projects/defend.ow",
186            source_sha256: "06a956b650313ee2d6e24ec989f907244dc4444579bdba27c580b031de97b268",
187            residuals: DEFEND_RESIDUALS,
188            gaps: DEFEND_GAPS,
189        },
190        RealProjectCaseExpectation {
191            id: "illari",
192            locale: "zh-CN",
193            source_fixture: "tests/fixtures/real-projects/illari.ow",
194            source_sha256: "f3aff73b9e677730bddc9c85b04c2bd38439bb7a4ba4fa2e80dc28db2e4a0860",
195            residuals: NO_RESIDUALS,
196            gaps: NO_GAPS,
197        },
198        RealProjectCaseExpectation {
199            id: "rework",
200            locale: "en-US",
201            source_fixture: "tests/fixtures/real-projects/rework.ow",
202            source_sha256: "aa32cda640dba41fd99245a7d425d9897b53875d15cf071862197a8e6840258c",
203            residuals: NO_RESIDUALS,
204            gaps: NO_GAPS,
205        },
206    ],
207};
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn real_projects_expectation_has_unique_pinned_case_identities() {
215        assert_eq!(
216            REAL_PROJECT_EXPECTATION.schema_version,
217            REAL_PROJECT_EXPECTATION_SCHEMA_VERSION
218        );
219        assert_eq!(REAL_PROJECT_EXPECTATION.corpus_id, REAL_PROJECT_CORPUS_ID);
220
221        for (index, case) in REAL_PROJECT_EXPECTATION.cases.iter().enumerate() {
222            assert!(!case.id.is_empty());
223            assert!(case.locale == "en-US" || case.locale == "zh-CN");
224            assert!(
225                case.source_fixture
226                    .starts_with("tests/fixtures/real-projects/")
227            );
228            assert_eq!(case.source_sha256.len(), 64);
229            assert!(
230                REAL_PROJECT_EXPECTATION.cases[index + 1..]
231                    .iter()
232                    .all(|other| other.id != case.id),
233                "duplicate real-project case identity: {}",
234                case.id
235            );
236        }
237    }
238
239    #[test]
240    fn real_projects_expectation_keeps_the_admitted_gap_identity_and_classification() {
241        let defend = REAL_PROJECT_EXPECTATION
242            .cases
243            .iter()
244            .find(|case| case.id == "defend")
245            .expect("defend case");
246        assert_eq!(defend.residuals, DEFEND_RESIDUALS);
247        assert_eq!(defend.gaps.len(), 3);
248        assert!(defend.gaps.iter().all(|gap| {
249            gap.kind == RealProjectGapKind::UnknownAction
250                && gap.identity == "rawWorkshopAction"
251                && gap.classification == ResidualClassification::LegacyOpaque
252        }));
253
254        let error = WorkshopError::Unknown {
255            kind: "action",
256            spelling: "rawWorkshopAction".to_string(),
257            locale: crate::catalog::Locale::new("en-US"),
258            span: None,
259        };
260        assert!(defend.admits_gap(RealProjectStage::Emission, &error));
261        assert!(!defend.admits_gap(
262            RealProjectStage::CanonicalValidation,
263            &WorkshopError::Unknown {
264                kind: "value",
265                spelling: "rawWorkshopAction".to_string(),
266                locale: crate::catalog::Locale::new("en-US"),
267                span: None,
268            }
269        ));
270    }
271}