fable-engine 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fable_compressor.py +356 -0
- fable_engine/__init__.py +1 -0
- fable_engine/actions/__init__.py +291 -0
- fable_engine/actions/cas.py +182 -0
- fable_engine/actions/deliberation.py +523 -0
- fable_engine/actions/fleet.py +807 -0
- fable_engine/actions/lifecycle.py +298 -0
- fable_engine/actions/scrapers.py +116 -0
- fable_engine/actions/system3.py +815 -0
- fable_engine/browser.py +824 -0
- fable_engine/cas.py +974 -0
- fable_engine/fable_session.json +510 -0
- fable_engine/guards.py +283 -0
- fable_engine/schema.py +714 -0
- fable_engine/scrapers/__init__.py +32 -0
- fable_engine/scrapers/arxiv.py +115 -0
- fable_engine/scrapers/base.py +386 -0
- fable_engine/scrapers/github.py +129 -0
- fable_engine/scrapers/reddit.py +154 -0
- fable_engine/scrapers/web.py +120 -0
- fable_engine/scrapers/x.py +125 -0
- fable_engine/scrapers/youtube.py +132 -0
- fable_engine/server.py +414 -0
- fable_engine/session.py +1819 -0
- fable_engine/test_server.py +1362 -0
- fable_engine/updater.py +541 -0
- fable_engine-1.3.1.dist-info/LICENSE +22 -0
- fable_engine-1.3.1.dist-info/METADATA +173 -0
- fable_engine-1.3.1.dist-info/RECORD +104 -0
- fable_engine-1.3.1.dist-info/WHEEL +5 -0
- fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
- fable_engine-1.3.1.dist-info/top_level.txt +6 -0
- fable_mode/__init__.py +3 -0
- fable_mode/__main__.py +4 -0
- fable_mode/adapters.py +1014 -0
- fable_mode/installer.py +553 -0
- fable_mode/launcher.py +437 -0
- fable_mode/manifest.py +142 -0
- fable_mode/resources.json +114 -0
- fable_mode/safety.py +103 -0
- fable_mode_entry.py +10 -0
- fable_v2/__init__.py +146 -0
- fable_v2/adapters.py +151 -0
- fable_v2/coder_fleet/__init__.py +100 -0
- fable_v2/coder_fleet/ast_tools.py +158 -0
- fable_v2/coder_fleet/compute.py +199 -0
- fable_v2/coder_fleet/design_engine.py +1316 -0
- fable_v2/coder_fleet/diagnostics.py +293 -0
- fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
- fable_v2/coder_fleet/mock_auditor.py +306 -0
- fable_v2/coder_fleet/mutation.py +216 -0
- fable_v2/coder_fleet/property_oracle.py +260 -0
- fable_v2/coder_fleet/receipt_attestor.py +122 -0
- fable_v2/coder_fleet/red_team_swarm.py +908 -0
- fable_v2/coder_fleet/test_harness.py +198 -0
- fable_v2/coder_fleet/vector_engine.py +1287 -0
- fable_v2/coder_fleet/visual.py +357 -0
- fable_v2/coder_fleet/workspace.py +153 -0
- fable_v2/cortical/__init__.py +20 -0
- fable_v2/cortical/plasticity_engine.py +992 -0
- fable_v2/execution_broker.py +811 -0
- fable_v2/proof_engine.py +1141 -0
- fable_v2/protocol.py +485 -0
- fable_v2/runtime.py +1010 -0
- fable_v2/system3/__init__.py +204 -0
- fable_v2/system3/causal.py +558 -0
- fable_v2/system3/dialectical.py +577 -0
- fable_v2/system3/evolution.py +503 -0
- fable_v2/system3/executive.py +338 -0
- fable_v2/system3/free_energy.py +479 -0
- fable_v2/system3/hyperbolic.py +555 -0
- fable_v2/system3/induction.py +336 -0
- fable_v2/system3/kripke.py +548 -0
- fable_v2/system3/oracle.py +745 -0
- fable_v2/verifiers.py +72 -0
- tests/__init__.py +1 -0
- tests/test_anti_loop_circuit_breaker.py +64 -0
- tests/test_auto_updater.py +407 -0
- tests/test_coder_fleet.py +535 -0
- tests/test_delegation_compiler.py +54 -0
- tests/test_descriptor_boundaries.py +126 -0
- tests/test_design_engine.py +603 -0
- tests/test_epistemic_evidence_validator.py +66 -0
- tests/test_execution_broker.py +233 -0
- tests/test_fable_v2.py +406 -0
- tests/test_fleet_transitions.py +116 -0
- tests/test_fsm_redteam_evolution.py +406 -0
- tests/test_goal_rubric_and_pipeline.py +367 -0
- tests/test_hebbian_plasticity.py +585 -0
- tests/test_packaging_runtime.py +194 -0
- tests/test_proof_engine.py +259 -0
- tests/test_red_team_swarm.py +645 -0
- tests/test_redteam_remediation.py +169 -0
- tests/test_registration_transaction.py +375 -0
- tests/test_requested_regressions.py +467 -0
- tests/test_scrapers.py +370 -0
- tests/test_server_actions.py +93 -0
- tests/test_server_frontier_actions.py +269 -0
- tests/test_server_protocol.py +88 -0
- tests/test_stealth_browser.py +970 -0
- tests/test_system3.py +381 -0
- tests/test_system3_deep_integration.py +385 -0
- tests/test_system3_frontier.py +436 -0
- tests/test_vector_engine.py +608 -0
fable_v2/runtime.py
ADDED
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
"""Evidence-gated, host-neutral Fable V2 runtime.
|
|
2
|
+
|
|
3
|
+
This module is intentionally model-agnostic. It does not pretend that a
|
|
4
|
+
prompt or MCP call makes a result correct; it provides the state machine and
|
|
5
|
+
acceptance gates that a host adapter and verifier must satisfy.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field, replace
|
|
11
|
+
from enum import Enum
|
|
12
|
+
import copy
|
|
13
|
+
import hashlib
|
|
14
|
+
import hmac
|
|
15
|
+
import secrets
|
|
16
|
+
import threading
|
|
17
|
+
from typing import Any, Iterable, Protocol
|
|
18
|
+
|
|
19
|
+
from .protocol import (
|
|
20
|
+
Candidate,
|
|
21
|
+
Evidence,
|
|
22
|
+
FileChangeRecord,
|
|
23
|
+
ModelVelocityProfile,
|
|
24
|
+
ProofReceipt,
|
|
25
|
+
TaskSpec,
|
|
26
|
+
ToolReceipt,
|
|
27
|
+
VerificationPolicy,
|
|
28
|
+
VerificationResult,
|
|
29
|
+
VisualMockupSpec,
|
|
30
|
+
canonical_hash,
|
|
31
|
+
utc_now,
|
|
32
|
+
)
|
|
33
|
+
from .proof_engine import (
|
|
34
|
+
DeterministicProofValidator,
|
|
35
|
+
ProofType,
|
|
36
|
+
ProofValidationResult,
|
|
37
|
+
)
|
|
38
|
+
from .system3 import (
|
|
39
|
+
ActiveInferenceEngine,
|
|
40
|
+
FreeEnergyReport,
|
|
41
|
+
Policy,
|
|
42
|
+
create_default_architecture_pomdp,
|
|
43
|
+
KripkeStructure,
|
|
44
|
+
KripkeWorld,
|
|
45
|
+
KripkeModelChecker,
|
|
46
|
+
CTLOperator,
|
|
47
|
+
FormulaNode,
|
|
48
|
+
FormulaParser,
|
|
49
|
+
HyperbolicPoint,
|
|
50
|
+
PoincareBall,
|
|
51
|
+
HyperbolicTreeEmbedder,
|
|
52
|
+
TreeEmbeddingNode,
|
|
53
|
+
TreeEmbeddingResult,
|
|
54
|
+
Contradiction,
|
|
55
|
+
ThesisCandidate,
|
|
56
|
+
AntithesisCritique,
|
|
57
|
+
TRIZPrinciple,
|
|
58
|
+
TRIZContradictionResolver,
|
|
59
|
+
TRIZResolutionRecommendation,
|
|
60
|
+
TRIZ_PRINCIPLES_CATALOG,
|
|
61
|
+
DialecticalSynthesizer,
|
|
62
|
+
EmergentSynthesis,
|
|
63
|
+
CognitiveBiasDetector,
|
|
64
|
+
CognitiveBiasFinding,
|
|
65
|
+
CognitiveBiasType,
|
|
66
|
+
TriLevelArbitrator,
|
|
67
|
+
System3Executive,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RegisteredVerifier(Protocol):
|
|
72
|
+
"""A verifier invoked through the in-process foundation API.
|
|
73
|
+
|
|
74
|
+
``verify`` must inspect the supplied candidate and return an un-attested
|
|
75
|
+
result. The runtime stamps its identity and candidate hash afterwards.
|
|
76
|
+
In-process registration is not a security boundary.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
name: str
|
|
80
|
+
verifier_class: str
|
|
81
|
+
independent: bool
|
|
82
|
+
trust_boundary: str
|
|
83
|
+
|
|
84
|
+
def verify(self, candidate: Candidate) -> VerificationResult:
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RunState(str, Enum):
|
|
89
|
+
CREATED = "created"
|
|
90
|
+
ACTIVE = "active"
|
|
91
|
+
VERIFYING = "verifying"
|
|
92
|
+
FINALIZED = "finalized"
|
|
93
|
+
REJECTED = "rejected"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class FableRun:
|
|
98
|
+
"""A single auditable task run."""
|
|
99
|
+
|
|
100
|
+
session_id: str
|
|
101
|
+
task: TaskSpec
|
|
102
|
+
state: RunState = RunState.CREATED
|
|
103
|
+
started_at: str = field(default_factory=utc_now)
|
|
104
|
+
receipts: dict[str, ToolReceipt] = field(default_factory=dict)
|
|
105
|
+
candidates: dict[str, Candidate] = field(default_factory=dict)
|
|
106
|
+
evidence: dict[str, Evidence] = field(default_factory=dict)
|
|
107
|
+
verifications: dict[str, VerificationResult] = field(default_factory=dict)
|
|
108
|
+
events: list[dict[str, Any]] = field(default_factory=list)
|
|
109
|
+
final_candidate_id: str | None = None
|
|
110
|
+
invalidated_verifiers: dict[str, str] = field(default_factory=dict)
|
|
111
|
+
# System 3 Meta-Cognitive Deliberation & Invariant Tracking
|
|
112
|
+
system3_free_energy: dict[str, Any] = field(default_factory=dict)
|
|
113
|
+
system3_kripke_invariants: dict[str, Any] = field(default_factory=dict)
|
|
114
|
+
system3_hyperbolic_embeddings: dict[str, Any] = field(default_factory=dict)
|
|
115
|
+
system3_meta_cycles: list[dict[str, Any]] = field(default_factory=list)
|
|
116
|
+
triz_repair_recommendations: list[dict[str, Any]] = field(default_factory=list)
|
|
117
|
+
file_changes: list[FileChangeRecord] = field(default_factory=list)
|
|
118
|
+
visual_mockups: list[VisualMockupSpec] = field(default_factory=list)
|
|
119
|
+
model_velocity: ModelVelocityProfile | None = None
|
|
120
|
+
proof_validator: DeterministicProofValidator = field(default_factory=DeterministicProofValidator,
|
|
121
|
+
repr=False, compare=False)
|
|
122
|
+
_attestation_secret: bytes = field(default_factory=lambda: secrets.token_bytes(32),
|
|
123
|
+
repr=False)
|
|
124
|
+
_lock: threading.RLock = field(default_factory=threading.RLock,
|
|
125
|
+
repr=False, compare=False)
|
|
126
|
+
|
|
127
|
+
def _event(self, event_type: str, **data: Any) -> None:
|
|
128
|
+
with self._lock:
|
|
129
|
+
event = {"type": event_type, "at": utc_now(), **data}
|
|
130
|
+
event["prev_hash"] = self.events[-1].get("event_hash", "0" * 64) if self.events else "0" * 64
|
|
131
|
+
event["event_hash"] = canonical_hash(event)
|
|
132
|
+
self.events.append(event)
|
|
133
|
+
|
|
134
|
+
def validate_event_history(self) -> None:
|
|
135
|
+
"""Reject edited, reordered, or truncated event history."""
|
|
136
|
+
previous = "0" * 64
|
|
137
|
+
for event in self.events:
|
|
138
|
+
if event.get("prev_hash") != previous:
|
|
139
|
+
raise ValueError("event history chain is broken")
|
|
140
|
+
supplied_hash = event.get("event_hash")
|
|
141
|
+
body = {key: value for key, value in event.items() if key != "event_hash"}
|
|
142
|
+
if supplied_hash != canonical_hash(body):
|
|
143
|
+
raise ValueError("event history contains a tampered event")
|
|
144
|
+
previous = supplied_hash
|
|
145
|
+
|
|
146
|
+
def start(self) -> None:
|
|
147
|
+
if self.state is not RunState.CREATED:
|
|
148
|
+
raise RuntimeError(f"run is already {self.state.value}")
|
|
149
|
+
self.state = RunState.ACTIVE
|
|
150
|
+
self._event("run_started", session_id=self.session_id)
|
|
151
|
+
|
|
152
|
+
def record_receipt(self, receipt: ToolReceipt) -> None:
|
|
153
|
+
with self._lock:
|
|
154
|
+
if receipt.session_id != self.session_id:
|
|
155
|
+
raise ValueError("tool receipt belongs to a different session")
|
|
156
|
+
if receipt.receipt_id in self.receipts:
|
|
157
|
+
raise ValueError(f"duplicate receipt: {receipt.receipt_id}")
|
|
158
|
+
self.receipts[receipt.receipt_id] = receipt
|
|
159
|
+
self._event("tool_receipt", receipt_id=receipt.receipt_id,
|
|
160
|
+
capability=receipt.capability, success=receipt.success)
|
|
161
|
+
|
|
162
|
+
def _evaluate_system3_for_candidate(self, candidate: Candidate) -> None:
|
|
163
|
+
"""Compute and track Friston Free Energy F, Kripke state invariants, and Hyperbolic tree embeddings."""
|
|
164
|
+
try:
|
|
165
|
+
# 1. Friston Active Inference Free Energy F
|
|
166
|
+
pomdp_model = create_default_architecture_pomdp()
|
|
167
|
+
fe_engine = ActiveInferenceEngine(pomdp_model)
|
|
168
|
+
obs = "HIGH_THROUGHPUT_CLEAN" if all(
|
|
169
|
+
self.receipts[r].success for r in candidate.receipt_ids if r in self.receipts
|
|
170
|
+
) else "CHECKSUM_FAIL"
|
|
171
|
+
fe_policies = [Policy(policy_id=f"p_{act}", actions=[act]) for act in pomdp_model.actions]
|
|
172
|
+
fe_report = fe_engine.select_action(obs, fe_policies)
|
|
173
|
+
fe_data = {
|
|
174
|
+
"variational_free_energy_f": round(fe_report.variational_free_energy_f, 4),
|
|
175
|
+
"complexity_kl": round(fe_report.complexity_kl, 4),
|
|
176
|
+
"accuracy_log_likelihood": round(fe_report.accuracy_log_likelihood, 4),
|
|
177
|
+
"observation": obs,
|
|
178
|
+
"selected_policy": fe_report.selected_action,
|
|
179
|
+
}
|
|
180
|
+
candidate.metadata["system3_free_energy"] = fe_data
|
|
181
|
+
self.system3_free_energy[candidate.candidate_id] = fe_data
|
|
182
|
+
|
|
183
|
+
# 2. Kripke state invariants AG(safe)
|
|
184
|
+
is_clean = all(
|
|
185
|
+
self.receipts[r].success for r in candidate.receipt_ids if r in self.receipts
|
|
186
|
+
)
|
|
187
|
+
kripke = KripkeStructure()
|
|
188
|
+
w_init_props = {"initialized"}
|
|
189
|
+
if is_clean:
|
|
190
|
+
w_init_props.add("safe")
|
|
191
|
+
kripke.add_world("w_init", propositions=w_init_props)
|
|
192
|
+
|
|
193
|
+
w_cand_props = {"candidate_registered", "artifact_bounded"}
|
|
194
|
+
if is_clean:
|
|
195
|
+
w_cand_props.add("safe")
|
|
196
|
+
else:
|
|
197
|
+
w_cand_props.add("unsafe")
|
|
198
|
+
kripke.add_world("w_cand", propositions=w_cand_props)
|
|
199
|
+
|
|
200
|
+
w_ver_props = {"verifiable"}
|
|
201
|
+
if is_clean and not self.invalidated_verifiers:
|
|
202
|
+
w_ver_props.add("safe")
|
|
203
|
+
else:
|
|
204
|
+
w_ver_props.add("unsafe")
|
|
205
|
+
kripke.add_world("w_ver", propositions=w_ver_props)
|
|
206
|
+
|
|
207
|
+
kripke.add_transition("w_init", "w_cand")
|
|
208
|
+
kripke.add_transition("w_cand", "w_ver")
|
|
209
|
+
kripke.add_transition("w_ver", "w_ver")
|
|
210
|
+
checker = KripkeModelChecker(kripke)
|
|
211
|
+
k_res = checker.check("AG(safe)", "w_init")
|
|
212
|
+
kripke_data = {
|
|
213
|
+
"formula": "AG(safe)",
|
|
214
|
+
"is_satisfied": k_res.is_satisfied,
|
|
215
|
+
"initial_world": "w_init",
|
|
216
|
+
"satisfying_worlds": sorted(list(k_res.satisfied_worlds)),
|
|
217
|
+
}
|
|
218
|
+
candidate.metadata["system3_kripke"] = kripke_data
|
|
219
|
+
self.system3_kripke_invariants[candidate.candidate_id] = kripke_data
|
|
220
|
+
|
|
221
|
+
# 3. Hyperbolic Tree Embeddings
|
|
222
|
+
tree = {candidate.candidate_id: list(candidate.receipt_ids) + list(candidate.evidence_ids)}
|
|
223
|
+
for r in candidate.receipt_ids:
|
|
224
|
+
tree[r] = []
|
|
225
|
+
for e in candidate.evidence_ids:
|
|
226
|
+
tree[e] = []
|
|
227
|
+
if not tree[candidate.candidate_id]:
|
|
228
|
+
tree[candidate.candidate_id] = ["artifact_root"]
|
|
229
|
+
tree["artifact_root"] = []
|
|
230
|
+
embedder = HyperbolicTreeEmbedder(dimension=2, base_step_distance=1.0)
|
|
231
|
+
hyp_res = embedder.embed_hierarchy(tree, root_id=candidate.candidate_id)
|
|
232
|
+
hyp_data = {
|
|
233
|
+
"root_id": hyp_res.root_id,
|
|
234
|
+
"total_nodes": hyp_res.total_nodes,
|
|
235
|
+
"tree_depth": hyp_res.tree_depth,
|
|
236
|
+
"average_distortion": hyp_res.average_distortion,
|
|
237
|
+
"stress": hyp_res.stress,
|
|
238
|
+
"hierarchical_capacity_ratio": hyp_res.hierarchical_capacity_ratio,
|
|
239
|
+
}
|
|
240
|
+
candidate.metadata["system3_hyperbolic"] = hyp_data
|
|
241
|
+
self.system3_hyperbolic_embeddings[candidate.candidate_id] = hyp_data
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
def _generate_triz_repair_recommendation(
|
|
246
|
+
self, candidate_id: str, reasons: list[str]
|
|
247
|
+
) -> dict[str, Any]:
|
|
248
|
+
"""Automatically synthesize dialectical contradictions and TRIZ repair recommendations on verification failure."""
|
|
249
|
+
candidate = self.candidates.get(candidate_id)
|
|
250
|
+
candidate_title = f"Candidate {candidate_id}"
|
|
251
|
+
if candidate and isinstance(candidate.artifact, dict) and "title" in candidate.artifact:
|
|
252
|
+
candidate_title = str(candidate.artifact["title"])
|
|
253
|
+
|
|
254
|
+
thesis = ThesisCandidate(
|
|
255
|
+
thesis_id=candidate_id,
|
|
256
|
+
title=candidate_title,
|
|
257
|
+
description=f"Candidate implementation for task {self.task.task_id}",
|
|
258
|
+
strengths=[f"Capability: {c}" for c in self.successful_capabilities(candidate_id)],
|
|
259
|
+
weaknesses=list(reasons),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
contradictions = []
|
|
263
|
+
for i, r in enumerate(reasons):
|
|
264
|
+
contradictions.append(
|
|
265
|
+
Contradiction(
|
|
266
|
+
contradiction_id=f"c_fail_{i+1:03d}",
|
|
267
|
+
improving_parameter="accuracy_verification",
|
|
268
|
+
worsening_parameter="implementation_complexity",
|
|
269
|
+
description=r,
|
|
270
|
+
severity=0.75,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
if not contradictions:
|
|
274
|
+
contradictions.append(
|
|
275
|
+
Contradiction(
|
|
276
|
+
contradiction_id="c_fail_def",
|
|
277
|
+
improving_parameter="verification_pass",
|
|
278
|
+
worsening_parameter="constraint_satisfaction",
|
|
279
|
+
description="Verification failed without specific reasons",
|
|
280
|
+
severity=0.6,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
critique = AntithesisCritique(
|
|
285
|
+
critique_id=f"crit_{candidate_id}",
|
|
286
|
+
thesis_id=candidate_id,
|
|
287
|
+
title=f"Falsification Critique for {candidate_id}",
|
|
288
|
+
contradictions=contradictions,
|
|
289
|
+
failure_modes=list(reasons),
|
|
290
|
+
severity_score=0.8,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
synthesizer = DialecticalSynthesizer()
|
|
294
|
+
synthesis = synthesizer.synthesize(thesis, critique)
|
|
295
|
+
|
|
296
|
+
resolver = TRIZContradictionResolver()
|
|
297
|
+
recommendations = []
|
|
298
|
+
for c in contradictions:
|
|
299
|
+
recs = resolver.resolve_contradiction(c)
|
|
300
|
+
for r in recs:
|
|
301
|
+
recommendations.append(r.to_dict())
|
|
302
|
+
|
|
303
|
+
triz_payload = {
|
|
304
|
+
"candidate_id": candidate_id,
|
|
305
|
+
"synthesis_id": synthesis.synthesis_id,
|
|
306
|
+
"synthesis_title": synthesis.title,
|
|
307
|
+
"synthesized_architecture": synthesis.synthesized_architecture,
|
|
308
|
+
"pareto_improvement_claim": synthesis.pareto_improvement_claim,
|
|
309
|
+
"transcended_principles": [p.to_dict() for p in synthesis.transcended_principles],
|
|
310
|
+
"resolved_contradictions": [c.to_dict() for c in synthesis.resolved_contradictions],
|
|
311
|
+
"initial_contradiction_score": synthesis.initial_contradiction_score,
|
|
312
|
+
"residual_contradiction_score": synthesis.residual_contradiction_score,
|
|
313
|
+
"recommendations": recommendations,
|
|
314
|
+
"timestamp": utc_now(),
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
with self._lock:
|
|
318
|
+
if candidate is not None:
|
|
319
|
+
candidate.metadata["triz_repair_recommendation"] = triz_payload
|
|
320
|
+
self.triz_repair_recommendations.append(triz_payload)
|
|
321
|
+
self._event(
|
|
322
|
+
"triz_repair_recommendation",
|
|
323
|
+
candidate_id=candidate_id,
|
|
324
|
+
synthesis_id=synthesis.synthesis_id,
|
|
325
|
+
residual_score=synthesis.residual_contradiction_score,
|
|
326
|
+
)
|
|
327
|
+
return triz_payload
|
|
328
|
+
|
|
329
|
+
def register_candidate(self, candidate: Candidate) -> None:
|
|
330
|
+
with self._lock:
|
|
331
|
+
if candidate.session_id != self.session_id:
|
|
332
|
+
raise ValueError("candidate belongs to a different session")
|
|
333
|
+
if candidate.candidate_id in self.candidates:
|
|
334
|
+
raise ValueError(f"duplicate candidate: {candidate.candidate_id}")
|
|
335
|
+
missing = [rid for rid in candidate.receipt_ids if rid not in self.receipts]
|
|
336
|
+
if missing:
|
|
337
|
+
raise ValueError(f"candidate references unknown receipts: {missing}")
|
|
338
|
+
missing_evidence = [eid for eid in candidate.evidence_ids if eid not in self.evidence]
|
|
339
|
+
if missing_evidence:
|
|
340
|
+
raise ValueError(f"candidate references unknown evidence: {missing_evidence}")
|
|
341
|
+
# Keep a private snapshot so callers cannot mutate an artifact or
|
|
342
|
+
# metadata after it enters the auditable run.
|
|
343
|
+
stored = replace(
|
|
344
|
+
candidate,
|
|
345
|
+
artifact=copy.deepcopy(candidate.artifact),
|
|
346
|
+
metadata=copy.deepcopy(dict(candidate.metadata)),
|
|
347
|
+
)
|
|
348
|
+
# System 3 Integration: Compute/track Friston Free Energy F, Kripke invariants, Hyperbolic tree embeddings
|
|
349
|
+
self._evaluate_system3_for_candidate(stored)
|
|
350
|
+
self.candidates[candidate.candidate_id] = stored
|
|
351
|
+
self._event("candidate_registered", candidate_id=candidate.candidate_id)
|
|
352
|
+
|
|
353
|
+
def attach_evidence(self, evidence: Evidence) -> None:
|
|
354
|
+
with self._lock:
|
|
355
|
+
self._validate_evidence(evidence)
|
|
356
|
+
if evidence.evidence_id in self.evidence:
|
|
357
|
+
raise ValueError(f"duplicate evidence: {evidence.evidence_id}")
|
|
358
|
+
self.evidence[evidence.evidence_id] = evidence
|
|
359
|
+
self._event("evidence_attached", evidence_id=evidence.evidence_id,
|
|
360
|
+
receipt_id=evidence.receipt_id)
|
|
361
|
+
|
|
362
|
+
def _validate_evidence(self, evidence: Evidence) -> None:
|
|
363
|
+
if evidence.session_id != self.session_id:
|
|
364
|
+
raise ValueError("evidence belongs to a different session")
|
|
365
|
+
receipt = self.receipts.get(evidence.receipt_id)
|
|
366
|
+
if receipt is None:
|
|
367
|
+
raise ValueError("evidence must reference a known tool receipt")
|
|
368
|
+
if not receipt.success:
|
|
369
|
+
raise ValueError("evidence cannot be anchored to a failed tool call")
|
|
370
|
+
if evidence.source_output_hash != receipt.output_hash:
|
|
371
|
+
raise PermissionError("evidence is not bound to the receipt output hash")
|
|
372
|
+
if evidence.content_hash != receipt.output_hash:
|
|
373
|
+
raise PermissionError("evidence content hash does not match receipt output hash")
|
|
374
|
+
|
|
375
|
+
def _candidate_graph_hash(self, candidate: Candidate) -> str:
|
|
376
|
+
"""Commit to a candidate and every receipt/evidence object it references."""
|
|
377
|
+
receipts = []
|
|
378
|
+
for receipt_id in candidate.receipt_ids:
|
|
379
|
+
receipt = self.receipts.get(receipt_id)
|
|
380
|
+
if receipt is None:
|
|
381
|
+
raise ValueError("candidate references an unknown receipt")
|
|
382
|
+
receipts.append({
|
|
383
|
+
"receipt_id": receipt_id,
|
|
384
|
+
"object_hash": canonical_hash(receipt.to_dict()),
|
|
385
|
+
})
|
|
386
|
+
evidence = []
|
|
387
|
+
for evidence_id in candidate.evidence_ids:
|
|
388
|
+
item = self.evidence.get(evidence_id)
|
|
389
|
+
if item is None:
|
|
390
|
+
raise ValueError("candidate references unknown evidence")
|
|
391
|
+
evidence.append({
|
|
392
|
+
"evidence_id": evidence_id,
|
|
393
|
+
"object_hash": canonical_hash(item.to_dict()),
|
|
394
|
+
})
|
|
395
|
+
return canonical_hash({
|
|
396
|
+
"candidate": candidate.to_dict(),
|
|
397
|
+
"receipts": receipts,
|
|
398
|
+
"evidence": evidence,
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
def _validate_attested_verification(self, result: VerificationResult) -> None:
|
|
402
|
+
"""Validate every immutable verdict field and its candidate binding."""
|
|
403
|
+
if result.session_id != self.session_id:
|
|
404
|
+
raise ValueError("verification belongs to a different session")
|
|
405
|
+
candidate = self.candidates.get(result.candidate_id)
|
|
406
|
+
if candidate is None:
|
|
407
|
+
raise ValueError("verification references an unknown candidate")
|
|
408
|
+
if not result.inspected_candidate:
|
|
409
|
+
raise PermissionError("verification must be produced by an executed verifier")
|
|
410
|
+
if result.trust_boundary not in VerificationPolicy.TRUST_BOUNDARY_RANK:
|
|
411
|
+
raise PermissionError("verification has no recognized trust boundary")
|
|
412
|
+
if result.candidate_hash != canonical_hash(candidate.artifact):
|
|
413
|
+
raise PermissionError("verification was not produced for the current candidate artifact")
|
|
414
|
+
expected_graph = self._candidate_graph_hash(candidate)
|
|
415
|
+
if not result.candidate_graph_hash or not hmac.compare_digest(
|
|
416
|
+
result.candidate_graph_hash, expected_graph
|
|
417
|
+
):
|
|
418
|
+
raise PermissionError("verification is not bound to the current candidate dependency graph")
|
|
419
|
+
expected = self._attestation(result)
|
|
420
|
+
if not hmac.compare_digest(result.runtime_attestation, expected):
|
|
421
|
+
raise PermissionError("verification has no valid runtime attestation")
|
|
422
|
+
unknown_evidence = [eid for eid in result.evidence_ids if eid not in self.evidence]
|
|
423
|
+
if unknown_evidence:
|
|
424
|
+
raise ValueError(f"verification references unknown evidence: {unknown_evidence}")
|
|
425
|
+
candidate_evidence = set(candidate.evidence_ids)
|
|
426
|
+
unrelated_evidence = [eid for eid in result.evidence_ids if eid not in candidate_evidence]
|
|
427
|
+
if unrelated_evidence:
|
|
428
|
+
raise PermissionError(
|
|
429
|
+
"verification evidence is not attached to the verified candidate: "
|
|
430
|
+
+ ", ".join(unrelated_evidence)
|
|
431
|
+
)
|
|
432
|
+
if result.passed and not result.evidence_ids:
|
|
433
|
+
raise PermissionError("a passing verification must cite candidate evidence")
|
|
434
|
+
|
|
435
|
+
def _record_attested_verification(self, result: VerificationResult) -> None:
|
|
436
|
+
"""Store a result after ``execute_verifier`` has attested it."""
|
|
437
|
+
if result.session_id != self.session_id:
|
|
438
|
+
raise ValueError("verification belongs to a different session")
|
|
439
|
+
if result.verification_id in self.verifications:
|
|
440
|
+
raise ValueError(f"duplicate verification: {result.verification_id}")
|
|
441
|
+
if any(v.candidate_id == result.candidate_id and v.verifier == result.verifier
|
|
442
|
+
for v in self.verifications.values()):
|
|
443
|
+
raise ValueError("verifier already produced a result for this candidate")
|
|
444
|
+
self._validate_attested_verification(result)
|
|
445
|
+
self.state = RunState.VERIFYING
|
|
446
|
+
self.verifications[result.verification_id] = result
|
|
447
|
+
self._event("verification_recorded", verification_id=result.verification_id,
|
|
448
|
+
candidate_id=result.candidate_id, verifier=result.verifier,
|
|
449
|
+
verifier_class=result.verifier_class, passed=result.passed)
|
|
450
|
+
|
|
451
|
+
def record_verification(self, result: VerificationResult) -> None:
|
|
452
|
+
"""Reject model-supplied or otherwise unattested results.
|
|
453
|
+
|
|
454
|
+
Results must come from ``execute_verifier`` so the runtime can bind the
|
|
455
|
+
verdict to an in-process verifier invocation and the exact candidate
|
|
456
|
+
artifact. This is an integrity boundary, not a process trust boundary.
|
|
457
|
+
"""
|
|
458
|
+
raise PermissionError(
|
|
459
|
+
"direct verification recording is disabled; execute a registered verifier"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def _attestation(self, result: VerificationResult) -> str:
|
|
463
|
+
"""MAC every immutable verdict field, excluding the MAC itself."""
|
|
464
|
+
payload = result.to_dict()
|
|
465
|
+
payload.pop("runtime_attestation", None)
|
|
466
|
+
digest = canonical_hash(payload).encode("utf-8")
|
|
467
|
+
return hmac.new(self._attestation_secret, digest, hashlib.sha256).hexdigest()
|
|
468
|
+
|
|
469
|
+
def execute_verifier(self, verifier: RegisteredVerifier, candidate_id: str) -> VerificationResult:
|
|
470
|
+
"""Run and attest an in-process verifier against one exact candidate.
|
|
471
|
+
|
|
472
|
+
The runtime binds the result to the invocation and artifact. The
|
|
473
|
+
caller's in-process code is still within the same trust domain; a
|
|
474
|
+
stronger boundary requires an isolated broker result.
|
|
475
|
+
"""
|
|
476
|
+
candidate = self.candidates.get(candidate_id)
|
|
477
|
+
if candidate is None:
|
|
478
|
+
raise ValueError(f"unknown candidate: {candidate_id}")
|
|
479
|
+
verifier_name = str(getattr(verifier, "name", "")).strip()
|
|
480
|
+
if not verifier_name:
|
|
481
|
+
raise ValueError("registered verifier must declare a name")
|
|
482
|
+
if verifier_name in self.invalidated_verifiers:
|
|
483
|
+
raise PermissionError(f"verifier is invalidated: {verifier_name}")
|
|
484
|
+
# In-process verifier objects are application-level declarations only.
|
|
485
|
+
# A process-attested result must arrive from an isolated broker path;
|
|
486
|
+
# this method deliberately refuses to stamp that stronger boundary.
|
|
487
|
+
trust_boundary = str(getattr(verifier, "trust_boundary", "")).strip()
|
|
488
|
+
if trust_boundary != "in_process":
|
|
489
|
+
raise PermissionError(
|
|
490
|
+
"in-process verifier execution cannot claim a process-attested boundary"
|
|
491
|
+
)
|
|
492
|
+
verifier_class = str(getattr(verifier, "verifier_class", "")).strip()
|
|
493
|
+
if not verifier_class:
|
|
494
|
+
raise ValueError("registered verifier must declare verifier_class")
|
|
495
|
+
# Objective checks must establish a baseline before an independent
|
|
496
|
+
# judge is allowed to approve the candidate. This prevents a model
|
|
497
|
+
# judge from becoming the first and only line of defense.
|
|
498
|
+
deterministic_classes = {"deterministic", "machine-check"}
|
|
499
|
+
required_deterministic = deterministic_classes & set(
|
|
500
|
+
self.task.verification_policy.required_verifier_classes
|
|
501
|
+
)
|
|
502
|
+
passed_classes = {v.verifier_class for v in self.passed_verifications(candidate_id)}
|
|
503
|
+
if (bool(getattr(verifier, "independent", False))
|
|
504
|
+
and required_deterministic - passed_classes):
|
|
505
|
+
raise PermissionError(
|
|
506
|
+
"independent verification must run after passing deterministic "
|
|
507
|
+
"verification: " + ", ".join(sorted(required_deterministic - passed_classes))
|
|
508
|
+
)
|
|
509
|
+
raw = verifier.verify(candidate)
|
|
510
|
+
if raw.candidate_id != candidate_id or raw.session_id != self.session_id:
|
|
511
|
+
raise ValueError("verifier returned a result for the wrong session or candidate")
|
|
512
|
+
if not raw.passed:
|
|
513
|
+
reasons = list(raw.reasons) if raw.reasons else ["Verifier rejected candidate"]
|
|
514
|
+
self._generate_triz_repair_recommendation(candidate_id, reasons)
|
|
515
|
+
result = replace(
|
|
516
|
+
raw,
|
|
517
|
+
verifier=verifier_name or raw.verifier,
|
|
518
|
+
verifier_class=verifier_class,
|
|
519
|
+
candidate_hash=canonical_hash(candidate.artifact),
|
|
520
|
+
inspected_candidate=True,
|
|
521
|
+
independent=bool(getattr(verifier, "independent", False)),
|
|
522
|
+
trust_boundary=trust_boundary,
|
|
523
|
+
candidate_graph_hash=self._candidate_graph_hash(candidate),
|
|
524
|
+
)
|
|
525
|
+
result = replace(result, runtime_attestation=self._attestation(result))
|
|
526
|
+
self._record_attested_verification(result)
|
|
527
|
+
return result
|
|
528
|
+
|
|
529
|
+
def invalidate_verifier(self, verifier: str, reason: str) -> None:
|
|
530
|
+
"""Revoke a verifier's authority for future finalization decisions."""
|
|
531
|
+
if not verifier or not verifier.strip() or not reason or not reason.strip():
|
|
532
|
+
raise ValueError("verifier and reason must be non-empty")
|
|
533
|
+
with self._lock:
|
|
534
|
+
self.invalidated_verifiers[verifier.strip()] = reason.strip()
|
|
535
|
+
self._event("verifier_invalidated", verifier=verifier.strip(),
|
|
536
|
+
reason=reason.strip())
|
|
537
|
+
|
|
538
|
+
def successful_capabilities(self, candidate_id: str | None = None) -> set[str]:
|
|
539
|
+
"""Return successful capabilities, scoped to a candidate when given."""
|
|
540
|
+
if candidate_id is None:
|
|
541
|
+
receipts = self.receipts.values()
|
|
542
|
+
else:
|
|
543
|
+
candidate = self.candidates.get(candidate_id)
|
|
544
|
+
if candidate is None:
|
|
545
|
+
raise ValueError(f"unknown candidate: {candidate_id}")
|
|
546
|
+
receipts = (self.receipts[receipt_id] for receipt_id in candidate.receipt_ids)
|
|
547
|
+
return {receipt.capability for receipt in receipts if receipt.success}
|
|
548
|
+
|
|
549
|
+
def missing_requirements(self, candidate_id: str | None = None) -> list[str]:
|
|
550
|
+
missing: list[str] = []
|
|
551
|
+
used = self.successful_capabilities(candidate_id)
|
|
552
|
+
for capability in self.task.required_capabilities:
|
|
553
|
+
if capability not in used:
|
|
554
|
+
missing.append(f"required capability not completed: {capability}")
|
|
555
|
+
|
|
556
|
+
if self.task.required_evidence:
|
|
557
|
+
candidate_evidence = set()
|
|
558
|
+
if candidate_id and candidate_id in self.candidates:
|
|
559
|
+
candidate_evidence = set(self.candidates[candidate_id].evidence_ids)
|
|
560
|
+
for kind in self.task.required_evidence:
|
|
561
|
+
if not any(e.kind == kind and e.evidence_id in candidate_evidence
|
|
562
|
+
for e in self.evidence.values()):
|
|
563
|
+
missing.append(f"required evidence not attached: {kind}")
|
|
564
|
+
return missing
|
|
565
|
+
|
|
566
|
+
def passed_verifications(self, candidate_id: str) -> list[VerificationResult]:
|
|
567
|
+
return [v for v in self.verifications.values()
|
|
568
|
+
if v.candidate_id == candidate_id and v.passed
|
|
569
|
+
and v.inspected_candidate
|
|
570
|
+
and v.trust_boundary in VerificationPolicy.TRUST_BOUNDARY_RANK
|
|
571
|
+
and v.verifier not in self.invalidated_verifiers]
|
|
572
|
+
|
|
573
|
+
def verification_requirements(self, candidate_id: str) -> list[str]:
|
|
574
|
+
"""Return missing policy requirements for the exact candidate."""
|
|
575
|
+
passed = self.passed_verifications(candidate_id)
|
|
576
|
+
policy = self.task.verification_policy
|
|
577
|
+
classes = {v.verifier_class for v in passed}
|
|
578
|
+
missing = [
|
|
579
|
+
f"required verifier class not passed: {kind}"
|
|
580
|
+
for kind in policy.required_verifier_classes
|
|
581
|
+
if kind not in classes
|
|
582
|
+
]
|
|
583
|
+
if len(passed) < policy.minimum_passing_verifiers:
|
|
584
|
+
missing.append(
|
|
585
|
+
f"requires {policy.minimum_passing_verifiers} passing verifiers "
|
|
586
|
+
f"(currently {len(passed)})"
|
|
587
|
+
)
|
|
588
|
+
if policy.require_independent and not any(v.independent for v in passed):
|
|
589
|
+
missing.append("requires a passing independently registered verifier")
|
|
590
|
+
boundary_rank = VerificationPolicy.TRUST_BOUNDARY_RANK[policy.minimum_trust_boundary]
|
|
591
|
+
if not any(VerificationPolicy.TRUST_BOUNDARY_RANK[v.trust_boundary] >= boundary_rank
|
|
592
|
+
for v in passed):
|
|
593
|
+
missing.append(
|
|
594
|
+
"requires a passing verifier at trust boundary "
|
|
595
|
+
+ policy.minimum_trust_boundary
|
|
596
|
+
)
|
|
597
|
+
return missing
|
|
598
|
+
|
|
599
|
+
def record_file_change(self, change: FileChangeRecord) -> None:
|
|
600
|
+
"""Record a file modification, creation, or deletion with cryptographic hash tracking."""
|
|
601
|
+
with self._lock:
|
|
602
|
+
self.file_changes.append(change)
|
|
603
|
+
self._event(
|
|
604
|
+
"file_change_recorded",
|
|
605
|
+
file_path=change.file_path,
|
|
606
|
+
change_type=change.change_type,
|
|
607
|
+
before_hash=change.before_hash,
|
|
608
|
+
after_hash=change.after_hash,
|
|
609
|
+
diff_summary=change.diff_summary,
|
|
610
|
+
rationale=change.rationale,
|
|
611
|
+
affected_invariants=list(change.affected_invariants),
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
def register_visual_mockup(self, mockup: VisualMockupSpec) -> None:
|
|
615
|
+
"""Register a visual design mockup specification and layout coordinates."""
|
|
616
|
+
with self._lock:
|
|
617
|
+
if any(m.mockup_id == mockup.mockup_id for m in self.visual_mockups):
|
|
618
|
+
raise ValueError(f"duplicate visual mockup: {mockup.mockup_id}")
|
|
619
|
+
self.visual_mockups.append(mockup)
|
|
620
|
+
self._event(
|
|
621
|
+
"visual_mockup_registered",
|
|
622
|
+
mockup_id=mockup.mockup_id,
|
|
623
|
+
concept_name=mockup.concept_name,
|
|
624
|
+
aesthetic_archetype=mockup.aesthetic_archetype,
|
|
625
|
+
status=mockup.status,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
def update_model_velocity(self, profile: ModelVelocityProfile) -> None:
|
|
629
|
+
"""Update model throughput, latency, and exploration multiplier telemetry."""
|
|
630
|
+
with self._lock:
|
|
631
|
+
self.model_velocity = profile
|
|
632
|
+
self._event(
|
|
633
|
+
"model_velocity_updated",
|
|
634
|
+
model_tier=profile.model_tier,
|
|
635
|
+
tokens_per_sec=profile.tokens_per_sec,
|
|
636
|
+
avg_tool_latency_sec=profile.avg_tool_latency_sec,
|
|
637
|
+
exploration_multiplier=profile.exploration_multiplier,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
def _gate_report(self, candidate_id: str | None = None) -> dict[str, Any]:
|
|
641
|
+
"""Run the DeterministicProofValidator across candidates, receipts, and attached evidence."""
|
|
642
|
+
with self._lock:
|
|
643
|
+
target_candidates = (
|
|
644
|
+
[self.candidates[candidate_id]]
|
|
645
|
+
if candidate_id and candidate_id in self.candidates
|
|
646
|
+
else list(self.candidates.values())
|
|
647
|
+
)
|
|
648
|
+
validated_proofs: list[dict[str, Any]] = []
|
|
649
|
+
all_passed = True
|
|
650
|
+
min_confidence = 1.0
|
|
651
|
+
|
|
652
|
+
# 1. Validate evidence integrity and anti-tautology
|
|
653
|
+
for ev in self.evidence.values():
|
|
654
|
+
if ev.receipt_id in self.receipts:
|
|
655
|
+
rcpt = self.receipts[ev.receipt_id]
|
|
656
|
+
rcpt_val = self.proof_validator.validate_tool_receipt(
|
|
657
|
+
receipt=rcpt,
|
|
658
|
+
session_receipts=self.receipts,
|
|
659
|
+
claim=ev.claim,
|
|
660
|
+
)
|
|
661
|
+
if not rcpt_val.passed:
|
|
662
|
+
all_passed = False
|
|
663
|
+
min_confidence = min(min_confidence, rcpt_val.confidence)
|
|
664
|
+
validated_proofs.append(rcpt_val.to_dict())
|
|
665
|
+
|
|
666
|
+
# Check claim itself is not tautological
|
|
667
|
+
taut_ok, taut_msg = self.proof_validator.check_anti_tautology(ev.claim, ev.claim)
|
|
668
|
+
if not taut_ok:
|
|
669
|
+
all_passed = False
|
|
670
|
+
min_confidence = 0.0
|
|
671
|
+
validated_proofs.append({
|
|
672
|
+
"passed": False,
|
|
673
|
+
"confidence": 0.0,
|
|
674
|
+
"proof_type": "anti_tautology",
|
|
675
|
+
"details": f"Evidence '{ev.evidence_id}': {taut_msg}",
|
|
676
|
+
"proof_receipt": None,
|
|
677
|
+
"metadata": {"evidence_id": ev.evidence_id},
|
|
678
|
+
})
|
|
679
|
+
else:
|
|
680
|
+
taut_ok, taut_msg = self.proof_validator.check_anti_tautology(str(ev.content), ev.claim)
|
|
681
|
+
if not taut_ok:
|
|
682
|
+
all_passed = False
|
|
683
|
+
min_confidence = 0.0
|
|
684
|
+
validated_proofs.append({
|
|
685
|
+
"passed": False,
|
|
686
|
+
"confidence": 0.0,
|
|
687
|
+
"proof_type": "anti_tautology",
|
|
688
|
+
"details": f"Evidence '{ev.evidence_id}': {taut_msg}",
|
|
689
|
+
"proof_receipt": None,
|
|
690
|
+
"metadata": {"evidence_id": ev.evidence_id},
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
# 2. Validate candidate artifacts (AST if python code)
|
|
694
|
+
for cand in target_candidates:
|
|
695
|
+
if isinstance(cand.artifact, dict) and "code" in cand.artifact:
|
|
696
|
+
ast_val = self.proof_validator.validate_ast(
|
|
697
|
+
code_or_path=cand.artifact["code"],
|
|
698
|
+
claim=f"Candidate {cand.candidate_id} AST validation",
|
|
699
|
+
)
|
|
700
|
+
if not ast_val.passed:
|
|
701
|
+
all_passed = False
|
|
702
|
+
min_confidence = min(min_confidence, ast_val.confidence)
|
|
703
|
+
validated_proofs.append(ast_val.to_dict())
|
|
704
|
+
elif isinstance(cand.artifact, str) and (cand.artifact.endswith(".py") or "\ndef " in cand.artifact or "\nclass " in cand.artifact):
|
|
705
|
+
ast_val = self.proof_validator.validate_ast(
|
|
706
|
+
code_or_path=cand.artifact,
|
|
707
|
+
claim=f"Candidate {cand.candidate_id} AST validation",
|
|
708
|
+
)
|
|
709
|
+
if not ast_val.passed:
|
|
710
|
+
all_passed = False
|
|
711
|
+
min_confidence = min(min_confidence, ast_val.confidence)
|
|
712
|
+
validated_proofs.append(ast_val.to_dict())
|
|
713
|
+
|
|
714
|
+
missing = self.missing_requirements(candidate_id)
|
|
715
|
+
if candidate_id:
|
|
716
|
+
missing += self.verification_requirements(candidate_id)
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
"passed": all_passed and not missing,
|
|
720
|
+
"confidence": round(min_confidence, 4) if all_passed else 0.0,
|
|
721
|
+
"candidate_id": candidate_id,
|
|
722
|
+
"total_proofs_evaluated": len(validated_proofs),
|
|
723
|
+
"validated_proofs": validated_proofs,
|
|
724
|
+
"missing_requirements": missing,
|
|
725
|
+
"timestamp": utc_now(),
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
def finalize(self, candidate_id: str) -> Candidate:
|
|
729
|
+
if candidate_id not in self.candidates:
|
|
730
|
+
raise ValueError(f"unknown candidate: {candidate_id}")
|
|
731
|
+
gate_report = self._gate_report(candidate_id)
|
|
732
|
+
missing = self.missing_requirements(candidate_id) + self.verification_requirements(candidate_id)
|
|
733
|
+
if not gate_report["passed"] and not missing:
|
|
734
|
+
for p in gate_report["validated_proofs"]:
|
|
735
|
+
if not p.get("passed"):
|
|
736
|
+
missing.append(f"deterministic proof validation failed: {p.get('details')}")
|
|
737
|
+
if missing:
|
|
738
|
+
self._generate_triz_repair_recommendation(candidate_id, missing)
|
|
739
|
+
self.state = RunState.REJECTED
|
|
740
|
+
self._event("finalization_rejected", candidate_id=candidate_id, missing=missing)
|
|
741
|
+
raise PermissionError("finalization rejected: " + "; ".join(missing))
|
|
742
|
+
self.final_candidate_id = candidate_id
|
|
743
|
+
self.state = RunState.FINALIZED
|
|
744
|
+
self._event("run_finalized", candidate_id=candidate_id)
|
|
745
|
+
return self.candidates[candidate_id]
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def run_system3_meta_cycle(self, candidate_id: str) -> dict[str, Any]:
|
|
749
|
+
"""Execute a full System 3 meta-cognitive reflection cycle for a candidate."""
|
|
750
|
+
with self._lock:
|
|
751
|
+
candidate = self.candidates.get(candidate_id)
|
|
752
|
+
if candidate is None:
|
|
753
|
+
raise ValueError(f"unknown candidate: {candidate_id}")
|
|
754
|
+
|
|
755
|
+
# 1. Active Inference Free Energy evaluation
|
|
756
|
+
pomdp_model = create_default_architecture_pomdp()
|
|
757
|
+
fe_engine = ActiveInferenceEngine(pomdp_model)
|
|
758
|
+
observation = "HIGH_THROUGHPUT_CLEAN" if all(
|
|
759
|
+
self.receipts[r].success for r in candidate.receipt_ids if r in self.receipts
|
|
760
|
+
) else "CHECKSUM_FAIL"
|
|
761
|
+
fe_policies = [Policy(policy_id=f"p_{act}", actions=[act]) for act in pomdp_model.actions]
|
|
762
|
+
fe_eval = fe_engine.select_action(observation, fe_policies)
|
|
763
|
+
fe_report = {
|
|
764
|
+
"variational_free_energy_f": round(fe_eval.variational_free_energy_f, 4),
|
|
765
|
+
"complexity_kl": round(fe_eval.complexity_kl, 4),
|
|
766
|
+
"accuracy_log_likelihood": round(fe_eval.accuracy_log_likelihood, 4),
|
|
767
|
+
"observation": observation,
|
|
768
|
+
"selected_policy": fe_eval.selected_action,
|
|
769
|
+
"evaluated_policies_count": len(fe_eval.evaluated_policies),
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
# 2. Kripke Modal Safety Invariant Model Checking
|
|
773
|
+
kripke = KripkeStructure()
|
|
774
|
+
kripke.add_world("w0", propositions={"init", "safe"})
|
|
775
|
+
kripke.add_world("w1", propositions={"executing", "safe"})
|
|
776
|
+
kripke.add_world("w2", propositions={"verified", "safe"})
|
|
777
|
+
kripke.add_transition("w0", "w1")
|
|
778
|
+
kripke.add_transition("w1", "w2")
|
|
779
|
+
kripke.add_transition("w2", "w2")
|
|
780
|
+
checker = KripkeModelChecker(kripke)
|
|
781
|
+
formula_res = checker.check("AG(safe)", "w0")
|
|
782
|
+
kripke_report = {
|
|
783
|
+
"formula": "AG(safe)",
|
|
784
|
+
"satisfied": formula_res.is_satisfied,
|
|
785
|
+
"initial_world": "w0",
|
|
786
|
+
"satisfying_worlds": sorted(list(formula_res.satisfied_worlds)),
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
# 3. Hyperbolic Tree Embedding
|
|
790
|
+
tree = {candidate_id: list(candidate.receipt_ids) + list(candidate.evidence_ids)}
|
|
791
|
+
for r in candidate.receipt_ids:
|
|
792
|
+
tree[r] = []
|
|
793
|
+
for e in candidate.evidence_ids:
|
|
794
|
+
tree[e] = []
|
|
795
|
+
if not tree[candidate_id]:
|
|
796
|
+
tree[candidate_id] = ["artifact_root"]
|
|
797
|
+
tree["artifact_root"] = []
|
|
798
|
+
embedder = HyperbolicTreeEmbedder(dimension=2, base_step_distance=1.0)
|
|
799
|
+
hyp_res = embedder.embed_hierarchy(tree, root_id=candidate_id)
|
|
800
|
+
hyp_report = {
|
|
801
|
+
"root_id": hyp_res.root_id,
|
|
802
|
+
"total_nodes": hyp_res.total_nodes,
|
|
803
|
+
"tree_depth": hyp_res.tree_depth,
|
|
804
|
+
"average_distortion": hyp_res.average_distortion,
|
|
805
|
+
"stress": hyp_res.stress,
|
|
806
|
+
"capacity_ratio": hyp_res.hierarchical_capacity_ratio,
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
# 4. Cognitive Bias Detection via System 3 Executive
|
|
810
|
+
bias_detector = CognitiveBiasDetector()
|
|
811
|
+
bias_findings = bias_detector.audit_session(
|
|
812
|
+
session_data={
|
|
813
|
+
"epistemic_ledger": [{"tag": "PROVEN", "claim": f"Candidate {candidate_id} registered"}],
|
|
814
|
+
"refinement_cycles": [{"refinement_type": "architectural", "focus_area": "system3"}],
|
|
815
|
+
"phase_history": [{"phase": self.state.value}],
|
|
816
|
+
}
|
|
817
|
+
)
|
|
818
|
+
bias_report = [b.to_dict() for b in bias_findings]
|
|
819
|
+
|
|
820
|
+
# 5. Tri-Level Arbitration
|
|
821
|
+
arbitrator = TriLevelArbitrator()
|
|
822
|
+
arbitration_res = arbitrator.arbitrate(
|
|
823
|
+
task_complexity=0.7,
|
|
824
|
+
contradiction_density=0.3,
|
|
825
|
+
failure_count=len(self.invalidated_verifiers),
|
|
826
|
+
epistemic_uncertainty=round(fe_eval.complexity_kl, 3),
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
# 6. Dialectical Synthesis
|
|
830
|
+
thesis = ThesisCandidate(
|
|
831
|
+
thesis_id=candidate_id,
|
|
832
|
+
title=f"Candidate {candidate_id}",
|
|
833
|
+
description=f"System 3 meta cycle for candidate {candidate_id}",
|
|
834
|
+
)
|
|
835
|
+
critique = AntithesisCritique(
|
|
836
|
+
critique_id=f"meta_crit_{candidate_id}",
|
|
837
|
+
thesis_id=candidate_id,
|
|
838
|
+
title="System 3 Dialectical Critique",
|
|
839
|
+
failure_modes=[],
|
|
840
|
+
severity_score=0.3,
|
|
841
|
+
)
|
|
842
|
+
synthesizer = DialecticalSynthesizer()
|
|
843
|
+
syn = synthesizer.synthesize(thesis, critique)
|
|
844
|
+
|
|
845
|
+
cycle_record = {
|
|
846
|
+
"candidate_id": candidate_id,
|
|
847
|
+
"timestamp": utc_now(),
|
|
848
|
+
"free_energy": fe_report,
|
|
849
|
+
"kripke_invariants": kripke_report,
|
|
850
|
+
"hyperbolic_embedding": hyp_report,
|
|
851
|
+
"bias_findings": bias_report,
|
|
852
|
+
"arbitration": arbitration_res.to_dict() if hasattr(arbitration_res, "to_dict") else dict(arbitration_res),
|
|
853
|
+
"dialectical_synthesis": syn.to_dict(),
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
self.system3_meta_cycles.append(cycle_record)
|
|
857
|
+
candidate.metadata["system3_meta_cycle"] = cycle_record
|
|
858
|
+
self._event("system3_meta_cycle_completed", candidate_id=candidate_id, f_val=fe_eval.variational_free_energy_f)
|
|
859
|
+
return cycle_record
|
|
860
|
+
|
|
861
|
+
def to_dict(self) -> dict[str, Any]:
|
|
862
|
+
"""Serialize a run for round-trip checkpointing."""
|
|
863
|
+
return {
|
|
864
|
+
"version": "2.0",
|
|
865
|
+
"session_id": self.session_id,
|
|
866
|
+
"task": self.task.to_dict(),
|
|
867
|
+
"state": self.state.value,
|
|
868
|
+
"started_at": self.started_at,
|
|
869
|
+
"receipts": [receipt.to_dict() for receipt in self.receipts.values()],
|
|
870
|
+
"candidates": [candidate.to_dict() for candidate in self.candidates.values()],
|
|
871
|
+
"evidence": [item.to_dict() for item in self.evidence.values()],
|
|
872
|
+
"verifications": [item.to_dict() for item in self.verifications.values()],
|
|
873
|
+
"events": copy.deepcopy(self.events),
|
|
874
|
+
"final_candidate_id": self.final_candidate_id,
|
|
875
|
+
"invalidated_verifiers": dict(self.invalidated_verifiers),
|
|
876
|
+
"system3_free_energy": copy.deepcopy(self.system3_free_energy),
|
|
877
|
+
"system3_kripke_invariants": copy.deepcopy(self.system3_kripke_invariants),
|
|
878
|
+
"system3_hyperbolic_embeddings": copy.deepcopy(self.system3_hyperbolic_embeddings),
|
|
879
|
+
"system3_meta_cycles": copy.deepcopy(self.system3_meta_cycles),
|
|
880
|
+
"triz_repair_recommendations": copy.deepcopy(self.triz_repair_recommendations),
|
|
881
|
+
"file_changes": [fc.to_dict() for fc in self.file_changes],
|
|
882
|
+
"visual_mockups": [vm.to_dict() for vm in self.visual_mockups],
|
|
883
|
+
"model_velocity": self.model_velocity.to_dict() if self.model_velocity else None,
|
|
884
|
+
# Production deployments should protect this with an external key
|
|
885
|
+
# store; it is included here so an in-memory checkpoint can be
|
|
886
|
+
# faithfully restored without silently trusting new signatures.
|
|
887
|
+
"attestation_secret": self._attestation_secret.hex(),
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
@classmethod
|
|
891
|
+
def from_dict(cls, data: dict[str, Any]) -> "FableRun":
|
|
892
|
+
"""Restore a run and reject tampered event history or payload hashes."""
|
|
893
|
+
task_data = dict(data["task"])
|
|
894
|
+
policy_data = dict(task_data.pop("verification_policy", {}))
|
|
895
|
+
task_data["constraints"] = tuple(task_data.get("constraints", ()))
|
|
896
|
+
task_data["definition_of_done"] = tuple(task_data.get("definition_of_done", ()))
|
|
897
|
+
task_data["required_capabilities"] = tuple(task_data.get("required_capabilities", ()))
|
|
898
|
+
task_data["required_evidence"] = tuple(task_data.get("required_evidence", ()))
|
|
899
|
+
task_data["verification_policy"] = VerificationPolicy(**policy_data)
|
|
900
|
+
run = cls(
|
|
901
|
+
session_id=data["session_id"],
|
|
902
|
+
task=TaskSpec(**task_data),
|
|
903
|
+
state=RunState(data.get("state", RunState.CREATED.value)),
|
|
904
|
+
started_at=data.get("started_at", utc_now()),
|
|
905
|
+
)
|
|
906
|
+
secret = data.get("attestation_secret")
|
|
907
|
+
if secret:
|
|
908
|
+
run._attestation_secret = bytes.fromhex(secret)
|
|
909
|
+
run.receipts = {}
|
|
910
|
+
for item in data.get("receipts", []):
|
|
911
|
+
receipt = ToolReceipt(**item)
|
|
912
|
+
if receipt.session_id != run.session_id:
|
|
913
|
+
raise ValueError("restored tool receipt belongs to a different session")
|
|
914
|
+
if receipt.receipt_id in run.receipts:
|
|
915
|
+
raise ValueError("duplicate restored tool receipt")
|
|
916
|
+
run.receipts[receipt.receipt_id] = receipt
|
|
917
|
+
run.candidates = {}
|
|
918
|
+
for item in data.get("candidates", []):
|
|
919
|
+
candidate = Candidate(
|
|
920
|
+
**{**item,
|
|
921
|
+
"receipt_ids": tuple(item.get("receipt_ids", ())),
|
|
922
|
+
"evidence_ids": tuple(item.get("evidence_ids", ()))})
|
|
923
|
+
if candidate.candidate_id in run.candidates:
|
|
924
|
+
raise ValueError("duplicate restored candidate")
|
|
925
|
+
run.candidates[candidate.candidate_id] = candidate
|
|
926
|
+
for candidate in run.candidates.values():
|
|
927
|
+
if any(receipt_id not in run.receipts for receipt_id in candidate.receipt_ids):
|
|
928
|
+
raise ValueError("candidate references an unknown restored receipt")
|
|
929
|
+
run.evidence = {}
|
|
930
|
+
for item in data.get("evidence", []):
|
|
931
|
+
evidence = Evidence(**item)
|
|
932
|
+
run._validate_evidence(evidence)
|
|
933
|
+
if evidence.evidence_id in run.evidence:
|
|
934
|
+
raise ValueError("duplicate restored evidence")
|
|
935
|
+
run.evidence[evidence.evidence_id] = evidence
|
|
936
|
+
for candidate in run.candidates.values():
|
|
937
|
+
if any(evidence_id not in run.evidence for evidence_id in candidate.evidence_ids):
|
|
938
|
+
raise ValueError("candidate references an unknown restored evidence item")
|
|
939
|
+
run.verifications = {}
|
|
940
|
+
seen_verifier_candidates: set[tuple[str, str]] = set()
|
|
941
|
+
for item in data.get("verifications", []):
|
|
942
|
+
result = VerificationResult(
|
|
943
|
+
**{**item,
|
|
944
|
+
"reasons": tuple(item.get("reasons", ())),
|
|
945
|
+
"evidence_ids": tuple(item.get("evidence_ids", ()))})
|
|
946
|
+
pair = (result.verifier, result.candidate_id)
|
|
947
|
+
if pair in seen_verifier_candidates:
|
|
948
|
+
raise ValueError("duplicate restored verifier verdict")
|
|
949
|
+
run._validate_attested_verification(result)
|
|
950
|
+
run.verifications[result.verification_id] = result
|
|
951
|
+
seen_verifier_candidates.add(pair)
|
|
952
|
+
run.events = copy.deepcopy(data.get("events", []))
|
|
953
|
+
run.final_candidate_id = data.get("final_candidate_id")
|
|
954
|
+
run.invalidated_verifiers = dict(data.get("invalidated_verifiers", {}))
|
|
955
|
+
run.system3_free_energy = copy.deepcopy(data.get("system3_free_energy", {}))
|
|
956
|
+
run.system3_kripke_invariants = copy.deepcopy(data.get("system3_kripke_invariants", {}))
|
|
957
|
+
run.system3_hyperbolic_embeddings = copy.deepcopy(data.get("system3_hyperbolic_embeddings", {}))
|
|
958
|
+
run.system3_meta_cycles = copy.deepcopy(data.get("system3_meta_cycles", []))
|
|
959
|
+
run.triz_repair_recommendations = copy.deepcopy(data.get("triz_repair_recommendations", []))
|
|
960
|
+
run.file_changes = [
|
|
961
|
+
FileChangeRecord(
|
|
962
|
+
**{**fc, "affected_invariants": tuple(fc.get("affected_invariants", ()))}
|
|
963
|
+
)
|
|
964
|
+
for fc in data.get("file_changes", [])
|
|
965
|
+
]
|
|
966
|
+
run.visual_mockups = [
|
|
967
|
+
VisualMockupSpec(
|
|
968
|
+
**{**vm, "palette": tuple(vm.get("palette", ()))}
|
|
969
|
+
)
|
|
970
|
+
for vm in data.get("visual_mockups", [])
|
|
971
|
+
]
|
|
972
|
+
mv_data = data.get("model_velocity")
|
|
973
|
+
run.model_velocity = ModelVelocityProfile(**mv_data) if mv_data else None
|
|
974
|
+
run.validate_event_history()
|
|
975
|
+
return run
|
|
976
|
+
|
|
977
|
+
def status(self) -> dict[str, Any]:
|
|
978
|
+
return {
|
|
979
|
+
"session_id": self.session_id,
|
|
980
|
+
"task_id": self.task.task_id,
|
|
981
|
+
"state": self.state.value,
|
|
982
|
+
"receipts": len(self.receipts),
|
|
983
|
+
"candidates": len(self.candidates),
|
|
984
|
+
"evidence": len(self.evidence),
|
|
985
|
+
"verifications": len(self.verifications),
|
|
986
|
+
"file_changes": len(self.file_changes),
|
|
987
|
+
"visual_mockups": len(self.visual_mockups),
|
|
988
|
+
"model_velocity": self.model_velocity.to_dict() if self.model_velocity else None,
|
|
989
|
+
"successful_capabilities": sorted(self.successful_capabilities()),
|
|
990
|
+
"missing_requirements": (
|
|
991
|
+
self.missing_requirements(self.final_candidate_id)
|
|
992
|
+
+ (self.verification_requirements(self.final_candidate_id)
|
|
993
|
+
if self.final_candidate_id else [])
|
|
994
|
+
),
|
|
995
|
+
"verification_policy": self.task.verification_policy.to_dict(),
|
|
996
|
+
"final_candidate_id": self.final_candidate_id,
|
|
997
|
+
"system3_state": {
|
|
998
|
+
"free_energy_tracked": len(self.system3_free_energy),
|
|
999
|
+
"kripke_invariants_tracked": len(self.system3_kripke_invariants),
|
|
1000
|
+
"hyperbolic_embeddings_tracked": len(self.system3_hyperbolic_embeddings),
|
|
1001
|
+
"meta_cycles_count": len(self.system3_meta_cycles),
|
|
1002
|
+
"triz_repairs_count": len(self.triz_repair_recommendations),
|
|
1003
|
+
},
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def new_run(session_id: str, task: TaskSpec) -> FableRun:
|
|
1008
|
+
run = FableRun(session_id=session_id, task=task)
|
|
1009
|
+
run.start()
|
|
1010
|
+
return run
|