dsfb_semiconductor/interface/
mod.rs1use crate::grammar::layer::{build_grammar_states, GrammarState};
2use crate::input::alarm_stream::{AlarmSample, AlarmStream};
3use crate::input::residual_stream::{ResidualSample, ResidualStream};
4use crate::policy::{derive_policy, PolicyDecision};
5use crate::semantics::{match_semantics, minimal_heuristics_bank, Heuristic, SemanticMatch};
6use crate::sign::{build_feature_signs, FeatureSignPoint};
7use crate::syntax::{build_motifs, Motif, MotifTimelinePoint};
8use std::sync::RwLock;
9
10pub trait DSFBObserver {
11 fn ingest(&self, residual: &ResidualSample);
12 fn output(&self) -> Vec<PolicyDecision>;
13}
14
15pub trait FabDataSource {
16 fn residual_stream(&self) -> Vec<ResidualSample>;
17}
18
19#[derive(Debug, Default)]
20pub struct ReadOnlyDsfbObserver {
21 residuals: RwLock<Vec<ResidualSample>>,
22 alarms: RwLock<Vec<AlarmSample>>,
23}
24
25#[derive(Debug, Clone)]
26pub struct ObserverArtifacts {
27 pub signs: Vec<FeatureSignPoint>,
28 pub motifs: Vec<Motif>,
29 pub motif_timeline: Vec<MotifTimelinePoint>,
30 pub grammar_states: Vec<GrammarState>,
31 pub heuristics: Vec<Heuristic>,
32 pub semantic_matches: Vec<SemanticMatch>,
33 pub policy_decisions: Vec<PolicyDecision>,
34}
35
36impl ReadOnlyDsfbObserver {
37 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn with_alarm_stream(alarm_stream: AlarmStream) -> Self {
42 Self {
43 residuals: RwLock::new(Vec::new()),
44 alarms: RwLock::new(alarm_stream.samples().to_vec()),
45 }
46 }
47
48 pub fn ingest_alarm(&self, alarm: &AlarmSample) {
49 self.alarms.write().unwrap().push(alarm.clone());
50 self.alarms.write().unwrap().sort_by(|left, right| {
51 left.timestamp
52 .total_cmp(&right.timestamp)
53 .then_with(|| left.source.cmp(&right.source))
54 });
55 }
56
57 pub fn residual_stream(&self) -> ResidualStream {
58 ResidualStream::new(self.residuals.read().unwrap().clone())
59 }
60
61 pub fn alarm_stream(&self) -> AlarmStream {
62 AlarmStream::new(self.alarms.read().unwrap().clone())
63 }
64
65 pub fn layered_output(&self) -> ObserverArtifacts {
66 let residual_stream = self.residual_stream();
67 let signs = build_feature_signs(&residual_stream);
68 let syntax = build_motifs(&signs);
69 let grammar_states = build_grammar_states(&signs, &syntax.timeline);
70 let heuristics = minimal_heuristics_bank();
71 let semantic_matches = match_semantics(&syntax.timeline, &grammar_states, &heuristics);
72 let policy_decisions = derive_policy(&semantic_matches, &grammar_states);
73 ObserverArtifacts {
74 signs,
75 motifs: syntax.motifs,
76 motif_timeline: syntax.timeline,
77 grammar_states,
78 heuristics,
79 semantic_matches,
80 policy_decisions,
81 }
82 }
83}
84
85impl DSFBObserver for ReadOnlyDsfbObserver {
86 fn ingest(&self, residual: &ResidualSample) {
87 self.residuals.write().unwrap().push(residual.clone());
88 self.residuals.write().unwrap().sort_by(|left, right| {
89 left.timestamp
90 .total_cmp(&right.timestamp)
91 .then_with(|| left.feature_id.cmp(&right.feature_id))
92 });
93 }
94
95 fn output(&self) -> Vec<PolicyDecision> {
96 self.layered_output().policy_decisions
97 }
98}
99
100impl FabDataSource for ResidualStream {
101 fn residual_stream(&self) -> Vec<ResidualSample> {
102 self.samples().to_vec()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn observer_replay_is_deterministic() {
112 let observer = ReadOnlyDsfbObserver::new();
113 let sample = ResidualSample {
114 timestamp: 1.0,
115 feature_id: "S059".into(),
116 value: 2.0,
117 };
118 observer.ingest(&sample);
119 observer.ingest(&ResidualSample {
120 timestamp: 2.0,
121 feature_id: "S059".into(),
122 value: 3.0,
123 });
124 observer.ingest(&ResidualSample {
125 timestamp: 3.0,
126 feature_id: "S059".into(),
127 value: 4.2,
128 });
129 let first = observer.layered_output();
130 let second = observer.layered_output();
131 assert_eq!(first.policy_decisions, second.policy_decisions);
132 assert_eq!(first.semantic_matches, second.semantic_matches);
133 assert_eq!(first.grammar_states, second.grammar_states);
134 }
135
136 #[test]
137 fn residual_stream_implements_fab_data_source_read_only() {
138 let source = ResidualStream::new(vec![ResidualSample {
139 timestamp: 1.0,
140 feature_id: "S059".into(),
141 value: 0.5,
142 }]);
143 let exported = source.residual_stream();
144 assert_eq!(exported.len(), 1);
145 assert_eq!(exported[0].feature_id, "S059");
146 assert_eq!(source.samples().len(), 1);
147 }
148}