File size: 9,366 Bytes
9425aed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}

module ConsensusTypes where

import qualified Data.Map as Map
import Data.Map (Map)
import GHC.Generics (Generic)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Hashable
import Data.List (sortBy, groupBy)
import Data.Ord (comparing)

-- ─────────────────────────────────────────────────────────────────────────────
-- Core Types: Agent + Observation + Vote
-- ─────────────────────────────────────────────────────────────────────────────

-- | Unique agent identifier
newtype AgentId = AgentId Int
  deriving (Eq, Ord, Show, Generic)

instance Hashable AgentId

-- | Unique observation identifier
newtype ObservationId = ObservationId Int
  deriving (Eq, Ord, Show, Generic)

instance Hashable ObservationId

-- | Unique region identifier
newtype RegionId = RegionId Int
  deriving (Eq, Ord, Show, Generic)

instance Hashable RegionId

-- | 3D vector for positions
data Vector = Vector { vx :: Double, vy :: Double, vz :: Double }
  deriving (Eq, Show, Generic)

instance Hashable Vector

-- | Region type classification (from manifold exploration)
data RegionType = RegionType
  { rtName :: String
  , rtDensity :: Double
  , rtCurvature :: Double
  , rtAnomalyLevel :: Double
  } deriving (Eq, Show, Generic, Ord)

instance Hashable RegionType

-- | Measurement bundle (timestamp + readings)
data Measurement = Measurement
  { measTimestamp :: Int
  , measValues :: Map String Double
  } deriving (Eq, Show, Generic)

-- | Observation from a single agent (immutable, observable-only)
data Observation = Observation
  { obsId :: ObservationId
  , agentId :: AgentId
  , timestamp :: Int
  , coordinates :: Vector
  , measurements :: Map String Double    -- e.g., "density" -> 0.523
  , confidence :: Double                 -- [0, 1] agent's self-confidence
  , regionType :: Maybe RegionType       -- classified region
  , wormSealed :: Bool                   -- WORM seal status
  , sealRound :: Maybe Int               -- round number when sealed
  } deriving (Eq, Show, Generic)

-- | Vote from one agent on another's observation
data Vote = Vote
  { voterId :: AgentId
  , votedObsId :: ObservationId
  , agreement :: Double                  -- [-1, 1]: -1 disagree, 0 uncertain, +1 agree
  , voteRound :: Int
  , voteTimestamp :: Int
  } deriving (Eq, Show, Generic)

-- | Voting round metadata
data VoteRound = VoteRound
  { roundNum :: Int
  , roundTimestamp :: Int
  , roundVotes :: [Vote]
  , roundObservations :: [Observation]
  } deriving (Eq, Show, Generic)

-- | Anomaly detection record
data Anomaly = Anomaly
  { anomalyId :: Int
  , anomalyLocation :: Vector
  , anomalySeverity :: Double            -- [0, 1]
  , anomalyRound :: Int
  , anomalyAgents :: [AgentId]           -- which agents observed it
  , anomalyConfidence :: Double          -- consensus confidence [0, 1]
  } deriving (Eq, Show, Generic)

-- | Conflict detection (two agents disagree on same measurement)
data Conflict = Conflict
  { conflictObsId :: ObservationId
  , conflictRegionId :: RegionId
  , conflictAgents :: [AgentId]
  , conflictMeasureDiff :: Double        -- magnitude of disagreement
  , conflictResolved :: Bool
  } deriving (Eq, Show, Generic)

-- | Shared world model (consensus result)
data WorldModel = WorldModel
  { regionTypes :: Map RegionId RegionType
  , agentPositions :: Map AgentId Vector
  , anomalies :: [Anomaly]
  , frontierRegions :: [RegionId]
  , modelConfidence :: Double            -- collective confidence [0, 1]
  , modelGeneration :: Int               -- which round produced this model
  } deriving (Eq, Show, Generic)

-- | Complete consensus state (accumulates over rounds)
data ConsensusState = ConsensusState
  { observations :: [Observation]
  , votes :: [Vote]
  , voteRounds :: [VoteRound]
  , worldModel :: WorldModel
  , confidence :: Double                 -- global consensus confidence [0, 1]
  , conflicts :: [Conflict]
  , wormSealLog :: [SealRecord]          -- WORM-sealed audit trail
  , generation :: Int                    -- consensus generation number
  } deriving (Eq, Show, Generic)

