File size: 4,393 Bytes
6ff09d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2819b19
 
 
 
 
6ff09d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python
"""Phase 2 verification β€” drives the env through the real OpenEnv HTTP+WS surface.

Boots the FastAPI app in a background uvicorn, connects via the sync EnvClient
(same path training code will use), and walks through reset -> unauth probe
(expect 401) -> authed probe (expect 200) -> update_model -> finalize.

Set env var SPACE_URL to hit a remote HF Space instead of a local server.
"""

from __future__ import annotations

import os
import sys
import threading
import time

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
if ROOT not in sys.path:
    sys.path.insert(0, ROOT)


def _start_local_server(port: int) -> threading.Thread:
    """Launch uvicorn in a background thread."""
    import uvicorn

    from server.app import app  # noqa: F401 β€” import to ensure app is built

    config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="warning")
    server = uvicorn.Server(config)

    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()

    # Wait for the server to come up
    import httpx
    for _ in range(50):
        try:
            r = httpx.get(f"http://127.0.0.1:{port}/health", timeout=0.5)
            if r.status_code == 200:
                return thread
        except Exception:
            pass
        time.sleep(0.1)
    raise RuntimeError("server never came up")


def main() -> int:
    from client import ProtocolOneEnv
    from models import ProtocolOneAction

    space_url = os.environ.get("SPACE_URL")
    if space_url:
        print(f"Using remote SPACE_URL={space_url}")
        base_url = space_url
    else:
        port = 8765
        print(f"Booting local uvicorn on :{port} …")
        _start_local_server(port)
        base_url = f"http://127.0.0.1:{port}"

    env = ProtocolOneEnv(base_url=base_url).sync()
    with env:
        # 1. Reset
        result = env.reset()
        text = result.observation.text or ""
        # Be lenient about the exact wording -- "reverse-engineer", "map the structure",
        # or any other framing of the task is fine.
        probes_remaining = result.observation.probes_remaining
        assert probes_remaining > 0, f"expected positive probes_remaining, got {probes_remaining}"
        print(f"βœ“ Reset β€” {len(text)} char initial observation, probes_remaining={probes_remaining}")

        # 2. Unauth probe -> expect 401
        result = env.step(ProtocolOneAction(
            tool="probe",
            args={"method": "GET", "path": "/users"},
        ))
        assert "401" in (result.observation.text or ""), (
            f"expected 401 in unauth probe, got: {result.observation.text[:200]}"
        )
        print("βœ“ Unauthenticated probe β†’ 401")

        # 3. Authed probe -> expect 200
        result = env.step(ProtocolOneAction(
            tool="probe",
            args={"method": "GET", "path": "/users",
                  "headers": {"Authorization": "Bearer token_full"}},
        ))
        assert "200" in (result.observation.text or ""), (
            f"expected 200 in authed probe, got: {result.observation.text[:200]}"
        )
        print("βœ“ Authed probe β†’ 200")

        # 4. update_model
        result = env.step(ProtocolOneAction(
            tool="update_model",
            args={"delta": {
                "endpoints": [
                    {"method": "GET", "path": "/users",
                     "auth_required": True, "auth_scope": "users:read"},
                ],
                "auth": {"type": "bearer", "scopes_observed": ["users:read"]},
            }},
        ))
        stats = result.observation.belief_graph_stats
        assert stats.get("endpoints") == 1, f"expected 1 endpoint, got {stats}"
        assert stats.get("auth_scopes_observed") == 1
        print(f"βœ“ update_model merged β€” stats={stats}")

        # 5. finalize -> expect reward > 0 but < 0.3 (we only submitted one endpoint)
        result = env.step(ProtocolOneAction(tool="finalize", args={}))
        assert result.done
        assert result.reward is not None
        assert 0.0 < result.reward < 0.3, (
            f"unexpected reward {result.reward} for single-endpoint finalize"
        )
        print(f"βœ“ Finalized β€” reward={result.reward:.3f} (expected in (0, 0.3))")

    print("\nPhase 2 verified.")
    return 0


if __name__ == "__main__":
    sys.exit(main())