From 676c11a498c4fb15f2101ac2bfcabddd76e06eb0 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 3 Jul 2026 18:33:56 +0200 Subject: [PATCH] feat(ingest): index generation and stamped-replacement semantics (I2) --- src/portfolio_optimiser/ingest.py | 87 ++++++++++++++++++++++++- tests/test_ingest_materialize.py | 102 ++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) diff --git a/src/portfolio_optimiser/ingest.py b/src/portfolio_optimiser/ingest.py index 4437e95..02fa2c8 100644 --- a/src/portfolio_optimiser/ingest.py +++ b/src/portfolio_optimiser/ingest.py @@ -50,6 +50,10 @@ _INGESTED_AT_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$") _LOGGER = logging.getLogger("portfolio_optimiser.ingest") +# §6: index.md is managed by the materializer (and reserved — the extraction-id grammar +# keeps generated names disjoint from it by construction). +_INDEX_NAME = "index.md" + class IngestError(RuntimeError): """A materialization-time refusal (cap exceeded, curated collision, malformed source).""" @@ -242,6 +246,49 @@ def _write_bytes(bundle_dir: Path, name: str, content: str) -> Path: return resolved +def _is_ingest_owned(path: Path) -> bool: + # §3/§5 ownership: the ingest stamp is `generated: true` AND an `ingest_manifest` + # reference. parse_frontmatter returns STRINGS ("true", never booleans). Promoted + # verdict files carry neither key, so they can never classify as ingest-owned. + frontmatter = okf.parse_frontmatter(path) + return frontmatter.get("generated") == "true" and "ingest_manifest" in frontmatter + + +# One managed index line: `- [