fix(evidence): close canonical contract gaps

This commit is contained in:
2026-07-12 03:15:17 +02:00
parent 4424fd3d90
commit 293d96e1a6
5 changed files with 176 additions and 15 deletions
+48 -5
View File
@@ -1,3 +1,4 @@
import hashlib
from datetime import UTC, datetime, timedelta, timezone
import pytest
@@ -7,26 +8,28 @@ from tht.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest
def document(source_uri: str = "https://host/a.md") -> CanonicalDocument:
content = "# A"
return CanonicalDocument(
document_id="doc:abc",
source_id="source:a",
source_uri=source_uri,
source_fingerprint="etag:abc",
content_hash=f"sha256:{'d' * 64}",
content_hash=f"sha256:{hashlib.sha256(content.encode()).hexdigest()}",
title="A",
content="# A",
content=content,
media_type="text/markdown",
pipeline_version="evidence-v1",
)
def chunk() -> CanonicalChunk:
content = "# A"
return CanonicalChunk(
chunk_id="chunk:abc:0",
document_id="doc:abc",
ordinal=0,
content="# A",
content_hash=f"sha256:{'e' * 64}",
content=content,
content_hash=f"sha256:{hashlib.sha256(content.encode()).hexdigest()}",
source_uri="https://host/a.md",
pipeline_version="evidence-v1",
)
@@ -156,7 +159,6 @@ def test_vector_generation_requires_embedding_compatibility():
("document_id", "not-namespaced"),
("content_hash", "sha256:not-hex"),
("source_uri", "https://user:pass@host/a"),
("source_uri", "https://host/a?refresh_token=secret"),
],
)
def test_canonical_document_rejects_malformed_or_sensitive_provenance(field, value):
@@ -164,6 +166,47 @@ def test_canonical_document_rejects_malformed_or_sensitive_provenance(field, val
CanonicalDocument.model_validate({**document().model_dump(), field: value})
@pytest.mark.parametrize(
"source_uri",
[
"https://host/a?X-Amz-Credential=abc&X-Amz-Signature=secret#access_token=bad",
"https://host/a?sig=sas-secret&sp=r#section",
],
)
def test_canonical_provenance_strips_query_and_fragment(source_uri):
doc = document(source_uri=source_uri)
canonical_chunk = chunk().model_copy(update={"source_uri": source_uri})
manifest = CorpusManifest(
pipeline_version="evidence-v1", documents=[doc], chunks=[canonical_chunk]
)
assert doc.source_uri == "https://host/a"
assert canonical_chunk.source_uri == "https://host/a"
payload = manifest.model_dump_json()
assert "X-Amz" not in payload
assert "sas-secret" not in payload
assert "access_token" not in payload
@pytest.mark.parametrize("factory", [document, chunk])
def test_content_hash_must_match_exact_canonical_utf8(factory):
record = factory()
with pytest.raises(ValidationError, match="exact canonical UTF-8 content"):
type(record).model_validate({**record.model_dump(), "content": record.content + "\n"})
def test_model_copy_revalidates_records_and_manifests():
with pytest.raises(ValidationError, match="namespaced"):
document().model_copy(update={"document_id": "invalid"})
manifest = CorpusManifest(
pipeline_version="evidence-v1",
embedding_model="embed-v1",
embedding_dimensions=768,
)
with pytest.raises(ValidationError, match="set together"):
manifest.model_copy(update={"embedding_dimensions": None})
def test_manifest_datetimes_are_aware_and_normalized_to_utc():
with pytest.raises(ValidationError, match="timezone-aware"):
CorpusManifest(created_at=datetime(2026, 7, 12), pipeline_version="evidence-v1")