fix(evidence): close canonical contract gaps
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user