"""Isolated real APS HTTP backend for the Round 87 browser acceptance. Only authentication is a test provider. Project files, database, approvals, master-data edits and scheduling execute through production code. """ from __future__ import annotations import argparse import hashlib import json import os import sys import tempfile from pathlib import Path from fastapi import HTTPException, Request def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=18787) parser.add_argument("--source", default=os.environ.get("ROUND87_SOURCE")) parser.add_argument("--expectations", type=Path) args = parser.parse_args() repo = Path(__file__).resolve().parents[2] sys.path.insert(0, str(repo)) from tests.workbook_acceptance import load_expectations, resolve_source expectations = load_expectations(args.expectations) source = resolve_source(args.source, required=True, expectations=expectations) expected_sha = expectations["sourceSha256"] if args.port in {8000, 8003, 5173}: raise ValueError("Refusing a user development service port") root = Path(tempfile.mkdtemp(prefix="aps-round87-browser-")) data = root / "data" data.mkdir() path_env = { "APS_HOME": root, "APS_DATA_DIR": data, "APS_DB_PATH": data / "master.db", "APS_WORLD_PATH": data / "world.json", "APS_PROJECTS_PATH": root / "sessions/workspace.json", "APS_KNOWLEDGE_PATH": data / "knowledge.json", "APS_CHECKPOINT_PATH": data / "checkpoints.json", "APS_PREFERENCE_PATH": data / "preferences.json", "APS_EMBEDDINGS_PATH": data / "embeddings.json", "APS_APPROVAL_PATH": data / "approvals.json", "APS_AUDIT_LEDGER_DIR": data / "audit-ledger", "APS_AUDIT_MIRROR_DIR": data / "audit-mirror", "APS_AUTOMATION_STATE_PATH": data / "automation.json", "APS_MCP_BUS_PATH": data / "mcp-bus.json", "APS_BRANCH_PATH": data / "branches.json", "APS_BRANCH_DIR": data / "branches", "APS_GOLDEN_CACHE": data / "golden-tests.json", "APS_MESH_PATH": data / "mesh.json", "APS_FALLBACK_ROOT": root / "fallback", } for key, value in path_env.items(): os.environ[key] = str(value) for key in ("APS_DATABASE_URL", "APS_DB_DISABLED", "MES_HTTP_BASE_URL"): os.environ.pop(key, None) os.environ.update(APS_MODE="web", APS_AUTH_ENABLED="1", APS_SEED_DEMO="0", APS_APPROVAL_BACKEND="file", APS_AUTH_COOKIE_SECURE="0", APS_AUTOMATION_DRIVER="0", EMBEDDING_PROVIDER="off") repo = Path(__file__).resolve().parents[2] sys.path.insert(0, str(repo)) import uvicorn from fastapi import FastAPI import server.auth.middleware as auth_middleware import server.gateway.app as gateway_module from tests.auth_provider import TestAuthProvider provider = TestAuthProvider("round87-masterdata-acceptance") auth_middleware.get_auth_provider = lambda: provider gateway_module.get_auth_provider = lambda: provider production_app = gateway_module.create_app() if os.environ.get("APS_E2E_FAKE_PI") == "1": # Browser acceptance must not depend on a live LLM: the suite sends one # analysis command and then works through the UI. Real Pi runs stay # covered by scripts/run-pi-real-workbook-e2e.ps1. from tests.pi_primary_adapter import install_fake_pi_tool_adapter class _ModulePatcher: @staticmethod def setattr(target, name, value): setattr(target, name, value) install_fake_pi_tool_adapter(_ModulePatcher) app = FastAPI() @app.get("/api/__e2e__/health") @app.get("/__e2e__/health") async def acceptance_health() -> dict: return {"isolated": True, "runtimeRoot": str(root), "sourceSha256": expected_sha, "sourceUnchanged": hashlib.sha256(source.read_bytes()).hexdigest() == expected_sha, "productionCode": True} @app.post("/api/__e2e__/seed-legacy-intake") async def seed_legacy_intake_fixture(payload: dict, request: Request) -> dict: from server.aps_domain.importers import preview_file from server.auth.context import bind_identity, reset_identity from server.state.projects import get_project_store from server.state.store import get_store from tests.intake_recovery_fixture import seed_legacy_intake identity = await provider.authenticate(request) token = bind_identity(identity) try: workspace = get_project_store().snapshot(include_messages=False) if workspace["activeProjectId"] != payload.get("projectId") or not any( row["id"] == payload.get("sessionId") and row["projectId"] == payload.get("projectId") for row in workspace["sessions"] ): raise HTTPException(status_code=409, detail="Fixture must target the active test project/session") store = get_store() if store.data.get("flexOrders"): raise HTTPException(status_code=409, detail="Fixture requires an empty test project") preview = preview_file(source.name, source.read_bytes(), store.data) seed_legacy_intake(store.data, store.next_id, preview) store.save() return {"formalOrderNos": [row["orderNo"] for row in store.data["flexOrders"]], "sourceSha256": expected_sha} finally: reset_identity(token) @app.post("/api/__e2e__/supplement-scheduling-skill") async def supplement_scheduling_skill(payload: dict, request: Request) -> dict: """Explicit test supplement, never installed on the production application.""" import copy from server.auth.context import bind_identity, reset_identity from server.state.projects import get_project_store from server.state.store import get_store identity = await provider.authenticate(request) token = bind_identity(identity) try: workspace = get_project_store().snapshot(include_messages=False) if workspace["activeProjectId"] != payload.get("projectId") or not any( row["id"] == payload.get("sessionId") and row["projectId"] == payload.get("projectId") for row in workspace["sessions"] ): raise HTTPException(status_code=409, detail="Fixture must target the active test project/session") store = get_store() if not any(item.get("sha256") == expected_sha for item in store.data.get("intakeSources") or []): raise HTTPException(status_code=409, detail="Fixture requires adoption of the designated workbook") materials = copy.deepcopy(store.data["materials"]) orders = copy.deepcopy(store.data["flexOrders"]) person = copy.deepcopy(expectations["trial"]["supplementPerson"]) if not any(row.get("code") == person["code"] for row in store.data.get("flexPersonnel") or []): store.data["flexPersonnel"].append({**person, "sourceRef": {"kind": "isolated_test_supplement"}}) store.save() assert store.data["materials"] == materials and store.data["flexOrders"] == orders return {"isolated": True, "personCode": person["code"], "orderCount": len(orders), "stockUnchanged": True} finally: reset_identity(token) app.mount("/", production_app) print(json.dumps({"event": "round87-browser-server", "port": args.port, "runtimeRoot": str(root)}, ensure_ascii=False), flush=True) try: uvicorn.run(app, host="127.0.0.1", port=args.port, log_level="warning") finally: from server.db.database import reset_engine reset_engine() print(json.dumps({"event": "round87-browser-server-stopped", "runtimeRoot": str(root)}), flush=True) if __name__ == "__main__": main()