1037 lines
52 KiB
Python
1037 lines
52 KiB
Python
import asyncio
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
import socket
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import httpx2
|
|
from mcp import Client
|
|
from mcp.client.streamable_http import streamable_http_client
|
|
from mcp.types import TextContent
|
|
|
|
from gewu.adapters.git_repository import GitCanonicalRepository
|
|
from gewu.adapters.source_extractor import LocalSourceExtractor
|
|
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
|
|
from gewu.adapters.sqlite_ingest_reviews import SQLiteIngestReviewStore
|
|
from gewu.adapters.url_fetch import PublicUrlFetcher
|
|
from gewu.application.acceptance import AcceptanceService
|
|
from gewu.application.context import ValidationContext
|
|
from gewu.application.reading import CanonicalNodeReader
|
|
from gewu.application.uploads import LocalArtifactStore
|
|
from gewu.bootstrap.app import create_app
|
|
from gewu.domain.contracts import ProposalOperation, ProposalRevision, Scope, SourceRevisionRecord
|
|
from gewu.domain.validation import evidence_digest, proposal_payload_digest
|
|
from gewu.transports.stdio_connector import SelectedFile, create_connector_server
|
|
from tests.domain.proposal_fixtures import (
|
|
BLOCK_TEXT,
|
|
NODE,
|
|
POLICIES,
|
|
PROJECTS,
|
|
SOURCE_REVISION,
|
|
proposal,
|
|
)
|
|
|
|
|
|
class _FixtureResponse:
|
|
def __init__(self, media_type: str, content: bytes, status: int = 200) -> None:
|
|
self.media_type = media_type
|
|
self.content = content
|
|
self.status = status
|
|
self.offset = 0
|
|
|
|
def getheader(self, name: str) -> str | None:
|
|
return {
|
|
"Content-Type": f"{self.media_type}; charset=utf-8",
|
|
"X-Source-Title": "Allowlisted fixture",
|
|
}.get(name)
|
|
|
|
def read(self, amount: int) -> bytes:
|
|
result = self.content[self.offset : self.offset + amount]
|
|
self.offset += len(result)
|
|
return result
|
|
|
|
|
|
class _FixtureConnection:
|
|
def __init__(self) -> None:
|
|
self.closed = False
|
|
self.path = ""
|
|
|
|
def request(self, _method: str, path: str, headers: dict[str, str]) -> None:
|
|
self.path = path
|
|
assert headers["Accept-Encoding"] == "identity"
|
|
return None
|
|
|
|
def getresponse(self) -> _FixtureResponse:
|
|
if self.path.startswith("/missing"):
|
|
return _FixtureResponse("text/plain", b"unavailable", status=404)
|
|
if self.path.startswith("/source.md?rev=2"):
|
|
return _FixtureResponse("text/markdown", b"# Captured fixture\nRevised evidence.\n")
|
|
if self.path.startswith("/page.html"):
|
|
return _FixtureResponse(
|
|
"text/html",
|
|
b"<article><h1>Web evidence</h1><p>Exact HTML statement.</p>"
|
|
b"<script>ignoreInjection()</script></article>",
|
|
)
|
|
if self.path.startswith("/paper.pdf"):
|
|
return _FixtureResponse("application/pdf", _pdf_fixture())
|
|
if self.path.startswith("/malformed.pdf"):
|
|
return _FixtureResponse("application/pdf", b"%PDF-1.4\nnot a valid PDF")
|
|
return _FixtureResponse(
|
|
"text/markdown", b"# Captured fixture\nAn exact source statement.\n"
|
|
)
|
|
|
|
def close(self) -> None:
|
|
self.closed = True
|
|
|
|
|
|
def _fixture_fetcher() -> PublicUrlFetcher:
|
|
def resolver(host: str, port: int, **_kwargs: Any) -> list[tuple[Any, ...]]:
|
|
return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("93.184.216.34", port))]
|
|
|
|
return PublicUrlFetcher(
|
|
{"research.example"},
|
|
resolver=resolver,
|
|
connection_factory=lambda *_args: _FixtureConnection(),
|
|
)
|
|
|
|
|
|
def _text_payload(result: Any) -> str:
|
|
assert result.content and isinstance(result.content[0], TextContent)
|
|
return result.content[0].text
|
|
|
|
|
|
def _pdf_fixture() -> bytes:
|
|
stream = b"BT /F1 12 Tf 72 720 Td (Archived PDF evidence.) Tj ET"
|
|
objects = [
|
|
b"<< /Type /Catalog /Pages 2 0 R >>",
|
|
b"<< /Type /Pages /Kids [4 0 R] /Count 1 >>",
|
|
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
|
|
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] "
|
|
b"/Resources << /Font << /F1 3 0 R >> >> /Contents 5 0 R >>",
|
|
b"<< /Length " + str(len(stream)).encode() + b" >>\nstream\n" + stream + b"\nendstream",
|
|
]
|
|
result = bytearray(b"%PDF-1.4\n")
|
|
offsets = [0]
|
|
for number, item in enumerate(objects, start=1):
|
|
offsets.append(len(result))
|
|
result.extend(f"{number} 0 obj\n".encode() + item + b"\nendobj\n")
|
|
xref = len(result)
|
|
result.extend(f"xref\n0 {len(offsets)}\n0000000000 65535 f \n".encode())
|
|
for offset in offsets[1:]:
|
|
result.extend(f"{offset:010d} 00000 n \n".encode())
|
|
result.extend(
|
|
f"trailer\n<< /Size {len(offsets)} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n".encode()
|
|
)
|
|
return bytes(result)
|
|
|
|
|
|
def test_mcp_source_to_citation_vertical_slice(tmp_path: Path) -> None:
|
|
async def exercise() -> None:
|
|
repository = GitCanonicalRepository(tmp_path / "knowledge.git")
|
|
artifacts = LocalArtifactStore(tmp_path)
|
|
extractor = LocalSourceExtractor()
|
|
context = ValidationContext(
|
|
projects=PROJECTS,
|
|
policies=POLICIES,
|
|
sources={},
|
|
extractions={},
|
|
existing_nodes={},
|
|
)
|
|
identities = SQLiteIdentityStore(
|
|
tmp_path / "identity.sqlite3",
|
|
authority_lock_path=tmp_path / "workflow/locks/canonical-writer.lock",
|
|
)
|
|
token = identities.provision(
|
|
credential_id="pilot-agent-key",
|
|
principal_id="agent-pilot",
|
|
roles=("agent",),
|
|
project_ids=("dome-performance", "shared"),
|
|
)
|
|
viewer_token = identities.provision(
|
|
credential_id="pilot-viewer-key",
|
|
principal_id="pilot-viewer",
|
|
roles=("viewer",),
|
|
project_ids=("dome-performance", "shared"),
|
|
)
|
|
acceptance = AcceptanceService(
|
|
repository=repository,
|
|
artifact_store=artifacts,
|
|
workflow_root=tmp_path / "workflow",
|
|
context_provider=lambda: context,
|
|
actor_is_current=lambda actor: identities.is_current(
|
|
actor.principal_id,
|
|
actor.credential_id,
|
|
roles=actor.roles,
|
|
project_ids=actor.project_ids,
|
|
),
|
|
)
|
|
app = create_app(
|
|
identity_provider=identities,
|
|
node_reader=CanonicalNodeReader(repository),
|
|
artifact_store=artifacts,
|
|
extractor=extractor,
|
|
acceptance_service=acceptance,
|
|
context_provider=lambda: context,
|
|
url_fetcher=_fixture_fetcher(),
|
|
ingest_review_store=SQLiteIngestReviewStore(tmp_path / "ingest-reviews.sqlite3"),
|
|
)
|
|
async with app.router.lifespan_context(app):
|
|
async with httpx2.AsyncClient(
|
|
transport=httpx2.ASGITransport(app=app),
|
|
base_url="http://localhost",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
) as http:
|
|
async with Client(
|
|
streamable_http_client("http://localhost/mcp", http_client=http)
|
|
) as mcp:
|
|
tools = await mcp.list_tools()
|
|
assert {tool.name for tool in tools.tools} == {
|
|
"diagnostics",
|
|
"get_node",
|
|
"node_history",
|
|
"get_project_context",
|
|
"get_source",
|
|
"ingest_review_create",
|
|
"source_upload_begin",
|
|
"source_capture_text",
|
|
"source_capture_url",
|
|
"source_upload_status",
|
|
"source_upload_chunk",
|
|
"source_upload_finalize",
|
|
"source_extract",
|
|
"source_extraction_block",
|
|
"proposal_prepare",
|
|
"proposal_evaluate",
|
|
"proposal_submit",
|
|
"proposal_status",
|
|
"proposal_withdraw",
|
|
"run_create",
|
|
"run_checkpoint",
|
|
"run_status",
|
|
}
|
|
project_context = await mcp.call_tool(
|
|
"get_project_context", {"project_id": "dome-performance"}
|
|
)
|
|
assert not project_context.is_error
|
|
lens_result = json.loads(_text_payload(project_context))
|
|
assert (
|
|
lens_result["lens"]["north_star"] == PROJECTS["dome-performance"].north_star
|
|
)
|
|
assert lens_result["lens"]["key_questions"] == list(
|
|
PROJECTS["dome-performance"].key_questions
|
|
)
|
|
assert "autonomy_policy_id" not in lens_result["lens"]
|
|
draft = proposal().model_dump(mode="json")
|
|
draft.pop("payload_digest")
|
|
for operation in draft["operations"]:
|
|
node = operation.get("node")
|
|
if node is None:
|
|
continue
|
|
for group in ("claims", "assumptions", "limitations"):
|
|
for claim in node.get(group, []):
|
|
claim["assessment"].pop("assertion_digest")
|
|
claim["assessment"].pop("evidence_digests")
|
|
for evidence in claim["evidence"]:
|
|
evidence.pop("assertion_path")
|
|
evidence.pop("assertion_digest")
|
|
prepared_result = await mcp.call_tool(
|
|
"proposal_prepare",
|
|
{"proposal_json": json.dumps(draft, ensure_ascii=False)},
|
|
)
|
|
assert not prepared_result.is_error, prepared_result.content
|
|
prepared_payload = json.loads(_text_payload(prepared_result))
|
|
assert prepared_payload["status"] == "prepared"
|
|
prepared_proposal = ProposalRevision.model_validate_json(
|
|
prepared_payload["proposal_json"], strict=True
|
|
)
|
|
assert prepared_payload["payload_digest"] == prepared_proposal.payload_digest
|
|
assert prepared_proposal.payload_digest == proposal_payload_digest(
|
|
prepared_proposal
|
|
)
|
|
invalid_draft = json.loads(json.dumps(draft))
|
|
invalid_evidence = invalid_draft["operations"][1]["node"]["claims"][0][
|
|
"evidence"
|
|
][0]
|
|
invalid_evidence["locator"]["kind"] = "text_span"
|
|
invalid_evidence["locator"]["page"] = 1
|
|
invalid_result = await mcp.call_tool(
|
|
"proposal_prepare",
|
|
{"proposal_json": json.dumps(invalid_draft, ensure_ascii=False)},
|
|
)
|
|
assert not invalid_result.is_error, invalid_result.content
|
|
invalid_payload = json.loads(_text_payload(invalid_result))
|
|
assert invalid_payload["status"] == "invalid"
|
|
assert any(
|
|
issue["path"].endswith("locator")
|
|
and "PDF locators" in issue["message"]
|
|
for issue in invalid_payload["issues"]
|
|
)
|
|
invalid_review = await mcp.call_tool(
|
|
"ingest_review_create", {"review_json": "{}"}
|
|
)
|
|
assert not invalid_review.is_error, invalid_review.content
|
|
review_payload = json.loads(_text_payload(invalid_review))
|
|
assert review_payload["status"] == "invalid"
|
|
assert {issue["path"] for issue in review_payload["issues"]} >= {
|
|
"review_id",
|
|
"extraction_id",
|
|
"relevance_rationale",
|
|
}
|
|
denied_lens = await mcp.call_tool(
|
|
"get_project_context", {"project_id": "interactive-video"}
|
|
)
|
|
assert denied_lens.is_error
|
|
async with httpx2.AsyncClient(
|
|
transport=httpx2.ASGITransport(app=app),
|
|
base_url="http://localhost",
|
|
headers={"Authorization": f"Bearer {viewer_token}"},
|
|
) as viewer_http:
|
|
async with Client(
|
|
streamable_http_client("http://localhost/mcp", http_client=viewer_http)
|
|
) as viewer_mcp:
|
|
viewer_proposal = await viewer_mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": proposal().model_dump_json()},
|
|
)
|
|
assert viewer_proposal.is_error
|
|
viewer_prepare = await viewer_mcp.call_tool(
|
|
"proposal_prepare",
|
|
{"proposal_json": json.dumps(draft, ensure_ascii=False)},
|
|
)
|
|
assert viewer_prepare.is_error
|
|
viewer_upload = await viewer_mcp.call_tool(
|
|
"source_upload_begin",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"filename": "viewer.md",
|
|
"media_type": "text/markdown",
|
|
"byte_length": 4,
|
|
"expected_sha256": hashlib.sha256(b"data").hexdigest(),
|
|
},
|
|
)
|
|
assert viewer_upload.is_error
|
|
viewer_paste = await viewer_mcp.call_tool(
|
|
"source_capture_text",
|
|
{
|
|
"capture_id": "viewer-capture-1",
|
|
"project_id": "dome-performance",
|
|
"source_key": "viewer-paste",
|
|
"text": "Viewer supplied text",
|
|
},
|
|
)
|
|
assert viewer_paste.is_error
|
|
diagnostics_result = await mcp.call_tool(
|
|
"diagnostics",
|
|
{"projects": ["dome-performance"], "include_shared": True},
|
|
)
|
|
assert not diagnostics_result.is_error
|
|
diagnostics = json.loads(_text_payload(diagnostics_result))
|
|
assert diagnostics["workflow_status"] == "not_verified"
|
|
assert diagnostics["selected_scope"]["include_shared"] is True
|
|
assert "proposal_submit" in diagnostics["authorized_operations"]
|
|
assert "run_checkpoint" in diagnostics["authorized_operations"]
|
|
assert token not in _text_payload(diagnostics_result)
|
|
run_request = {
|
|
"run_id": "mcp-run-pilot",
|
|
"objective": "Compare audience response",
|
|
"scope": {
|
|
"projects": ["dome-performance"],
|
|
"include_shared": False,
|
|
"all_projects": False,
|
|
},
|
|
"lens": "Audience behavior",
|
|
"pack_id": "gewu",
|
|
"pack_version": "0.3.0",
|
|
"pack_snapshot_digest": "a" * 64,
|
|
"module_pins": [
|
|
{
|
|
"module_id": "profile",
|
|
"implementation_version": "1.0.0",
|
|
"sha256": "b" * 64,
|
|
}
|
|
],
|
|
}
|
|
created_run = await mcp.call_tool(
|
|
"run_create", {"request_json": json.dumps(run_request)}
|
|
)
|
|
assert not created_run.is_error
|
|
run_record = json.loads(_text_payload(created_run))
|
|
assert run_record["revision"] == 0
|
|
checkpoint = {
|
|
"run_id": "mcp-run-pilot",
|
|
"expected_revision": 0,
|
|
"stage": "source-review",
|
|
}
|
|
saved_run = await mcp.call_tool(
|
|
"run_checkpoint", {"request_json": json.dumps(checkpoint)}
|
|
)
|
|
assert not saved_run.is_error
|
|
assert json.loads(_text_payload(saved_run))["revision"] == 1
|
|
read_run = await mcp.call_tool("run_status", {"run_id": "mcp-run-pilot"})
|
|
assert not read_run.is_error
|
|
assert json.loads(_text_payload(read_run))["revision"] == 1
|
|
denied = await mcp.call_tool(
|
|
"source_upload_begin",
|
|
{
|
|
"project_id": "interactive-video",
|
|
"filename": "private.md",
|
|
"media_type": "text/markdown",
|
|
"byte_length": 4,
|
|
"expected_sha256": "0" * 64,
|
|
},
|
|
)
|
|
assert denied.is_error
|
|
forbidden_capture = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "interactive-video",
|
|
"url": "https://research.example/source.md",
|
|
},
|
|
)
|
|
assert forbidden_capture.is_error
|
|
forbidden_paste = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{
|
|
"capture_id": "out-of-scope-paste-1",
|
|
"project_id": "interactive-video",
|
|
"source_key": "out-of-scope-paste",
|
|
"text": "Must not be uploaded",
|
|
},
|
|
)
|
|
assert forbidden_paste.is_error
|
|
unallowlisted_capture = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://internal.example/private",
|
|
},
|
|
)
|
|
assert unallowlisted_capture.is_error
|
|
captured = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/source.md?token=do-not-echo",
|
|
},
|
|
)
|
|
assert not captured.is_error
|
|
capture_result = json.loads(_text_payload(captured))
|
|
assert capture_result["title"] == "Allowlisted fixture"
|
|
assert capture_result["media_type"] == "text/markdown"
|
|
assert "do-not-echo" not in _text_payload(captured)
|
|
assert capture_result["original_url"] == ("https://research.example/source.md")
|
|
captured_source = SourceRevisionRecord.model_validate_json(
|
|
json.dumps(capture_result["source_candidate"]), strict=True
|
|
)
|
|
assert captured_source.archive_status == "archived"
|
|
assert captured_source.final_url == "https://research.example/source.md"
|
|
assert captured_source.artifact_sha256 == capture_result["sha256"]
|
|
assert captured_source.capture_method == "url-fetch"
|
|
assert f":r:{captured_source.artifact_sha256}:" in captured_source.revision_id
|
|
pasted_text = "## Meeting note\nPasted evidence: 400 visitors.\n"
|
|
pasted_arguments = {
|
|
"capture_id": "interview-paste-1",
|
|
"project_id": "dome-performance",
|
|
"source_key": "research-interview-2026-10-14",
|
|
"text": pasted_text,
|
|
"title": "Research interview notes",
|
|
"original_url": "https://research.example/interview?token=secret",
|
|
"media_type": "text/markdown",
|
|
"source_type": "internal",
|
|
}
|
|
pasted = await mcp.call_tool("source_capture_text", pasted_arguments)
|
|
assert not pasted.is_error
|
|
pasted_retry = await mcp.call_tool("source_capture_text", pasted_arguments)
|
|
assert not pasted_retry.is_error
|
|
assert json.loads(_text_payload(pasted_retry)) == json.loads(
|
|
_text_payload(pasted)
|
|
)
|
|
changed_paste_retry = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{**pasted_arguments, "text": pasted_text + "changed"},
|
|
)
|
|
assert changed_paste_retry.is_error
|
|
invalid_capture_id = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{**pasted_arguments, "capture_id": "invalid capture id"},
|
|
)
|
|
assert invalid_capture_id.is_error
|
|
pasted_result = json.loads(_text_payload(pasted))
|
|
pasted_source = SourceRevisionRecord.model_validate_json(
|
|
json.dumps(pasted_result["source_candidate"]), strict=True
|
|
)
|
|
assert pasted_source.capture_method == "pasted-text"
|
|
assert pasted_source.original_url == "https://research.example/interview"
|
|
assert "secret" not in _text_payload(pasted)
|
|
capture_journal = tmp_path / "staging" / "capture-receipts"
|
|
assert all(
|
|
pasted_text.encode() not in path.read_bytes()
|
|
for path in capture_journal.glob("*.json")
|
|
)
|
|
assert (
|
|
pasted_source.artifact_sha256
|
|
== hashlib.sha256(pasted_text.encode()).hexdigest()
|
|
)
|
|
assert pasted_result["byte_length"] == len(pasted_text.encode())
|
|
assert pasted_result["extraction_id"]
|
|
pasted_extraction = artifacts.get_extraction(
|
|
pasted_result["extraction_id"],
|
|
owner_id="agent-pilot",
|
|
project_id="dome-performance",
|
|
)
|
|
assert pasted_extraction is not None
|
|
assert pasted_extraction.source_revision_id == pasted_source.revision_id
|
|
assert (
|
|
"Pasted evidence: 400 visitors."
|
|
in pasted_extraction.blocks[0].original_text
|
|
)
|
|
oversized_paste = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{
|
|
"capture_id": "oversized-paste-1",
|
|
"project_id": "dome-performance",
|
|
"source_key": "oversized-paste",
|
|
"text": "x" * 1_000_001,
|
|
},
|
|
)
|
|
assert oversized_paste.is_error
|
|
control_character_paste = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{
|
|
"capture_id": "invalid-paste-1",
|
|
"project_id": "dome-performance",
|
|
"source_key": "invalid-paste",
|
|
"text": "unsafe\x00content",
|
|
},
|
|
)
|
|
assert control_character_paste.is_error
|
|
oversized_author_paste = await mcp.call_tool(
|
|
"source_capture_text",
|
|
{
|
|
"capture_id": "invalid-author-paste-1",
|
|
"project_id": "dome-performance",
|
|
"source_key": "invalid-author-paste",
|
|
"text": "valid body",
|
|
"authors": ["a" * 301],
|
|
},
|
|
)
|
|
assert oversized_author_paste.is_error
|
|
changed_capture = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/source.md?rev=2",
|
|
},
|
|
)
|
|
assert not changed_capture.is_error
|
|
changed_source = SourceRevisionRecord.model_validate_json(
|
|
json.dumps(json.loads(_text_payload(changed_capture))["source_candidate"]),
|
|
strict=True,
|
|
)
|
|
assert changed_source.source_id == captured_source.source_id
|
|
assert changed_source.revision_id != captured_source.revision_id
|
|
with artifacts.open_artifact(
|
|
capture_result["sha256"], project_id="dome-performance"
|
|
) as artifact:
|
|
assert artifact.read() == (
|
|
b"# Captured fixture\nAn exact source statement.\n"
|
|
)
|
|
failed_capture = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/missing?access-token=must-not-leak",
|
|
"title": "Temporarily unavailable paper",
|
|
"source_type": "paper",
|
|
},
|
|
)
|
|
assert not failed_capture.is_error
|
|
failed_result = json.loads(_text_payload(failed_capture))
|
|
failed_source = SourceRevisionRecord.model_validate_json(
|
|
json.dumps(failed_result["source_candidate"]), strict=True
|
|
)
|
|
assert failed_result["status"] == "capture_failed"
|
|
assert failed_source.archive_status == "capture-failed"
|
|
assert failed_source.artifact_sha256 is None
|
|
assert "access-token" not in _text_payload(failed_capture)
|
|
failed_proposal = ProposalRevision(
|
|
proposal_id="proposal-failed-url-capture",
|
|
revision=1,
|
|
initiating_project_id="dome-performance",
|
|
scope=Scope(projects=("dome-performance",), include_shared=True),
|
|
base_commit=repository.head(),
|
|
payload_digest="0" * 64,
|
|
proposer_id="agent-pilot",
|
|
run_id="run-failed-url-capture",
|
|
operations=(
|
|
ProposalOperation(operation="register_source", source=failed_source),
|
|
),
|
|
)
|
|
failed_proposal = failed_proposal.model_copy(
|
|
update={"payload_digest": proposal_payload_digest(failed_proposal)}
|
|
)
|
|
registered_failure = await mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": failed_proposal.model_dump_json()},
|
|
)
|
|
assert not registered_failure.is_error
|
|
assert (
|
|
json.loads(_text_payload(registered_failure))["receipt"]["state"]
|
|
== "accepted"
|
|
)
|
|
captured_html = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/page.html",
|
|
},
|
|
)
|
|
assert not captured_html.is_error
|
|
html_result = json.loads(_text_payload(captured_html))
|
|
assert html_result["media_type"] == "text/html"
|
|
html_extraction_result = await mcp.call_tool(
|
|
"source_extract",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"artifact_sha256": html_result["sha256"],
|
|
"source_revision_id": "shared:web-page:r1",
|
|
},
|
|
)
|
|
assert not html_extraction_result.is_error
|
|
html_extraction = json.loads(_text_payload(html_extraction_result))
|
|
html_block_result = await mcp.call_tool(
|
|
"source_extraction_block",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"extraction_id": html_extraction["extraction_id"],
|
|
"block_id": html_extraction["blocks"][0]["block_id"],
|
|
"offset": 0,
|
|
"length": 500,
|
|
},
|
|
)
|
|
assert not html_block_result.is_error
|
|
html_block = json.loads(_text_payload(html_block_result))
|
|
assert "Exact HTML statement." in html_block["normalized_text"]
|
|
assert "original_text" not in html_block
|
|
assert "normalization_map" not in html_block
|
|
mapped_html_result = await mcp.call_tool(
|
|
"source_extraction_block",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"extraction_id": html_extraction["extraction_id"],
|
|
"block_id": html_extraction["blocks"][0]["block_id"],
|
|
"offset": 0,
|
|
"length": 500,
|
|
"include_original_mapping": True,
|
|
},
|
|
)
|
|
assert not mapped_html_result.is_error
|
|
mapped_html = json.loads(_text_payload(mapped_html_result))
|
|
assert mapped_html["original_text"]
|
|
assert len(mapped_html["normalization_map"]) == len(
|
|
mapped_html["normalized_text"]
|
|
)
|
|
assert "ignoreInjection" not in html_block["normalized_text"]
|
|
captured_pdf = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/paper.pdf",
|
|
},
|
|
)
|
|
assert not captured_pdf.is_error
|
|
pdf_result = json.loads(_text_payload(captured_pdf))
|
|
assert pdf_result["media_type"] == "application/pdf"
|
|
with artifacts.open_artifact(
|
|
pdf_result["sha256"], project_id="dome-performance"
|
|
) as artifact:
|
|
assert artifact.read() == _pdf_fixture()
|
|
pdf_extracted = await mcp.call_tool(
|
|
"source_extract",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"artifact_sha256": pdf_result["sha256"],
|
|
"source_revision_id": "shared:pdf:r1",
|
|
},
|
|
)
|
|
assert not pdf_extracted.is_error
|
|
pdf_record = json.loads(_text_payload(pdf_extracted))
|
|
assert pdf_record["blocks"][0]["page"] == 1
|
|
pdf_block_result = await mcp.call_tool(
|
|
"source_extraction_block",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"extraction_id": pdf_record["extraction_id"],
|
|
"block_id": pdf_record["blocks"][0]["block_id"],
|
|
"offset": 0,
|
|
"length": 500,
|
|
},
|
|
)
|
|
assert not pdf_block_result.is_error
|
|
pdf_block = json.loads(_text_payload(pdf_block_result))
|
|
assert pdf_block["page"] == 1
|
|
assert "Archived PDF evidence." in pdf_block["normalized_text"]
|
|
assert "normalization_map" not in pdf_block
|
|
malformed_pdf = await mcp.call_tool(
|
|
"source_capture_url",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"url": "https://research.example/malformed.pdf",
|
|
},
|
|
)
|
|
assert not malformed_pdf.is_error
|
|
malformed_result = json.loads(_text_payload(malformed_pdf))
|
|
malformed_extraction = await mcp.call_tool(
|
|
"source_extract",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"artifact_sha256": malformed_result["sha256"],
|
|
"source_revision_id": malformed_result["source_candidate"][
|
|
"revision_id"
|
|
],
|
|
},
|
|
)
|
|
assert not malformed_extraction.is_error
|
|
unavailable = json.loads(_text_payload(malformed_extraction))
|
|
assert unavailable["status"] == "unavailable"
|
|
assert unavailable["error_code"] == "PDF_UNSUPPORTED"
|
|
content = BLOCK_TEXT.encode("utf-8")
|
|
digest = hashlib.sha256(content).hexdigest()
|
|
interrupted_begin = await mcp.call_tool(
|
|
"source_upload_begin",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"filename": "interrupted.md",
|
|
"media_type": "text/markdown",
|
|
"byte_length": len(content),
|
|
"expected_sha256": digest,
|
|
},
|
|
)
|
|
assert not interrupted_begin.is_error
|
|
interrupted_upload = json.loads(_text_payload(interrupted_begin))
|
|
interrupted_finalize = await mcp.call_tool(
|
|
"source_upload_finalize",
|
|
{"upload_id": interrupted_upload["upload_id"]},
|
|
)
|
|
assert interrupted_finalize.is_error
|
|
uncitable = await mcp.call_tool(
|
|
"source_extract",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"artifact_sha256": digest,
|
|
"source_revision_id": SOURCE_REVISION.revision_id,
|
|
},
|
|
)
|
|
assert uncitable.is_error
|
|
|
|
begun = await mcp.call_tool(
|
|
"source_upload_begin",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"filename": "pilot.md",
|
|
"media_type": "text/markdown",
|
|
"byte_length": len(content),
|
|
"expected_sha256": digest,
|
|
},
|
|
)
|
|
assert not begun.is_error
|
|
upload = json.loads(_text_payload(begun))
|
|
wrong_digest_chunk = await mcp.call_tool(
|
|
"source_upload_chunk",
|
|
{
|
|
"upload_id": upload["upload_id"],
|
|
"offset": 0,
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
"chunk_sha256": "0" * 64,
|
|
},
|
|
)
|
|
assert wrong_digest_chunk.is_error
|
|
chunked = await mcp.call_tool(
|
|
"source_upload_chunk",
|
|
{
|
|
"upload_id": upload["upload_id"],
|
|
"offset": 0,
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
"chunk_sha256": digest,
|
|
},
|
|
)
|
|
assert not chunked.is_error
|
|
finalized = await mcp.call_tool(
|
|
"source_upload_finalize", {"upload_id": upload["upload_id"]}
|
|
)
|
|
assert not finalized.is_error
|
|
artifact = json.loads(_text_payload(finalized))
|
|
extracted = await mcp.call_tool(
|
|
"source_extract",
|
|
{
|
|
"project_id": "dome-performance",
|
|
"artifact_sha256": artifact["sha256"],
|
|
"source_revision_id": SOURCE_REVISION.revision_id,
|
|
},
|
|
)
|
|
assert not extracted.is_error
|
|
extraction_info = json.loads(_text_payload(extracted))
|
|
record = extractor.extract(
|
|
SOURCE_REVISION.revision_id, content, "text/markdown"
|
|
)
|
|
assert extraction_info["extraction_id"] == record.extraction_id
|
|
|
|
evidence = (
|
|
NODE.claims[0]
|
|
.evidence[0]
|
|
.model_copy(
|
|
update={
|
|
"extraction_id": record.extraction_id,
|
|
"locator": NODE.claims[0]
|
|
.evidence[0]
|
|
.locator.model_copy(update={"block_id": record.blocks[0].block_id}),
|
|
}
|
|
)
|
|
)
|
|
claim = NODE.claims[0].model_copy(
|
|
update={
|
|
"evidence": (evidence,),
|
|
"assessment": NODE.claims[0].assessment.model_copy(
|
|
update={"evidence_digests": (evidence_digest(evidence),)}
|
|
),
|
|
}
|
|
)
|
|
node = NODE.model_copy(update={"claims": (claim,)})
|
|
source = SOURCE_REVISION.model_copy(
|
|
update={"artifact_sha256": artifact["sha256"], "byte_length": len(content)}
|
|
)
|
|
candidate = proposal(node=node, source=source, base_commit=repository.head())
|
|
candidate = candidate.model_copy(update={"payload_digest": "0" * 64})
|
|
candidate = candidate.model_copy(
|
|
update={"payload_digest": proposal_payload_digest(candidate)}
|
|
)
|
|
|
|
wrong_location_evidence = evidence.model_copy(
|
|
update={
|
|
"quote": "400 visitors",
|
|
"locator": evidence.locator.model_copy(update={"start": 0, "end": 13}),
|
|
}
|
|
)
|
|
wrong_location_claim = claim.model_copy(
|
|
update={
|
|
"evidence": (wrong_location_evidence,),
|
|
"assessment": claim.assessment.model_copy(
|
|
update={
|
|
"evidence_digests": (evidence_digest(wrong_location_evidence),)
|
|
}
|
|
),
|
|
}
|
|
)
|
|
wrong_location_node = node.model_copy(
|
|
update={"claims": (wrong_location_claim,)}
|
|
)
|
|
wrong_location = proposal(
|
|
node=wrong_location_node,
|
|
source=source,
|
|
base_commit=repository.head(),
|
|
)
|
|
wrong_location = wrong_location.model_copy(
|
|
update={
|
|
"proposal_id": "proposal-wrong-location",
|
|
"payload_digest": "0" * 64,
|
|
}
|
|
)
|
|
wrong_location = wrong_location.model_copy(
|
|
update={"payload_digest": proposal_payload_digest(wrong_location)}
|
|
)
|
|
head_before_evaluate = repository.head()
|
|
invalid_evaluation = await mcp.call_tool(
|
|
"proposal_evaluate",
|
|
{"proposal_json": wrong_location.model_dump_json()},
|
|
)
|
|
assert not invalid_evaluation.is_error, invalid_evaluation.content
|
|
invalid_evaluation_payload = json.loads(_text_payload(invalid_evaluation))
|
|
assert invalid_evaluation_payload["status"] == "invalid"
|
|
assert "EVIDENCE_MISMATCH" in {
|
|
finding["code"] for finding in invalid_evaluation_payload["findings"]
|
|
}
|
|
assert repository.head() == head_before_evaluate
|
|
rejected = await mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": wrong_location.model_dump_json()},
|
|
)
|
|
assert not rejected.is_error, rejected.content
|
|
rejected_outcome = json.loads(_text_payload(rejected))
|
|
assert rejected_outcome["status"] == "invalid"
|
|
assert "EVIDENCE_MISMATCH" in {
|
|
finding["code"] for finding in rejected_outcome["findings"]
|
|
}
|
|
rejected_status = await mcp.call_tool(
|
|
"proposal_status",
|
|
{"proposal_id": "proposal-wrong-location", "revision": 1},
|
|
)
|
|
assert not rejected_status.is_error, rejected_status.content
|
|
rejected_status_payload = json.loads(_text_payload(rejected_status))
|
|
assert rejected_status_payload["lifecycle"] == "evaluated"
|
|
assert rejected_status_payload["validation_status"] == "invalid"
|
|
assert rejected_status_payload["receipt"] is None
|
|
assert "quote" not in _text_payload(rejected_status)
|
|
withdrawn = await mcp.call_tool(
|
|
"proposal_withdraw",
|
|
{"proposal_id": "proposal-wrong-location", "revision": 1},
|
|
)
|
|
assert not withdrawn.is_error, withdrawn.content
|
|
assert json.loads(_text_payload(withdrawn))["lifecycle"] == "withdrawn"
|
|
resubmitted = await mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": wrong_location.model_dump_json()},
|
|
)
|
|
assert resubmitted.is_error
|
|
|
|
candidate_head = repository.head()
|
|
valid_evaluation = await mcp.call_tool(
|
|
"proposal_evaluate",
|
|
{"proposal_json": candidate.model_dump_json()},
|
|
)
|
|
assert not valid_evaluation.is_error, valid_evaluation.content
|
|
valid_evaluation_payload = json.loads(_text_payload(valid_evaluation))
|
|
assert valid_evaluation_payload["status"] == "eligible"
|
|
assert valid_evaluation_payload["eligible_for_auto_accept"]
|
|
assert repository.head() == candidate_head
|
|
spoofed_assessment = claim.assessment.model_copy(
|
|
update={"assessor_id": "agent-other"}
|
|
)
|
|
spoofed_claim = claim.model_copy(update={"assessment": spoofed_assessment})
|
|
spoofed_node = node.model_copy(update={"claims": (spoofed_claim,)})
|
|
spoofed = proposal(
|
|
node=spoofed_node,
|
|
source=source,
|
|
base_commit=repository.head(),
|
|
)
|
|
spoofed = spoofed.model_copy(update={"payload_digest": "0" * 64})
|
|
spoofed = spoofed.model_copy(
|
|
update={"payload_digest": proposal_payload_digest(spoofed)}
|
|
)
|
|
spoofed_result = await mcp.call_tool(
|
|
"proposal_evaluate",
|
|
{"proposal_json": spoofed.model_dump_json()},
|
|
)
|
|
assert not spoofed_result.is_error, spoofed_result.content
|
|
assert "ASSESSOR_ATTRIBUTION_MISMATCH" in {
|
|
finding["code"]
|
|
for finding in json.loads(_text_payload(spoofed_result))["findings"]
|
|
}
|
|
unstored = await mcp.call_tool(
|
|
"proposal_status",
|
|
{"proposal_id": candidate.proposal_id, "revision": candidate.revision},
|
|
)
|
|
assert unstored.is_error
|
|
|
|
submitted = await mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": candidate.model_dump_json()},
|
|
)
|
|
assert not submitted.is_error, submitted.content
|
|
outcome = json.loads(_text_payload(submitted))
|
|
assert outcome["status"] == "accepted"
|
|
assert outcome["validation_status"] == "eligible"
|
|
assert outcome["receipt"] is not None
|
|
accepted_head = repository.head()
|
|
retried = await mcp.call_tool(
|
|
"proposal_submit",
|
|
{"proposal_json": candidate.model_dump_json()},
|
|
)
|
|
assert not retried.is_error, retried.content
|
|
retry_outcome = json.loads(_text_payload(retried))
|
|
assert retry_outcome["receipt"] == outcome["receipt"]
|
|
assert repository.head() == accepted_head
|
|
accepted_status = await mcp.call_tool(
|
|
"proposal_status",
|
|
{"proposal_id": candidate.proposal_id, "revision": candidate.revision},
|
|
)
|
|
assert not accepted_status.is_error, accepted_status.content
|
|
accepted_status_payload = json.loads(_text_payload(accepted_status))
|
|
assert accepted_status_payload["lifecycle"] == "accepted"
|
|
assert accepted_status_payload["receipt"] == outcome["receipt"]
|
|
other_token = identities.provision(
|
|
credential_id="other-agent-key",
|
|
principal_id="agent-other",
|
|
roles=("agent",),
|
|
project_ids=("dome-performance",),
|
|
)
|
|
async with httpx2.AsyncClient(
|
|
transport=httpx2.ASGITransport(app=app),
|
|
base_url="http://localhost",
|
|
headers={"Authorization": f"Bearer {other_token}"},
|
|
) as other_http:
|
|
async with Client(
|
|
streamable_http_client("http://localhost/mcp", http_client=other_http)
|
|
) as other_mcp:
|
|
private_status = await other_mcp.call_tool(
|
|
"proposal_status",
|
|
{
|
|
"proposal_id": candidate.proposal_id,
|
|
"revision": candidate.revision,
|
|
},
|
|
)
|
|
assert private_status.is_error
|
|
impersonated_evaluation = await other_mcp.call_tool(
|
|
"proposal_evaluate",
|
|
{"proposal_json": candidate.model_dump_json()},
|
|
)
|
|
assert impersonated_evaluation.is_error
|
|
unauthorized_withdrawal = await other_mcp.call_tool(
|
|
"proposal_withdraw",
|
|
{
|
|
"proposal_id": candidate.proposal_id,
|
|
"revision": candidate.revision,
|
|
},
|
|
)
|
|
assert unauthorized_withdrawal.is_error
|
|
read = await mcp.call_tool(
|
|
"get_node",
|
|
{"project_id": node.project_id, "node_id": node.node_id},
|
|
)
|
|
assert not read.is_error
|
|
envelope = json.loads(_text_payload(read))
|
|
assert envelope["canonical_revision"] == outcome["receipt"]["commit_id"]
|
|
assert envelope["citations"][0]["quote"] == BLOCK_TEXT
|
|
|
|
selected_path = tmp_path / "operator-selected.md"
|
|
selected_bytes = b"A separately selected source for the stdio bridge.\n"
|
|
selected_path.write_bytes(selected_bytes)
|
|
|
|
@asynccontextmanager
|
|
async def remote_client() -> AsyncIterator[Client]:
|
|
async with Client(
|
|
streamable_http_client("http://localhost/mcp", http_client=http)
|
|
) as remote:
|
|
yield remote
|
|
|
|
connector = create_connector_server(
|
|
(SelectedFile("source-1", selected_path),), remote_client
|
|
)
|
|
async with Client(connector) as local:
|
|
connector_tools = await local.list_tools()
|
|
assert {tool.name for tool in connector_tools.tools} == {
|
|
"list_selected_files",
|
|
"upload_selected_file",
|
|
}
|
|
connector_result = await local.call_tool(
|
|
"upload_selected_file",
|
|
{
|
|
"file_id": "source-1",
|
|
"project_id": "dome-performance",
|
|
"source_revision_id": "dome:connector-probe:r1",
|
|
},
|
|
)
|
|
assert not connector_result.is_error
|
|
connector_text = _text_payload(connector_result)
|
|
connector_receipt = json.loads(connector_text)
|
|
assert (
|
|
connector_receipt["sha256"]
|
|
== hashlib.sha256(selected_bytes).hexdigest()
|
|
)
|
|
assert selected_bytes.decode() not in connector_text
|
|
|
|
asyncio.run(exercise())
|