hirann commited on
Commit
1f9fc8c
·
0 Parent(s):

Add Core Identity environment for OpenEnv

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ .pytest_cache/
Dockerfile ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Identity Environment - Docker configuration for HF Spaces
2
+
3
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
4
+ FROM ${BASE_IMAGE} AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ COPY . /app/env
9
+
10
+ WORKDIR /app/env
11
+
12
+ RUN if ! command -v uv > /dev/null 2>&1; then \
13
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
14
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
15
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
16
+ fi
17
+
18
+ RUN apt-get update && apt-get install -y --no-install-recommends \
19
+ git \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ RUN --mount=type=cache,target=/root/.cache/uv \
23
+ uv sync --no-install-project --no-editable
24
+
25
+ RUN --mount=type=cache,target=/root/.cache/uv \
26
+ uv sync --no-editable
27
+
28
+ FROM ${BASE_IMAGE}
29
+
30
+ WORKDIR /app
31
+
32
+ COPY --from=builder /app/env/.venv /app/.venv
33
+ COPY --from=builder /app/env /app/env
34
+
35
+ ENV PATH="/app/.venv/bin:$PATH"
36
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
37
+
38
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
39
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
40
+
41
+ EXPOSE 8000
42
+
43
+ CMD ["sh", "-c", "cd /app/env && uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000"]
README.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Identity Environment — OpenEnv
2
+
3
+ [![OpenEnv](https://img.shields.io/badge/OpenEnv-compatible-blue)](https://github.com/meta-pytorch/OpenEnv)
4
+ [![Python](https://img.shields.io/badge/python-3.10%2B-brightgreen)](https://python.org)
5
+
6
+ An identity verification environment for OpenEnv (`meta-pytorch/OpenEnv`), where AI agents can perform document verification, credential authentication, profile management, and fraud detection tasks.
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ This environment simulates real-world identity verification tasks. Agents must:
13
+
14
+ 1. Analyze identity documents or credentials
15
+ 2. Perform verification checks
16
+ 3. Detect fraud or issues
17
+ 4. Make accurate verification decisions
18
+
19
+ ### Why Core Identity?
20
+
21
+ - **Security-focused** — critical real-world application
22
+ - **Multi-faceted** — document, credentials, and profile tasks
23
+ - **Difficulty progression** — simple auth → hard fraud detection
24
+ - **Measurable** — clear true/false outcomes
25
+
26
+ ---
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ core_identity_env/ # Environment package
32
+ ├── __init__.py # Exports: CoreIdentityEnv, CoreIdentityAction
33
+ ├── client.py # CoreIdentityEnv — async EnvClient (WebSocket)
34
+ ├── models.py # Typed models for all task types
35
+ ├── openenv.yaml # OpenEnv manifest (spec_version: 1)
36
+ ├── pyproject.toml # Per-env package config
37
+ ├── tasks/
38
+ │ ├── __init__.py
39
+ │ └── definitions.py # 5 task types + graders
40
+ └── server/
41
+ ├── __init__.py # FastAPI app via create_app()
42
+ ├── core_identity_environment.py # Server-side Environment
43
+ ├── requirements.txt # Server/Docker deps
44
+ └── Dockerfile # Multi-stage build
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Tasks
50
+
51
+ | Task ID | Name | Task Type | Difficulty |
52
+ |---------|------|-----------|------------|
53
+ | `doc_verify_passport` | Passport Verification | document_verification | Medium |
54
+ | `doc_verify_fake_id` | Fake ID Detection | document_verification | Hard |
55
+ | `auth_valid_creds` | Credential Authentication | credential_authentication | Easy |
56
+ | `auth_brute_force` | Brute Force Detection | credential_authentication | Hard |
57
+ | `profile_update` | Profile Update Validation | profile_management | Medium |
58
+
59
+ ---
60
+
61
+ ## Quick Start
62
+
63
+ ### 1. Install the environment
64
+
65
+ ```bash
66
+ pip install -e core_identity_env/
67
+ ```
68
+
69
+ ### 2. Start the server
70
+
71
+ ```bash
72
+ uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000
73
+ ```
74
+
75
+ ### 3. Use the async client
76
+
77
+ ```python
78
+ import asyncio
79
+ from core_identity_env import CoreIdentityEnv, CoreIdentityAction, VerificationResult
80
+
81
+ async def main():
82
+ async with CoreIdentityEnv(base_url="http://localhost:8000") as env:
83
+ result = await env.reset()
84
+ obs = result.observation
85
+ print(f"Task: {obs.task_name}")
86
+
87
+ if obs.document:
88
+ print(f"Document type: {obs.document.document_type}")
89
+
90
+ action = CoreIdentityAction(
91
+ verification=VerificationResult(
92
+ verified=True,
93
+ confidence=0.95,
94
+ checks_performed={"expiry_check": True, "format_check": True},
95
+ ),
96
+ submit=True,
97
+ )
98
+ result = await env.step(action)
99
+ print(f"Reward: {result.reward:.4f}")
100
+
101
+ asyncio.run(main())
102
+ ```
103
+
104
+ ### 4. From a Hugging Face Space
105
+
106
+ ```python
107
+ import asyncio
108
+ from core_identity_env import CoreIdentityEnv
109
+
110
+ async def main():
111
+ env = await CoreIdentityEnv.from_env("hirann/core-identity-env")
112
+ try:
113
+ result = await env.reset()
114
+ print(result.observation.task_name)
115
+ finally:
116
+ await env.close()
117
+
118
+ asyncio.run(main())
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Docker
124
+
125
+ ```bash
126
+ # Build
127
+ docker build -t core-identity-env -f core_identity_env/server/Dockerfile .
128
+
129
+ # Run
130
+ docker run -p 8000:8000 core-identity-env
131
+ ```
132
+
133
+ ## Hugging Face Spaces Deployment
134
+
135
+ 1. Create a new Space at https://huggingface.co/new-space
136
+ 2. Select **Docker** as the SDK
137
+ 3. Push your repo
138
+
139
+ ---
140
+
141
+ ## Architecture
142
+
143
+ ```
144
+ ┌───────────────────────────────────┐
145
+ │ Client Application │
146
+ │ CoreIdentityEnv (EnvClient) │
147
+ │ async with env: ... │
148
+ └───────────────┬───────────────────┘
149
+ │ WebSocket /ws
150
+ │ (reset, step, state)
151
+ ┌───────────────▼───────────────────┐
152
+ │ Docker Container / HF Space │
153
+ │ FastAPI — create_app() │
154
+ │ CoreIdentityEnvironment │
155
+ │ reset() / step() / state │
156
+ └───────────────────────────────────┘
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Reward Function
162
+
163
+ | Component | Weight | Notes |
164
+ |-----------|--------|-------|
165
+ | Accuracy | 70% | Correct verification decision |
166
+ | Completeness | 30% | Checks performed / required |
167
+ | Efficiency bonus | +0.1 | Completed in ≤50% of max steps |
168
+
169
+ ---
170
+
171
+ ## OpenEnv Spec Compliance
172
+
173
+ | Spec requirement | Status |
174
+ |-----------------|--------|
175
+ | `openenv.yaml` with `spec_version: 1` | ✅ |
176
+ | `type: space`, `runtime: fastapi` | ✅ |
177
+ | `Environment` base class (`reset/step/state`) | ✅ |
178
+ | `EnvClient` base class (async WebSocket) | ✅ |
179
+ | `create_app()` factory | ✅ |
180
+ | Per-env `pyproject.toml` | ✅ |
181
+ | `server/Dockerfile` with `openenv-base` | ✅ |
182
+ | Typed `Action` / `Observation` models | ✅ |
183
+
184
+ ---
185
+
186
+ ## License
187
+
188
+ MIT — see LICENSE file for details.
__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Core Identity Environment — OpenEnv.
2
+
3
+ An identity verification environment for AI agents that can:
4
+ - Verify user identity documents (ID, passport, etc.)
5
+ - Authenticate users via credentials
6
+ - Manage user profiles and attributes
7
+ - Detect fraudulent documents
8
+ """
9
+
10
+ from core_identity_env.client import CoreIdentityEnv
11
+ from core_identity_env.models import (
12
+ CoreIdentityAction,
13
+ CoreIdentityObservation,
14
+ TaskType,
15
+ IdentityDocument,
16
+ VerificationResult,
17
+ )
18
+
19
+ __all__ = [
20
+ "CoreIdentityEnv",
21
+ "CoreIdentityAction",
22
+ "CoreIdentityObservation",
23
+ "TaskType",
24
+ "IdentityDocument",
25
+ "VerificationResult",
26
+ ]
client.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Core Identity Environment Client.
2
+
3
+ Provides the async EnvClient for connecting to a CoreIdentityEnvironment server.
4
+ Supports both async (recommended) and synchronous usage via .sync() wrapper.
5
+
6
+ Example (async):
7
+ >>> import asyncio
8
+ >>> from core_identity_env import CoreIdentityEnv, CoreIdentityAction, VerificationResult
9
+ >>>
10
+ >>> async def main():
11
+ ... async with CoreIdentityEnv(base_url="http://localhost:8000") as env:
12
+ ... result = await env.reset()
13
+ ... obs = result.observation
14
+ ... print(obs.document)
15
+ ...
16
+ ... action = CoreIdentityAction(
17
+ ... verification=VerificationResult(
18
+ ... verified=True,
19
+ ... confidence=0.95,
20
+ ... issues=[],
21
+ ... ),
22
+ ... submit=True,
23
+ ... )
24
+ ... result = await env.step(action)
25
+ ... print(result.reward)
26
+ ...
27
+ >>> asyncio.run(main())
28
+
29
+ Example (from Hugging Face Space):
30
+ >>> env = await CoreIdentityEnv.from_env("openenv/core-identity-env")
31
+ >>> try:
32
+ ... result = await env.reset()
33
+ ... finally:
34
+ ... await env.close()
35
+ """
36
+
37
+ from typing import Any, Dict
38
+
39
+ try:
40
+ from openenv.core.env_client import EnvClient
41
+ from openenv.core.client_types import StepResult
42
+ except ImportError:
43
+ from openenv.core.env_client import EnvClient
44
+ from openenv.core.client_types import StepResult
45
+
46
+ from core_identity_env.models import CoreIdentityObservation, CoreIdentityAction
47
+
48
+
49
+ class CoreIdentityEnv(EnvClient[CoreIdentityAction, CoreIdentityObservation, Dict[str, Any]]):
50
+ """Async client for the Core Identity Environment.
51
+
52
+ Connects to a running CoreIdentityEnvironment server via WebSocket and
53
+ provides step/reset/state methods for interacting with the environment.
54
+
55
+ Inherits from EnvClient:
56
+ - Async by default — use ``async with`` and ``await``
57
+ - Sync wrapper — call ``.sync()`` for synchronous usage
58
+ - ``from_env(repo_id)`` — connect to a Hugging Face Space
59
+ - ``from_docker_image(image)`` — spin up a local Docker container
60
+ """
61
+
62
+ def _step_payload(self, action: CoreIdentityAction) -> Dict[str, Any]:
63
+ """Serialise CoreIdentityAction to JSON payload for the server."""
64
+ return action.model_dump()
65
+
66
+ def _parse_result(self, payload: Dict[str, Any]) -> "StepResult[CoreIdentityObservation]":
67
+ """Deserialise server JSON response to a typed StepResult."""
68
+ metadata = payload.get("metadata", payload)
69
+ obs_data = metadata.get("observation", metadata)
70
+ reward_data = metadata.get("reward", {})
71
+ done = payload.get("done", metadata.get("done", False))
72
+ reward_value = payload.get("reward", reward_data.get("value", 0.0))
73
+
74
+ try:
75
+ observation = CoreIdentityObservation.model_validate(obs_data)
76
+ except Exception:
77
+ observation = CoreIdentityObservation(
78
+ task_id=obs_data.get("task_id", "unknown"),
79
+ task_type=obs_data.get("task_type", "document_verification"),
80
+ task_name=obs_data.get("task_name", "unknown"),
81
+ task_description=obs_data.get("task_description", ""),
82
+ difficulty=obs_data.get("difficulty", "medium"),
83
+ challenge_data=obs_data.get("challenge_data", {}),
84
+ )
85
+
86
+ return StepResult(
87
+ observation=observation,
88
+ reward=reward_value,
89
+ done=done,
90
+ info=metadata.get("info", {}),
91
+ )
92
+
93
+ def _parse_state(self, payload: Dict[str, Any]) -> Dict[str, Any]:
94
+ """Return raw state dict from server."""
95
+ return payload
models.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+ from typing import Any, Optional
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class TaskType(str, Enum):
7
+ """Types of identity verification tasks the environment supports."""
8
+ DOCUMENT_VERIFICATION = "document_verification"
9
+ CREDENTIAL_AUTHENTICATION = "credential_authentication"
10
+ PROFILE_MANAGEMENT = "profile_management"
11
+ FRAUD_DETECTION = "fraud_detection"
12
+
13
+
14
+ class DocumentType(str, Enum):
15
+ """Types of identity documents."""
16
+ PASSPORT = "passport"
17
+ DRIVERS_LICENSE = "drivers_license"
18
+ NATIONAL_ID = "national_id"
19
+ BIRTH_CERTIFICATE = "birth_certificate"
20
+
21
+
22
+ class IdentityDocument(BaseModel):
23
+ """Identity document data for verification."""
24
+ document_type: DocumentType = Field(description="Type of document")
25
+ document_number: str = Field(description="Document identifier number")
26
+ full_name: str = Field(description="Full name as on document")
27
+ date_of_birth: str = Field(description="Date of birth (YYYY-MM-DD)")
28
+ expiry_date: Optional[str] = Field(default=None, description="Document expiry date")
29
+ issuing_country: str = Field(description="Country that issued the document")
30
+ additional_fields: dict[str, Any] = Field(default_factory=dict, description="Additional document data")
31
+
32
+
33
+ class UserCredentials(BaseModel):
34
+ """User credentials for authentication."""
35
+ username: str = Field(description="Username or email")
36
+ password: str = Field(description="Password")
37
+ two_factor_code: Optional[str] = Field(default=None, description="2FA code if enabled")
38
+
39
+
40
+ class UserProfile(BaseModel):
41
+ """User profile information."""
42
+ user_id: str = Field(description="Unique user identifier")
43
+ full_name: str = Field(description="Full name")
44
+ email: str = Field(description="Email address")
45
+ phone: Optional[str] = Field(default=None, description="Phone number")
46
+ date_of_birth: Optional[str] = Field(default=None, description="Date of birth")
47
+ address: Optional[str] = Field(default=None, description="Physical address")
48
+ attributes: dict[str, Any] = Field(default_factory=dict, description="Additional attributes")
49
+
50
+
51
+ class VerificationResult(BaseModel):
52
+ """Result of identity verification."""
53
+ verified: bool = Field(description="Whether verification passed")
54
+ confidence: float = Field(default=0.0, description="Confidence score 0-1")
55
+ issues: list[str] = Field(default_factory=list, description="List of verification issues")
56
+ checks_performed: dict[str, bool] = Field(default_factory=dict, description="Individual check results")
57
+
58
+
59
+ class CoreIdentityObservation(BaseModel):
60
+ """Observation returned by the environment after reset or step."""
61
+ task_id: str = Field(description="Unique identifier for the task")
62
+ task_type: TaskType = Field(description="Type of identity task")
63
+ task_name: str = Field(description="Human-readable name of the task")
64
+ task_description: str = Field(description="Description of what needs to be done")
65
+ difficulty: str = Field(default="medium", description="Difficulty level: easy, medium, hard")
66
+
67
+ document: Optional[IdentityDocument] = Field(default=None, description="Document to verify")
68
+ credentials: Optional[UserCredentials] = Field(default=None, description="Credentials to authenticate")
69
+ profile: Optional[UserProfile] = Field(default=None, description="User profile to manage")
70
+
71
+ expected_verification: Optional[bool] = Field(default=None, description="Expected verification result")
72
+ challenge_data: dict[str, Any] = Field(default_factory=dict, description="Additional challenge data")
73
+ max_steps: int = Field(default=10, description="Maximum allowed steps")
74
+
75
+
76
+ class CoreIdentityAction(BaseModel):
77
+ """Action to perform in the environment."""
78
+ verification: VerificationResult = Field(description="The verification result from the agent")
79
+ submit: bool = Field(default=False, description="Whether to submit the final result")
80
+
81
+
82
+ class CoreIdentityReward(BaseModel):
83
+ """Reward components for the environment."""
84
+ accuracy: float = Field(default=0.0, description="Accuracy of verification")
85
+ completeness: float = Field(default=0.0, description="Completeness of checks performed")
86
+ efficiency: float = Field(default=0.0, description="Efficiency bonus for fewer steps")
87
+ total: float = Field(default=0.0, description="Total reward value")
openenv.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: core_identity_env
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "core_identity_env"
3
+ version = "0.1.0"
4
+ description = "Core Identity environment for verification, authentication, and profile management"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "openenv-core>=0.1.0",
9
+ "fastapi>=0.109.0",
10
+ "uvicorn>=0.27.0",
11
+ "pydantic>=2.5.0",
12
+ ]
13
+
14
+ [project.scripts]
15
+ server = "uvicorn:main"
16
+ core-identity = "core_identity_env.server.app:app"
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=61.0"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["."]
24
+ include = ["core_identity_env*"]
server/Dockerfile ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Identity Environment - Docker configuration for HF Spaces
2
+
3
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
4
+ FROM ${BASE_IMAGE} AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ ARG BUILD_MODE=standalone
9
+
10
+ COPY . /app/env
11
+
12
+ WORKDIR /app/env
13
+
14
+ RUN if ! command -v uv > /dev/null 2>&1; then \
15
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
16
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
17
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
18
+ fi
19
+
20
+ RUN apt-get update && apt-get install -y --no-install-recommends \
21
+ git \
22
+ && rm -rf /var/lib/apt/lists/*
23
+
24
+ RUN --mount=type=cache,target=/root/.cache/uv \
25
+ if [ -f uv.lock ]; then \
26
+ uv sync --frozen --no-install-project --no-editable; \
27
+ else \
28
+ uv sync --no-install-project --no-editable; \
29
+ fi
30
+
31
+ RUN --mount=type=cache,target=/root/.cache/uv \
32
+ if [ -f uv.lock ]; then \
33
+ uv sync --frozen --no-editable; \
34
+ else \
35
+ uv sync --no-editable; \
36
+ fi
37
+
38
+ FROM ${BASE_IMAGE}
39
+
40
+ WORKDIR /app
41
+
42
+ COPY --from=builder /app/env/.venv /app/.venv
43
+ COPY --from=builder /app/env /app/env
44
+
45
+ ENV PATH="/app/.venv/bin:$PATH"
46
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
47
+
48
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
49
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
50
+
51
+ EXPOSE 8000
52
+
53
+ CMD ["sh", "-c", "cd /app/env && uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000"]
server/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from core_identity_env.server.core_identity_environment import CoreIdentityEnvironment
2
+ from core_identity_env.models import CoreIdentityAction, CoreIdentityObservation
3
+
4
+
5
+ def create_app():
6
+ """Create and configure the FastAPI application for the Core Identity Environment."""
7
+ from openenv.core.env_server.http_server import create_app as create_openenv_app
8
+ return create_openenv_app(
9
+ CoreIdentityEnvironment,
10
+ CoreIdentityAction,
11
+ CoreIdentityObservation,
12
+ env_name="core_identity_env",
13
+ )
14
+
15
+
16
+ app = create_app()
server/core_identity_environment.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Core Identity Environment - server-side implementation."""
2
+
3
+ import random
4
+ from typing import Any, Dict, Optional
5
+ from dataclasses import dataclass, field
6
+ from uuid import uuid4
7
+
8
+ try:
9
+ from openenv.core.env_server.interfaces import Environment
10
+ from openenv.core.env_server.types import Action, Observation, State
11
+ except ImportError:
12
+ from openenv.core.env_server.interfaces import Environment
13
+ from openenv.core.env_server.types import Action, Observation, State
14
+
15
+ from core_identity_env.models import (
16
+ CoreIdentityObservation,
17
+ CoreIdentityAction,
18
+ VerificationResult,
19
+ TaskType,
20
+ IdentityDocument,
21
+ UserCredentials,
22
+ UserProfile,
23
+ )
24
+ from core_identity_env.tasks.definitions import (
25
+ get_all_tasks,
26
+ get_task_by_id,
27
+ CoreIdentityTask,
28
+ CoreIdentityTaskEvaluator,
29
+ GradingResult,
30
+ )
31
+
32
+
33
+ @dataclass
34
+ class _EpisodeState:
35
+ task: CoreIdentityTask
36
+ episode_id: str
37
+ current_step: int = 0
38
+ cumulative_reward: float = 0.0
39
+ submitted_verification: Dict[str, Any] = field(default_factory=dict)
40
+ episode_complete: bool = False
41
+
42
+
43
+ DIFFICULTY_WEIGHTS = {
44
+ "easy": 0.15,
45
+ "medium": 0.12,
46
+ "hard": 0.08,
47
+ }
48
+
49
+
50
+ class CoreIdentityEnvironment(Environment):
51
+ """Server-side Core Identity Environment compliant with OpenEnv spec."""
52
+
53
+ def __init__(
54
+ self,
55
+ task_id: Optional[str] = None,
56
+ seed: Optional[int] = None,
57
+ max_steps: int = 10,
58
+ ):
59
+ self._task_id = task_id
60
+ self._seed = seed
61
+ self._max_steps = max_steps
62
+ self._ep: Optional[_EpisodeState] = None
63
+ self._state = State(episode_id=str(uuid4()), step_count=0)
64
+
65
+ if seed is not None:
66
+ random.seed(seed)
67
+
68
+ def reset(
69
+ self,
70
+ seed: Optional[int] = None,
71
+ episode_id: Optional[str] = None,
72
+ task_id: Optional[str] = None,
73
+ **kwargs: Any,
74
+ ) -> Observation:
75
+ if seed is not None:
76
+ random.seed(seed)
77
+
78
+ target_task_id = task_id or self._task_id
79
+ if target_task_id:
80
+ task = get_task_by_id(target_task_id)
81
+ else:
82
+ task = random.choice(get_all_tasks())
83
+
84
+ eid = episode_id or str(uuid4())
85
+
86
+ document = None
87
+ credentials = None
88
+ profile = None
89
+
90
+ if task.document:
91
+ document = IdentityDocument(**task.document)
92
+ if task.credentials:
93
+ credentials = UserCredentials(**task.credentials)
94
+ if task.profile:
95
+ profile = UserProfile(**task.profile)
96
+
97
+ self._ep = _EpisodeState(
98
+ task=task,
99
+ episode_id=eid,
100
+ current_step=0,
101
+ cumulative_reward=0.0,
102
+ submitted_verification={},
103
+ episode_complete=False,
104
+ )
105
+ self._state = State(episode_id=eid, step_count=0)
106
+
107
+ obs = self._build_observation(document, credentials, profile)
108
+ return Observation(
109
+ done=False,
110
+ reward=0.0,
111
+ metadata=obs.model_dump(),
112
+ )
113
+
114
+ def step(
115
+ self,
116
+ action: Action,
117
+ timeout_s: Optional[float] = None,
118
+ **kwargs: Any,
119
+ ) -> Observation:
120
+ if self._ep is None:
121
+ return Observation(
122
+ done=True,
123
+ reward=0.0,
124
+ metadata={"error": "Environment not reset. Call reset() first."},
125
+ )
126
+
127
+ if self._ep.episode_complete:
128
+ return Observation(
129
+ done=True,
130
+ reward=0.0,
131
+ metadata={"error": "Episode already finished."},
132
+ )
133
+
134
+ action_data: Dict[str, Any] = {}
135
+ if hasattr(action, "data") and isinstance(action.data, dict):
136
+ action_data = action.data
137
+ elif isinstance(action, dict):
138
+ action_data = action
139
+ elif hasattr(action, "__dict__"):
140
+ action_data = vars(action)
141
+
142
+ try:
143
+ env_action = CoreIdentityAction.model_validate(action_data)
144
+ except Exception:
145
+ env_action = CoreIdentityAction(verification=VerificationResult())
146
+
147
+ self._ep.current_step += 1
148
+ self._state.step_count = self._ep.current_step
149
+
150
+ verification_dict = env_action.verification.model_dump()
151
+ self._ep.submitted_verification = verification_dict
152
+
153
+ evaluator = CoreIdentityTaskEvaluator(self._ep.task)
154
+ result = evaluator.grade(verification_dict)
155
+
156
+ reward_value = result.score * DIFFICULTY_WEIGHTS.get(self._ep.task.difficulty, 0.1)
157
+
158
+ terminal = env_action.submit or self._ep.current_step >= self._ep.task.max_steps
159
+
160
+ if terminal:
161
+ final_score = result.score
162
+ if self._ep.current_step <= self._ep.task.max_steps * 0.5:
163
+ final_score += 0.1
164
+ reward_value = min(1.0, final_score)
165
+ self._ep.episode_complete = True
166
+
167
+ self._ep.cumulative_reward += reward_value
168
+
169
+ document = None
170
+ credentials = None
171
+ profile = None
172
+ if self._ep.task.document:
173
+ document = IdentityDocument(**self._ep.task.document)
174
+ if self._ep.task.credentials:
175
+ credentials = UserCredentials(**self._ep.task.credentials)
176
+ if self._ep.task.profile:
177
+ profile = UserProfile(**self._ep.task.profile)
178
+
179
+ obs = self._build_observation(document, credentials, profile)
180
+ step_result = {
181
+ "observation": obs.model_dump(),
182
+ "reward": {
183
+ "value": reward_value,
184
+ "accuracy": result.accuracy,
185
+ "completeness": result.completeness,
186
+ "total": result.score,
187
+ "feedback": result.feedback,
188
+ },
189
+ "done": self._ep.episode_complete,
190
+ "info": {
191
+ "step": self._ep.current_step,
192
+ "is_final": self._ep.episode_complete,
193
+ },
194
+ }
195
+
196
+ return Observation(
197
+ done=self._ep.episode_complete,
198
+ reward=reward_value,
199
+ metadata=step_result,
200
+ )
201
+
202
+ @property
203
+ def state(self) -> State:
204
+ return self._state
205
+
206
+ def _build_observation(
207
+ self,
208
+ document: Optional[IdentityDocument],
209
+ credentials: Optional[UserCredentials],
210
+ profile: Optional[UserProfile],
211
+ ) -> CoreIdentityObservation:
212
+ ep = self._ep
213
+ return CoreIdentityObservation(
214
+ task_id=ep.task.task_id,
215
+ task_type=TaskType(ep.task.task_type),
216
+ task_name=ep.task.name,
217
+ task_description=ep.task.description,
218
+ difficulty=ep.task.difficulty,
219
+ document=document,
220
+ credentials=credentials,
221
+ profile=profile,
222
+ expected_verification=ep.task.expected_verification,
223
+ challenge_data=ep.task.challenge_data,
224
+ max_steps=ep.task.max_steps,
225
+ )
226
+
227
+ def get_task_list(self) -> Dict[str, Any]:
228
+ return [
229
+ {
230
+ "task_id": t.task_id,
231
+ "name": t.name,
232
+ "task_type": t.task_type,
233
+ "difficulty": t.difficulty,
234
+ "description": t.description,
235
+ }
236
+ for t in get_all_tasks()
237
+ ]
server/requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi>=0.109.0
2
+ uvicorn>=0.27.0
3
+ pydantic>=2.5.0
4
+ openenv-core
tasks/definitions.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Task definitions and graders for the Core Identity environment."""
2
+
3
+ from typing import List, Dict, Any
4
+ from dataclasses import dataclass
5
+ import re
6
+ from datetime import datetime, timedelta
7
+
8
+
9
+ @dataclass
10
+ class CoreIdentityTask:
11
+ task_id: str
12
+ name: str
13
+ description: str
14
+ task_type: str
15
+ difficulty: str
16
+ document: Dict[str, Any] | None
17
+ credentials: Dict[str, Any] | None
18
+ profile: Dict[str, Any] | None
19
+ expected_verification: bool
20
+ required_checks: List[str]
21
+ challenge_data: Dict[str, Any]
22
+ max_steps: int
23
+
24
+
25
+ @dataclass
26
+ class GradingResult:
27
+ accuracy: float
28
+ completeness: float
29
+ score: float
30
+ feedback: str
31
+
32
+
33
+ class CoreIdentityTaskEvaluator:
34
+ """Evaluate responses for Core Identity tasks."""
35
+
36
+ def __init__(self, task: CoreIdentityTask):
37
+ self.task = task
38
+
39
+ def grade(self, response: Dict[str, Any]) -> GradingResult:
40
+ verification = response.get("verification", {})
41
+ verified = verification.get("verified", False)
42
+ issues = verification.get("issues", [])
43
+ checks_performed = verification.get("checks_performed", {})
44
+
45
+ expected = self.task.expected_verification
46
+
47
+ accuracy = 1.0 if verified == expected else 0.0
48
+
49
+ performed_count = sum(1 for v in checks_performed.values() if v)
50
+ required_count = len(self.task.required_checks)
51
+ completeness = performed_count / required_count if required_count > 0 else 0.5
52
+
53
+ if accuracy == 1.0:
54
+ base_score = 0.7
55
+ completeness_bonus = completeness * 0.3
56
+ else:
57
+ base_score = 0.0
58
+ completeness_bonus = 0.0
59
+
60
+ score = base_score + completeness_bonus
61
+
62
+ feedback = f"Verification: {verified}, Expected: {expected}, Checks: {performed_count}/{required_count}"
63
+
64
+ return GradingResult(
65
+ accuracy=accuracy,
66
+ completeness=completeness,
67
+ score=score,
68
+ feedback=feedback,
69
+ )
70
+
71
+
72
+ def create_document_verification_task() -> CoreIdentityTask:
73
+ return CoreIdentityTask(
74
+ task_id="doc_verify_passport",
75
+ name="Passport Verification",
76
+ description="Verify the authenticity of a passport document",
77
+ task_type="document_verification",
78
+ difficulty="medium",
79
+ document={
80
+ "document_type": "passport",
81
+ "document_number": "P12345678",
82
+ "full_name": "John Smith",
83
+ "date_of_birth": "1990-01-15",
84
+ "expiry_date": "2030-01-15",
85
+ "issuing_country": "United States",
86
+ },
87
+ credentials=None,
88
+ profile=None,
89
+ expected_verification=True,
90
+ required_checks=["expiry_check", "format_check", "country_validation"],
91
+ challenge_data={
92
+ "valid_passport": True,
93
+ "expired_document": False,
94
+ },
95
+ max_steps=5
96
+ )
97
+
98
+
99
+ def create_document_fraud_task() -> CoreIdentityTask:
100
+ return CoreIdentityTask(
101
+ task_id="doc_verify_fake_id",
102
+ name="Fake ID Detection",
103
+ description="Detect a potentially fraudulent ID document",
104
+ task_type="document_verification",
105
+ difficulty="hard",
106
+ document={
107
+ "document_type": "drivers_license",
108
+ "document_number": "DL999999999",
109
+ "full_name": "Jane Doe",
110
+ "date_of_birth": "1985-06-20",
111
+ "expiry_date": "2025-06-20",
112
+ "issuing_country": "California",
113
+ },
114
+ credentials=None,
115
+ profile=None,
116
+ expected_verification=False,
117
+ required_checks=["expiry_check", "format_check", "country_validation", "fraud_indicators"],
118
+ challenge_data={
119
+ "valid_passport": False,
120
+ "expired_document": True,
121
+ "suspicious_number": True,
122
+ "fraud_indicators": ["expired", "invalid_format"],
123
+ },
124
+ max_steps=8
125
+ )
126
+
127
+
128
+ def create_authentication_task() -> CoreIdentityTask:
129
+ return CoreIdentityTask(
130
+ task_id="auth_valid_creds",
131
+ name="Credential Authentication",
132
+ description="Verify user credentials for authentication",
133
+ task_type="credential_authentication",
134
+ difficulty="easy",
135
+ document=None,
136
+ credentials={
137
+ "username": "john.smith@email.com",
138
+ "password": "SecureP@ss123",
139
+ },
140
+ profile=None,
141
+ expected_verification=True,
142
+ required_checks=["username_format", "password_strength"],
143
+ challenge_data={
144
+ "valid_credentials": True,
145
+ "account_locked": False,
146
+ },
147
+ max_steps=3
148
+ )
149
+
150
+
151
+ def create_authentication_fraud_task() -> CoreIdentityTask:
152
+ return CoreIdentityTask(
153
+ task_id="auth_brute_force",
154
+ name="Brute Force Detection",
155
+ description="Detect and block brute force authentication attempts",
156
+ task_type="credential_authentication",
157
+ difficulty="hard",
158
+ document=None,
159
+ credentials={
160
+ "username": "admin@company.com",
161
+ "password": "wrong_password",
162
+ },
163
+ profile=None,
164
+ expected_verification=False,
165
+ required_checks=["username_format", "password_strength", "attempt_tracking", "ip_validation"],
166
+ challenge_data={
167
+ "valid_credentials": False,
168
+ "failed_attempts": 5,
169
+ "suspicious_ip": True,
170
+ "time_window": "5 minutes",
171
+ },
172
+ max_steps=6
173
+ )
174
+
175
+
176
+ def create_profile_task() -> CoreIdentityTask:
177
+ return CoreIdentityTask(
178
+ task_id="profile_update",
179
+ name="Profile Update Validation",
180
+ description="Validate a user profile update request",
181
+ task_type="profile_management",
182
+ difficulty="medium",
183
+ document=None,
184
+ credentials=None,
185
+ profile={
186
+ "user_id": "user_12345",
187
+ "full_name": "Alice Johnson",
188
+ "email": "alice.j@newdomain.com",
189
+ "phone": "+1-555-987-6543",
190
+ "date_of_birth": "1992-03-10",
191
+ },
192
+ expected_verification=True,
193
+ required_checks=["email_format", "phone_format", "age_verification"],
194
+ challenge_data={
195
+ "valid_request": True,
196
+ "requested_changes": ["email", "phone"],
197
+ },
198
+ max_steps=5
199
+ )
200
+
201
+
202
+ def get_all_tasks() -> List[CoreIdentityTask]:
203
+ return [
204
+ create_document_verification_task(),
205
+ create_document_fraud_task(),
206
+ create_authentication_task(),
207
+ create_authentication_fraud_task(),
208
+ create_profile_task(),
209
+ ]
210
+
211
+
212
+ def get_task_by_id(task_id: str) -> CoreIdentityTask:
213
+ for task in get_all_tasks():
214
+ if task.task_id == task_id:
215
+ return task
216
+ raise ValueError(f"Task not found: {task_id}")