Skip to main content

sim_incremental_core/projection/
assay.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use sim_kernel::{ContentId, Datum, Symbol};
4
5use super::{
6    ConclusionId, Explanation, FactId, FederatedClosure,
7    assay_repair::{ProjectionRepairItem, ProjectionRepairSet},
8};
9
10const REQUIRED_DELTAS: [&str; 6] = ["D1", "D2", "D3", "D4", "D5", "D6"];
11
12/// Semantic class of one controlled stage-one delta.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum ControlledDeltaClass {
15    /// An identical semantic input batch.
16    NoSemanticChange,
17    /// One owner's private implementation changes.
18    PrivateImplementation,
19    /// A declared public API changes.
20    PublicApi,
21    /// Three public heads and their control-plane pins change together.
22    PublicHeadsAndPins,
23    /// Proof implementation and checker implementation identities change.
24    ProofAndCheckerImplementation,
25    /// Display or ordering changes without changing semantics.
26    PresentationOnly,
27}
28
29/// One controlled input delta. It predicts work but never executes proof.
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct ControlledDelta {
32    /// Stable assay row (`D1` through `D6`).
33    pub id: String,
34    /// Semantic class under test.
35    pub class: ControlledDeltaClass,
36    /// Exact changed semantic facts; empty for semantic no-ops.
37    pub changed_facts: BTreeSet<FactId>,
38    /// Maximum journal revision movement produced by normalization.
39    pub revision_delta: u64,
40    /// Whether the prediction emitted carried execution state.
41    pub carried_state: bool,
42}
43
44impl ControlledDelta {
45    /// Constructs a checked controlled delta.
46    pub fn new(
47        id: impl Into<String>,
48        class: ControlledDeltaClass,
49        changed_facts: impl IntoIterator<Item = FactId>,
50        revision_delta: u64,
51        carried_state: bool,
52    ) -> Result<Self, AssayError> {
53        let id = id.into();
54        if id.trim().is_empty() {
55            return Err(AssayError::InvalidDeltaId);
56        }
57        let changed_facts = changed_facts.into_iter().collect::<BTreeSet<_>>();
58        if matches!(
59            class,
60            ControlledDeltaClass::NoSemanticChange | ControlledDeltaClass::PresentationOnly
61        ) && !changed_facts.is_empty()
62        {
63            return Err(AssayError::SemanticNoOpCarriesChangedFacts(id));
64        }
65        Ok(Self {
66            id,
67            class,
68            changed_facts,
69            revision_delta,
70            carried_state,
71        })
72    }
73}
74
75/// One independently authored expected affected set.
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct ExpectedClosure {
78    /// Controlled delta identity.
79    pub delta: String,
80    /// Declared-conclusion denominator used for every percentage.
81    pub denominator: usize,
82    /// Maximum affected conclusions admitted by the assay.
83    pub max_affected: usize,
84    /// Maximum semantic journal revision movement.
85    pub max_revision_delta: u64,
86    /// Whether carried execution state is allowed.
87    pub carried_state: bool,
88    /// Exact independently expected affected conclusions.
89    pub affected: BTreeSet<ConclusionId>,
90}
91
92/// Frozen independently authored stage-one oracle.
93#[derive(Clone, Debug, Eq, PartialEq)]
94pub struct ExpectedClosureSet {
95    source: ContentId,
96    id: ContentId,
97    rows: BTreeMap<String, ExpectedClosure>,
98}
99
100impl ExpectedClosureSet {
101    /// Freezes six unique D1-D6 rows under the exact authored-source identity.
102    pub fn freeze(
103        source: ContentId,
104        rows: impl IntoIterator<Item = ExpectedClosure>,
105    ) -> Result<Self, AssayError> {
106        let mut canonical = BTreeMap::new();
107        for row in rows {
108            if canonical.insert(row.delta.clone(), row).is_some() {
109                return Err(AssayError::DuplicateDelta);
110            }
111        }
112        let actual = canonical.keys().map(String::as_str).collect::<Vec<_>>();
113        if actual != REQUIRED_DELTAS {
114            return Err(AssayError::IncompleteDeltaSet(actual.join(",")));
115        }
116        let denominator = canonical["D1"].denominator;
117        for row in canonical.values() {
118            if row.denominator == 0
119                || row.denominator != denominator
120                || row.max_affected > row.denominator
121                || row.affected.len() > row.max_affected
122            {
123                return Err(AssayError::InvalidExpectedClosure {
124                    delta: row.delta.clone(),
125                    reason: ExpectedClosureViolation::InconsistentDenominatorOrCeiling,
126                });
127            }
128        }
129        let d1 = &canonical["D1"];
130        if !d1.affected.is_empty()
131            || d1.max_affected != 0
132            || d1.max_revision_delta != 0
133            || d1.carried_state
134        {
135            return Err(AssayError::InvalidExpectedClosure {
136                delta: "D1".into(),
137                reason: ExpectedClosureViolation::NoChangeContract,
138            });
139        }
140        let d2 = &canonical["D2"];
141        if unaffected_basis_points(d2.denominator, d2.affected.len()) < 9_500 {
142            return Err(AssayError::InvalidExpectedClosure {
143                delta: "D2".into(),
144                reason: ExpectedClosureViolation::UnaffectedFloor,
145            });
146        }
147        let d4 = &canonical["D4"];
148        if unaffected_basis_points(d4.denominator, d4.affected.len()) < 7_000 {
149            return Err(AssayError::InvalidExpectedClosure {
150                delta: "D4".into(),
151                reason: ExpectedClosureViolation::UnaffectedFloor,
152            });
153        }
154        let d6 = &canonical["D6"];
155        if !d6.affected.is_empty()
156            || d6.max_affected != 0
157            || d6.max_revision_delta > 1
158            || d6.carried_state
159        {
160            return Err(AssayError::InvalidExpectedClosure {
161                delta: "D6".into(),
162                reason: ExpectedClosureViolation::PresentationOnlyContract,
163            });
164        }
165        let datum = Datum::Node {
166            tag: Symbol::qualified("projection", "expected-closure-set-v1"),
167            fields: vec![
168                (Symbol::new("source"), content_id_datum(&source)),
169                (
170                    Symbol::new("rows"),
171                    Datum::Vector(canonical.values().map(expected_datum).collect()),
172                ),
173            ],
174        };
175        let id = datum
176            .content_id()
177            .map_err(|error| AssayError::Canonical(error.to_string()))?;
178        Ok(Self {
179            source,
180            id,
181            rows: canonical,
182        })
183    }
184
185    /// Returns the exact authored-source identity.
186    #[must_use]
187    pub fn source(&self) -> &ContentId {
188        &self.source
189    }
190
191    /// Returns the canonical frozen-oracle identity.
192    #[must_use]
193    pub fn id(&self) -> &ContentId {
194        &self.id
195    }
196}
197
198/// Prediction-only work counters. There is deliberately no proof result field.
199#[derive(Clone, Debug, Eq, PartialEq)]
200pub struct PredictedWork {
201    /// Number of planner decisions emitted.
202    pub decisions: usize,
203    /// Number of semantic changed facts consumed.
204    pub changed_facts: usize,
205    /// Number of exact explanation paths emitted.
206    pub explanations: usize,
207    /// Number of semantic journal records predicted.
208    pub journal_records: usize,
209    /// Number of work items a later planner would start.
210    pub planned_starts: usize,
211}
212
213/// Complete stage-one prediction for one controlled delta.
214#[derive(Clone, Debug, Eq, PartialEq)]
215pub struct PredictedClosureReport {
216    /// Delta identity.
217    pub delta: String,
218    /// Exact predicted affected set.
219    pub affected: BTreeSet<ConclusionId>,
220    /// Exact predicted unaffected set.
221    pub unaffected: BTreeSet<ConclusionId>,
222    /// Explicit declared-conclusion denominator.
223    pub denominator: usize,
224    /// Integer basis points of conclusions predicted unaffected.
225    pub unaffected_basis_points: u16,
226    /// Predicted semantic journal revision movement.
227    pub revision_delta: u64,
228    /// Whether the prediction emitted carried execution state.
229    pub carried_state: bool,
230    /// Exact causal explanation paths.
231    pub explanations: Vec<Explanation>,
232    /// Prediction-only counters.
233    pub work: PredictedWork,
234}
235
236/// Closed stage-one contracts that can require projection repair.
237#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
238pub enum AssayContract {
239    /// A controlled delta used the wrong semantic class.
240    DeltaClass,
241    /// A changed fact was absent from the sealed fact universe.
242    UnknownChangedFact,
243    /// The sealed graph disagreed with the declared denominator.
244    Denominator,
245    /// The prediction exceeded its affected-conclusion ceiling.
246    AffectedCeiling,
247    /// The prediction exceeded its journal revision ceiling.
248    RevisionDelta,
249    /// Carried-state emission disagreed with the oracle.
250    CarriedState,
251    /// D1 did not leave every conclusion unaffected.
252    D1UnaffectedFloor,
253    /// D2 left less than 95 percent of conclusions unaffected.
254    D2UnaffectedFloor,
255    /// D4 left less than 70 percent of conclusions unaffected.
256    D4UnaffectedFloor,
257    /// D6 emitted carried state.
258    D6CarriedState,
259    /// A semantic no-op predicted journal or execution work.
260    SemanticNoOpWork,
261}
262
263/// One assay row either matches its frozen oracle or enters bounded repair.
264#[derive(Clone, Debug, Eq, PartialEq)]
265pub enum AssayOutcome {
266    /// Exact expected closure and every row contract passed.
267    Passed(PredictedClosureReport),
268    /// The report is retained with the canonical bounded repair set.
269    Repair {
270        /// Failed prediction.
271        report: PredictedClosureReport,
272        /// Exact locations to repair.
273        repairs: ProjectionRepairSet,
274    },
275}
276
277/// Qualification token proving all six stage-one deltas passed together.
278#[derive(Clone, Debug, Eq, PartialEq)]
279pub struct StageOneQualification {
280    /// Frozen oracle identity.
281    pub oracle: ContentId,
282    /// Six passing reports in D1-D6 order.
283    pub reports: Vec<PredictedClosureReport>,
284}
285
286/// Pure prediction-only assay over a sealed federated closure.
287pub struct PredictedClosureAssay<'a> {
288    closure: &'a FederatedClosure,
289}
290
291impl<'a> PredictedClosureAssay<'a> {
292    /// Binds the sealed projection closure under test.
293    #[must_use]
294    pub const fn new(closure: &'a FederatedClosure) -> Self {
295        Self { closure }
296    }
297
298    /// Predicts one row and compares it with the independently frozen oracle.
299    pub fn predict(
300        &self,
301        delta: &ControlledDelta,
302        expected: &ExpectedClosureSet,
303    ) -> Result<AssayOutcome, AssayError> {
304        let oracle = expected
305            .rows
306            .get(&delta.id)
307            .ok_or_else(|| AssayError::UnknownDelta(delta.id.clone()))?;
308        let all = self.closure.conclusions().cloned().collect::<BTreeSet<_>>();
309        let affected = self
310            .closure
311            .affected(delta.changed_facts.iter().cloned())
312            .into_iter()
313            .collect::<BTreeSet<_>>();
314        let unaffected = all.difference(&affected).cloned().collect::<BTreeSet<_>>();
315        let explanations = affected
316            .iter()
317            .flat_map(|conclusion| {
318                delta
319                    .changed_facts
320                    .iter()
321                    .filter_map(move |fact| self.closure.explain(conclusion, fact).ok())
322            })
323            .collect::<Vec<_>>();
324        let semantic = !delta.changed_facts.is_empty();
325        let work = PredictedWork {
326            decisions: affected.len(),
327            changed_facts: delta.changed_facts.len(),
328            explanations: explanations.len(),
329            journal_records: usize::from(semantic) * delta.changed_facts.len(),
330            planned_starts: affected.len(),
331        };
332        let basis_points = unaffected_basis_points(all.len(), affected.len());
333        let report = PredictedClosureReport {
334            delta: delta.id.clone(),
335            affected: affected.clone(),
336            unaffected,
337            denominator: all.len(),
338            unaffected_basis_points: basis_points,
339            revision_delta: delta.revision_delta,
340            carried_state: delta.carried_state,
341            explanations,
342            work,
343        };
344        let mut repairs = BTreeSet::new();
345        if expected_delta_class(&delta.id) != Some(delta.class) {
346            repairs.insert(ProjectionRepairItem::Contract(AssayContract::DeltaClass));
347        }
348        if delta
349            .changed_facts
350            .iter()
351            .any(|fact| !self.closure.contains_fact(fact))
352        {
353            repairs.insert(ProjectionRepairItem::Contract(
354                AssayContract::UnknownChangedFact,
355            ));
356        }
357        if oracle.denominator != all.len() {
358            repairs.insert(ProjectionRepairItem::Contract(AssayContract::Denominator));
359        }
360        for conclusion in affected.difference(&oracle.affected) {
361            for fact in &delta.changed_facts {
362                if self.closure.depends_on(conclusion, fact) {
363                    repairs.insert(ProjectionRepairItem::GraphEdge {
364                        conclusion: conclusion.clone(),
365                        fact: fact.clone(),
366                    });
367                }
368            }
369        }
370        for conclusion in oracle.affected.difference(&affected) {
371            repairs.insert(ProjectionRepairItem::Declaration(conclusion.clone()));
372        }
373        if affected.len() > oracle.max_affected {
374            repairs.insert(ProjectionRepairItem::Contract(
375                AssayContract::AffectedCeiling,
376            ));
377        }
378        if delta.revision_delta > oracle.max_revision_delta {
379            repairs.insert(ProjectionRepairItem::Contract(AssayContract::RevisionDelta));
380        }
381        if delta.carried_state != oracle.carried_state {
382            repairs.insert(ProjectionRepairItem::Contract(AssayContract::CarriedState));
383        }
384        match delta.id.as_str() {
385            "D1" if report.unaffected_basis_points != 10_000 => {
386                repairs.insert(ProjectionRepairItem::Contract(
387                    AssayContract::D1UnaffectedFloor,
388                ));
389            }
390            "D2" if report.unaffected_basis_points < 9_500 => {
391                repairs.insert(ProjectionRepairItem::Contract(
392                    AssayContract::D2UnaffectedFloor,
393                ));
394            }
395            "D4" if report.unaffected_basis_points < 7_000 => {
396                repairs.insert(ProjectionRepairItem::Contract(
397                    AssayContract::D4UnaffectedFloor,
398                ));
399            }
400            "D6" if report.carried_state => {
401                repairs.insert(ProjectionRepairItem::Contract(
402                    AssayContract::D6CarriedState,
403                ));
404            }
405            _ => {}
406        }
407        if !semantic && (report.work.planned_starts != 0 || report.work.journal_records != 0) {
408            repairs.insert(ProjectionRepairItem::Contract(
409                AssayContract::SemanticNoOpWork,
410            ));
411        }
412        if repairs.is_empty() {
413            Ok(AssayOutcome::Passed(report))
414        } else {
415            Ok(AssayOutcome::Repair {
416                report,
417                repairs: ProjectionRepairSet::new(&delta.id, repairs)?,
418            })
419        }
420    }
421
422    /// Qualifies exactly D1-D6 together; any repair prevents the token.
423    pub fn qualify(
424        &self,
425        deltas: &[ControlledDelta],
426        expected: &ExpectedClosureSet,
427    ) -> Result<StageOneQualification, AssayError> {
428        let actual = deltas
429            .iter()
430            .map(|delta| delta.id.as_str())
431            .collect::<Vec<_>>();
432        if actual != REQUIRED_DELTAS {
433            return Err(AssayError::IncompleteDeltaSet(actual.join(",")));
434        }
435        let mut reports = Vec::with_capacity(REQUIRED_DELTAS.len());
436        for delta in deltas {
437            match self.predict(delta, expected)? {
438                AssayOutcome::Passed(report) => reports.push(report),
439                AssayOutcome::Repair { repairs, .. } => {
440                    return Err(AssayError::RepairRequired(repairs));
441                }
442            }
443        }
444        Ok(StageOneQualification {
445            oracle: expected.id.clone(),
446            reports,
447        })
448    }
449}
450
451/// Invalid oracle, delta, graph, or failed stage-one qualification.
452#[derive(Clone, Debug, Eq, PartialEq)]
453pub enum AssayError {
454    /// Delta id is empty.
455    InvalidDeltaId,
456    /// Two expected rows name one delta.
457    DuplicateDelta,
458    /// Exact D1-D6 row set or ordering is absent.
459    IncompleteDeltaSet(String),
460    /// Requested delta is absent from the frozen oracle.
461    UnknownDelta(String),
462    /// An expected row contradicts the stage-one measurement contract.
463    InvalidExpectedClosure {
464        /// Invalid controlled delta.
465        delta: String,
466        /// Stable reason code.
467        reason: ExpectedClosureViolation,
468    },
469    /// A semantic no-op declared changed semantic facts.
470    SemanticNoOpCarriesChangedFacts(String),
471    /// Canonical evidence construction failed.
472    Canonical(String),
473    /// At least one exact prediction requires repair.
474    RepairRequired(ProjectionRepairSet),
475}
476
477/// Structural reason an independently authored oracle cannot be frozen.
478#[derive(Clone, Copy, Debug, Eq, PartialEq)]
479pub enum ExpectedClosureViolation {
480    /// Denominators differ, are zero, or conflict with affected ceilings.
481    InconsistentDenominatorOrCeiling,
482    /// D1 does not describe a complete semantic no-op.
483    NoChangeContract,
484    /// D2 or D4 violates its required unaffected percentage.
485    UnaffectedFloor,
486    /// D6 predicts semantic work, excessive revision, or carried state.
487    PresentationOnlyContract,
488}
489
490impl std::fmt::Display for AssayError {
491    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492        write!(formatter, "{self:?}")
493    }
494}
495
496impl std::error::Error for AssayError {}
497
498fn content_id_datum(id: &ContentId) -> Datum {
499    Datum::Node {
500        tag: Symbol::qualified("core", "content-id-v1"),
501        fields: vec![
502            (
503                Symbol::new("algorithm"),
504                Datum::Symbol(id.algorithm.clone()),
505            ),
506            (Symbol::new("digest"), Datum::Bytes(id.bytes.to_vec())),
507        ],
508    }
509}
510
511fn expected_delta_class(delta: &str) -> Option<ControlledDeltaClass> {
512    match delta {
513        "D1" => Some(ControlledDeltaClass::NoSemanticChange),
514        "D2" => Some(ControlledDeltaClass::PrivateImplementation),
515        "D3" => Some(ControlledDeltaClass::PublicApi),
516        "D4" => Some(ControlledDeltaClass::PublicHeadsAndPins),
517        "D5" => Some(ControlledDeltaClass::ProofAndCheckerImplementation),
518        "D6" => Some(ControlledDeltaClass::PresentationOnly),
519        _ => None,
520    }
521}
522
523fn unaffected_basis_points(denominator: usize, affected: usize) -> u16 {
524    if denominator == 0 || affected > denominator {
525        return 0;
526    }
527    u16::try_from((denominator - affected).saturating_mul(10_000) / denominator).unwrap_or(10_000)
528}
529
530fn expected_datum(row: &ExpectedClosure) -> Datum {
531    Datum::Node {
532        tag: Symbol::qualified("projection", "expected-closure-v1"),
533        fields: vec![
534            (Symbol::new("delta"), Datum::String(row.delta.clone())),
535            (
536                Symbol::new("denominator"),
537                Datum::String(row.denominator.to_string()),
538            ),
539            (
540                Symbol::new("max-affected"),
541                Datum::String(row.max_affected.to_string()),
542            ),
543            (
544                Symbol::new("max-revision-delta"),
545                Datum::String(row.max_revision_delta.to_string()),
546            ),
547            (Symbol::new("carried-state"), Datum::Bool(row.carried_state)),
548            (
549                Symbol::new("affected"),
550                Datum::Vector(
551                    row.affected
552                        .iter()
553                        .map(|id| Datum::String(id.as_str().to_owned()))
554                        .collect(),
555                ),
556            ),
557        ],
558    }
559}