test(spec-guard): ingest contract field cross-check vs spec §12 (I2)
This commit is contained in:
parent
6236f2fb78
commit
dbf5dc5c13
1 changed files with 41 additions and 0 deletions
|
|
@ -16,6 +16,7 @@ import json
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from portfolio_optimiser.ingest import Extraction, FileSource, HttpSource, ManifestV1, SqlSource
|
||||||
from portfolio_optimiser.verdicts import ProposalFeatures, capture_verdict, verdict_to_dict
|
from portfolio_optimiser.verdicts import ProposalFeatures, capture_verdict, verdict_to_dict
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
@ -172,3 +173,43 @@ def test_ingest_spec_exists_with_required_structure() -> None:
|
||||||
assert "promoted-verdict-" in text, "ingest spec must state the reserved filename namespace"
|
assert "promoted-verdict-" in text, "ingest spec must state the reserved filename namespace"
|
||||||
# The determinism anchor: the explicit-timestamp rule must be visible at field level.
|
# The determinism anchor: the explicit-timestamp rule must be visible at field level.
|
||||||
assert "`ingested_at`" in text, "ingest spec must document the explicit timestamp field"
|
assert "`ingested_at`" in text, "ingest spec must document the explicit timestamp field"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ingest_spec_documents_every_contract_field() -> None:
|
||||||
|
"""Test 5 (I2 — discharges test 4's deferred note): every field of the REAL ingest manifest
|
||||||
|
contract is documented in the ingest spec — driven from the pydantic models' ``model_fields``
|
||||||
|
(all three polymorphic source variants + the extraction shape), the seven §5/§7 provenance
|
||||||
|
frontmatter keys, and the §11 golden-case entries. Mirrors test 3: the test goes RED the
|
||||||
|
moment the contract code and the spec's §12 cross-check table drift apart (a new model field
|
||||||
|
without a spec entry, a renamed frontmatter key, a changed golden layout)."""
|
||||||
|
text = INGEST_SPEC_PATH.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def documented(field: str, source: str) -> None:
|
||||||
|
assert f"`{field}`" in text, f"ingest spec does not document {source} field `{field}`"
|
||||||
|
|
||||||
|
for field in ManifestV1.model_fields:
|
||||||
|
documented(field, "manifest top-level")
|
||||||
|
for model, source in (
|
||||||
|
(FileSource, "file source"),
|
||||||
|
(SqlSource, "sql source"),
|
||||||
|
(HttpSource, "http source"),
|
||||||
|
(Extraction, "extraction"),
|
||||||
|
):
|
||||||
|
for field in model.model_fields:
|
||||||
|
documented(field, source)
|
||||||
|
|
||||||
|
# The §5/§7 provenance layer — exactly the keys the materializer stamps.
|
||||||
|
for key in (
|
||||||
|
"type",
|
||||||
|
"title",
|
||||||
|
"source_system",
|
||||||
|
"source_query",
|
||||||
|
"ingested_at",
|
||||||
|
"ingest_manifest",
|
||||||
|
"generated",
|
||||||
|
):
|
||||||
|
documented(key, "provenance frontmatter")
|
||||||
|
|
||||||
|
# The §11 golden extraction case layout.
|
||||||
|
for entry in ("manifest.json", "fixture/", "ingested-at.txt", "expected-bundle/"):
|
||||||
|
documented(entry, "golden extraction case")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue