1414from dataclasses import dataclass
1515from enum import Enum
1616
17+ # TYPE_CHECKING import avoids circular dependencies at runtime;
18+ # ThreeTierConstitutionalEvaluator and ConstitutionalGeometry are optional.
19+ from typing import TYPE_CHECKING
20+ if TYPE_CHECKING :
21+ from nodes .common .governance import ThreeTierConstitutionalEvaluator
22+ from nodes .common .constitutional_geometry import ConstitutionalGeometry
23+
1724logger = logging .getLogger (__name__ )
1825
1926
@@ -84,23 +91,93 @@ def _current_time(self) -> float:
8491class GovernanceEngine (BaseEngine ):
8592 """
8693 The node's duty to participate in collective decision-making.
87- Validates instructions against constitutional principles.
94+ Validates instructions against constitutional principles via the 3-tier
95+ constitutional evaluation pipeline (geometric → bottleneck → LLM).
8896 """
8997
9098 def __init__ (self , node : "Node" ):
9199 super ().__init__ (node )
92100 self .pending_instructions : List [Instruction ] = []
93101 self .validated_instructions : List [Instruction ] = []
94102
103+ # Optional 3-tier evaluator (wired at node startup when JEPA model available)
104+ self ._constitutional_evaluator : Optional ["ThreeTierConstitutionalEvaluator" ] = None
105+ self ._encode_fn : Optional [Any ] = None # text → embedding callable
106+
107+ # Calibration: recent embeddings buffer for drift monitoring
108+ # Holds at most _drift_buffer_size embeddings
109+ self ._drift_buffer : List [Any ] = []
110+ self ._drift_buffer_size : int = 64
111+ self ._drift_check_interval : int = 16 # check every N new embeddings
112+
113+ def wire_constitutional_evaluator (
114+ self ,
115+ evaluator : "ThreeTierConstitutionalEvaluator" ,
116+ encode_fn : Any ,
117+ ) -> None :
118+ """
119+ Wire the 3-tier constitutional evaluator into the engine.
120+
121+ Called by the node startup sequence after the JEPA model is loaded.
122+
123+ Args:
124+ evaluator: ThreeTierConstitutionalEvaluator instance
125+ encode_fn: Callable[[str], Tensor] — text → embedding (from TextEncoder)
126+ """
127+ self ._constitutional_evaluator = evaluator
128+ self ._encode_fn = encode_fn
129+ self .logger .info ("3-tier constitutional evaluator wired" )
130+
95131 def tick (self ) -> None :
96132 self .check_for_proposals ()
133+ self .run_drift_calibration ()
97134 self .process_pending_instructions ()
98135
99136 def check_for_proposals (self ) -> None :
100137 """Check for new proposals from the consensus network."""
101- # In production, this would poll the blockchain or P2P network
138+ # In production: poll blockchain or P2P for new evolution proposals
102139 pass
103140
141+ def run_drift_calibration (self ) -> None :
142+ """
143+ Run embedding drift detection using buffered recent embeddings.
144+
145+ Called every tick. When the buffer is full, updates DriftMonitor
146+ statistics. If drift is detected, logs a warning so operators can
147+ trigger recalibration (by calling geometry.calibrate() again with
148+ the updated model's encode_fn).
149+ """
150+ if (
151+ self ._constitutional_evaluator is None
152+ or self ._encode_fn is None
153+ or not self ._drift_buffer
154+ ):
155+ return
156+
157+ evaluator = self ._constitutional_evaluator
158+ geometry = getattr (evaluator , "_geometry" , None )
159+ if geometry is None or not geometry .drift_monitor .is_calibrated :
160+ return
161+
162+ if len (self ._drift_buffer ) < self ._drift_check_interval :
163+ return
164+
165+ try :
166+ import torch
167+ stacked = torch .stack (self ._drift_buffer [- self ._drift_check_interval :])
168+ drift = geometry .update_drift_stats (stacked )
169+ if drift :
170+ self .logger .warning (
171+ "Embedding drift detected during governance tick — "
172+ "constitutional geometry recalibration required. "
173+ "Call geometry.calibrate(encode_fn, reference_embeddings) "
174+ "after model weight update."
175+ )
176+ # Keep buffer bounded
177+ self ._drift_buffer = self ._drift_buffer [- self ._drift_buffer_size :]
178+ except Exception as e :
179+ self .logger .debug (f"Drift calibration step failed: { e } " )
180+
104181 def process_pending_instructions (self ) -> None :
105182 """Validate pending instructions against constitutional principles."""
106183 for instruction in list (self .pending_instructions ):
@@ -110,20 +187,72 @@ def process_pending_instructions(self) -> None:
110187 self .node .work .queue_instruction (instruction )
111188 else :
112189 instruction .status = InstructionStatus .REJECTED
113- self .logger .warning (f"Rejected instruction { instruction .id } : violates principles" )
190+ self .logger .warning (
191+ f"Rejected instruction { instruction .id } : violates constitutional principles"
192+ )
114193
115194 self .pending_instructions .remove (instruction )
116195
117196 def validate_instruction (self , instruction : Instruction ) -> bool :
118197 """
119- The node's "Right of Adherence" - validate against constitution.
120- In production, this would use an LLM for semantic analysis.
198+ Validate an instruction against the RPB constitution (3-tier pipeline).
199+
200+ Fast path: geometric evaluation (Tier 1) handles ~80-90% of cases in O(1).
201+ Medium path: concept bottleneck (Tier 2) for nuanced cases.
202+ Slow path: LLM evaluation (Tier 3) for uncertain/adversarial cases.
203+
204+ Falls back to the existing constitution.validate_action() check if no
205+ constitutional evaluator is wired.
121206 """
207+ # Try 3-tier evaluator first
208+ if self ._constitutional_evaluator is not None :
209+ action_text = f"{ instruction .action } : { instruction .proof_of_adherence } "
210+ verdict , confidence = self ._constitutional_evaluator .check_action (
211+ action_text , self ._encode_fn , instruction .proof_of_adherence
212+ )
213+
214+ if verdict == "violation" :
215+ self .logger .warning (
216+ f"Instruction { instruction .id } rejected by constitutional evaluator: "
217+ f"verdict=violation, confidence={ confidence :.3f} "
218+ )
219+ return False
220+
221+ if verdict == "compliant" :
222+ self .logger .debug (
223+ f"Instruction { instruction .id } approved: "
224+ f"verdict=compliant, confidence={ confidence :.3f} "
225+ )
226+ # Buffer embedding for drift monitoring
227+ self ._buffer_embedding (action_text )
228+ return True
229+
230+ # verdict == "uncertain": fall through to constitution check
231+ self .logger .debug (
232+ f"Constitutional evaluator uncertain for { instruction .id } "
233+ f"(confidence={ confidence :.3f} ) — falling back to constitution"
234+ )
235+
236+ # Fallback: existing constitution.validate_action()
122237 return self .node .constitution .validate_action (
123238 instruction .action ,
124- instruction .proof_of_adherence
239+ instruction .proof_of_adherence ,
125240 )
126241
242+ def _buffer_embedding (self , text : str ) -> None :
243+ """Buffer an embedding for drift monitoring (best-effort)."""
244+ if self ._encode_fn is None :
245+ return
246+ try :
247+ emb = self ._encode_fn (text )
248+ if emb is not None :
249+ import torch
250+ if emb .dim () > 1 :
251+ emb = emb .mean (dim = 0 )
252+ self ._drift_buffer .append (emb .detach ())
253+ except Exception :
254+ pass # Non-critical
255+
127256 def submit_instruction (self , instruction : Instruction ) -> None :
128257 """Add an instruction to the pending queue."""
129258 self .pending_instructions .append (instruction )
@@ -161,6 +290,18 @@ def execute_next(self) -> None:
161290 self .current_task = instruction
162291
163292 try :
293+ # Defense-in-depth: re-check compliance at execution time.
294+ # GovernanceEngine validates before queueing; this catches any
295+ # instructions that bypass the governance queue (e.g., injected
296+ # directly during testing or via buggy code paths).
297+ if not self ._compliance_check (instruction ):
298+ instruction .status = InstructionStatus .REJECTED
299+ self .logger .warning (
300+ f"WorkEngine compliance check rejected { instruction .id } "
301+ f"at execution time — should have been caught by GovernanceEngine"
302+ )
303+ return
304+
164305 self ._execute_instruction (instruction )
165306 instruction .status = InstructionStatus .EXECUTED
166307 self .logger .info (f"Executed: { instruction .action } " )
@@ -170,6 +311,31 @@ def execute_next(self) -> None:
170311 finally :
171312 self .current_task = None
172313
314+ def _compliance_check (self , instruction : Instruction ) -> bool :
315+ """
316+ Lightweight compliance check before instruction execution.
317+
318+ Delegates to GovernanceEngine's constitutional evaluator when available.
319+ Fails open (returns True) if no evaluator is configured — execution
320+ is never blocked by evaluator unavailability alone, since GovernanceEngine
321+ already validated the instruction on entry.
322+ """
323+ gov = self .node .governance
324+ evaluator = getattr (gov , "_constitutional_evaluator" , None )
325+ encode_fn = getattr (gov , "_encode_fn" , None )
326+
327+ if evaluator is None :
328+ return True # No evaluator: trust GovernanceEngine's prior check
329+
330+ action_text = f"{ instruction .action } : { instruction .proof_of_adherence } "
331+ verdict , confidence = evaluator .check_action (action_text , encode_fn )
332+
333+ if verdict == "violation" and confidence >= 0.85 :
334+ # Only hard-reject on high-confidence violations to avoid false positives
335+ return False
336+
337+ return True
338+
173339 def _execute_instruction (self , instruction : Instruction ) -> None :
174340 """Execute an instruction based on its action type."""
175341 action = instruction .action
0 commit comments