From c32d70807b140492fb925398b86c2ac0df2cf6bd Mon Sep 17 00:00:00 2001 From: Flamehaven Date: Wed, 19 Nov 2025 19:07:37 +0700 Subject: [PATCH 1/3] Add control plane registry and metrics exporter --- .gitignore | 2 + docs/SimUniverseLabBlueprint.md | 82 ++++++++ samples/omega_base_sample.json | 15 ++ samples/stage5_sample.json | 30 +++ scripts/build_simuniverse_trust_summary.py | 52 +++++ scripts/sync_simuniverse_trust_to_asdp.py | 60 ++++++ src/fastapi/__init__.py | 55 +++++ src/fastapi/responses.py | 12 ++ src/pydantic/__init__.py | 30 ++- src/rex/sidrce/__init__.py | 17 ++ src/rex/sidrce/cli.py | 50 +++++ src/rex/sidrce/omega_schema.py | 33 +++ .../sidrce/omega_simuniverse_integration.py | 159 ++++++++++++++ src/rex/sim_universe/__init__.py | 31 +++ src/rex/sim_universe/control_plane.py | 176 ++++++++++++++++ src/rex/sim_universe/dfi_meta_cli.py | 197 +++++++++++++++++ src/rex/sim_universe/metrics_exporter.py | 92 ++++++++ src/rex/sim_universe/registry.py | 168 +++++++++++++++ src/rex/sim_universe/reporting.py | 14 +- src/rex/sim_universe/stage5_loader.py | 86 ++++++++ src/rex/sim_universe/trust_integration.py | 198 ++++++++++++++++++ tests/test_control_plane.py | 43 ++++ tests/test_dfi_meta_cli.py | 64 ++++++ tests/test_metrics_exporter.py | 40 ++++ tests/test_registry.py | 39 ++++ tests/test_sidrce_integration.py | 140 +++++++++++++ tests/test_simuniverse_trust.py | 104 +++++++++ tests/test_stage5_loader.py | 44 ++++ 28 files changed, 2025 insertions(+), 8 deletions(-) create mode 100644 .gitignore create mode 100644 samples/omega_base_sample.json create mode 100644 samples/stage5_sample.json create mode 100644 scripts/build_simuniverse_trust_summary.py create mode 100644 scripts/sync_simuniverse_trust_to_asdp.py create mode 100644 src/fastapi/__init__.py create mode 100644 src/fastapi/responses.py create mode 100644 src/rex/sidrce/__init__.py create mode 100644 src/rex/sidrce/cli.py create mode 100644 src/rex/sidrce/omega_schema.py create mode 100644 src/rex/sidrce/omega_simuniverse_integration.py create mode 100644 src/rex/sim_universe/control_plane.py create mode 100644 src/rex/sim_universe/dfi_meta_cli.py create mode 100644 src/rex/sim_universe/metrics_exporter.py create mode 100644 src/rex/sim_universe/registry.py create mode 100644 src/rex/sim_universe/stage5_loader.py create mode 100644 src/rex/sim_universe/trust_integration.py create mode 100644 tests/test_control_plane.py create mode 100644 tests/test_dfi_meta_cli.py create mode 100644 tests/test_metrics_exporter.py create mode 100644 tests/test_registry.py create mode 100644 tests/test_sidrce_integration.py create mode 100644 tests/test_simuniverse_trust.py create mode 100644 tests/test_stage5_loader.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..270be45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +simuniverse_runs.db diff --git a/docs/SimUniverseLabBlueprint.md b/docs/SimUniverseLabBlueprint.md index 4987528..d17460c 100644 --- a/docs/SimUniverseLabBlueprint.md +++ b/docs/SimUniverseLabBlueprint.md @@ -60,4 +60,86 @@ Example `values.yaml` fragments (not exhaustive) set image tags, service ports, 2. Create a toy world via `/toe/world` then run spectral-gap and RG queries via `/toe/query` as shown in `scripts/run_toe_heatmap*.py`. 3. Inspect summaries in the returned payload: coverage, undecidability index, RG observables, and astro-driven energy feasibility. +## 8. SIDRCE Omega integration hooks +- `src/rex/sidrce/omega_schema.py`: Pydantic schema for Omega reports and axes, now treated as the authoritative spec for SIDRCE outputs. +- `src/rex/sidrce/omega_simuniverse_integration.py`: Aggregates MUH, Faizal, undecidability, and energy summaries into the `simuniverse_consistency` axis, renormalizes weights, and recalculates `omega_total`. +- `src/rex/sidrce/cli.py`: CLI wrapper that reads a base Omega JSON plus the Stage-5 SimUniverse trust summary and emits an updated report that GitOps gates and ASDP exporters can consume. + +Example usage: + +```bash +python -m rex.sidrce.cli \ + --omega-json sidrce_report_base.json \ + --simuniverse-trust-json artifacts/stage5_simuniverse/simuniverse_trust_summary_RUN.json \ + --out sidrce_report_with_simuniverse.json +``` + All narrative text, comments, and code in this repository are now English-only to avoid mixed-language confusion. + +## 9. Stage-5 trust summaries and registry updates +- `scripts/build_simuniverse_trust_summary.py`: Consumes a LawBinder Stage-5 JSON (or direct `simuniverse_summary` list), converts each entry into `ToeScenarioScores`, then emits per-TOE aggregates using `build_toe_trust_summary`. +- `rex.sim_universe.stage5_loader.load_stage5_scores`: Shared helper that parses Stage-5 payloads, extracts the embedded `run_id`, and normalizes list-style or dict-style JSON into `ToeScenarioScores` objects. +- `src/rex/sim_universe/trust_integration.py`: Hosts `ToeTrustSummary`, `serialize_trust_summaries`, and `update_registry_with_trust`. The default heuristic (`mu_min_good=0.4`, `faizal_max_good=0.7`) labels a TOE as low-trust when MUH support stays low while Faizal obstruction stays high across worlds. +- Registry automation flow: + 1. Stage-5 gate invokes the script: + + ```bash + python scripts/build_simuniverse_trust_summary.py \ + --stage5-json artifacts/stage5/LawBinderStage5Report.json \ + --run-id "$RUN_ID" \ + --out artifacts/stage5/simuniverse_trust_summary_$RUN_ID.json + ``` + + 2. A GitOps sidecar loads the resulting JSON and calls `update_registry_with_trust` so every `toe_candidate` entry gains `trust.simuniverse.{mu_score_avg,...,low_trust_flag}` plus the `simuniverse.low_trust` sovereign tag when appropriate. + 3. Meta-Router and downstream policies can now read `trust.tier` and `trust.simuniverse` without parsing the original Stage-5 artifact. + +## 10. Router scoring, gates, and trust-tier penalties +- `simuniverse_quality(mu, faizal)` implements the canonical `0.7 * mu + 0.3 * (1 - faizal)` formula so MUH-aligned TOE candidates rise while Faizal-obstructed ones fall. +- `route_omega(base_omega, sim_q, trust_tier)` blends the Stage-5 signal into the Omega score and applies tier penalties (`high=1.0`, `normal=0.9`, `unknown=0.8`, `low=0.6`). Use the result inside Meta-Router/Fusion-Orchestrator weighting functions. +- `compute_trust_tier_from_failures(prev_tier, failure_count, failure_threshold)` lets Prometheus counters such as `simuniverse_stage5_gate_failures_total{toe_candidate=*}` demote chronic offenders to low-trust. +- GitOps Gate DSL example, assuming the exporter publishes per-TOE metrics like `simuniverse_mu_score_avg` and `simuniverse_faizal_score_avg`: + + ```yaml + rules: + - 'IF metric("asdpi_omega_total") < 0.82 THEN warn("Omega below 0.82")' + - 'IF metric("simuniverse_mu_score_avg","toe_candidate","${TOE}") < 0.20 AND \ + metric("simuniverse_faizal_score_avg","toe_candidate","${TOE}") > 0.70 \ + THEN fail("SimUniverse gate failed for ${TOE}")' + - 'IF metric("asdpi_omega_axis","axis","simuniverse_consistency") >= 0.75 AND \ + metric("simuniverse_mu_score_avg","toe_candidate","${TOE}") >= 0.60 \ + THEN warn("SimUniverse strong candidate ${TOE}")' + ``` + +Together these helpers ensure that the Stage-5 experiment acts as a first-class governance signal across ASDP, Meta-Router, and FinOps surfaces. + +## 11. DFI meta CLI for end-to-end governance rehearsals +- `python -m rex.sim_universe.dfi_meta_cli` ties Stage-5 payloads, trust summaries, and Omega axes into a single “DFI meta” batch. + - Inputs: `--stage5-json`, `--omega-json`, `--out-dir`, optional `--run-id`, and knobs for MUH/Faizal heuristics or axis weights. + - Default behavior runs **30 iterations** to mimic repeated governance reviews, emitting `simuniverse_trust_summary_iter_XX.json`, `omega_with_simuniverse_iter_XX.json`, and a consolidated `dfi_meta_summary.json` verdict ("simulatable" vs "obstructed"). +- Run it with `PYTHONPATH=src` (or install the package) so the `rex.*` modules resolve without poetry/pip installs. +- Sample artifacts live under `samples/` so CI or humans can quickly sanity-check the loop: + +```bash +python -m rex.sim_universe.dfi_meta_cli \ + --stage5-json samples/stage5_sample.json \ + --omega-json samples/omega_base_sample.json \ + --out-dir artifacts/dfi_meta_demo \ + --iterations 30 +``` + +This meta CLI gives ASDP operators a reproducible way to rehearse the combined REx Engine → SimUniverse → LawBinder → Ω certification flow whenever new TOE evidence lands. + +## 12. Control plane, registry sync, and metrics exporter +- **Run registry (`rex.sim_universe.registry`)** keeps every SimUniverse execution keyed by `run_id`, environment, Git SHA, and experiment inputs. The helper stores `omega_total` plus `simuniverse_consistency` once a run finishes so dashboards and downstream automation can trace the provenance of every Omega verdict. +- **Control plane API (`rex.sim_universe.control_plane`)** exposes a FastAPI surface with `POST /runs`, `GET /runs/{run_id}`, and `GET /runs` endpoints. Internally it calls `run_stage5_for_run_id`, which executes the Stage-5 → trust summary → Omega merge loop, writes artifacts to `artifacts/simuniverse_runs//`, and updates the registry atomically. +- **ASDP registry sync (`scripts/sync_simuniverse_trust_to_asdp.py`)** consumes any `simuniverse_trust_summary.json` plus `asdp.knowledge.yaml`, applies `update_registry_with_trust`, and rewrites the registry so every TOE candidate gains `trust.simuniverse.*` metrics and the `simuniverse.low_trust` tag when needed: + + ```bash + python scripts/sync_simuniverse_trust_to_asdp.py \ + --registry asdp.knowledge.yaml \ + --trust artifacts/stage5/simuniverse_trust_summary_RUN.json + ``` + +- **Prometheus exporter (`rex/sim_universe/metrics_exporter.py`)** publishes gauges such as `simuniverse_mu_score_avg{toe_candidate=*}`, `simuniverse_faizal_score_avg{toe_candidate=*}`, `asdpi_omega_axis{axis="simuniverse_consistency"}`, and `asdpi_omega_total`. Deploy it next to the artifacts bucket or attach it to the same persistent volume used by Stage-5 so GitOps Gate DSL rules, Grafana, and alerting policies see real-time SimUniverse governance signals. + +Together these three pillars turn the conceptual Stage-5 workflow into an operational control plane: every run is registered, Omega/ASDP artifacts remain queryable, and governance signals stay synchronized across Meta-Router, FinOps, and certification dashboards. diff --git a/samples/omega_base_sample.json b/samples/omega_base_sample.json new file mode 100644 index 0000000..d913bb5 --- /dev/null +++ b/samples/omega_base_sample.json @@ -0,0 +1,15 @@ +{ + "omega_version": "8.1", + "tenant": "flamehaven", + "service": "rex-engine", + "run_id": "omega-demo-run", + "created_at": "2025-01-01T00:00:00Z", + "axes": { + "coverage": {"value": 0.91, "weight": 0.25}, + "robustness": {"value": 0.87, "weight": 0.25}, + "safety": {"value": 0.88, "weight": 0.25}, + "finops": {"value": 0.85, "weight": 0.25} + }, + "omega_total": 0.0, + "level": "fail" +} diff --git a/samples/stage5_sample.json b/samples/stage5_sample.json new file mode 100644 index 0000000..0aa7129 --- /dev/null +++ b/samples/stage5_sample.json @@ -0,0 +1,30 @@ +{ + "tenant": "flamehaven", + "service": "rex-simuniverse", + "stage": "stage5", + "run_id": "demo-stage5-run", + "simuniverse_summary": [ + { + "toe_candidate_id": "toe_candidate_muh_cuh", + "world_id": "world-muh-001", + "mu_score": 0.82, + "faizal_score": 0.28, + "coverage_alg": 0.88, + "mean_undecidability_index": 0.32, + "energy_feasibility": 0.91, + "rg_phase_index": 0.15, + "rg_halting_indicator": 0.85 + }, + { + "toe_candidate_id": "toe_candidate_faizal_mtoe", + "world_id": "world-faizal-042", + "mu_score": 0.24, + "faizal_score": 0.86, + "coverage_alg": 0.34, + "mean_undecidability_index": 0.81, + "energy_feasibility": 0.18, + "rg_phase_index": 0.92, + "rg_halting_indicator": 0.18 + } + ] +} diff --git a/scripts/build_simuniverse_trust_summary.py b/scripts/build_simuniverse_trust_summary.py new file mode 100644 index 0000000..6a90687 --- /dev/null +++ b/scripts/build_simuniverse_trust_summary.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from rex.sim_universe.stage5_loader import load_stage5_scores +from rex.sim_universe.trust_integration import ( + build_toe_trust_summary, + serialize_trust_summaries, +) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Build per-TOE SimUniverse trust summaries from Stage-5 results." + ) + parser.add_argument("--stage5-json", required=True, help="Path to the Stage-5 results JSON.") + parser.add_argument("--out", required=True, help="Path to write the trust summary JSON.") + parser.add_argument("--run-id", help="Optional run identifier to embed in the trust summary.") + parser.add_argument( + "--mu-min-good", + type=float, + default=0.4, + help="Minimum MUH score to avoid a low-trust designation.", + ) + parser.add_argument( + "--faizal-max-good", + type=float, + default=0.7, + help="Maximum Faizal score to avoid a low-trust designation.", + ) + args = parser.parse_args() + + stage5_path = Path(args.stage5_json) + stage5_payload = load_stage5_scores(stage5_path) + scores = stage5_payload.scores + run_id = args.run_id or stage5_payload.run_id + summaries = build_toe_trust_summary( + scores, + mu_min_good=args.mu_min_good, + faizal_max_good=args.faizal_max_good, + run_id=run_id, + ) + payload = serialize_trust_summaries(summaries) + + out_path = Path(args.out) + out_path.write_text(json.dumps(payload, indent=2), encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/scripts/sync_simuniverse_trust_to_asdp.py b/scripts/sync_simuniverse_trust_to_asdp.py new file mode 100644 index 0000000..63ef748 --- /dev/null +++ b/scripts/sync_simuniverse_trust_to_asdp.py @@ -0,0 +1,60 @@ +"""CLI for syncing SimUniverse trust summaries into the ASDP registry.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +try: # pragma: no cover - optional dependency + import yaml +except ModuleNotFoundError: # pragma: no cover - fallback path + yaml = None + +from rex.sim_universe.trust_integration import update_registry_with_trust + + +def load_trust_summary(path: Path) -> list[dict]: + return json.loads(path.read_text(encoding="utf-8")) + + +def load_registry(path: Path) -> dict: + text = path.read_text(encoding="utf-8") + if yaml is not None: + return yaml.safe_load(text) + return json.loads(text) + + +def write_registry(path: Path, data: dict) -> None: + if yaml is not None: + path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8") + else: + path.write_text(json.dumps(data, indent=2), encoding="utf-8") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Sync SimUniverse trust metrics into ASDP registry files.") + parser.add_argument("--registry", required=True, help="Path to asdp.knowledge.yaml or registry JSON.") + parser.add_argument("--trust", required=True, help="Path to simuniverse_trust_summary.json.") + parser.add_argument("--out", help="Optional output path. Defaults to --registry in-place update.") + args = parser.parse_args() + + registry_path = Path(args.registry) + trust_path = Path(args.trust) + out_path = Path(args.out) if args.out else registry_path + + registry_doc = load_registry(registry_path) + trust_summary = load_trust_summary(trust_path) + + updated = update_registry_with_trust( + registry_doc, + trust_summary, + fallback_run_id=trust_summary[0].get("run_id") if trust_summary else None, + ) + + write_registry(out_path, updated) + print(f"[sync] registry updated -> {out_path}") + + +if __name__ == "__main__": + main() diff --git a/src/fastapi/__init__.py b/src/fastapi/__init__.py new file mode 100644 index 0000000..0be52cb --- /dev/null +++ b/src/fastapi/__init__.py @@ -0,0 +1,55 @@ +"""Lightweight FastAPI stub for offline testing.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, List, Tuple + +from . import responses +from .responses import PlainTextResponse + + +class HTTPException(Exception): + def __init__(self, status_code: int, detail: str | None = None) -> None: + super().__init__(detail) + self.status_code = status_code + self.detail = detail + + +@dataclass +class _Route: + method: str + path: str + handler: Callable[..., Any] + + +class FastAPI: + """Minimal subset of FastAPI used for documentation and tests.""" + + def __init__(self, title: str | None = None) -> None: + self.title = title + self._routes: List[_Route] = [] + + def _register(self, method: str, path: str, handler: Callable[..., Any]) -> Callable[..., Any]: + self._routes.append(_Route(method=method.upper(), path=path, handler=handler)) + return handler + + def get(self, path: str, response_model: object | None = None, response_class: object | None = None): # noqa: D401 + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + return self._register("GET", path, func) + + return decorator + + def post(self, path: str, response_model: object | None = None, response_class: object | None = None): # noqa: D401 + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + return self._register("POST", path, func) + + return decorator + + # Minimal helpers for tests to inspect routes if desired + @property + def routes(self) -> Tuple[_Route, ...]: + return tuple(self._routes) + + +__all__ = ["FastAPI", "HTTPException", "PlainTextResponse", "responses"] diff --git a/src/fastapi/responses.py b/src/fastapi/responses.py new file mode 100644 index 0000000..585ade6 --- /dev/null +++ b/src/fastapi/responses.py @@ -0,0 +1,12 @@ +"""Responses module for the lightweight FastAPI stub.""" + +from __future__ import annotations + + +class PlainTextResponse: + def __init__(self, content: str, status_code: int = 200) -> None: + self.content = content + self.status_code = status_code + + +__all__ = ["PlainTextResponse"] diff --git a/src/pydantic/__init__.py b/src/pydantic/__init__.py index 55cfbb8..358713c 100644 --- a/src/pydantic/__init__.py +++ b/src/pydantic/__init__.py @@ -1,24 +1,52 @@ """Minimal pydantic stub for offline use.""" from __future__ import annotations +import json +from datetime import datetime from typing import Any +def _serialize(value: Any): + if isinstance(value, BaseModel): + return value.model_dump() + if isinstance(value, dict): + return {k: _serialize(v) for k, v in value.items()} + if isinstance(value, list): + return [_serialize(v) for v in value] + if isinstance(value, datetime): + return value.isoformat() + return value + + class BaseModel: def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def model_dump(self): - return self.__dict__.copy() + return {k: _serialize(v) for k, v in self.__dict__.items()} def model_dict(self): return self.model_dump() + def model_dump_json(self, **kwargs): + return json.dumps(self.model_dump(), **kwargs) + + def json(self, **kwargs): + return self.model_dump_json(**kwargs) + @classmethod def model_validate(cls, data: dict[str, Any]): return cls(**data) + @classmethod + def model_validate_json(cls, data: str): + return cls.model_validate(json.loads(data)) + + @classmethod + def parse_raw(cls, data: str): + return cls.model_validate_json(data) + def Field(default: Any = None, **_: Any): return default diff --git a/src/rex/sidrce/__init__.py b/src/rex/sidrce/__init__.py new file mode 100644 index 0000000..42bbe4d --- /dev/null +++ b/src/rex/sidrce/__init__.py @@ -0,0 +1,17 @@ +"""SIDRCE Omega integration utilities.""" + +from .omega_schema import OmegaAxis, OmegaReport, OmegaLevel # noqa: F401 +from .omega_simuniverse_integration import ( # noqa: F401 + apply_simuniverse_axis, + compute_simuniverse_consistency, + integrate_simuniverse_into_omega, +) + +__all__ = [ + "OmegaAxis", + "OmegaReport", + "OmegaLevel", + "apply_simuniverse_axis", + "compute_simuniverse_consistency", + "integrate_simuniverse_into_omega", +] diff --git a/src/rex/sidrce/cli.py b/src/rex/sidrce/cli.py new file mode 100644 index 0000000..e106593 --- /dev/null +++ b/src/rex/sidrce/cli.py @@ -0,0 +1,50 @@ +"""Command line entrypoint for SIDRCE Omega + SimUniverse integration.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from .omega_simuniverse_integration import integrate_simuniverse_into_omega + + +def main() -> None: + parser = argparse.ArgumentParser( + description="SIDRCE Omega + SimUniverse integration CLI.", + ) + parser.add_argument("--omega-json", required=True, help="Path to base Omega JSON report.") + parser.add_argument( + "--simuniverse-trust-json", + required=True, + help="Path to SimUniverse trust summary JSON.", + ) + parser.add_argument("--out", required=True, help="Path to write the integrated Omega report.") + parser.add_argument( + "--sim-weight", + type=float, + default=0.12, + help="Weight to assign to the simuniverse_consistency axis before renormalization.", + ) + parser.add_argument( + "--no-renormalize", + action="store_true", + help="Skip weight renormalization if set.", + ) + args = parser.parse_args() + + report = integrate_simuniverse_into_omega( + omega_path=args.omega_json, + simuniverse_trust_path=args.simuniverse_trust_json, + sim_weight=args.sim_weight, + renormalize=not args.no_renormalize, + ) + Path(args.out).write_text( + json.dumps(report.model_dump(), indent=2, default=str), + encoding="utf-8", + ) + + +if __name__ == "__main__": + main() + diff --git a/src/rex/sidrce/omega_schema.py b/src/rex/sidrce/omega_schema.py new file mode 100644 index 0000000..6e53ec0 --- /dev/null +++ b/src/rex/sidrce/omega_schema.py @@ -0,0 +1,33 @@ +"""Pydantic models for SIDRCE Omega reports.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Dict, Literal, Optional + +from pydantic import BaseModel, Field + + +class OmegaAxis(BaseModel): + """Represents a single Omega axis entry.""" + + value: float = Field(ge=0.0, le=1.0) + weight: float = Field(ge=0.0, le=1.0) + details: Optional[dict] = None + + +OmegaLevel = Literal["pass", "warn", "fail"] + + +class OmegaReport(BaseModel): + """Canonical structure for SIDRCE Omega reports.""" + + omega_version: str + tenant: str + service: str + run_id: str + created_at: datetime + axes: Dict[str, OmegaAxis] + omega_total: float = Field(ge=0.0, le=1.0) + level: OmegaLevel + diff --git a/src/rex/sidrce/omega_simuniverse_integration.py b/src/rex/sidrce/omega_simuniverse_integration.py new file mode 100644 index 0000000..04ff0f2 --- /dev/null +++ b/src/rex/sidrce/omega_simuniverse_integration.py @@ -0,0 +1,159 @@ +"""Utilities for merging SimUniverse metrics into SIDRCE Omega reports.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Dict, Iterable, List, Mapping, Sequence + +from .omega_schema import OmegaAxis, OmegaReport + + +SIMUNIVERSE_AXIS_NAME = "simuniverse_consistency" + + +def load_simuniverse_trust_summary(path: str) -> List[dict]: + """Load the SimUniverse trust summary JSON as a list of dicts.""" + + data = json.loads(Path(path).read_text(encoding="utf-8")) + if not isinstance(data, list): + raise ValueError("SimUniverse trust summary must be a list") + return data + + +def _safe_average(items: Iterable[Mapping[str, float]], key: str, default: float = 0.0) -> float: + values = [float(entry.get(key, default)) for entry in items] + if not values: + return default + return sum(values) / len(values) + + +def compute_simuniverse_consistency(trust_summary: List[Mapping[str, float]]) -> Dict[str, float]: + """Aggregate per-TOE SimUniverse metrics into a scalar axis value.""" + + if not trust_summary: + return { + "value": 0.0, + "mu_score_avg_global": 0.0, + "faizal_score_avg_global": 1.0, + "undecidability_avg_global": 0.0, + "energy_feasibility_avg_global": 0.0, + } + + mu_avg = _safe_average(trust_summary, "mu_score_avg") + faizal_avg = _safe_average(trust_summary, "faizal_score_avg", default=1.0) + u_avg = _safe_average(trust_summary, "undecidability_avg") + energy_avg = _safe_average(trust_summary, "energy_feasibility_avg") + + sim_q = 0.7 * mu_avg + 0.3 * (1.0 - faizal_avg) + sim_value = max(0.0, min(1.0, sim_q)) + + return { + "value": sim_value, + "mu_score_avg_global": mu_avg, + "faizal_score_avg_global": faizal_avg, + "undecidability_avg_global": u_avg, + "energy_feasibility_avg_global": energy_avg, + } + + +def _build_trust_tiers(trust_summary: List[Mapping[str, float]]) -> Dict[str, str]: + tiers: Dict[str, str] = {} + for entry in trust_summary: + toe_id = str(entry.get("toe_candidate_id", "unknown_toe")) + if entry.get("high_trust_flag"): + tier = "high" + elif entry.get("low_trust_flag"): + tier = "low" + else: + tier = "normal" + tiers[toe_id] = tier + return tiers + + +def _evaluate_omega_level(omega_total: float, sim_value: float) -> str: + if omega_total >= 0.85 and sim_value >= 0.75: + return "pass" + if omega_total >= 0.82 and sim_value >= 0.60: + return "warn" + return "fail" + + +def _normalize_trust_summary( + trust_summary: Sequence[Mapping[str, float] | object] +) -> List[Mapping[str, float]]: + normalized: List[Mapping[str, float]] = [] + for entry in trust_summary: + if hasattr(entry, "to_payload"): + payload = entry.to_payload() # type: ignore[attr-defined] + else: + payload = dict(entry) # type: ignore[arg-type] + normalized.append(payload) + return normalized + + +def apply_simuniverse_axis( + omega: OmegaReport, + trust_summary: Sequence[Mapping[str, float] | object], + *, + sim_weight: float = 0.12, + renormalize: bool = True, +) -> OmegaReport: + """Return a copy of ``omega`` enriched with the SimUniverse axis.""" + + report = OmegaReport.model_validate(omega.model_dump()) + normalized = _normalize_trust_summary(trust_summary) + sim_stats = compute_simuniverse_consistency(normalized) + + axes: Dict[str, OmegaAxis] = {} + for axis_name, axis_value in getattr(report, "axes", {}).items(): + if isinstance(axis_value, OmegaAxis): + axes[axis_name] = axis_value + else: + axes[axis_name] = OmegaAxis(**axis_value) + + axes[SIMUNIVERSE_AXIS_NAME] = OmegaAxis( + value=sim_stats["value"], + weight=sim_weight, + details={ + "mu_score_avg_global": sim_stats["mu_score_avg_global"], + "faizal_score_avg_global": sim_stats["faizal_score_avg_global"], + "undecidability_avg_global": sim_stats["undecidability_avg_global"], + "energy_feasibility_avg_global": sim_stats["energy_feasibility_avg_global"], + "toe_trust_tiers": _build_trust_tiers(normalized), + }, + ) + + if renormalize: + total_weight = sum(axis.weight for axis in axes.values()) + if total_weight > 0: + for axis in axes.values(): + axis.weight = axis.weight / total_weight + + omega_value = sum(axis.value * axis.weight for axis in axes.values()) + report.axes = axes + report.omega_total = max(0.0, min(1.0, omega_value)) + + sim_value = axes[SIMUNIVERSE_AXIS_NAME].value + report.level = _evaluate_omega_level(report.omega_total, sim_value) + return report + + +def integrate_simuniverse_into_omega( + omega_path: str, + simuniverse_trust_path: str, + sim_weight: float = 0.12, + renormalize: bool = True, +) -> OmegaReport: + """Merge SimUniverse metrics into an Omega report and recompute totals.""" + + omega_payload = json.loads(Path(omega_path).read_text(encoding="utf-8")) + omega = OmegaReport.model_validate(omega_payload) + trust_summary = load_simuniverse_trust_summary(simuniverse_trust_path) + return apply_simuniverse_axis( + omega, + trust_summary, + sim_weight=sim_weight, + renormalize=renormalize, + ) + diff --git a/src/rex/sim_universe/__init__.py b/src/rex/sim_universe/__init__.py index d257f10..9a615ff 100644 --- a/src/rex/sim_universe/__init__.py +++ b/src/rex/sim_universe/__init__.py @@ -21,6 +21,23 @@ print_heatmap_with_evidence_markdown, format_evidence_markdown, ) +from .trust_integration import ( + ToeTrustSummary, + build_toe_trust_summary, + compute_trust_tier_from_failures, + route_omega, + serialize_trust_summaries, + simuniverse_quality, + update_registry_with_trust, +) +from .stage5_loader import load_stage5_scores, Stage5SimUniversePayload +from .registry import ( + SimUniverseRunCreate, + SimUniverseRunRecord, + SimUniverseRunRegistry, + SimUniverseRunUpdate, +) +from .control_plane import run_stage5_for_run_id __all__ = [ "WorldSpec", @@ -39,4 +56,18 @@ "print_heatmap_with_evidence_markdown", "format_evidence_markdown", "EvidenceLink", + "ToeTrustSummary", + "build_toe_trust_summary", + "serialize_trust_summaries", + "simuniverse_quality", + "route_omega", + "update_registry_with_trust", + "compute_trust_tier_from_failures", + "load_stage5_scores", + "Stage5SimUniversePayload", + "SimUniverseRunCreate", + "SimUniverseRunRecord", + "SimUniverseRunRegistry", + "SimUniverseRunUpdate", + "run_stage5_for_run_id", ] diff --git a/src/rex/sim_universe/control_plane.py b/src/rex/sim_universe/control_plane.py new file mode 100644 index 0000000..27e5c6e --- /dev/null +++ b/src/rex/sim_universe/control_plane.py @@ -0,0 +1,176 @@ +"""SimUniverse control plane API and Stage-5 orchestration helpers.""" + +from __future__ import annotations + +import json +import os +import uuid +from pathlib import Path +from typing import Any, Dict, List, Optional + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel, Field + +from rex.sidrce.omega_simuniverse_integration import ( + compute_simuniverse_consistency, + integrate_simuniverse_into_omega, +) + +from .registry import ( + SimUniverseRunCreate, + SimUniverseRunRecord, + SimUniverseRunRegistry, + SimUniverseRunUpdate, +) +from .stage5_loader import load_stage5_scores +from .trust_integration import build_toe_trust_summary, serialize_trust_summaries + + +DEFAULT_ARTIFACTS_ROOT = Path(os.environ.get("SIMUNIVERSE_ARTIFACTS", "artifacts/simuniverse_runs")) +REGISTRY_DB = os.environ.get("SIMUNIVERSE_REGISTRY_DB", "simuniverse_runs.db") + + +def run_stage5_for_run_id( + *, + env: str, + run_id: str, + stage5_json_path: str | Path, + git_sha: str, + config_path: str, + corpus_path: str, + registry: SimUniverseRunRegistry | None = None, + omega_json_path: str | Path | None = None, + artifacts_root: Path = DEFAULT_ARTIFACTS_ROOT, + mu_min_good: float = 0.4, + faizal_max_good: float = 0.7, +) -> Dict[str, Any]: + """Execute the Stage-5 → trust summary → Omega merge loop for a run.""" + + stage5_path = Path(stage5_json_path) + if not stage5_path.exists(): + raise FileNotFoundError(stage5_path) + + stage5_payload = load_stage5_scores(stage5_path) + summaries = build_toe_trust_summary( + stage5_payload.scores, + mu_min_good=mu_min_good, + faizal_max_good=faizal_max_good, + run_id=stage5_payload.run_id or run_id, + ) + serialized = serialize_trust_summaries(summaries) + + run_dir = artifacts_root / run_id + run_dir.mkdir(parents=True, exist_ok=True) + trust_path = run_dir / "simuniverse_trust_summary.json" + trust_path.write_text(json.dumps(serialized, indent=2), encoding="utf-8") + + sim_stats = compute_simuniverse_consistency(serialized) + + omega_report_path: Path | None = None + omega_total: float | None = None + if omega_json_path is not None: + omega_report_path = run_dir / "omega_with_simuniverse.json" + report = integrate_simuniverse_into_omega( + omega_path=str(omega_json_path), + simuniverse_trust_path=str(trust_path), + ) + omega_report_path.write_text(report.json(indent=2), encoding="utf-8") + omega_total = report.omega_total + + if registry is not None: + registry.update_run( + run_id, + SimUniverseRunUpdate( + status="success", + omega_total=omega_total, + simuniverse_consistency=sim_stats["value"], + ), + ) + + return { + "run_id": run_id, + "env": env, + "git_sha": git_sha, + "config_path": config_path, + "corpus_path": corpus_path, + "trust_summary_path": str(trust_path), + "omega_report_path": str(omega_report_path) if omega_report_path else None, + "simuniverse_consistency": sim_stats["value"], + "omega_total": omega_total, + } + + +class RunRequest(BaseModel): + env: str = Field(default="staging") + git_sha: str + config_path: str = Field(default="configs/rex_simuniverse.yaml") + corpus_path: str = Field(default="corpora/REx.SimUniverseCorpus.v0.2.json") + stage5_json_path: str = Field(default="samples/stage5_sample.json") + omega_json_path: Optional[str] = Field(default="samples/omega_base_sample.json") + + +class RunResponse(BaseModel): + run_id: str + status: str + trust_summary_path: Optional[str] = None + omega_report_path: Optional[str] = None + simuniverse_consistency: Optional[float] = None + omega_total: Optional[float] = None + + +registry = SimUniverseRunRegistry(REGISTRY_DB) +app = FastAPI(title="SimUniverse Control Plane") + + +@app.post("/runs", response_model=RunResponse) +def create_run(req: RunRequest) -> RunResponse: + run_id = f"{req.env}-{req.git_sha[:7]}-{uuid.uuid4().hex[:8]}" + registry.create_run( + SimUniverseRunCreate( + run_id=run_id, + env=req.env, + git_sha=req.git_sha, + config_path=req.config_path, + corpus_path=req.corpus_path, + ) + ) + + try: + result = run_stage5_for_run_id( + env=req.env, + run_id=run_id, + stage5_json_path=req.stage5_json_path, + git_sha=req.git_sha, + config_path=req.config_path, + corpus_path=req.corpus_path, + registry=registry, + omega_json_path=req.omega_json_path, + ) + except Exception as exc: # noqa: BLE001 + registry.update_run( + run_id, + SimUniverseRunUpdate(status="failed", error_message=str(exc)), + ) + raise HTTPException(status_code=500, detail=f"SimUniverse run failed: {exc}") from exc + + return RunResponse( + run_id=run_id, + status="success", + trust_summary_path=result["trust_summary_path"], + omega_report_path=result.get("omega_report_path"), + simuniverse_consistency=result.get("simuniverse_consistency"), + omega_total=result.get("omega_total"), + ) + + +@app.get("/runs/{run_id}", response_model=SimUniverseRunRecord) +def get_run(run_id: str) -> SimUniverseRunRecord: + record = registry.get_run(run_id) + if record is None: + raise HTTPException(status_code=404, detail="Run not found") + return record + + +@app.get("/runs", response_model=List[SimUniverseRunRecord]) +def list_runs(env: str | None = None, limit: int = 50) -> List[SimUniverseRunRecord]: + return registry.list_runs(env=env, limit=limit) diff --git a/src/rex/sim_universe/dfi_meta_cli.py b/src/rex/sim_universe/dfi_meta_cli.py new file mode 100644 index 0000000..e7c59d2 --- /dev/null +++ b/src/rex/sim_universe/dfi_meta_cli.py @@ -0,0 +1,197 @@ +"""DFI Meta CLI for chaining Stage-5, SimUniverse, and Omega reports.""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from dataclasses import dataclass +from pathlib import Path +from statistics import mean +from typing import Iterable, List, Sequence + +from rex.sidrce.omega_schema import OmegaAxis, OmegaReport +from rex.sidrce.omega_simuniverse_integration import ( + SIMUNIVERSE_AXIS_NAME, + apply_simuniverse_axis, +) + +from .reporting import ToeScenarioScores +from .stage5_loader import load_stage5_scores +from .trust_integration import build_toe_trust_summary, serialize_trust_summaries + + +@dataclass +class MetaCycleResult: + iteration: int + omega_total: float + sim_axis_value: float + level: str + low_trust_candidates: List[str] + omega_path: Path + trust_path: Path + + +def _load_base_omega(path: Path) -> OmegaReport: + payload = json.loads(path.read_text(encoding="utf-8")) + axes = { + name: (axis if isinstance(axis, OmegaAxis) else OmegaAxis(**axis)) + for name, axis in payload.get("axes", {}).items() + } + payload["axes"] = axes + return OmegaReport.model_validate(payload) + + +def _collect_low_trust(trust_payload: Iterable[dict]) -> List[str]: + toes = [entry["toe_candidate_id"] for entry in trust_payload if entry.get("low_trust_flag")] + return sorted(set(toes)) + + +def run_meta_cycles( + *, + scores: Sequence[ToeScenarioScores], + base_omega: OmegaReport, + base_run_id: str, + out_dir: Path, + iterations: int, + mu_min_good: float, + faizal_max_good: float, + sim_weight: float, + renormalize: bool, +) -> dict: + out_dir.mkdir(parents=True, exist_ok=True) + + results: List[MetaCycleResult] = [] + for iteration in range(1, iterations + 1): + iter_run_id = f"{base_run_id}-iter{iteration:02d}" + summaries = build_toe_trust_summary( + scores, + mu_min_good=mu_min_good, + faizal_max_good=faizal_max_good, + run_id=iter_run_id, + ) + trust_payload = serialize_trust_summaries(summaries) + + trust_path = out_dir / f"simuniverse_trust_summary_iter_{iteration:02d}.json" + trust_path.write_text(json.dumps(trust_payload, indent=2), encoding="utf-8") + + report = apply_simuniverse_axis( + base_omega, + trust_payload, + sim_weight=sim_weight, + renormalize=renormalize, + ) + omega_path = out_dir / f"omega_with_simuniverse_iter_{iteration:02d}.json" + omega_path.write_text(report.model_dump_json(indent=2), encoding="utf-8") + + sim_axis_value = report.axes[SIMUNIVERSE_AXIS_NAME].value + results.append( + MetaCycleResult( + iteration=iteration, + omega_total=report.omega_total, + sim_axis_value=sim_axis_value, + level=report.level, + low_trust_candidates=_collect_low_trust(trust_payload), + omega_path=omega_path, + trust_path=trust_path, + ) + ) + + omega_totals = [result.omega_total for result in results] + sim_values = [result.sim_axis_value for result in results] + level_counts = Counter(result.level for result in results) + low_trust_union = sorted({toe for result in results for toe in result.low_trust_candidates}) + + omega_mean = mean(omega_totals) if omega_totals else 0.0 + sim_mean = mean(sim_values) if sim_values else 0.0 + verdict = "simulatable" if sim_mean >= 0.6 and omega_mean >= 0.82 else "obstructed" + + meta_summary = { + "iterations": iterations, + "omega_total_mean": omega_mean, + "omega_total_min": min(omega_totals, default=0.0), + "omega_total_max": max(omega_totals, default=0.0), + "sim_axis_mean": sim_mean, + "sim_axis_min": min(sim_values, default=0.0), + "sim_axis_max": max(sim_values, default=0.0), + "levels": level_counts, + "low_trust_candidates": low_trust_union, + "verdict": verdict, + "iterations_detail": [ + { + "iteration": result.iteration, + "omega_total": result.omega_total, + "sim_axis_value": result.sim_axis_value, + "level": result.level, + "omega_path": str(result.omega_path), + "trust_path": str(result.trust_path), + } + for result in results + ], + } + + summary_path = out_dir / "dfi_meta_summary.json" + summary_path.write_text(json.dumps(meta_summary, indent=2), encoding="utf-8") + return meta_summary + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Run the DFI meta loop to tie Stage-5 trust data into Omega reports.", + ) + parser.add_argument("--stage5-json", required=True, help="Path to the Stage-5 results JSON.") + parser.add_argument("--omega-json", required=True, help="Path to the base Omega JSON.") + parser.add_argument("--out-dir", required=True, help="Directory to write per-iteration artifacts.") + parser.add_argument( + "--iterations", + type=int, + default=30, + help="Number of DFI meta iterations to run (default: 30).", + ) + parser.add_argument("--run-id", help="Optional run identifier override for iteration labeling.") + parser.add_argument( + "--mu-min-good", + type=float, + default=0.4, + help="Minimum MUH score to avoid low-trust demotion.", + ) + parser.add_argument( + "--faizal-max-good", + type=float, + default=0.7, + help="Maximum Faizal score tolerated before low-trust demotion.", + ) + parser.add_argument( + "--sim-weight", + type=float, + default=0.12, + help="Weight for the simuniverse_consistency axis in Omega reports.", + ) + parser.add_argument( + "--no-renormalize", + action="store_true", + help="Skip weight renormalization when injecting the SimUniverse axis.", + ) + args = parser.parse_args() + + stage5 = load_stage5_scores(Path(args.stage5_json)) + base_run_id = args.run_id or stage5.run_id or "stage5" + base_omega = _load_base_omega(Path(args.omega_json)) + + summary = run_meta_cycles( + scores=stage5.scores, + base_omega=base_omega, + base_run_id=base_run_id, + out_dir=Path(args.out_dir), + iterations=args.iterations, + mu_min_good=args.mu_min_good, + faizal_max_good=args.faizal_max_good, + sim_weight=args.sim_weight, + renormalize=not args.no_renormalize, + ) + + print(json.dumps(summary, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/rex/sim_universe/metrics_exporter.py b/src/rex/sim_universe/metrics_exporter.py new file mode 100644 index 0000000..56409fd --- /dev/null +++ b/src/rex/sim_universe/metrics_exporter.py @@ -0,0 +1,92 @@ +"""Minimal Prometheus exporter for SimUniverse and Omega metrics.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +from typing import Any, Dict, List + +from fastapi import FastAPI +from fastapi.responses import PlainTextResponse + + +app = FastAPI(title="SimUniverse Metrics Exporter") + +TRUST_PATH = Path(os.environ.get("SIMUNIVERSE_TRUST_PATH", "artifacts/stage5_simuniverse/last_simuniverse_trust_summary.json")) +OMEGA_PATH = Path(os.environ.get("SIMUNIVERSE_OMEGA_PATH", "artifacts/stage5_simuniverse/omega_with_simuniverse.json")) + + +def _load_json(path: Path) -> Any: + if not path.exists(): + return None + return json.loads(path.read_text(encoding="utf-8")) + + +def _format_metric(name: str, labels: Dict[str, str] | None, value: float) -> str: + if labels: + label_str = ",".join(f'{k}="{v}"' for k, v in sorted(labels.items())) + return f"{name}{{{label_str}}} {value}" + return f"{name} {value}" + + +def render_metrics(trust_path: Path = TRUST_PATH, omega_path: Path = OMEGA_PATH) -> str: + trust_data = _load_json(trust_path) or [] + omega_data = _load_json(omega_path) + + lines: List[str] = [] + lines.append("# HELP simuniverse_mu_score_avg Average MUH score per TOE candidate.") + lines.append("# TYPE simuniverse_mu_score_avg gauge") + for entry in trust_data: + lines.append( + _format_metric( + "simuniverse_mu_score_avg", + {"toe_candidate": str(entry.get("toe_candidate_id", "unknown"))}, + float(entry.get("mu_score_avg", 0.0)), + ) + ) + + lines.append("# HELP simuniverse_faizal_score_avg Average Faizal score per TOE candidate.") + lines.append("# TYPE simuniverse_faizal_score_avg gauge") + for entry in trust_data: + lines.append( + _format_metric( + "simuniverse_faizal_score_avg", + {"toe_candidate": str(entry.get("toe_candidate_id", "unknown"))}, + float(entry.get("faizal_score_avg", 0.0)), + ) + ) + + if omega_data: + axes = omega_data.get("axes", {}) + sim_axis = axes.get("simuniverse_consistency") + if sim_axis: + lines.append("# HELP asdpi_omega_axis Omega axis values.") + lines.append("# TYPE asdpi_omega_axis gauge") + for axis_name, axis_payload in axes.items(): + lines.append( + _format_metric( + "asdpi_omega_axis", + {"axis": axis_name}, + float(axis_payload.get("value", 0.0)), + ) + ) + if "omega_total" in omega_data: + lines.append("# HELP asdpi_omega_total Omega total value.") + lines.append("# TYPE asdpi_omega_total gauge") + lines.append( + _format_metric("asdpi_omega_total", None, float(omega_data.get("omega_total", 0.0))) + ) + + return "\n".join(lines) + "\n" + + +@app.get("/metrics", response_class=PlainTextResponse) +def metrics_endpoint() -> str: + return render_metrics() + + +if __name__ == "__main__": # pragma: no cover - manual run helper + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8000"))) diff --git a/src/rex/sim_universe/registry.py b/src/rex/sim_universe/registry.py new file mode 100644 index 0000000..af29b3b --- /dev/null +++ b/src/rex/sim_universe/registry.py @@ -0,0 +1,168 @@ +"""Persistent registry for SimUniverse runs.""" + +from __future__ import annotations + +import sqlite3 +import threading +from datetime import datetime +from pathlib import Path +from typing import Iterable, List, Optional + +from pydantic import BaseModel + +def _now() -> str: + return datetime.utcnow().isoformat(timespec="seconds") + + +class SimUniverseRunRecord(BaseModel): + """Serializable representation of a SimUniverse run.""" + + run_id: str + env: str + git_sha: str + config_path: str + corpus_path: str + created_at: datetime + status: str + error_message: str | None = None + omega_total: float | None = None + simuniverse_consistency: float | None = None + + @classmethod + def from_row(cls, row: sqlite3.Row) -> "SimUniverseRunRecord": + return cls( + run_id=row["run_id"], + env=row["env"], + git_sha=row["git_sha"], + config_path=row["config_path"], + corpus_path=row["corpus_path"], + created_at=datetime.fromisoformat(row["created_at"]), + status=row["status"], + error_message=row["error_message"], + omega_total=row["omega_total"], + simuniverse_consistency=row["simuniverse_consistency"], + ) + + +class SimUniverseRunCreate(BaseModel): + run_id: str + env: str + git_sha: str + config_path: str + corpus_path: str + + +class SimUniverseRunUpdate(BaseModel): + status: Optional[str] = None + error_message: Optional[str] = None + omega_total: Optional[float] = None + simuniverse_consistency: Optional[float] = None + + def __init__(self, **data: object) -> None: + super().__init__(**data) + self._provided_fields = { + field for field, value in data.items() if field in { + "status", + "error_message", + "omega_total", + "simuniverse_consistency", + } and value is not None + } + + def items(self) -> Iterable[tuple[str, object]]: + for field in self._provided_fields: + yield field, getattr(self, field) + + +class SimUniverseRunRegistry: + """SQLite-backed registry for SimUniverse runs.""" + + def __init__(self, db_path: str | Path = "simuniverse_runs.db") -> None: + self.db_path = str(db_path) + self._lock = threading.Lock() + self._ensure_table() + + def _connect(self) -> sqlite3.Connection: + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + return conn + + def _ensure_table(self) -> None: + with self._connect() as conn: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS simuniverse_runs ( + run_id TEXT PRIMARY KEY, + env TEXT NOT NULL, + git_sha TEXT NOT NULL, + config_path TEXT NOT NULL, + corpus_path TEXT NOT NULL, + created_at TEXT NOT NULL, + status TEXT NOT NULL, + error_message TEXT, + omega_total REAL, + simuniverse_consistency REAL + ) + """ + ) + conn.commit() + + def create_run(self, data: SimUniverseRunCreate) -> SimUniverseRunRecord: + with self._lock: + with self._connect() as conn: + conn.execute( + """ + INSERT INTO simuniverse_runs ( + run_id, env, git_sha, config_path, corpus_path, + created_at, status + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + data.run_id, + data.env, + data.git_sha, + data.config_path, + data.corpus_path, + _now(), + "running", + ), + ) + conn.commit() + record = self.get_run(data.run_id) + if record is None: + raise RuntimeError("Failed to read run after creation") + return record + + def update_run(self, run_id: str, data: SimUniverseRunUpdate) -> SimUniverseRunRecord | None: + updates = list(data.items()) + if not updates: + return self.get_run(run_id) + assignments = ", ".join(f"{field} = ?" for field, _ in updates) + values = [value for _, value in updates] + values.append(run_id) + with self._lock: + with self._connect() as conn: + conn.execute(f"UPDATE simuniverse_runs SET {assignments} WHERE run_id = ?", values) + conn.commit() + return self.get_run(run_id) + + def get_run(self, run_id: str) -> SimUniverseRunRecord | None: + with self._connect() as conn: + row = conn.execute( + "SELECT * FROM simuniverse_runs WHERE run_id = ?", (run_id,) + ).fetchone() + if row is None: + return None + return SimUniverseRunRecord.from_row(row) + + def list_runs(self, env: str | None = None, limit: int = 50) -> List[SimUniverseRunRecord]: + query = "SELECT * FROM simuniverse_runs" + params: tuple[object, ...] = () + if env: + query += " WHERE env = ?" + params = (env,) + query += " ORDER BY created_at DESC LIMIT ?" + params = params + (limit,) + with self._connect() as conn: + rows = conn.execute(query, params).fetchall() + return [SimUniverseRunRecord.from_row(row) for row in rows] diff --git a/src/rex/sim_universe/reporting.py b/src/rex/sim_universe/reporting.py index 40468f3..1e5ef26 100644 --- a/src/rex/sim_universe/reporting.py +++ b/src/rex/sim_universe/reporting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Mapping, Sequence from .corpus import AssumptionRole, SimUniverseCorpus @@ -13,12 +13,12 @@ class ToeScenarioScores: world_id: str mu_score: float faizal_score: float - coverage_alg: float - mean_undecidability_index: float - energy_feasibility: float - rg_phase_index: float - rg_halting_indicator: float - evidence: List["EvidenceLink"] + coverage_alg: float = 0.0 + mean_undecidability_index: float = 0.0 + energy_feasibility: float = 0.0 + rg_phase_index: float = 0.0 + rg_halting_indicator: float = 0.0 + evidence: List["EvidenceLink"] = field(default_factory=list) @dataclass diff --git a/src/rex/sim_universe/stage5_loader.py b/src/rex/sim_universe/stage5_loader.py new file mode 100644 index 0000000..b82fefa --- /dev/null +++ b/src/rex/sim_universe/stage5_loader.py @@ -0,0 +1,86 @@ +"""Helpers for loading LawBinder Stage-5 SimUniverse payloads.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, List, Sequence + +from .reporting import ToeScenarioScores + + +@dataclass +class Stage5SimUniversePayload: + """Container for Stage-5 SimUniverse scores and metadata.""" + + scores: List[ToeScenarioScores] + run_id: str | None = None + + +def _coerce_float( + record: Sequence | dict, + key: str, + default: float = 0.0, + alternate_keys: Iterable[str] | None = None, +) -> float: + value = default + if isinstance(record, dict): + candidates = [key, f"{key}_avg"] + if alternate_keys: + candidates.extend(alternate_keys) + for candidate in candidates: + if candidate in record: + value = record[candidate] + break + try: + return float(value) + except (TypeError, ValueError): # pragma: no cover - defensive + return float(default) + + +def load_stage5_scores(path: str | Path) -> Stage5SimUniversePayload: + """Load Stage-5 JSON payloads into ToeScenarioScores entries.""" + + payload = json.loads(Path(path).read_text(encoding="utf-8")) + run_id: str | None = None + records: Sequence[dict] + + if isinstance(payload, list): + records = payload + elif isinstance(payload, dict): + run_id = payload.get("run_id") or payload.get("stage5_run_id") + if "simuniverse_summary" in payload and isinstance(payload["simuniverse_summary"], list): + records = payload["simuniverse_summary"] + elif "scores" in payload and isinstance(payload["scores"], list): + records = payload["scores"] + else: + raise ValueError("Stage-5 payload must contain simuniverse_summary or scores list") + else: + raise ValueError("Stage-5 payload must be a list or dict") + + scores: List[ToeScenarioScores] = [] + for record in records: + scores.append( + ToeScenarioScores( + toe_candidate_id=str(record["toe_candidate_id"]), + world_id=str(record.get("world_id", "unknown_world")), + mu_score=_coerce_float(record, "mu_score"), + faizal_score=_coerce_float(record, "faizal_score", default=1.0), + coverage_alg=_coerce_float(record, "coverage_alg"), + mean_undecidability_index=_coerce_float( + record, + "mean_undecidability_index", + alternate_keys=["undecidability_avg"], + ), + energy_feasibility=_coerce_float( + record, + "energy_feasibility", + alternate_keys=["energy_feasibility_avg"], + ), + rg_phase_index=_coerce_float(record, "rg_phase_index"), + rg_halting_indicator=_coerce_float(record, "rg_halting_indicator"), + ) + ) + + return Stage5SimUniversePayload(scores=scores, run_id=run_id) diff --git a/src/rex/sim_universe/trust_integration.py b/src/rex/sim_universe/trust_integration.py new file mode 100644 index 0000000..c84125b --- /dev/null +++ b/src/rex/sim_universe/trust_integration.py @@ -0,0 +1,198 @@ +"""SimUniverse trust aggregation plus ASDP/Meta-Router integration helpers.""" + +from __future__ import annotations + +from collections import defaultdict +from copy import deepcopy +from dataclasses import dataclass +from typing import Dict, Iterable, List, Mapping, MutableMapping, Sequence + +from .reporting import ToeScenarioScores + + +@dataclass +class ToeTrustSummary: + """Aggregate metrics describing a TOE candidate across Stage-5 worlds.""" + + toe_candidate_id: str + runs: int + mu_score_avg: float + faizal_score_avg: float + undecidability_avg: float + energy_feasibility_avg: float + low_trust_flag: bool + run_id: str | None = None + + def to_payload(self) -> dict: + payload = { + "toe_candidate_id": self.toe_candidate_id, + "runs": self.runs, + "mu_score_avg": self.mu_score_avg, + "faizal_score_avg": self.faizal_score_avg, + "undecidability_avg": self.undecidability_avg, + "energy_feasibility_avg": self.energy_feasibility_avg, + "low_trust_flag": self.low_trust_flag, + } + if self.run_id is not None: + payload["run_id"] = self.run_id + return payload + + +ScoreInput = ToeScenarioScores | Mapping[str, object] + + +def _score_value(score: ScoreInput, field: str, default: float = 0.0) -> float: + if isinstance(score, ToeScenarioScores): + return float(getattr(score, field, default)) + value = score.get(field, default) + return float(value) + + +def _score_toe_id(score: ScoreInput) -> str: + if isinstance(score, ToeScenarioScores): + return score.toe_candidate_id + value = score.get("toe_candidate_id") + if value is None: + raise KeyError("toe_candidate_id is required for trust summaries") + return str(value) + + +def build_toe_trust_summary( + scores: Sequence[ScoreInput], + mu_min_good: float = 0.4, + faizal_max_good: float = 0.7, + run_id: str | None = None, +) -> List[ToeTrustSummary]: + """Aggregate ToeScenarioScores objects into ToeTrustSummary entries.""" + + grouped: Dict[str, List[ScoreInput]] = defaultdict(list) + for score in scores: + grouped[_score_toe_id(score)].append(score) + + summaries: List[ToeTrustSummary] = [] + for toe_id, group in grouped.items(): + n = len(group) + mu_avg = sum(_score_value(item, "mu_score") for item in group) / n + faizal_avg = sum(_score_value(item, "faizal_score") for item in group) / n + undecidability_avg = ( + sum(_score_value(item, "mean_undecidability_index") for item in group) / n + ) + energy_avg = sum(_score_value(item, "energy_feasibility") for item in group) / n + + low_trust = (mu_avg < mu_min_good) and (faizal_avg > faizal_max_good) + + summaries.append( + ToeTrustSummary( + toe_candidate_id=toe_id, + runs=n, + mu_score_avg=mu_avg, + faizal_score_avg=faizal_avg, + undecidability_avg=undecidability_avg, + energy_feasibility_avg=energy_avg, + low_trust_flag=low_trust, + run_id=run_id, + ) + ) + + return summaries + + +def serialize_trust_summaries(summaries: Iterable[ToeTrustSummary]) -> List[dict]: + """Convert ToeTrustSummary objects to JSON-friendly dicts.""" + + return [summary.to_payload() for summary in summaries] + + +def update_registry_with_trust( + registry_doc: Mapping[str, object], + trust_summary: Sequence[Mapping[str, object] | ToeTrustSummary], + *, + low_trust_tag: str = "simuniverse.low_trust", + fallback_run_id: str | None = None, +) -> dict: + """Patch an ASDP registry dictionary with SimUniverse trust metrics.""" + + updated = deepcopy(registry_doc) + toe_entries = updated.get("toe_candidates") + if not isinstance(toe_entries, list): + return updated + + normalized: Dict[str, Mapping[str, object]] = {} + for entry in trust_summary: + if isinstance(entry, ToeTrustSummary): + payload = entry.to_payload() + else: + payload = dict(entry) + toe_id = str(payload.get("toe_candidate_id")) + normalized[toe_id] = payload + + for entry in toe_entries: + toe_id = entry.get("id") + if toe_id is None: + continue + payload = normalized.get(str(toe_id)) + if payload is None: + continue + + trust_block: MutableMapping[str, object] = entry.setdefault("trust", {}) + sim_block: MutableMapping[str, object] = trust_block.setdefault("simuniverse", {}) + sim_block.update( + mu_score_avg=payload.get("mu_score_avg", 0.0), + faizal_score_avg=payload.get("faizal_score_avg", 0.0), + undecidability_avg=payload.get("undecidability_avg", 0.0), + energy_feasibility_avg=payload.get("energy_feasibility_avg", 0.0), + low_trust_flag=payload.get("low_trust_flag", False), + ) + sim_block["last_update_run_id"] = payload.get("run_id") or fallback_run_id + + low_trust_flag = bool(payload.get("low_trust_flag")) + current_tier = str(trust_block.get("tier", "unknown")) + if low_trust_flag: + trust_block["tier"] = "low" + elif current_tier in {"unknown", "", "low"}: + trust_block["tier"] = "normal" + + tags = set(entry.get("sovereign_tags", [])) + if low_trust_flag: + tags.add(low_trust_tag) + else: + tags.discard(low_trust_tag) + entry["sovereign_tags"] = sorted(tags) + + return updated + + +def simuniverse_quality(mu_score: float, faizal_score: float) -> float: + """Blend MUH and Faizal scores into a normalized [0, 1] quality signal.""" + + quality = 0.7 * mu_score + 0.3 * (1.0 - faizal_score) + return max(0.0, min(1.0, quality)) + + +def route_omega(base_omega: float, sim_q: float, trust_tier: str) -> float: + """Apply trust-tier penalties to an omega score using SimUniverse quality.""" + + tier_factor = { + "high": 1.0, + "normal": 0.9, + "low": 0.6, + "unknown": 0.8, + }.get(trust_tier, 0.8) + + omega_sim = 0.6 * base_omega + 0.4 * sim_q + return max(0.0, min(1.0, omega_sim * tier_factor)) + + +def compute_trust_tier_from_failures( + prev_tier: str, + failure_count: int, + *, + failure_threshold: int = 3, +) -> str: + """Derive a trust tier using Stage-5 failure counters.""" + + if failure_count >= failure_threshold: + return "low" + if prev_tier in {"unknown", "", "low"}: + return "normal" + return prev_tier diff --git a/tests/test_control_plane.py b/tests/test_control_plane.py new file mode 100644 index 0000000..5e8034f --- /dev/null +++ b/tests/test_control_plane.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from pathlib import Path + +from rex.sim_universe.control_plane import run_stage5_for_run_id +from rex.sim_universe.registry import SimUniverseRunCreate, SimUniverseRunRegistry + + +def test_run_stage5_pipeline(tmp_path: Path) -> None: + registry = SimUniverseRunRegistry(tmp_path / "runs.db") + run_id = "staging-abcdef01" + registry.create_run( + SimUniverseRunCreate( + run_id=run_id, + env="staging", + git_sha="abcdef0123456789", + config_path="configs/rex_simuniverse.yaml", + corpus_path="corpora/REx.SimUniverseCorpus.v0.2.json", + ) + ) + + result = run_stage5_for_run_id( + env="staging", + run_id=run_id, + stage5_json_path=Path("samples/stage5_sample.json"), + git_sha="abcdef0123456789", + config_path="configs/rex_simuniverse.yaml", + corpus_path="corpora/REx.SimUniverseCorpus.v0.2.json", + registry=registry, + omega_json_path=Path("samples/omega_base_sample.json"), + artifacts_root=tmp_path, + ) + + run_dir = tmp_path / run_id + assert run_dir.exists() + assert (run_dir / "simuniverse_trust_summary.json").exists() + assert (run_dir / "omega_with_simuniverse.json").exists() + assert result["simuniverse_consistency"] is not None + + record = registry.get_run(run_id) + assert record is not None + assert record.status == "success" + assert record.simuniverse_consistency == result["simuniverse_consistency"] diff --git a/tests/test_dfi_meta_cli.py b/tests/test_dfi_meta_cli.py new file mode 100644 index 0000000..8159c33 --- /dev/null +++ b/tests/test_dfi_meta_cli.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from datetime import datetime +from pathlib import Path + +from rex.sidrce.omega_schema import OmegaAxis, OmegaReport +from rex.sim_universe.dfi_meta_cli import run_meta_cycles +from rex.sim_universe.reporting import ToeScenarioScores + + +def _base_omega() -> OmegaReport: + return OmegaReport( + omega_version="8.1", + tenant="flamehaven", + service="rex-engine", + run_id="run-base", + created_at=datetime(2025, 1, 1), + axes={ + "coverage": OmegaAxis(value=0.9, weight=0.25), + "robustness": OmegaAxis(value=0.85, weight=0.25), + "safety": OmegaAxis(value=0.87, weight=0.25), + "finops": OmegaAxis(value=0.84, weight=0.25), + }, + omega_total=0.0, + level="fail", + ) + + +def _scores() -> list[ToeScenarioScores]: + return [ + ToeScenarioScores( + toe_candidate_id="toe_high", + world_id="world-1", + mu_score=0.8, + faizal_score=0.2, + ), + ToeScenarioScores( + toe_candidate_id="toe_low", + world_id="world-2", + mu_score=0.2, + faizal_score=0.8, + ), + ] + + +def test_run_meta_cycles_writes_outputs(tmp_path: Path) -> None: + summary = run_meta_cycles( + scores=_scores(), + base_omega=_base_omega(), + base_run_id="demo", + out_dir=tmp_path, + iterations=3, + mu_min_good=0.4, + faizal_max_good=0.7, + sim_weight=0.12, + renormalize=True, + ) + + assert summary["iterations"] == 3 + assert summary["verdict"] in {"simulatable", "obstructed"} + assert (tmp_path / "dfi_meta_summary.json").exists() + assert (tmp_path / "simuniverse_trust_summary_iter_01.json").exists() + assert (tmp_path / "omega_with_simuniverse_iter_01.json").exists() + assert len(summary["iterations_detail"]) == 3 diff --git a/tests/test_metrics_exporter.py b/tests/test_metrics_exporter.py new file mode 100644 index 0000000..8da6ce8 --- /dev/null +++ b/tests/test_metrics_exporter.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import importlib +import json +from pathlib import Path + + +def test_metrics_exporter_outputs_metrics(tmp_path: Path) -> None: + trust_path = tmp_path / "trust.json" + omega_path = tmp_path / "omega.json" + trust_path.write_text( + json.dumps( + [ + { + "toe_candidate_id": "toe_a", + "mu_score_avg": 0.8, + "faizal_score_avg": 0.2, + } + ] + ), + encoding="utf-8", + ) + omega_path.write_text( + json.dumps( + { + "axes": { + "coverage": {"value": 0.9}, + "simuniverse_consistency": {"value": 0.75}, + }, + "omega_total": 0.88, + } + ), + encoding="utf-8", + ) + + exporter = importlib.import_module("rex.sim_universe.metrics_exporter") + exporter = importlib.reload(exporter) + body = exporter.render_metrics(trust_path, omega_path) + assert "simuniverse_mu_score_avg" in body + assert "asdpi_omega_total" in body diff --git a/tests/test_registry.py b/tests/test_registry.py new file mode 100644 index 0000000..920313e --- /dev/null +++ b/tests/test_registry.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from pathlib import Path + +from rex.sim_universe.registry import ( + SimUniverseRunCreate, + SimUniverseRunRegistry, + SimUniverseRunUpdate, +) + + +def test_registry_create_and_update(tmp_path: Path) -> None: + db_path = tmp_path / "registry.db" + registry = SimUniverseRunRegistry(db_path) + + create = SimUniverseRunCreate( + run_id="staging-123", + env="staging", + git_sha="abc1234", + config_path="configs/sample.yaml", + corpus_path="corpora/sample.json", + ) + record = registry.create_run(create) + assert record.run_id == "staging-123" + assert record.status == "running" + + updated = registry.update_run( + "staging-123", + SimUniverseRunUpdate(status="success", omega_total=0.9, simuniverse_consistency=0.8), + ) + assert updated is not None + assert updated.status == "success" + assert updated.omega_total == 0.9 + assert updated.simuniverse_consistency == 0.8 + + # list API should include our single run + runs = registry.list_runs(env="staging") + assert len(runs) == 1 + assert runs[0].run_id == "staging-123" diff --git a/tests/test_sidrce_integration.py b/tests/test_sidrce_integration.py new file mode 100644 index 0000000..0d64526 --- /dev/null +++ b/tests/test_sidrce_integration.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import json +from datetime import datetime +from pathlib import Path + +from rex.sidrce.omega_simuniverse_integration import ( + SIMUNIVERSE_AXIS_NAME, + apply_simuniverse_axis, + compute_simuniverse_consistency, + integrate_simuniverse_into_omega, +) +from rex.sidrce.omega_schema import OmegaAxis, OmegaReport + + +def _write_json(path: Path, payload: dict) -> None: + path.write_text(json.dumps(payload, indent=2), encoding="utf-8") + + +def test_compute_simuniverse_consistency_scales_with_scores() -> None: + summary = [ + { + "toe_candidate_id": "toe_a", + "mu_score_avg": 0.8, + "faizal_score_avg": 0.2, + "undecidability_avg": 0.6, + "energy_feasibility_avg": 0.9, + }, + { + "toe_candidate_id": "toe_b", + "mu_score_avg": 0.6, + "faizal_score_avg": 0.5, + "undecidability_avg": 0.4, + "energy_feasibility_avg": 0.8, + }, + ] + + stats = compute_simuniverse_consistency(summary) + expected = 0.7 * ((0.8 + 0.6) / 2) + 0.3 * (1 - ((0.2 + 0.5) / 2)) + assert stats["value"] == expected + assert stats["mu_score_avg_global"] == (0.8 + 0.6) / 2 + assert stats["faizal_score_avg_global"] == (0.2 + 0.5) / 2 + assert stats["undecidability_avg_global"] == (0.6 + 0.4) / 2 + assert stats["energy_feasibility_avg_global"] == (0.9 + 0.8) / 2 + + +def test_integrate_simuniverse_into_omega(tmp_path: Path) -> None: + base_report = OmegaReport( + omega_version="8.1", + tenant="flamehaven", + service="rex-engine", + run_id="run-123", + created_at=datetime.utcnow(), + axes={ + "coverage": OmegaAxis(value=0.9, weight=0.25), + "robustness": OmegaAxis(value=0.85, weight=0.25), + "safety": OmegaAxis(value=0.88, weight=0.25), + "finops": OmegaAxis(value=0.86, weight=0.25), + }, + omega_total=0.0, + level="fail", + ) + omega_path = tmp_path / "omega.json" + omega_path.write_text( + json.dumps(base_report.model_dump(), default=str), + encoding="utf-8", + ) + + sim_summary = [ + { + "toe_candidate_id": "toe_a", + "mu_score_avg": 0.75, + "faizal_score_avg": 0.3, + "undecidability_avg": 0.5, + "energy_feasibility_avg": 0.95, + "low_trust_flag": False, + "high_trust_flag": True, + }, + { + "toe_candidate_id": "toe_b", + "mu_score_avg": 0.65, + "faizal_score_avg": 0.4, + "undecidability_avg": 0.55, + "energy_feasibility_avg": 0.85, + "low_trust_flag": True, + }, + ] + trust_path = tmp_path / "trust.json" + _write_json(trust_path, sim_summary) + + report = integrate_simuniverse_into_omega( + omega_path=str(omega_path), + simuniverse_trust_path=str(trust_path), + ) + + assert SIMUNIVERSE_AXIS_NAME in report.axes + axis = report.axes[SIMUNIVERSE_AXIS_NAME] + assert 0.0 <= axis.value <= 1.0 + assert abs(sum(a.weight for a in report.axes.values()) - 1.0) < 1e-9 + assert report.omega_total == sum(a.value * a.weight for a in report.axes.values()) + assert report.level in {"pass", "warn", "fail"} + assert axis.details["toe_trust_tiers"]["toe_a"] == "high" + assert axis.details["toe_trust_tiers"]["toe_b"] == "low" + + +def test_apply_simuniverse_axis_in_memory() -> None: + base_report = OmegaReport( + omega_version="8.2", + tenant="flamehaven", + service="rex-engine", + run_id="run-xyz", + created_at=datetime.utcnow(), + axes={ + "coverage": OmegaAxis(value=0.9, weight=0.5), + "robustness": OmegaAxis(value=0.85, weight=0.5), + }, + omega_total=0.0, + level="fail", + ) + trust_summary = [ + { + "toe_candidate_id": "toe_a", + "mu_score_avg": 0.8, + "faizal_score_avg": 0.3, + "undecidability_avg": 0.5, + "energy_feasibility_avg": 0.9, + } + ] + + report = apply_simuniverse_axis( + base_report, + trust_summary, + sim_weight=0.2, + renormalize=False, + ) + + assert SIMUNIVERSE_AXIS_NAME in report.axes + assert abs(sum(axis.weight for axis in report.axes.values()) - 1.2) < 1e-9 + assert report.axes[SIMUNIVERSE_AXIS_NAME].weight == 0.2 + diff --git a/tests/test_simuniverse_trust.py b/tests/test_simuniverse_trust.py new file mode 100644 index 0000000..db6d6d5 --- /dev/null +++ b/tests/test_simuniverse_trust.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +from rex.sim_universe.reporting import ToeScenarioScores +from rex.sim_universe.trust_integration import ( + ToeTrustSummary, + build_toe_trust_summary, + compute_trust_tier_from_failures, + route_omega, + simuniverse_quality, + update_registry_with_trust, +) + + +def _scenario(**overrides: float | str) -> ToeScenarioScores: + base = dict( + toe_candidate_id="toe_a", + world_id="world-1", + mu_score=0.5, + faizal_score=0.5, + coverage_alg=0.0, + mean_undecidability_index=0.5, + energy_feasibility=0.5, + rg_phase_index=0.0, + rg_halting_indicator=0.0, + ) + base.update(overrides) + return ToeScenarioScores(**base) + + +def test_build_toe_trust_summary_flags_low_trust() -> None: + scores = [ + _scenario(toe_candidate_id="toe_low", mu_score=0.2, faizal_score=0.9, mean_undecidability_index=0.8), + _scenario( + toe_candidate_id="toe_low", + world_id="world-2", + mu_score=0.25, + faizal_score=0.85, + mean_undecidability_index=0.7, + ), + _scenario(toe_candidate_id="toe_high", mu_score=0.8, faizal_score=0.2, mean_undecidability_index=0.3), + ] + + summaries = build_toe_trust_summary(scores, mu_min_good=0.4, faizal_max_good=0.7, run_id="run-abc") + summary_by_toe = {summary.toe_candidate_id: summary for summary in summaries} + + assert summary_by_toe["toe_low"].low_trust_flag is True + assert summary_by_toe["toe_low"].runs == 2 + assert summary_by_toe["toe_low"].run_id == "run-abc" + assert summary_by_toe["toe_high"].low_trust_flag is False + + +def test_update_registry_with_trust_sets_tags_and_tier() -> None: + registry = { + "toe_candidates": [ + {"id": "toe_low", "sovereign_tags": [], "trust": {"tier": "unknown"}}, + {"id": "toe_high", "trust": {"tier": "high", "simuniverse": {}}}, + ] + } + summaries = [ + ToeTrustSummary( + toe_candidate_id="toe_low", + runs=2, + mu_score_avg=0.2, + faizal_score_avg=0.9, + undecidability_avg=0.7, + energy_feasibility_avg=0.4, + low_trust_flag=True, + run_id="run-1", + ), + ToeTrustSummary( + toe_candidate_id="toe_high", + runs=1, + mu_score_avg=0.8, + faizal_score_avg=0.2, + undecidability_avg=0.3, + energy_feasibility_avg=0.8, + low_trust_flag=False, + run_id="run-1", + ), + ] + + updated = update_registry_with_trust(registry, summaries) + low_entry = updated["toe_candidates"][0] + high_entry = updated["toe_candidates"][1] + + assert low_entry["trust"]["tier"] == "low" + assert "simuniverse.low_trust" in low_entry["sovereign_tags"] + assert low_entry["trust"]["simuniverse"]["low_trust_flag"] is True + + assert high_entry["trust"]["tier"] == "high" + assert "simuniverse.low_trust" not in high_entry.get("sovereign_tags", []) + + +def test_simuniverse_quality_and_route_omega() -> None: + sim_q = simuniverse_quality(mu_score=0.9, faizal_score=0.1) + assert 0.0 <= sim_q <= 1.0 + + routed = route_omega(base_omega=0.85, sim_q=sim_q, trust_tier="low") + assert routed < 0.85 # penalized by low trust tier + + +def test_compute_trust_tier_from_failures_threshold() -> None: + assert compute_trust_tier_from_failures("unknown", failure_count=0) == "normal" + assert compute_trust_tier_from_failures("normal", failure_count=5, failure_threshold=3) == "low" diff --git a/tests/test_stage5_loader.py b/tests/test_stage5_loader.py new file mode 100644 index 0000000..cd5b135 --- /dev/null +++ b/tests/test_stage5_loader.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from pathlib import Path + +from rex.sim_universe.stage5_loader import load_stage5_scores + + +def test_load_stage5_scores_detects_run_id(tmp_path: Path) -> None: + payload = { + "run_id": "stage5-abc", + "simuniverse_summary": [ + { + "toe_candidate_id": "toe_a", + "world_id": "world-1", + "mu_score": 0.5, + "faizal_score": 0.4, + } + ], + } + path = tmp_path / "stage5.json" + path.write_text(__import__("json").dumps(payload), encoding="utf-8") + + stage5 = load_stage5_scores(path) + assert stage5.run_id == "stage5-abc" + assert len(stage5.scores) == 1 + assert stage5.scores[0].toe_candidate_id == "toe_a" + + +def test_load_stage5_scores_accepts_list(tmp_path: Path) -> None: + payload = [ + { + "toe_candidate_id": "toe_b", + "world_id": "world-2", + "mu_score_avg": 0.7, + "faizal_score_avg": 0.3, + } + ] + path = tmp_path / "stage5_list.json" + path.write_text(__import__("json").dumps(payload), encoding="utf-8") + + stage5 = load_stage5_scores(path) + assert stage5.run_id is None + assert stage5.scores[0].mu_score == 0.7 + assert stage5.scores[0].faizal_score == 0.3 From 37ddb3718b2f7bb05ab0283879bc4c97608ecb1e Mon Sep 17 00:00:00 2001 From: Flamehaven Date: Wed, 19 Nov 2025 19:27:54 +0700 Subject: [PATCH 2/3] Add merge readiness playbook --- docs/MergeReadiness.md | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/MergeReadiness.md diff --git a/docs/MergeReadiness.md b/docs/MergeReadiness.md new file mode 100644 index 0000000..0124d87 --- /dev/null +++ b/docs/MergeReadiness.md @@ -0,0 +1,62 @@ +# Merge Readiness Playbook + +This note documents the checks we just ran while trying to understand why the +`codex/add-simuniverse-evidence-corpus-blueprint-g4zfcn` branch could not be +merged into `main`, plus the exact steps required to land the branch on +`main`. + +## What went wrong + +* The repository checkout inside CI/local environments does not have a remote + configured, so `git remote -v` prints nothing. Without the `origin` + definition, commands such as `git fetch origin main` or `git push origin + work:main` always fail because Git does not know where `main` lives. +* The only local branch is `work`. Running `git branch -a` confirms that there + is no `main` tracking branch in the checkout, which explains why `git checkout + main` (a prerequisite for merging) fails. + +## Verifying the branch graph + +Even though we cannot fetch the remote, the local history shows that `work` is a +strict descendant of the merge commit +`27060942476bbba10584285b0679940bdd3458ef` (the merge of PR #1). We verified the +shape of the graph with the following commands: + +```bash +git merge-base work 2706094 +git log 2706094..work --oneline +``` + +Because `work` contains only the new "Wire SimUniverse control plane and metrics +exporter" commit on top of the existing PR #1 merge, merging `work` into +`main`—once `main` is checked out—should be a fast-forward operation with no +conflicts. + +## Fix and merge checklist + +1. Configure the remote once inside your checkout: + ```bash + git remote add origin git@github.com:flamehaven01/Rex-Sim-Universe-Lab.git + git fetch origin --prune + ``` +2. Create or update the local `main` branch to track the remote branch: + ```bash + git checkout -B main origin/main + ``` +3. Rebase or merge the feature branch. Because `work` is already ahead of + `origin/main`, a fast-forward merge is enough: + ```bash + git checkout main + git merge --ff-only work + ``` +4. Push the updated `main` branch (and optionally keep `work` in sync): + ```bash + git push origin main + git push origin work --force-with-lease # only if the remote branch needs updating + ``` +5. If the push still fails, double-check that your SSH key has permission to the + GitHub repository and re-run `git remote -v` to ensure the remote URL is + correct. + +Following this checklist ensures every patch and update currently staged in the +`work` branch lands on `main` with the exact history we validated locally. From d890765dbd9258187f878aa6d31babbdcbbc77d7 Mon Sep 17 00:00:00 2001 From: Flamehaven Date: Wed, 19 Nov 2025 19:28:00 +0700 Subject: [PATCH 3/3] Add research report and core graph --- docs/SimUniverseResearchReport.md | 41 +++++++ docs/assets/simuniverse_core_graph.svg | 36 +++++++ scripts/generate_simuniverse_core_graph.py | 120 +++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 docs/SimUniverseResearchReport.md create mode 100644 docs/assets/simuniverse_core_graph.svg create mode 100644 scripts/generate_simuniverse_core_graph.py diff --git a/docs/SimUniverseResearchReport.md b/docs/SimUniverseResearchReport.md new file mode 100644 index 0000000..106861b --- /dev/null +++ b/docs/SimUniverseResearchReport.md @@ -0,0 +1,41 @@ +# SimUniverse Research Report + +This report condenses the latest Stage-5 evidence, registry context, and Ω integration outputs so researchers can defend the question **“Is this universe mathematically simulatable?”** with auditable data. + +## 1. Stage-5 evidence at a glance + +The sample Stage-5 payload (`samples/stage5_sample.json`) captures the MUH/Faizal envelope for the two TOE candidates that are currently under review. + +| TOE candidate | MUH score | Faizal score | Undecidability index | Energy feasibility | Notes | +| --- | --- | --- | --- | --- | --- | +| `toe_candidate_muh_cuh` | 0.82 | 0.28 | 0.32 | 0.91 | High MUH affinity and tractable Faizal obstruction keep it production-ready. | +| `toe_candidate_faizal_mtoe` | 0.24 | 0.86 | 0.81 | 0.18 | Falls into the low-trust zone (low MUH, high Faizal), so LawBinder marks it for sandbox-only routing. | + +## 2. Core graph for researchers + +The MUH/Faizal scatter below highlights the operational trust boundary (red = heuristic low trust). Regenerate it after each Stage-5 batch with: + +```bash +python scripts/generate_simuniverse_core_graph.py \ + --stage5-json artifacts/stage5_simuniverse/lawbinder_stage5_report_.json \ + --out docs/assets/simuniverse_core_graph.svg +``` + +![SimUniverse core graph](assets/simuniverse_core_graph.svg) + +## 3. Aggregated metrics feeding Ω / ASDP + +| Metric | Value | How it is used | +| --- | --- | --- | +| `mu_score_avg` | 0.53 | Drives positive SimUniverse consistency. | +| `faizal_score_avg` | 0.57 | Penalizes TOE candidates that resist constructive simulation. | +| `undecidability_avg` | 0.565 | Bounded within LawBinder’s acceptable range; routed to ASDP `trust.simuniverse` payloads. | +| `energy_feasibility_avg` | 0.545 | Provides FinOps-aware guardrails when the router tunes ensemble costs. | +| `simuniverse_consistency` | 0.50 | Injected as the `axes.simuniverse_consistency` value during SIDRCE Ω merging. | + +## 4. Narrative summary for audits + +1. **High-trust branch (`toe_candidate_muh_cuh`)** maintains MUH affinity above 0.8 with Faizal obstruction < 0.3, so it satisfies both Ω and router gates without extra penalties. +2. **Faizal-heavy branch** violates the `mu < 0.4 && faizal > 0.7` heuristic, pushing it into the automated `trust.tier = "low"` classification that GitOps syncers write back into `asdp.knowledge.yaml`. +3. The combined scalar `simuniverse_consistency = 0.50` keeps Ω in the “warn” boundary, which is acceptable for research but insufficient for Meta-Router promotion until follow-up Stage-5 iterations lift the MUH average or suppress Faizal pressure. +4. Evidence artifacts (Stage-5 JSON + SVG graph + Ω report) now form a reproducible thread that research reviewers can cite in LawBinder, FinOps, and ASDP hearings without needing to re-run the simulations manually. diff --git a/docs/assets/simuniverse_core_graph.svg b/docs/assets/simuniverse_core_graph.svg new file mode 100644 index 0000000..8a2c1b0 --- /dev/null +++ b/docs/assets/simuniverse_core_graph.svg @@ -0,0 +1,36 @@ + + + + + + + + + + +MUH affinity (higher is better) +Faizal obstruction (lower is better) +SimUniverse core evidence map +0.00 +0.00 +0.25 +0.25 +0.50 +0.50 +0.75 +0.75 +1.00 +1.00 + +toe_candidate_muh_cuh + +toe_candidate_faizal_mtoe + +Trusted/normal + +Low-trust heuristic + \ No newline at end of file diff --git a/scripts/generate_simuniverse_core_graph.py b/scripts/generate_simuniverse_core_graph.py new file mode 100644 index 0000000..1d7f070 --- /dev/null +++ b/scripts/generate_simuniverse_core_graph.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import List + +SVG_TEMPLATE = """ + +{content} +""" + + +COLORS = { + "normal": "#1f77b4", + "low": "#d62728", +} + + +def load_stage5_summary(path: Path) -> List[dict]: + data = json.loads(path.read_text(encoding="utf-8")) + summary = data.get("simuniverse_summary", []) + if not isinstance(summary, list): + raise ValueError("stage5 JSON must contain a simuniverse_summary list") + return summary + + +def trust_tier(mu_score: float, faizal_score: float) -> str: + if mu_score < 0.4 and faizal_score > 0.7: + return "low" + return "normal" + + +def project_x(value: float, width: float, padding: float) -> float: + return padding + value * (width - 2 * padding) + + +def project_y(value: float, height: float, padding: float) -> float: + return height - padding - value * (height - 2 * padding) + + +def create_svg(summary: List[dict], width: int = 720, height: int = 480) -> str: + padding = 60 + elements: List[str] = [] + + # grid lines + for frac in [0.25, 0.5, 0.75]: + y = project_y(frac, height, padding) + elements.append(f"") + x = project_x(frac, width, padding) + elements.append(f"") + + # axes + elements.append(f"") + elements.append(f"") + + # labels + elements.append(f"MUH affinity (higher is better)") + elements.append(f"Faizal obstruction (lower is better)") + elements.append(f"SimUniverse core evidence map") + + # ticks + for frac in [0.0, 0.25, 0.5, 0.75, 1.0]: + x = project_x(frac, width, padding) + y = project_y(frac, height, padding) + elements.append(f"{frac:.2f}") + elements.append(f"{frac:.2f}") + + # points + for entry in summary: + mu = float(entry.get("mu_score", 0.0)) + faizal = float(entry.get("faizal_score", 0.0)) + toe_id = entry.get("toe_candidate_id", "unknown") + tier = trust_tier(mu, faizal) + cx = project_x(mu, width, padding) + cy = project_y(faizal, height, padding) + color = COLORS[tier] + elements.append( + f"" + ) + elements.append( + f"{toe_id}" + ) + + # legend + legend_y = padding + for label, color in [("Trusted/normal", COLORS["normal"]), ("Low-trust heuristic", COLORS["low"])]: + elements.append( + f"" + ) + elements.append( + f"{label}" + ) + legend_y += 24 + + return SVG_TEMPLATE.format(width=width, height=height, content="\n".join(elements)) + + +def write_svg(summary: List[dict], out_path: Path) -> None: + svg = create_svg(summary) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(svg, encoding="utf-8") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Generate the SimUniverse core MUH/Faizal graph.") + parser.add_argument("--stage5-json", required=True, help="Path to Stage-5 summary JSON file.") + parser.add_argument("--out", required=True, help="Path for the generated SVG graph.") + args = parser.parse_args() + + summary = load_stage5_summary(Path(args.stage5_json)) + write_svg(summary, Path(args.out)) + + +if __name__ == "__main__": + main()