-- | WORM-sealed audit record
data SealRecord = SealRecord
  { sealRound :: Int
  , sealTimestamp :: Int
  , sealedObsCount :: Int
  , sealedVoteCount :: Int
  , sealHash :: ByteString               -- Blake3 hash of sealed data
  , sealAgents :: [AgentId]              -- agents participating in round
  } deriving (Eq, Show, Generic)

-- ─────────────────────────────────────────────────────────────────────────────
-- Initial State Constructors
-- ─────────────────────────────────────────────────────────────────────────────

-- | Create empty world model
emptyWorldModel :: WorldModel
emptyWorldModel = WorldModel
  { regionTypes = Map.empty
  , agentPositions = Map.empty
  , anomalies = []
  , frontierRegions = []
  , modelConfidence = 0.0
  , modelGeneration = 0
  }

-- | Create empty consensus state
emptyConsensusState :: ConsensusState
emptyConsensusState = ConsensusState
  { observations = []
  , votes = []
  , voteRounds = []
  , worldModel = emptyWorldModel
  , confidence = 0.0
  , conflicts = []
  , wormSealLog = []
  , generation = 0
  }

-- | Create observation from agent reading
makeObservation :: ObservationId -> AgentId -> Int -> Vector
                -> Map String Double -> Double -> Maybe RegionType
                -> Observation
makeObservation oid aid ts pos meas conf regType = Observation
  { obsId = oid
  , agentId = aid
  , timestamp = ts
  , coordinates = pos
  , measurements = meas
  , confidence = max 0.0 (min 1.0 conf)
  , regionType = regType
  , wormSealed = False
  , sealRound = Nothing
  }

-- | Create vote
makeVote :: AgentId -> ObservationId -> Double -> Int -> Int -> Vote
makeVote voter obsId agrmt round ts = Vote
  { voterId = voter
  , votedObsId = obsId
  , agreement = max (-1.0) (min 1.0 agrmt)
  , voteRound = round
  , voteTimestamp = ts
  }

-- ─────────────────────────────────────────────────────────────────────────────
-- Utility Functions
-- ─────────────────────────────────────────────────────────────────────────────

-- | Calculate Euclidean distance between vectors
vectorDistance :: Vector -> Vector -> Double
vectorDistance v1 v2 = sqrt ((vx v1 - vx v2)^2 + (vy v1 - vy v2)^2 + (vz v1 - vz v2)^2)

-- | Average a list of doubles
averageDouble :: [Double] -> Double
averageDouble [] = 0.0
averageDouble xs = sum xs / fromIntegral (length xs)

-- | Filter votes for a specific observation
votesForObservation :: [Vote] -> ObservationId -> [Vote]
votesForObservation vs obsId = filter (\v -> votedObsId v == obsId) vs

-- | Filter observations by agent
observationsByAgent :: [Observation] -> AgentId -> [Observation]
observationsByAgent obs aid = filter (\o -> agentId o == aid) obs

-- | Calculate consensus score for observation (average agreement)
consensusScore :: [Vote] -> Double
consensusScore [] = 0.0
consensusScore vs = averageDouble (map agreement vs)

-- | Check if observation has consensus (66%+ agreement)
hasConsensus :: [Vote] -> Bool
hasConsensus vs
  | null vs = False
  | otherwise = let score = consensusScore vs
                    positiveVotes = fromIntegral (length (filter (\v -> agreement v > 0.5) vs)) :: Double
                    ratioVotes = positiveVotes / fromIntegral (length vs)
                in score > 0.33 && ratioVotes >= 0.66

-- | Measurement difference metric (normalized L2)
measurementDifference :: Map String Double -> Map String Double -> Double
measurementDifference m1 m2 =
  let allKeys = Map.keys m1 ++ Map.keys m2
      diffs = map (\k -> let v1 = Map.findWithDefault 0.0 k m1
                             v2 = Map.findWithDefault 0.0 k m2
                         in abs (v1 - v2)) allKeys
  in if null diffs then 0.0 else averageDouble diffs

-- | Group observations by region
groupByRegion :: [Observation] -> Map RegionId [Observation]
groupByRegion obs =
  let grouped = groupBy (\o1 o2 -> regionType o1 == regionType o2)
                        (sortBy (comparing regionType) obs)
  in Map.fromList [(RegionId i, g) | (i, g) <- zip [0..] grouped]

-- | Get all agents from observations and votes
extractAgentIds :: ConsensusState -> [AgentId]
extractAgentIds state =
  let fromObs = map agentId (observations state)
      fromVotes = map voterId (votes state)
  in nub (fromObs ++ fromVotes)
  where
    nub [] = []
    nub (x:xs) = x : nub (filter (/= x) xs)