feat: implement GEWU service and local agent workflows
CI / quality (push) Canceled after 0s

This commit is contained in:
vasily committed 2026-10-14 19:21:35 +08:00
1 parent 4db3f45164
commit d25c04cdd2
175 files changed
+56243 -15

No files matched your search

+1
View File
@@ -0,0 +1 @@
"""Domain contract tests and fixtures."""
+212
View File
@@ -0,0 +1,212 @@
"""Golden pilot proposal fixtures for deterministic evidence and policy checks."""
from datetime import UTC, datetime
from gewu.domain.contracts import (
AutoAcceptPolicy,
Claim,
Confidence,
EvidenceReference,
ExtractionBlock,
ExtractionRecord,
KnowledgeNode,
LensFit,
Lifecycle,
Locator,
NodeType,
ProjectRecord,
ProjectStatus,
ProposalOperation,
ProposalRevision,
ReliabilityGrade,
ReviewState,
Scope,
SourceRevisionRecord,
SupportAssessment,
SupportStatus,
)
from gewu.domain.validation import (
assertion_digest,
evidence_digest,
extraction_digest,
proposal_payload_digest,
)
BASE_COMMIT = "1" * 40
NOW = datetime(2026, 10, 13, tzinfo=UTC)
SOURCE_REVISION = SourceRevisionRecord(
source_id="shared:pilot-report",
revision_id="shared:pilot-report:r1",
artifact_sha256="2" * 64,
media_type="text/markdown",
byte_length=100,
captured_at=NOW,
original_url=None,
title="Pilot dome installation report",
authors=("Pilot team",),
publisher="GEWU fixtures",
license="internal",
source_type="internal",
reliability_grade=ReliabilityGrade.MEDIUM,
ingested_for=("dome-performance",),
archive_status="archived",
)
BLOCK_TEXT = "The installation hosted 400 visitors during the 2026 season."
_EXTRACTION_UNHASHED = ExtractionRecord(
extraction_id="0" * 64,
source_revision_id=SOURCE_REVISION.revision_id,
parser_name="gewu-text",
parser_version="0.1.0",
parser_config_sha256="4" * 64,
normalization_profile="unicode-nfc-lf-v1",
status="complete",
blocks=(
ExtractionBlock(
block_id="block-1",
ordinal=0,
original_text=BLOCK_TEXT,
normalized_text=BLOCK_TEXT,
normalization_map=tuple((index, index + 1) for index in range(len(BLOCK_TEXT))),
page=None,
),
),
)
EXTRACTION = _EXTRACTION_UNHASHED.model_copy(
update={"extraction_id": extraction_digest(_EXTRACTION_UNHASHED)}
)
CLAIM_TEXT = "The installation hosted 400 visitors during the 2026 season."
CLAIM_DIGEST = assertion_digest(CLAIM_TEXT)
START = 0
END = len(BLOCK_TEXT)
EVIDENCE = EvidenceReference(
source_revision_id=SOURCE_REVISION.revision_id,
extraction_id=EXTRACTION.extraction_id,
assertion_path="claims[0].text",
assertion_digest=CLAIM_DIGEST,
quote=BLOCK_TEXT,
locator=Locator(kind="text_span", block_id="block-1", start=START, end=END),
normalization_profile="unicode-nfc-lf-v1",
)
CLAIM = Claim(
claim_id="dome:claim-visitors",
text=CLAIM_TEXT,
evidence_type="reported",
evidence=(EVIDENCE,),
assessment=SupportAssessment(
assessment_id="assessment-1",
assertion_digest=CLAIM_DIGEST,
evidence_digests=(evidence_digest(EVIDENCE),),
assessor_id="agent-pilot",
run_id="run-pilot-1",
method="paired source claim check",
status=SupportStatus.SUPPORTED,
rationale="The quoted sentence states the count and period.",
),
)
NODE = KnowledgeNode(
node_id="dome:season-visitors",
project_id="dome-performance",
node_type=NodeType.DATAPOINT,
title="Dome season visitors",
summary="The installation hosted 400 visitors in the 2026 season.",
created=NOW,
updated=NOW,
review_state=ReviewState.DRAFT,
lifecycle=Lifecycle.ACTIVE,
confidence=Confidence.MEDIUM,
claim_support="supported",
tags=("attendance",),
audience=("product",),
domain=("production",),
use_for=("comparison",),
lens_fit={
"dome-performance": LensFit(
score=0.9,
horizon="H1",
key_question="How do audiences engage?",
reason="Reports a representative audience measure.",
),
},
run_id="run-pilot-1",
agent_id="agent-pilot",
model_id="fixture-model",
claims=(CLAIM,),
as_of=NOW,
stale_after=datetime(2027, 1, 1, tzinfo=UTC),
value=400,
unit="visitors",
sample_size=None,
reporting_year=2026,
)
PROJECTS = {
"dome-performance": ProjectRecord(
project_id="dome-performance",
title="Dome interactive content",
status=ProjectStatus.ACTIVE,
north_star="Create compelling interactive experiences for dome audiences.",
key_questions=("How do audiences engage?",),
autonomy_policy_id="dome-policy",
),
"interactive-video": ProjectRecord(
project_id="interactive-video",
title="Interactive video",
status=ProjectStatus.ACTIVE,
north_star="Understand useful interactive video experiences.",
key_questions=("How do teams control branching?",),
autonomy_policy_id="video-policy",
),
}
POLICIES = {
"dome-policy": AutoAcceptPolicy(
policy_id="dome-policy",
policy_version=1,
project_id="dome-performance",
automatic_node_types=("datapoint", "problem", "technique", "case"),
automatic_source_registration=True,
require_supported_assessment=True,
),
"video-policy": AutoAcceptPolicy(
policy_id="video-policy",
policy_version=1,
project_id="interactive-video",
automatic_node_types=("datapoint", "problem", "technique", "case"),
automatic_source_registration=True,
require_supported_assessment=True,
),
"shared": AutoAcceptPolicy(
policy_id="shared",
policy_version=1,
project_id="shared",
automatic_source_registration=True,
require_supported_assessment=True,
),
}
def proposal(
*,
node: KnowledgeNode | None = NODE,
source: SourceRevisionRecord | None = SOURCE_REVISION,
scope: Scope | None = None,
base_commit: str = BASE_COMMIT,
) -> ProposalRevision:
operations = tuple(
operation
for operation in (
ProposalOperation(operation="register_source", source=source) if source else None,
ProposalOperation(operation="upsert_node", node=node) if node else None,
)
if operation is not None
)
candidate = ProposalRevision(
proposal_id="proposal-pilot-1",
revision=1,
initiating_project_id="dome-performance",
scope=scope or Scope(projects=("dome-performance",), include_shared=True),
base_commit=base_commit,
payload_digest="0" * 64,
proposer_id="agent-pilot",
run_id="run-pilot-1",
operations=operations,
)
return candidate.model_copy(update={"payload_digest": proposal_payload_digest(candidate)})
+14
View File
@@ -0,0 +1,14 @@
import subprocess
import sys
from scripts.export_agent_contract import PACK
def test_pack_manifest_and_nested_contract_hashes_verify() -> None:
result = subprocess.run(
[sys.executable, str(PACK / "scripts" / "manifest_tool.py"), "verify"],
capture_output=True,
check=False,
text=True,
)
assert result.returncode == 0, result.stderr
+40
View File
@@ -0,0 +1,40 @@
import json
from pathlib import Path
from gewu.domain.operations import OPERATIONS, Transport, catalog_bytes, operations_for
def test_read_http_catalog_contains_no_mutation_operations() -> None:
http_operations = operations_for(Transport.HTTP)
assert all(not operation.mutates_knowledge for operation in http_operations)
assert not {"proposal_submit", "owner_decide"} & {item.name for item in http_operations}
def test_owner_decision_is_separate_from_agent_mcp() -> None:
agent_operations = {item.name for item in operations_for(Transport.MCP)}
owner_operations = {item.name for item in operations_for(Transport.OWNER_MCP)}
assert "owner_decide" not in agent_operations
assert owner_operations == {"owner_challenge_get", "owner_diff_get", "owner_decide"}
def test_stdio_connector_has_only_selected_file_operations() -> None:
stdio_operations = {item.name for item in operations_for(Transport.STDIO)}
assert stdio_operations == {"list_selected_files", "upload_selected_file"}
def test_exported_catalog_matches_the_application_allowlist() -> None:
path = Path(__file__).resolve().parents[2] / "schema" / "v1" / "catalog.json"
catalog = json.loads(path.read_text(encoding="utf-8"))
assert path.read_bytes() == catalog_bytes()
names_by_transport = {
transport.value: {operation.name for operation in operations_for(transport)}
for transport in Transport
}
entries = {operation["name"]: operation for operation in catalog["operations"]}
assert set(entries) == {operation.name for operation in OPERATIONS}
for name, entry in entries.items():
assert set(entry["transports"]) == {
transport for transport, names in names_by_transport.items() if name in names
}
expected_mutates = next(item.mutates_knowledge for item in OPERATIONS if item.name == name)
assert entry["mutates_knowledge"] is expected_mutates
+231
View File
@@ -0,0 +1,231 @@
import json
from datetime import UTC, date, datetime
from pathlib import Path
import pytest
from pydantic import ValidationError
from gewu.domain.contracts import (
AutoAcceptPolicy,
Confidence,
Locator,
NodeType,
ProjectRecord,
ProjectStatus,
ProposalReceipt,
ProposalState,
ReliabilityGrade,
Scope,
SearchRequest,
SourceRevisionRecord,
)
def test_project_contract_rejects_unknown_fields() -> None:
with pytest.raises(ValidationError):
ProjectRecord.model_validate(
{
"project_id": "dome",
"title": "Dome",
"status": ProjectStatus.ACTIVE,
"north_star": "Understand interactive environments",
"key_questions": ("What works?",),
"autonomy_policy_id": "pilot",
"untrusted_extra": "ignored?",
}
)
def test_project_status_supports_pause_without_losing_registry_record() -> None:
project = ProjectRecord(
project_id="dome",
title="Dome",
status=ProjectStatus.PAUSED,
north_star="Understand interactive environments",
key_questions=("What works?",),
autonomy_policy_id="pilot",
)
assert (
ProjectRecord.model_validate_json(project.model_dump_json()).status == ProjectStatus.PAUSED
)
@pytest.mark.parametrize("project_id", ["Dome", "two_words", "project.slug"])
def test_project_ids_must_be_lowercase_slugs(project_id: str) -> None:
with pytest.raises(ValidationError):
ProjectRecord(
project_id=project_id,
title="Invalid project ID",
status=ProjectStatus.ACTIVE,
north_star="A stable project lens.",
key_questions=("What matters?",),
autonomy_policy_id="policy",
)
def test_scope_requires_explicit_all_projects_flag() -> None:
with pytest.raises(ValidationError):
Scope(projects=("all",))
assert Scope(projects=("all",), all_projects=True)
@pytest.mark.parametrize("unsafe_id", ["../private", "bad/id", " leading-space"])
def test_scope_rejects_unsafe_project_ids(unsafe_id: str) -> None:
with pytest.raises(ValidationError):
Scope(projects=(unsafe_id,))
def test_search_facets_are_typed_and_reject_duplicate_node_types() -> None:
request = SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
node_types=(NodeType.PROBLEM, NodeType.TECHNIQUE),
tags=("attendance", "real-time"),
audience=("product",),
use_for=("comparison",),
domain=("production",),
confidences=(Confidence.MEDIUM,),
as_of_from=date(2024, 1, 1),
as_of_to=date(2024, 12, 31),
stale_only=True,
budget_tokens=1000,
)
assert request.stale_only
assert request.tags == ("attendance", "real-time")
assert request.domain == ("production",)
assert request.confidences == (Confidence.MEDIUM,)
assert request.as_of_from == date(2024, 1, 1)
with pytest.raises(ValidationError, match="must not be later"):
SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
as_of_from=date(2025, 1, 1),
as_of_to=date(2024, 12, 31),
budget_tokens=1000,
)
with pytest.raises(ValidationError, match="confidences must be unique"):
SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
confidences=(Confidence.MEDIUM, Confidence.MEDIUM),
budget_tokens=1000,
)
with pytest.raises(ValidationError):
SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
node_types=(NodeType.PROBLEM, NodeType.PROBLEM),
budget_tokens=1000,
)
with pytest.raises(ValidationError):
SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
tags=("attendance", "attendance"),
budget_tokens=1000,
)
with pytest.raises(ValidationError):
SearchRequest(
query="audience",
scope=Scope(projects=("dome-performance",)),
domain=(" production",),
budget_tokens=1000,
)
def test_search_allows_empty_query_only_for_a_typed_browse() -> None:
request = SearchRequest(
scope=Scope(projects=("dome-performance",)),
node_types=(NodeType.PROBLEM,),
budget_tokens=1000,
)
assert request.query == ""
with pytest.raises(ValidationError, match="search requires text"):
SearchRequest(
scope=Scope(projects=("dome-performance",)),
budget_tokens=1000,
)
with pytest.raises(ValidationError, match="search requires text"):
SearchRequest(
query=" ",
scope=Scope(projects=("dome-performance",)),
budget_tokens=1000,
)
def test_pdf_locator_uses_one_based_page_and_nonempty_span() -> None:
with pytest.raises(ValidationError):
Locator(kind="pdf_page_span", block_id="p1-b1", start=8, end=8, page=0)
def test_unarchived_source_requires_a_url_and_cannot_claim_artifact_bytes() -> None:
base = {
"source_id": "project:bookmark",
"revision_id": "project:bookmark:r1",
"media_type": "application/octet-stream",
"byte_length": 0,
"captured_at": datetime.now(UTC),
"original_url": "https://example.org/paper",
"title": "Referenced paper",
"license": "unspecified",
"source_type": "paper",
"reliability_grade": ReliabilityGrade.MEDIUM,
"ingested_for": ("dome-performance",),
"archive_status": "reference-only",
}
record = SourceRevisionRecord.model_validate(base)
assert record.artifact_sha256 is None
with pytest.raises(ValidationError):
SourceRevisionRecord.model_validate({**base, "byte_length": 10})
with pytest.raises(ValidationError):
SourceRevisionRecord.model_validate({**base, "original_url": None})
def test_receipt_is_immutable_and_versioned() -> None:
receipt = ProposalReceipt(
proposal_id="proposal-1",
revision=1,
payload_digest="a" * 64,
state=ProposalState.ACCEPTED,
outcome="accepted",
commit_id="b" * 64,
decided_at=datetime.now(UTC),
)
assert receipt.schema_version == 1
with pytest.raises(ValidationError):
receipt.revision = 2 # type: ignore[misc]
def test_pilot_registry_fixture_matches_project_and_policy_contracts() -> None:
fixture_path = (
Path(__file__).resolve().parents[2] / "schema" / "fixtures" / "pilot-projects.json"
)
def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]:
result: dict[str, object] = {}
for key, value in pairs:
if key in result:
raise ValueError(f"duplicate JSON key: {key}")
result[key] = value
return result
fixture = json.loads(
fixture_path.read_text(encoding="utf-8"), object_pairs_hook=reject_duplicate_keys
)
projects = tuple(
ProjectRecord.model_validate_json(json.dumps(record), strict=True)
for record in fixture["projects"]
)
policies = tuple(
AutoAcceptPolicy.model_validate_json(json.dumps(record), strict=True)
for record in fixture["test_policies"]
)
assert {project.project_id for project in projects} == {
"dome-performance",
"interactive-video",
}
assert {policy.project_id for policy in policies} == {
"dome-performance",
"interactive-video",
"shared",
}
+160
View File
@@ -0,0 +1,160 @@
import copy
import hashlib
import json
import stat
from pathlib import Path
import agents.gewu.scripts.run_checkpoint as checkpoint_module
import pytest
from agents.gewu.scripts.run_checkpoint import (
create_checkpoint,
load_checkpoint,
update_checkpoint,
)
def test_run_checkpoint_records_scope_coverage_receipt_and_pins(tmp_path: Path) -> None:
workspace = tmp_path / "research-workspace"
checkpoint = create_checkpoint(
workspace,
run_id="run-2026-01",
objective="Compare audience response",
projects=["dome-performance"],
include_shared=True,
lens="Audience behavior",
budgets={"sources": 8},
)
path = workspace / "Runs" / "run-2026-01.json"
assert stat.S_IMODE(path.stat().st_mode) == 0o600
assert checkpoint["pack"]["snapshot_digest"]
updated = update_checkpoint(
workspace,
"run-2026-01",
{
"stage": "report",
"sources": [
{
"source_revision_id": "source:r1",
"extraction_id": "a" * 64,
"status": "extracted",
}
],
"coverage": {
"selected": ["section-1", "section-2"],
"included": ["section-1"],
"excluded": [{"item": "section-2", "reason": "outside project lens"}],
"skipped": [],
},
"pending_operations": [
{
"operation_id": "proposal-op-1",
"tool_name": "proposal_submit",
"input_sha256": "b" * 64,
"status": "complete",
}
],
"proposals": [
{
"proposal_id": "proposal-1",
"revision": 1,
"status": "accepted",
"receipt": {
"proposal_id": "proposal-1",
"revision": 1,
"state": "accepted",
"commit_id": "c" * 40,
},
}
],
"outputs": [{"sha256": "d" * 64}],
},
)
resumed = load_checkpoint(workspace, "run-2026-01")
assert resumed == updated
assert resumed["coverage"]["excluded"][0]["reason"] == "outside project lens"
assert resumed["proposals"][0]["receipt"]["commit_id"] == "c" * 40
with pytest.raises(ValueError, match="does not match"):
update_checkpoint(
workspace,
"run-2026-01",
{
"proposals": [
{
"proposal_id": "proposal-1",
"revision": 1,
"status": "accepted",
"receipt": {
"proposal_id": "proposal-other",
"revision": 1,
"state": "accepted",
"commit_id": "c" * 40,
},
}
]
},
)
def test_run_checkpoint_rejects_secrets_bad_progress_and_snapshot_drift(tmp_path: Path) -> None:
workspace = tmp_path / "research-workspace"
create_checkpoint(
workspace,
run_id="run-drift-01",
objective="Test resume pinning",
projects=["dome-performance"],
include_shared=False,
lens="A bounded lens",
)
with pytest.raises(ValueError, match="credentials or source bodies"):
update_checkpoint(workspace, "run-drift-01", {"last_failure": {"api_key": "secret"}})
with pytest.raises(ValueError, match="SHA-256"):
update_checkpoint(
workspace,
"run-drift-01",
{
"outputs": [{"sha256": "not-a-digest"}],
"stage": "report",
},
)
path = workspace / "Runs" / "run-drift-01.json"
checkpoint = json.loads(path.read_text(encoding="utf-8"))
checkpoint["pack"]["snapshot_digest"] = "0" * 64
path.write_text(json.dumps(checkpoint), encoding="utf-8")
with pytest.raises(ValueError, match="pack changed"):
load_checkpoint(workspace, "run-drift-01")
def test_existing_run_cannot_resume_after_verified_pack_upgrade(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
workspace = tmp_path / "research-workspace"
create_checkpoint(
workspace,
run_id="run-pack-update-01",
objective="Resume with pinned modules",
projects=["dome-performance"],
include_shared=True,
lens="Keep the original extraction and evidence rules",
)
checkpoint_path = workspace / "Runs" / "run-pack-update-01.json"
original_checkpoint = checkpoint_path.read_bytes()
updated_manifest = copy.deepcopy(checkpoint_module.load_manifest())
updated_manifest["pack_version"] = "0.7.0"
updated_manifest["modules"][0]["implementation_version"] += ".updated"
modules_json = json.dumps(
updated_manifest["modules"], ensure_ascii=False, sort_keys=True, separators=(",", ":")
)
updated_manifest["snapshot"]["digest"] = hashlib.sha256(modules_json.encode()).hexdigest()
# This fixture stands in for a newer pack whose own manifest verification
# succeeded; the behavior under test is the active run's compatibility pin.
monkeypatch.setattr(checkpoint_module, "load_manifest", lambda: updated_manifest)
monkeypatch.setattr(checkpoint_module, "verify", lambda _manifest: None)
with pytest.raises(ValueError, match="pack changed since this run"):
load_checkpoint(workspace, "run-pack-update-01")
assert checkpoint_path.read_bytes() == original_checkpoint
+698
View File
@@ -0,0 +1,698 @@
from gewu.adapters.text_extractor import TextMarkdownExtractor
from gewu.domain.contracts import (
EdgeEndpoint,
KnowledgeNode,
Locator,
NodeType,
ProjectStatus,
ProposalOperation,
ProposalRevision,
Scope,
SupportStatus,
TypedEdge,
)
from gewu.domain.validation import (
ValidationReport,
assertion_digest,
edge_assertion_digest,
evidence_digest,
proposal_payload_digest,
validate_proposal,
)
from tests.domain.proposal_fixtures import (
BASE_COMMIT,
CLAIM,
EVIDENCE,
EXTRACTION,
NODE,
POLICIES,
PROJECTS,
SOURCE_REVISION,
proposal,
)
def _validate(candidate: ProposalRevision) -> ValidationReport:
return validate_proposal(
candidate,
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
def test_supported_exact_citation_and_both_bundle_policies_are_eligible() -> None:
report = _validate(proposal())
assert report.status == "eligible", report.findings
assert report.eligible_for_auto_accept
assert set(report.affected_policy_ids) == {"dome-policy", "shared"}
def test_transcript_timestamp_locator_must_match_the_cited_cue() -> None:
transcript_bytes = (
b"WEBVTT\n\n"
b"cue-1\n00:14:32.000 --> 00:14:36.500\n"
b"The installation hosted 400 visitors during the 2026 season.\n"
)
source = SOURCE_REVISION.model_copy(
update={
"media_type": "text/vtt",
"byte_length": len(transcript_bytes),
"artifact_sha256": __import__("hashlib").sha256(transcript_bytes).hexdigest(),
}
)
extraction = TextMarkdownExtractor().extract(source.revision_id, transcript_bytes, "text/vtt")
cue = next(block for block in extraction.blocks if block.time_start_ms is not None)
quote = CLAIM.text
start = cue.normalized_text.index(quote)
evidence = EVIDENCE.model_copy(
update={
"extraction_id": extraction.extraction_id,
"quote": quote,
"locator": Locator(
kind="transcript_timestamp_span",
block_id=cue.block_id,
start=start,
end=start + len(quote),
time_start_ms=cue.time_start_ms,
time_end_ms=cue.time_end_ms,
),
}
)
assessment = CLAIM.assessment.model_copy(
update={"evidence_digests": (evidence_digest(evidence),)}
)
claim = CLAIM.model_copy(update={"evidence": (evidence,), "assessment": assessment})
node = NODE.model_copy(update={"claims": (claim,)})
eligible = validate_proposal(
proposal(node=node, source=source),
projects=PROJECTS,
policies=POLICIES,
sources={},
extractions={extraction.extraction_id: extraction},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert eligible.status == "eligible", eligible.findings
wrong_time = evidence.model_copy(
update={
"locator": evidence.locator.model_copy(
update={"time_start_ms": evidence.locator.time_start_ms + 1}
)
}
)
wrong_assessment = assessment.model_copy(
update={"evidence_digests": (evidence_digest(wrong_time),)}
)
wrong_claim = claim.model_copy(
update={"evidence": (wrong_time,), "assessment": wrong_assessment}
)
rejected = validate_proposal(
proposal(node=NODE.model_copy(update={"claims": (wrong_claim,)}), source=None),
projects=PROJECTS,
policies=POLICIES,
sources={source.revision_id: source},
extractions={extraction.extraction_id: extraction},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert rejected.status == "invalid"
assert "EVIDENCE_LOCATION_MISMATCH" in {finding.code for finding in rejected.findings}
def test_paused_or_archived_projects_reject_new_acceptance() -> None:
for status in (ProjectStatus.PAUSED, ProjectStatus.ARCHIVED):
project = PROJECTS["dome-performance"].model_copy(update={"status": status})
report = validate_proposal(
proposal(),
projects={**PROJECTS, "dome-performance": project},
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert not report.eligible_for_auto_accept
assert any(item.code == "PROJECT_NOT_ACTIVE" for item in report.findings)
def test_cross_project_source_bundle_requires_every_affected_policy() -> None:
shared_source = SOURCE_REVISION.model_copy(
update={"ingested_for": ("dome-performance", "interactive-video")}
)
candidate = proposal(
source=shared_source,
scope=Scope(projects=("dome-performance", "interactive-video"), include_shared=True),
)
eligible = validate_proposal(
candidate,
projects=PROJECTS,
policies=POLICIES,
sources={},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert eligible.status == "eligible", eligible.findings
assert set(eligible.affected_policy_ids) == {
"dome-policy",
"video-policy",
"shared",
}
restrictive_video_policy = POLICIES["video-policy"].model_copy(
update={"automatic_source_registration": False}
)
blocked = validate_proposal(
candidate,
projects=PROJECTS,
policies={**POLICIES, "video-policy": restrictive_video_policy},
sources={},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert blocked.status == "needs-review"
assert not blocked.eligible_for_auto_accept
assert "AUTO_ACCEPT_UNSPECIFIED" in {finding.code for finding in blocked.findings}
def test_matching_quote_at_wrong_location_is_rejected() -> None:
evidence = EVIDENCE.model_copy(
update={
"quote": "400 visitors",
"locator": EVIDENCE.locator.model_copy(update={"start": 0, "end": 13}),
}
)
changed_claim = CLAIM.model_copy(update={"evidence": (evidence,)})
changed_node = NODE.model_copy(update={"claims": (changed_claim,)})
report = _validate(proposal(node=changed_node, source=None))
assert report.status == "invalid"
assert "EVIDENCE_MISMATCH" in {finding.code for finding in report.findings}
def test_reference_only_source_cannot_support_accepted_evidence() -> None:
reference = SOURCE_REVISION.model_copy(
update={
"artifact_sha256": None,
"media_type": "application/octet-stream",
"byte_length": 0,
"original_url": "https://research.example/missing",
"archive_status": "reference-only",
}
)
report = validate_proposal(
proposal(source=None),
projects=PROJECTS,
policies=POLICIES,
sources={reference.revision_id: reference},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert report.status == "invalid"
assert "SOURCE_NOT_ARCHIVED" in {finding.code for finding in report.findings}
def test_correct_quote_with_unsupported_assessment_is_rejected() -> None:
assessment = CLAIM.assessment.model_copy(update={"status": SupportStatus.UNSUPPORTED})
changed_claim = CLAIM.model_copy(update={"assessment": assessment})
changed_node = NODE.model_copy(update={"claims": (changed_claim,)})
report = _validate(proposal(node=changed_node, source=None))
assert report.status == "invalid"
assert "SUPPORT_UNAVAILABLE" in {finding.code for finding in report.findings}
def test_node_and_assessor_attribution_cannot_impersonate_another_agent() -> None:
other_assessment = CLAIM.assessment.model_copy(update={"assessor_id": "agent-other"})
assessment_claim = CLAIM.model_copy(update={"assessment": other_assessment})
assessment_node = NODE.model_copy(update={"claims": (assessment_claim,)})
assessment_report = _validate(proposal(node=assessment_node, source=None))
assert "ASSESSOR_ATTRIBUTION_MISMATCH" in {
finding.code for finding in assessment_report.findings
}
other_node = NODE.model_copy(update={"agent_id": "agent-other"})
node_report = _validate(proposal(node=other_node, source=None))
assert "AGENT_ATTRIBUTION_MISMATCH" in {finding.code for finding in node_report.findings}
def test_entity_name_collision_requires_review_without_creating_a_link() -> None:
entity = NODE.model_copy(
update={"node_type": NodeType.ENTITY, "title": "The Dome", "aliases": ("Planetarium",)}
)
existing = NODE.model_copy(
update={
"node_type": NodeType.ENTITY,
"node_id": "dome:existing-entity",
"title": " planetarium ",
}
)
report = validate_proposal(
proposal(
node=entity,
source=None,
scope=Scope(projects=("dome-performance",), include_shared=False),
),
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={existing.node_id: existing},
current_base_commit=BASE_COMMIT,
)
assert report.status == "needs-review"
assert not report.eligible_for_auto_accept
assert "POSSIBLE_DUPLICATE_ENTITY" in {finding.code for finding in report.findings}
def test_entity_duplicate_detection_respects_project_scope() -> None:
entity = NODE.model_copy(
update={"node_type": NodeType.ENTITY, "title": "The Dome", "aliases": ("Planetarium",)}
)
unrelated = NODE.model_copy(
update={
"node_type": NodeType.ENTITY,
"node_id": "video:existing-entity",
"project_id": "interactive-video",
"title": "Planetarium",
}
)
report = validate_proposal(
proposal(node=entity, source=None),
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={unrelated.node_id: unrelated},
current_base_commit=BASE_COMMIT,
)
assert "POSSIBLE_DUPLICATE_ENTITY" not in {finding.code for finding in report.findings}
def test_entity_duplicate_review_uses_only_readable_projects_in_explicit_scope() -> None:
entity = NODE.model_copy(
update={"node_type": NodeType.ENTITY, "title": "The Dome", "aliases": ("Planetarium",)}
)
other_entity = NODE.model_copy(
update={
"node_type": NodeType.ENTITY,
"node_id": "video:existing-entity",
"project_id": "interactive-video",
"title": "Planetarium",
}
)
existing = {other_entity.node_id: other_entity}
candidate = proposal(
node=entity,
source=None,
scope=Scope(projects=("dome-performance", "interactive-video")),
)
readable = validate_proposal(
candidate,
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes=existing,
current_base_commit=BASE_COMMIT,
readable_project_ids=frozenset({"interactive-video"}),
)
unreadable = validate_proposal(
candidate,
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes=existing,
current_base_commit=BASE_COMMIT,
)
assert "POSSIBLE_DUPLICATE_ENTITY" in {finding.code for finding in readable.findings}
assert "POSSIBLE_DUPLICATE_ENTITY" not in {finding.code for finding in unreadable.findings}
def test_entity_duplicate_review_does_not_expand_beyond_proposal_scope() -> None:
entity = NODE.model_copy(
update={"node_type": NodeType.ENTITY, "title": "The Dome", "aliases": ("Planetarium",)}
)
shared_entity = NODE.model_copy(
update={
"node_type": NodeType.ENTITY,
"node_id": "shared:planetarium-entity",
"project_id": "shared",
"title": "Planetarium",
}
)
report = validate_proposal(
proposal(
node=entity,
source=None,
scope=Scope(projects=("dome-performance",), include_shared=False),
),
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={shared_entity.node_id: shared_entity},
current_base_commit=BASE_COMMIT,
readable_project_ids=frozenset({"shared"}),
)
assert "POSSIBLE_DUPLICATE_ENTITY" not in {finding.code for finding in report.findings}
def test_requested_shared_scope_does_not_reveal_unreadable_shared_entity_names() -> None:
entity = NODE.model_copy(
update={"node_type": NodeType.ENTITY, "title": "The Dome", "aliases": ("Planetarium",)}
)
shared_entity = NODE.model_copy(
update={
"node_type": NodeType.ENTITY,
"node_id": "shared:planetarium-entity",
"project_id": "shared",
"title": "Planetarium",
}
)
report = validate_proposal(
proposal(node=entity, source=None),
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={shared_entity.node_id: shared_entity},
current_base_commit=BASE_COMMIT,
)
assert "POSSIBLE_DUPLICATE_ENTITY" not in {finding.code for finding in report.findings}
explicitly_readable = validate_proposal(
proposal(node=entity, source=None, scope=Scope(projects=("dome-performance", "shared"))),
projects=PROJECTS,
policies=POLICIES,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={shared_entity.node_id: shared_entity},
current_base_commit=BASE_COMMIT,
readable_project_ids=frozenset({"shared"}),
)
assert "POSSIBLE_DUPLICATE_ENTITY" in {finding.code for finding in explicitly_readable.findings}
def test_changed_assertion_and_old_assessment_are_rejected() -> None:
changed_text = "The installation hosted 401 visitors during the 2026 season."
stale_assessment = CLAIM.assessment
evidence = CLAIM.evidence[0].model_copy(
update={"assertion_digest": assertion_digest(changed_text)}
)
changed_claim = CLAIM.model_copy(
update={"text": changed_text, "evidence": (evidence,), "assessment": stale_assessment}
)
changed_node = NODE.model_copy(update={"claims": (changed_claim,)})
report = _validate(proposal(node=changed_node, source=None))
assert report.status == "invalid"
assert "ASSESSMENT_STALE" in {finding.code for finding in report.findings}
def test_assumption_and_limitation_claims_use_the_same_evidence_contract() -> None:
assumption_text = CLAIM.text
assumption_digest = assertion_digest(assumption_text)
assumption_evidence = EVIDENCE.model_copy(
update={
"assertion_path": "assumptions[0].text",
"assertion_digest": assumption_digest,
}
)
assumption = CLAIM.model_copy(
update={
"claim_id": "dome:assumption-seasonality",
"text": assumption_text,
"evidence": (assumption_evidence,),
"assessment": CLAIM.assessment.model_copy(
update={
"assertion_digest": assumption_digest,
"evidence_digests": (evidence_digest(assumption_evidence),),
}
),
}
)
limitation_evidence = EVIDENCE.model_copy(
update={
"assertion_path": "limitations[0].text",
"assertion_digest": assumption_digest,
}
)
limitation = assumption.model_copy(
update={
"claim_id": "dome:limitation-seasonality",
"evidence": (limitation_evidence,),
"assessment": assumption.assessment.model_copy(
update={"evidence_digests": (evidence_digest(limitation_evidence),)}
),
}
)
node = NODE.model_copy(update={"assumptions": (assumption,), "limitations": (limitation,)})
report = _validate(proposal(node=node, source=None))
assert report.eligible_for_auto_accept, report.findings
wrong_location = assumption_evidence.model_copy(
update={
"quote": "400 visitors",
"locator": assumption_evidence.locator.model_copy(update={"start": 0, "end": 13}),
}
)
invalid_assumption = assumption.model_copy(
update={
"evidence": (wrong_location,),
"assessment": assumption.assessment.model_copy(
update={"evidence_digests": (evidence_digest(wrong_location),)}
),
}
)
invalid = _validate(
proposal(node=NODE.model_copy(update={"assumptions": (invalid_assumption,)}), source=None)
)
assert "EVIDENCE_MISMATCH" in {finding.code for finding in invalid.findings}
assert any(
finding.path == "operations[0].node.assumptions[0].evidence[0]"
for finding in invalid.findings
)
def test_missing_policy_and_explicit_scope_escape_do_not_accept() -> None:
no_shared_policy = {key: value for key, value in POLICIES.items() if key != "shared"}
candidate = proposal()
report = validate_proposal(
candidate,
projects=PROJECTS,
policies=no_shared_policy,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert report.status == "needs-review"
assert "POLICY_MISSING" in {finding.code for finding in report.findings}
outside_scope = proposal(
node=NODE.model_copy(update={"project_id": "interactive-video"}),
source=None,
scope=Scope(projects=("dome-performance",)),
)
report = _validate(outside_scope)
assert report.status == "invalid"
assert "OUT_OF_SCOPE" in {finding.code for finding in report.findings}
def test_policy_cannot_waive_required_support_review() -> None:
weakened_policy = POLICIES["dome-policy"].model_copy(
update={"require_supported_assessment": False}
)
report = validate_proposal(
proposal(),
projects=PROJECTS,
policies={**POLICIES, "dome-policy": weakened_policy},
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert report.status == "invalid"
assert not report.eligible_for_auto_accept
assert "POLICY_WEAKENING" in {finding.code for finding in report.findings}
def test_changed_base_is_a_conflict_and_edge_policy_is_explicit() -> None:
report = _validate(proposal(base_commit="f" * 64))
assert report.status == "conflict"
assert "BASE_CONFLICT" in {finding.code for finding in report.findings}
def test_relationship_evidence_is_bound_to_the_edge_and_exact_source_span() -> None:
policies = {
**POLICIES,
"dome-policy": POLICIES["dome-policy"].model_copy(
update={"automatic_edge_types": ("causes",)}
),
}
edge = TypedEdge(
edge_id="edge-pilot",
edge_type="causes",
source=EdgeEndpoint(kind="node", id=NODE.node_id),
target=EdgeEndpoint(kind="node", id="dome:other-node"),
evidence=(EVIDENCE,),
)
bound_evidence = EVIDENCE.model_copy(update={"assertion_digest": edge_assertion_digest(edge)})
edge = edge.model_copy(update={"evidence": (bound_evidence,)})
base = proposal(source=None)
candidate = base.model_copy(
update={
"operations": (*base.operations, ProposalOperation(operation="add_edge", edge=edge)),
"payload_digest": "0" * 64,
}
)
candidate = candidate.model_copy(update={"payload_digest": proposal_payload_digest(candidate)})
report = validate_proposal(
candidate,
projects=PROJECTS,
policies=policies,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={
"dome:other-node": NODE.model_copy(update={"node_id": "dome:other-node"}),
},
current_base_commit=BASE_COMMIT,
)
assert report.status == "eligible", report.findings
tampered = bound_evidence.model_copy(
update={"assertion_digest": assertion_digest("unrelated claim")}
)
invalid_edge = edge.model_copy(update={"evidence": (tampered,)})
invalid = candidate.model_copy(
update={
"operations": (
*base.operations,
ProposalOperation(operation="add_edge", edge=invalid_edge),
),
"payload_digest": "0" * 64,
}
)
invalid = invalid.model_copy(update={"payload_digest": proposal_payload_digest(invalid)})
invalid_report = validate_proposal(
invalid,
projects=PROJECTS,
policies=policies,
sources={SOURCE_REVISION.revision_id: SOURCE_REVISION},
extractions={EXTRACTION.extraction_id: EXTRACTION},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert invalid_report.status == "invalid"
assert "EVIDENCE_ASSERTION_MISMATCH" in {finding.code for finding in invalid_report.findings}
def test_contradiction_bundle_preserves_both_supported_claims() -> None:
import hashlib
from gewu.domain.contracts import EdgeEndpoint
first_quote = "The seasonal audience count was 400."
second_quote = "The seasonal audience count was 450."
content = f"Field report: {first_quote}\n\nVendor report: {second_quote}\n".encode()
source = SOURCE_REVISION.model_copy(
update={
"artifact_sha256": hashlib.sha256(content).hexdigest(),
"byte_length": len(content),
}
)
extraction = TextMarkdownExtractor().extract(source.revision_id, content, "text/markdown")
def node_with_claim(node_id: str, claim_id: str, quote: str, title: str) -> KnowledgeNode:
block = next(item for item in extraction.blocks if quote in item.normalized_text)
start = block.normalized_text.index(quote)
evidence = EVIDENCE.model_copy(
update={
"source_revision_id": source.revision_id,
"extraction_id": extraction.extraction_id,
"quote": quote,
"locator": Locator(
kind="text_span", block_id=block.block_id, start=start, end=start + len(quote)
),
"assertion_digest": assertion_digest(quote),
}
)
assessment = CLAIM.assessment.model_copy(
update={
"assertion_digest": assertion_digest(quote),
"evidence_digests": (evidence_digest(evidence),),
}
)
claim = CLAIM.model_copy(
update={
"claim_id": claim_id,
"text": quote,
"evidence": (evidence,),
"assessment": assessment,
}
)
return NODE.model_copy(
update={
"node_id": node_id,
"title": title,
"summary": quote,
"claims": (claim,),
"value": 400 if "400" in quote else 450,
}
)
first = node_with_claim(
"dome:count-field-report", "dome:claim-count-400", first_quote, "Field count"
)
second = node_with_claim(
"dome:count-vendor-report", "dome:claim-count-450", second_quote, "Vendor count"
)
contradiction = TypedEdge(
edge_id="dome:counts-conflict",
edge_type="contradicts",
source=EdgeEndpoint(kind="node", id=first.node_id),
target=EdgeEndpoint(kind="node", id=second.node_id),
conditions="Both reports refer to the same seasonal audience count.",
interpretation_rationale="The source-backed counts differ for the same reporting period.",
)
base = proposal(node=None, source=source)
candidate = base.model_copy(
update={
"operations": (
*base.operations,
ProposalOperation(operation="upsert_node", node=first),
ProposalOperation(operation="upsert_node", node=second),
ProposalOperation(operation="add_edge", edge=contradiction),
),
"payload_digest": "0" * 64,
}
)
candidate = candidate.model_copy(update={"payload_digest": proposal_payload_digest(candidate)})
report = validate_proposal(
candidate,
projects=PROJECTS,
policies={
**POLICIES,
"dome-policy": POLICIES["dome-policy"].model_copy(
update={"automatic_edge_types": ("contradicts",)}
),
},
sources={},
extractions={extraction.extraction_id: extraction},
existing_nodes={},
current_base_commit=BASE_COMMIT,
)
assert report.status == "eligible", report.findings
assert tuple(
operation.node.node_id for operation in candidate.operations if operation.node is not None
) == (first.node_id, second.node_id)
assert first.claims[0].text == first_quote
assert second.claims[0].text == second_quote
assert any(operation.edge == contradiction for operation in candidate.operations)