from __future__ import annotations import argparse import atexit import copy import hashlib import json import os import shutil import sys import tempfile import types from pathlib import Path from threading import RLock from typing import Any REPO_ROOT = Path(__file__).resolve().parents[2] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) LIVE_MOM_WORLD = REPO_ROOT / "server" / "data" / "world-proj_712276ba.json" _RUNTIME_ROOT = Path(tempfile.mkdtemp(prefix="aps-round65-e2e-")) _RUNTIME_DATA = _RUNTIME_ROOT / "data" _RUNTIME_DATA.mkdir(parents=True, exist_ok=True) def _configure_isolated_environment() -> None: paths = { "APS_HOME": _RUNTIME_ROOT, "APS_DATA_DIR": _RUNTIME_DATA, "APS_DB_PATH": _RUNTIME_DATA / "master.db", "APS_WORLD_PATH": _RUNTIME_DATA / "world.json", "APS_PROJECTS_PATH": _RUNTIME_ROOT / "sessions" / "workspace.json", "APS_KNOWLEDGE_PATH": _RUNTIME_DATA / "knowledge.json", "APS_CHECKPOINT_PATH": _RUNTIME_DATA / "checkpoints.json", "APS_PREFERENCE_PATH": _RUNTIME_DATA / "preferences.json", "APS_EMBEDDINGS_PATH": _RUNTIME_DATA / "embeddings.json", "APS_APPROVAL_PATH": _RUNTIME_DATA / "approvals.json", "APS_AUDIT_LEDGER_DIR": _RUNTIME_DATA / "audit-ledger", "APS_AUDIT_MIRROR_DIR": _RUNTIME_DATA / "audit-mirror", "APS_AUTOMATION_STATE_PATH": _RUNTIME_DATA / "automation.json", "APS_MCP_BUS_PATH": _RUNTIME_DATA / "mcp-bus.json", "APS_BRANCH_PATH": _RUNTIME_DATA / "branches.json", "APS_BRANCH_DIR": _RUNTIME_DATA / "branches", "APS_GOLDEN_CACHE": _RUNTIME_DATA / "golden-tests.json", } for key, value in paths.items(): os.environ[key] = str(value) os.environ["APS_APPROVAL_BACKEND"] = "file" os.environ["APS_AUTOMATION_DRIVER"] = "0" os.environ["APS_AUTH_COOKIE_SECURE"] = "0" os.environ.pop("APS_DATABASE_URL", None) _configure_isolated_environment() atexit.register(lambda: shutil.rmtree(_RUNTIME_ROOT, ignore_errors=True)) from fastapi import FastAPI, HTTPException from server.agent_core import harness from tests.auth_provider import TestAuthProvider from tests.golden.test_closed_loop_runtime import _ready_world import server.auth.middleware as auth_middleware import server.gateway.app as gateway_module import server.state.projects as projects_module def _sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() _LIVE_WORLD_SHA256 = _sha256(LIVE_MOM_WORLD) _GENERATED_LIST_KEYS = ( "closedLoopProblems", "manufacturingDemands", "supplyEvents", "flexScheduleVersions", "flexVirtualLines", "flexWorkOrders", "scheduleVersions", "productionOrders", "workOrders", "conflicts", "flexConflicts", "purchaseOrders", "outsourceOrders", "makeSuggestions", "mesLinks", "mesDispatches", "mesReports", "mesReceipts", "wmsReceipts", ) def _mom_world() -> dict[str, Any]: # Read-only source: every test receives a deep in-memory copy. save() below # never serializes this object back to server/data/**. world = json.loads(LIVE_MOM_WORLD.read_text(encoding="utf-8")) world = copy.deepcopy(world) world["businessDate"] = "2026-08-02" for key in _GENERATED_LIST_KEYS: world[key] = [] return world def _positive_world() -> dict[str, Any]: world = copy.deepcopy(_ready_world()) world["businessDate"] = "2026-08-02" for key in _GENERATED_LIST_KEYS: world[key] = [] for order in world.get("salesOrders") or []: order.setdefault("customerName", "Round 65 E2E ??") order.setdefault("customerLevel", "A") order.setdefault("orderDate", "2026-08-02") order.setdefault("deliveryDate", order.get("dueDate") or "2026-08-04") return world _FIXTURES = {"mom": _mom_world, "ready": _positive_world} class MemoryWorldStore: """WorldStore-compatible process-local store; save is deliberately a no-op.""" def __init__(self) -> None: self._lock = RLock() self.world_key = "round65-e2e" self.fixture_name = "mom" self.save_count = 0 self.data: dict[str, Any] = _mom_world() def switch(self, fixture_name: str) -> dict[str, Any]: factory = _FIXTURES.get(fixture_name) if factory is None: raise KeyError(fixture_name) with self._lock: self.fixture_name = fixture_name self.save_count = 0 self.data = factory() return self.data def next_id(self, kind: str) -> int: with self._lock: key = f"_round65_e2e_{kind}" self.data[key] = int(self.data.get(key) or 900_000) + 1 return int(self.data[key]) def save(self) -> None: # Preserve production write semantics in memory while proving no live # world file is ever mutated by this runner. with self._lock: self.save_count += 1 STORE = MemoryWorldStore() class Round65TestAuthProvider(TestAuthProvider): async def precheck( self, username: str = "", tenant_code: str = "", tenant_name: str = "", ) -> dict[str, Any]: return { "requireCaptcha": False, "username": username, "tenantCode": tenant_code, "tenantName": tenant_name, } async def captcha(self) -> dict[str, Any]: return {"captchaId": "round65-e2e", "image": "", "expiresIn": 300} AUTH_PROVIDER = Round65TestAuthProvider("tenant-round65-e2e") auth_middleware.get_auth_provider = lambda: AUTH_PROVIDER gateway_module.get_auth_provider = lambda: AUTH_PROVIDER gateway_module.get_store = lambda: STORE # The production middleware enforces project write membership. The E2E runner # keeps the real ProjectStore API for page bootstrap, but makes its temporary # tenant workspace writable so P2/P3 API calls exercise the production routes. _ORIGINAL_GET_PROJECT_STORE = projects_module.get_project_store def _get_writable_temp_project_store(): project_store = _ORIGINAL_GET_PROJECT_STORE() project_store.require_active_write = types.MethodType( lambda self: None, project_store, ) return project_store projects_module.get_project_store = _get_writable_temp_project_store # Confirmations staged against the in-memory E2E world must resolve to that # exact world rather than looking up a persistent project world. harness.resolve_confirmation_store = lambda _confirm_id, fallback: fallback def _reset_approval_store() -> None: approval_path = _RUNTIME_DATA / "approvals.json" approval_path.unlink(missing_ok=True) harness.configure_approval_store(path=str(approval_path)) _reset_approval_store() production_app: FastAPI = gateway_module.create_app() app = FastAPI(title="Round 65 isolated closed-loop E2E") @app.get("/__e2e__/health") async def e2e_health() -> dict[str, Any]: return { "ok": True, "fixture": STORE.fixture_name, "runtimeRoot": str(_RUNTIME_ROOT), "liveWorldSha256": _LIVE_WORLD_SHA256, "liveWorldSha256Now": _sha256(LIVE_MOM_WORLD), } @app.post("/__e2e__/fixture/{fixture_name}") async def e2e_switch_fixture(fixture_name: str) -> dict[str, Any]: try: world = STORE.switch(fixture_name) except KeyError as exc: raise HTTPException(status_code=404, detail=f"unknown fixture: {fixture_name}") from exc _reset_approval_store() return { "ok": True, "fixture": fixture_name, "businessDate": world.get("businessDate"), "salesOrderCount": len(world.get("salesOrders") or []), "liveWorldSha256": _LIVE_WORLD_SHA256, "liveWorldSha256Now": _sha256(LIVE_MOM_WORLD), } @app.get("/__e2e__/state") async def e2e_state() -> dict[str, Any]: world = STORE.data versions = world.get("flexScheduleVersions") or [] latest = versions[-1] if versions else None return { "fixture": STORE.fixture_name, "saveCount": STORE.save_count, "closedLoopProblemCount": len(world.get("closedLoopProblems") or []), "manufacturingDemandCount": len(world.get("manufacturingDemands") or []), "purchaseSuggestionCount": len(world.get("purchaseOrders") or []), "virtualLineCount": len(world.get("flexVirtualLines") or []), "workOrderCount": len(world.get("flexWorkOrders") or []), "latestVersion": latest, "mesLinkCount": len(world.get("mesLinks") or []), "liveWorldSha256": _LIVE_WORLD_SHA256, "liveWorldSha256Now": _sha256(LIVE_MOM_WORLD), } # Keep test-only endpoints ahead of the production SPA catch-all route. app.mount("/", production_app) def main() -> None: parser = argparse.ArgumentParser(description="Round 65 isolated closed-loop E2E backend") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=int(os.environ.get("ROUND65_E2E_PORT", "8115"))) args = parser.parse_args() import uvicorn print( json.dumps( { "event": "round65-e2e-server-start", "host": args.host, "port": args.port, "runtimeRoot": str(_RUNTIME_ROOT), "liveWorldSha256": _LIVE_WORLD_SHA256, }, ensure_ascii=False, ), flush=True, ) uvicorn.run(app, host=args.host, port=args.port, log_level="info") if __name__ == "__main__": main()