122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
"""框架协议与运行工件的离线合同测试。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from jsonschema import Draft202012Validator
|
|
|
|
ROOT = next(
|
|
parent
|
|
for parent in (pathlib.Path(__file__).resolve().parent, *pathlib.Path(__file__).resolve().parents)
|
|
if (parent / "AGENTS.md").is_file() and (parent / ".git").exists()
|
|
)
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from framework.adapters.pi.normalization import normalize_pi_transcript
|
|
from framework.primitives.artifacts import (
|
|
ArtifactError,
|
|
detect_seq_gaps,
|
|
read_jsonl,
|
|
write_jsonl_atomic,
|
|
)
|
|
from framework.primitives.execution import (
|
|
FrameworkEvent,
|
|
FrameworkExecutionRequest,
|
|
FrameworkExecutionResult,
|
|
)
|
|
|
|
SCHEMA_DIR = ROOT / "framework" / "primitives" / "schemas"
|
|
|
|
|
|
class FrameworkProtocolTest(unittest.TestCase):
|
|
def test_schemas_are_valid_draft_2020_documents(self) -> None:
|
|
for path in sorted(SCHEMA_DIR.glob("*.schema.json")):
|
|
with self.subTest(path=path.name):
|
|
document = json.loads(path.read_text(encoding="utf-8"))
|
|
Draft202012Validator.check_schema(document)
|
|
|
|
def test_request_has_no_business_state_fields(self) -> None:
|
|
request = FrameworkExecutionRequest(
|
|
system_prompt="system",
|
|
user_content="user",
|
|
tool_allowlist=("read",),
|
|
)
|
|
self.assertEqual(request.session_mode, "fresh")
|
|
self.assertFalse(hasattr(request, "work_id"))
|
|
self.assertFalse(hasattr(request, "candidate_status"))
|
|
request_schema = json.loads((SCHEMA_DIR / "framework_execution_request.schema.json").read_text(encoding="utf-8"))
|
|
Draft202012Validator(request_schema).validate(request.as_dict())
|
|
|
|
event = FrameworkEvent(
|
|
framework="pi",
|
|
framework_version="test",
|
|
session_id="s1",
|
|
source_seq=1,
|
|
kind="agent",
|
|
phase="completed",
|
|
observed_at="2026-08-24T00:00:00Z",
|
|
)
|
|
event_schema = json.loads((SCHEMA_DIR / "framework_event.schema.json").read_text(encoding="utf-8"))
|
|
Draft202012Validator(event_schema).validate(event.as_dict())
|
|
result = FrameworkExecutionResult(
|
|
status="completed", final_text="{}", requested_model="p/m"
|
|
)
|
|
result_schema = json.loads((SCHEMA_DIR / "framework_execution_result.schema.json").read_text(encoding="utf-8"))
|
|
Draft202012Validator(result_schema).validate(result.as_dict())
|
|
|
|
def test_pi_transcript_produces_common_events_and_preserves_unknown_type(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
source = pathlib.Path(directory) / "pi.jsonl"
|
|
target = pathlib.Path(directory) / "framework-events.jsonl"
|
|
source.write_text(
|
|
json.dumps({"type": "session", "id": "s1"}) + "\n"
|
|
+ json.dumps({"type": "future_event", "payload": "not copied"}) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
result = normalize_pi_transcript(source, target, run_id="run-1", framework_version="test")
|
|
self.assertEqual(result["unknownEventTypes"], ["future_event"])
|
|
normalized = read_jsonl(target)
|
|
self.assertEqual([event["sourceSeq"] for event in normalized], [1, 2])
|
|
self.assertEqual(normalized[1]["kind"], "unknown")
|
|
self.assertNotIn("payload", normalized[1]["safeDetails"])
|
|
|
|
def test_artifact_flush_replay_and_gap_detection(self) -> None:
|
|
events = [
|
|
{
|
|
"framework": "pi",
|
|
"frameworkVersion": "test",
|
|
"sessionId": "s1",
|
|
"sourceSeq": 1,
|
|
"kind": "session",
|
|
"phase": "started",
|
|
"safeDetails": {},
|
|
},
|
|
{
|
|
"framework": "pi",
|
|
"frameworkVersion": "test",
|
|
"sessionId": "s1",
|
|
"sourceSeq": 2,
|
|
"kind": "agent",
|
|
"phase": "completed",
|
|
"safeDetails": {},
|
|
},
|
|
]
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = pathlib.Path(directory) / "transcript.jsonl"
|
|
digest = write_jsonl_atomic(path, events)
|
|
self.assertTrue(digest.startswith("sha256:"))
|
|
self.assertEqual(read_jsonl(path), events)
|
|
self.assertEqual(detect_seq_gaps([events[0], {**events[1], "sourceSeq": 4}]), [(2, 3)])
|
|
with self.assertRaises(ArtifactError):
|
|
write_jsonl_atomic(path, [{**events[0], "sourceSeq": 2}])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|