feat(inbox): root frontmatter emission at Door B with a caller-owned bundle_id

This commit is contained in:
Kjell Tore Guttormsen 2026-09-01 00:04:09 +02:00
commit 224121f762
2 changed files with 214 additions and 2 deletions

View file

@ -21,13 +21,14 @@ from __future__ import annotations
import hashlib
import unicodedata
from collections.abc import Callable
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from pathlib import Path
from .errors import IngestError, MaterializationError, SourceError
from .extract import extract_text
from .materialize import (
_render_root_frontmatter,
check_filename_length,
link_in_index,
parse_frontmatter,
@ -244,6 +245,7 @@ def process_inbox(
okf_type: str,
gate: Gate,
profile: BundleProfile = DEFAULT,
root_frontmatter_values: Mapping[str, str] | None = None,
) -> InboxResult:
"""Convert every file dropped in `inbox_dir` into an OKF concept.
@ -261,6 +263,12 @@ def process_inbox(
a reserved `okf_type`, and a missing inbox directory.
"""
validate_ingested_at(ingested_at)
# Rendered HERE, before anything is read or written, and the result carried
# to the index write at the bottom. `_render_root_frontmatter` refuses a key
# the policy does not name, and a refusal must leave no bundle behind --
# `materialize.py` states the same rule for Door A, and a door that half-built
# a bundle before refusing would be worse than one that never started.
root_head = _render_root_frontmatter(root_frontmatter_values or {}, profile=profile)
run_rejection = profile.types.rejection(okf_type)
if run_rejection is not None:
raise MaterializationError(f"okf_type {run_rejection.reason}", code=run_rejection.code)
@ -398,7 +406,9 @@ def process_inbox(
if persisted:
index_path = bundle / profile.index.name
if not index_path.is_file():
write_bytes(bundle, profile.index.name, "")
write_bytes(bundle, profile.index.name, root_head)
else:
_refresh_root_frontmatter(index_path, root_head)
if profile.index.facets is None:
for entry in persisted:
link_in_index(
@ -436,6 +446,36 @@ def _validate_facets(structure: DocumentStructure, profile: BundleProfile) -> No
raise MaterializationError(str(exc), code="index_facet_invalid") from exc
def _refresh_root_frontmatter(index_path: Path, head: str) -> None:
"""Put `head` at the top of an existing index, replacing any block already there.
Idempotent by construction: the leading block is removed and the current one
written, so applying this twice produces the same bytes. That is what makes a
second round with the same `bundle_id` a no-op and a rebuild-from-scratch
byte-identical to an incremental update -- neither is diffed against the
other, both are the same function of the same inputs.
The block is recognised the way `parse_frontmatter` recognises one: an
opening `---` on the very first line, up to the next `---`. Nothing else is
read, because a value inside the block is the caller's and this door only
ever restates it.
"""
body = index_path.read_bytes().decode("utf-8")
lines = body.splitlines(keepends=True)
if lines and lines[0].strip() == "---":
for position, line in enumerate(lines[1:], start=1):
if line.strip() == "---":
rest = lines[position + 1 :]
# The blank line the block is separated by belongs to the block.
if rest and rest[0].strip() == "":
rest = rest[1:]
body = "".join(rest)
break
if body == head:
return
index_path.write_bytes((head + body).encode("utf-8"))
def _reproject_index(bundle: Path, profile: BundleProfile) -> None:
"""Rewrite the managed region of the index from the WHOLE bundle.