@@ -151,15 +151,31 @@ func (p *Plugin) collectShardInfo(aos []types.AttributedObservation) (shardHealt
151151 return shardHealth , workflows , timestamps
152152}
153153
154- func (p * Plugin ) countHealthyShards (shardHealth map [uint32 ]int ) uint32 {
155- var count uint32
154+ func (p * Plugin ) getHealthyShards (shardHealth map [uint32 ]int ) [] uint32 {
155+ var healthyShards [] uint32
156156 for shardID , votes := range shardHealth {
157157 if votes > p .config .F {
158- count ++
158+ healthyShards = append ( healthyShards , shardID )
159159 p .store .SetShardHealth (shardID , true )
160160 }
161161 }
162- return max (p .minShardCount , min (count , p .maxShardCount ))
162+ slices .Sort (healthyShards )
163+
164+ // Apply min/max bounds on shard count
165+ if uint32 (len (healthyShards )) < p .minShardCount {
166+ // Pad with sequential shard IDs if below minimum
167+ for i := uint32 (0 ); uint32 (len (healthyShards )) < p .minShardCount ; i ++ {
168+ if ! slices .Contains (healthyShards , i ) {
169+ healthyShards = append (healthyShards , i )
170+ }
171+ }
172+ slices .Sort (healthyShards )
173+ } else if uint32 (len (healthyShards )) > p .maxShardCount {
174+ // Truncate to max (keep lowest shard IDs for determinism)
175+ healthyShards = healthyShards [:p .maxShardCount ]
176+ }
177+
178+ return healthyShards
163179}
164180
165181func (p * Plugin ) Outcome (_ context.Context , outctx ocr3types.OutcomeContext , _ types.Query , aos []types.AttributedObservation ) (ocr3types.Outcome , error ) {
@@ -185,9 +201,9 @@ func (p *Plugin) Outcome(_ context.Context, outctx ocr3types.OutcomeContext, _ t
185201
186202 allWorkflows = uniqueSorted (allWorkflows )
187203
188- healthyShardCount := p .countHealthyShards (currentShardHealth )
204+ healthyShards := p .getHealthyShards (currentShardHealth )
189205
190- nextState , err := p .calculateNextState (prior .State , healthyShardCount , now )
206+ nextState , err := p .calculateNextState (prior .State , uint32 ( len ( healthyShards )) , now )
191207 if err != nil {
192208 return nil , err
193209 }
@@ -196,7 +212,7 @@ func (p *Plugin) Outcome(_ context.Context, outctx ocr3types.OutcomeContext, _ t
196212 // This must be a pure function of consensus-derived data to avoid protocol failures
197213 routes := make (map [string ]* pb.WorkflowRoute )
198214 for _ , wfID := range allWorkflows {
199- assignedShard := getShardForWorkflow (wfID , healthyShardCount )
215+ assignedShard := getShardForWorkflow (wfID , healthyShards )
200216 routes [wfID ] = & pb.WorkflowRoute {
201217 Shard : assignedShard ,
202218 }
@@ -207,7 +223,7 @@ func (p *Plugin) Outcome(_ context.Context, outctx ocr3types.OutcomeContext, _ t
207223 Routes : routes ,
208224 }
209225
210- p .lggr .Infow ("Consensus Outcome" , "healthyShards" , healthyShardCount , "totalObservations" , len (aos ), "workflowCount" , len (routes ))
226+ p .lggr .Infow ("Consensus Outcome" , "healthyShards" , len ( healthyShards ) , "totalObservations" , len (aos ), "workflowCount" , len (routes ))
211227
212228 return proto.MarshalOptions {Deterministic : true }.Marshal (outcome )
213229}
0 commit comments